《unix环境高级编程》--- 守护进程

守护进程也称精灵进程,是生存期较长的一种进程。它们常常在系统自举时启动,仅在系统关闭时才终止。因为它们没有
控制终端,所以说它们是在后台运行的。

编写守护进程规则
1、调用umask将文件模式创建屏蔽字设置为0。
2、调用fork,然后使父进程退出。
3、调用setsid创建一个新会话。使调用进程:a、成为新会话的首进程,b、成为一个新进程组的组长进程,c、没有控制终端。
4、将当前工作目录改为根目录。
5、关闭不再需要的文件描述符。
6、某些守护进程打开/dev/null使其具有文件描述符0、1、2,这样,任何一个试图读标准输入、写标准输出或标准出错的库 例程都不会产生任何效果。

#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", cmd);
    if((pid = fork()) < 0)
        err_quit("%s: can't fork", cmd);
    else if(pid != 0)  /* parent */
        exit(0);

    /* Change the current working diretory to the root so
       we won't prevent file systems from being unmounted */
    if(chdir("/") < 0)
        err_quit("%s: can't change directory to /", cmd);

    /* 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, 2 to /dev/null */
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);

    /* Initialize the log file */
    /*
       void openlog(const char *ident, int option, int facility);
       ident:程序名称。
       option:指定许多项的位屏蔽。
               LOG_CONS---若日志消息不能通过UNIX域数据报送至syslogd,
                   则将该消息写至控制台。
       facility:目的是可以让配置文件说明,来自不同设施的消息将以不同方式进行处理。
                 LOG_DAEMON---系统守护进程。
    */
    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("cron");
    pause();
    return 0;
}

结果
这里写图片描述
这里写图片描述
用ps命令验证,没有一个活动线程的ID是2625,说明守护进程在一个孤儿进程组中,它不是一个会话首进程,于是不会有机会分配到
一个控制终端。这是由第二个fork造成的。可见此守护进程已被正确初始化。

保证只运行某个守护进程的一个副本
文件锁和记录锁时可用来保证一个守护进程只有一个副本在运行。如果每个守护进程创建一个文件,并且在整个文件上加上一把写锁,那就只允许创建一把这样的写锁,所以在此之后如试图创建一把这样的写锁就将失败。当该守护进程终止时,这把锁将被自动删除。

single.h

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

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

/*extern int lockfile(int);*/

int already_running(void)
{
    int fd;
    char buf[16];

    fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE);
    if(fd < 0)
    {
        /*
           void openlog(const char *ident, int option, int facility);
           void syslog(int priority, const char *fromat, ...);
           如果不调用openlog,第一次调用syslog时,自动调用openlog
           priority:facility和level的组合。level--LOG_ERR--出错状态。
        */
        syslog(LOG_ERR, "can't open %s: %s", LOCKFILE, strerror(errno));
        exit(1);
    }

    /*
       int lockf(int fd, int cmd, off_t len);
       如果 len 为0,则锁定从当前偏移量到文件结尾的区域
       #define F_LOCK 1 //互斥锁定区域
    */
    if(lockf(fd, F_TLOCK, 0) < 0)
    {
        if(errno == EACCES || errno == EAGAIN)
        {
            close(fd);
            return (1);
        }
        syslog(LOG_ERR, "can't lock %s: %s", LOCKFILE, strerror(errno));
        exit(1);
    }
    ftruncate(fd, 0);
    sprintf(buf, "%ld", (long)getpid());
    write(fd, buf, strlen(buf)+1);
    return (0);
}

守护进程的惯例
1、若守护进程使用锁文件,那么该文件通常存放在/var/run目录中,文件名通常为name.pid。
2、若守护进程支持配置选项,那么配置文件通常存放在/etc目录中,文件名通常为name.conf。
3、守护进程可用命令行启动,但通常它们是由系统初始化脚本之一启动的(/etc/rc*或/etc/init.d/*)。
4、若一守护进程有一配置文件,那么当该守护进程启动时,它读该文件,但此之后一般不会再查看它。

守护进程重读配置文件
若管理员更改了配置文件,那么守护进程可能需要被停止,然后再启动,以使配置文件的更改生效。为避免此种麻烦,某些守护进程将捕捉SIGHUP信号,当接收到该信号时,重读配置文件。因为守护进程并不与终端结合,可能是无控制终端的会话首进程,可能是孤儿进程组的成员,所以守护进程不期望接收SIGHUP。

使用sigwait及多线程重读配置文件。
1、调用daemonize初始化守护进程。
2、调用alread_runing确保该守护进程只有一个副本在运行。
3、SIGHUP信号仍被忽略,需恢复对信号SIGHUP的系统默认处理方式,否则调用sigwait的线程不会见到该信号。
4、阻塞所有信号,然后创建一线程来处理信号,该线程只需等待SIGHUP和SIGTERM。当收到SIGHUP信号时,调用reread重读配置文件。当收到SIGTERM时,记录一消息,然后终止。

#include "apue.h"
#include <pthread.h>
#include <syslog.h>
#include "single.h"

sigset_t mask;

void reread(void)
{
    /* ... */
}

