人不为己,天诛地灭。
直到这一天,某个进程也决定要这么去做。
而你,也决定做个幕后黑手,助他一臂之力。
需求
指定一个文件路径,所有访问这个文件的进程,除目标进程外,全部杀掉。
因为自己也在访问这个文件,所以不能直接lsof
后全部杀死。
而因为系统机制,父进程及祖先进程也不能杀死。
而子进程可能是目标进程启动的子任务,因此也不能杀死。
悲催的是,pstree等第三包的命令不允许使用。
基本方法
不能杀死父进程,以及祖先进程,否则可能会被一窝端。
也不能杀死子孙进程:不孝有三,无后为大。
但是叔叔辈、伯伯辈以及他们的子孙分支,那就只能 say sorry 了。
祖先进程
典型的比如 shell 脚本与父进程 bash。
而我们知道,ps -ef
不仅能显示所有进程信息(包含PID),还会显示父进程(PPID)。
因此找到某个进程的所有前代进程很简单:
进程->父进程->爷进程->太爷进程->…->祖宗进程(PID为1,个别为0)。
也即按照此链路一直往上找,直到PID为0或1。
简单通过一个map即可实现。
子孙进程
很典型的例子是一个 shell 脚本,基本命令、tee日志等都会启动子进程。
而找到子孙进程要稍微复杂一些,毕竟开枝散叶了嘛。
原始进程我们称之为目标进程。在 shell 中,可以通过$$
获取到自己的PID。
我们对随便一个PID按照上面相同的路径向上寻找,会有两种可能:
- 找到了1或0:此进程不是目标进程的子孙进程。可能和目标进程没关系,或者是它的祖先进程。
- 找到了目标进程:是目标进程的子孙进程,属于保护目标。
使用命令
简单的使用shell命令实现即可。
其实如果有 pstree
及类似命令,一条命令即可解决。但奈何不允许装这个包。
注意使用 lsof 和 ps 命令实现。
lsof
lsof
查找访问文件的全部进程,可获取到PID。lsof +D
还会搜索目录下的目录。
如果文件不存在,lsof 会报错,因此可以先检查。
简单看了下,没找到能直接去掉 header 的选项。那就解析时过滤吧。
ps
ps -ef
查找所有的进程及与父进程的对应关系。
–noheaders 可以去掉 header。(MAC下不行)
但是发现指定列后,显示的进程不全了,可以继续深究下。
kill
暴力一点,kill -9
即可。
但需要注意,一些进程,比如lsof自身,只短暂存在。
考虑到代码执行时间很短,忽略掉误杀其它进程的情况。
但是要考虑kill不存在的进程导致的报错。
遗留问题
screen 抱养之后的问题
测试发现,screen 运行有个特殊问题,可称之为“抱养”的场景。
总结一句话:从 screen detach后,screen的父进程会变为1。因此在screen内运行此脚本,会导致外面的shell被杀。但是screen不会被杀,其内运行的脚本也不会受影响。但是使用者会被退出终端,ssh断开连接等。
场景如下:
screen -S test
,之后在screen内ps -ef
可看到,screen进程的PPID为之前的bash进程在screen内运行
bash test.sh
,此脚本内容就是简单打印自己PID然后睡眠:echo $$
sleep 999999从screen detach出来,
ps -ef
可看到,screen进程的PPID变为了1!
脱离父子关系!好绝情!
但也可以理解,screen要在外层shell退出之后依然运行。它实在忍受不了这个不靠谱的爹了。再次screen -x test 来attach进去,执行脚本,发现父进程是之前的screen -S!
这种情况下,在 screen 内执行脚本,除非screen -S
未detach过,否则会有问题。
但是万幸的是,脚本执行不会被中断。
代码实现
python+shell实现方式如下。
echo "my PID: $$" |
#!/usr/bin/env python |
其它方式
- pstree 拿到进程树
- 读取系统文件等方式,解析出进程树
- 拷贝执行文件到其它目录等方式,保证自己不会访问这个目录,然后直接全部杀掉