APUE学习笔记——第十三章 守护进程

1、编程规则
(1)首先调用umask将文件模式创建屏蔽字设置为0.由继承得来的文件模式创建屏蔽字可能会拒绝设置某些权限。例如:若守护进程要创建一个组可读、写的文件,而继承的文件模式创建屏蔽字可能屏蔽了这两种权限,于是所要求的组可读、写就不能起作用。
(2)调用fork,然后使父进程退出(exit)。这样做实现了下面几点:第一,如果该守护进程是作为一条简单shell命令启动的,那么父进程终止使得shell认为这条命令已经执行完毕;第二,子进程继承了父进程的进程组ID,但具有一个新的进程ID,这就保证了子进程不是一个进程组的组长进程。这对于接下来setsid调用是必要的前提条件。
(3)调用setsid以创建一个新会话。创建新会话实现:该进程变成新会话首进程;该进程成为一个新进程组的组长进程;该进程没有控制终端
(4)将当前工作目录更改为根目录。因为守护进程通常在系统引导之前是一直存在的。
(5)关闭不再需要的文件描述符。这使得守护进程不再持有从父进程继承来的某些文件描述符。可以使用open_max函数或getrlimit函数来判断最高文件描述符值,并关闭直到该值的所有描述符。
(6)某些守护进程打开/dev/null使其具有文件描述符0、1和2。使得任何一个试图读标准输入、写标准输出或者标准出错的历程都不会产生任何效果。

(7)处理SIGCHLD信号 。处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。

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

void daemonize(const char*cmd){
    int i,fd0,fd1,fd2;
    pid_t pid;
    struct rlimit rlt;
    struct sigaction act;
    
    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGHUP,&act,NULL);
    
    umask(0);
    if((pid = fork()) == -1){
        perror("fork error");
        exit(-1);
    }else if(pid > 0){
        exit(0);
    }
    setsid();
    chdir("/");
    getrlimit(RLIMIT_NOFILE,&rlt);
    if(rlt.rlim_max == RLIM_INFINITY)
        rlt.rlim_max = 1024;
    for(i = 0 ; i < rlt.rlim_max ; i ++)
        close(i);
    fd0 = open("/dev/null",O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);
    
    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(){
    daemonize("w");
    sleep(50);
    exit(0);
}

2、出错处理
守护进程没有控制终端,不能将错误写到标准输错上。大多数进程使用集中的守护进程出错syslog设施,该设施的接口是syslog函数
#include <syslog.h> 
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void); 
int setlogmask(int mask);
#include <stdarg.h> 
void vsyslog(int priority, const char *format, va_list ap);

下面各种就看不懂了。

3、单实例守护进程

为了正常运作,某些守护进程实现为单实例,也就是在任一时刻只运行该守护进程的一个副本。文件锁和记录锁机制是这种方法的基础该方法用来保证一个守护进程只有一个副本在运行,如果每一个守护进程创建一个文件,并且在整个文件上加上一把写锁,那么就只允许创建一把这样的写锁,此后如试图再创建这样一把写锁就将失败,以此向后续守护进程副本指明已有一个副本正在运行。

#define LOCKFILE "/var/run/daemon.pid"
#define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)


int lockfile(int fd){
    struct flock fk;
    fk.l_type = F_WRLCK;
    fk.l_start = 0;
    fk.l_whence = SEEK_SET;
    fk.l_len = 0;
    return fcntl(fd,F_SETLK,&fk);
}

int already_running(){
    int fd;
    char buf[16];
    fd = open(LOCKFILE,O_RDWR | O_CREAT,LOCKMODE);
    if(fd < 0){
        syslog(LOG_ERR,"Can't open %s: %s\n",LOCKFILE,strerror(errno));
        exit(1);
    }
    if(lockfile(fd) < 0){
        if(errno == EACCES || errno == EAGAIN){
            close(fd);
            return 1;
        }
        syslog(LOG_ERR,"Can't lock %s: %s\n",LOCKFILE,strerror(errno));
        exit(1);
    }
    ftruncate(fd,0);
    sprintf(buf,"%ld",(long)getpid());
    write(fd,buf,strlen(buf)+1);
    return 0;
}
1. 守护进程的每个副本都试图建立一个文件,并将进程ID写入该文件中。如果该文件已经加了锁,那么lockfile函数将失败,errno设置为EACCSS或EAGAIN,函数返回1,表明该进程已经运行在。
2. ftruncate(fd, 0)函数是将文件长度截断为0。因为以前守护进程实例的进程ID字符串可能长于调用此函数的当前进程的进程ID字符串。

5、守护进程惯例

