目录
守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
当Linux系统启动的时候,会启动很多系统服务,这些进程服务是没有终端的也就是说你把终端关闭了这些系统服务是不会停止的,这就是守护进程。Linux大多数服务器就是用守护进程实现的。比如,Internet 服务器 inetd,Web 服务器 httpd 等。
在终端敲如下命令:
ps? ? ? ? axj?
参数说明:
-a:表示不仅列当前用户的进程,也列出所有其他用户的进程
-x:表示不仅列有控制终端的进程,也列出所有无控制终端的进程
-j:表示列出与作业控制相关的信息
其中,我们可以发现守护进程的一些特点:
进程组,也叫做作业。代表一个或者多个进程的集合,每个进程都属于一个进程组。父进程创建子进程的时候,默认父进程和子进程同属于同一个进程组,进程组的ID等于第一个进程的ID,也就是组长进程的ID。我们在使用kill杀死进程的时候,可以通过 kill -SIGKILL -进程组ID 来杀死整个进程组内的全部进程。组长进程可以创建一个进程组,只要进程组中有一个进程存在,那么这个进程组就存在,并且即使组长进程终止也不会影响进程组。进程组的生命周期时从进程组创建到进程组最后一个进程终止或转移到另一个进程组。
创建会话的注意事项:
(1)调用进程不能是进程组组长(即父进程不能创建会话),该进程变成新会话的首进程;
(2)该进程成为一个新的进程组的组长进程,也就是说如果子进程创建了一个会话,那么子进程就脱离父进程的进程组,成为一个新进程的进程组组长;
(3)新会话丢弃原有的控制终端,该会话没有控制终端;
(4)该调用进程是组长进程,则出错返回,也就是说组长不能当会长;
(5)建立新会话时,先调用fork,父进程会终止,子进程调用setsid,也就是说,只有父进程终止了,子进程才能创建会话;
(1)进程组是由一个进程或者多个进程组成。
(2)会话是有一个或者多个进程组组成的集合
(3)创建会话的时候,不能使用进程组组长创建,必须使用组员创建。
(4)创建会话主要包括创建子进程、结束父进程、子进程做会长三步。
(1)fork()创建子进程,父进程exit()退出
????????由于守护进程是脱离控制终端的,完成这一步后就会在Shell终端里造成程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离,在后台工作。由于父进程先于子进程退出,子进程就变为孤儿进程,并由 init 进程作为其父进程收养。
(2)在子进程调用setsid()创建新会话
????????在调用了 fork() 函数后,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变。这还不是真正意义上的独立开来,而 setsid() 函数能够使进程完全独立出来。
?setsid()创建一个新会话,调用进程担任新会话的首进程,其作用有:使当前进程脱离原会话的控制;?使当前进程脱离原进程组的控制;使当前进程脱离原控制终端的控制。?这样,当前进程才能实现真正意义上完全独立出来,摆脱其他进程的控制。
(3)再次 fork() 一个子进程,父进程exit退出
????????现在,该进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。我们可以通过 fork() 一个子进程,而该子进程不是会话首进程,所以该进程将不能重新打开控制终端。然后我们再退出该父进程。也就是说通过再次创建子进程结束当前进程,使进程不再是会话首进程来禁止进程重新打开控制终端。
(4)在子进程中调用chdir()让根目录“/”成为子进程的工作目录
????????使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前目录所在的文件系统(如“/mnt/usb”)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。因此,通常的做法是让"/"作为守护进程的当前工作目录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数是chdir。(避免原父进程当前目录带来的一些麻烦)
(5)在子进程中调用umask()重设文件权限掩码为0
????????文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限(就是说可读可执行权限均变为7)。由于使用fork函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此把文件权限掩码重设为0即清除掩码(权限为777),这样可以大大增强该守护进程的灵活性。通常的使用方法为umask(0)。
(6)在子进程中close()不需要的文件描述符
????????同文件权限码一样,用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。其实在上面的第二步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2 的3个文件(常说的输入、输出和报错)已经失去了存在的价值,也应被关闭。(关闭失去价值的输入、输出、报错等对应的文件描述符)
(7)守护进程退出处理
当用户需要外部停止守护进程运行时,往往会使用 kill 命令停止该守护进程。所以,守护进程中需要编码来实现 kill 发出的signal信号处理,达到进程的正常退出。
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdio.h>
#include <stdlib.h>
int daemonize(void);
int main(int argc,char *argv[])
{
daemonize();
while(1)
printf("It is daemonize!\n");
return 0;
}
int daemonize(void)
{
int pid;
int i;
/*创建子进程、关闭父进程,使程序在后台运行*/
if( pid=fork() )
{
exit(0); /*结束父进程,子进程继续*/
}
else if(pid < 0)
{
printf("fork failure:%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
/*创建新会话,脱离控制终端*/
setsid();
/*禁止进程重新打开控制终端*/
if( pid=fork() )
{
exit(0); /*结束第一子进程,第二进程继续(第二子进程不再是会话组长)*/
}
else if(pid < 0)
{
printf("fork2 failure:%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
/*改变当前目录*/
chdir("/");
/*重设文件创建掩码*/
umask(0);
/*关闭打开的文件描述符*/
//NOFILE是<sys/param.h>的宏定义,表示文件描述符的最大个数,不同系统有不同限制
for(i=0; i<NOFILE; i++)
{
close(i);
}
return 0;
}
将上图代码运行,结果如下:
我们可以看见,刚刚创建的daemonize正在后台运行。如果我们想要关掉这个守护进程,直接 kill? 509120(PID)即可。?
最后,在这里再讲解一个知识点。Linux系统专门提供了一个用来创建守护进程的库函数,该函数的原型是:
#include? ? <unistd.h>
int? ? daemon? (int? ? nochdir? ,? int? ? noclose);
参数说明:
(1)nochdir指定是否切换当前工作路径到"/"根目录
(2)noclose指定是否要关闭标准输入、标准输出和标准出错(即重定向到/dev/null)。
在创建守护进程的时候,往往需要将进程的工作目录修改为" / "根目录,并将标准输入、标准输出和标准出错关闭。所以这两个参数我们一般都是传0.