在创建守护进程之前,我们先把创建守护进程所涉及到的几个概念讲清楚
然后带大家创建守护进程
终端
- 在UINIX系统中,用户通过终端登录系统得到一个shell进程,这个终端成为shell进程的控制终端,进程中控制终端是保持在PCB中的信息,而fork()会复制PCB中的信息,因此由shell进程启动的其他进程控制终端也是这个终端。
- 默认情况下(没有重定向),每一个进程的标准输入,标准输出和标准错误都是指向控制终端,进程从标准输入读也就是用户的键盘输入,进程往标准输出或标准错误输出到显示器上
- 在控制终端输入一些特殊的控制键可以给前台进程发信号。例如:ctrl+c发送SIGINT
其实对于终端的概念,知道这些已经足够了,为了更清除的终端,我们再涉猎一些内容吧!
终端的启动流程
每一个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端。事实上每一个终端设备都对应一个不同的设备文件,/dev/tty提供了一个通用的接口,一个进程要访问它的控制终端既可以通过/dev/tty,也可以通过该终端设备所对应的设备文件来访问。
硬件驱动程序负责读写实际的硬件设备,比如从键盘读入字符和把字符输出到显示器,线路规程像一个过滤器,对于某些特殊字符并不是让它直接通过,而是做特殊处理,比如在键盘按下ctrl+Z,对应的字符并不会被read读到,而是被线路规程截获,解释成SIGTSTP信号发送给前台进程,通常会终止该进程。(线路规程:用来过滤键盘输入的内容)
网络中断
图形终端和网络终端的数目是没有限制的,通过伪终端实现。一套伪终端由一个主设备和一个从设备组成。主设备在概念上相当于键盘和显示器,只不过它不是正真的硬件设备而是一个内核模块,操作它的也不是用户而是另一个进程。网络终端或图形终端窗口的sehll进程以及它启动的其他进程都会认为自己的控制终端是伪终端从设备。
进程组
- 进程组,也称之为作业。代表一个或多个进程的集合。每一个进程都属于一个进程组,操作系统设计的进程组的概念是为了简化多个进程的管理。
- 进程组由一个或多个共享同一个进程组标识符(pgid)的进程组成。一个进程组拥有一个进程组首进程(组长),该进程是创建该组的进程,其进程ID为该进程组的ID,新进程会继承其父进程所属的进程组ID。
- kill -9 进程组ID(负) 将该组进程中的所有进程杀死
- 进程组拥有一个声明周期,其开始时间为创建组的时刻,结束时间为最后一个成员进程退出组的时刻。一个进程可能会因为终止而退出,也可能会因为加入了另一个进程而退出进程组。进程组首进程(组长)无需是最后一个离开进程组的成员。
进程组操作函数
//获取当前进程的进程组ID
pid_t getpgrp(void);
//获取指定进程的进程组ID pid=0与getpgrp作用相同
pid_t getpgid(pid_t pid);
//改变进程默认所属进程组。通常可以用来加入或创建一个进程组
int setpgid(pid_t pid,pid_t pgid);
1.如果改变子进程为新的组,应fork后,exec前
2.权级问题:非root用户只能改变自己创建的子进程,或有权限操作的进程
会话
- 会话是一组进程组的集合。会话首进程是创建该新会话的进程,其进程ID会成为会话ID。新进程会继承其父进程的会话ID.
- 一个会话中的所有所有进程共享单个控制终端。控制终端会在会话首先进程首次打开一个终端设备时被建立。一个终端最多会成为一个会话的控制终端。
- 在任一时刻,会话中的其中一个进程组会成为终端的前台进程组,其他进程组会成为后台进程组。只有前台进程组中的过程才能从控制终端中读取输入。当用户在控制终端中输入终端字符生成信号后,该信号会被送到前台进程组中的所有成员。
- 当控制终端的连接建立起来之后,会话首进程会成为该终端的控制进程。
操作会话的函数
pid_t getsid(pid_t pid); //获取进程所属的会话ID
pid为0表示察看当前进程 session ID
ps ajx命令查看系统中的进程。参数a表示不仅列当前用户进程,也列出所有其他用户的进程。x表示不仅列出控制终端的进程,也列出所有无法控制终端的进程,参数j表示列出与作业控制相关的信息。
pid_t setsid(void); //创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID
守护进程
守护进程(Daemon Process),也就是通常说的Daemon进程(精灵进程),是Linux中的后台服务进程(后台进程是没有拿到控制终端的)。它是一个长期的进程,通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。一般采用d结尾的名字。
守护进程的特征:
- 生命周期很长,守护进程会在系统启动的时候被创建并一直运行直至系统被关闭
- 它在后台运行并且不拥有控制终端。没有控制终端确保了内核永远不会为守护进程自动生成任何控制信号以及终端相关的信号(脱离终端控制)
- Linux的大多数服务器就是用守护进程实现的。比如:Internet服务器、web服务器...
守护进程创建模型
1.创建子进程,父进程退出
所有工作在子进程中进行形式上脱离了控制终端
2.在子进程中创建新会话
setsid()函数
使子进程完全独立出来,脱离控制
3.改变当前目录为根目录
chidr()函数
防止占用可卸载的文件系统,也可以换成其他路径
4.重设文件权限掩码
umask()函数
防止继承的文件创建屏蔽字拒绝某些权限,增加守护进程灵活性
5.关闭文件描述符
重定向dup2()
继承的打开文件不会用到,浪费系统资源,无法卸载
6.开始执行守护进程核心工作
7.守护进程退出
#include <iostream> #include <sys/types.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> using namespace std; void daemond() { //父进程创建子进程,然后退出,形式上脱离控制终端 pid_t pid = fork(); if(pid > 0) { exit(0); } //子进程创建一个新的会话 setsid(); //改变当前目录为根目录 chdir("/"); //设置umask掩码,防止从父进程继承的过来的屏蔽字拒绝某些权限 umask(0); //关闭文件描述符,节省系统资源 //STDIN_FILENO就是标准输入设备(一般是键盘)的文件描述符 //写入/dev/null的东西会被系统丢掉 close(STDIN_FILENO); //再一次的保护 open("/dev/null",O_RDWR); dup2(0,1); dup2(0,2); //守护进程的核心逻辑 //退出 } int main(void) { daemond(); while(1); return 0; }