子进程:删除Windows中的子进程

发布于 2024-07-30 08:05:50 字数 158 浏览 13 评论 0原文

在 Windows 上,subprocess.Popen.terminate 调用 win32 的 TerminalProcess。 但是,我看到的行为是我尝试终止的进程的子进程仍在运行。 这是为什么? 如何确保该进程启动的所有子进程都被杀死?

On Windows, subprocess.Popen.terminate calls win32's TerminalProcess. However, the behavior I see is that child processes of the process I am trying to terminate are still running. Why is that? How do I ensure all child processes started by the process are killed?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

云归处 2024-08-06 08:05:50

通过使用 psutil

import psutil, os

def kill_proc_tree(pid, including_parent=True):    
    parent = psutil.Process(pid)
    children = parent.children(recursive=True)
    for child in children:
        child.kill()
    gone, still_alive = psutil.wait_procs(children, timeout=5)
    if including_parent:
        parent.kill()
        parent.wait(5)

me = os.getpid()
kill_proc_tree(me)

By using psutil:

import psutil, os

def kill_proc_tree(pid, including_parent=True):    
    parent = psutil.Process(pid)
    children = parent.children(recursive=True)
    for child in children:
        child.kill()
    gone, still_alive = psutil.wait_procs(children, timeout=5)
    if including_parent:
        parent.kill()
        parent.wait(5)

me = os.getpid()
kill_proc_tree(me)
只是我以为 2024-08-06 08:05:50

taskkill/T 标志一起使用

p = subprocess.Popen(...)
<wait>
subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)])

taskkill 的标志具有以下文档:

TASKKILL [/S system [/U username [/P [password]]]]
         { [/FI filter] [/PID processid | /IM imagename] } [/T] [/F]

/S    system           Specifies the remote system to connect to.
/U    [domain\]user    Specifies the user context under which the
                       command should execute.
/P    [password]       Specifies the password for the given user
                       context. Prompts for input if omitted.
/FI   filter           Applies a filter to select a set of tasks.
                       Allows "*" to be used. ex. imagename eq acme*
/PID  processid        Specifies the PID of the process to be terminated.
                       Use TaskList to get the PID.
/IM   imagename        Specifies the image name of the process
                       to be terminated. Wildcard '*' can be used
                       to specify all tasks or image names.
/T                     Terminates the specified process and any
                       child processes which were started by it.
/F                     Specifies to forcefully terminate the process(es).
/?                     Displays this help message.

或者使用 comtypes 和 win32api 遍历进程树:

def killsubprocesses(parent_pid):
    '''kill parent and all subprocess using COM/WMI and the win32api'''

    log = logging.getLogger('killprocesses')

    try:
        import comtypes.client
    except ImportError:
        log.debug("comtypes not present, not killing subprocesses")
        return

    logging.getLogger('comtypes').setLevel(logging.INFO)

    log.debug('Querying process tree...')

    # get pid and subprocess pids for all alive processes
    WMI = comtypes.client.CoGetObject('winmgmts:')
    processes = WMI.InstancesOf('Win32_Process')
    subprocess_pids = {} # parent pid -> list of child pids

    for process in processes:
        pid = process.Properties_('ProcessID').Value
        parent = process.Properties_('ParentProcessId').Value
        log.trace("process %i's parent is: %s" % (pid, parent))
        subprocess_pids.setdefault(parent, []).append(pid)
        subprocess_pids.setdefault(pid, [])

    # find which we need to kill
    log.debug('Determining subprocesses for pid %i...' % parent_pid)

    processes_to_kill = []
    parent_processes = [parent_pid]
    while parent_processes:
        current_pid = parent_processes.pop()
        subps = subprocess_pids[current_pid]
        log.debug("process %i children are: %s" % (current_pid, subps))
        parent_processes.extend(subps)
        processes_to_kill.extend(subps)

    # kill the subprocess tree
    if processes_to_kill:
        log.info('Process pid %i spawned %i subprocesses, terminating them...' % 
            (parent_pid, len(processes_to_kill)))
    else:
        log.debug('Process pid %i had no subprocesses.' % parent_pid)

    import ctypes
    kernel32 = ctypes.windll.kernel32
    for pid in processes_to_kill:
        hProcess = kernel32.OpenProcess(PROCESS_TERMINATE, FALSE, pid)
        if not hProcess:
            log.warning('Unable to open process pid %i for termination' % pid)
        else:
            log.debug('Terminating pid %i' % pid)                        
            kernel32.TerminateProcess(hProcess, 3)
            kernel32.CloseHandle(hProcess)

