《UNIX环境高级编程》笔记--守护进程

1.守护进程编程规则

守护进程也被称为精灵进程(daemon),是生存期较长的一种进程。常常在系统启动时启动,仅在系统关闭时才终止,因为

它没有控制终端,所以说他它们是在后台运行的。

编写守护进程程序时需要遵循一些基本规则,以便防止产生并不需要的交互作用,下面先说明这些规则:

1.调用umask将文件模式创建屏蔽字设置为0。因为由继承的来的文件模式创建屏蔽字可能会拒绝设置某些权限。

2.调用fork,然后使父进程退出。这样做实现了下面几点:第一,如果该守护进程是作为一条简单shell命令启动的,那么父进程

终止使得shell认为这几条命令已经执行完毕。第二,子进程继承了父进程的进程组ID,但具有一个新的进程ID,这就保证了子

进程不是一个进程组的组长进程,这对于下面就是做的setsid调用是必要的前提条件。

3.调用setsid以创建一个新会话。使调用进程:a.成为新会话的首进程,b.成为一个新进程组的组长进程,c.没有控制终端。

4.将当前工作目录更改为目录。

5.关闭不需要的文件描述符。这使守护进程不再持有从其父进程继承来的某些文件描述符。

6.某些守护进程打开/dev/null使其具有文件描述符0,1和2,这样,任何一个试图读标准输入,写标准输出或标准出错的库例程都

不会产生任何效果。


实践:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <signal.h>
#include <syslog.h>

void daemonize(const char *cmd){
        int i,fd0,fd1,fd2;
        pid_t pid;
        struct rlimit rl;
        struct sigaction sa;

        umask(0);

        //get maximum number of file descriptors
        if(getrlimit(RLIMIT_NOFILE,&rl) <0){
                perror("getlimit");
                return;
        }

        //become a session leader to lose controlling TTY
        if((pid = fork()) < 0){
                perror("fork");
                return;
        }else if(pid > 0){
                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){
                perror("sigaction");
                return;
        }
        if((pid = fork()) < 0){
                perror("fork");
                return;
        }else if(pid > 0){
                exit(0);
        }

        //change the current working dir to the root
        if(chdir("/") < 0){
                perror("chdir");
                return;
        }

        //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 descriptor 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);
        }
}

int main(void){
        daemonize("dtest");
        while(1){
                sleep(1);
        }
}
运行结果:

 PPID   PID       PGID        SID        TTY      TPGID   STAT   UID   TIME    COMMAND

    1      20725  20724     20724      ?             -1            S        0      0:00        ./a.out

程序里有以下几点需要说明下:

1.为什么进程调用setsid失去控制终端后,还可以使用perror函数打印出错信息到控制台呢?

答: fork()后再调用setsid(),还是可以读写“原”终端的,因为fork()后从父进程继承了文件描述符,所以对“原”终端依然可以

读写。


2.为什么要进行第二次fork?

 在基于系统V的系统中,有些人建议在此时再次调用fork,使父进程终止。第二个子进程作为守护进程继续运行。这就保证了

该守护进程不是会话首进程,于是按照系统V规则可以防止它取得控制终端。避免取得控制终端的另一种方法是,无论何时打开

一个终端设备都一定要指定O_NOTTY。


2.单实例守护进程

为了正常运行,某些守护进行实现为单例,也就是在任一时刻只运行该守护进程的一个副本。这可以通过文件和记录锁来实现,

每个守护进程创建一个文件,并且在整个文件上加上一把写锁,那就只允许创建一把这样的写锁,所以在此以后如试图再创建

一把这样的写锁就会失败。从此向后续守护进程指明已有一个副本正在运行。同时在文件中还会写入运行副本的pid,可以定位

到该进程。文件和记录锁提供了一种方便的互斥机制。如果守护进程在整个文件上得到一把写锁,那么在该守护进程终止时,

这把锁会被自动删除。


3.守护进程的惯例

a.若守护进程使用锁文件,那么该文件通常存放在/var/run目录下,锁文件名字通常为name.pid,其中name是该守护进程或者

服务的名字。

b.若守护进程支持配置选项,那么配置文件通常存放在/etc目录下,配置文件名称通常是name.conf,其中name是该守护进程

或服务的名字。

c.守护进程可用命令行(/etc/rc*或者/etc/init.d/*)启动.

d.若一守护进程有一配置文件,那么当该守护进程启动时,它读取文件单在此之后一般不会再查看它。所以我们修改了配置文件

后都要重启该服务。

你可能感兴趣的:(《UNIX环境高级编程》笔记--守护进程)