跳到主要内容
约 6 分钟5 张卡

进程管理与信号

进程管理协调程序启动、停止和子进程回收。本文负责特权状态、进程状态、信号与 systemd 托管。

1. 面试核心​

先记住这几条,再读正文

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 需查看单元定义。

8. 参考资料​