转载:http://blog.chinaunix.net/uid-25365622-id-3055635.html
概念:
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进 程。Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任 务。比如,作业规划进程crond,打印进程lpd等。(这里的结尾字母d就是Daemon的意思)
创建步骤:
①使进程在后台运行
创建子进程父进程退出
if((pid = fork())>0)
exit(0);
else if(pid<0)
{
perror("fail to fork");
exit(-1);
}
②脱离控制终端,登录会话和进程组(创建新会话)
有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号 (PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。 控制终端,登录会话和进程组通常是从父 进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长:
setsid();
说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。
说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。
setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进
程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
③禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:
if(pid=fork())
exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
④关闭所有文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误:
for(i=0;i<=getdtablesize();i++)
close(i);
⑤改变当前工作目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如/tmp:
chdir("/tmp") ;
⑥重设权限掩码
进程从创建它的父进程那里继承了文件创建掩码。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:
umask(0);
⑦处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为
僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将
SIGCHLD信号的操作设为SIG_IGN。
signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。
无代码无真相
代码说明:
init_deamon.c:按照送上面步骤创建守护进程
test.c:调用创建守护进程函数,每隔一秒向/tmp目录下的print_time文件打印系统时间
- /*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;
- /* 处理SIGCHLD信号。处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。*/
- if(signal(SIGCHLD,SIG_IGN) == SIG_ERR){
- printf("Cant signal in init_daemon.");
- exit(1);
- }
- if(pid=fork())
- exit(0);//是父进程,结束父进程
- else if(pid< 0){
- perror("fail to fork1");
- exit(1);//fork失败,退出
- }
- //是第一子进程,后台继续执行
- setsid();//第一子进程成为新的会话组长和进程组长
- //并与控制终端分离
- if(pid=fork())
- exit(0);//是第一子进程,结束第一子进程
- else if(pid< 0)
- exit(1);//fork失败,退出
- //是第二子进程,继续
- //第二子进程不再是会话组长
- for(i=0;i< getdtablesize();++i)//关闭打开的文件描述符
- close(i);
- chdir("/tmp");//改变工作目录到/tmp
- 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;
- }
测试:
参考:http://blog.crackcell.com/posts/2012/01/13/TPLI_daemonize.html
1 概述
守护进程顾名思义就是默默运行在后台的进程。它具有以下特征:
- 父进程是init
- 没有和任何控制终端关联,所以也不会收到诸如SIGINT、SIGTSTP等信号
2 创建守护进程
2.1 步骤
- fork()创建子进程,父进程退出 这样做的2个目的:调用setsid(),创建新的session,使得调用者进程失去和控制终端的关联
- 避免进程后续打开终端设备不会成为控制终端,有2种办法:清除从祖先继承下来umask值,以便后续的创建的文件文件夹能有指定的权限
- 每次open()的时候指定参数O_NOTTY
- 再执行一次fork()并且让主进程退出,使得进程的子进程不会成为会话的leader3
- 改变工作目录(可选)
- 一般是换到/目录下。如果不这样做,有可能导致占用文件系统,使之无法卸载。
- 关闭从父进程继承的文件描述符(可选)
- 既然已经失去了控制终端,还保留0 1 2文件描述符就显得多余。
- 当还有文件描述符在某个磁盘上时,会妨碍文件系统的卸载。
- 重定向描述符(可选)
- 将0 1 2指向/dev/null
- 避免后续向这3个描述符的io操作不会发生异常
2.2 代码
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int daemonize() {
// 1. 第一次fork()
switch (fork()) {
case -1: exit(-1); // fork错误
case 0: break; // 子进程继续
default: exit(0); // 父进程退出
}
// 2. 创建新的session
setsid();
// 3. 第二次fork()
switch (fork()) {
case -1: exit(-1); // fork错误
case 0: break; // 子进程继续
default: exit(0); // 父进程退出
}
// 4. 清空umask值
umask(0);
// 5. 改变工作目录(可选)
chdir("/");
// 6. 关闭从父进程继承来的文件描述符
int maxfd = sysconf(_SC_OPEN_MAX); // 获取系统的最大打开文件数
if (-1 == maxfd) {
maxfd = 8192; // 获取失败,使用猜测的最大数
}
// 关闭所有文件描述符
for (int fd = 0; fd < maxfd; fd++) {
close(fd);
}
// 7. 重定向0 1 2
close(STDIN_FILENO);
int fd = open("/dev/null", O_RDWR);
if (fd != STDIN_FILENO) {
return -1;
}
if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO) {
return -1;
}
if (dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO) {
return -1;
}
return 0;
}
int main(int argc, char *argv[]) {
daemonize();
sleep(100);
return 0;
}
Footnotes:
1 进程组的id需要和leader进程的id保持一致。由于组id是父进程的id,故子进程id必然不会和组的id一致
2 进程组leader调用setsid(),会发生EPERM错误。进程调用setsid()时,会同时创建新的进程组,并使用调用进程的id作为新进程组 的组id。又因为进程组的组id总是组leader进程的id,若leader进程使用setsid()创建了新的session,一并创建的新的进程组 id就会和原进程组id冲突。
3 会话的id是setsid()的调用者的进程id。这里再fork()一次,会话的id就是父进程的id。则子进程的id必然不会是会话的id,进而失去 了获得控制终端的能力。详情见《The Design and Implementation of the FreeBSD Operating System》的4.8节Process Groups and Sessions