zoukankan      html  css  js  c++  java
  • linux下的daemon进程

    http://www.cublog.cn/u3/100661/showart_2091898.html

    #include <unistd.h> 

    int daemon(int nochdir,int noclose) 

    在创建精灵进程的时候,往往需要将精灵进程的工作目录修改为"/"根目录 
    并且将标准输入,输出和错误输出重定向到/dev/null 

    daemon的作用就是当参数nochdir为0时,将根目录修改为工作目录 
    noclose为0时,做输入,输出以及错误输出重定向到/dev/null 

    执行成功返回0 
    错误返回-1 
     
    <----------------------------------------->

    浅析daemon精灵守护进程创建背后的故事

    #include <unistd.h>
    int main(int argc, char *argv[])
    {
        ...
        if (daemon(0, 0)) {//调用glibc库函数daemon,创建daemon守护进程
            perror("daemon");
            return -1;
        }
        好了执行到这里的就是daemon的子进程了[luther.gliethttp].
        ...
    }
    =========================================================
    int daemon( int nochdir, int noclose )
    {
       pid_t pid;

       if ( !nochdir && chdir("/") != 0 )//如果nochdir=0,那么改变到"/"根目录
           return -1;
       
       if ( !noclose )//如果没有close
       {
         int fd = open("/dev/null", O_RDWR);//打开空洞文件.

         if ( fd < 0 )
          return -1;

    //对于每个进程,它的fds文件描述符表中:0,1和2文件句柄位置对应的fops文件操作函数集,
    //fdt->fd[0],fdt->fd[1],fdt->fd[2],
    //规定将分别与标准输入,标准输出,标准错误输出相关联[luther.gliethttp].
    //所以用户应用程序调用open函数打开文件时,默认都是以3索引为开始句柄,故当前open返回的文件句柄最小值为3[luther.gliethttp].
    //dup2(unsigned int oldfd, unsigned int newfd)系统调用就是用oldfd的fops操作文件集file,复制到newfd所在处
    //即:fdt->fd[newfd] = fdt->fd[oldfd];
         if ( dup2( fd, 0 ) < 0 || //使用字符设备/dev/null的fops函数操作集,替换0句柄对应的文件操作集.
         dup2( fd, 1 ) < 0 || //使用字符设备/dev/null的fops函数操作集,替换1句柄对应的文件操作集.
              dup2( fd, 2 ) < 0 ) //使用字符设备/dev/null的fops函数操作集,替换2句柄对应的文件操作集.
         {
           close(fd);
          return -1;
         }
    //如果上面替换成功,那么键盘的任何操作将不会对该进程产生任何影响,因为0,1,2句柄所在处的fops文件操作集已经都变成了,

    //被重定向为"/dev/null"空洞设备的fops.所以对0,1,2句柄的读写操作,也就是在对/dev/null设备作读写操作.
         close(fd);//关闭打开的/dev/null
      }
      
       pid = fork();//创建子进程.
       if (pid < 0)
        return -1;

       if (pid > 0)
        _exit(0);//返回执行的是父进程,那么父进程退出,让子进程变成真正的孤儿进程.

    //ok,我们期望的daemon子进程执行到这里了.
       if ( setsid() < 0 )//设置session id.
         return -1;

       return 0;//成功创建daemon子进程[luther.gliethttp].
    }

    http://docs.linuxtone.org/ebooks/C&CPP/c/ch34s03.html

    http://blog.csdn.net/yyyzlf/article/details/5267954

     

    由于守护进程的特点,编写守护进程程序必须遵守一定的规则。本节将阐述这些规则的要点,并给出相关代码。

    8.2.1  实现守护进程的步骤

    在Linux系统中,要编程实现一个守护进程必须遵守如下的步骤。

    1.让init进程成为新产生进程的父进程。

    调用fork函数创建子进程后,使父进程立即退出。这样,产生的子进程将变成孤儿进程,并被init进程接管,同时,所产生的新进程将变为在后台运行。

    2.调用setsid函数

    通过调用setsid函数,使得新创建的进程脱离控制终端,同时创建新的进程组,并成为该进程组的首进程。为了使读者更好地理解这一步骤,下面介绍进程组、会话(session)的基本概念。

    在Linux系统中,所有的进程都属于各自的进程组。进程组是一个或多个进程的集合。打个比方,可以认为某个班级是一个进程组,而其中成员就是进程。一个班级至少有一个成员。当一个班级的最后一个成员不存在的时候,这个班级也就不存在了,也就是进程组消亡了。

    每个进程组都有类似于进程号的标识,称为进程组ID。进程组ID是由领头进程的进程号决定的,每个进程组都存在一个领头进程。进程组的存在与否与领头进程是否存在没有关系。

    会话是一个或多个进程组的集合。与进程组类似,每个会话都存在一个领头进程。Linux是一个多用户的操作系统,在同一时刻系统中会存在属于不同用户的多个进程。如果用户在某个终端上发送了某个信号,例如,按下“Ctrl+C”发送SIGINT信号,如何确保信号被正确地发送到对应的进程,同时不会影响使用其他终端的用户的进程?

    会话和进程组是Linux内核用于管理多用户情况下用户进程的方法。每个进程都属于一个进程组,而进程组又属于某个会话。当用户从终端登录系统(不管是终端还是伪终端),系统会创建一个新的会话。在该终端上启动的进程都会被系统划归到会话的进程组中。

    会话中的进程通过该会话中的领头进程(常称其为控制进程)与一个终端相连。该终端是会话的控制终端。一个会话只能有一个控制终端,反之一样。如果会话存在一个控制终端,则它必然拥有一个前台进程组。属于该组的进程可以从控制终端获得输入。这时,其他的进程组都为后台进程组。图8.3所示为会话、进程组、进程与控制终端之间的关系。

    图8.3  会话、进程组、进程与控制终端的关系

    由于守护进程没有控制终端,而使用fork函数创建的子进程继承了父进程的控制终端、会话和进程组,因此,必须创建新的会话,以脱离父进程的影响。Linux系统提供了setsid函数用于创建新的会话。setsid函数的信息如表8.1所示。

    表8.1                                                              setsid函数

    头文件

    <unistd.h>

    函数形式

    pid_t setsid(void);

    返回值

    成功

    失败

    是否设置errno

    调用进程的会话ID

    −1

    setsid函数将创建新的会话,并使得调用setsid函数的进程成为新会话的领头进程。调用setsid函数的进程是新创建会话中的惟一的进程组,进程组ID为调用进程的进程号。setsid函数产生这一结果还有个条件,即调用进程不为一个进程的领头进程。由于在第一步中调用fork的父进程退出,使得子进程不可能是进程组的领头进程。该会话的领头进程没有控制终端与其相连。至此,满足了守护进程没有控制终端的要求。

    3.更改当前工作目录

    使用fork函数产生的子进程将继承父进程的当前工作目录。当进程没有结束时,其工作目录是不能被卸载的。为了防止这种问题发生,守护进程一般会将其工作目录更改到根目录下(/目录)。更改工作目录使用的函数是chdir。

    4.关闭文件描述符,并重定向标准输入、输出和错误输出

    新产生的进程从父进程继承了某些打开的文件描述符,如果不使用这些文件描述符,则需要关闭它们。守护进程是运行在系统后台的,不应该在终端有任何的输出信息。可以使用dup函数将标准输入、输出和错误输出重定向到/dev/null设备上(/dev/null是一个空设备,向其写入数据不会有任何输出)。下面给出具体的代码:

          int fd;

          //将标准输入输出重定向到空设备

          fd = open ("/dev/null", O_RDWR, 0);

          if (fd != -1)

        {

          dup2 (fd, STDIN_FILENO);

          dup2 (fd, STDOUT_FILENO);

          dup2 (fd, STDERR_FILENO);

          if (fd > 2)

            close (fd);

        }

    5.设置守护进程的文件权限创建掩码

    很多情况下,守护进程会创建一些临时文件。出于安全性的考虑,往往不希望这些文件被别的用户查看。这时,可以使用umask函数修改文件权限,创建掩码的取值,以满足守护进程的要求。

    8.2.2  守护进程具体实现

    本节给出一个守护进程创建的实例。程序p8.1.c中定义了daemon函数,用于实现对守护进程的创建。其创建思想在8.2.1中有详细的介绍,程序的具体代码如下:

    //p8.1.c 守护进程的实现

    #include <stdio.h>

    #include <unistd.h>

    #include <sys/types.h>

    #include <sys/stat.h>

    #include <fcntl.h>

    /* daemon函数用于将调用函数的进程转化为守护进程 */

    int

    daemon (int nochdir, int noclose)

    {

      pid_t pid;

      pid = fork ();

      /* 如果创建进程失败 */

      if (pid < 0)

        {

          perror ("fork");

          return -1;

        }

      /* 父进程退出运行 */

      if (pid != 0)

        exit (0);

      /* 成为会话领头进程 */

      pid = setsid();

      if (pid < -1)

        {

          perror ("setsid");

          return -1;

        }

      /* 将工作目录修改成根目录 */

      if (! nochdir)

        chdir ("/");

      /* 将标准输入输出重定向到空设备 */

      if (! noclose)

        {

          int fd;

          fd = open ("/dev/null", O_RDWR, 0);

          if (fd != -1)

        {

          dup2 (fd, STDIN_FILENO);

          dup2 (fd, STDOUT_FILENO);

          dup2 (fd, STDERR_FILENO);

          if (fd > 2)

            close (fd);

        }

        }

      umask (0027);

      return 0;

    }

    int main(void)

    {

          daemon(0,0);

          sleep(1000);

       

          return 0;

    }  

    使用gcc编译p8.1.c,得到名为p8.1的可执行文件。执行该程序,程序将以守护进程的状态运行,如图8.4所示。

  • 相关阅读:
    Struts2SpringHibernate整合示例,一个HelloWorld版的在线书店(项目源码+详尽注释+单元测试)
    Java实现蓝桥杯勇者斗恶龙
    Java实现 LeetCode 226 翻转二叉树
    Java实现 LeetCode 226 翻转二叉树
    Java实现 LeetCode 226 翻转二叉树
    Java实现 LeetCode 225 用队列实现栈
    Java实现 LeetCode 225 用队列实现栈
    Java实现 LeetCode 225 用队列实现栈
    Java实现 LeetCode 224 基本计算器
    Java实现 LeetCode 224 基本计算器
  • 原文地址:https://www.cnblogs.com/xuxm2007/p/2121280.html
Copyright © 2011-2022 走看看