守护进程(Daemon)是运行在后台的一种特殊进程。守护进程(Daemon)是一种运行在后台的一种特殊的进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。由于在Linux中,每个系统与用户进行交流的界面成为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端被称为这些进程的控制终端,当控制终端被关闭的时候,相应的进程都会自动关闭。但是守护进程却能突破这种限制,它脱离于终端并且在后台运行,并且它脱离终端的目的是为了避免进程在运行的过程中的信息在任何终端中显示并且进程也不会被任何终端所产生的终端信息所打断。它从被执行的时候开始运转,知道整个系统关闭才退出(当然可以认为的杀死相应的守护进程)。如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程
Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond,打印进程lpd等。(这里的结尾字母d就是Daemon的意思)
创建步骤:
①调用fork,然后终止父进程,留下子进程继续运行。
如果本进程是从前台作为一个shell命令启动的,当父进程终止时,shell就认为该命令已执行完毕。这样子进程就自动在后台运行。另外,子进程继承了父进程的进程组ID,不过它有自己的进程ID,就保证就、了子进程不是一个进程组的头进程,这是接下去调用setsid的必要条件
if((pid = fork())>0)
exit(0);
else if(pid<0)
{
perror("fail to fork");
exit(-1);
}
②脱离控制终端,登录会话和进程组(创建新会话)
setsid是一个POSIX函数,用于创建一个新对话。这时调用进程:称为会话首进程,成为一个新进程组的组长进程,没有控制终端
当调用setsid时通常发生以下三件事:
a. 该进程变成新会话的会话首进程(会话首进程是创建该会话的进程)。此时,该进程是新会话中的唯一进程。
b. 该进程成为一个新进程的组长进程,新进程组ID是调用该进程的进程ID
c. 该进程没有控制终端,如果在调用setsid之前该进程有一个控制终端,那么这种联系也被切断。
有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。 控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长: setsid();
说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。
setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进
程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
③ 忽略SIGHUP信号
忽略SIGHUP信号,因为会话头进程终止时,其会话中的所有进程都收到SIGUP信号
signal(SIGCHLD,SIG_IGN)
1、终端关闭时,该信号被发送到session首进程以及作为job提交的进程(即用 & 符号提交的进程)
2、session首进程退出时,该信号被发送到该session中的前台进程组中的每一个进程
3、若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到SIGSTOP或SIGTSTP信号),该信号会被发送到该进程组中的每一个进程。
系统对SIGHUP信号的默认处理是终止收到该信号的进程。所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出。
④再次fork, 禁止进程重新打开控制终端
该函数返回时,父进程实际上是上一次调用fork产生的子进程,他被终止掉,留下新的子进程继续运行,再次fork的目的是确保本守护进程将来即使打开了一个终端设备,也不会自动获得控制终端。当没有控制终端的一个会话头进程打开一个终端设备时,该终端自动成为这个会话首进程的控制终端。然而再次调用fork之后,确保了新的子进程不再是一个会话首进程,从而不能自动获得一个控制终端。 这里忽略SIGHUP信号,因为会话头进程终止时,其会话中的所有进程都收到SIGUP信号。
if(pid=fork())
exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
⑤改变当前工作目录
把工作目录改到根目录,因为守护进程可能是在某个任意的文件系统中启动,如果仍然在其中,那么文件系统就无法拆卸,除非使用潜在的破坏性的强制措施。
chdir("/tmp") ;
⑥关闭所有文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误:for(i=0;i<=getdtablesize();i++)
close(i);
进程从创建它的父进程那里继承了文件创建掩码。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:
umask(0);
/*name: init_deamon.c
*function:创建一个守护进程
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
void init_deamon(void)
{
int pid;
int i;
if(pid=fork())
exit(0);//是父进程,结束父进程
else if(pid< 0){
perror("fail to fork1");
exit(1);//fork失败,退出
}
//是第一子进程,后台继续执行
setsid();//第一子进程成为新的会话组长和进程组长
/* 处理SIGUP信号*/
if(signal(SIGCHLD,SIG_IGN) == SIG_ERR){
printf("Cant signal in init_daemon.");
exit(1);
}
//并与控制终端分离
if(pid=fork())
exit(0);//是第一子进程,结束第一子进程
else if(pid< 0)
exit(1);//fork失败,退出
//是第二子进程,继续
//第二子进程不再是会话组长
chdir("/");//改变工作目录到/
for(i=0;i< getdtablesize();++i)//关闭打开的文件描述符
close(i);
umask(0);//重设文件创建掩模
return;
}
/* name :test.c
* function :调用init_deamon函数使进程变成守护进程,然后每个一秒向/tmp目录下的print_time文件打印当前时间
* */
#include <stdio.h>
#include <time.h>
void init_deamon(void);//守护进程初始化函数
void main()
{
FILE *fp;
time_t t;
init_deamon();//初始化为Daemon
while(1)//每隔一分钟向test.log报告运行状态
{
sleep(1);//睡眠一秒钟
if((fp=fopen("print_time","a")) >=0)
{
t=time(0);
fprintf(fp,"The time right now is : %s",asctime(localtime(&t)));
fclose(fp);
}
}
return;
}
测试: