python兼职平台信号处理_如何在Windows机器上处理python中的信号
Python的os.kill在Windows上包含了兩個不相關的API.當sig參數為CTRL_C_EVENT或CTRL_BREAK_EVENT時,它會調用GenerateConsoleCtrlEvent.在這種情況下,pid參數是進程組ID.如果后一個調用失敗,并且對于所有其他sig值,則調用OpenProcess然后調用TerminateProcess.在這種情況下,pid參數是進程ID,sig值作為退出代碼傳遞.終止Windows進程類似于將SIGKILL發送到POSIX進程.通常應該避免這種情況,因為它不允許過程干凈地退出.
請注意,os.kill的文檔錯誤地聲稱“kill()還會使進程句柄被殺死”,這從來都不是真的.它調用OpenProcess來獲取進程句柄.
對于跨平臺代碼,使用WinAPI CTRL_C_EVENT和CTRL_BREAK_EVENT而不是SIGINT和SIGBREAK的決定是不幸的.它還沒有定義GenerateConsoleCtrlEvent在傳遞不是進程組ID的進程ID時所執行的操作.在采用進程ID的API中使用此函數充其量是可疑的,并且可能非常錯誤.
根據您的特殊需求,您可以編寫一個適配器函數,使os.kill對于跨平臺代碼更加友好.例如:
import os
import sys
import time
import signal
if sys.platform != 'win32':
kill = os.kill
sleep = time.sleep
else:
# adapt the conflated API on Windows.
import threading
sigmap = {signal.SIGINT: signal.CTRL_C_EVENT,
signal.SIGBREAK: signal.CTRL_BREAK_EVENT}
def kill(pid, signum):
if signum in sigmap and pid == os.getpid():
# we don't know if the current process is a
# process group leader, so just broadcast
# to all processes attached to this console.
pid = 0
thread = threading.current_thread()
handler = signal.getsignal(signum)
# work around the synchronization problem when calling
# kill from the main thread.
if (signum in sigmap and
thread.name == 'MainThread' and
callable(handler) and
pid == 0):
event = threading.Event()
def handler_set_event(signum, frame):
event.set()
return handler(signum, frame)
signal.signal(signum, handler_set_event)
try:
os.kill(pid, sigmap[signum])
# busy wait because we can't block in the main
# thread, else the signal handler can't execute.
while not event.is_set():
pass
finally:
signal.signal(signum, handler)
else:
os.kill(pid, sigmap.get(signum, signum))
if sys.version_info[0] > 2:
sleep = time.sleep
else:
import errno
# If the signal handler doesn't raise an exception,
# time.sleep in Python 2 raises an EINTR IOError, but
# Python 3 just resumes the sleep.
def sleep(interval):
'''sleep that ignores EINTR in 2.x on Windows'''
while True:
try:
t = time.time()
time.sleep(interval)
except IOError as e:
if e.errno != errno.EINTR:
raise
interval -= time.time() - t
if interval <= 0:
break
def func(signum, frame):
# note: don't print in a signal handler.
global g_sigint
g_sigint = True
#raise KeyboardInterrupt
signal.signal(signal.SIGINT, func)
g_kill = False
while True:
g_sigint = False
g_kill = not g_kill
print('Running [%d]' % os.getpid())
sleep(2)
if g_kill:
kill(os.getpid(), signal.SIGINT)
if g_sigint:
print('SIGINT')
else:
print('No SIGINT')
討論
Windows不在系統級[*]實現信號. Microsoft的C運行時實現了標準C所需的六個信號:SIGINT,SIGABRT,SIGTERM,SIGSEGV,SIGILL和SIGFPE.
SIGABRT和SIGTERM僅針對當前流程實施.您可以通過C raise調用處理程序.例如(在Python 3.5中):
>>> import signal, ctypes
>>> ucrtbase = ctypes.CDLL('ucrtbase')
>>> c_raise = ucrtbase['raise']
>>> foo = lambda *a: print('foo')
>>> signal.signal(signal.SIGTERM, foo)
>>> c_raise(signal.SIGTERM)
foo
0
SIGTERM沒用.
使用信號模塊也無法對SIGABRT做很多事情,因為abort函數會在處理程序返回時終止進程,這會在使用信號模塊的內部處理程序時立即發生(它會觸發已注冊的Python可調用的標記)主線).對于Python 3,您可以改為使用faulthandler模塊.或者通過ctypes調用CRT的signal函數來設置ctypes回調作為處理程序.
CRT通過為相應的Windows異常設置Windows structured exception handler來實現SIGSEGV,SIGILL和SIGFPE:
STATUS_ACCESS_VIOLATION SIGSEGV
STATUS_ILLEGAL_INSTRUCTION SIGILL
STATUS_PRIVILEGED_INSTRUCTION SIGILL
STATUS_FLOAT_DENORMAL_OPERAND SIGFPE
STATUS_FLOAT_DIVIDE_BY_ZERO SIGFPE
STATUS_FLOAT_INEXACT_RESULT SIGFPE
STATUS_FLOAT_INVALID_OPERATION SIGFPE
STATUS_FLOAT_OVERFLOW SIGFPE
STATUS_FLOAT_STACK_CHECK SIGFPE
STATUS_FLOAT_UNDERFLOW SIGFPE
STATUS_FLOAT_MULTIPLE_FAULTS SIGFPE
STATUS_FLOAT_MULTIPLE_TRAPS SIGFPE
CRT對這些信號的實現與Python的信號處理不兼容.異常過濾器調用已注冊的處理程序,然后返回EXCEPTION_CONTINUE_EXECUTION.但是,Python的處理程序只會為解釋器跳轉一個標志,以便稍后在主線程中調用已注冊的可調用對象.因此,觸發異常的錯誤代碼將繼續在無限循環中觸發.在Python 3中,您可以使用faulthandler模??塊來處理這些基于異常的信號.
這就留下了SIGINT,Windows添加了非標準的SIGBREAK.控制臺和非控制臺進程都可以引發這些信號,但只有控制臺進程可以從另一個進程接收它們. CRT通過SetConsoleCtrlHandler注冊控制臺控制事件處理程序來實現這一點.
控制臺通過在附加進程中創建一個新線程來發送控制事件,該進程在kernel32.dll或kernelbase.dll(未記錄)中的CtrlRoutine處開始執行.處理程序不在主線程上執行會導致同步問題(例如在REPL中或使用輸入).此外,如果在等待同步對象或等待同步I / O完成時被阻塞,則控制事件不會中斷主線程.如果SIGINT可以中斷,則需要注意避免主線程中的阻塞. Python 3嘗試通過使用Windows事件對象解決此問題,該事件對象也可用于應該可被SIGINT中斷的等待.
當控制臺向進程發送CTRL_C_EVENT或CTRL_BREAK_EVENT時,CRT的處理程序分別調用已注冊的SIGINT或SIGBREAK處理程序.還會為控制臺在窗口關閉時發送的CTRL_CLOSE_EVENT調用SIGBREAK處理程序. Python默認通過在主線程中運行KeyboardInterrupt來處理SIGINT.但是,SIGBREAK最初是默認的CTRL_BREAK_EVENT處理程序,它調用ExitProcess(STATUS_CONTROL_C_EXIT).
您可以通過GenerateConsoleCtrlEvent將控制事件發送到連接到當前控制臺的所有進程.這可以定位屬于進程組的進程子集,或目標組0,以將事件發送到連接到當前控制臺的所有進程.
進程組不是Windows API的詳細記錄方面.查詢進程組沒有公共API,但Windows會話中的每個進程都屬于進程組,即使它只是wininit.exe組(服務會話)或winlogon.exe組(交互式會話).通過在創建新進程時傳遞創建標志CREATE_NEW_PROCESS_GROUP來創建新組.組ID是已創建進程的進程ID.據我所知,控制臺是唯一使用進程組的系統,而且只適用于GenerateConsoleCtrlEvent.
當目標ID不是進程組ID時,控制臺所執行的操作是未定義的,不應依賴它.如果進程及其父進程都附加到控制臺,則向其發送控制事件基本上就像目標是組0一樣.如果父進程未附加到當前控制臺,則GenerateConsoleCtrlEvent失敗,并且os.kill調用TerminateProcess.奇怪的是,如果您定位“系統”進程(PID 4)及其子進程smss.exe(會話管理器),則調用成功但沒有任何反應,除了目標被錯誤地添加到附加進程列表(即GetConsoleProcessList).這可能是因為父進程是“空閑”進程,由于它是PID 0,因此被隱式接受為廣播PGID.父進程規則也適用于非控制臺進程.定位非控制臺子進程不會做任何事情 – 除了通過添加未附加的進程錯誤地破壞控制臺進程列表.我希望您應該只將控制事件發送到組0或通過CREATE_NEW_PROCESS_GROUP創建的已知進程組.
不要依賴于能夠將CTRL_C_EVENT發送到除組0之外的任何內容,因為它最初在新進程組中被禁用.將此事件發送到新組并非不可能,但目標進程首先必須通過調用SetConsoleCtrlHandler(NULL,FALSE)來啟用CTRL_C_EVENT.
CTRL_BREAK_EVENT是您可以依賴的所有內容,因為它無法禁用.發送此事件是一種優雅地殺死使用CREATE_NEW_PROCESS_GROUP啟動的子進程的簡單方法,假設它具有Windows CTRL_BREAK_EVENT或C SIGBREAK處理程序.如果沒有,默認處理程序將終止進程,將退出代碼設置為STATUS_CONTROL_C_EXIT.例如:
>>> import os, signal, subprocess
>>> p = subprocess.Popen('python.exe',
... stdin=subprocess.PIPE,
... creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
>>> os.kill(p.pid, signal.CTRL_BREAK_EVENT)
>>> STATUS_CONTROL_C_EXIT = 0xC000013A
>>> p.wait() == STATUS_CONTROL_C_EXIT
True
請注意,CTRL_BREAK_EVENT未發送到當前進程,因為該示例以子進程的進程組為目標(包括連接到控制臺的所有子進程,依此類推).如果示例使用了組0,那么當前進程也會被殺死,因為我沒有定義SIGBREAK處理程序.讓我們嘗試一下,但設置一個處理程序:
>>> ctrl_break = lambda *a: print('^BREAK')
>>> signal.signal(signal.SIGBREAK, ctrl_break)
>>> os.kill(0, signal.CTRL_BREAK_EVENT)
^BREAK
[*]
Windows有asynchronous procedure calls(APC)將目標函數排入線程.有關Windows APC的深入分析,請參閱文章Inside NT’s Asynchronous Procedure Call,尤其是闡明內核模式APC的作用.您可以通過QueueUserAPC將用戶模式APC排隊到一個線程.它們也會在ReadFileEx和WriteFileEx排隊等待I / O完成例程.
當線程進入可警告等待時(例如,WaitForSingleObjectEx或SleepEx,其中bAlertable為TRUE),執行用戶模式APC.另一方面,內核模式APC立即被調度(當IRQL低于APC_LEVEL時).它們通常由I / O管理器用于在發出請求的線程的上下文中完成異步I / O請求包(例如,將數據從IRP復制到用戶模式緩沖區).有關顯示APC如何影響可警告和不可警報等待的表,請參閱Waits and APCs.請注意,內核模式APC不會中斷等待,而是由等待例程在內部執行.
Windows可以使用APC實現類似POSIX的信號,但實際上它使用其他方法來實現相同的目的.例如:
窗口消息可以發送并發布到共享調用thread’s desktop且處于相同或更低完整性級別的所有線程.當線程調用PeekMessage或GetMessage時,發送窗口消息會將其置于系統隊列中以調用窗口過程.發布消息會將其添加到線程的消息隊列中,該消息隊列的默認配額為10,000條消息.具有消息隊列的線程應具有消息循環以通過GetMessage和DispatchMessage處理隊列.僅控制臺進程中的線程通常沒有消息隊列.但是,控制臺主機進程conhost.exe顯然可以.單擊關閉按鈕時,或者通過任務管理器或taskkill.exe終止控制臺的主進程時,WM_CLOSE消息將發布到控制臺窗口的線程的消息隊列中.控制臺輪流向其所有連接的進程發送CTRL_CLOSE_EVENT.如果進程處理該事件,則在強制終止之前,它會在5秒內正常退出.
總結
以上是生活随笔為你收集整理的python兼职平台信号处理_如何在Windows机器上处理python中的信号的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 联想新一代拯救者 9000X 笔记本搭载
- 下一篇: 在python中使用关键字define定