8

我有一些代码,它fork()调用setsid()孩子,并开始一些处理。如果任何子进程退出(waitpid(-1, 0)),我将杀死所有子进程组:

child_pids = []
for child_func in child_functions:
    pid = fork()
    if pid == 0:
        setsid()
        child_func()
        exit()
    else:
        child_pids.append(pid)

waitpid(-1, 0)
for child_pid in child_pids:
    try:
        killpg(child_pid, SIGTERM)
    except OSError as e:
        if e.errno != 3: # 3 == no such process
            print "Error killing %s: %s" %(child_pid, e)

但是,有时调用killpg会失败并显示“不允许操作”:

杀死 22841 时出错:[Errno 1] 不允许操作

为什么会发生这种情况?

一个完整的工作示例:

从信号导入 SIGTERM
从系统导入退出
从时间导入睡眠
从操作系统导入 *

定义慢():
    叉()
    睡觉(10)

定义快速():
    睡觉(1)

child_pids = []
对于 [快、慢、慢、快] 中的 child_func:
    pid = fork()
    如果 pid == 0:
        设置ID()
        child_func()
        退出(0)
    别的:
        child_pids.append(pid)

waitpid(-1, 0)
对于 child_pids 中的 child_pid:
    尝试:
        killpg(child_pid, SIGTERM)
    除了 OSError 为 e:
        print "错误杀死 %s: %s" %(child_pid, e)

产生:

$蟒蛇killpg.py
杀死 23293 时出错:[Errno 3] 没有这样的进程
杀死 23296 时出错:[Errno 1] 不允许操作
4

3 回答 3

6

我也添加了一些调试(稍微修改了源代码)。当您尝试杀死已经退出并处于僵尸状态的进程组时,就会发生这种情况。哦,它很容易重复[fast, fast]

$ python so.py 
spawned pgrp 6035
spawned pgrp 6036
Reaped pid: 6036, status: 0
 6035  6034  6035 Z    (Python)
 6034   521  6034 S+   python so.py
 6037  6034  6034 S+   sh -c ps -e -o pid,ppid,pgid,state,command | grep -i python
 6039  6037  6034 R+   grep -i python

killing pg 6035
Error killing 6035: [Errno 1] Operation not permitted
 6035  6034  6035 Z    (Python)
 6034   521  6034 S+   python so.py
 6040  6034  6034 S+   sh -c ps -e -o pid,ppid,pgid,state,command | grep -i python
 6042  6040  6034 S+   grep -i python

killing pg 6036
Error killing 6036: [Errno 3] No such process

不知道如何处理。也许您可以将 waitpid 放在一个 while 循环中以获取所有终止的子进程,然后继续 pgkill() 其余的。

但是您的问题的答案是您获得了 EPERM,因为您不允许 killpg 僵尸进程组负责人(至少在 Mac OS 上)。

此外,这在 python 之外是可验证的。如果你在那里休眠,找到其中一个僵尸的 pgrp,并尝试杀死它的进程组,你也会得到 EPERM:

$ kill -TERM -6115
-bash: kill: (-6115) - Operation not permitted

确认这也不会在 Linux 上发生。

于 2012-09-20T23:51:50.800 回答
5

您显然无法杀死由僵尸组成的进程组。当一个进程退出时,它会变成一个僵尸,直到有人调用waitpid它。通常,init会接管父母已经死亡的孩子,以避免孤儿僵尸孩子。

因此,从某种意义上说,该进程仍然是“周围”的,但它没有获得 CPU 时间并忽略kill直接发送给它的任何命令。但是,如果一个进程组完全由僵尸组成,则行为似乎是杀死进程组抛出EPERM而不是静默失败。请注意,杀死包含非僵尸的进程组仍然会成功。

演示这一点的示例程序:

import os
import time

res = os.fork()

if res:
    time.sleep(0.2)
    pgid = os.getpgid(res)
    print pgid

    while 1:
        try:
            print os.kill(-pgid, 9)
        except Exception, e:
            print e
            break

    print 'wait', os.waitpid(res, 0)

    try:
        print os.kill(-pgid, 9)
    except Exception, e:
        print e

else:
    os.setpgid(0, 0)
    while 1:
        pass

输出看起来像

56621
None
[Errno 1] Operation not permitted
wait (56621, 9)
[Errno 3] No such process

父母用 SIGKILL 杀死孩子,然后再试一次。第二次,它得到EPERM,所以它等待子进程(收获它并销毁它的进程组)。因此,第三个按预期kill产生。ESRCH

于 2012-09-21T00:13:04.547 回答
1

通过添加更多日志记录,看起来有时 killpg 返回 EPERM 而不是 ESRCH:

#!/usr/bin/python

from signal import SIGTERM
from sys import exit
from time import sleep
from os import *

def slow():
    fork()
    sleep(10)

def fast():
    sleep(1)

child_pids = []
for child_func in [fast, slow, slow, fast]:
    pid = fork()
    if pid == 0:
        setsid()
        print child_func, getpid(), getuid(), geteuid()
        child_func()
        exit(0)
    else:
        child_pids.append(pid)

print waitpid(-1, 0)
for child_pid in child_pids:
    try:
        print child_pid, getpgid(child_pid)
    except OSError as e:
        print "Error getpgid %s: %s" %(child_pid, e)      
    try:
        killpg(child_pid, SIGTERM)
    except OSError as e:
        print "Error killing %s: %s" %(child_pid, e)

每当 killpg 因 EPERM 失败时,getpgid 先前已因 ESRCH 失败。例如:

<function fast at 0x109950d70> 26561 503 503
<function slow at 0x109950a28> 26562 503 503
<function slow at 0x109950a28> 26563 503 503
<function fast at 0x109950d70> 26564 503 503
(26564, 0)
26561 Error getpgid 26561: [Errno 3] No such process
Error killing 26561: [Errno 1] Operation not permitted
26562 26562
26563 26563
26564 Error getpgid 26564: [Errno 3] No such process
Error killing 26564: [Errno 3] No such process

我不知道为什么会发生这种情况——无论是合法行为,还是 Darwin 中的错误(继承自 FreeBSD 或其他)等等。

似乎您可以通过调用kill(child_pid, 0);仔细检查 EPERM 来解决它。如果返回 ESRCH,则没有实际的权限问题。当然,这在代码中看起来很丑:

for child_pid in child_pids:
    try:
        killpg(child_pid, SIGTERM)
    except OSError as e:
        if e.errno != 3: # 3 == no such process
            if e.errno == 1:
                try:
                    kill(child_pid, 0)
                except OSError as e2:
                    if e2.errno != 3:
                        print "Error killing %s: %s" %(child_pid, e)
            else:
                print "Error killing %s: %s" %(child_pid, e)
于 2012-09-20T23:34:34.680 回答