1.概念
守护进程也称精灵进程,是在后台运行的一种特殊进程,它脱离控制终端并且周期性的执行某种任务或者等待某种事件的发生,脱离终端是为了避免进程在执行过程中的信息在任何终端上显示,并且进程也不会被任何终端产生的中断信息所终止;linux下的大多 服务器都是用守护进程实现的。比如internet 的inted 和wed 服务器httpd
2.创建守护进程的一般步骤
(1)调用umask重设文件权限掩码
文件权限掩码:指屏蔽掉文件权限中的对应位。例如,有个文件权限掩码为050,它就屏蔽掉了文件组拥有者的可读与可执行权限。由于 fork创建的子进程继承了父进程的文件掩码,这给子进程使用文件带来了麻烦,因此将文件掩码设置为0(即不屏蔽任何权限)可以增强守护进程的灵活性
(2)创建子进程退出父进程
为了脱离控制终端需要推测出父进程,之后的工作由子进程完成。在linux中父进程先与子进程退出会造成孤儿进程,每当发现一个孤儿进程 时就会自动由1号进程(init)收养它,这样原先的子进程就会变成init的子进程
(3)调用setid(void)函数在子进程中创建一个新会话
man 2 setsid 查看关于setsid函数的说明
setsid – creates a session and sets theprocess group ID
#include <unistd.h>
pid_t setsid(void);
setsid() creates a new session if thecalling process is not a process group leader. The calling process is theleader of the new session, the process group leader of the new process group,and has no controlling tty. The process group ID and session ID of the callingprocess are set to the PID of the calling process. The calling process will bethe only process in this new process group and in this new session.
进程组:一个或多个进程的集合,进程组由进程组的ID来唯一标示 ,每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID,该进程组ID不会因为组长进程退出而受到影响
setid函数的作用:用于创建一个新的会话,并担任该会话组的组长,其中有以下三个作用
a.让进程摆脱原来会话的控制
b.让进程摆脱原来会话组的控制
c.让进程摆脱原来会话终端的控制
使用setid函数的目的
由于创建守护进程第一步调用了fork函数创建子进程再将父进程退出。由于在调用fork函数的时候,子进程拷贝了父进程的会话期,进程组,控制终端等,虽然父进程退出了但是会话期,进程组,控制终端没有改变,因此还不是真正意义上独立了。使用ssetid函数之后能够使进程完全独立出来,从而摆脱其他进程的控制。
(4)改变当前目录为根目录
使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行过程中,当前目录所在的文件系统是不能卸载的,这对以后的使用会造成麻烦,因此常用做法是让根目录作为守护进程的当前工作目录
(5)关闭文件描述符
用fork创建的子进程会从父进程哪里继承一些已经打开 的文件。这些打开的文件可能永远不会被守护进程读写,但他们一样消耗资源。在使用setid后守护进程已经与 所属的控制终端失去联系,因此终端输入的进程不可能到达守护进程。所以文件描述符0,1,2已经失去了存在 的价值应该关闭
(6)守护进程的退出处理
当用户需要外部停止守护进程时,通常用kill命令停止该守户进程
1 #include<stdio.h> 2 #include<signal.h> 3 #include<unistd.h> 4 #include<stdlib.h> 5 #include<fcntl.h> 6 #include<sys/stat.h> 7 8 void creat_daemon(void){ 9 int i; 10 int fd0; 11 pid_t pid; 12 struct sigaction sa; 13 umask(0);//设置文件掩码为0 14 if(pid=fork()<0){ 15 printf("child dir error\n"); 16 } 17 else { 18 exit(0);// 19 } 20 21 setid(0);//设置新的会话 22 sa.sa_handler=SIG_IGN; 23 sigemptyset(&sa.sa_mask); 24 sa.sa_flags=0; 25 if(sigaction(SIGCHLD,&sa,NULL)<0){//注册子进程退出忽略信号 26 return; 27 } 28 if(pid=fork()<0){//再次fork,终止父进程保证子进程不是话首进程,保证进程不会受到其他进程的干扰 29 printf("fork error!\n"); 30 return; 31 } 32 else if(pid!=0){ 33 exit(0); 34 } 35 36 if(chdir("/")<0){//更改工作目录到根目录 37 printf("child dir error\n"); 38 return; 39 } 40 close(0); 41 fd0=open("/dev/null",O_RDWR);//关闭标准输入 ,标准输出,标准错误 42 dup2(fd0,1); 43 dup2(fd0,2); 44 } 45 46 int main() 47 { 48 creat_daemon(); 49 while(1){ 50 sleep(1); 51 52 } 53 54 } 55
那么问题来了,daemon 函数为什么会fork两次?????
原因如下:
(1)第一次fork是为了让shell认为本条命令已经终止,不用挂在输入端。还有一个为后边的setid服务。setid的调用者不能是进程组组长,此时父进程是进程组组长
(2)setid是daemon函数中的一个重要的调用,它完成了daeemon函数要做的大部分事情调用完之后子进程是会话组组长,并且脱离了原来终端的控制,以后不官终端如何新的进程都不会受到那些信号
(3)第二次fork的目的是为了防止进程再次打开一个控制 终端。因为打开一个控制终端的前提条件是该进程必须是会话组组长。再fork一次子进程id!=ppid所以也无法打开新的控制终端