37 Daemon
在概述小节的疑惑
书上说:在 Linux 上,特定的 daemon 会作为内核线程运行,比如 pdflush,用 ps(1) 列出线程时,这些 daemon 的名字会用花括号括起来。但可能因为书太老了,很多内容已经变化了。我在服务器上查出来的情况是(用我的 wsl 查不出来什么,可能内核修改太多了):
$ ps -p 1 -p 2 -o user,pgid,ppid,pid,tid,cmd
USER PGID PPID PID TID CMD
root 1 0 1 1 /sbin/init splash
root 0 0 2 2 [kthreadd]
我的理解:[kthreadd]
和 /sbin/init
的父进程 ID 都是 0,可能表示都是被内核创建出来的?PID 和 TID 相等表示它们都是进程而不是线程。[kthreadd]
的 PGID 为 0,可能表示内核进程组,而 init 则是在一个单独的进程组中。
查找和“flush”匹配的进程:
$ ps axf -o user,pgid,ppid,pid,tid,cmd | grep flush
root 0 2 5 5 \_ [slub_flushwq]
root 0 2 549 549 \_ [kdmflush]
root 0 2 553 553 \_ [kdmflush]
root 0 2 557 557 \_ [kdmflush]
root 0 2 3639641 3639641 \_ [kworker/u65:2-flush-259:0]
amax 3642118 3618263 3642119 3642119 \_ grep --color=auto flush
下面的一堆 [*flush]
都是 [kthreadd]
的子进程。再看看 ps axf -o user,pgid,ppid,pid,tid,cmd|less
的结果(太长了,这里省略):
[kthreadd]
会创建一堆子进程,它们的名字都有中括号,而且它们的 PGID 都为 0。这些进程的用户都是 root。- 1 号 PID 的进程也是属于 root 用户的,随后它会创建很多用户进程,有的用户进程可能使用不同的用户。
37.2~37.3 创建 daemon
书上在此处列举了创建 daemon 的原理和一些要点。
- 逃脱进程组:执行
fork()
,父进程退出、子进程继续执行。这样子进程就成为孤儿,被 init 收养而不是被 shell 管理。 - 释放控制终端:子进程调用
setsid()
开启新的会话,以释放控制终端(必须不是进程组 leader 才能创建新的会话,所以要放在上一步之后做)。 - 避免再次打开控制终端:每次打开可能是控制终端的设备时都增加
O_NOCTTY
标记,或者执行第二次“fork()
+ 父进程退出、子进程执行”,这样子进程就不是会话首进程,不会不小心打开控制终端。 - 清除 umask 来确保创建的文件的权限。
- 修改工作目录为 / 以保证不占用某个文件系统导致其无法卸载。
- 关闭不需要的文件描述符,然后把 0、1、2 号文件描述符打开为 /dev/null 以确保操作被忽略而不是引发程序崩溃。
系统在关闭时,init 会向所有子进程(基本上都是守护进程)发送 SIGTERM 信号。这个时候 daemon 可以处理信号完成清理工作。在发送完 SIGTERM 信号后的 5 秒,init 又会发送 SIGKILL 信号。注意:init 发送信号是按照真实时间来的,不是按照 CPU 时间来的,很多守护进程都要做清理工作,所以每个守护进程不一定真的能够有 5 秒的时间做清理工作。
使用 SIGHUP 让 daemon 重载配置
Daemon 都是被 init 来收养的,而且和控制终端没有关联,因此不会在终端断开连接时收到 SIGHUP 信号。那这个时候 SIGHUP 就可以另作它用,比如让 daemon 重载其配置文件,或者让它关闭日志文件、创建新的日志等。
一般来说,守护进程的配置放在 /etc 目录,而日志放在 /var/log 目录中。
使用 syslog 记录消息和错误
Syslog 工具可以集中记录日志,系统的很多程序都会往其中写日志,用户程序也可以通过 syslog(3)
往里面写日志。Syslog 工具的两大组件:syslogd daemon 和 syslog(3) 库函数。
Syslogd 收集日志的地方有两个:
- /dev/log 这个 UNIX domain socket 文件(也可能是个指向 socket 文件的符号链接,我的 wsl 中这个符号链接指向 /run/systemd/journal/dev-log)来存储本地日志;
- 以及 Internet domain socket(保存通过 TCP/IP 发送的信息,使用 UDP 端口 514)。
在一些 UNIX 实现中,syslog socket 位于 /var/run/log,在我的 wsl 和 Ubuntu 服务器中,/var/run/log 是一个文件夹,里面有 journal 子文件夹。Syslogd 收到信息之后,又会根据配置文件将日志写到不同的位置。整体过程如图:
用户可以通过 syslog()
API 来输出日志:
SYNOPSIS
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
openlog()
是可选的,如果不使用,第一次 syslog()
时会使用默认的配置和日志连接。默认的 facility
值是 LOG_USER
。priority
可以为:
Value | Description |
---|---|
LOG_EMERG | Emergency or panic condition (system is unusable) |
LOG_ALERT | Condition requiring immediate action (e.g., corrupt system database) |
LOG_CRIT | Critical condition (e.g., error on disk device) |
LOG_ERR | General error condition |
LOG_WARNING | Warning message |
LOG_NOTICE | Normal condition that may require special handling |
LOG_INFO | Informational message |
LOG_DEBUG | Debugging message |
/etc/syslog.conf 配置文件举例:
*.err /dev/tty10
auth.notice root
*.debug;mail.none;news.none -/var/log/messages
含义略。