【问题标题】:How is Python blocking signals while os.system("sleep...")?Python 如何在 os.system("sleep...") 时阻塞信号?
【发布时间】:2015-01-20 13:15:04
【问题描述】:

当我在 Ubuntu 12.04 上使用 os.system 运行这个 Python 脚本时:

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 变成了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 返回之前无法执行?

是因为信号处理程序被阻塞(在 Python 级别,而不是在操作系统级别)直到 Py_END_ALLOW_THREADS 返回?

这是带有os.system的Python代码的strace输出:http://pastebin.com/Wjn9KBye

【问题讨论】:

    标签: python linux python-2.7 signals


    【解决方案1】:

    也许魔法就在 PY_BEGIN_ALLOW_THREADS 中?

    魔法主要在system 本身。 system 不能返回 EINTR,因此 libc 实现会费力地恢复其在子进程上的 wait'ing。这意味着在您使用os.system 时,在底层system 完成之前,控制永远不会返回给python,因此不会及时调用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 被中断时,这里的控制是否返回给python。 OSError/EINTR 提示 python 查看是否有任何信号被触发,如果是,则调用与该信号关联的用户提供的代码块。 (这就是解释器如何适应系统的信号语义:设置一个标志,并在“原子”python 操作之间检查它,如果合适的话调用用户的代码。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-24
      • 2011-08-16
      • 1970-01-01
      • 2016-01-19
      • 1970-01-01
      • 2023-03-09
      相关资源
      最近更新 更多