zoukankan      html  css  js  c++  java
  • 守护进程(daemon)为什么要fork两次

    aemon进程是后台守护进程,有时候也叫精灵进程(agent).linux 下server都是daemon进程。相信大部分开发人员都知道如何去写一个daemon进程。但是另一方面,大部分人不知道为什么要这么做,不少人是从 某个地方copy一个函数,拿来主义。但是具体为什么这么实现,却不是很透彻。

     
      见过一些面试官或被面试人。很多人解释daemon进程存在的理由是因为僵死进程。或者输入输出。其实和这些东西一毛钱关系都没有。daemon函数存在 的原因是因为控制终端由于某些原因(如断开终端链接)会发送一些信号的原因。而接收进城处理这些信号缺省动作会让进程退出。这些信号会由于终端上敲一些特 殊按键而产生。
     
      贴一个daemon函数常见的实现:
    1. int daemon(void)
    2. {
    3.     pid_t pid = fork();
    4.     if( pid != 0 ) exit(0);//parent
    5.     //first children
    6.     if(setsid() == -1)
    7.     {
    8.        printf("setsid failed\n");
    9.        assert(0);
    10.        exit(-1);
    11.     }
    12.     umask(0);
    13.     pid = fork();
    14.     if( pid != 0) exit(0);
    15.     //second children
    16.     chdir ("/");
    17.     for (int i = 0; i < 3; i++)
    18.     {
    19.         close (i);
    20.     }
    21.     int stdfd = open ("/dev/null", O_RDWR);
    22.     dup2(stdfd, STDOUT_FILENO);
    23.     dup2(stdfd, STDERR_FILENO);
    24.     return 0;
    25. }
        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)。所以也无法打开新的控制终端。
     
        daemon目的就是防止终端产生的一些信号让进程退出。上面函数并没有直接调用signal函数去处理它。而是间接通过fork和setsid函数使用更少代码优雅处理。而被有些人误以为是僵死进程的原因需要这样处理。
     
        当然,也有很多程序不是像上面函数那样去实现。而是直接通过忽略信号方式处理。这样其实也不错,因为这些信号很少会有用到的价值。直接忽略基本上不存在误杀的情况。反正达到最终目的就可以。条条大路通罗马。
     
       下面罗列一下控制终端会产生哪些信号。程序中只要处理好这些信号,同样能达到上面函数实现的目的。
     
       //后台进程读取/写入终端输入产生下面两个信号,或者控制终端不存在情况读取和写入会产生
       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),不是退出进程。不过按照上面写也不会造成什么问题。
    下面这个程序,是本人练习时做的,可以参考下,希望对所有看到这篇博文的人有所帮助......
    1. #include <stdio.h>
    2. #include <unistd.h>
    3. #include <stdlib.h>
    4. #include <sys/types.h>
    5. #include <sys/stat.h>
    6. #include <time.h>
    7. int main()
    8. {
    9.     pid_t pid;
    10.     int i;
    11.     time_t t;
    12.     FILE *fp;
    13.     if((pid = fork()) < 0)
    14.     {
    15.         perror("fail to fork");
    16.         exit(-1);
    17.     }else if(pid > 0){
    18.         exit(0);
    19.     }
    20.     setsid();
    21.     if((pid = fork()) > 0)
    22.         exit(0);
    23.     else if(pid < 0){
    24.         exit(1);
    25.     }
    26.     for(i = 0;i < getdtablesize();++ i)
    27.         close(i);
    28.     chdir("/tmp");
    29.     umask(0);
    30.     while(1)
    31.     {
    32.         sleep(2);
    33.         if((fp = fopen("test.log","a")) != NULL)
    34.         {
    35.             t = time(0);
    36.             fprintf(fp,"i'm here at %s\n",asctime(localtime(&t)));
    37.             fclose(fp);
    38.         }
    39.     }
    40. }
  • 相关阅读:
    npm 学习笔记
    Splash 学习笔记
    lodash 学习笔记
    运用 CSS in JS 实现模块化
    运用 CSS methodologies 去实现模块化
    less 学习笔记
    初探爬虫 ——《python 3 网络爬虫开发实践》读书笔记
    mitmproxy 使用笔记
    Appium 使用笔记
    Selenium 与自动化测试 —— 《Selenium 2 自动化测试实战》读书笔记
  • 原文地址:https://www.cnblogs.com/sdgwc/p/2937233.html
Copyright © 2011-2022 走看看