创建守护进程以及为什么fork两次

守护进程

守护进程也叫精灵进程(Daemon),它在后台运行,独立于控制终端并周期性地执行某种任务或等待某些事情发生,一般用于C/S 服务中,在服务端监听端口,等待客户端的连接,如果有客户端发出连接,则fork出一个子进程去响应,而守护进程继续监听。

步骤

创建一个守护进程一般有如下几步:

  1. 调用umask(0),将文件模式创建屏蔽字改为0
  2. 调用fork(),父进程退出,目的是: 
    • 父进程终止可以让shell切换到前台继续等待用户输入命令
    • 保证子进程不是一个进程组的组长进程
  3. 在子进程中调用 ssetsid(),会导致如下结果: 
    • 调用进程成为新会话的首进程
    • 调用进程成为一个进程组的组长进程
    • 调用进程脱离终端,即没有控制终端
  4. 调用chdir("/")将当前工作目录更改为根目录,目的是防止用户改动目录,从而影响进程的运行
  5. 关闭相应的文件描述符,原因是,当前进程已经脱离终端,防止产生并不需要的交互作用,还有一个原因,用fork新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,所以需要关闭
  6. 忽略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
最重要的一步就是 调用setsid
#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 两次,原因如下:

  1. 彻底和控制终端脱离关系。
  2. 防止父进程运行很长时间,而第一次fork出的子进程成为僵尸进程(少见)。

对于原因1的解释:

APUE 第13 章中关于守护进程的介绍,作者在小字部分提到,在System V 环境下,有人建议 fork 两次,为了保证守护进程不是会话首进程,(因为会话首进程可以获取到控制终端,而非会话收进程无法打开控制终端),避免它取得控制终端。所以说第二次fork 可以在你确保不获取控制终端的情况下略之。

对于原因2的解释:

这里有一个假定,父进程生成守护进程后,还有自己的事要做,它的人生意义并不只是为了生成守护进程。这样,如果父进程fork一次创建了一个守护进程,然后继续做其它事时阻塞了,这时守护进程一直在运行,父进程却没有正常退出。如果守护进程因为正常或非正常原因退出了,就会变成ZOMBIE进程。 
如果fork两次呢?父进程先fork出一个儿子进程,儿子进程再fork出孙子进程做为守护进程,然后儿子进程立刻退出,守护进程被init进程接管,这样无论父进程做什么事,无论怎么被阻塞,都与守护进程无关了。所以,fork两次的守护进程很安全,避免了僵尸进程出现的可能性。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值