Linux 进程杀不死,子进程还一大串?kill 只点了名,没连坐
运维现场的熟悉一幕:服务器上跑着一个业务进程,底下挂了几十个子进程,用 kill 杀掉主进程,回头一看,子进程还在跑;挨个 kill,杀了这个又冒出那个,跟打地鼠一样。折腾几下后不免怀疑命令坏了,或者干脆想重启服务器。其实不是 kill 不灵,是它的工作方式跟你以为的不一样。
为什么杀了一个,还有一串
kill 的本质是「给某个进程号发一个信号」。进程号(PID)在系统里是一个个独立的编号,kill 后面跟一个 PID,信号就只发给这一个进程。主进程被终止了,它之前拉起来的子进程并不会跟着退场——它们成了「没人管但还活着」的进程,有的还会把主进程的活儿继续干着。所以「杀了老的、小的还在」不是玄学,是进程模型的正常表现。
反过来还有一种情况:kill 发了信号,进程根本不理你。默认的终止信号是可以被进程拦截或忽略的,程序可以在自己代码里写好「收到终止信号,先把手头的活干完再退」。于是「杀不死」就有两层含义:一层是只杀了一个,树还在;一层是信号被忽略了。
按进程组来,一次清干净
第一步,先看清这棵树。用 pstree 加 -p 参数跟上主进程号,把整个进程树连着进程号打印出来,看明白谁是谁的父进程、一共多少个:
pstree -p 主进程号
第二步,确认进程组号。同一个任务拉起来的一家人,通常被放进同一个进程组,组长往往就是主进程,进程组号一般等于组长的进程号。用 ps 命令加 -o pid,pgid,cmd 参数,把每个成员的组号列出来核对。
第三步,对进程组整组发信号。在进程组号前面加一个负号,kill 就不再是对单个进程点名,而是对整组连坐:
kill – -进程组号
第四步,如果组杀完还有顽固分子,说明有进程忽略了普通终止信号,升级到强制信号再补一轮:
kill -9 – -进程组号
第五步,实在不放心或进程组分了家,回到第一步打出来的那棵树,对残留的进程号逐个补刀。这里补一个边界:如果 ps 里看到的是 defunct 状态、状态栏挂着 Z 的进程,那是另一种情况——进程已经退出了,只是「善后」还没被父进程收走,不在杀进程的范围里,对着尸体开枪没有意义,得从它的父进程下手。
防复发的正路
杀进程治标,根子常在主程序身上:一个设计良好的服务程序,应该自己管理好子进程——收到终止信号时带着孩子们一起有序退出。哪个业务程序每次都靠人手动连坐,就值得在下一个迭代里提需求了。
对企业运维来说,这套「看树、认组、组杀、升级、补刀」的动作值得固化进操作手册。贵州诚鑫致达科技在给企业做服务器运维托管时,进程治理是日常巡检的固定一节,变更窗口里终止服务一律按进程组操作,避免留下孤儿进程占着内存和句柄。
下次再遇到杀不死的一大串,先 pstree 看全树,再对着负号开枪——多数时候一轮就清净了。