2014-11-22 229 views
5

當我os.system在Ubuntu 12.04運行此Python腳本:Python在os.system(「sleep ...」)時如何阻塞信號?

import os, signal 
signal.signal(signal.SIGABRT, lambda *args: os.write(2, 'HANDLER\n')) 
print 'status=%r' % os.system('sleep 5') 

在5秒內,然後我送SIGABRT的腳本程序很多次,我得到下面的輸出:

status=0 
HANDLER 

這表明信號傳輸被阻止,直到sleep 5退出,然後只傳送一個信號。

然而,隨着subprocess.call

import os, signal, subprocess 
signal.signal(signal.SIGABRT, lambda *args: os.write(2, 'HANDLER\n')) 
print 'cstatus=%r' % subprocess.call('sleep 5', shell=True) 

,所有單個信號年初交付:

HANDLER 
HANDLER 
HANDLER 
cstatus=0 

要的glibc從Python中的魔法區別的魔力,我用C改寫了Python腳本,所以os.system變成了系統(3)

#include <errno.h> 
#include <signal.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
static void handler(int signum) { (void)signum; write(2, "HANDLER\n", 8); } 
int main(int argc, char **argv) { 
    int got; 
    struct sigaction sa; 
    (void)argc; (void)argv; 
    memset(&sa, 0, sizeof sa); 
    sa.sa_handler = handler; 
    if (0 != sigaction(SIGABRT, &sa, NULL)) return 3; 
    got = system("sleep 5"); 
    return !printf("system=0x%x\n", got); 
} 

信號得到了交付早:

HANDLER 
HANDLER 
HANDLER 
system=0x0 

所以我推斷,神奇的是在Python 2.7,而不是在eglibc。但魔法在哪裏?根據strace輸出並查看Modules/posixmodule.c中的posix_system函數,我找不到Python如何阻止信號直到os.system返回。從Modules/posixmodule.c

相關代碼:

static PyObject *posix_system(PyObject *self, PyObject *args) { 
    char *command; 
    long sts; 
    if (!PyArg_ParseTuple(args, "s:system", &command)) return NULL; 
    Py_BEGIN_ALLOW_THREADS 
    sts = system(command); 
    Py_END_ALLOW_THREADS 
    return PyInt_FromLong(sts); 
} 

也許神奇的是在Py_BEGIN_ALLOW_THREADS

我是否正確理解,我的Python信號處理程序(由signal.signal設置)不可能執行,直到os.system返回?

是否因爲在Py_END_ALLOW_THREADS返回之前信號處理程序被阻塞(在Python級別上,而不是OS級別上)?

這裏是Python代碼的strace的輸出與os.systemhttp://pastebin.com/Wjn9KBye

回答

4

也許神奇的是在PY_BEGIN_ALLOW_THREADS?

神奇的是大多在system本身。 system無法返回EINTR,因此libc實現很難繼續在子進程中使用wait。這意味着在使用os.system時,控制將永遠不會返回到python,直到底層system完成,從而不會及時調用python信號處理機制。

subprocess.call,但是,基本上是這樣做的:

# Compare subprocess.py:Popen/_eintr_retry_call(os.waitpid, self.pid, 0) 
while True: 
    try: 
    return os.waitpid(the_child_pid, 0) 
    except OSError, e: 
    if e.errno == errno.EINTR: # signal.signal() handler already invoked 
     continue 
    raise 

這裏控制確實返回蟒蛇在底層wait被中斷。 OSError/EINTR提示python查看是否有信號被觸發,如果是,則調用用戶提供的與該信號相關的代碼塊。 (這就是解釋器如何適應系統的信號語義:設置一個標誌,並在「原子」python操作之間檢查它,如果適當,調用用戶的代碼。)