13.1 引言
守护进程(daemon)是生存期长的一种进程。它们常常在系统引导装入时启动,仅在系统关闭时才终止(生存期)。因为它们没有控制终端,所以说它们时在后头运行的。UNIX系统有很多守护进程,它们执行日常事务活动。
本章将说明守护进程的结构,以及如何编写守护进程程序。因为守护进程没有控制终端,我们需要了解在出现问题时,daemon如何报告出错情况。
13.2守护进程的特征
先来看看一些常用的系统守护进程,以及它们是怎样和进程组、控制终端和会话这三个概论相关联的。
ps -axj
- -a显示由其他用户所拥有的进程状态(UNIX为多用户系统)
- -x显示没有控制终端的进程;
- -j显示与作业有关的信息
PPID为0的进程通常是内核进程,它们作为系统引导装入过程的一部分而启动。(init是一个例外,它是一个由内核在引导装入时启动的用户层次的命令)内核进程是特殊的,通常存在于系统的整个生命周期中。它们以超级用户特权运行,无控制终端、无命令行。
在ps输出实例中,内核守护进程的名字出现在方框中,很多形式为’k*d’的进程其中‘k’指kernel,‘d’指daemon。
该版本的linux使用一个名为kthreadd的特殊内核进程类创建其他内核进程,所以kthreadd表现为其他内核进程的父进程。对于需要在进程上下文执行工作但却不被用户层进程上下文调用的每一个内核组件,通常有它自己的内核守护进程。 例如在linux中:
- kswapd守护进程也称为内存换页守护进程。它支持虚拟内存子系统在经过一段时间后将脏页面慢慢地写回磁盘来回收这些页面。
- flush守护进程在可用内存达到设置的最小阈值时将脏页面冲洗至磁盘。多个冲洗守护进程可以同时存在,每个写回设备都有一个冲洗守护进程。例如flush-8:0的冲洗守护进程。从名字可以看出是通过主设备号8和副设备号0来识别的。
- sync_supers守护进程定期将文件系统元数据冲洗至磁盘。
- jbd守护进程帮助实现了ext4文件系统中的日志功能。
- rpcbind守护进程提供将远程过程调用(remote procedure call,RPC)程序号映射为网络端口号的服务。
- inetd守护进程,它侦听系统网络接口,以便取得来自网络的对各种网络服务进程的请求。
- cron守护进程在定期安排的日期和时间执行命令。许多系统管理任务是通过cron每隔一段固定时间就运行相关程序得以实现的。
- atd守护进程与cron类似。它允许用户在指定的时间执行任务,但是每个任务它只执行一次,而非在定期安排的时间反复执行。
- cupsd守护进程是格打印假脱机进程,它处理系统提出的各个打印请求。
- sshd守护进程提供了安全的远程登录和执行设施。
注意:大多数守护进程都以超级用户(root)权限运行。所有守护进程都没有控制终端,其终端名设置为问号。用户层守护进程的父进程是init进程。
13.3 编程规则
在编写守护进程程序时要遵循一些基本规则,以防止产生不必要的交换作用。
- 首先要做的是调用umask将文件模式创建屏蔽字(文件权限用) 设置为一个已知的值(通常是0)。由继承得来的文件模式创建屏蔽字可能会被设置为拒绝某些权限。
- 调用fork,然后使父进程exit。这样做实现了下面几点。第一,如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止会让shell认为这条命令已经执行完毕。第二,虽然子进程继承了父进程的进程组ID,但获得了一个新的进程ID,这就保证了子进程不是一个进程组的组长进程。这是下面将要进行的setsid调用的先决条件。
- 调用setsid创建一个会话。然后使调用进程称为新会话的首进程,称为一个新进程组的组长进程,没有控制终端。
- 将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个挂载的文件系统中。因为守护进程一直存在,那么该文件系统就不能被卸载。
- 关闭不再需要的文件描述符。
- 某些守护进程打开/dev/null使其具有文件描述符0、1和2,这样任何一个试图使用标准输入输出的库例程都不会产生任何效果。
13.4 出错记录
守护进程存在的一个问题是如何处理出错消息。因为它本身就不应该有控制终端,所以不能只是简单地写到标准出错上。我们不希望守护进程使用控制台设备,因为很多工作站的控制台设备都运行着一个窗口系统,也不会希望每个守护进程都将出错消息写到单独的文件中,管理这些文件很费事。所以需要一个集中的守护进程出错记录设施。
BSD的syslog设施得到了广泛的应用,大多数守护进程都使用这一设施。
有一下3种产生日志消息的方法:
- 内核例程可以调用log函数。任何一个用户进程都可以通过打开(open)并读取(read)/dev/klog设备来读取这些消息。这里不涉及内核例程,就不进一步说明了。
- 大多数用户进程(仅指守护进程)调用syslog函数来产生日志消息。这使消息被发送至UNIX域数据报套接字/dev/log.
- 无论一个用户进程是在此主机上,还是通过TCP/IP网络连接的此主机上的其他主机上,都可以讲日志消息发向UDP端口514.
通常,syslogd守护进程读取所有3种格式的日志消息。此守护进程在启动时读一个配置文件,其文件名一般为/etc/syslog.conf,该文件决定了不同种类的消息应送向何处。如:紧急消息可发送至系统管理员(若已经登录),并在控制台上打印,而警告消息则可以记录到一个文件中。
守护进程记录日志消息的实施是/dev/log,其接口是syslog函数:
#include <syslog.h>
void openlog(const char *ident,int optipn,int facility);
void syslog(int priority,const char *format,...);
void closelog(void);
int setlogmask(int maskpri);//用于设置进程的记录优先级屏蔽字。
除了syslog,很多平台提供一种变体来处理可变参数列表。
#include <syslog.h>
#include <stdarg.h>
void vsyslog(int priority,const char *format,va_list arg);
大多数syslog实现将消息短时间处于队列中。如果在此段时间中有重复消息达到,那么syslog守护进程不会把它记录到日志记录中,而是会打印输出一条类似“上一条消息重复了N此”的消息。
13.5 单实例守护进程
为了正常运作,某些守护进程(不是所有的都必须)会实现为:在任意时刻只运行该守护进程的一个副本。
文件和记录锁机制为这一种方法提供了基础,该方法保障一个守护进程只有一个副本在运行。如果每一个守护进程创建一个固定名字的文件,并在该文件的整体上加一把锁,那么只允许创建一把这样的锁。在此后创建写锁的尝试都会失败,这向后续守护进程副本指明已有一个副本正在运行。
13.6 守护进程的惯例
在UNIX系统中,守护进程遵循下列通用惯例:
- 若守护进程使用锁文件,那么该文件通常存储在/var/run目录中。然而需要注意的是,守护进程可能需要具有超级用户权限才能在此目录下创建文件。锁文件的名字通常是name.pid。
- 若守护进程支持配置选项,那么配置文件通常存放在/etc目录中。配置文件的名字通常是name.conf。
- 守护进程可以用命令行启动,但通常他们是由系统初始化脚本之一(/etc/rc*或/etc/init.d/*)启动的。如果在守护进程终止时,应当自动地重新启动它,则我们可以在/etc/inittab(为init进程的配置文件)中为该守护进程包括respawn记录项,这样init就将重新启动该守护进程。
- 若一个守护进程有一个配置文件,那么该守护进程启动时会读该文件,但在此之后一般就不会再查看它。若某个管理员更改了配置文件,那么该守护进程可能需要配停止,然后再启动,以使配置文件的更改生效。为了避免此种麻烦,某些守护进程将捕捉SIGHUP信号,当他们接受到该信号时,重新读配置文件。
13.7 客户进程-服务器进程模型
守护进程常常用作服务器进程。确实,我们可以称syslogd进程为服务器进程,用户进程(客户进程)用UNIX域数据报套接字向其发送消息。
一般而言,服务器进程等待客户进程与其联系,提出某种服务类型的请求。例如,syslogd服务器进程提供的服务是将一条出错消息记录到日志文件中。
在服务器进程中调用fork然后exec另一个程序来向客户进程提供服务是很常见的。这些服务器进程通常管理者多个文件描述符:通信端点、配置文件、日志文件和类似的文件。
13.8 小结
在大多数UNIX系统中,守护进程是一直运行的。
为了初始化我们自己的进程,使之作为守护进程运行,需要谨慎的思索并理解第9章所说明的进程之间的关系。
因为守护进程没有控制终端,本章还说明了守护进程记录出错消息的几种方法。
还讨论了守护进程遵循的若干惯例,给出了几个如何实现惯例的实例。