授权与合规声明
本文全部操作对象均为自建隔离靶场(本机容器或隔离虚拟机),涉及安全测试的环节必须以取得合法授权为前提。未经授权的渗透测试违反《中华人民共和国网络安全法》与《刑法》相关条款,须承担相应法律责任。本文只讲环境配置、版本对照与靶场隔离,不含任何攻击步骤、利用载荷与绕过手法,请勿将文中环境指向任何非自有系统。
一、现象:三种"杀不死"
1.1 三个现场,看起来都像"kill 没生效"
在隔离环境里做靶场,谁都遇到过这种场面:进程列表里那个条目明明还在,可你已经发过信号了。三种最常见的现场:
现场一——状态列是一个字母,进程名后面挂着一对尖括号。你发过信号,它还在列表里,状态列是Z,进程名后面跟着<defunct>之类的标注;而它的父进程还活得好好的。
现场二——不占 CPU,也不退出。状态列是D,既没响应也没消失;伴生现象是它原先占着的那个端口也没释放(端口这里只作现象提一句,"端口被占用"的分档与判据是另一条轴)。
现场三——父进程没了,子进程还在跑。原父进程已经不在列表里,子进程还在,它的父进程那一栏换成了另一个数字。
1.2 共同点只有一个,不同点才是关键
三个现场的共同点只有一句话:都会让人得出"kill不好使"这个结论。除此之外,它们在机制上几乎没有交集:现场一是已经终止的进程没被回收,现场二是还没终止、但正卡在一次等待里的进程,现场三是父进程先走、子进程被交出去的进程——它本身可能完全健康。
"杀不死"是现象描述,不是机制,同一个现象背后至少有三条不同的线。
1.3 本文的支点:kill只负责送信,不负责收尾
本文全部论证压在一句话上:
「kill只负责把信号送出去,不负责让进程消失;进程能不能消失,取决于它当时处于哪个状态、以及它有没有父进程来收尾。」
第一半:kill投递出去的信号是发给"整个进程"的,属于 process-directed(PS19);而内核里"信号被生成"和"信号被投递"又是两个不同的时刻,signal(7)把这两个时刻之间的信号称作pending:
Between the time when it is generated and when it is delivered a signal is said to be pending.
(PS11)也就是说:信号被生成之后、被投递给进程之前,它处于"已生成、待投递"的状态。"我发了信号"和"对方收到了信号"并不是同一件事——这是第六、七章都要用到的前提。
第二半是"收尾":谁来把已经结束的进程从内核的进程表里划掉。这件事的答案不在kill手里。
1.4 本文写什么、不写什么
本文只讲进程状态字段与进程回收机制这一条轴。不在范围:信号的语义框架(SIGTERM、SIGINT、SIGQUIT、SIGHUP的默认行为与容器停机宽限期,已在另一篇写透);端口占用的分档与判据;inode、磁盘占用、开机自启;以及任何"照着敲就能解决"的操作步骤清单。文中命令一律只承担一个角色:观察。
本章可以带走的一句:“杀不死"是现象不是机制:Z、D、父进程先走这三件事在机制上互不相干,唯一相同的地方只是它们都让你以为"kill没生效”;而kill的职责从一开始就只是把信号送出去。
二、先看状态字段
2.1 那一列字母是从哪来的
要分清三种情况,第一步不是敲命令,而是找到那个最该看、又最容易被忽略的字段:进程状态。
它的来源写得很清楚——/proc/pid/stat提供进程的状态信息,而且这份信息正是ps(1)用的(PS01):
Status information about the process. This is used by
ps(1).
这句话值得停一下:你在ps输出里看到的那一列字母,源头在这里——它属于"内核提供、ps呈现",而不是ps自己发明的分类。
2.2proc_pid_stat(5)的 state 字段,逐字照抄
在这份文件格式里,字段是有顺序的,state排在第 3 个,类型标记是%c(单个字符)(PS01)。手册对它的说明是:
One of the following characters, indicating process state:
下面这张表就是紧随其后的整张字符表,本文照抄,不增不减(PS02):
| 字符 | 官方说明 |
|---|---|
R | Running |
S | Sleeping in an interruptible wait |
D | Waiting in uninterruptible disk sleep |
Z | Zombie |
T | Stopped (on a signal) or (before Linux 2.6.33) trace stopped |
t | Tracing stop (Linux 2.6.33 onward) |
W | Paging (only before Linux 2.6.0) |
X | Dead (from Linux 2.6.0 onward) |
x | Dead (Linux 2.6.33 to 3.13 only) |
K | Wakekill (Linux 2.6.33 to 3.13 only) |
W | Waking (Linux 2.6.33 to 3.13 only) |
P | Parked (Linux 3.9 to 3.13 only) |
I | Idle (Linux 4.14 onward) |
这张表里有两处细节容易被"顺手整理掉",但都不能动:W出现了两次(一次 Paging、一次 Waking),各自带着有效的内核版本区间;好几个字符后面带着“(…only)”或“onward”的版本限定,说明它只在某个区间有效。表里没有的可打印字符不代表"不可能是状态"——本文不替手册补字。
2.3ps(1)那张给人看的表,口径略有不同
ps(1)在PROCESS STATE CODES一节给出了另一份清单,说明s、stat、state这几个输出说明符会用哪些取值描述进程状态。和本文直接相关的两条逐字如下(PS04):
D uninterruptible sleep (usually I/O)
Z defunct (“zombie”) process, terminated but not reaped by its parent
措辞差异很有信息量:内核那份手册对D写的是 “uninterruptible disk sleep”,ps(1)写的是 “usually I/O”;而对Z,ps(1)多补了半句——“terminated but not reaped by its parent”(已终止,但未被其父进程回收)。
“reaped”(回收)就是本文第三、四、五章的主线:僵尸不是"还没结束"的进程,而是"已经结束、但没人来收尾"的进程。
2.4 顺便记住第 4 个字段
同一个文件里,第 4 个字段是ppid,类型%d,手册的定义只有一句(PS03):
The PID of the parent of this process.
判断"这个进程的收尾责任人是谁",看的正是这一栏。
⚠️代码待验证(下面这条是 macOS 上的只读观察,输出结构 2026-10-15 本机实测;Linux 上ps(1)的列名与取值口径另算)
ps-axopid,ppid,stat,command本机实测能确认的只有结构:输出是定宽多列,列头依次是 PID、PPID、STAT、COMMAND;PID 为 1 的那行是系统第一个进程;STAT 是一列紧凑的状态字符,本机观察到的字符里出现过Z。这只说明"状态是一列字符",不能用来证明 Linux 上的取值——两边来源不同。
⚠️代码待验证
awk'{print $1, $3, $4}'/proc/<pid>/stat这一段读的是进程 ID、状态字符、父进程 ID。它只用于观察:$3对上面那张表,$4留给第五章。
本章可以带走的一句:进程状态是一列单字符,proc_pid_stat(5)给出了它的完整字符清单(含两处W与各自的版本区间,照抄不动),而ps(1)把这列字符呈现给你时,会在Z的解释里补上关键半句 “not reaped by its parent”——这一栏是三种"杀不死"的第一道分岔口。
三、僵尸是什么:终止了但没被回收
3.1 官方对僵尸的定义,只有一句话
wait(2)的 NOTES 一开头就把这件事说完了(PS07):
A child that terminates, but has not been waited for becomes a “zombie”.
中文读法:一个已经终止、但还没有被wait过的子进程,就成了僵尸。留意两个成分——terminates(已经终止)说明它不是"活着的坏进程";has not been waited for(还没被 wait 过)说明造成僵尸的责任在父进程这一侧。换句话说,"终止"之后本来还有一个"被 wait"的收尾动作;这个动作没做,进程就停在僵尸状态。
3.2 僵尸在内核里留了些什么
既然已经终止,僵尸还占着什么?手册给的答案非常克制(PS08):
The kernel maintains a minimal set of information about the zombie process (PID, termination status, resource usage information) in order to allow the parent to later perform a wait to obtain information about the child.
三个关键词:minimal(最小一组)、in order to后面那句目的——留着是为了让父进程以后还能wait到它的信息。这组信息一共三项:
| 内核为僵尸保留的信息 | 官方措辞 |
|---|---|
| 进程 ID | PID |
| 终止状态 | termination status |
| 资源使用信息 | resource usage information |
这也解释了僵尸为什么不等于"进程还在跑":它不是一段仍在执行的程序,而是内核为它保留的一份最小记录。
3.3 代价:进程表里的一个槽位
保留不是没有代价。紧接着的一句写得很重(PS09):
As long as a zombie is not removed from the system via a wait, it will consume a slot in the kernel process table, and if this table fills, it will not be possible to create further processes.
意思是:只要僵尸没有被wait从系统里移除,它就占着内核进程表里的一个槽位;这张表填满之后,就无法再创建新进程。这也是僵尸值得单独讲一遍的原因——它的危害不在"占用",而在"占位"。
3.4 从ps看到的那一眼
在ps(1)里,这类进程有一个额外的视觉标记。手册在 NOTES 里写着(PS06):
Processes marked
<defunct>are dead processes (so-called “zombies”) that remain because their parent has not destroyed them properly. These processes will be destroyed byinit(8)if the parent process exits.
这里有两句话值得抄下来。第一句给了僵尸的成因:“remain because their parent has not destroyed them properly”——留下来,是因为父进程没有正常地把它收掉。第二句给了出路:“These processes will be destroyed byinit(8)if the parent process exits.”
第二句话正好把我们带到第五章:父进程的退出,会改变收尾责任的归属。
⚠️代码待验证
ps-eopid,ppid,stat,comm这道观察的意义在 3.2 节已经说清:你在列表里看到的Z,是内核留着的一条记录,不是一段还在跑的代码。
本章可以带走的一句:僵尸是"已经终止、但没有被wait过"的子进程——它在内核里只保留最小一组信息(PID、终止状态、资源使用信息),却会占住进程表里的一个槽位;ps(1)给出的Z释义里那半句 “not reaped by its parent” 已经把锅指清楚了:成因在父进程,不在这个已经终止的进程身上。
四、为什么 kill 送不走它
4.1 两个原因必须拆开
“为什么kill送不走僵尸”,混着两个不同的原因。
原因之一是它已经终止了。第三章那句定义用的是完成时——terminates,终止已经发生。而信号要产生影响,需要一个还在执行的对象去处置它;僵尸留下的只是"最小一组信息"。没有代码在跑,就没有"处置信号"这个动作——这不是"信号被拒绝",是"没有执行流去处理它"。
原因之二是"信号送不走"另有其名。如果一个进程还在跑、只是不理会信号,那才轮到信号处置这一层。signal(7)写得很短(PS10):
The signals
SIGKILLandSIGSTOPcannot be caught, blocked, or ignored.
中文读法:SIGKILL与SIGSTOP既不能被捕获(caught)、不能被阻塞(blocked),也不能被忽略(ignored)。三个动词对应三条路——"捕获"是自定义处理函数,"阻塞"是信号掩码,"忽略"是显式设置忽略处置;这三条路对这两个信号全都关死。
4.2 但这条规则对僵尸不成立
上面那条硬规则只管**"信号与处置"这一层**,而僵尸进不了这一层——它已经终止,不存在"捕获、阻塞、忽略"中的任何一种。
所以"kill -9杀不掉僵尸"的准确说法不是"SIGKILL弱",而是:信号即便被投递过去,也已经没有处置它的执行流了。混起来就会得出那句常见的误判——“连-9都不行,是不是权限不够?”
本文只在这一个点上用到SIGKILL的不可捕获性,到此为止——信号语义框架与容器停机宽限期是另一条轴。
回到第一章的支点:即便走完"投递",僵尸的收尾仍缺一个执行者。“你已经发出过信号"只说明"生成"发生了;“进程还在列表里"既可能是"没终止”,也可能是"终止了没被回收”——"我发了信号"是一个动作,"进程消失了"是一个结果,中间隔着状态与回收。
4.3 能送走它的只有wait
第三章引过那句:僵尸是"not removed from the system via a wait"时占着槽位的;反过来说,移除它的手段就是wait。至于"谁有资格wait它"——它的父进程。这也是第一章现场一为什么特意让你去看父进程还在不在。
4.4 有没有可能"根本不产生僵尸"
有,条件官方写清楚了(出自 POSIX.1-2001 的规定,逐字引,PS16):
POSIX.1-2001 specifies that if the disposition of
SIGCHLDis set toSIG_IGNor theSA_NOCLDWAITflag is set forSIGCHLD(seesigaction(2)), then children that terminate do not become zombies and a call towait()orwaitpid()will block until all children have terminated, and then fail witherrnoset toECHILD.
读法:当SIGCHLD的处置被设为SIG_IGN,或者为SIGCHLD设置了SA_NOCLDWAIT标志时,终止的子进程不会变成僵尸;相应地,wait()或waitpid()会一直阻塞到所有子进程都终止,然后以errno设为ECHILD的方式失败。
这里有个容易踩的坑,手册专门补了一句提醒(PS17):
Note that even though the default disposition of
SIGCHLDis “ignore”, explicitly setting the disposition toSIG_IGNresults in different treatment of zombie process children.
中文读法:虽然SIGCHLD的默认处置是 “ignore”,但显式地把处置设成SIG_IGN,会导致对僵尸子进程产生不同的处理方式。也就是说,"默认就是 ignore"和"你亲手设成SIG_IGN"是两回事——不要因为默认值里写着 ignore,就以为"默认不会有僵尸"。
4.5 顺带记一下口径的版本
wait(2)这一页的 STANDARDS 一栏只有一项(PS18):
POSIX.1-2024.
即:截至 2026-10-15,该手册页标注的标准口径是POSIX.1-2024。
本章可以带走的一句:僵尸送不走有两个互不相干的原因——它已经终止、不再有执行流去处置信号;而SIGKILL那条"不可捕获、不可阻塞、不可忽略"的硬规则只管"还在跑的进程",对僵尸不适用。收尾只有wait一条路,而SIGCHLD处置为SIG_IGN、或为它设了SA_NOCLDWAIT时,子进程才从一开始就不会变成僵尸。
五、孤儿进程去哪了
5.1 先做一次方向上的区分
孤儿和僵尸容易被混为一谈,但方向正好相反:
- 僵尸:子进程先结束,父进程没有来做收尾;
- 孤儿:父进程先结束,子进程还在跑。
一个是"走了的人没人送",另一个是"送的人先走了"。这也是第一章现场三看起来不像前两个的原因:问题出在它的父进程那一栏,而不是子进程自己。
5.2 官方原句:收养与自动收尾
wait(2)的 NOTES 在讲完僵尸的代价之后,紧接着给出了一段关键说明,本文逐字引用(PS15):
If a parent process terminates, then its “zombie” children (if any) are adopted by
init(1), (or by the nearest “subreaper” process as defined through the use of theprctl(2)PR_SET_CHILD_SUBREAPERoperation);init(1)automatically performs a wait to remove the zombies.
拆成三层读:
- 收养:父进程终止后,它那些僵尸子进程(如果有的话)会被
init(1)收养。措辞是adopted by(被……收养)——收尾责任人发生了转移。 - 例外:括号里给了一条替代路径——由"最近的 subreaper 进程"收养,它由
prctl(2)的PR_SET_CHILD_SUBREAPER操作定义。也就是说,收养者不必然是init(1):进程树上方最近、且被显式设成 subreaper 的那个才是接手的人。所以读到"孤儿归 init",要顺手加一句"除非上方存在更近的 subreaper"。 - 自动收尾:
init(1)会automatically performs a wait(自动执行一次 wait)把这些僵尸移除——注意automatically,这一步不需要任何人再发指令。
三点合起来就能回答"孤儿去哪了":孤儿(以及父进程退出时留下的僵尸子进程)被上方的收养者接手,这个收养者会自己把收尾做完。所以父进程先退出,通常不会遗留长期存在的僵尸。
一句边界:官方这段的原句主语是its "zombie" children(它的僵尸子进程)。"孤儿被收养后由收养者完成收尾"是从同一段描述读出的机制含义;要当硬结论依赖,请以最新手册原文为准。
5.3 两处引用里的一处小差异
第三章引过的ps(1)那句讲的是同一件事,只是换了主语,用词有个差异值得记:wait(2)说的是init(1),ps(1)那里写的是init(8)。两处指的是同一个"系统的第一个进程",但括号里的编号不同——引用时按原文照抄,不要统一。
5.4 本机实测的那一眼:PID 1 不叫 init
⚠️代码待验证(macOS 上的只读观察,输出结构 2026-10-15 本机实测)
ps-axopid,ppid,stat,command|head-3本机实测能确认的结构是:第一行是列头,第二行的 PID 是 1、PPID 是 0,而它的命令名不是init——本机是 macOS,PID 1 是launchd。"被init(1)收养"这句官方原文描述的是 Linux 上那个角色;坐在 PID 1 位置上的程序名字随系统而变,读这段话时要把"角色"和"名字"分开。
本章可以带走的一句:孤儿是"父进程先走、子进程还在",方向与僵尸正好相反;父进程终止时,它留下的僵尸子进程会被init(1)收养,而init(1)会自动执行 wait 把它们移除——但这个收养者有一个例外,是"最近的 subreaper 进程",它由PR_SET_CHILD_SUBREAPER定义。
完整版进程状态与回收对照表:这一章讲的"收养者会变"、上一章的"
wait是唯一收尾手段",连同第二章那张 state 字符表和第七章的状态对照表,汇总进资料包,扫码即可获取:
六、D 状态:不可中断睡眠
6.1 官方对D的两句描述
回到第二章那张表。D在内核手册里的说明是:
D Waiting in uninterruptible disk sleep
而ps(1)给的是(PS04):
D uninterruptible sleep (usually I/O)
两句合起来,这个词有三个成分:uninterruptible(不可中断)、disk sleep(磁盘睡眠)、以及ps(1)补的usually I/O——"不可中断"修饰的是这次等待,"磁盘"和"I/O"说的是它在等什么。
6.2 和S的差别,就在一个词上
对照第二章那张表,S的官方说明是:
S Sleeping in an interruptible wait
ps(1)对S的措辞是(PS05):
S interruptible sleep (waiting for an event to complete)
把S和D并排看,差别就在 uninterruptible / interruptible 这一个词:两者都在等某个事件完成,区别在于能不能被打断。
那"能不能被打断"指什么?signal(7)先给了"慢设备"的定义(PS13):
A “slow” device is one where the I/O call may block for an indefinite time, for example, a terminal, pipe, or socket.
读法:"慢设备"指的是那次 I/O 调用可能无限期阻塞的设备,例如终端、管道、套接字。对这类慢设备上的 I/O 调用,手册接着说它们可以被信号处理函数打断。
而紧接着那一句,正是D状态这条线的关键(PS14):
Note that a (local) disk is not a slow device according to this definition; I/O operations on disk devices are not interrupted by signals.
中文读法:注意,按这个定义,一块(本地)磁盘并不属于"慢设备";对磁盘设备的 I/O 操作不会被信号打断。
两句放在一起,D和S的差别就落地了:等在终端、管道、套接字上,是可被信号打断的等待;等在磁盘 I/O 上,不是。
6.3 为什么D状态下"连SIGKILL都像没送到"
既然SIGKILL不可捕获、不可阻塞、不可忽略(PS10),一个D状态的进程为什么还能顶着它不动?答案不在这条硬规则里,而在信号什么时候被处理上。
signal(7)描述了内核检查待处理信号的时机(PS12):
Whenever there is a transition from kernel-mode to user-mode execution (e.g., on return from a system call or scheduling of a thread onto the CPU), the kernel checks whether there is a pending unblocked signal for which the process has established a signal handler.
读法:每当发生一次从内核态到用户态执行的转换(例如系统调用返回、或线程被调度上 CPU),内核就会检查是否存在待处理的、未被阻塞的信号。
两句合起来:信号能被"处理",需要一个从内核态回到用户态的时机;而D状态恰恰是卡在不可被信号打断的 I/O 等待里(6.2 节);这次等待结束前那个时机不会到来,信号处于 pending(PS11),而不是"已经生效"。
注意措辞——不是SIGKILL失效了,而是它生效所需的那个时刻还没出现。D卡住的不是"信号与处置"这一层,而是"什么时候能走到处置"这一层。
⚠️代码待验证
awk'{print $1, $3, $4}'/proc/<pid>/stat这是"看",不是"解决":读到D,下一步是去看它在等什么,而不是再发一次信号。
6.4 关于D状态,本文不替手册补的那部分
有一点必须说清:这两份手册给的都只是"是什么"——uninterruptible、disk sleep、usually I/O——没有给出"内核为什么把磁盘 I/O 设计成不可中断";D状态常见于哪些子系统、能持续多久,本文也没有核到一手依据。这些内容本文不给结论。
本章可以带走的一句:D与S只差"可不可中断"这一个词——等终端、管道、套接字是可被打断的等待,等磁盘 I/O 不是;D状态下信号会停在 pending,因为内核要等到"从内核态回到用户态"的时机才去检查待处理信号,而这个时机在不可中断的等待结束前不会出现——不是SIGKILL失效,是它生效的时刻还没到。
七、按状态决定下一步的对照表
7.1 一张表:先把状态读出来,再决定往哪看
前面六章拆了三条线,落到操作上只有一件事:先读状态字符,再决定下一步该看什么。下面这张表的"含义"一列直接取自第二章那张字符表(PS02)。
| 状态字段 | 含义(官方口径) | 能不能 kill 掉 | 下一步看什么 |
|---|---|---|---|
R | Running(运行中) | 还在执行代码,信号可被处置;SIGKILL不可捕获、不可阻塞、不可忽略 | 它在忙什么,以及是谁在让它忙 |
S | Sleeping in an interruptible wait(可中断等待) | 等的是可被信号打断的那类对象,信号有机会生效 | 它在等哪个事件完成,等的是不是"慢设备"那一类对象 |
D | Waiting in uninterruptible disk sleep(不可中断的磁盘睡眠) | 卡在不可被信号打断的等待里,信号会停在 pending | 它在等哪一次 I/O,而不是再发一次信号 |
Z | Zombie(僵尸) | 已经终止,没有执行流去处置信号;kill不是收尾手段 | ppid指向谁、父进程是否还在;父进程退出后收养者是谁 |
T | Stopped (on a signal)(被信号停止) | 进程还在,只是被停住了 | 先分清是"停住"还是"终止",再回到状态字符本身 |
t | Tracing stop (Linux 2.6.33 onward)(被跟踪停止) | 处于被跟踪状态下的停止,性质与T不同 | 是不是有调试或跟踪环节挂在它上面 |
I | Idle (Linux 4.14 onward)(空闲) | 空闲不是"卡住",与信号是否可处置是两回事 | 先确认它是不是内核线程一类的空闲状态 |
7.2 这张表怎么用:三步,不涉及操作清单
第一步,读字符。只读第二章那张表的字符本体,不要顺手加字。
第二步,分岔。Z走回收线——下一步永远是"看父进程";D走等待线——下一步是"看它在等什么";R、S、T、t、I走第三条线:进程还在,问题在"它此刻在做什么"。
第三步,回到现象。三个现场各自归位:现场一对应Z,现场二对应D,现场三对应"父进程先走"——三种"杀不死",在状态字符上是三个不同的入口。
本文刻意不提供任何命令序列:读状态的价值在于把问题归到哪一条线上。
7.3 收束与边界
截至 2026-10-15,本文口径来自 Linux man-pages 6.19 的四份手册页——proc_pid_stat(5)、wait(2)(STANDARDS 标注为 POSIX.1-2024)、signal(7)、ps(1),逐条事实与出处位置见附表 A。
本文只讲状态与回收机制这一条轴:1.4 节列出的那些邻域都不在范围内,也不提供任何"照着敲就能解决"的步骤清单。文中命令只用于观察;因本机为 macOS、没有 Linux procfs,全部命令块都保留⚠️ 代码待验证标记。
本章可以带走的一句:三种"杀不死"在状态字符上是三个不同的入口——Z去看父进程与回收责任,D去看它在等哪一次不可中断的 I/O,其余状态去看它此刻在做什么;把入口读对,比多敲一遍命令有用得多。
完整版"状态字段 → 下一步"对照表:第七章这张表,连同第二章的 state 字符表(含两处
W与各自版本区间)、第三至六章逐字引用的英文原句与中文解读,汇总进资料包,扫码即可获取:
附表 A:本文引用事实与官方出处对照表
| # | 事实(照抄口径,可含英文原句) | 出处(文档名 + 章节号 + 链接) | 核验日期 | 本文位置 |
|---|---|---|---|---|
| PS01 | 逐字:/proc/pid/stat提供进程状态信息,“This is used byps(1).”;state是第 3 个字段(%c),“One of the following characters, indicating process state:” | Linux man-pages 6.19 ·proc_pid_stat(5)· DESCRIPTION · https://man7.org/linux/man-pages/man5/proc_pid_stat.5.html | 2026-10-15 | 第 2 章 |
| PS02 | 🔴 逐字:state 字符表 13 行,完整内容见第二章表内(含两处W与各自版本区间) | 同上 · DESCRIPTION (3) state | 2026-10-15 | 第 2、7 章 |
| PS03 | 逐字:ppid是第 4 个字段,“The PID of the parent of this process.” | 同上 · DESCRIPTION (4) ppid | 2026-10-15 | 第 2、5 章 |
| PS04 | 逐字:D uninterruptible sleep (usually I/O);Z defunct (“zombie”) process, terminated but not reaped by its parent | Linux man-pages 6.19 ·ps(1)· PROCESS STATE CODES · https://man7.org/linux/man-pages/man1/ps.1.html | 2026-10-15 | 第 2、3 章 |
| PS05 | 逐字:S interruptible sleep (waiting for an event to complete) | 同上 · PROCESS STATE CODES | 2026-10-15 | 第 6 章 |
| PS06 | 🔴 逐字:Processes marked<defunct>是 dead processes(so-called “zombies”),“remain because their parent has not destroyed them properly”;父进程退出时它们会被init(8)销毁 | 同上 · NOTES | 2026-10-15 | 第 3、5 章 |
| PS07 | 🔴 逐字:A child that terminates, but has not been waited for becomes a “zombie”. | Linux man-pages 6.19 ·wait(2)· NOTES · https://man7.org/linux/man-pages/man2/wait.2.html | 2026-10-15 | 第 3 章 |
| PS08 | 逐字:内核为僵尸保留最小一组信息(PID、termination status、resource usage information),目的是让父进程日后能 wait 到它的信息 | 同上 · NOTES | 2026-10-15 | 第 3 章 |
| PS09 | 🔴 逐字:僵尸未被 wait 移除就占内核进程表一个槽位;表满则无法再创建进程 | 同上 · NOTES | 2026-10-15 | 第 3 章 |
| PS10 | 逐字:SIGKILL与SIGSTOP不可捕获、不可阻塞、不可忽略 | Linux man-pages 6.19 ·signal(7)· Standard signals · https://man7.org/linux/man-pages/man7/signal.7.html | 2026-10-15 | 第 4、6 章 |
| PS11 | 逐字:信号在 generated 与 delivered 之间被称为 pending | 同上 · Signal mask and pending signals | 2026-10-15 | 第 1、6 章 |
| PS12 | 逐字:每当发生从内核态到用户态执行的转换,内核检查是否有待处理的未阻塞信号 | 同上 · Execution of signal handlers | 2026-10-15 | 第 6 章 |
| PS13 | 逐字:“slow” device 是 I/O 调用可能无限期阻塞的设备,例如 terminal、pipe、socket | 同上 · Interruption of system calls and library functions by signal handlers | 2026-10-15 | 第 6 章 |
| PS14 | 🔴 逐字:(local) disk 不属于 “slow” device;对磁盘设备的 I/O 操作不会被信号打断 | 同上 · 同节 | 2026-10-15 | 第 6 章 |
| PS15 | 🔴 逐字:父进程终止时其 “zombie” 子进程被init(1)收养,或由"最近的 subreaper"(PR_SET_CHILD_SUBREAPER)收养;init(1)自动执行 wait 移除僵尸 | wait(2)· NOTES | 2026-10-15 | 第 5 章 |
| PS16 | 🔴 逐字:POSIX.1-2001 ——SIGCHLD处置设为SIG_IGN、或为其设SA_NOCLDWAIT时,终止的子进程不会变成僵尸;相应调用会阻塞到子进程全终止后以ECHILD失败 | 同上 · NOTES | 2026-10-15 | 第 4 章 |
| PS17 | 逐字:SIGCHLD默认处置是 “ignore”,但显式设为SIG_IGN会导致对僵尸子进程的不同处理 | 同上 · NOTES | 2026-10-15 | 第 4 章 |
| PS18 | 逐字:STANDARDS —— POSIX.1-2024 | 同上 · STANDARDS | 2026-10-15 | 第 4 章 |
| PS19 | 逐字:kill(2)发出的信号属于 process-directed | signal(7)· Signal mask and pending signals | 2026-10-15 | 第 1 章 |
| 边界-1 | 本文全部命令在Linux 上未实测:本机为 macOS,无 Linux procfs;相关代码块一律保留⚠️ 代码待验证 | 本批执行记录(未实测) | 2026-10-15 | 全文 |
| 边界-2 | 本机ps -axo pid,ppid,stat,command为只读观察,仅确认输出结构(列序、STAT 为紧凑状态字符、PID 1 非init),不用于证明 Linux 取值口径 | 2026-10-15 本机实测(结构) | 2026-10-15 | 第 2、5 章 |
| 边界-3 | 内核为何把磁盘 I/O 设计为不可中断、D状态的时长与涉及子系统:本文未核到一手依据,正文不给结论 | 本批核验边界(未核) | 2026-10-15 | 第 6 章 |
| 边界-4 | "孤儿被收养后由收养者完成收尾"是wait(2)NOTES 同段的机制含义;原句主语为its "zombie" children,正文已标注该边界 | wait(2)· NOTES(边界说明) | 2026-10-15 | 第 5 章 |
| 边界-5 | wait(2)写init(1)、ps(1)写init(8),两处各自照抄,不作统一 | 两份手册页原文 | 2026-10-15 | 第 5 章 |
附表 B:术语速查表
| 术语 | 一句话解释 | 易混点 |
|---|---|---|
S | Sleeping in an interruptible wait,可中断等待 | 与D只差"可不可中断" |
D | Waiting in uninterruptible disk sleep,不可中断的磁盘睡眠 | 信号不是"失效",是生效时机未到 |
Z | Zombie,已终止但未被父进程wait回收 | 它不是"还在跑的进程",是一条最小记录 |
| zombie(僵尸) | 已终止、但还没有被wait过的子进程 | 成因在父进程,不在它自己 |
| orphan(孤儿) | 父进程先终止、自己还在跑的进程 | 方向与僵尸相反,别当同义词 |
| reap(回收) | 通过wait把僵尸从系统里移除 | 唯一手段是wait,不是kill |
<defunct> | ps输出里标记僵尸的写法 | 与之配套的是Z状态字符 |
SIGCHLD/SA_NOCLDWAIT | 处置设为SIG_IGN或设该标志时,终止的子进程不会变成僵尸 | 默认处置是 “ignore”,但显式SIG_IGN是另一回事 |
| subreaper | 收养僵尸子进程的"最近"进程,由PR_SET_CHILD_SUBREAPER定义 | 收养者不必然是无条件的init(1) |
| pending | 信号已生成、尚未被投递的状态 | "发出去了"和"被处理了"是两个时刻 |
写在最后:这篇用到的资料
写这篇文章时,我一直在想那三种被笼统叫作"杀不死"的现场:一个
Z、一个D、一个父进程先走的子进程。它们看起来是同一件事,其实是三条不同的线——“终止了没人收尾”、“卡在不可中断的等待里”、“收尾责任人换了人”。把状态字符读对,问题就归到线上了。顺手也整理了几份配套的东西:
- 靶场环境对照表:DVWA、upload-labs 在 Windows / macOS / Linux 三平台的可行性与推荐路径
- Web 安全学习路线图:从基础打牢到安全管理,四个阶段各学什么
- 常用靶场清单:每个靶场练什么、适合哪个阶段
资料是我自己整理的,放在下面这个码上,扫码即可获取:
添加时备注「靶场」,优先通过。
拿到之后建议先看环境对照表那一份,对照着本文第七章那张表过一遍——下次再遇到"发过信号了、进程还在",先读状态字符,再决定往哪一条线上看,不要一上来就再发一次信号。