前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >UNIX环境高级编程(APUE)之单实例守护进程

UNIX环境高级编程(APUE)之单实例守护进程

作者头像
typecodes
发布2024-03-29 14:38:05
650
发布2024-03-29 14:38:05
举报
文章被收录于专栏:typecodestypecodes

在UNIX环境高级编程(APUE)中提到了守护进程的创建方法,思路很清晰,所以这里通过代码具体研究下。

UNIX环境高级编程(APUE)之单实例守护进程
UNIX环境高级编程(APUE)之单实例守护进程
1 完整程序:单实例守护进程

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176

/** * @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 bufMAXLINE; 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查看用户进程
使用命令pstree -pul查看用户进程
4 创建第二个守护进程

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

创建第二个守护进程失败
创建第二个守护进程失败

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

查看/var/log/message日志内容
查看/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系统某一类型的日志文件中。

代码语言:javascript
复制
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.
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-03-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 完整程序:单实例守护进程
  • 2 程序编译
  • 3 创建第一个守护进程
  • 4 创建第二个守护进程
  • 5 附录
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档