Use taskkill with the /T flag

p = subprocess.Popen(...)
<wait>
subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)])

The flags to taskkill has the following docs:

TASKKILL [/S system [/U username [/P [password]]]]
         { [/FI filter] [/PID processid | /IM imagename] } [/T] [/F]

/S    system           Specifies the remote system to connect to.
/U    [domain\]user    Specifies the user context under which the
                       command should execute.
/P    [password]       Specifies the password for the given user
                       context. Prompts for input if omitted.
/FI   filter           Applies a filter to select a set of tasks.
                       Allows "*" to be used. ex. imagename eq acme*
/PID  processid        Specifies the PID of the process to be terminated.
                       Use TaskList to get the PID.
/IM   imagename        Specifies the image name of the process
                       to be terminated. Wildcard '*' can be used
                       to specify all tasks or image names.
/T                     Terminates the specified process and any
                       child processes which were started by it.
/F                     Specifies to forcefully terminate the process(es).
/?                     Displays this help message.

Or walk the process tree using comtypes and win32api:

def killsubprocesses(parent_pid):
    '''kill parent and all subprocess using COM/WMI and the win32api'''

    log = logging.getLogger('killprocesses')

    try:
        import comtypes.client
    except ImportError:
        log.debug("comtypes not present, not killing subprocesses")
        return

    logging.getLogger('comtypes').setLevel(logging.INFO)

    log.debug('Querying process tree...')

    # get pid and subprocess pids for all alive processes
    WMI = comtypes.client.CoGetObject('winmgmts:')
    processes = WMI.InstancesOf('Win32_Process')
    subprocess_pids = {} # parent pid -> list of child pids

    for process in processes:
        pid = process.Properties_('ProcessID').Value
        parent = process.Properties_('ParentProcessId').Value
        log.trace("process %i's parent is: %s" % (pid, parent))
        subprocess_pids.setdefault(parent, []).append(pid)
        subprocess_pids.setdefault(pid, [])

    # find which we need to kill
    log.debug('Determining subprocesses for pid %i...' % parent_pid)

    processes_to_kill = []
    parent_processes = [parent_pid]
    while parent_processes:
        current_pid = parent_processes.pop()
        subps = subprocess_pids[current_pid]
        log.debug("process %i children are: %s" % (current_pid, subps))
        parent_processes.extend(subps)
        processes_to_kill.extend(subps)

    # kill the subprocess tree
    if processes_to_kill:
        log.info('Process pid %i spawned %i subprocesses, terminating them...' % 
            (parent_pid, len(processes_to_kill)))
    else:
        log.debug('Process pid %i had no subprocesses.' % parent_pid)

    import ctypes
    kernel32 = ctypes.windll.kernel32
    for pid in processes_to_kill:
        hProcess = kernel32.OpenProcess(PROCESS_TERMINATE, FALSE, pid)
        if not hProcess:
            log.warning('Unable to open process pid %i for termination' % pid)
        else:
            log.debug('Terminating pid %i' % pid)                        
            kernel32.TerminateProcess(hProcess, 3)
            kernel32.CloseHandle(hProcess)
静赏你的温柔 2024-08-06 08:05:50

以下是 Job 对象方法的示例代码,但它使用 win32api.CreateProcess 而不是 subprocess

import win32process
import win32job
startup = win32process.STARTUPINFO()
(hProcess, hThread, processId, threadId) = win32process.CreateProcess(None, command, None, None, True, win32process.CREATE_BREAKAWAY_FROM_JOB, None, None, startup)

