进程管理与信号
进程管理协调程序启动、停止和子进程回收。本文负责特权状态、进程状态、信号与 systemd 托管。
1. 面试核心
先记住这几条,再读正文- 进入内核。 系统调用、中断和异常都可使 CPU 进入内核执行路径。卡 · 用户态与内核态
- 僵尸回收。 僵尸进程已退出,父进程通过 wait 系列接口回收状态。卡 · 进程状态与父子关系
- 孤儿接管。 孤儿进程由最近的 subreaper 或所在 PID 空间的 init 接管。卡 · 进程状态与父子关系
- 信号处理。 SIGTERM 可捕获处理,SIGKILL 和 SIGSTOP 无法捕获、屏蔽或忽略。卡 · 信号
- 服务依赖。 systemd 的 After 指定顺序,Wants 声明弱依赖。卡 · systemd 依赖与重启策略
2. 用户态与内核态
CPU 特权级限制指令和地址访问。用户态程序访问自己的虚拟地址空间。共享内存可显式映射进该空间。内核通过页表与权限检查维护隔离。
| 入口 | 触发条件 |
|---|---|
| 系统调用 | 应用请求 read、write、open 等内核服务 |
| 中断 | 时钟、网卡等设备触发 |
| 异常 | 缺页、非法指令等执行事件 |
进入内核需保存相关执行上下文。系统调用返回后,原线程可继续执行。特权级切换与线程调度是不同事件。
频繁的小块 I/O 会增加调用与复制开销。批量 I/O 能减少调用次数。strace -c -p <pid> 可统计调用情况。协程自身的用户态切换可由运行时完成。协程内执行阻塞系统调用仍会进入内核。
3. 进程状态与父子关系
| 状态 | 含义 |
|---|---|
| R | 正在运行或等待 CPU |
| S | 可中断睡眠 |
| D | 不可中断睡眠 |
| T | 停止 |
| t | 被调试器跟踪而停止 |
| Z | 已退出,等待回收 |
fork 后,父子有各自的虚拟地址空间。私有可写页通过写时复制分离。共享映射仍可让修改对双方可见。继承的描述符可指向同一打开文件描述,因而共享偏移。详细关系见 文件描述符。
子进程退出后,内核保留 PID、退出状态等信息。父进程调用 wait 或 waitpid 后完成回收。大量僵尸会占用 PID 等内核资源。
父进程退出时,子进程被最近的 subreaper 接管。没有 subreaper 时,由所在 PID 空间的 init 接管。能否及时回收仍取决于收养者的行为。
容器 PID 1 需要处理信号和收养的子进程。应用若未实现这些职责,可通过 docker run --init 补充轻量 init。
4. 信号
| 信号 | 默认动作 | 典型用途 |
|---|---|---|
| SIGHUP | 终止 | 终端挂断;部分服务约定为重载 |
| SIGINT | 终止 | 终端前台任务中断 |
| SIGTERM | 终止 | 请求正常退出 |
| SIGKILL | 终止 | 强制终止 |
| SIGSTOP | 停止 | 暂停执行 |
| SIGCONT | 继续 | 恢复停止任务 |
| SIGCHLD | 默认忽略 | 通知子进程状态变化 |
信号编号存在架构差异,脚本优先使用名称。kill -TERM <pid> 请求进程退出。应用处理时先停新任务,再清理在途工作和资源。
kill 成功表示请求被接受,退出完成还需另行确认。D 状态任务等待内核操作完成时,终止可被延迟。SIGKILL 也无法跳过尚未结束的内核等待。
普通信号在阻塞期间多次产生,只保留一个待决实例。实时信号可以排队。收到 SIGCHLD 后应循环调用 waitpid,回收所有已退出子进程。
SIGCHLD 的默认“忽略”与显式设置 SIG_IGN 有区别。默认行为仍可留下僵尸。显式 SIG_IGN 或 SA_NOCLDWAIT 可让子进程自动回收。
5. 服务托管与后台运行
| 方式 | 行为 |
|---|---|
cmd & | 交给 Shell 后台执行 |
nohup cmd >out.log 2>&1 & | 忽略 SIGHUP,并重定向输出 |
| tmux、screen | 保留可重连的终端会话 |
| systemd | 管理启动、依赖、失败重启和资源 |
nohup 只改变挂断信号处理,缺少服务监督功能。CentOS 7 使用 systemd。CentOS 6 使用 Upstart,并兼容 SysV 服务脚本。
[Unit]
Description=Demo app
Wants=network-online.target
After=network-online.target
[Service]
Type=exec
User=app
ExecStart=/opt/app/bin/app --config /etc/app.yaml
Restart=on-failure
RestartSec=3s
TimeoutStopSec=30s
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
示例要求 systemd 240 及以上版本。app 用户、程序和配置文件需已经存在。服务默认将输出交给 journal。Type=exec 在程序执行成功后确认启动步骤完成。它只确认进程执行,业务就绪仍需额外机制。
systemctl daemon-reload 重读单元文件。systemctl enable --now app 设置开机启动并立即启动。systemctl cat app 查看定义及覆盖配置。journalctl -u app -f 跟踪日志。
6. systemd 依赖与重启策略
After 只规定两个单元之间的启动顺序。Wants 将另一个单元加入启动事务。Requires 声明更强的依赖关系。需要先启动某依赖时,应组合依赖和顺序配置。
network-online.target 的效果取决于等待网络的实现。它只描述启动阶段的网络条件。远端数据库是否可用仍需应用处理。
Restart=on-failure 在异常退出等失败后重启。RestartSec 控制重试间隔。启动频率还受 StartLimitIntervalSec 和 StartLimitBurst 约束。
修改单元文件后执行 daemon-reload,再按需重启。reload 请求服务重新加载自身配置。是否支持 reload 取决于服务与单元定义。
7 · 面试题卡片
本页题目与参考回答
7.1 用户态和内核态有什么区别?什么情况下会切换到内核态?
考察重点
- 特权级与内存访问范围的区别。
- 系统调用、中断、异常三种进入方式;切换的开销与优化方向。
相关内容:用户态与内核态。
参考回答
用户态与内核态拥有不同的执行和访问权限。
用户程序通过自己的虚拟地址空间访问数据。内核执行受特权保护的系统操作。
系统调用、中断和异常可进入内核执行路径。进入内核涉及上下文保存,但未必发生线程切换。原线程完成系统调用后可直接继续运行。协程切换可由用户态运行时完成。它发起系统调用时仍会进入内核。
7.2 僵尸进程和孤儿进程有什么区别?僵尸进程怎样处理?
考察重点
- 两种进程产生的条件和谁负责回收。
- 僵尸进程不能被 kill,要处理父进程;容器中的 PID 1 问题。
相关内容:进程状态与父子关系。
参考回答
僵尸已退出但尚未回收,孤儿的父进程先退出。
wait 系列接口读取退出信息并完成回收。孤儿由最近的 subreaper 或 PID 空间的 init 接管。
僵尸已经结束执行,发送 SIGKILL 无法再次终止它。应定位父进程并修复回收逻辑。结束父进程后,仍需收养者执行回收。容器 PID 1 若缺少该能力,可使用轻量 init。
7.3 怎样让程序在后台长期运行?systemd 是什么,怎样知道一个服务运行的是哪个程序?
考察重点
nohup、tmux与systemd的适用场景。- 单元文件的关键配置;SysV init 与 systemd 的差异;优雅停止。
参考回答
长期服务适合由 systemd 监督运行。
临时任务可用 nohup,交互任务可用 tmux。
systemctl status app 查看服务与主进程。systemctl cat app 查看 ExecStart 及覆盖配置。需要确认实际程序时,可进一步核对主进程信息。
单元文件定义用户、启动命令和重启策略。After 只控制顺序,Wants 或 Requires 定义依赖。停止行为还受 KillSignal 和 TimeoutStopSec 等配置影响。默认停止信号为 SIGTERM,超时后可发送 SIGKILL。
7.4 kill -9 后进程为什么还在?一次 SIGCHLD 能对应多个退出吗?
考察重点:信号发送、内核等待与普通信号合并。
相关内容:信号。
参考回答
进程可处于尚未结束的内核等待,或已经变成僵尸。
应先用 ps 确认状态,再检查等待点或父进程。普通 SIGCHLD 会合并多个待决通知。处理函数应循环调用 waitpid,直到没有可回收的子进程。
7.5 After 能保证依赖服务被启动吗?daemon-reload 与 reload 有何区别?
考察重点:依赖事务、启动顺序与配置重载。
相关内容:systemd 依赖与重启策略。
参考回答
After 只规定顺序,Wants 或 Requires 才声明依赖。
需要依赖先启动时,应组合配置。daemon-reload 重读 systemd 单元文件。reload 则要求服务重读自己的配置。应用是否支持 reload 需查看单元定义。