apue学习笔记(第十三章 守护进程)

本章将说明守护进程结构,以及如何编写守护进程程序。

守护进程,也就是通常说的Daemon进程,是Unix中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

编程规则

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

1.调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0)

2.调用fork,然后使父进程exit,保证了子进程不是进程组的组长进程,是下面进行setsid调用的先决条件

3.调用setsid创建一个新会话。然后它会执行3个步骤:(a)成为新会话的首进程 (b)成为一个新进程组的组长进程 (c)没有控制终端

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

5.关闭不再需要的文件描述符

6.某些守护进程打开/dev/null使其具有文件描述符0、1、2

下面函数演示了创建守护进程的基本步骤

 1 #include "apue.h"
 2 #include <syslog.h>
 3 #include <fcntl.h>
 4 #include <sys/resource.h>
 5
 6 void
 7 daemonize(const char *cmd)
 8 {
 9     int                    i, fd0, fd1, fd2;
10     pid_t                pid;
11     struct rlimit        rl;
12     struct sigaction    sa;
13
14     /*
15      * Clear file creation mask.
16      */
17     umask(0);
18
19     /*
20      * Get maximum number of file descriptors.
21      */
22     if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
23         err_quit("%s: can‘t get file limit", cmd);
24
25     /*
26      * Become a session leader to lose controlling TTY.
27      */
28     if ((pid = fork()) < 0)
29         err_quit("%s: can‘t fork", cmd);
30     else if (pid != 0) /* parent */
31         exit(0);
32     setsid();
33
34     /*
35      * Ensure future opens won‘t allocate controlling TTYs.
36      */
37     sa.sa_handler = SIG_IGN;
38     sigemptyset(&sa.sa_mask);
39     sa.sa_flags = 0;
40     if (sigaction(SIGHUP, &sa, NULL) < 0)
41         err_quit("%s: can‘t ignore SIGHUP", cmd);
42     if ((pid = fork()) < 0)
43         err_quit("%s: can‘t fork", cmd);
44     else if (pid != 0) /* parent */
45         exit(0);
46
47     /*
48      * Change the current working directory to the root so
49      * we won‘t prevent file systems from being unmounted.
50      */
51     if (chdir("/") < 0)
52         err_quit("%s: can‘t change directory to /", cmd);
53
54     /*
55      * Close all open file descriptors.
56      */
57     if (rl.rlim_max == RLIM_INFINITY)
58         rl.rlim_max = 1024;
59     for (i = 0; i < rl.rlim_max; i++)
60         close(i);
61
62     /*
63      * Attach file descriptors 0, 1, and 2 to /dev/null.
64      */
65     fd0 = open("/dev/null", O_RDWR);
66     fd1 = dup(0);
67     fd2 = dup(0);
68
69     /*
70      * Initialize the log file.
71      */
72     openlog(cmd, LOG_CONS, LOG_DAEMON);
73     if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
74         syslog(LOG_ERR, "unexpected file descriptors %d %d %d",
75           fd0, fd1, fd2);
76         exit(1);
77     }
78 }

出错记录

因为守护进程本就不应该有控制终端,所以不能简单地把出错信息写到标准错误上。

syslog设施是一个集中的守护进程出错记录设施,下图演示了syslog设施的详细组织结构:

有以下3种产生日志消息的方法:

1.内核例程可以调用log函数。任何一个用户进程都可以通过打开并读取/dev/klog设备来读取这些信息。

2.大多数用户进程(守护进程)调用syslog函数来产生日志消息。这使消息被发送至UNIX域数据报套接字/dev/log。

3.可将日志消息发向UDP端口514

通常,syslogd守护进程读取所有3种格式的日志消息。此守护进程在启动时读一个配置文件,其文件名一般为/etc/syslog.conf,该文件决定了不同类型的消息应送向何处。

该设施的接口是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 maskpri);

调用openlog是可选的。如果不调用openlog,则在第一次调用syslog时,自动调用openlog。

调用closelog也是可选的。因为它只是关闭增被用于与syslogd守护进程进行通信的描述符。

