Windowsで以下に貼り付けたコードを試していますが、シグナルを処理する代わりに、プロセスを強制終了しています。ただし、同じコードがUbuntuで機能しています。
import os, sys
import time
import signal
def func(signum, frame):
print 'You raised a SigInt! Signal handler called with signal', signum
signal.signal(signal.SIGINT, func)
while True:
print "Running...",os.getpid()
time.sleep(2)
os.kill(os.getpid(),signal.SIGINT)
Pythonの _os.kill
_ は、Windowsで2つの無関係なAPIをラップします。 GenerateConsoleCtrlEvent
パラメーターが_CTRL_C_EVENT
_または_CTRL_BREAK_EVENT
_の場合、 sig
を呼び出します。この場合、pid
パラメーターはプロセスグループIDです。後者の呼び出しが失敗し、他のすべてのsig
値については、 OpenProcess
を呼び出してから TerminateProcess
を呼び出します。この場合、pid
パラメーターはプロセスIDであり、sig
値は終了コードとして渡されます。 Windowsプロセスの終了は、SIGKILL
をPOSIXプロセスに送信することに似ています。通常、プロセスを正常に終了できないため、これは避ける必要があります。
_os.kill
_のドキュメントでは、誤って「kill()はさらにプロセスハンドルを強制終了する」と主張していることに注意してください。 OpenProcess
を呼び出して、プロセスハンドルを取得します。
SIGINT
およびSIGBREAK
の代わりにWinAPI _CTRL_C_EVENT
_および_CTRL_BREAK_EVENT
_を使用する決定は、クロスプラットフォームコードにとっては残念です。また、プロセスグループIDではないプロセスIDが渡されたときのGenerateConsoleCtrlEvent
の動作も定義されていません。プロセス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に必要な6つの信号を実装しています: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)
<Handlers.SIG_DFL: 0>
>>> c_raise(signal.SIGTERM)
foo
0
_
SIGTERM
は無意味です。
SIGABRT
関数はハンドラーが戻るとプロセスを強制終了するため、シグナルモジュールを使用してabort
で多くのことを行うことはできません。メインスレッドで呼び出される登録済みPython callableのフラグ)。 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
が追加されます。コンソールプロセスと非コンソールプロセスの両方がこれらのシグナルをraise
できますが、別のプロセスからそれらを受信できるのはコンソールプロセスだけです。 CRTは、 SetConsoleCtrlHandler
を介してコンソールコントロールイベントハンドラーを登録することでこれを実装します。
コンソールは、kernel32.dllまたはkernelbase.dll(文書化されていない)のCtrlRoutine
で実行を開始する接続プロセスで新しいスレッドを作成することにより、制御イベントを送信します。ハンドラーがメインスレッドで実行されない場合、同期の問題が発生する可能性があります(たとえば、REPLまたはinput
で)。また、制御オブジェクトは、同期オブジェクトの待機中または同期I/Oの完了を待機中にブロックされた場合、メインスレッドを中断しません。 SIGINT
によって割り込み可能である必要がある場合、メインスレッドでのブロックを回避するように注意する必要があります。 Python 3は、Windowsイベントオブジェクトを使用してこの問題を回避しようとします。このイベントオブジェクトは、SIGINT
によって割り込み可能な待機でも使用できます。
コンソールがプロセスに_CTRL_C_EVENT
_または_CTRL_BREAK_EVENT
_を送信すると、CRTのハンドラーは登録されたSIGINT
またはSIGBREAK
ハンドラーをそれぞれ呼び出します。 SIGBREAK
ハンドラーは、ウィンドウが閉じられたときにコンソールが送信する_CTRL_CLOSE_EVENT
_に対しても呼び出されます。 Pythonは、メインスレッドでSIGINT
をrasingすることで、デフォルトでKeyboardInterrupt
を処理します。ただし、SIGBREAK
は、最初はExitProcess(STATUS_CONTROL_C_EXIT)
を呼び出すデフォルトの_CTRL_BREAK_EVENT
_ハンドラーです。
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
_で作成したknownプロセスグループのいずれかにのみ送信する必要があることを明確にしたいと思います。
_CTRL_C_EVENT
_をグループ0以外に送信できることに依存しないでください。これは、新しいプロセスグループで最初に無効になっているためです。このイベントを新しいグループに送信することは不可能ではありませんが、ターゲットプロセスは最初にSetConsoleCtrlHandler(NULL, FALSE)
を呼び出して_CTRL_C_EVENT
_を有効にする必要があります。
_CTRL_BREAK_EVENT
_は無効にできないため、信頼できるすべてです。このイベントの送信は、Windows _CREATE_NEW_PROCESS_GROUP
_またはC SIGBREAK
ハンドラーがある場合、_CTRL_BREAK_EVENT
_で開始された子プロセスを簡単に強制終了する簡単な方法です。そうでない場合、デフォルトのハンドラーはプロセスを終了し、終了コードを_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)
<Handlers.SIG_DFL: 0>
>>> os.kill(0, signal.CTRL_BREAK_EVENT)
^BREAK
_
[*]
Windowsには、 非同期プロシージャコール (APC)があり、ターゲット関数をスレッドにキューイングします。特にカーネルモードAPCの役割を明確にするために、Windows APCの詳細な分析については、記事 Inside NT's Asynchronous Procedure Call を参照してください。 QueueUserAPC
を介して、ユーザーモードAPCをスレッドのキューに入れることができます。また、 ReadFileEx
および WriteFileEx
によってI/O完了ルーチンのキューに入れられます。
ユーザーモードのAPCは、スレッドがアラート可能な待機に入ると実行されます(例: WaitForSingleObjectEx
または SleepEx
with bAlertable
as TRUE
)。一方、カーネルモードAPCは、すぐにディスパッチされます(IRQLが_APC_LEVEL
_を下回る場合)。 I/Oマネージャーは通常、要求を発行したスレッドのコンテキストで非同期I/O要求パケットを完了するために使用します(データをIRPからユーザーモードバッファーにコピーするなど)。 APCがアラート可能およびアラート不可待機に与える影響を示す表については、 Waits and APCs を参照してください。カーネルモードAPCは待機を中断せず、待機ルーチンによって内部的に実行されることに注意してください。
WindowsはAPCを使用してPOSIXのような信号を実装できますが、実際には同じ目的で他の手段を使用します。例えば:
__try
_、___except
_、___finally
_、___leave
_ 、 RaiseException
、 AddVectoredExceptionHandler
。Kernel Dispatcher Objects (つまり Synchronization Objects )、例: SetEvent
、 SetWaitableTimer
。
ウィンドウメッセージ、例 SendMessage
(ウィンドウプロシージャへ)、 PostMessage
(ウィンドウプロシージャにディスパッチされるスレッドのメッセージキューへ)、 PostThreadMessage
(スレッドのメッセージキューへ)、 _WM_CLOSE
_ 、 _WM_TIMER
_ 。
ウィンドウメッセージは、呼び出し元の スレッドのdesktop を共有し、同じまたはより低い整合性レベルにあるすべてのスレッドに送信およびポストできます。ウィンドウメッセージを送信すると、システムキューに入れられ、スレッドが PeekMessage
または GetMessage
を呼び出すときにウィンドウプロシージャを呼び出します。メッセージを投稿すると、そのメッセージがスレッドのメッセージキューに追加されます。メッセージキューのデフォルトの割り当ては10,000メッセージです。メッセージキューのあるスレッドには、GetMessage
および DispatchMessage
を介してキューを処理するメッセージループが必要です。通常、コンソールのみのプロセスのスレッドにはメッセージキューがありません。ただし、コンソールのホストプロセスであるconhost.exeは明らかにそうです。閉じるボタンがクリックされるか、コンソールのプライマリプロセスがタスクマネージャーまたは taskkill.exe を介して強制終了されると、_WM_CLOSE
_メッセージがメッセージキューに投稿されますコンソールウィンドウのスレッドの。コンソールは、_CTRL_CLOSE_EVENT
_を接続されているすべてのプロセスに順番に送信します。プロセスがイベントを処理する場合、強制的に終了する前に正常に終了するまで5秒が与えられます。