我刚刚更新到Ubuntu 15.10,并突然在Python 2.7中,我不能终止我作为根时创build的进程。 例如,这不会终止tcpdump:
import subprocess, shlex, time tcpdump_command = "sudo tcpdump -w example.pcap -i eth0 -n icmp" tcpdump_process = subprocess.Popen( shlex.split(tcpdump_command), stdout=subprocess.PIPE, stderr=subprocess.PIPE) time.sleep(1) tcpdump_process.terminate() tcpdump_out, tcpdump_err = tcpdump_process.communicate()
发生了什么? 它适用于以前的版本。
TL; DR : sudo
不会转发由命令进程组中的进程发送的信号, 自2014年5月28日在sudo 1.8.11
发布的提交 – python进程(sudo的父进程)和tcpdump进程(grandchild)处于相同进程组,因此sudo
不会将.terminate()
发送的SIGTERM
信号转发给tcpdump
进程。
它在作为root用户运行代码时以及作为常规用户+ sudo时显示相同的行为
以常规用户身份运行会引发OSError: [Errno 1] Operation not permitted
.terminate()
上的OSError: [Errno 1] Operation not permitted
例外(如预期的那样)。
以root
身份运行重现了这个问题: sudo
和tcpdump
进程不会在.terminate()
被.terminate()
,并且代码在Ubuntu 15.10的.terminate()
被卡住。
这个代码杀死了Ubuntu 12.04上的两个进程。
tcpdump_process
名称是误导的,因为变量引用了sudo
进程(子进程),而不是tcpdump
(孙子):
python └─ sudo tcpdump -w example.pcap -i eth0 -n icmp └─ tcpdump -w example.pcap -i eth0 -n icmp
正如@E先生在评论中指出的那样,在这里你不需要sudo
:你已经是root了(尽管你不应该是 – 你可以在没有root权限的情况下嗅探网络 )。 如果你放弃sudo
; .terminate()
作品。
一般来说, .terminate()
不会递归地杀死整个进程树,因此希望孙子进程能够存活。 虽然sudo
是一个特例, 从sudo(8)手册页 :
当命令作为
sudo
进程的子进程运行时,sudo
会将收到的信号转发给命令。 重点是我的
即, sudo
应该将SIGTERM
中继到tcpdump
并且tcpdump
应该停止在SIGTERM
上捕获数据包,从tcpdump(8)手册页 :
Tcpdump将继续捕获数据包,直到它被一个SIGINT信号(例如,通过键入中断字符,通常为control-C)或一个SIGTERM信号(通常由kill(1)命令生成) ;
即预期的行为是 : tcpdump_process.terminate()
发送SIGTERM到sudo
,它将信号传递给tcpdump
, tcpdump
应该停止捕获,并且这两个进程都退出,而.communicate()
将tcpdump
的stderr输出返回给python脚本。
注意:原则上,可以从相同的sudo(8)手册页运行该命令而不创建子进程:
作为一种特殊情况,如果策略插件没有定义关闭函数并且不需要pty,
sudo
将直接执行命令,而不是先调用fork(2)
因此.terminate()
可以直接发送SIGTERM到tcpdump
进程 – 虽然这不是解释: sudo tcpdump
在我的测试中在Ubuntu 12.04和15.10上创建了两个进程。
如果我在shell中运行sudo tcpdump -w example.pcap -i eth0 -n icmp
然后kill -SIGTERM
终止这两个进程。 它看起来不像Python问题(Python 2.7.3(在Ubuntu 12.04上使用)在Ubuntu 15.10上表现相同,Python 3在这里也失败了)。
它与进程组( 作业控制 )有关:将preexec_fn=os.setpgrp
传递preexec_fn=os.setpgrp
subprocess.Popen()
这样sudo
将在一个新的进程组(作业)中,在shell中作为领导者,使得tcpdump_process.terminate()
在这种情况下工作。
发生了什么? 它适用于以前的版本。
解释是在sudo的源代码中 :
不要转发命令进程组中进程发送的信号 ,不要转发它,因为我们不希望孩子间接自杀。 例如,这可能会发生一些重新启动的版本,调用kill(-1,SIGTERM)来终止所有其他进程。 重点是我的
preexec_fn=os.setpgrp
更改了sudo
的进程组。 sudo
的后代如tcpdump
进程继承了这个组。 python
和tcpdump
不再处于同一个进程组中,因此.terminate()
发送的信号被sudo
中继到tcpdump
并退出。
Ubuntu 15.04使用Sudo version 1.8.9p5
,问题代码运行正常。
Ubuntu 15.10使用包含提交的 Sudo version 1.8.12
。
在wily(15.10)中的sudo(8)手册页仍然只谈论关于子进程本身 – 没有提到进程组:
作为特殊情况,sudo不会中继正在运行的命令发送的信号。
它应该是:
作为特殊情况,sudo不会中继正在运行的命令的进程组中的进程发送的信号。
您可以在Ubuntu的错误跟踪器和/或上游错误跟踪器上打开文档问题。