调用openlog使我们可以指定一个ident(一般是程序的名称),以后,此ident将被加至每则日志消息中。

option参数是指定各种选项的位屏蔽。下图介绍了可用的option选项:

facility参数值选取自下图,用来让配置文件说明来自不同设施的消息将以不同的方式进行处理。

调用syslog产生一个日志消息。其priority参数是facility和level的组合。其中level的值按优先级由最高到最低一次排列如下:

将format参数以及其他所有参数传至vsprintf函数以便进行格式化。其中,format中每个出现的%m字符都先被替换成与errno值相对应的出错消息字符串。

setlogmask函数用于设置进程的记录优先级屏蔽字。各条消息除非已在记录优先级屏蔽字中进行了设置,否则将不被记录。

除了syslog,很多平台还提供它的一种辩题来处理可变参数列表。

#include <syslog.h>
#include <stdarg.h>
void vsyslog(int priority,const char *format,va_list arg);

单实例守护进程

为了正常运作,某些守护进程会实现为在任一时刻只运行该守护进程的一个副本。

文件和记录锁机制(第十四章)为一种方法提供了基础,该方法保证了一个守护进程只有一个副本,下面函数将演示这一点。

 1 #include <unistd.h>
 2 #include <stdlib.h>
 3 #include <fcntl.h>
 4 #include <syslog.h>
 5 #include <string.h>
 6 #include <errno.h>
 7 #include <stdio.h>
 8 #include <sys/stat.h>
 9
10 #define LOCKFILE "/var/run/daemon.pid"
11 #define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
12
13 extern int lockfile(int);
14
15 int
16 already_running(void)
17 {
18     int        fd;
19     char    buf[16];
20
21     fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE);
22     if (fd < 0) {
23         syslog(LOG_ERR, "can‘t open %s: %s", LOCKFILE, strerror(errno));
24         exit(1);
25     }
26     if (lockfile(fd) < 0) {
27         if (errno == EACCES || errno == EAGAIN) {
28             close(fd);
29             return(1);
30         }
31         syslog(LOG_ERR, "can‘t lock %s: %s", LOCKFILE, strerror(errno));
32         exit(1);
33     }
34     ftruncate(fd, 0);
35     sprintf(buf, "%ld", (long)getpid());
36     write(fd, buf, strlen(buf)+1);
37     return(0);
38 }

守护进程实例

下面程序说明了守护进程可以重读其配置文件的一种方法

 1 #include "apue.h"
 2 #include <pthread.h>
 3 #include <syslog.h>
 4
 5 sigset_t    mask;
 6
 7 extern int already_running(void);
 8
 9 void
10 reread(void)
11 {
12     /* ... */
13 }
14
15 void *
16 thr_fn(void *arg)
17 {
18     int err, signo;
19
20     for (;;) {
21         err = sigwait(&mask, &signo);
22         if (err != 0) {
23             syslog(LOG_ERR, "sigwait failed");
24             exit(1);
25         }
26
27         switch (signo) {
28         case SIGHUP:
29             syslog(LOG_INFO, "Re-reading configuration file");
30             reread();
31             break;
32
33         case SIGTERM:
34             syslog(LOG_INFO, "got SIGTERM; exiting");
35             exit(0);
36
37         default:
38             syslog(LOG_INFO, "unexpected signal %d\n", signo);
39         }
40     }
41     return(0);
42 }
43
44 int
45 main(int argc, char *argv[])
46 {
47     int                    err;
48     pthread_t            tid;
49     char                *cmd;
50     struct sigaction    sa;
51
52     if ((cmd = strrchr(argv[0], ‘/‘)) == NULL)
53         cmd = argv[0];
54     else
55         cmd++;
56
57     /*
58      * Become a daemon.
59      */
60     daemonize(cmd);
61
62     /*
63      * Make sure only one copy of the daemon is running.
64      */
65     if (already_running()) {
66         syslog(LOG_ERR, "daemon already running");
67         exit(1);
68     }
69
70     /*
71      * Restore SIGHUP default and block all signals.
72      */
73     sa.sa_handler = SIG_DFL;
74     sigemptyset(&sa.sa_mask);
75     sa.sa_flags = 0;
76     if (sigaction(SIGHUP, &sa, NULL) < 0)
77         err_quit("%s: can‘t restore SIGHUP default");
78     sigfillset(&mask);
79     if ((err = pthread_sigmask(SIG_BLOCK, &mask, NULL)) != 0)
80         err_exit(err, "SIG_BLOCK error");
81
82     /*
83      * Create a thread to handle SIGHUP and SIGTERM.
84      */
85     err = pthread_create(&tid, NULL, thr_fn, 0);
86     if (err != 0)
87         err_exit(err, "can‘t create thread");
88
89     /*
90      * Proceed with the rest of the daemon.
91      */
92     /* ... */
93     exit(0);
94 }