void *thr_fn(void *args)
{
    int err, signo;
    for(;;)
    {
        err = sigwait(&mask, &signo);
        if(err != 0)
        {
            syslog(LOG_ERR, "sigwait failed");
            exit(1);
        }
        switch(signo)
        {
        case SIGHUP:
            syslog(LOG_INFO, "Re-reading configuration file");
            reread();
            break;
        case SIGTERM:
            syslog(LOG_INFO, "got SIGTERM; exiting");
            exit(0);
        default:
            syslog(LOG_INFO, "unexpected signal %d\n", signo);
        }
    }
    return (0);
}

int main(int argc, char *argv[])
{
    int err;
    pthread_t tid;
    char *cmd;
    struct sigaction sa;

    if((cmd = strchr(argv[0], '/')) == NULL)
        cmd = argv[0];
    else
        cmd++;

    /* Become a daemon */
    daemonize(cmd);

    /* Make sure only one copy of the daemon is running */
    if(already_running())
    {
        syslog(LOG_ERR, "daemon already running");
        exit(1);
    }

    /* Restore SIGHUP default and block all signals */
    sa.sa_handler = SIG_DFL;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if(sigaction(SIGHUP, &sa, NULL) < 0)
        err_quit("%s: can't restore SIGHUP default");
    sigfillset(&mask);
    if((err = pthread_sigmask(SIG_BLOCK, &mask, NULL)) != 0)
        err_exit(err, "SIG_BLOCK err");

    /* Create a thread to handle SIGHUP and SIGTERM */
    err = pthread_create(&tid, NULL, thr_fn, 0);
    if(err != 0)
        err_exit(err, "can't create thread");

    /* Proceed with the rest of the daemon */
    /* ... */
    exit(0);

}

守护进程重读配置文件的另一种实现
守护进程无需使用多线程也可以捕捉SIGHUP并重读其配置文件。
在初始化守护进程后,为SIGHUP和SIGTERM配置信号处理程序。可将重读逻辑放在信号处理程序中,也可只在其中设置一个标志,有守护进程的主线程做所需的工作。

#include "apue.h"
#include <syslog.h>
#include <errno.h>
#include "single.h"

void reread(void)
{
    /* ... */
}

void sigterm(int signo)
{
    syslog(LOG_INFO, "got SIGTERM; exiting");
    exit(0);
}

void sighup(int signo)
{
    syslog(LOG_INFO, "Re-reading configuration file");
    reread();
}

int main(int argc, char *argv[])
{
    char *cmd;
    struct sigaction sa;

    if((cmd = strchr(argv[0], '/')) == NULL)
        cmd = argv[0];
    else
        cmd++;

    /* Become a daemon */
    daemonize(cmd);

    /* Make sure only one copy of the daemon is running */
    if(already_running())
    {
        syslog(LOG_ERR, "daemon already running");
        exit(1);
    }

    /* Handle signals of interest */
    sa.sa_handler = sigterm;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGHUP);
    sa.sa_flags = 0;
    if(sigaction(SIGTERM, &sa, NULL) < 0)
    {
        syslog(LOG_ERR, "can't catch SIGTERM: %s", strerror(errno));
        exit(1);
    }
    sa.sa_handler = sighup;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGTERM);
    sa.sa_flags = 0;
    if(sigaction(SIGHUP, &sa, NULL) < 0)
    {
        syslog(LOG_ERR, "can't catch SIGHUP: %s", strerror(errno));
        exit(1);
    }

    /* Proceed with the rest of the daemon */
    /* ... */
    exit(0);
}

猜你喜欢

转载自blog.csdn.net/u012319493/article/details/80481263