守护进程也叫做精灵进程,是运行在后台的一种特殊的进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。生存周期长,它们常常在系统引导装入的时候启动,仅仅在系统关闭的时候才终止。因为它们没有控制终端,所以说它们是在后台运行的。很多服务器的应用都和守护进程相关
守护进程的特点:
守护进程启动后没有控制终端,不能直接和用户交互。
守护进程不受用户登录注销的影响。只受开机关机的影响。
创建一个守护进程一般有如下几步:
1.调用umask(0),将文件模式创建屏蔽字改为0,守护进程创建文件有最高的权限
2.调用fork(),父进程退出,目的是: (1)父进程终止,子进程成为孤儿进程在后台运行,可以让shell切换到前台继续等待用户输入命令
(2)保证子进程不是一个进程组的组长进程,因为孤儿进程的父进程是1号进程,
3.在子进程中调用 setsid(),会导致如下结果:
调用进程成为新会话的首进程
调用进程成为一个进程组的组长进程
调用进程脱离终端,即没有控制终端
4.调用chdir("/")将当前工作目录更改为根目录,目的是防止用户改动目录,从而影响进程的运行
5.关闭相应的文件描述符,原因是,当前进程已经脱离终端,用fork新建的子进程会从父进程那里继承文件描述符。这些文件描述符都不会用,但它们一样消耗系统资源,所以需要关闭
6忽略SIGCHLD信号
关于忽略SIGCHLD信号:父进程fork出一个进程,然后父进程立马退出, 在子进程的执行流中, 设置 signal(SIGCHLD, SIG_IGN)。 此时原来的父进程已经不存在,而子进程脱离终端,自成会话, 相当于 子进程就变成了新会话的父进程, 又因为此时它是守护进程, 代码已经写好了, 而在它的执行流中很有可能会再次 fork 产生新的子进程, 此时如果它fork出的子进程退出的话,必须要回收资源,可以使用wait 或者 waitpid, 但是这样会导致守护进程阻塞, 所以 在守护进程中捕捉SICCHLD信号, 并忽略, 可以让守护进程不阻塞,并回收子进程资源。
守护进程的实现:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
void mydaemon()
{
umask(0); //1.重设文件权限,守护进程创建最高的文件权限。
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
exit(1);
}
if(pid > 0)
{
exit(0); //2.父进程退出,子进程成为一个孤儿进程,(1)让出shell执行其他的命令。(2)保证子进程不是组长进程。(因为此时组长进程是1号进程)
}
pid_t sid = setsid(); //3.子进程创建一个新的会话。脱离终端控制。
if(sid < 0)
{
perror("setsid");
exit(2);
}
chdir("/"); //4.改变根目录
close(0);
close(1);
close(2); //5.关闭文件描述符,因为已经脱离终端了,文件描述符就没用了,关闭它节省资源。
signal(SIGCHLD,SIG_IGN); //6.捕捉SIGCHLD信号,这是因为如果在守护进程中创建子进程,子进程退出需要回收子进程的资源。
}
int main()
{
mydaemon();
while(1); //这个while循环是为了方便查看这个程序确实运行了,也就是守护进程确实被创建了。
return 0;
}
从中我们看到,所创建的PID(进程号) PGID(进程组号) SID(会话号)三个号是一样的
守护进程的两次fork()
第二次fork的作用是为子进程fork()出一个子进程,第二次fork的原因是为了防止进程再次打开一个控制终端,因为当前的进程是一个会话的会话组长,所以是可以去打开控制终端的前台的,如果再fork()一次,子进程ID!=sid。所以也就无法打开新的控制终端。子进程的子进程做相关的守护进程的操作。所以这个子进程的子进程现在是当前会话的非控制进程,所以它是无法进行创建控制进程。这样也就没了什么影响。
两次fork的代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
void mydaemon2()
{
umask(0);
pid_t pid = fork();
if(pid > 0)
{
exit(0); //父进程退出。
}
//子进程
pid_t sid = setsid(); //子进程创建一个新的会话,脱离终端控制。
if(sid < 0)
{
perror("setsid");
exit(1);
}
if(fork() > 0) //子进程再次fork出孙子进程,因为子进程是新的会话的会首进程,可以去再次打开控制终端的,,如果再fork()一次,孙子进程id != sid,就无法打开新的控制终端了,
{
exit(0);
}
chdir("/");
close(0);
close(1);
close(2);
signal(SIGCHLD,SIG_IGN);
}
int main()
{
mydaemon2();
while(1);
return 0;
}
创建的子进程的子进程,所以PID是不一样的,依然是被1号进程领养的进程,自成会话,避免了再次控制终端