hJob = win32job.CreateJobObject(None, '')
extended_info = win32job.QueryInformationJobObject(hJob, win32job.JobObjectExtendedLimitInformation)
extended_info['BasicLimitInformation']['LimitFlags'] = win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
win32job.SetInformationJobObject(hJob, win32job.JobObjectExtendedLimitInformation, extended_info)
win32job.AssignProcessToJobObject(hJob, hProcess)

Here's example code for the Job object method, but instead of subprocess it uses win32api.CreateProcess

import win32process
import win32job
startup = win32process.STARTUPINFO()
(hProcess, hThread, processId, threadId) = win32process.CreateProcess(None, command, None, None, True, win32process.CREATE_BREAKAWAY_FROM_JOB, None, None, startup)

hJob = win32job.CreateJobObject(None, '')
extended_info = win32job.QueryInformationJobObject(hJob, win32job.JobObjectExtendedLimitInformation)
extended_info['BasicLimitInformation']['LimitFlags'] = win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
win32job.SetInformationJobObject(hJob, win32job.JobObjectExtendedLimitInformation, extended_info)
win32job.AssignProcessToJobObject(hJob, hProcess)
忆悲凉 2024-08-06 08:05:50

这是一件很难做到的事情。 Windows 实际上并不在进程空间中存储进程树。 也不可能终止一个进程并指定它的子进程也应该死亡。

解决这个问题的一种方法是使用 taskkill 并告诉它破坏整棵树。

另一种方法(假设您正在生成顶级进程)是使用一个根据此类事情开发的模块:http://benjamin.smedbergs.us/blog/tag/killableprocess/

为了一般地为自己执行此操作,您必须花一些时间向后构建列表。 也就是说,一个进程存储指向其父进程的指针,但父进程似乎不存储有关子进程的信息。

因此,您必须查看系统中的所有进程(这实际上并不难),然后通过查看父进程字段手动将这些点连接起来。 然后,您选择您感兴趣的树并遍历整个树,依次杀死每个节点,一个接一个。

请注意,当父级死亡时,Windows 不会更新子级的父级指针,因此您的树中可能存在间隙。 我不知道你能对这些做些什么。

This is a hard thing to do. Windows does not actually store a process tree in the process space. Nor is it possible to terminate a process and specify that it's children should also die.

One way around that is to use taskkill and tell it to wack the whole tree.

Another way to do it (assuming that you are spawning the top-level process) is to use a module that was developed with this sort of thing in mind: http://benjamin.smedbergs.us/blog/tag/killableprocess/

In order to do this generically for yourself, you have to spend some time building the list backwards. That is, a process stores pointers to it's PARENT, but parents appear to not store information about children.

