2014-11-22 4 views
5

Quando eseguo questo script Python con os.system su Ubuntu 12.04:In che modo Python blocca i segnali mentre os.system ("sleep ...")?

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

, e poi mando SIGABRT al processo di script più volte entro 5 secondi, ottengo il seguente output:

status=0 
HANDLER 

Ciò indica che l'invio del segnale è stato bloccato fino all'uscita da sleep 5 e quindi è stato recapitato solo un singolo segnale.

Tuttavia, con 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) 

, tutti i singoli segnali vengono consegnati in anticipo:

HANDLER 
HANDLER 
HANDLER 
cstatus=0 

Per distinguere la magia nella glibc dalla magia in Python, ho riscritto la sceneggiatura Python in C , quindi os.system diventa sistema (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); 
} 

segnali ricevuti consegnati presto:

HANDLER 
HANDLER 
HANDLER 
system=0x0 

Così ho dedotto che la magia è in Python 2.7, non in EGLIBC. Ma dov'è la magia? Sulla base dell'output di strace e guardando la funzione posix_system in Modules/posixmodule.c, non sono riuscito a capire come Python blocchi il segnale fino al ritorno di os.system.

relativo codice da 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); 
} 

Forse la magia è nel Py_BEGIN_ALLOW_THREADS?

Ho capito correttamente che è impossibile eseguire il mio gestore di segnale Python (impostato da signal.signal) fino al os.system resi?

È perché i gestori di segnale sono bloccati (a livello di Python, non a livello di sistema operativo) fino al Py_END_ALLOW_THREADS resi?

Ecco l'output strace del codice Python con os.system: http://pastebin.com/Wjn9KBye

risposta

4

Forse la magia è nel PY_BEGIN_ALLOW_THREADS?

La magia è principalmente in system stesso. system non è in grado di restituire EINTR, quindi l'implementazione della libc si impegna a riprendere il suo wait sul processo figlio. Ciò significa che nel tuo utilizzo di os.system, il controllo non ritorna mai a python finché non termina il sottostante system, e quindi la meccanica di gestione del segnale di Python non viene invocata tempestivamente.

subprocess.call, tuttavia, è essenzialmente facendo questo:

# 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 

Qui controllare fa ritorno pitone quando il sottostante wait viene interrotto. L'OSError/EINTR richiede a python di vedere se qualche segnale è scattato e, in tal caso, di richiamare il blocco di codice fornito dall'utente associato a quel segnale. (Ed è così che l'interprete adatta la semantica del segnale del sistema: imposta un flag e controllalo tra operazioni Python "atomiche", invocando il codice dell'utente, se appropriato.)