下面程序说明一个单线程守护进程然后捕捉SIGHUP并重读其配置文件

 1 #include "apue.h"
 2 #include <syslog.h>
 3 #include <errno.h>
 4
 5 extern int lockfile(int);
 6 extern int already_running(void);
 7
 8 void
 9 reread(void)
10 {
11     /* ... */
12 }
13
14 void
15 sigterm(int signo)
16 {
17     syslog(LOG_INFO, "got SIGTERM; exiting");
18     exit(0);
19 }
20
21 void
22 sighup(int signo)
23 {
24     syslog(LOG_INFO, "Re-reading configuration file");
25     reread();
26 }
27
28 int
29 main(int argc, char *argv[])
30 {
31     char                *cmd;
32     struct sigaction    sa;
33
34     if ((cmd = strrchr(argv[0], ‘/‘)) == NULL)
35         cmd = argv[0];
36     else
37         cmd++;
38
39     /*
40      * Become a daemon.
41      */
42     daemonize(cmd);
43
44     /*
45      * Make sure only one copy of the daemon is running.
46      */
47     if (already_running()) {
48         syslog(LOG_ERR, "daemon already running");
49         exit(1);
50     }
51
52     /*
53      * Handle signals of interest.
54      */
55     sa.sa_handler = sigterm;
56     sigemptyset(&sa.sa_mask);
57     sigaddset(&sa.sa_mask, SIGHUP);
58     sa.sa_flags = 0;
59     if (sigaction(SIGTERM, &sa, NULL) < 0) {
60         syslog(LOG_ERR, "can‘t catch SIGTERM: %s", strerror(errno));
61         exit(1);
62     }
63     sa.sa_handler = sighup;
64     sigemptyset(&sa.sa_mask);
65     sigaddset(&sa.sa_mask, SIGTERM);
66     sa.sa_flags = 0;
67     if (sigaction(SIGHUP, &sa, NULL) < 0) {
68         syslog(LOG_ERR, "can‘t catch SIGHUP: %s", strerror(errno));
69         exit(1);
70     }
71
72     /*
73      * Proceed with the rest of the daemon.
74      */
75     /* ... */
76     exit(0);
77 }

时间: 2024-10-13 00:27:58

apue学习笔记(第十三章 守护进程)的相关文章

《APUE》读书笔记第十三章-守护进程

守护进程 守护进程是生存期较长的一种进程,它们常常在系统自举时启动,仅在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的.UNIX系统由很多守护进程,它们执行日常事务活动. 本章主要介绍守护进程的结构,以及如何编写守护进程程序和守护进程如何报告错误情况. 一.守护进程的编程规则 (1)首先要做的是调用umask将文件模式创建屏蔽字设置为0.这是由于继承得来的文件模式创建屏蔽字可能会拒绝设置某些权限. (2)调用fork,然后使父进程退出(exit). (3)调用setsid以创建

APUE学习笔记:第九章 进程关系

