python 子进程:在Windows中删除子进程

dhxwm5r4  于 2023-03-11  发布在  Python
关注(0)|答案(8)|浏览(186)

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

ergxz8rk

ergxz8rk1#

通过使用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)
rhfm7lfc

rhfm7lfc2#

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)
6rvt4ljy

6rvt4ljy3#

下面是Job对象方法的示例代码,但它使用的不是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)
k7fdbhmy

k7fdbhmy4#

这是一件很难做到的事情。Windows实际上并没有在进程空间中存储进程树。也不可能终止一个进程并指定它的子进程也应该死亡。
一种解决方法是使用taskkill并告诉它wack整棵树。
另一种方法(假设您正在生成顶层流程)是使用这样一个模块:http://benjamin.smedbergs.us/blog/tag/killableprocess/
为了自己做这些,你必须花一些时间反向构建列表,也就是说,一个进程存储指向它的父进程的指针,但是父进程似乎不存储关于子进程的信息。
因此,您必须查看系统中的所有进程(这其实并不难),然后通过查看父进程字段手动连接各个点,然后选择感兴趣的树并遍历整个过程,依次逐个删除每个节点。
请注意,Windows不会在父节点死亡时更新子节点的父节点指针,因此您的树中可能会有空隙。我不知道您可以对这些空隙做些什么。

gv8xihay

gv8xihay5#

把孩子放在NT Job object中,然后你可以杀死所有的孩子

gpfsuwkq

gpfsuwkq6#

我有同样的问题,只是杀死进程通过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)
a0zr77ik

a0zr77ik7#

我使用kevin-smyth的答案为subprocess.Popen创建了一个drop-in replacement,它将创建的子进程限制在一个匿名作业对象中,设置为在关闭时终止:

# 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)
b5buobof

b5buobof8#

如果你像我一样,在你的vbs脚本中创建一个对象,那么这些答案都不会起作用,逻辑类似于:

Set oExcel = CreateObject("Excel.Application")

这是因为在Windows中,这会将excel作为服务生成,并且它的所有者是svchost.exe,而不是您的VBS脚本。感谢their answer的spokers,这对诊断此问题非常有帮助。
我做得相当粗糙,基本上在启动之前创建了一个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.03s有效,所以0.05s似乎是安全的。
except代码块中的else只是一个万能函数,以防它没有注意到创建了哪个PID,它将杀死脚本启动后作为服务启动的所有excel进程。
更接近的答案可能是扩展Spokes的链接答案,并从shell中运行excel,但我没有时间完成这个工作。

相关问题