多进程编程之守护进程Daemonize

发布时间 2023-09-11 09:32:24作者: HelloMarsMan

1、守护进程

  守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。所有的守护进程都没有控制终端,其终端名设置为问号。

2、编程规则

  1)首先调用umask函数将文件模式创建屏蔽字设置为一个已知值,通常是0;

  umask函数为进程设置文件模式创建屏蔽字,并返回以前的值。umask也是shell命令,功能和umask函数一样。

1 #include <sys/stat.h>
2 
3 mode_t umask(mode_t mask);

  在进程创建一个新的文件或目录时,如调用open函数创建一个新文件,新文件的实际存取权限是mode与umask按照  mode&~umask运算以后的结果。umask函数用来修改进程的umask。

  首先看一下umask命令的作用:

    首先查看一下当前的umask为022,用vi创建一个umask_3.c,查看该文件的权限为644,修改umask为0,vi创建umask_4.c,查看该文件的权限为666。

     

  umask函数的使用:

    实现函数,首先修改当前进程umask为0.创建fan_test1文件,然后修改umask为006,创建文件fan_test2,输出结果如下图

复制代码
 1 #include <stdio.h>
 2 #include <sys/stat.h>
 3 
 4 int main()
 5 {
 6         umask(0);
 7         if (creat("fan_test1",S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) < 0)
 8                 printf("error creat\n");
 9         umask(S_IROTH|S_IWOTH);
10         if (creat("fan_test2",S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) < 0)
11                 printf("error creat\n");
12         return 0;
13 }
复制代码

    

  2)调用fork,然后是父进程exit。创建守护进程最关键的一步是调用setsid函数创建一个新的会话(会话是一个或多个进程组的集合),使守护进程成为新会话的首进程,并成为新进程组的组长,失去当前的控制终端,成为一个没有控制终端的进程。而调用函数setsid()之前,要保证当前进程不是进程组的组长,否则该函数返回-1;要保证当前进程不是进程组的组长,就要调用fork,父进程退出;fork创建的子进程和父进程在同一个进程组中,进程组的组长必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,fork之后调用setsid就没有问题了;

  3)调用setsid()函数创建一个新的会话:

       (1) 该进程变成新会话首进程,会话首进程通常是创建该会话的进程,该进程是新会话中的唯一的进程。
       (2) 该进程成为一个新进程组的组长进程,新进程组ID就是调用进程的ID。
       (3) 该进程没有控制终端,如果在调用setsid之前该进程有一个控制终端,那么这种联系将被中断。

  4)将当前的工作目录更改为根目录。如果守护进程所在的目录为一个挂载的文件系统,那么该文件系统就不能被卸载;另外如果守护进程所在的目录不是根目录,启动守护进程之后,当前工作目录所在的文件夹将不能删除;调用函数chdir("/");

   5)关闭打开的文件描述符;子进程有可能从父进程继承了一些打开的文件,这些文件可能守护进程将不再使用,但这些文件描述符依然消耗系统资源,也有可能导致相关的文件系统无法被卸载。

  关闭文件描述符的方法:

复制代码
 1 方法1:
 2 #include <sys/resource.h>
 3     
 4 struct rlimit    rl;
 5     
 6 if(getrlimit(RLIMIT_NOFILE, &rl) < 0)
 7 {
 8   perror("getrlimit(RLIMIT_NOFILE, &rl)");
 9   return -1;
10 }
11 if(rl.rlim_max == RLIM_INFINITY)
12 {
13   rl.rlim_max = 1024;
14 }
15 for(i = 0; i < rlim_max; i++)
16 {
17   close(i);
18 }
19 方法2:
20 max_fd = sysconf(_SC_OPEN_MAX);
21 for(i = 0; i < max_fd; i++)
22 {
23     close(i);
24 }
复制代码

  6)守护进程打开/dev/null使其具有文件描述符0、1和2,也就是将0、1、2的文件描述符都指向/dev/null; 

1 /*attach file descriptions 0,1 and  2 to /dev/null*/
2 fd0 = open("/dev/null", O_RDWR);
3 fd1 = dup(0);
4 fd2 = dup(0);

  为什么会有这一步操作,守护进程已经脱离终端了,为什么还要将文件描述符0、1、2重定向到/dev/null呢,并且前面已经有了关闭所有文件描述符的操作,文件描述符已经关闭了,在此处为什么还要再打开,两者不是冲突了么?

  7)再次调用fork,使父进程退出;第一次fork()后的子进程已经成为会话组的组长,有权利再调出一个终端,如果出现此情况,则未达到完全脱离终端的目的,此时再调用fork并退出父进程,使得此时的子进程成为完全的后台进程,独立于任何的终端,在第二次fork之前通常会忽略SIGHUP信号,这是因为会话首进程退出时会给该会话中的前台进程组(当打开控制终端后,就有一个前台进程组)的所有进程发送SIGHUP信号,而信号的默认处理函数通常是进程终止,因此需要对信号进程屏蔽处理

 

实现一个守护进程:

复制代码
 1 #include <stdio.h>
 2 #include <sys/stat.h>
 3 #include <signal.h>
 4 #include <sys/resource.h>
 5 #include <unistd.h>
 6 #include <stdlib.h>
 7 void daemonize(void)
 8 {
 9     struct rlimit rl;
10     pid_t pid;
11     struct sigaction sa;
12     int i;
13     umask(0);
14     if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
15         printf("error getrlimit\n");
16         exit(0);
17     }
18     if ((pid = fork()) < 0) {
19         printf("error fork\n");
20         exit(0);
21     } else if (pid != 0) {
22         exit(0);
23     }
24     setsid();
25     sa.sa_handler = SIG_IGN;
26     sigemptyset(&sa.sa_mask);
27     sa.sa_flags = 0;
28     if (sigaction(SIGHUP, &sa, NULL) < 0) {
29         printf("error sigaction\n");
30         exit(0);
31     }
32     if (chdir("/") < 0) {
33         printf("error chdir\n");
34         exit(0);
35     }
36     if (rl.rlim_max == RLIM_INFINITY)
37         rl.rlim_max = 1024;
38     for (i = 0; i < rl.rlim_max; i++)
39         close(i);
40 
41     if ((pid = fork()) < 0) {
42          printf("error fork\n");
43          exit(0);
44      } else if (pid != 0) {
45           exit(0);
46      }
47     
48 }
49 
50 int main()
51 {
52     daemonize();
53     while(1) {
54         printf("111111111111111\n");
55         sleep(2);
56     }
57     
58 }
复制代码