(1)若守护进程使用锁文件,那么该文件通常存放在/var/run目录中。
(2)若守护进程支持配置选项,那么配置文件通常存放在/etc目录中,配置文件的名字通常是name.conf,name就是该守护进程或服务的名字
(3)守护进程可以用命令行启动
(4)若一守护进程有一配置文件,那么当该守护进程启动时,它读该文件,但在此之后一般不会再查看它。若管理员更改了配置文件,那么该守护进程可能需要被重启,以使配置文件更改生效。为避免这种麻烦,某些守护进程将捕捉SIGHUP信号,当他们接收到该信号时,重新读取配置文件。因为守护进程不与终端相结合,或者是无控制终端的会话首进程,或者是孤儿进程组的成员,所以守护进程并不期望接受SIGHUP信号。于是,他们可以安全的重复使用它。

下面给出重读配置文件的程序:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>

#define LOCKFILE "/home/xkey/APUE/13/daemon.pid"
#define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)

sigset_t mask;

int lockfile(int fd){
    struct flock fk;
    fk.l_type = F_WRLCK;
    fk.l_start = 0;
    fk.l_whence = SEEK_SET;
    fk.l_len = 0;
    return fcntl(fd,F_SETLK,&fk);
}

int already_running(){
    int fd;
    char buf[16];
    fd = open(LOCKFILE,O_RDWR | O_CREAT,LOCKMODE);
    if(fd < 0){
        syslog(LOG_ERR,"Can't open %s: %s\n",LOCKFILE,strerror(errno));
        exit(1);
    }
    if(lockfile(fd) < 0){
        if(errno == EACCES || errno == EAGAIN){
            close(fd);
            return 1;
        }
        syslog(LOG_ERR,"Can't lock %s: %s\n",LOCKFILE,strerror(errno));
        exit(1);
    }
    ftruncate(fd,0);
    sprintf(buf,"%ld",(long)getpid());
    write(fd,buf,strlen(buf)+1);
    return 0;
}

void daemonize(const char*cmd){
    int i,fd0,fd1,fd2;
    pid_t pid;
    struct rlimit rlt;
    struct sigaction act;
    
    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGHUP,&act,NULL);
    
    umask(0);
    if((pid = fork()) == -1){
        perror("fork error");
        exit(-1);
    }else if(pid > 0){
        exit(0);
    }
    setsid();
    chdir("/");
    getrlimit(RLIMIT_NOFILE,&rlt);
    if(rlt.rlim_max == RLIM_INFINITY)
        rlt.rlim_max = 1024;
    for(i = 0 ; i < rlt.rlim_max ; i ++)
        close(i);
    fd0 = open("/dev/null",O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);
    
    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);
    }
}

void reread(){
    puts("read daemo config file again.");
}

void* thread_func(void* arg){
    //sigset_t *mask = (sigset_t*)arg;
    printf("mask %d\n",mask);
    int err,signo;
    while(1){
        sigwait(&mask,&signo);
        switch(signo){
            case SIGHUP:
                syslog(LOG_INFO,"Rereading configuration file.\n");
                reread();
                break;
            case SIGTERM:
                syslog(LOG_INFO,"Get SIGTERM;exiting.\n");
                exit(0);
            default:
                syslog(LOG_INFO,"Unexpected signal %d.\n",signo);
        }
    }
}

void sighup(int signo){
    printf("sighup %d\n",signo);
    reread();
}

void sigterm(int signo){
    printf("sigterm %d\n",signo);
    exit(0);
}

int main(int argc,char* argv[]){
    pthread_t tid;
    struct sigaction act;
    char* cmd;
    if((cmd = strrchr(argv[0],'/'))==NULL)
         cmd = argv[0];
    else cmd++;
    sleep(5);
    printf("%s\n",cmd);
    daemonize(cmd);
    if(already_running()){
        syslog(LOG_ERR,"Daemon already running.\n");
        exit(1);
    }
    //act.sa_handler = SIG_DFL;//多线程的方法
    //act.sa_flags = 0;
    //sigemptyset(&act.sa_mask);
    //sigaction(SIGHUP,&act,NULL);
    //sigset_t mask;
    //sigfillset(&mask);
    //pthread_sigmask(SIG_BLOCK,&mask,NULL);
    //pthread_create(&tid,NULL,thread_func,0);
    //
    
    act.sa_handler = sighup;//简单信号
    act.sa_flags = 0;
    sigaddset(&act.sa_mask,SIGTERM);//注意
    sigaction(SIGHUP,&act,NULL);
    
    act.sa_handler = sigterm;
    act.sa_flags = 0;
    sigaddset(&act.sa_mask,SIGHUP);//注意
    sigaction(SIGTERM,&act,NULL);
    sleep(60);
    exit(0);
}


你可能感兴趣的:(APUE学习笔记——第十三章 守护进程)