python兼职平台信号处理_如何在Windows机器上处理python中的信号
Python的os.kill在Windows上包含了兩個(gè)不相關(guān)的API.當(dāng)sig參數(shù)為CTRL_C_EVENT或CTRL_BREAK_EVENT時(shí),它會(huì)調(diào)用GenerateConsoleCtrlEvent.在這種情況下,pid參數(shù)是進(jìn)程組ID.如果后一個(gè)調(diào)用失敗,并且對(duì)于所有其他sig值,則調(diào)用OpenProcess然后調(diào)用TerminateProcess.在這種情況下,pid參數(shù)是進(jìn)程ID,sig值作為退出代碼傳遞.終止Windows進(jìn)程類似于將SIGKILL發(fā)送到POSIX進(jìn)程.通常應(yīng)該避免這種情況,因?yàn)樗辉试S過(guò)程干凈地退出.
請(qǐng)注意,os.kill的文檔錯(cuò)誤地聲稱“kill()還會(huì)使進(jìn)程句柄被殺死”,這從來(lái)都不是真的.它調(diào)用OpenProcess來(lái)獲取進(jìn)程句柄.
對(duì)于跨平臺(tái)代碼,使用WinAPI CTRL_C_EVENT和CTRL_BREAK_EVENT而不是SIGINT和SIGBREAK的決定是不幸的.它還沒(méi)有定義GenerateConsoleCtrlEvent在傳遞不是進(jìn)程組ID的進(jìn)程ID時(shí)所執(zhí)行的操作.在采用進(jìn)程ID的API中使用此函數(shù)充其量是可疑的,并且可能非常錯(cuò)誤.
根據(jù)您的特殊需求,您可以編寫(xiě)一個(gè)適配器函數(shù),使os.kill對(duì)于跨平臺(tái)代碼更加友好.例如:
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不在系統(tǒng)級(jí)[*]實(shí)現(xiàn)信號(hào). Microsoft的C運(yùn)行時(shí)實(shí)現(xiàn)了標(biāo)準(zhǔn)C所需的六個(gè)信號(hào):SIGINT,SIGABRT,SIGTERM,SIGSEGV,SIGILL和SIGFPE.
SIGABRT和SIGTERM僅針對(duì)當(dāng)前流程實(shí)施.您可以通過(guò)C raise調(diào)用處理程序.例如(在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沒(méi)用.
使用信號(hào)模塊也無(wú)法對(duì)SIGABRT做很多事情,因?yàn)閍bort函數(shù)會(huì)在處理程序返回時(shí)終止進(jìn)程,這會(huì)在使用信號(hào)模塊的內(nèi)部處理程序時(shí)立即發(fā)生(它會(huì)觸發(fā)已注冊(cè)的Python可調(diào)用的標(biāo)記)主線).對(duì)于Python 3,您可以改為使用faulthandler模塊.或者通過(guò)ctypes調(diào)用CRT的signal函數(shù)來(lái)設(shè)置ctypes回調(diào)作為處理程序.
CRT通過(guò)為相應(yīng)的Windows異常設(shè)置Windows structured exception handler來(lái)實(shí)現(xiàn)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對(duì)這些信號(hào)的實(shí)現(xiàn)與Python的信號(hào)處理不兼容.異常過(guò)濾器調(diào)用已注冊(cè)的處理程序,然后返回EXCEPTION_CONTINUE_EXECUTION.但是,Python的處理程序只會(huì)為解釋器跳轉(zhuǎn)一個(gè)標(biāo)志,以便稍后在主線程中調(diào)用已注冊(cè)的可調(diào)用對(duì)象.因此,觸發(fā)異常的錯(cuò)誤代碼將繼續(xù)在無(wú)限循環(huán)中觸發(fā).在Python 3中,您可以使用faulthandler模??塊來(lái)處理這些基于異常的信號(hào).
這就留下了SIGINT,Windows添加了非標(biāo)準(zhǔn)的SIGBREAK.控制臺(tái)和非控制臺(tái)進(jìn)程都可以引發(fā)這些信號(hào),但只有控制臺(tái)進(jìn)程可以從另一個(gè)進(jìn)程接收它們. CRT通過(guò)SetConsoleCtrlHandler注冊(cè)控制臺(tái)控制事件處理程序來(lái)實(shí)現(xiàn)這一點(diǎn).
控制臺(tái)通過(guò)在附加進(jìn)程中創(chuàng)建一個(gè)新線程來(lái)發(fā)送控制事件,該進(jìn)程在kernel32.dll或kernelbase.dll(未記錄)中的CtrlRoutine處開(kāi)始執(zhí)行.處理程序不在主線程上執(zhí)行會(huì)導(dǎo)致同步問(wèn)題(例如在REPL中或使用輸入).此外,如果在等待同步對(duì)象或等待同步I / O完成時(shí)被阻塞,則控制事件不會(huì)中斷主線程.如果SIGINT可以中斷,則需要注意避免主線程中的阻塞. Python 3嘗試通過(guò)使用Windows事件對(duì)象解決此問(wèn)題,該事件對(duì)象也可用于應(yīng)該可被SIGINT中斷的等待.
當(dāng)控制臺(tái)向進(jìn)程發(fā)送CTRL_C_EVENT或CTRL_BREAK_EVENT時(shí),CRT的處理程序分別調(diào)用已注冊(cè)的SIGINT或SIGBREAK處理程序.還會(huì)為控制臺(tái)在窗口關(guān)閉時(shí)發(fā)送的CTRL_CLOSE_EVENT調(diào)用SIGBREAK處理程序. Python默認(rèn)通過(guò)在主線程中運(yùn)行KeyboardInterrupt來(lái)處理SIGINT.但是,SIGBREAK最初是默認(rèn)的CTRL_BREAK_EVENT處理程序,它調(diào)用ExitProcess(STATUS_CONTROL_C_EXIT).
您可以通過(guò)GenerateConsoleCtrlEvent將控制事件發(fā)送到連接到當(dāng)前控制臺(tái)的所有進(jìn)程.這可以定位屬于進(jìn)程組的進(jìn)程子集,或目標(biāo)組0,以將事件發(fā)送到連接到當(dāng)前控制臺(tái)的所有進(jìn)程.
進(jìn)程組不是Windows API的詳細(xì)記錄方面.查詢進(jìn)程組沒(méi)有公共API,但Windows會(huì)話中的每個(gè)進(jìn)程都屬于進(jìn)程組,即使它只是wininit.exe組(服務(wù)會(huì)話)或winlogon.exe組(交互式會(huì)話).通過(guò)在創(chuàng)建新進(jìn)程時(shí)傳遞創(chuàng)建標(biāo)志CREATE_NEW_PROCESS_GROUP來(lái)創(chuàng)建新組.組ID是已創(chuàng)建進(jìn)程的進(jìn)程ID.據(jù)我所知,控制臺(tái)是唯一使用進(jìn)程組的系統(tǒng),而且只適用于GenerateConsoleCtrlEvent.
當(dāng)目標(biāo)ID不是進(jìn)程組ID時(shí),控制臺(tái)所執(zhí)行的操作是未定義的,不應(yīng)依賴它.如果進(jìn)程及其父進(jìn)程都附加到控制臺(tái),則向其發(fā)送控制事件基本上就像目標(biāo)是組0一樣.如果父進(jìn)程未附加到當(dāng)前控制臺(tái),則GenerateConsoleCtrlEvent失敗,并且os.kill調(diào)用TerminateProcess.奇怪的是,如果您定位“系統(tǒng)”進(jìn)程(PID 4)及其子進(jìn)程smss.exe(會(huì)話管理器),則調(diào)用成功但沒(méi)有任何反應(yīng),除了目標(biāo)被錯(cuò)誤地添加到附加進(jìn)程列表(即GetConsoleProcessList).這可能是因?yàn)楦高M(jìn)程是“空閑”進(jìn)程,由于它是PID 0,因此被隱式接受為廣播PGID.父進(jìn)程規(guī)則也適用于非控制臺(tái)進(jìn)程.定位非控制臺(tái)子進(jìn)程不會(huì)做任何事情 – 除了通過(guò)添加未附加的進(jìn)程錯(cuò)誤地破壞控制臺(tái)進(jìn)程列表.我希望您應(yīng)該只將控制事件發(fā)送到組0或通過(guò)CREATE_NEW_PROCESS_GROUP創(chuàng)建的已知進(jìn)程組.
不要依賴于能夠?qū)TRL_C_EVENT發(fā)送到除組0之外的任何內(nèi)容,因?yàn)樗畛踉谛逻M(jìn)程組中被禁用.將此事件發(fā)送到新組并非不可能,但目標(biāo)進(jìn)程首先必須通過(guò)調(diào)用SetConsoleCtrlHandler(NULL,FALSE)來(lái)啟用CTRL_C_EVENT.
CTRL_BREAK_EVENT是您可以依賴的所有內(nèi)容,因?yàn)樗鼰o(wú)法禁用.發(fā)送此事件是一種優(yōu)雅地殺死使用CREATE_NEW_PROCESS_GROUP啟動(dòng)的子進(jìn)程的簡(jiǎn)單方法,假設(shè)它具有Windows CTRL_BREAK_EVENT或C SIGBREAK處理程序.如果沒(méi)有,默認(rèn)處理程序?qū)⒔K止進(jìn)程,將退出代碼設(shè)置為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
請(qǐng)注意,CTRL_BREAK_EVENT未發(fā)送到當(dāng)前進(jìn)程,因?yàn)樵撌纠宰舆M(jìn)程的進(jìn)程組為目標(biāo)(包括連接到控制臺(tái)的所有子進(jìn)程,依此類推).如果示例使用了組0,那么當(dāng)前進(jìn)程也會(huì)被殺死,因?yàn)槲覜](méi)有定義SIGBREAK處理程序.讓我們嘗試一下,但設(shè)置一個(gè)處理程序:
>>> 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)將目標(biāo)函數(shù)排入線程.有關(guān)Windows APC的深入分析,請(qǐng)參閱文章Inside NT’s Asynchronous Procedure Call,尤其是闡明內(nèi)核模式APC的作用.您可以通過(guò)QueueUserAPC將用戶模式APC排隊(duì)到一個(gè)線程.它們也會(huì)在ReadFileEx和WriteFileEx排隊(duì)等待I / O完成例程.
當(dāng)線程進(jìn)入可警告等待時(shí)(例如,WaitForSingleObjectEx或SleepEx,其中bAlertable為T(mén)RUE),執(zhí)行用戶模式APC.另一方面,內(nèi)核模式APC立即被調(diào)度(當(dāng)IRQL低于APC_LEVEL時(shí)).它們通常由I / O管理器用于在發(fā)出請(qǐng)求的線程的上下文中完成異步I / O請(qǐng)求包(例如,將數(shù)據(jù)從IRP復(fù)制到用戶模式緩沖區(qū)).有關(guān)顯示APC如何影響可警告和不可警報(bào)等待的表,請(qǐng)參閱Waits and APCs.請(qǐng)注意,內(nèi)核模式APC不會(huì)中斷等待,而是由等待例程在內(nèi)部執(zhí)行.
Windows可以使用APC實(shí)現(xiàn)類似POSIX的信號(hào),但實(shí)際上它使用其他方法來(lái)實(shí)現(xiàn)相同的目的.例如:
窗口消息可以發(fā)送并發(fā)布到共享調(diào)用thread’s desktop且處于相同或更低完整性級(jí)別的所有線程.當(dāng)線程調(diào)用PeekMessage或GetMessage時(shí),發(fā)送窗口消息會(huì)將其置于系統(tǒng)隊(duì)列中以調(diào)用窗口過(guò)程.發(fā)布消息會(huì)將其添加到線程的消息隊(duì)列中,該消息隊(duì)列的默認(rèn)配額為10,000條消息.具有消息隊(duì)列的線程應(yīng)具有消息循環(huán)以通過(guò)GetMessage和DispatchMessage處理隊(duì)列.僅控制臺(tái)進(jìn)程中的線程通常沒(méi)有消息隊(duì)列.但是,控制臺(tái)主機(jī)進(jìn)程conhost.exe顯然可以.單擊關(guān)閉按鈕時(shí),或者通過(guò)任務(wù)管理器或taskkill.exe終止控制臺(tái)的主進(jìn)程時(shí),WM_CLOSE消息將發(fā)布到控制臺(tái)窗口的線程的消息隊(duì)列中.控制臺(tái)輪流向其所有連接的進(jìn)程發(fā)送CTRL_CLOSE_EVENT.如果進(jìn)程處理該事件,則在強(qiáng)制終止之前,它會(huì)在5秒內(nèi)正常退出.
總結(jié)
以上是生活随笔為你收集整理的python兼职平台信号处理_如何在Windows机器上处理python中的信号的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 联想新一代拯救者 9000X 笔记本搭载
- 下一篇: 打造无界体验!魅族Flyme 10无界生