守护进程(精灵进程)
守护进程也称精灵进程(Daemon),是运行在后台的一种特殊进程 。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其他进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程不受用户登录注销的影响,它们一直在运行着。这种进程就是守护进程。
用ps axj指令查看系统中的进程。(凡是TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程。守护进程通常采用以d结尾的名字,表示Daemon)
1.setsid函数
创建守护进程最关键的一步是调用setsid函数创建一个新的session,并成功会话首进程
该函数调用成功时返回新创建的Session的id,出错返回-1。(调用这个函数之前,当前进程不允许是进程组的组长,否则该函数返回-1)
保证当前进程不是该进程组的组长的方法是:先fork再调用。fork创建的子进程和父进程在同一个进程组中,进程组的组长必然是该组的第一个进程。
成功调用该函数的结果:
1、创建一个新的Session,当前进程成功Session Leader,当前进程的id就是Session的id。
2、创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
3、如果当前进程原本有一个控制终端,成为一个没有没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端。
2.创建守护进程
1.调用umask将文件模式创建屏蔽字设置为0。
2.调用fork,父进程退出(exit)。
原因:1)如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕。
2)保证子进程不是一个进程组的组长进程。
3.调用setsid创建一个新会话。
setsid会导致:1)调用进程成为新会话的首进程。
2)调用进程成为一个进程组的组长进程。
3)调用进程没有控制终端。
4.将当前工作目录更改为根目录。
5.关闭不在需要的文件描述符。
6.其他:忽略SIGCHLD信号
3.编写mydaemon.c文件
1)简单版本(简单粗暴的直接关闭文件描述符表,只进行一次fork)
#include #include #include #include #include #include void mydaemon() { pid_t pid; umask(0); //first, set umask if((pid=fork())==0)//second,creat child process {} else if(pid!=0) { exit(0);//stop father process } setsid();//third,creat new session chdir("/"); //change root catalogue close(0); close(1); close(2); signal(SIGCHLD,SIG_IGN); } void main() { mydaemon(); while(1); }
检验运行结果
可以看出该程序已成功运行,且成为守护进程。
2)改进版(将文件描述符重定向到/dev/null,进行两次fork)
2 #include <unistd.h>
3 #include <signal.h>
4 #include <stdlib.h>
5 #include <fcntl.h>
6 #include <sys/stat.h>
7
8 void mydaemon()
9 {
10 int fd0;
11 pid_t pid;
12 umask(0); //first, set umask
13 if((pid=fork())==0)//second,creat child process
14 {}
15 else if(pid!=0)
16 {
17 exit(0);//stop father process
18 }
19 setsid();//third,creat new session
20 chdir("/"); //change root catalogue
21 close(0);
22 fd0=open("/dev/null",O_RDWR);
23 dup2(fd0,1);
24 dup2(fd0,2);
25 signal(SIGCHLD,SIG_IGN);
26 if((pid=fork())<0)//再次fork,终止父进程,保证子进程不是话首进程,保证后续不会再和其他终端关联
27 {}
28 else if(pid!=0)
29 {
30 exit(0);
31 }
32 }
33
34 void main()
35 {
36 mydaemon();
37 while(1);
38 }
再次fork,终止父进程,保证子进程不是话首进程,保证后续不会再和其他终端关联
程序运行结果
4.调用库中的daemon函数
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main()
5 {
6 daemon(0,0);
7 while(1);
8 }
缺省参数时,表明更改目录为根目录,且关闭文件描述符
程序运行结果
为了方便使用可以建立Makefile文件,代码如下
1 daemon:daemon.c
2 gcc -o daemon daemon.c
3 .PHONY:clean
4 clean:
5 rm -f daemon
6
7
调用fork()的作用
在创建守护进程时我们会发现,有些人会fork()一次,但是也有人fork()两次,所以我们来看看,这两者分别有什么作用
(1)调用一次fork的作用:
第一次fork的作用是让shell认为这条命令已经终止,不用挂在终端输入上,还有就是为了后面上的setsid服务,因为调用setsid函数的进程不能是组长进程,如果不fork出子进程,则此时的父进程是进程组长,就无法调用setsid。当子进程调用完setsid函数之后,子进程是会话组长也是进程组组长,并且脱离了控制终端,此时,不管控制终端如何操作,新的进程都不会收到一些信号使得进程退出。
(2)第二次fork的作用:
虽然当前关闭了和终端的联系,但是后期可能会误操作打开了终端。
只有会话首进程能打开终端设备,也就是在fork一次,再把父进程退出,再次fork的子进程作为守护进程继续运行,保证了该精灵进程不是对话期的首进程,第二次不是必须的,是可选的,市面上有些开源项目也是fork一次。