守护进程daemon是一种生存周期很长的进程。它们通常在系统引导时启动,在系统关闭时终止。守护进程是没有终端的,它们一直在后台运行。
守护进程的特征
在Linux系统中,可以通过命令 ps -efj 来查看守护进程。例如下图:
从上图中可以看到 TTY 一列,该列显示每个进程的终端,对于问号(?)显示的进程,表明该进程没有控制终端。而CMD列中方括号[ ]显示的进程表明这是一个内核守护进程,对于用户守护进程则没有方括号[ ]。
编程规则
如果需要编写进程守护程序,则需要遵循一些基本规则,具体如下:
- 1. 调用umask将文件模式创建屏蔽字设置为一个指定值。因为守护进程如果要创建文件,那么该文件必须指定权限,确保文件权限是自己期望的。
- 2. 调用fork,然后使父进程exit,这是使得守护进程不关联终端的前提条件。另外,如果守护进程从终端命令行启动,那么父进程exit会使得shell认为该命令执行完毕从而正常返回。
- 3. 调用setsid创建新会话,并丢掉控制终端。
- 4. 将进程当前工作目录更改为根目录,因为进程可能启用于一个临时挂载的目录,如果进程一直执行,那么挂载目录就无法卸载。
- 5.关闭不再需要的文件描述符,主要防止守护进程误写。
- 6. 打开/dev/null 文件,使得进程具有文件描述符0,1,2,这样做是为了预防守护进程调用的第三方接口或者库组件尝试从标准输入输出读写。
以上6点基本上是编写一个守护进程所必须的,也就是说,如果要编写一个严谨的守护进程,那么最好将上述步骤全部囊括。
出错记录
守护进程需要处理的一个问题是如何处理出错问题,因为守护进程没有控制终端,所以它无法进行标准错误输出,另外也不能简单地直接将出错信息写入到一个日志文件中,因为对管理人员而言,他不可能去统计记住系统中所有进程对应的出错记录日志文件(目录及其文件名)。因此需要有一个集中的出错记录管理守护进程来专门统一负责出错记录。
对于内核守护进程,可以通过调用log( )函数来向出错日志统计守护进程发送消息,但我们不会编写内核级守护进程。
对于用户守护进程,可以通过调用syslog( )函数来向出错日志统计守护进程发送消息,我们还可以同网络编程来向远程主机守护进程发送消息,对于网络编程本书并不涉及。对于syslog( )函数,其头文件及函数原型如下:
#include <syslog.h> void openlog (const char* ident, int option, int facility);
void syslog (int pri, const char* fmt, ...);
void closelog (void);
int setlogmask (int mask);
对于setlogmask( )函数返回之前日志记录优先级屏蔽字。
单示例守护进程
有些程序由于实际需要,在任一时刻系统中只能运行一个守护进程,例如守护进程要排他性的独占一个设备。典型的,对于cron进程而言,如果同时有多个示例运行,那么可能导致定时重复执行从而造成问题。为了实现单实例守护进程,可以利用UNIX系统的文件和记录锁功能来实现互斥。文件和记录锁功能是下一章的内容,在这里只需要知道,经过UNIX系统的支持,利用文件和记录锁功能能实现单实例守护进程,其原理是对文件写进行加锁,一次只能加一把写性质的锁,第二个守护进程试图加写锁时会报错,这样就实现了单实例进程。
守护进程的惯例
在UNIX系统中创建守护进程通常会遵循以下惯例:
如果守护进程是单实例的,且守护进程使用锁文件,那么该锁文件通常在 /var/run 目录下,并且守护进程创建的锁文件名字是name.pid形式,其中name替换为具体守护进程的名字。
如果守护进程支持配置选项,那么配置文件通常放在 /etc 目录下,并且配置文件名为name.conf,其中name替换为具体守护进程的名字。
如果守护进程意外终止,应该自动重新启动,在Ubuntu系统中,可以通过upstart来实现。
如果守护进程读取配置文件,那么守护进程会在启动时读取该文件,但在之后如果有人重新配置了文件,那么守护进程应该重新读取,为了重新读取配置文件,守护进程应该使用SIGHUP信号作为触发条件,因为守护终端不会有终端,因此永远不会用到SIGHUP信号,从而我们可以利用SIGHUP信号来告知守护进程重新读取配置文件,而不用担心信号误用。