• 守护进程之编程规则


    在编写守护进程时需遵循一些基本规则,以便防止产生并不需要的交互作用。下面先说明这些规则,然后给出一个按照这些规则编写的函数daemonize。

    (1)首先要做的是调用umask将文件模式创建屏蔽字设置为0。由继承得来的文件模式创建屏蔽字可能会拒绝设置某些权限。例如,若守护进程要创建一个组可读、写的文件,而继承的文件模式创建屏蔽字可能屏蔽了这两种权限,于是所要求的组可读、写就不能起作用。

    (2)调用fork,然后使父进程退出(exit)。这样做实现了下面几点:第一,如果该守护进程是作为一条简单shell命令启动的,那么父进程终止使得shell认为这条命令已经执行完毕(也就没有了控制终端);第二,子进程继承了父进程的进程组ID,但具有一个新的进程ID,这就保证了子进程不是一个进程组的组长进程。这对于下面就要做的setsid调用是必要的前提条件。

    (3)调用setsid以创建一个新会话。于是执行http://www.cnblogs.com/nufangrensheng/p/3513400.html中列举的三个操作,使调用进程:(a)成为新会话的首进程,(b)成为一个新进程组的组长进程,(c)没有控制终端。

    在基于系统V的系统中,有些人建议在此时再次调用fork,并使父进程终止。第二个子进程作为守护进程继续运行。这样就保证了该守护进程不是会话首进程,于是按照系统V规则(见http://www.cnblogs.com/nufangrensheng/p/3513443.html)可以防止它取得控制终端。避免取得控制终端的另一种方法是,无论何时打开一个终端设备都一定要指定O_NOCTTY。

    (4)将当前工作目录更改为根目录。从父进程出继承过来的当前工作目录可能在一个挂载的文件系统(a mounted file system)中。因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录在一个挂载的文件系统中,那么该文件系统就不能被卸载。这与挂载文件系统的原意不符。

    另外,某些守护进程可能会把当前工作目录更改到某个指定位置,在那里做它们的工作,例如,行式打印机假脱机守护进程常常将其工作目录更改到它们的spool目录上。

    (5)关闭不再需要的文件描述符。这使守护进程不再持有从其父进程继承来的某些文件描述符(父进程可能是shell进程,或某个其他进程)。可以使用http://www.cnblogs.com/nufangrensheng/p/3496323.html中程序清单2-4中的open_max函数或getrlimit函数(http://www.cnblogs.com/nufangrensheng/p/3509262.html)来判定最高文件描述符值,并关闭直到该值的所有描述符。

    (6)某些守护进程打开/dev/null使其具有文件描述符0、1和2,这样,任何一个试图读标准输入、写标准输出和标准出错的库例程都不会产生任何效果。因为守护进程并不与终端设备相关联,所以不能在终端设备上显示其输出,也无处从交互式用户那里接受输入。即使守护进程是从交互式会话启动的,但因为守护进程是在后台运行的,所以登录会话的终止并不影响守护进程。如果其他用户在同一终端设备上登录,我们也不会在该终端上见到守护进程的输出,用户也不可期望他们在终端上的输入会由守护进程读取。

    实例

    程序清单13-1是个函数,可由想初始化成为一个守护进程的程序调用。

    程序清单13-1 初始化一个守护进程

    #include "apue.h"
    #include <syslog.h>
    #include <fcntl.h>
    #include <sys/resource.h>
    
    void
    daemonize(const char *cmd)
    {
        int                  i, fd0, fd1, fd2;
        pid_t                pid;
        struct rlimit        rl;
        struct sigaction     sa;
        
        /*
        * Clear file creation mask.
        */
        umask(0);
    
        /*
        * Get maximum number of file descriptors.
        */
        if(getrlimit(RLIMIT_NOFILE, &rl) < 0)
            err_quit("%s: can't get file limit", cmd);
        
        /*
        * Become a session leader to lose controlling TTY.
        */
        if((pid = fork()) < 0)
            err_quit("%s: can't fork", cmd);
        else if (pid != 0)    /* parent */
            exit(0);
        setsid();
    
        /*
        * Ensure future opens won't allocate controlling TTYs.
        */
        sa.sa_handler = SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        if(sigaction(SIGHUP, &sa, NULL) < 0)
            err_quit("%s: can't ignore SIGHUP");
        if((pid = fork()) < 0)
            err_quit("%s: can't fork", cmd);
        else if( pid != 0 )    /* parent */
            exit(0);
    
        /*
        * Change the current working directory to the root so 
        * we won't prevent file system from being unmounted.
        */
        if(chdir("/") < 0)
            err_quit("%s: can't change directory to /");
        
        /*
        * Close all open file descriptors.
        */
        if(rl.rlim_max = RLIM_INFINITY)
            rl.rlim_max = 1024;
        for(i = 0; i < rl.rlim_max; i++)
            close(i);
    
        /*
        * Attach file descriptors 0, 1, and 2 to /dev/null.
        */    
        fd0 = open("/dev/null", O_RDWR);
        fd1 = dup(0);
        fd2 = dup(0);
    
        /*
        * Initialize the log file.
        */
        openlog(cmd, LOG_CONS, LOG_DAEMON);
        if(fd0 != 0 || fd1 != 1 || fd2 != 2)
        {
            syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
            exit(1);
        }
    }

    本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

  • 相关阅读:
    oracle 查找或删除重复记录的语句
    多线程案例
    JAVA调用增删改的存储过程
    设计中最常用的CSS选择器
    ORACLE多表查询优化
    oracle存储过程的事务处理
    oracle函数调用存储过程
    oracle存储过程的基本语法
    java.lang.OutOfMemoryError: Java heap space解决方法
    文件操作工具类
  • 原文地址:https://www.cnblogs.com/nufangrensheng/p/3544104.html
Copyright © 2020-2023  润新知