9.1 引言 本章将更详尽地说明进程组以及POSIX.1引入的会话的概念.还将介绍登陆shell(登录时所调用的)和所有从登陆shell启动的进程之间的关系. 9.1 终端登陆 系统管理员创建通常名为/etc/ttys的文件,其中每个终端设备都有一行,每一行说明设备名传递给getty程序的参数.当系统自举时,内核创建进程ID为1的进程,依旧是init进程.init进程使系统进入多用户状态.init进程读文件/etc/ttys,对每一个允许登陆的终端设备,init调用一次fork,所生成的子进程则

APUE学习笔记:第一章 UNUX基础知识

1.2 UNIX体系结构 从严格意义上,可将操作系统定义为一种软件(内核),它控制计算机硬件资源,提供程序运行环境.内核的接口被称为系统调用.公用函数库构建在系统调用接口之上,应用软件即可使用公用函数库,也可使用系统调用.shell是一种特殊的应用程序,它为运行其他应用程序提供了一个接口 从广义上,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并给予计算机以独有的特性(软件包括系统实用程序,应用软件,shell以及公用函数库等) 1.3  shell shell是一个命令行解

APUE学习笔记:第二章 UNIX标准化及实现

2.2UNIX标准化 2.2.1 ISO C 国际标准化组织(International Organization for Standardization,ISO) 国际电子技术委员会(International Electrotechnical Commission,IEC) ISO C标准的意图是提供C程序的可移植性,使其能适合于大量不同的操作系统,而不只是UNIX系统.此标准不仅定义了C程序设计语言的语法和语义,还定义了其标准库.因为所有现今的UNIX系统都提供C标准中定义的库例程,所以该

apue学习笔记(第九章 进程关系)

本章将详细地说明进程组以及POSIX.1引入的会话的概念.还将介绍登录shell和所有从登录shell启动的进程之间的关系 终端登录 BSD终端登录.系统管理者创建通常名为/etc/ttys的文件,其中每个终端设备都有一行,用来说明设备名和传到getty程序的参数. 当系统自举时,内核创建进程ID为1的进程(init进程).init进程读取文件/etc/ttys,对每一个允许登录的终端设备调用一次fork,它所生成的子进程则exec getty程序,如下图所示: getty对终端设备调用open

《Unix环境高级编程》读书笔记 第13章-守护进程

1. 引言 守护进程是生存期长的一种进程.它们常常在系统引导装入时启动,仅在系统关闭时才终止.它们没有控制终端,在后台运行. 本章说明守护进程结构.如何编写守护进程程序.守护进程如何报告出错情况. 2. 守护进程的特征 基于BSD的系统下执行:ps -axj -a 显示由其他用户所拥有的进程的状态:-x 显示没有控制终端的进程状态:-j 显示与作业有关的信息 基于System V的系统下执行:ps -efj Linux下执行以上两个命令输出一致 常见的守护进程: kswapd,内存换页守护进程.

Python学习笔记__10.5章 分布式进程

# 这是学习廖雪峰老师python教程的学习笔记 1.概览 在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上. Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上.依靠网络通信,一个服务进程可以作为调度者,将任务分布到其他多个进程中.由于managers模块封装很好,不必了解网络通信的细节,就可以很

docker 学习笔记20:docker守护进程的配置与启动

安装好docker后,需要启动docker守护进程.有多种启动方式. 一.服务的方式 因为docker守护进程被安装成服务.所以,可以通过服务的方式启停docker守护进程,包括查看状态. sudo start docker  //启动 sudo stop docker  //停止 sudo status docker  //查看状态 二.利用docker daemon命令 sudo docker daemon 利用sudo ps -A 可以获取守护进程的进程号 三.让远程api可以访问dock

Java学习笔记—第十三章 数据库编程入门

第十三章 数据库编程入门 了解JDBC Java中对数据库的访问主要是通过JDBC进行的.JDBC是Java数据库连接技术(Java Database Connectivity)的简称,是用于执行SQL语句的API,可以为多种关系数据库提供统一访问.由一组用Java语言编写的类和接口组成.(SQL是Structure Query Language的缩写,意义为结构化查询语言,是一种标准的关系数据库访问语言.) JDBC的工作机制 使用JDBC完成对数据库的访问主要包括以下五个层次:Java应用程