【原文】https://www.toutiao.com/i6566814959966093837/
Linux守护进程
一、 守护进程概述
守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。守护进程常常在系统启动时开始运行,在系统关闭时终止。
Linux系统有很多守护进程,大多数服务都是用守护进程实现的。例如常见的常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。
二、进程与终端
在Linux中,每一个系统与用户进行交流的界面称为终端。从该终端开始运行的进程都会依附于这个终端,这个终端称为这些进程的控制终端。当控制终端被关闭时,相应的进程都会被自动关闭。
守护进程能够突破这种限制,它从开始运行,直到整个系统关闭才会退出。如果想让某个进程不会因为用户或终端的变化而受到影响,就必须把这个进程变成一个守护进程。
三、 查看守护进程
命令: ps axj
父进程ID : PPID
进程ID : PID
进程组ID : PGID
会话期ID : SID
终端ID : TTY
终端进程组ID : TPGID
状态 : STAT
用户 : UID
运行时间 : TIME
指令: COMMAND
四、 Linux守护进程编写(五步)
1. 创建子进程,父进程退出
第一步完成以后,子进程就在形式上做到了与控制终端的脱离
由于父进程已经先于子进程退出,子进程变成孤儿进程
pid = fork(); if (pid > 0) /*父进程退出*/ { exit(0); }由于守护进程是脱离控制终端的,因此,完成第一步后就会在shell终端里造成一程序已经运行完毕的假象。之后的所有后续工作都在子进程中完成,而用户在shell终端里则可以执行其他的命令,从而在形式上做到了与控制终端的脱离
由于父进程已经先于子进程退出,会造成子进程没有父进程,从而变成一个孤儿进程。在Linux中,每当系统发现一个孤儿进程,就会自动由1号进程收养。原先的子进程就会变成init进程的子进程。
2. 在子进程中创建新会话
setsid()函数作用
setsid函数用于创建一个新的会话,并使得当前进程成为新会话组的组长setsid函数能够使进程完全独立出来,从而脱离所有其他进程的控制。
进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长。
进程组: 一个或多个进程的集合。进程组由进程组ID来唯一标识。每个进程组都有一个组长进程,进程组ID就是组长进程的进程号。
会话期: 会话组是一个或多个进程组的集合
进程组 对话期 终端
3. 修改当前工作目录
chdir(“/tmp”);
通常的做法是让“/”或”/tmp”作为守护进程的当前工作目录 。
在进程运行过程中,当前目录所在的文件系统是不能卸载的。
chdir函数可以改变进程当前工作目录
4. 重设置文件权限掩码
umask(0);
文件权限掩码是指文件权限中被屏蔽掉的对应位。把文件权限掩码设置为0,可以增加该守护进程的灵活性。设置文件权限掩码的函数umask();
5. 关闭文件描述符
fdtablesize = getdtablesize();
for (fd = 0; fd < fdtablesize; fd++)
close(fd);
新建的子进程会从父进程那里继承所有已经打开的文件。在创建完新的会话后,守护进程已经脱离任何控制终端,应当关闭用不到的文件。这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸载
从终端输入的字符不可能达到守护进程,守护进程中用常规的方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2的三个文件(对应标准输入、标准输出和标准错误这三个流)已经失去了存在的意义,也应被关闭。
五、 应用代码举例
创建一个守护进程每隔1s向daemon.txt文件中写入一次系统时间。
#include int main() { pid_t pid; int num,i; int fd; time_t t; char buf[100]; pid = fork(); //第一步:创建子进程,父进程结束(子进程有init进程托管); if(pid == 0){ //第二步:创建新会话,脱离原来的进程,脱离控制终端,脱离原来的进程组; setsid(); //第三步:修改当前目录,每一个进程都有一个当前目录;(不是必须的) chdir("/tmp"); //第四步:重新设置文件权限掩码(不是必须的) umask(0); //第五步:关闭打开的文件描述符(如果父进程打开的文件,子进程会继承过来) num = getdtablesize(); //获得当前打开的文件描述符表 for(i = 0; i < num; i++){ close(i); } fd = open("daemon.txt",O_WRONLY | O_CREAT ,0666); while(1){ t = time(0); sprintf(buf,"time: %s ",ctime(&t)); write(fd,buf,strlen(buf)); sleep(1); } }else if(pid > 0){ exit(0); } return 0; }