So you have to look at all the processes in the system (which really isn't that hard), and then manually connect the dots yourself by looking at the parent process field. Then, you select the tree you are interested in and walk the whole thing, killing each node in turn, one by one.

Note that Windows doesn't update a child's parent pointer when the parent dies, so there may be gaps in your tree. I'm not aware of anything you can do about those.

缱绻入梦 2024-08-06 08:05:50

将子项放入 NT Job 对象,那么你就可以杀死所有的孩子

Put the children in a NT Job object, then you can kill all children

疑心病 2024-08-06 08:05:50

我遇到了同样的问题,只是通过 Windows 命令杀死进程,并选择杀死儿童“/T”

def kill_command_windows(pid):
    '''Run command via subprocess'''
    dev_null = open(os.devnull, 'w')
    command = ['TASKKILL', '/F', '/T', '/PID', str(pid)]
    proc = subprocess.Popen(command, stdin=dev_null, stdout=sys.stdout, stderr=sys.stderr)

I had the same problem and just killing process via windows command with option for child killing "/T"

def kill_command_windows(pid):
    '''Run command via subprocess'''
    dev_null = open(os.devnull, 'w')
    command = ['TASKKILL', '/F', '/T', '/PID', str(pid)]
    proc = subprocess.Popen(command, stdin=dev_null, stdout=sys.stdout, stderr=sys.stderr)
云柯 2024-08-06 08:05:50

我使用 kevin-smyth 的答案为 subprocess.Popen 创建 直接替换 将创建的子进程限制在匿名作业对象中,设置为在关闭时终止:

# coding: utf-8

from subprocess import Popen
import subprocess
import win32job
import win32process
import win32api


class JobPopen(Popen):
    """Start a process in a new Win32 job object.

    This `subprocess.Popen` subclass takes the same arguments as Popen and
    behaves the same way. In addition to that, created processes will be
    assigned to a new anonymous Win32 job object on startup, which will
    guarantee that the processes will be terminated by the OS as soon as
    either the Popen object, job object handle or parent Python process are
    closed.
    """

    class _winapijobhandler(object):
        """Patches the native CreateProcess function in the subprocess module
        to assign created threads to the given job"""

        def __init__(self, oldapi, job):
            self._oldapi = oldapi
            self._job = job

        def __getattr__(self, key):
            if key != "CreateProcess":
                return getattr(self._oldapi, key)  # Any other function is run as before
            else:
                return self.CreateProcess  # CreateProcess will call the function below

        def CreateProcess(self, *args, **kwargs):
            hp, ht, pid, tid = self._oldapi.CreateProcess(*args, **kwargs)
            win32job.AssignProcessToJobObject(self._job, hp)
            win32process.ResumeThread(ht)
            return hp, ht, pid, tid

    def __init__(self, *args, **kwargs):
        """Start a new process using an anonymous job object. Takes the same arguments as Popen"""

        # Create a new job object
        self._win32_job = self._create_job_object()

        # Temporarily patch the subprocess creation logic to assign created
        # processes to the new job, then resume execution normally.
        CREATE_SUSPENDED = 0x00000004
        kwargs.setdefault("creationflags", 0)
        kwargs["creationflags"] |= CREATE_SUSPENDED
        try:
            _winapi = subprocess._winapi  # Python 3
            _winapi_key = "_winapi"
        except AttributeError:
            _winapi = subprocess._subprocess  # Python 2
            _winapi_key = "_subprocess"
        try:
            setattr(subprocess, _winapi_key, JobPopen._winapijobhandler(_winapi, self._win32_job))
            super(JobPopen, self).__init__(*args, **kwargs)
        finally:
            setattr(subprocess, _winapi_key, _winapi)

    def _create_job_object(self):
        """Create a new anonymous job object"""
        hjob = win32job.CreateJobObject(None, "")
        extended_info = win32job.QueryInformationJobObject(hjob, win32job.JobObjectExtendedLimitInformation)
        extended_info['BasicLimitInformation']['LimitFlags'] = win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
        win32job.SetInformationJobObject(hjob, win32job.JobObjectExtendedLimitInformation, extended_info)
        return hjob

    def _close_job_object(self, hjob):
        """Close the handle to a job object, terminating all processes inside it"""
        if self._win32_job:
            win32api.CloseHandle(self._win32_job)
            self._win32_job = None

    # This ensures that no remaining subprocesses are found when the process
    # exits from a `with JobPopen(...)` block.
    def __exit__(self, exc_type, value, traceback):
        super(JobPopen, self).__exit__(exc_type, value, traceback)
        self._close_job_object(self._win32_job)

    # Python does not keep a reference outside of the parent class when the
    # interpreter exits, which is why we keep it here.
    _Popen = subprocess.Popen  
    def __del__(self):
        self._Popen.__del__(self)
        self._close_job_object(self._win32_job)

I used kevin-smyth's answer to create a drop-in replacement for subprocess.Popen that confines the created child process in an anonymous job object, set up for terminating on close:

# coding: utf-8

from subprocess import Popen
import subprocess
import win32job
import win32process
import win32api


class JobPopen(Popen):
    """Start a process in a new Win32 job object.

    This `subprocess.Popen` subclass takes the same arguments as Popen and
    behaves the same way. In addition to that, created processes will be
    assigned to a new anonymous Win32 job object on startup, which will
    guarantee that the processes will be terminated by the OS as soon as
    either the Popen object, job object handle or parent Python process are
    closed.
    """

    class _winapijobhandler(object):
        """Patches the native CreateProcess function in the subprocess module
        to assign created threads to the given job"""

        def __init__(self, oldapi, job):
            self._oldapi = oldapi
            self._job = job

        def __getattr__(self, key):
            if key != "CreateProcess":
                return getattr(self._oldapi, key)  # Any other function is run as before
            else:
                return self.CreateProcess  # CreateProcess will call the function below

        def CreateProcess(self, *args, **kwargs):
            hp, ht, pid, tid = self._oldapi.CreateProcess(*args, **kwargs)
            win32job.AssignProcessToJobObject(self._job, hp)
            win32process.ResumeThread(ht)
            return hp, ht, pid, tid

    def __init__(self, *args, **kwargs):
        """Start a new process using an anonymous job object. Takes the same arguments as Popen"""

        # Create a new job object
        self._win32_job = self._create_job_object()

        # Temporarily patch the subprocess creation logic to assign created
        # processes to the new job, then resume execution normally.
        CREATE_SUSPENDED = 0x00000004
        kwargs.setdefault("creationflags", 0)
        kwargs["creationflags"] |= CREATE_SUSPENDED
        try:
            _winapi = subprocess._winapi  # Python 3
            _winapi_key = "_winapi"
        except AttributeError:
            _winapi = subprocess._subprocess  # Python 2
            _winapi_key = "_subprocess"
        try:
            setattr(subprocess, _winapi_key, JobPopen._winapijobhandler(_winapi, self._win32_job))
            super(JobPopen, self).__init__(*args, **kwargs)
        finally:
            setattr(subprocess, _winapi_key, _winapi)

    def _create_job_object(self):
        """Create a new anonymous job object"""
        hjob = win32job.CreateJobObject(None, "")
        extended_info = win32job.QueryInformationJobObject(hjob, win32job.JobObjectExtendedLimitInformation)
        extended_info['BasicLimitInformation']['LimitFlags'] = win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
        win32job.SetInformationJobObject(hjob, win32job.JobObjectExtendedLimitInformation, extended_info)
        return hjob

    def _close_job_object(self, hjob):
        """Close the handle to a job object, terminating all processes inside it"""
        if self._win32_job:
            win32api.CloseHandle(self._win32_job)
            self._win32_job = None

    # This ensures that no remaining subprocesses are found when the process
    # exits from a `with JobPopen(...)` block.
    def __exit__(self, exc_type, value, traceback):
        super(JobPopen, self).__exit__(exc_type, value, traceback)
        self._close_job_object(self._win32_job)

    # Python does not keep a reference outside of the parent class when the
    # interpreter exits, which is why we keep it here.
    _Popen = subprocess.Popen  
    def __del__(self):
        self._Popen.__del__(self)
        self._close_job_object(self._win32_job)
忆梦 2024-08-06 08:05:50

如果您像我一样从 vbs 脚本中创建一个对象,其逻辑类似于:

Set oExcel = CreateObject("Excel.Application")

这是因为在 Windows 中,它会生成 excel 作为服务,并且它有所有者svchost.exe 而不是您的 VBS 脚本。 感谢发言人他们的回答对诊断这个问题非常有帮助。

我相当粗略地处理了这个问题,基本上在启动之前创建了一个 Excel 进程列表,然后获取另一个 Excel 进程列表并进行比较,新的 PID 是我的新 Excel 脚本。 然后如果我需要杀死它,我可以通过它的 PID 识别它来杀死它。

this_script_pid = 0

try:
    running_excel_pids = [pid for pid in psutil.pids() \
                          if psutil.Process(pid).name() == "EXCEL.EXE"] # record all instances of excel before this starts
    p = subprocess.Popen(<starts vbs script that starts excel>)
    
    time.sleep(0.05) # give the script time to execute
    for pid in [p for p in psutil.pids() if psutil.Process(p).name() == "EXCEL.EXE"]:
        if pid not in running_excel_pids:
            this_script_pid = pid
            break
    
    p.communicate() # required to make our python program wait for the process to end


except:
    p.terminate() # kill the parent script
    if this_script_pid != 0:
        print("killing individual script")
        psutil.Process(this_script_pid).kill()

    else: 
        for pid in [p for p in psutil.pids() if psutil.Process(p).name() == "EXCEL.EXE"]:
            if (pid not in running_excel_pids) and (psutil.Process(pid).parent().name()=="svchost.exe"):
                proc = psutil.Process(pid)
                proc.kill()
    exit()  # gracefully quit

请注意,上述内容仅适用于我的特定情况,尽管我已尝试使其尽可能有针对性,但几乎肯定不应该在多线程环境中使用。

0.05s 的等待时间是根据经验发现的。 0.01s 太短了,0.03 有效,所以 0.05s 似乎是安全的。

except 块中的 else 只是一个包罗万象的工具,以防它无法记录创建了哪个 PID。 它将杀死自脚本启动以来作为服务启动的所有 Excel 进程。

更简洁的答案可能是扩展 Spokes 的链接答案并从 shell 内运行 excel,但我没有时间解决这个问题。

None of the answers will work if, like me, you're creating an Object from within your vbs script with logic similar to:

Set oExcel = CreateObject("Excel.Application")

This is because in Windows this spawns excel as a service and it has the owner of svchost.exe rather than your VBS script. Thank you to spokes for their answer which was surpremely helpful in diagnosing this.

I went about this fairly crudely and basically created a list of excel processes before I start it, then get another list of the excel processes afterwards and compare them, with the new PID being my new excel script. Then if I need to kill it, I kill it by identifying it through its PID.

this_script_pid = 0

try:
    running_excel_pids = [pid for pid in psutil.pids() \
                          if psutil.Process(pid).name() == "EXCEL.EXE"] # record all instances of excel before this starts
    p = subprocess.Popen(<starts vbs script that starts excel>)
    
    time.sleep(0.05) # give the script time to execute
    for pid in [p for p in psutil.pids() if psutil.Process(p).name() == "EXCEL.EXE"]:
        if pid not in running_excel_pids:
            this_script_pid = pid
            break
    
    p.communicate() # required to make our python program wait for the process to end


except:
    p.terminate() # kill the parent script
    if this_script_pid != 0:
        print("killing individual script")
        psutil.Process(this_script_pid).kill()

    else: 
        for pid in [p for p in psutil.pids() if psutil.Process(p).name() == "EXCEL.EXE"]:
            if (pid not in running_excel_pids) and (psutil.Process(pid).parent().name()=="svchost.exe"):
                proc = psutil.Process(pid)
                proc.kill()
    exit()  # gracefully quit

Note the above only works in my specific circumstance and even though I've tried to make it as targeted as possible it almost certainly should not be used in a multi-threaded environment.

The 0.05s wait was found empirically. 0.01s was too short, 0.03 worked, so 0.05s seemed safe.

The else in the except block is just a catch all in case it didn't manage to note which PID was created. It will kill all excel processes that have started as a service since the script began.

Neater answer is probably extending on Spokes' linked answer and running excel from within a shell, but I didn't have time to work through that.

花落人断肠 2024-08-06 08:05:50

您可以为此使用控制台进程组 subprocess 模块已提供必要的 Windows API:

p = subprocess.Popen(cmd, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, ...)
# some actions, e.g. timeout expiration
p.send_signal(signal.CTRL_BREAK_EVENT)  # CTRL_C_EVENT is disabled. Read more below
p.terminate()  # not necessary

附加信息:
Windows进程创建标志
Python 子进程文档
Python 源代码示例

You can use Console process groups for this and the subprocess module already provides the necessary Windows API:

p = subprocess.Popen(cmd, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, ...)
# some actions, e.g. timeout expiration
p.send_signal(signal.CTRL_BREAK_EVENT)  # CTRL_C_EVENT is disabled. Read more below
p.terminate()  # not necessary

Additional info:
Windows process creation flags
Python subprocess docs
Python source code example

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文