12.1. 概述
守护进程(daemon)是在后台运行不受终端控制的进程。Unix系统中一般有很多守护进程在后台运行(20到50个),执行不同的管理任务。
启动守护进程的几种方法:
1. 在系统启动时很多守护程序都是由系统初始化脚本启动。
2. 许多网络服务器是由inetd超级服务器启动的。
3. cron守护进程按规则定期执行一些程序。
4. 可用at命令指定在将来的某一时刻执行程序。
5. 不管是在前台还是在后台,守护进程也可以在用户终端上启动,这在测试守护进程或守护进程因某些原因终止而要重启时经常使用。
由于守护进程没有控制终端,再发生问题时它要用一些其他方式以输出消息,这些消息既有一般的通告消息,也有需要管理员处理的紧急事件消息。syslog函数是输出这些消息的标准方式,它将消息发往syslogd守护进程。
12.2. syslogd守护进程
12.3. syslog函数
因为守护进程没有控制终端,所以它不能fprintf到stderr上,守护进程为登记消息通常调用syslog函数。
#include <syslog.h>
void syslog(int priority, const char * message, ... );
priority参数是级别(level)和设施(facility)的组合,举例来说,当调用rename函数失败时,守护进程可能会做以下调用:
syslog(LOG_INFO | LOG_LOCAL2, "rename(%s, %s): %m", file1, file2);
设施和级别的目的是,允许在/etc/syslog.conf文件中进行配置,使得对相同设施的消息得到同样的处理,或使相同级别的消息得到同样的处理。
当应用程序第一次调用syslog时,它创建一个Unix域数据报套接口,然后调用connect连往syslogd守护进程建立的套机口的众所周知路径名(如 /var/run/log),这个套接口在进程终止前一直打开。另外,进程也可以调用openlog和closelog
#include <syslog.h>
void openlog(const char * ident, int options, int facility);
void closelog(void);
ident是一个字符串,它将被syslog加到每条登记消息的前面,一般情况下它的值为程序名
12.4. daemon_init函数
调用它(通常从服务器程序)可使一个进程变成守护进程。
12.5. inetd守护进程
inetd进程使用前面介绍的daemon_init函数中的技术将自己变成一个守护进程,然后读入并处理它的配置文件,通常为/etc/inetd.conf
服务器程序的实际名字总是由inetd调用exec执行时作为第一个参数传递给它
图12.7给出了inetd守护进程的工作流程:
1. 启动时读/etc/inetd.conf文件并给文件中指定的所有服务创建一个相应类型的套接口(字节流或数据报)。inetd能处理的服务器数目依赖于它最多能创建的描述字的数目。每个新创建的套机口都被加入到select调用所用到的描述字集中。
2. 为每个套接口调用bind,给它们捆绑服务器众所周知的端口和通配地址。他们的TCP和UDP端口号是通过调用getservbyname获得的,其中使用了配置文件中的service-name和protocol栏目作为参数。
3. 对TCP套接口调用listen,已接受外的连接请求。对数据报接口则不做这一步。
4. 所有套接口建立后,掉用select等待这些套接口变为可读。inetd大部分时间里阻塞在select调用上,等待有一个套接口变成可读。
5. select返回一个可读的套接口后,如果是一个TCP套接口就调用accept接受这个新的连接。
6. inetd守护进程fork,由子进程处理服务请求。
子进程关闭除要处理的套接口描述字之外的所有描述字:对TCP服务来说是由accept返回的新的已连接套接口,对UDP服务器则是最初的那个UDP套接口。
子进程三次调用dup2,把套接口描述字复制到描述字0,1,2(标准输入,输出,错误)。然后关闭原套接口描述字。这样,子进程打开的描述字就只有0,1,2.如果子进程读标准输入,它实际上是从套接口读,写标准输出和错误也是写到套接口。
子进程调用getpwnam得到在配置文件中指定的login-name对应的保密字文件项。如果login-name不是root,子进程会调用setgid和setuid变为指定的用户(因为inetd是以用户ID为0运行,子进程跨fork继承了这个用户ID,所以它能变成任何用户)
子进程用exec执行相应server-program处理请求,并将配置文件中指定的参数传递给它。
7. 如果是一个字节流套接口,父进程必须关闭已连接套接口(就像标准并发服务器那样),父进程在调用select以等待下一个变成可读的套接口。
12.6. daemon_inetd函数
我们可以在一个由inetd启动的服务器程序中调用deamon_inetd函数
#include "unp.h"
#include <syslog.h>
extern int daemon_proc; /* defined in error.c */
void daemon_inetd(const char * pname, int facility)
{
daemon_proc = 1; /* for our err_XXX() functions */
openlog(pname, LOG_PID, facility);
}
// daemon_inetd函数:使通过inetd运行的进程变为守护进程
12.7. 小结