☰
kill 之后进程还在:僵尸、孤儿与不可中断睡眠
2026/10/15 16:02:59 网站建设 项目流程

授权与合规声明
本文全部操作对象均为自建隔离靶场(本机容器或隔离虚拟机),涉及安全测试的环节必须以取得合法授权为前提。未经授权的渗透测试违反《中华人民共和国网络安全法》与《刑法》相关条款,须承担相应法律责任。本文只讲环境配置、版本对照与靶场隔离,不含任何攻击步骤、利用载荷与绕过手法,请勿将文中环境指向任何非自有系统。

一、现象:三种"杀不死"

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 byps(1).

这句话值得停一下:你在ps输出里看到的那一列字母,源头在这里——它属于"内核提供、ps呈现",而不是ps自己发明的分类。

2.2proc_pid_stat(5)的 state 字段,逐字照抄

在这份文件格式里,字段是有顺序的,state排在第 3 个,类型标记是%c(单个字符)(PS01)。手册对它的说明是:

One of the following characters, indicating process state:

下面这张表就是紧随其后的整张字符表,本文照抄,不增不减(PS02):

字符官方说明
RRunning
SSleeping in an interruptible wait
DWaiting in uninterruptible disk sleep
ZZombie
TStopped (on a signal) or (before Linux 2.6.33) trace stopped
tTracing stop (Linux 2.6.33 onward)
WPaging (only before Linux 2.6.0)
XDead (from Linux 2.6.0 onward)
xDead (Linux 2.6.33 to 3.13 only)
KWakekill (Linux 2.6.33 to 3.13 only)
WWaking (Linux 2.6.33 to 3.13 only)
PParked (Linux 3.9 to 3.13 only)
IIdle (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到它的信息。这组信息一共三项:

内核为僵尸保留的信息官方措辞
进程 IDPID
终止状态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 signalsSIGKILLandSIGSTOPcannot 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 ofSIGCHLDis 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 ofSIGCHLDis “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 byinit(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.

拆成三层读:

  1. 收养:父进程终止后,它那些僵尸子进程(如果有的话)会被init(1)收养。措辞是adopted by(被……收养)——收尾责任人发生了转移。
  2. 例外:括号里给了一条替代路径——由"最近的 subreaper 进程"收养,它由prctl(2)的PR_SET_CHILD_SUBREAPER操作定义。也就是说,收养者不必然是init(1):进程树上方最近、且被显式设成 subreaper 的那个才是接手的人。所以读到"孤儿归 init",要顺手加一句"除非上方存在更近的 subreaper"。
  3. 自动收尾: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 掉下一步看什么
RRunning(运行中)还在执行代码,信号可被处置;SIGKILL不可捕获、不可阻塞、不可忽略它在忙什么,以及是谁在让它忙
SSleeping in an interruptible wait(可中断等待)等的是可被信号打断的那类对象,信号有机会生效它在等哪个事件完成,等的是不是"慢设备"那一类对象
DWaiting in uninterruptible disk sleep(不可中断的磁盘睡眠)卡在不可被信号打断的等待里,信号会停在 pending它在等哪一次 I/O,而不是再发一次信号
ZZombie(僵尸)已经终止,没有执行流去处置信号;kill不是收尾手段ppid指向谁、父进程是否还在;父进程退出后收养者是谁
TStopped (on a signal)(被信号停止)进程还在,只是被停住了先分清是"停住"还是"终止",再回到状态字符本身
tTracing stop (Linux 2.6.33 onward)(被跟踪停止)处于被跟踪状态下的停止,性质与T不同是不是有调试或跟踪环节挂在它上面
IIdle (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.html2026-10-15第 2 章
PS02🔴 逐字:state 字符表 13 行,完整内容见第二章表内(含两处W与各自版本区间)同上 · DESCRIPTION (3) state2026-10-15第 2、7 章
PS03逐字:ppid是第 4 个字段,“The PID of the parent of this process.”同上 · DESCRIPTION (4) ppid2026-10-15第 2、5 章
PS04逐字:D uninterruptible sleep (usually I/O);Z defunct (“zombie”) process, terminated but not reaped by its parentLinux man-pages 6.19 ·ps(1)· PROCESS STATE CODES · https://man7.org/linux/man-pages/man1/ps.1.html2026-10-15第 2、3 章
PS05逐字:S interruptible sleep (waiting for an event to complete)同上 · PROCESS STATE CODES2026-10-15第 6 章
PS06🔴 逐字:Processes marked<defunct>是 dead processes(so-called “zombies”),“remain because their parent has not destroyed them properly”;父进程退出时它们会被init(8)销毁同上 · NOTES2026-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.html2026-10-15第 3 章
PS08逐字:内核为僵尸保留最小一组信息(PID、termination status、resource usage information),目的是让父进程日后能 wait 到它的信息同上 · NOTES2026-10-15第 3 章
PS09🔴 逐字:僵尸未被 wait 移除就占内核进程表一个槽位;表满则无法再创建进程同上 · NOTES2026-10-15第 3 章
PS10逐字:SIGKILL与SIGSTOP不可捕获、不可阻塞、不可忽略Linux man-pages 6.19 ·signal(7)· Standard signals · https://man7.org/linux/man-pages/man7/signal.7.html2026-10-15第 4、6 章
PS11逐字:信号在 generated 与 delivered 之间被称为 pending同上 · Signal mask and pending signals2026-10-15第 1、6 章
PS12逐字:每当发生从内核态到用户态执行的转换,内核检查是否有待处理的未阻塞信号同上 · Execution of signal handlers2026-10-15第 6 章
PS13逐字:“slow” device 是 I/O 调用可能无限期阻塞的设备,例如 terminal、pipe、socket同上 · Interruption of system calls and library functions by signal handlers2026-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)· NOTES2026-10-15第 5 章
PS16🔴 逐字:POSIX.1-2001 ——SIGCHLD处置设为SIG_IGN、或为其设SA_NOCLDWAIT时,终止的子进程不会变成僵尸;相应调用会阻塞到子进程全终止后以ECHILD失败同上 · NOTES2026-10-15第 4 章
PS17逐字:SIGCHLD默认处置是 “ignore”,但显式设为SIG_IGN会导致对僵尸子进程的不同处理同上 · NOTES2026-10-15第 4 章
PS18逐字:STANDARDS —— POSIX.1-2024同上 · STANDARDS2026-10-15第 4 章
PS19逐字:kill(2)发出的信号属于 process-directedsignal(7)· Signal mask and pending signals2026-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 章
边界-5wait(2)写init(1)、ps(1)写init(8),两处各自照抄,不作统一两份手册页原文2026-10-15第 5 章

附表 B:术语速查表

术语一句话解释易混点
SSleeping in an interruptible wait,可中断等待与D只差"可不可中断"
DWaiting in uninterruptible disk sleep,不可中断的磁盘睡眠信号不是"失效",是生效时机未到
ZZombie,已终止但未被父进程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 安全学习路线图:从基础打牢到安全管理,四个阶段各学什么
  • 常用靶场清单:每个靶场练什么、适合哪个阶段

资料是我自己整理的,放在下面这个码上,扫码即可获取:

添加时备注「靶场」,优先通过。

拿到之后建议先看环境对照表那一份,对照着本文第七章那张表过一遍——下次再遇到"发过信号了、进程还在",先读状态字符,再决定往哪一条线上看,不要一上来就再发一次信号。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询