守护进程(Daemon)是一种运行在后台的一种特殊的进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。由于在linux中,每个系统与用户进行交流的界面成为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端被称为这些进程的控制终端,当控制终端被关闭的时候,相应的进程都会自动关闭。但是守护进程却能突破这种限制,它脱离于终端并且在后台运行,并且它脱离终端的目的是为了避免进程在运行的过程中的信息在任何终端中显示并且进程也不会被任何终端所产生的中断信息所打断。它从被执行的时候开始运转,知道整个系统关闭才退出(当然可以认为的杀死相应的守护进程)。如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程。
(1)修改文件模式创建屏蔽字:
(2)调用fork,然后使父进程exit:
针对第一点,由于守护进程是要脱离终端的,如果该守护进程是作为一条简单的shell命令启动的,那么父进程的终止会让shell认为这条命令执行完毕,这样既可以让用户可以在shell终端里执行其它命令,让可以让子进程脱离终端的控制。
(3)调用setsid创建一个新会话:
进程组:是一个或多个进程的集合。进程组有进程组ID来唯一标识。除了进程号(PID)之外,进程组ID也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID。且该进程组ID不会因组长进程的退出而受到影响。
会话周期:会话期是一个或者多个进程的集合。通常一个会话开始于用户的登录,终止于用户的退出,在此期间该用户运行的所有进程都属于这个会话期。
调用setid()函数的作用:
(a) 创建一个新的会话,并且担任该会话组的组长。具体作用包括:让一个进程摆脱原会话的控制,让进程摆脱原进程的控制,让进程摆脱原控制终端的控制。
(b) 创建守护进程要调用setsid()函数的原因:由于创建守护进程的第一步是调用fork()函数来创建子进程,再将父进程退出。由于在调用了fork()函数的时候,子进程拷贝了父进程的会话期、进程组、控制终端等资源、虽然父进程退出了,但是会话期、进程组、控制终端等并没有改变,因此,需要用setsid()韩式来时该子进程完全独立出来,从而摆脱其他进程的控制。
(4)将当前工作目录改为根目录:
使用fork()创建的子进程是继承了父进程的当前工作目录,由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后使用会造成很多的麻烦。因此通常的做法是让“/”作为守护进程的当前目录,当然也可以指定其他的别的目录来作为守护进程的工作目录。改变工作目录主要是为了切断进程与原有文件系统的联系。并且保证无论从什么地方启动进程都能正常的工作。清除文件掩码是为了消除进程自身掩码对其创建文件的影响。
(5)关闭不再需要的文件描述符:
(6)将stdin,stdout,stderr重定向到/dev/null:
下面我们就用《unix环境高级编程》书中的例子来演示守护进程的创建过程:
#include "apue.h"
#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <iostream>
using namespace std;
int daemon(){
int i, fd0, fd1, fd2;
FILE *fp;
pid_t pid;
struct rlimit rl;
struct sigaction sa;
umask(0);
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)printf(" can't get file limit\n");//get the maximum number of file descriptors
/* Become a session leader to lose controlling TTY.*/
if ((pid = fork()) < 0)printf("can't fork\n");
else if (pid != 0)exit(0);/* parent */
setsid();
/* Ensure future opens won't allocate controlling TTYs.*/
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)printf("can't ignore SIGHUP\n");// set SIGHUP ignored
if ((pid = fork()) < 0)printf("can't fork\n");
else if (pid != 0)exit(0); /* parent */
/* Change the current working directory to the root sowe won't prevent file systems from being unmounted.*/
if (chdir("/") < 0)printf("can't change directory to /n");
cout<<"the process id is: "<<getpid()<<endl;
/* Close all open file descriptors.*/
if (rl.rlim_max == RLIM_INFINITY)rl.rlim_max = 1024;
for (i = 0; i < rl.rlim_max; i++)close(i);
/* Attach file descriptors 0, 1, and 2 to /dev/null.*/
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);
fp=fopen("/home/caoyan/unix/daemon/tmp.txt","a");
fprintf(fp,"the fds are:%d,%d,%d",fd0,fd1,fd2);
/* Initialize the log file.*/
if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
printf("unexpected file descriptors %d %d %d\n",fd0, fd1, fd2);
exit(1);
}
return 0;
}
int main(){
FILE *fp;
time_t t;
daemon();
while(1){
sleep(2);
if((fp=fopen("/home/caoyan/unix/daemon/time.txt","a")) >=0){
t=time(0);
fprintf(fp,"The time right now is : %s",asctime(localtime(&t)));
fclose(fp);
}
}
return 0;
}
在调用setsid()之后,子进程成了新会话的会话首进程,之后要屏蔽掉SIGHUP。因为在将会话首进程exit之后,其SIGHUP信号会被发送到进程组中的所有进程,进程组中的进程收到该信号之后的默认操作是退出,显然对于守护进程来说,这不是我们所需要的,所以要屏蔽它!!!!