主要特点
- 一般在系统启动时装入,仅在系统关闭时终止。
- 大多数守护进程以超级用户特权运行。
- 所有的守护进程都没有控制终端,其终端名设置为问号。
- 内核守护进程以无控制终端方式启动。
- 用户层守护进程可以通过调用
setsid
实现。
- 用户层守护进程的父进程是init进程。
消息输出
前面提到,守护进程是没有控制终端的,显然无法将自己的消息输出到标准输出或标准错误上。而且系统中运行着许多守护进程,因此需要一个集中的守护进程记录设施,即syslog
。
如上图所示,主要有3中产生日志消息的方式:
- 内核例程调用log函数
- 大多数用户进程调用syslog函数
- 将日志消息发送到UDP的514端口
而syslogd守护进程接收这些日志消息,在其启动前会读取配置文件(/etc/syslog.conf),以决定各类消息的处理方式。
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
// Returns: previous log priority mask value
int setlogmask(int maskpri);
注:在没有调用openlog
的情况下,先调用了syslog
,会自动调用openlog
。
ident
参数指向的字符串会被加到日志消息中去,因此一般指定为程序名称。
option
参数指定各种选项的位屏蔽,选项见图13.3。
facility
参数可选值见图13.4。
priority
参数包含facility和level(图13.5)的组合,如果参数中没有指定facility,则会使用openlog
中指定的facility,如果没有调用openlog
,那么会使用默认值LOG_USER
。
编程规则
可以按照如下步骤编写一个守护进程。
- 调用
umask
函数将文件模式创建屏蔽字设置为指定值(通常为0)。守护进程可能需要创建一些文件,如果使用继承的屏蔽字,可能文件的权限会不符合预期。 - 调用
fork
后,使父进程exit
。这样可以保证子进程不是进程组的组长进程。 - 调用
setsid
创建新会话。这可以保证当前进程没有控制终端,且成为新会话的首进程和新进程组的组长进程。 - 将当前工作目录改为根目录或某个指定位置。
- 关闭不再需要的文件描述符。可以使用
getrlimit
函数获取最高文件描述符值,并关闭直到该值的所有描述符。 - 某些守护进程将文件描述符0、1和2指向/dev/null,这样任何需要输入输出的库例程都不会产生影响。
单实例守护进程
某些守护进程在同一时刻只能运行一个实例程序,这时候可以使用文件和记录锁(下文简称文件锁,详见14章)来实现这个功能。
守护进程只要创建一个固定名字的文件(一般在/var/run目录中),并在该文件整体上加一把写锁,那么此后其他的守护进程如果想要给该文件加锁就会失败,也就不应该继续运行。在守护进程终止时,锁会被自动删除,简化了复原过程。