Linux守护进程总结(2) : 实现

将一个普通进程改造成守护进程,需要下面几步:

一、fork子进程,退出父进程 - 为下一步创建新会话做准备
fork出的子进程必定与其父同组,且不是该组的首个进程,所以,子进程一定不是该组的组长进程。经过这一步操作,就去掉了进程的进程组组长身份,如果它是组长的话。

二、调用setsid创建新会话
要想使setsid顺利工作,必须要满足一个条件:调用进程不能是进程组的组长进程。这就是上一步工作的目的。

上面这两步是创建守护进程的核心步骤,顺利完成这两步,就可得到守护进程。但实际上,还需要考虑一些其它问题。见下面几步(不是必须):

三、忽略干扰信号
有一些信号会导致进程“终止”,我们不希望守护进程去理会这些信号。其实,与终端有关的那些信号,系统是不可能再发给守护进程的。这里还是要显式地进行忽略,是为了防止人为发送(kill)这些信号。

SIGHUP - 连接挂断 - 进程终止
SIGINT - 终端中断符 - 进程终止
SIGQUIT - 终端退出符 - 进程终止
SIGTERM - 终止 - 进程终止

四、更改当前工作目录
守护进程的工作目录可能是一个挂载的文件系统。此时,若要umount掉这个文件系统,就必须要终止该守护进程。假设该守护进程正忙于为大量用户提供服务,那么,将其终止的代价就太高了。为避免这种情况,必须要重置其工作目录到一个固定的位置 - 根目录。

五、重置文件模式创建掩码
守护进程首先是一个子进程,它会继承父进程的文件模式创建掩码值。如果守护进程需要对文件进行操作,应该明确自己所使用的掩码值。因此,要显示地进行设置,如果没想好置为何值,就先设置为0吧。

六、屏蔽标准输入/输出和标准错误
我们开发的程序通常要链接一些库。如果库中有读标准输入或写标准输出、标准错误的操作的话,在shell中启动该守护进程后,就会将内容输出到当前终端或从终端读取内容(虽然该进程已脱离了当前终端,但是,终端并未关闭,0,1,2三个文件描述符还是指向当前终端的设备文件)。我们要屏蔽掉任何读标准输入,写标准输出、标准错误的行为。

七、去除“会话leader”身份
第2步完成以后,这个守护进程的身份是会话leader兼进程组组长。会话leader就意味着它有获取控制终端的能力。也就是说这个进程如果open了终端设备文件的话,被open的这个终端设备就有可能(这依赖于操作系统的具体实现)成为该守护进程的新控制终端。这个地方只要再fork一次,让父进程退出,即可去除进程的会话leader身份,它就没有资格再去获取新的控制终端了。

实现

 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
int InitDaemon()
{
    // step_1
    //////////////////////////////////
    pid_t pid = 0;
    if ((pid = fork()) < 0)
        return -1;
    if (pid != 0)
        exit(0);

    // step_2
    //////////////////////////////////
    if (setsid() < 0)
        return -2;

    // step_3 *
    //////////////////////////////////
    signal(SIGHUP, SIG_IGN);
    signal(SIGINT, SIG_IGN);
    signal(SITERM, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);

    // step_4 *
    //////////////////////////////////
    if (chdir("/") < 0)
        return -4;

    // step_5 *
    //////////////////////////////////
    umask(0);

    // step_6 *
    //////////////////////////////////
    close(0);
    if (open("/dev/null", O_RDWR) < 0)
        return -6;
    dup2(0, 1);
    dup2(0, 2);

    // step_7 *
    //////////////////////////////////
    if ((pid = fork()) < 0)
        return -7;
    if (pid != 0)
        exit(0);
}


Prev-Linux守护进程总结(1):基本理论



References: