@FunC
2018-05-20T10:42:03.000000Z
字数 2767
阅读 1999
unix
守护进程(daemon)是在后台运行且不与任何控制终端关联的进程。
守护进程有多种启动方法:
* 在系统启动阶段,由系统初始化脚本启动
* 许多网络服务器由 inetd 超级服务器启动
* cron 守护进程按照规则定期执行一些程序,由它启动执行的程序同样作为守护进程运行
* at 命令用于指定将来某个时刻的程序执行
* 从用户终端或在前台或在后台启动(这种做法通常用于测试)
因为守护进程没有控制终端,当有事件发生时需要由输出消息的某种方法可用。 syslog 函数是输出这些消息的标准方法,它把这些消息发送给 syslogd 守护进程
Unix 系统中的 syslogd 守护进程通常由某个系统初始化脚本启动,而且在系统工作期间一直运行,用于接受并处理日志信息。
启动时执行以下步骤:
1. 读取配置文件
2. 创建一个 Unix 域数据报套接字,给他捆绑路径名 /var/run/log
3. 创建一个 UDP 套接字, 给它捆绑端口 514 (syslog 服务使用的端口)
4. 打开路径名 /dev/klog
, 来自内核中的任何出错信息看着像是这个设备的输入
之后 syslogd 守护进程在一个无限循环中运行: 调用 select 以等待它的三个描述符之一变为可读,并按照配置文件进行处理。如果收到 SIGHUP 信号,则重新读取配置文件。
因为守护进程没有控制终端,所以它们不能把消息 fprintf 到 stderr 上。从守护进程中登记消息的常用技巧就是调用 syslog 函数
#include <syslog.h>
void syslog(int priority, const char *message, ...);
其中 priority 参数是 级别(level)和 设施(facility)两者的结合。message 参数类似 printf 的格式串,增设了%m规范,它将被替换成与当前 errno 值对应的出错信息:
syslog(LOG_INFO | LOG_LOCAL2, "rename(%s, %s): %m", file1, fil2)
当 syslog 被应用进程首次调用时,它创建一个 Unix 域数据报套接字,然后调用 connect 连接到由 syslogd 守护进程创建的 Unix 域数据报套接字的众所周知路径名。这个套接字一直保持打开,直到进程终止为止。作为替换,进程也可以调用 openlog 和 closelog:
#include <syslog.h>
void openlog(const char *ident, int options, int facility);
void closelog(void);
openlog 可以在首次调用 syslog 前调用, closelog 可以在应用进程不再需要发送日志消息时调用。
讨论完守护进程输出消息的方法,我们是时候来看看怎么把一个普通进程变为守护进程了。
如果只是让前台任务变为后台任务,那我们只需要在命令的尾部加上符号& 即可:
node server.js &
但这样做只是变为后台任务,并不是变成守护进程。它继承了当前 session 的标准输出和标准错误,并且不继承标准输入。同时还会在用户退出 session 时因接受到 SIGHUP 信号而退出。这样看来,将普通进程转变为守护进程还是有很多细节要考虑的。
我们来看一下 daemon_init 函数是怎么完成这个任务的:
#include "unp.h"
#include <syslog.h>
#define MAXFD 64
extern int daemon_proc; /* defined in error.c */
int daemon_init(const char *pname, int facility)
{
int i;
pid_t pid;
if ( (pid = Fork()) < 0)
return (-1);
else if (pid)
_exit(0); /* 终止父进程 */
/* 下面由子进程 1 执行 */
// 因为子进程继承了父进程的进程组ID,故不是当前进程组的头进程,可以调用setsid
if (setsid() < 0) /* 成为会话的头进程 */
return (-1);
// 会话头进程终止时,会话中的所有进程都会收到 SIGHUP 信号然后终止
Signal(SIGHUP, SIG_IGN); /* 屏蔽SIGHUP信号 */
if ( (pid = Fork()) < 0) /* 再次fork,避免头进程自动获得控制终端的问题 */
return (-1);
else if (pid)
_exit(0); /* 终止子进程 1 */
/* 下面由子进程 2 执行 */
daemon_proc = 1; /* for err_XXX() functions */
chdir("/"); /* 修改工作目录,避免无法 unmount */
// 因为fork会继承fd, 关闭所有的文件描述符,包括stdin,stdout,stderr
for (i = 0; i < MAXFD; i++)
close(i);
// 将stdin, stdout, stderr 重定向到 /dev/null
open("/dev/null", O_RDONLY); /* fd依次为0,1,2 */
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
openlog(pname, LOG_PID, facility); /* 调用openlog,将日志发送给syslogd */
return (0); /* success */
}
大致步骤为:调用 fork 以转到后台运行,调用 setsid 建立一个新的 POSIX 会话并成为会话头进程,再次 fork 以避免无意中获得新的控制终端,改变工作目录和文件创建模式掩码,最后关闭所有非必要的描述符。
Inter 守护进程作为一个超级服务器,用单个进程为多个服务等待外来的客户请求,以此取代每个服务一个进程的做法,减少了系统中的进程总数。
下面是 inetd 守护进程的工作流程:
核心是使用 select 等待多个套接字描述符,之后使用 exec 执行服务器程序
需要注意的是,fork 出子进程处理服务请求时,子进程调用 dup2 三次,把这个待处理套接字的描述符复制到描述符0、1和2,然后关闭原套接字描述符。
因为替一个 TCP 服务器调用 accept 的进程是 inetd,由 inetd 启动的真正服务器通常通过调用 getpeername 获取客户的 IP 地址和端口号。
inetd 通常不适用于服务密集型的服务器(如Web 服务器),相比于标准的并发服务器只有一个 fork 的开销,inetd 启动的服务器开销为一个 fork + exec
对于 Node 应用,有不少优秀的 npm 包可供使用。
如简单的用于文件变化时自动重启进程的 nodemon
保证进程退出时能自动重启,同时能作为服务进程启动的 forever
以及功能强大,甚至能实时收集日志和监控的 pm2