守护进程,脱离于终端,可避免被任何的终端信号所打断,常用于服务器程序。我们用ssh远程登录,可能做的事情就是启动某个程序,如果不是守护进程,在我们退出终端时,进程立刻被杀死了,因为由终端启动的进程是tty的子进程。可用cron或nohup来启动。另外的启动方式有1.在系统启动时由启动脚本启动,通常放在/etc/rc.d/.2.利用inetd超级服务器启动,如telnet等。
守护进程编程要点
1.屏蔽一些有关控制终端的信号。signal(SIGTTOU,SIG_IGN);
2.让子进程在后台运行,父进程退出。
if(pid=fork()) exit(0);
3.脱离控制终端和进程组setsid(),调用此函数的进程成为新的会话组长和新的进程组长,并与原来的进程组脱离关系。
4.禁止进程重新打开控制终端,进程已经成为无终端的回话组长和新的进程组长,但他可以重新申请打开一个控制终端,采用的办法是再次创建一个子进程,并让父进程退出。
5.关闭打开的文件描述符包括标准输入输出,以节省资源。
#define NOFILE 256
for(i=0;i<NOFILE;i++) close(i);
6.写日志的话需要改变当前工作目录,如chdir("/tmp");
7.重设文件创建掩码umask(0);
8.处理SIGCHLD信号,对于某些需要在请求到来时让子进程处理的一种方式是父进程等待子进程结束,但增加父进程的负担,影响并发性能。另一种简单方式是将子进程退出的SIGCHLD的操作设为SIG_IGN方式,让系统帮助回收僵死进程资源。signal(SIGCHLD,SIG_IGN)
一下是一个简单的守护进程示例:
#include<unistd.h> #include<signal.h> #include<fcntl.h> #include<sys/syslog.h> #include<sys/param.h>//包含NOFILE的宏定义 #include<sys/types.h> #include<sys/stat.h> #include<stdio.h> #include<stdlib.h>
int init_daemon(const char *pname,int facility){ int pid; int i; signal(SIGTTOU,SIG_IGN);//处理可能的终端信号 signal(SIGTTIN,SIG_IGN); signal(SIGTSTP,SIG_IGN); signal(SIGHUP,SIG_IGN); if(pid==fork()) exit(0); else if(pid<0){ perror("fork"); exit(-1); } setsid();//设置新会话组长,新进程组长,脱离终端 if(pid=fork()) exit(0);//子进程不能再申请终端 else if(pid<0){ perror("fork"); exit(-1); } for(i=0;i<NOFILE;i++) close(i); open("/dev/null",O_RDONLY); open("/dev/null",O_RDWR);//描述符0,1,2都定向到null open("/dev/null",O_RDWR); chdir("/tmp");//修改主目录
umask(0);//重置文件掩码
signal(SIGCHLD,SIG_IGN);
openlog(pname,LOG_PID,facility);
//与守护进程建立联系,加上进程号,文件名
return ; } int main(int argc,char*argv[]){ FILE* fp; time_t ticks; init_daemon(argv[0],LOG_KERN); while(1){ sleep(1); ticks=time(0);//time(NULL);
syslog(LOG_INFO,"%s",(char*)asctime(localtime(&ticks))); } }
在日志中记录当时的本地时间,可以通过cat /var/log/syslog |grep 进程id 来查看。
在Linux中专门提供了一个函数来完成这个daemon化的过程,这个函数的原型如下
int daemon (int __nochdir, int __noclose);
如果__nochdir的值为0,则将切换工作目录为根目录;如果__noclose为0,则将标准输入,输出和标准错误都重定向到/dev /null。
经过这个函数调用后的程序将运行在后台,成为一个daemon程序,而linux下大多的服务都是以此方式运行的。
我们来看一个简单的例子。例如编写例子程序test.c
#include <unistd.h>
#include <stdio.h>
int do_sth()
{
return 0;
}
int main()
{
daemon(0,0);
while ( 1 )
{
do_sth();
sleep(1);
}
}
编译并运行
$ gcc -o test test.c
$ ./test
程序进入了后台,通过ps查看进程情况,可以看到进程的父进程id为1,即init进程
用lsof查看test进程所打开的文件,可以看到文件描述符0,1,2都被重定向到/dev/null
并且能够看到,进程的当前工作目录(cwd)为根目录/,daemon函数已经帮我们完成了daemon化的过程,接下来我们只需要关注于程序功能 的实现了。