守护进程,也就是通常所说的Daemon进程(又称精灵进程),是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。Linux系统有很多守护进程,大多数服务都是通过守护进程实现的,如作业规划进程crond、打印进程lqd等。
在Linux终端执行进程时,当终端被关闭时,相应的进程都会自动关闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才会退出。如果想让某个进程不因为用户、终端、或其他的变化而受到影响,那么就必须把这个进程变成一个守护进程。
(1)守护进程的编写步骤
① 创建子进程,父进程退出
首先调用fork,然后使父进程调用exit函数退出,这一步给Shell进程造成程序运行完成的假象,让Shell进程把自己提到会话前台,此时用户在Shell终端里可以执行其他命令,从而让程序形式上脱离了控制终端。
由于父进程已经先于子进程退出,会造成子进程没有父进程,此时子进程变成一个孤儿进程。在Linux中,每当系统发现一个孤儿进程,就会自动由1号进程(init进程)收养它,这样,子进程就会成了init进程的子进程。
② 调用setsid创建一个新会话
在第1步调用fork函数后,子进程几乎是父进程的副本,虽然父进程退出了,但会话ID、进程组ID等并没有改变,因此,还不是真正意义上脱离控制终端,而setsid函数能够使进程完全独立出来。
调用setsid函数后,该子进程成为新会话的首进程,成为一个新进程组的首进程而且没有控制终端。
③ 再次fork确保子进程不是会话首进程
此进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。可以通过使进程不再是会话组长来禁止进程重新打开控制终端,所以再次fork使父进程退出,子进程确保不是会话首进程,子进程将永远不会重获控制终端。
④ 调用chdir函数改变当前工作目录
使用fork创建的子进程继承了父进程的当前工作目录。调用函数chdir("/")将当前工作目录更改为根目录,这样进程不使用任何目录,否则守护进程可能一直占用某个目录,这可能会造成超级用户不能卸载一个文件系统。
⑤ 重设文件权限掩码
由于使用fork系统调用新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性,设置方法为umask(0)。
⑥ 关闭文件描述符
由于子进程从其父进程继承来的某些文件描述符,这些文件描述符通常需要关闭,究竟关闭哪些描述符则与具体的守护进程有关,文件描述符0、1和2所指的这3个文件已经失去了存在的价值,一般应被关闭。
⑦ 重定向0、1、2三个文件描述符
出于安全以及健壮性考虑,即使当前进程不使用stdin、stdout、stderr,也应重新打开0、1、2三个文件描述符,使之指向/dev/null。当然,也可以根据需要使之对应不同的(伪)文件。总之,最好保持0、1、2三个文件描述符呈现打开状态,并使之指向无害文件。
⑧ 处理SIGCHLD信号
处理SIGCHLD信号并不是必需的。但对于某些进程(如并发服务器进程),往往要在客户端请求到来时fork子进程来处理请求,如果父进程不等待子进程结束,那么子进程将成为僵尸进程(zombie),而父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。这时可以简单将SIGCHLD信号操作设为SIG_IGN(表示忽略信号)来避免僵尸进程。设置方法如下:
signal(SIGCHLD,SIG_IGN);
(2)守护进程代码举例
dameon.c源代码如下:
/*创建守护进程实例*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
/*进程打开最多文件数,根据内核参数有关*/
#define MAXFILE 512
int main()
{
pid_t pc;
int i,fd,len;
int j ;
/*第一步,fork进程,父进程退出*/
pc=fork();
if(pc<0){
printf("error fork\n");
exit(1);
}else if(pc>0){
exit(0);
}
/*第二步,设置新的会话进程,摆脱原终端会话的控制*/
setsid();
/*第三步,再次fork,使之成为无会话的进程*/
pc=fork();
if(pc<0){
printf("error fork\n");
exit(1);
}else if(pc>0){
exit(0);
}
/*第四步,设置工作路径*/
chdir("/");
/*第五步,设置文件掩码*/
umask(0);
/*第六步,关闭继承的打开文件描述符*/
for(i=0;i<MAXFILE;i++)
close(i);
/*第七步,重定向0、1、2文件描述符到零设备*/
j = open("/dev/null", O_RDWR );
dup2( j, 0 );
dup2( j, 1 );
dup2( j, 2 );
/*第八步,忽略SIGCHLD信号*/
signal(SIGCHLD,SIG_IGN);
/*这时创建完守护进程,以下开始正式进入守护进程工作*/
while(1){
/*处理内容*/
sleep(3) ;
}
return 0 ;
}
编译 gcc dameon.c -o dameon。
执行./dameon,用ps -ef|grep dameon查看守护进程的运行,运行结果如下:
zjkf 7967 1 88 15:38 ? 00:00:09 ./dameon
摘录自《深入浅出Linux工具与编程》