创建单实例守护进程

原文链接


1 完整程序:单实例守护进程

根据APUE的介绍,创建守护进程基本需要如下7个步骤。需要注意的是由于守护进程没有TTY(控制终端),所以代码中部分特意写上去的printf语句是不会输出到终端界面上的。

/**
 * @FileName    daemon_process.c
 * @Describe    A simple example for creating a single object of daemon process in linux.
 * @Author      vfhky 2016-03-14 17:52 https://typecodes.com/cseries/apuesingledaemonprocess.html
 * @Compile     gcc daemon_process.c -o daemon_process
 * @Reference   program list 13-1 in APUE.
 */
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <errno.h>

#define PRINT_PID() printf( "Row[%d]: getpid=[%d].\n", __LINE__, getpid() )
//守护进程对应的用户必须对该文件具有访问权限
#define LOCK_FILE "/home/vfhky/daemon_process.pid"
#define MAXLINE 1024


/**
 * Print a message and return to caller.
 * Caller specifies "errnoflag".
 */
static void err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
    char    buf[MAXLINE];

    vsnprintf(buf, MAXLINE, fmt, ap);
    if (errnoflag)
        snprintf(buf+strlen(buf), MAXLINE-strlen(buf), ": %s",
                 strerror(error));
    strcat(buf, "\n");
    fflush(stdout);     /* in case stdout and stderr are the same */
    fputs(buf, stderr);
    fflush(NULL);       /* flushes all stdio output streams */
}

/**
 * Fatal error unrelated to a system call.
 * Print a message and terminate.
 */
void err_quit(const char *fmt, ...)
{
    va_list     ap;

    va_start(ap, fmt);
    err_doit(0, 0, fmt, ap);
    va_end(ap);
    exit(1);
}


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

    /**
     * 第一步:设置文件模式屏蔽字为0
     * 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);

    PRINT_PID();

    /**
     * 第三步:创建一个新的会话ID
     */
    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);

    PRINT_PID();

    /**
     * 第四步:切换当前工作目录到根目录
     * Change the current working directory to the root so
     * we won't prevent file systems 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);

    /**
     * 第六步:使/dev/null具有文件描述符0,1,2.
     * 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.
     * @para-in:    cmd: the identifier in the log.
     */
    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 lockfd = open( LOCK_FILE, O_RDWR );
    if( lockfd < 0 )
    {
        syslog( LOG_ERR, "Cannot lock file[%s], aborting[%s].\n", LOCK_FILE, strerror(errno) );
        //下面这一行无法打印到控制台,项目上应该打印到日志文件中
        printf( "Cannot lock file[%s], aborting[%s].\n", LOCK_FILE, strerror(errno) );
        exit(-1);
    }
    if( lockf(lockfd,F_TLOCK,0) < 0 )
    {
        syslog( LOG_ERR, "Daemon process is already running[%s].\n", strerror(errno) );
        //下面这一行无法打印到控制台,项目上应该打印到日志文件中
        printf( "Daemon process is already running[%s].\n", strerror(errno) );
        exit(-2);
    }
}

int main( int argc, char **argv )
{
    PRINT_PID();
    daemonize( "Daemon test." );
    //由于父进程退出,所有只有最后一个子进程执行下面的语句(休眠)
    printf( "This line will not be print for the daemon process has no terminate.\n" );
    while(1)
        sleep(120);
    return 0;
}


2 程序编译

使用《Linux C/C++工程中可生成ELF、动/静态库文件的通用Makefile》一文中的Makefile文件进行程序编译,当然也可以使用命令进行编译gcc daemon_process.c -o daemon_process

3 创建第一个守护进程

如下图所示,程序先执行第178行main函数中的打印语句,输出当前第一个进程的PID值为25872;然后由于在daemonize函数中第一个进程(PID:25872)退出,所以它的子进程(PID:25873)执行第101行的打印语句;接着由于第二个进程(PID:25873)退出,那么它的子进程(PID:25874)执行第121行的打印语句;在关闭了所有文件描述符后,该子进程(PID:25874)打开标准输入/输出/错误流,最后该子进程成为由Linux系统init进程托管的孤儿进程,没有终端terminal,这也就是守护进程。

创建第一个守护进程

其中使用ps -axj|head -n 1; ps -axj|grep daemon_process命令发现子进程(PID:25874)的父进程为1进程(init进程),终端TTY为空。

接着使用命令pstree -pul查看当前用户的所有进程情况,如下图所示,再次说明守护进程(PID:25874)创建成功了。

使用命令pstree -pul查看用户进程

4 创建第二个守护进程

如果尝试再次创建一个同样的守护进程,如下图所示。执行命令ps -axj|head -n 1; ps -axj|grep daemon_process,发现仍然只有一个守护进程(PID:25874),也就是创建第二个守护进程失败。

创建第二个守护进程失败

这时使用cat /var/log/message命令查看进程在Linux系统日志文件中打印的内容,如下图所示:

查看/var/log/message日志内容

很显然程序执行到第160行,由于第一个守护进程对LOCK_FILE文件加锁的缘故而无法获取该文件的访问权限最终导致子进程(PID:25909)终止。于是,第二次创建守护进程失败了。

5 附录

关于openlogsyslog函数的使用方法,可以通过命令man 3 syslog查看,大概就是根据日志标识符(ident)和日志level(LOG_EMERG、LOG_ERR、LOG_WARNING等)和日志文件类型facility(LOG_CRON、LOG_MAIL、LOG_SYSLOG和默认的LOG_USER等)把进程的内容输出到Linux系统某一类型的日志文件中。

SYNOPSIS
    #include <syslog.h>

    void openlog(const char *ident, int option, int facility);
    void syslog(int priority, const char *format, ...);
    void closelog(void);

DESCRIPTION
    closelog()  closes  the descriptor being used to write to the system logger.
    The use of closelog() is optional.

    openlog() opens a connection to the system logger for a program.  The string
    pointed  to  by ident is prepended to every message, and is typically set to
    the  program  name.   If  ident  is  NULL,  the  program   name   is   used.
    (POSIX.1-2008 does not specify the behavior when ident is NULL.)

    The option argument specifies flags which control the operation of openlog()
    and subsequent calls to  syslog().   The  facility  argument  establishes  a
    default  to  be  used  if none is specified in subsequent calls to syslog().
    Values for option and facility are given below.  The  use  of  openlog()  is
    optional; it will automatically be called by syslog() if necessary, in which
    case ident will default to NULL.

    syslog() generates a log message, which will be distributed  by  syslogd(8).
    The  priority  argument is formed by ORing the facility and the level values
    (explained below).  The remaining arguments are a format,  as  in  printf(3)
    and  any  arguments  required  by  the format, except that the two character
    sequence %m will be replaced by the error message string strerror(errno).  A
    trailing newline may be added if needed.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值