如何编写daemon程序

24.3 如何编写daemon程序
Q: 在FreeBSD下用"ps auxw"查看进程列表时,注意到某些进程没有控制终端,也就
   是说TT列显示??。知道这是所谓的daemon进程,如果我想自己编写这样的程序,
   该如何做。
A: Andrew Gierth < andrew@erlenstar.demon.co.uk>
这个回答来自著名的<<Unix Programming FAQ ver 1.37>>,由Andrew Gierth负责维
护,其它细节请参看原文1.7小节。
通常将一个不与任何终端相关联的后台进程定义为daemon进程。下面是通常所需的七
个步骤:
a. fork()之后父进程退出。子进程确保不是process group leader,这是成功调用
   setsid()所要求的。
b. setsid(),创建新的session和process group,成为其leader,并脱离控制终端。
c. 再次fork()之后父进程退出,子进程确保不是session leader,将永远不会重获
   控制终端。这是SVR4的特性所致。
d. chdir( "/" ),减少管理员卸载(unmount)文件系统时可能遇上的麻烦。这一步可
   选,也可chdir()到其它目录。
e. umask( 0 ),使当前进程对自己所写文件拥有完全控制权,避免继承的umask()设
   置带来困挠。这一步可选。
f. 关闭0、1、2三个句柄。许多daemon程序用sysconf()获取_SC_OPEN_MAX,并在一
   个偱环中关闭所有可能打开的文件句柄。目的在于释放不必要的系统资源,它们
   是有限资源。
g. 出于安全以及健壮性考虑,即使当前进程不使用stdin、stdout、stderr,也应重
   新打开0、1、2三个句柄,使之对应/dev/null。当然,你也可以根据需要使之对
   应不同的(伪)文件。总之,保持0、1、2三个句柄呈打开状态,并使之指向无害文
   件。
D: scz < scz@nsfocus.com>
以FreeBSD 4.5-RELEASE为例进行讨论。
注意,存在与终端相关联的后台进程,比如在支持作业控制的bash上以&符结尾启动
的进程。当用"ps auxw"查看时,这种后台进程的TT列不为??。用"ps -p pid -jfl"
查看这种后台进程,可以看到其PGID与父进程的PGID不同,属于另外一个进程组。支
持作业控制的现代shell对&符的解释一般都是fork/setpgid。前台进程组、后台进程
组是终端的属性,不是进程本身的属性,没有控制终端的进程无所谓前台、后台,一
定要算就都算是后台进程。
非作业控制型的shell对&符的解释一般只是fork,而没有setpgid,这样启动的进程
与shell属于同一进程组。后面的讨论都假设使用支持作业控制的现代shell。
当在控制终端上按下Ctrl-C,终端驱动程序产生SIGINT信号(可用stty设置)并分发至
前台进程组的所有进程。
APUE 10.2中提到,当session leader终止时,系统会向该session前台进程组中所有
进程分发SIGHUP信号。我的疑问是,如果某session没有控制终端,也就没有所谓前
台进程组,当session leader终止时,系统会向该session中所有进程分发SIGHUP信
号吗。UNP 12.4的例子正是这种情形,可是Stevens没有在其它地方进一步阐述,也
永远不可能得到他本人的解释了。
APUE 13.3所给的daemon_init()与UNP 12.4所给不同,没有做二次fork()。因为二次
fork()只是SVR4的要求。从最广泛兼容角度出发,如果daemon进程企图打开一个(伪)
终端设备,无论是否二次fork()过,open()时都应该指定O_NOCTTY。由于daemon程序
是自己完全可控的,将来是否会打开终端是已知的,如果确认将来不会打开终端,就
完全不必考虑重获控制终端的问题,换句话说,二次fork()很大程度上是不必要的。
关于Andrew Gierth所提第七步骤,1987年Henry Spencer在setuid(7)手册页中做了
相关建议,1991年,在comp news上有人重贴了这份文档。1992年Richard Stevens建
议daemon进程应该关闭所有不必要的文件句柄,并将stdin、stdout、stderr指向
/dev/null。参看<<x86/FreeBSD 4.5-RELEASE IO Smash及S/Key机制分析>>。第七步
骤严格意义上来说,不是可选的,而是必须的。
参看<<19.0 如何将stdin、stdout、stderr重定向到/dev/null>>。
A: W. Richard Stevens
一般我会使用类似daemon_init()这样的函数,使当前进程成为daemon进程。
--------------------------------------------------------------------------
static void daemon_init ( const char *workdir, mode_t mask )
{
    int i, j;
    /*
     * change working directory, this step is optional
     */
    chdir( "/tmp" );
    if ( 0 != Fork() )
    {
        /*
         * parent terminates
         */
        exit( EXIT_SUCCESS );
    }
    /*
     * first child continues
     *
     * become session leader
     */
    setsid();
    Signal( SIGHUP, SIG_IGN );
    if ( 0 != Fork() )
    {
        /*
         * first child terminates
         */
        exit( EXIT_SUCCESS );
    }
    /*
     * second child continues
     *
     * change working directory, chdir( "/" )
     */
    chdir( workdir );
    /*
     * clear our file mode creation mask, umask( 0 )
     */
    umask( mask );
    j = Open( "/dev/null", O_RDWR );
    Dup2( j, 0 );
    Dup2( j, 1 );
    Dup2( j, 2 );
    j = getdtablesize();
    for ( i = 3; i < j; i++ )
    {
        close( i );
    }
    return;
}  /* end of daemon_init */
--------------------------------------------------------------------------
调用setsid(),如果成功,导致三个结果:
a. 创建一个新的session,当前进程成为session leader,也是新session中的惟一
   进程。
