守护进程
守护进程也叫精灵进程(Daemon),它在后台运行,独立于控制终端并周期性地执行某种任务或等待某些事情发生,一般用于C/S 服务中,在服务端监听端口,等待客户端的连接,如果有客户端发出连接,则fork出一个子进程去响应,而守护进程继续监听。
步骤
创建一个守护进程一般有如下几步:
- 调用
umask(0)
,将文件模式创建屏蔽字改为0 - 调用
fork()
,父进程退出,目的是:
- 父进程终止可以让shell切换到前台继续等待用户输入命令
- 保证子进程不是一个进程组的组长进程
- 在子进程中调用
ssetsid()
,会导致如下结果:
- 调用进程成为新会话的首进程
- 调用进程成为一个进程组的组长进程
- 调用进程脱离终端,即没有控制终端
- 调用
chdir("/")
将当前工作目录更改为根目录,目的是防止用户改动目录,从而影响进程的运行 - 关闭相应的文件描述符,原因是,当前进程已经脱离终端,防止产生并不需要的交互作用,还有一个原因,用fork新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,所以需要关闭
- 忽略SIGCHLD信号,该动作是为了,子进程退出的时候资源能够被系统所回收,防止出现僵尸进程
关于忽略SIGCHLD信号:父进程fork出一个进程,然后父进程立马退出, 在子进程的执行流中, 设置 signal(SIGCHLD, SIG_IGN)。 此时原来的父进程已经不存在,而子进程脱离终端,自成会话, 相当于 子进程就变成了新会话的父进程, 又因为此时它是守护进程, 代码已经写好了, 而在它的执行流中很有可能会再次 fork 产生新的子进程, 此时如果它fork出的子进程退出的话,必须要回收资源,可以使用wait 或者 waitpid, 但是这样会导致守护进程阻塞, 所以 在守护进程中捕捉SICCHLD信号, 并忽略, 可以让守护进程不阻塞,并回收子进程资源。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
void create_daemon()
{
//1. 将文件模式创建屏蔽字设置为0
umask(0);
//2. 调用fork 父进程退出
// 作用:确保子进程不是一个进程组的组长
pid_t id = 0;
if ( (id = fork()) < 0)
{
perror("fork");
exit(2);
}
if(id > 0 )
{// father
exit(3);
}
// 3.调用setsid 创建一个新的会话
setsid();
//4. 将当前工作目录改为更目录
chdir("/");
//5. 关闭不需要的文件描述符
close(0);
close(1);
close(2);
//6. 忽略SIGCHLD 信号
signal(SIGCHLD, SIG_IGN);
// 守护进程逻辑
while(1)
{
// Do something
}
}
int main()
{
create_deamon();
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
#include <unistd.j>
pid_t setsid(void);
成功返回新创建的会话 ID ,出错返回-1
- 1
- 2
- 3
- 4
调用此函数之前,当前进程不允许是进程组的Leader,如果是则出错返回-1。
所以可以先fork创建子进程,然后在调用setsid,fork出的子进程和父进程在同一个进程组中,进程组的Leader必然是父进程,所以可以保证该函数会执行成功。
该函数成功的表现:
- 创建一个新的会话,当前进程成为会话Leader,SID = PID
- 创建一个新的进程组,当前进程称为进程组的Leader,GID = PID
- 当前进程与控制终端脱离关系
下面内容于修改:2017年6月19日19:00:10
有时候创建守护进程时会fork 两次,原因如下:
- 彻底和控制终端脱离关系。
- 防止父进程运行很长时间,而第一次fork出的子进程成为僵尸进程(少见)。
对于原因1的解释:
APUE 第13 章中关于守护进程的介绍,作者在小字部分提到,在System V 环境下,有人建议 fork 两次,为了保证守护进程不是会话首进程,(因为会话首进程可以获取到控制终端,而非会话收进程无法打开控制终端),避免它取得控制终端。所以说第二次fork 可以在你确保不获取控制终端的情况下略之。
对于原因2的解释:
这里有一个假定,父进程生成守护进程后,还有自己的事要做,它的人生意义并不只是为了生成守护进程。这样,如果父进程fork一次创建了一个守护进程,然后继续做其它事时阻塞了,这时守护进程一直在运行,父进程却没有正常退出。如果守护进程因为正常或非正常原因退出了,就会变成ZOMBIE进程。
如果fork两次呢?父进程先fork出一个儿子进程,儿子进程再fork出孙子进程做为守护进程,然后儿子进程立刻退出,守护进程被init进程接管,这样无论父进程做什么事,无论怎么被阻塞,都与守护进程无关了。所以,fork两次的守护进程很安全,避免了僵尸进程出现的可能性。