Linux 守护进程 、inetd超级服务器
守护进程
通常源于系统初始化脚本启动。它们于控制台脱离关联。
启动方式:
- 系统启动阶段由系统初始化脚本启动,通常位于 /etc /etc/rc 目录,启动一开始有root权限
- 由inetd启动
- cron 按规则定期启动
- at 命令指定于某个时刻执行,通常由cron启动它们
- shell 通过
nohup xxx > /dev/null 2>&1 &
启动 - 通过代码,fork出子进程再杀死父进程,得到的孤儿进程也是守护进程
创建守护进程
void daemon_init(){ // 创建孤儿进程 pid_t pid = fork(); if (pid < 0) { exit(-1); } else if (pid > 0) { exit(0); } // 创建新的会话 if (setsid() < 0) { exit(-1); } // 处理 SIGHUP 信号 // 此信号在终端断开时发出,默认会退出进程 signal(SIGHUP, SIG_IGN); // 再次 fork 保证进程不是会话首进程,不再拥有控制终端 if (pid < 0) { exit(-1); } else if (pid > 0) { exit(0); } // 更改工作目录 chdir("/"); // 设置权限掩码 if (umask(0) < 0) { perror("umask"); exit(-1); } // 关闭文件描述符,并重定向 stdio stdout stderr for (int i = 0; i < MAXFD /*自定义宏*/; ++i) { close(i); } open("/dev/null", O_RDONLY); open("/dev/null", O_RDWR); open("/dev/null", O_RDWR); // 守护进程开始执行任务 while (1) { // ... } return 0; }
syslogd 守护进程
syslogd 启动时:
- 读取 /etc/syslog.conf 配置,指定了可能收取的各种log
- 创建一个 unix 域套接字,绑定到 /dev/run/log
- 创建一个UDP套接字,绑定到端口514(新版本禁止了这个端口防止 Dos)
- 打开 /dev/klog 这个设备接收内核输出的错误消息
- 调用select等待前3个描述符,收到 SIGHUP 信号则重新读配置文件
#include <sys/syslog.h>
void openlog (const char *__ident, int __option, int __facility);
void syslog (int __pri, const char *__fmt, ...);
/*
第一个参数用于指定level和facility,如:
LOG_EMERG LOG_ERR LOG_WARNING LOG_INFO
LOG_AUTH LOG_CRON LOG_DAEMON
*/
void closelog (void);
inetd/xinetd 守护进程
inetd 处理普通守护进程的大部分启动细节,单个inetd进程能为多个服务等待外来客户请求,减少了系统进程总数。其不适合服务密集型服务器
配置文件示例
# /etc/xinetd.conf
# 禁用所有网络服务,除了我们明确启用的服务
defaults
{
instances = 60 # 允许的最大并发连接数
log_type = SYSLOG # 使用 syslog 记录日志
log_on_success = HOST PID # 记录连接成功的信息
log_on_failure = HOST # 记录连接失败的信息
cps = 25 30 # 控制连接速率:25个连接/30秒
per_source = 10 # 每个来源IP的最大连接数
v6only = no # 启用 IPv4 和 IPv6 支持
}
# 配置一个 echo 服务,允许客户端发送文本,返回相同的文本
service echo
{
disable = no # 禁用该服务:no 表示启用
socket_type = stream # 使用流套接字
protocol = tcp # 使用 TCP 协议
wait = no # 不等待服务程序退出,允许并发连接
user = nobody # 服务运行的用户
server = /usr/bin/echo # 服务程序的路径
log_on_success = HOST DURATION # 记录连接成功和连接持续时间
log_on_failure = HOST # 记录连接失败
}
# 配置一个 daytime 服务,返回服务器当前时间
service daytime
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = nobody
server = /usr/sbin/daytime # 服务程序的路径
log_on_success = HOST DURATION
log_on_failure = HOST
}
如果inted指定 nowait,每个请求派生一个子进程处理:
- 读入配置文件,为每个服务创建一个套接字并加入 select
- 每个套接字调用 bind listen
- 创建完毕后调用select阻塞
- 调用fork派生进程,子进程处理服务请求
- 子进程将套接字描述符 dup2 到标准io,关闭其他所有描述符
- 根据配置文件中用户名等调整自身,使用 exec 执行
指定 wait 要求inetd必须在这个套接字再次称为select调用的候选套接字前等待当前服务该套接字的子进程终止:
- fork 返回到父进程时,父进程报错子进程id,通过 waitpid确定终止时间
- 通过 FD_CLR 关闭这个套接字在 select 中对应位,直到子进程终止对该套接字的接管
- 子进程终止时父进程收到 SIGCHLD 信号,父进程打开select所用描述符集中对应的位