Linux 阻止向 Python init 发送信号
我似乎找到了解决办法;使用ctypes
通过libc(在本例中为uClibc)中的signal()
函数安装信号处理程序。下面是一个基于 python 的测试初始化。它在 TTY2 上打开一个 shell,我可以从中向 PID1 发送信号进行测试。它似乎在我用于测试的 KVM 中工作(我愿意与感兴趣的任何人共享 VM)
这是解决此问题的最佳方法吗?是否有一种“更好”的方法来安装没有信号模块的信号处理程序? (我根本不关心可移植性)
这是Python中的一个错误吗?
#!/usr/bin/python
import os
import sys
import time
from ctypes import *
def SigHUP():
print "Caught SIGHUP"
return 0
def SigCHLD():
print "Caught SIGCHLD"
return 0
SIGFUNC = CFUNCTYPE(c_int)
SigHUPFunc = SIGFUNC(SigHUP)
SigCHLDFunc = SIGFUNC(SigCHLD)
libc = cdll.LoadLibrary('libc.so.0')
libc.signal(1, SigHUPFunc) # 1 = SIGHUP
libc.signal(17, SigCHLDFunc) # 17 = SIGCHLD
print "Mounting Proc: %s" % libc.mount(None, "/proc", "proc", 0, None)
print "forking for ash"
cpid = os.fork()
if cpid == 0:
os.closerange(0, 4)
sys.stdin = open('/dev/tty2', 'r')
sys.stdout = open('/dev/tty2', 'w')
sys.stderr = open('/dev/tty2', 'w')
os.execv('/bin/ash', ('ash',))
print "ash started on tty2"
print "sleeping"
while True:
time.sleep(0.01)
This is a follow up to my other post Installing signal handler with Python. In short, Linux blocks all signals to PID 1 (including SIGKILL) unless Init has installed a signal handler for a particular signal; as to prevent kernel panic if someone were to send a termination signal to PID1. The issue I've been having, is it would seem that the signal
module in Python doesn't install signal handlers in a way the system recognises. My Python Init script was seemingly, completely ignoring all signals as I think they were being blocked.
I seem to have found a solution; using ctypes
to install the signal handlers with the signal()
function in libc (in this case uClibc). Below is a python based test init. It opens a shell on TTY2 from which I can send signals to PID1 for testing. It seems to work in the KVM im using for testing (I'm willing to share the VM with anyone interested)
Is this the best way around this issue? Is there a 'better' way to install the signal handlers without the signal module? (I am not at all concerned with portably)
Is this a bug in Python?
#!/usr/bin/python
import os
import sys
import time
from ctypes import *
def SigHUP():
print "Caught SIGHUP"
return 0
def SigCHLD():
print "Caught SIGCHLD"
return 0
SIGFUNC = CFUNCTYPE(c_int)
SigHUPFunc = SIGFUNC(SigHUP)
SigCHLDFunc = SIGFUNC(SigCHLD)
libc = cdll.LoadLibrary('libc.so.0')
libc.signal(1, SigHUPFunc) # 1 = SIGHUP
libc.signal(17, SigCHLDFunc) # 17 = SIGCHLD
print "Mounting Proc: %s" % libc.mount(None, "/proc", "proc", 0, None)
print "forking for ash"
cpid = os.fork()
if cpid == 0:
os.closerange(0, 4)
sys.stdin = open('/dev/tty2', 'r')
sys.stdout = open('/dev/tty2', 'w')
sys.stderr = open('/dev/tty2', 'w')
os.execv('/bin/ash', ('ash',))
print "ash started on tty2"
print "sleeping"
while True:
time.sleep(0.01)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我在 KVM 下进行了一些调试,发现当标准信号模块安装信号处理程序时,内核正在向 pid 1 传递信号。然而,当接收到信号时,“某些东西”会导致生成进程的克隆,而不是打印预期的输出。
这是当我将 HUP 发送到非工作 init.sig-mod 时的 strace 输出:
这会导致新进程正在运行(pid 23),它是 init.sig-mod 的克隆:
我没有没有时间深入探究原因,但这进一步缩小了范围。可能与 Python 的信号传递逻辑有关(它注册了一个 C 钩子,在调用时调用字节码函数)。 ctypes 技术绕过了这个。相关的Python源文件是 Python/pythonrun.c 和 Modules/signalmodule.c< /a>,如果你想仔细看看。
旧信息——我不确定这是否能解决您的问题,但可能会让您更接近。我
比较了信号处理程序的这些不同安装方式:
signal()
系统调用。ctypes 调用的
signal()
系统调用和 Upstart 的sigaction()
注册处理程序时,系统调用会设置
SA_RESTART
标志。环境该标志表示当进程正在接收信号时
在某些系统调用(读、写、等待、
nanosleep 等),信号处理程序完成后,系统调用应该是
自动重新启动。应用程序不会意识到这一点。
当Python的信号模块注册一个处理程序时,它会将SA_RESTART清零
通过调用 siginterrupt(signum, 1) 进行标记。这对系统说“当
信号处理程序完成后,系统调用被信号中断
将 errno 设置为 EINTR 并从系统调用返回”。这由开发人员自行决定
处理这个问题并决定是否重新启动系统调用。
您可以通过以下方式注册信号来设置 SA_RESTART 标志:
I did a bit of debugging under KVM and I found that the kernel is delivering signals to pid 1 when the signal handlers are installed by the standard signal module. However, when the signal is received "something" causes a clone of the process to be spawned, rather than printing the expected output.
Here is the strace output when I send HUP to the non-working init.sig-mod:
Which results in a new process running (pid 23) which is a clone of init.sig-mod:
I didn't have time to dig deeper into the cause, but this narrows things further. Probably something to do with Python's signal delivery logic (it registers a C hook which invokes your bytecode function when called). The ctypes technique bypasses this. The relevant Python source files are Python/pythonrun.c and Modules/signalmodule.c, in case you want to take a closer look.
Old Info -- I'm not sure this will solve your problem, but might get you closer. I
compared these different ways signal handlers are installed:
signal()
syscall directly.Both the ctypes-invoked
signal()
system call and Upstart'ssigaction()
syscalls set the
SA_RESTART
flag when the handler is registered. Settingthis flag indicates that when a signal is received while the process is
executing or blocking inside certain syscalls (read, write, wait,
nanosleep, etc), after the signal handler completes the syscall should be
automatically restarted. The application won't be aware of this.
When Python's signal module registers a handler, it zeros the SA_RESTART
flag by calling
siginterrupt(signum, 1)
. This says to the system "when asystem call is interrupted by a signal, after the signal handler completes
set errno to EINTR and return from the syscall". This leaves it up to the developer to
handle this and decide whether to restart the system call.
You can set the SA_RESTART flag by registering your signal this way:
该问题是使用旧 Linux 线程针对 uClibc 0.9.31 编译的 Python 的兼容性问题。针对 0.9.32-rc3 进行编译并使用 NPTL 解决了该问题。
The issue was a compatibility issue with Python compiled against uClibc 0.9.31 with old linux threads. Compiling against 0.9.32-rc3 and using NPTL has fixed the issue.