多进程编程之守护进程Daemonize
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()函数创建一个新的会话:
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 }