b. 当前进程成为一个新进程组的组长(process group leader)。
c. 如果当前进程以前有一个控制终端,现在将脱离这个控制终端。
对于SVR4,一个session leader调用open()打开一个(伪)终端设备,如果这个终端不
是其它会话的控制终端,而open()时又未指定O_NOCTTY,则这个终端成为当前会话的
控制终端。第二次fork()后,孙子进程将确保不是session leader。于是以后不会再
有任何控制终端,彻底脱离。
必须在第二次fork()之前显式忽略SIGHUP信号。孙子进程将继承子进程所设置的信号
句柄。Stevens是这样解释的,当session leader终止时,系统会向该session中所有
进程分发SIGHUP信号。即这里的子进程终止时,系统会向孙子进程分发SIGHHUP信号。
前面有关于这个问题的讨论。
getdtablesize()返回的也就是sysconf( _SC_OPEN_MAX )返回的值。
D: scz < scz@nsfocus.com>
以FreeBSD 4.5-RELEASE为例进行讨论。
做为Guru of the Unix gurus,Andrew Gierth与Richard Stevens在各类文档或书籍
中对"进程"进行了相当广泛、深入的解释,其中可能引发困惑的一个问题是,父子进
程关系与信号分发的关系。
有相当多的人认为父进程终止时,子进程应该收到一个SIGHUP信号。即使熟练的Unix
程序员参与某些讨论时,也可能忘记几分钟前TA还在fork(),并立即让父进程退出的
事实。一般来说,有两种典型的与SIGHUP信号相关的情形。
假设某session有控制终端,当session leader终止时,系统会向该session前台进程
组中所有进程分发SIGHUP信号。
如果某进程组中有一个进程,其父进程属于同一会话(session)的另一个进程组,则
该进程组不是"孤儿进程组),反之该进程组称为"孤儿进程组"。
APUE 9.10指出,当某进程的终止导致一个新的"孤儿进程组",系统会向这个新的"孤
儿进程组"中处于"停止"状态的每个进程分发SIGHUP信号,然后分发SIGCONT信号。那
些未处于"停止"状态的进程不会收到这两个信号。
启动"tcpdump -i lnc0 udp &",此时tcpdump成为后台进程组成员。退出当前shell,
此时tcpdump成为孤儿进程组成员,但它处于"运行"状态。重新登录后会发现该进程
仍然存在,它不是daemon进程,TT列不为??。它没有收到SIGHUP信号,手动kill -1
是可以杀掉它的。
启动"nohup tcpdump -i lnc0 udp",此时tcpdump仍为前台进程组成员。从另一shell
执行"kill -1"杀掉前一shell,此时tcpdump成为孤儿进程组成员。有SIGHUP信号分
发到tcpdump,因为session leader终止了。nohup确保tcpdump继续运行。对比没有
使用nohup时的情形。
有一个bindshell,它只是简单fork()了一次,父进程立即退出。并未处理SIGHUP信
号,也未调用setsid()。它已经达到目的了。fork()之后产生一个后台孤儿进程组,
并未脱离控制终端,但再也不会有SIGHUP信号分发到bindshell。前述两种情形都不
会出现。在控制终端上按Ctrl-C产生的SIGINT信号不会分发到后台进程组。一般入侵
中要的就是这个效果,并不需要复杂的daemon_init()。
还有一种情形,简单fork()一次,父进程调用setpgid()使子进程自己成为进程组长,
然后父进程退出。这只是确保产生后台孤儿进程组,setpgid()不是必须的。子进程
仍然过继给init进程。
两位先生给出的daemon化步骤考虑得相当周全。但更多的入侵者、临时跳板工具并不
需要daemon化,最省事的办法就是fork()一次。最后再强调一次,脱离控制终端、彻
底脱离控制终端与不受SIGHUP信号影响是两回事,绝大多数时候要的只是后者的效果。
此外,Linux可能在某些细节上与上述讨论有出入,但最后的结论一样,最省事的办
法就是fork()一次。
个人推荐严肃的Unix/C程序员在需要这类效果时,统一使用daemon_init(),并捕捉
相关信号。
D:  lskuangren@bbs.apue.net 2003-07-11
FreeBSD和Linux直接提供了一个函数,DAEMON(3)
--------------------------------------------------------------------------
DAEMON(3)          FreeBSD库函数手册                             DAEMON(3)
名字
    daemon - 在后台运行程序
    标准C库(libc, -lc)
语法
    #include <stdlib.h>
    int daemon ( int nochdir, int noclose );
描述
    daemon()用于脱离控制终端、转入后台运行程序(守护进程)。
    如果第一形参nochdir为零,daemon()最终执行chdir( "/" )。
    如果第二形参noclose为零,daemon()最终将stdin、stdout、stderr重定向到
    /dev/null。
错误
    失败时返回-1,并设置errno,errno的值与fork(2)、setsid(2)的情形一致。
参看
    fork(2), setsid(2)
历史
    4.4BSD首次引入了daemon()。
--------------------------------------------------------------------------
从man手册可以看出daemon()都做了些什么。在清楚自己到底需要何种效果的前提下,
可以不使用复杂的daemon_init()而直接使用daemon()。
AIX、Solaris未直接提供daemon(),如果编写最广泛兼容程序,应避免使用daemon()。

转载于:https://my.oschina.net/qihh/blog/66466

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值