daemon进程

本文详细解析了Linux daemon进程为何必须存在及其核心实现原理,包括如何避免因控制终端变化导致进程退出的问题,以及如何确保进程独立运行,不受外界影响。

转自:http://blog.chinaunix.net/uid-27105712-id-3356916.html       

      为什么一定要使用daemon进程呢?Linux中每一个系统与用户进行交流的界面称为终端(terminal),每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端(Controlling terminal),当控制终端被关闭时,相应的进程都会被自动关闭。关于这点,读者可以用X-Window中的XTerm试验一下,(每一个XTerm就是一个打开的终端,)我们可以通过键入命令启动应用程序,比如:$netscape。然后我们关闭XTerm窗口,刚刚启动的netscape窗口也会随之一同突然蒸发。但是daemon进程却能够突破这种限制,即使对应的终端关闭,它也能在系统中长久地存在下去,如果我们想让某个进程长命百岁,不因为用户或终端或其他的变化而受到影响,就必须把这个进程变成一个daemon进程。
    daemon进程是后台守护进程,有时候也叫精灵进程(agent).linux 下server都是daemon进程。相信大部分开发人员都知道如何去写一个daemon进程。但是另一方面,大部分人不知道为什么要这么做,不少人是从某个地方copy一个函数,拿来主义。但是具体为什么这么实现,却不是很透彻。见过一些面试官或被面试人。很多人解释daemon进程存在的理由是因为僵死进程。或者输入输出。其实和这些东西一毛钱关系都没有。daemon函数存在的原因是因为控制终端由于某些原因(如断开终端链接)会发送一些信号的原因。而接收进城处理这些信号缺省动作会让进程退出。这些信号会由于终端上敲一些特殊按键而产生。

    一种典型实现:

int daemon(void)
{
    pid_t pid = fork();

    if( pid != 0 ) exit(0);//parent

    //first children
    if(setsid() == -1)
    {
       printf("setsid failed\n");
       assert(0);
       exit(-1);
    }

    umask(0);

    pid = fork();

    if( pid != 0) exit(0);
  
    //second children 
    chdir ("/");

    for (int i = 0; i < 3; i++)
    {
        close (i);
    }

    int stdfd = open ("/dev/null", O_RDWR);
    dup2(stdfd, STDOUT_FILENO);
    dup2(stdfd, STDERR_FILENO);

    return 0;
}

    1、第一次fork的作用是让shell 认为本条命令 已经终止,不用挂在终端输入上。还有一个作用是为后面setsid服务。setsid的调用者不能是进程组组长(group leader). 此时父进程是进程组组长。
    
    2、setsid() 是本函数最重要的一个调用。它完成了daemon函数想要做的大部分事情。调用完整个函数。子进程是会话组长(sid==pid),也是进程组组长(pgid == pid),并且脱离了原来控制终端。到了这一步,基本上不管控制终端如何怎么样。新的进程都不会收到那些信号。

    3、经过前面2个步骤,基本想要做的都做了。第2次fork不是必须的。也看到很多开源服务没有fork第二次。fork第二次主要目的是。防止进程再次打开一个控制终端。因为打开一个控制终端的前台条件是该进程必须是会话组长。再fork一次,子进程ID != sid(sid是进程父进程的sid)。所以也无法打开新的控制终端。

    4、把当前工作目录切换到根目录。如果我们是在一个临时加载的文件系统上执行这个进程的,比如:/mnt/floppy/,该进程的当前工作目录就会是/mnt/floppy/。在整个进程运行期间该文件系统都无法被卸下(umount),而无论我们是否在使用这个文件系统,这会给我们带来很多不便。解决的方法是使用chdir系统调用把当前工作目录变为根目录,应该不会有人想把根目录卸下吧。当然,在这一步里,如果有特殊的需要,我们也可以把当前工作目录换成其他的路径,比如/tmp。 

    daemon目的就是防止终端产生的一些信号让进程退出,总之,就是让调用进程完全独立出来,脱离所有其他进程、终端的控制。上面函数并没有直接调用signal函数去处理它。而是间接通过fork和setsid函数使用更少代码优雅处理,而被有些人误以为是由于僵死进程的原因需要这样处理

    5、最后关闭不需要的文件描述符。

     当然,也有很多程序在第4步不是像上面函数那样去实现。而是直接通过忽略信号方式处理。这样其实也不错,因为这些信号很少会有用到的价值。直接忽略基本上不存在误杀的情况。反正达到最终目的就可以。条条大路通罗马。

   下面罗列一下控制终端会产生哪些信号。程序中只要处理好这些信号,同样能达到上面函数实现的目的。

   //后台进程读取/写入终端输入产生下面两个信号,或者控制终端不存在情况读取和写入会产生
   signal(SIGTTOU, SIG_IGN);
   signal(SIGTTIN, SIG_IGN);

   //按CTRL-C ,CTRL-\ CTRL-Z会向前台进程组发送下面这些信号
   signal(SIGINT,  SIG_IGN );
   signal(SIGQUIT, SIG_IGN );
   signal(SIGTSTP, SIG_IGN );
   
   //终端断开,会给会话组长或孤儿进程组所有成员发送下面信号
   signal(SIGHUP,  SIG_IGN );

   还有有些信号也可以由终端shell产生,需要关注
   signal(SIGCONT, SIG_IGN );
   signal(SIGSTOP, SIG_IGN );

  上面这些信号,应该有些程序缺省处理(SIG_DFL)本身动作就是忽略(SIG_IGN),不是退出进程。不过按照上面写也不会造成什么问题。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值