在linux或者unix操作系统中在系统的引导的时候会开启很多服务,这些服务就叫做守护进程。守护进程是在后台运行不与任何控制终端关联,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。大多数服务都是通过守护进程实现的。它从被执行开始运转,直到整个系统关闭时才退出。如果想让某个进程不因为用户或终端或其他地变化而受到影响,那么就必须把这个进程变成一个守护进程。
通过一系列的操作,可以把一个普通进程转变位守护进程。
1.调用umask将文件模式创建屏蔽字设为0。
为了防止守护进程从继承来的文件模式创建屏蔽字屏蔽了某些权限。
2.调用fork,终止父进程。
如果进程是从一个shell命令启动的,父进程终止shell会认为命令执行完毕。
而子进程继承了父进程的进程组ID,而它的进程ID和父进程ID不同,这就保证 .子进程不是一个进程组的头进程,为调用setsid提供必要条件。
3.进程调用setsid建立一个会话。
首先要知道会话的概念,会话是一个多个进程组的集合。
如果该调用进程已经是一个进程组的组长进程该函数返回出错。所以通常先fork后使父进程终止。子进程再调用setsid.正如第一步。如下面的程序,父进程作为组长进程调用setsid将会报错。
main() { int pid; if((pid = fork()) != -1) { if (pid != 0) { if ((pid = setsid()) == -1) perror("setsid error"); } } }
调用setsid后,调用进程成为新会话的第一个进程,此时该进程是新会话中唯一的进程,并且成为新进程组的组长进程,新进程组ID为调用进程ID。
4.忽略SIGHUP信号,再次调用fork,终止父进程。
确保子进程不再是一个会话头进程,不能自动获得一个控制终端。这里忽略SIGHUP信号是因为会话首进程终止时,会话中所有进程都会收到SIGHUP信号而终止。
5.改变工作目录。
当前工作目录可能是挂在到系统上的,如果守护进程运行,会导致挂载的目录无法卸载,所以要改变目录。
6.关闭不再需要的文件描述符。
调用sysconf(_SC_OPEN_MAX)获取进程可打开文件的限制,并关闭所有描述符。
7.将stdin,stdout,stderr重定向到/dev/null
如果stdin,stdout,stderr被关闭,如果建立了socket描述符,描述符值可能为stdin,stdout或stderr中值的某一个,那么当调用向标准输入输出出错写入数据的操作可能会发送给socket套接字对端,造成错误。
例如下面的程序,关闭标准出错,建立一个套接字,正常情况下该套接字描述符为2,即标准出错的描述符值,那么调用perror向标准出错中写入数据,将发送到对端,对端将接收到调用perror发送来的数据并打印,然后perror写入到标准出错的数据只被对端打印了一次,具体内部细节这里不做讨论。
关闭标准出错,调用perror的客户程序
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdlib.h> #include <strings.h> #include <string.h> main() { int r; int sockfd; struct sockaddr_in addr; //关闭标准出错 close(2); sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd != 2) { printf("sockfd != 2\n"); exit(-1); } bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(12345); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); r = connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)); if (r == -1) { printf("connect error\n"); exit(-1); } //向标准出错中发送数据 while (1) { sleep(2); perror("test"); } }
服务程序
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdlib.h> #include <strings.h> main() { int r; int sockfd, clifd; struct sockaddr_in addr; char buf[1025]; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(12345); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); r = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); if (r == -1) { printf("bind error\n"); exit(-1); } r = listen(sockfd, 10); if (r == -1) { printf("listen error\n"); exit(-1); } while(1) { clifd = accept(sockfd, NULL, NULL); if (clifd == -1) { printf("accept error\n"); exit(-1); } r = read(clifd, buf, sizeof(buf)); if (r == -1) { perror("error\n"); exit(-1); } if (r == 0) { printf("对端关闭\n"); exit(-1); } buf[r] = 0; printf("%s\n", buf); } }
下面展示了一个创建守护进程的示例
#include <unistd.h> #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <syslog.h> #define TEST_PID 1234 main() { int pid; int i; long maxfd; //文件模式创建屏蔽字置0 umask(0); pid = fork(); if (pid == -1) { perror("fork1 error"); exit(-1); } if (pid != 0) exit(0); //子进程1继续 if (setsid() == -1) { perror("setsid error"); exit(-1); } signal(SIGHUP, SIG_IGN); pid = fork(); if (pid == -1) { perror("fork2 error"); exit(-1); } if (pid != 0) exit(0); //子进程2继续 //改变工作目录 chdir("/"); //关闭描述符 maxfd = sysconf(_SC_OPEN_MAX); for (i=0; i<maxfd; i++) close(i); //stdin,stdout,stderr重定向到/dev/null open("/dev/null", O_RDONLY); open("/dev/null", O_RDWR); open("/dev/null", O_RDWR); }