Linux 阻止向 Python init 发送信号

发布于 2024-11-04 04:07:12 字数 1441 浏览 10 评论 0原文

这是我的另一篇文章使用 Python 安装信号处理程序。简而言之,Linux 会阻止所有 PID 1 的信号(包括 SIGKILL),除非 Init 为特定信号安装了信号处理程序;如果有人向 PID1 发送终止信号,则可以防止内核发生恐慌。我一直遇到的问题是,Python 中的 signal 模块似乎没有以系统识别的方式安装信号处理程序。我的 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

柳絮泡泡 2024-11-11 04:07:12

我在 KVM 下进行了一些调试,发现当标准信号模块安装信号处理程序时,内核正在向 pid 1 传递信号。然而,当接收到信号时,“某些东西”会导致生成进程的克隆,而不是打印预期的输出。

这是当我将 HUP 发送到非工作 init.sig-mod 时的 strace 输出:

strace output

这会导致新进程正在运行(pid 23),它是 init.sig-mod 的克隆:

clone of init as pid 23

我没有没有时间深入探究原因,但这进一步缩小了范围。可能与 Python 的信号传递逻辑有关(它注册了一个 C 钩子,在调用时调用字节码函数)。 ctypes 技术绕过了这个。相关的Python源文件是 Python/pythonrun.c Modules/signalmodule.c< /a>,如果你想仔细看看。

旧信息——我不确定这是否能解决您的问题,但可能会让您更接近。我
比较了信号处理程序的这些不同安装方式:

  • 通过 Python 的信号模块安装处理程序。
  • Upstart 的信号处理程序。
  • 使用 ctypes 直接调用 signal() 系统调用。
  • C 中的一些快速测试。

ctypes 调用的 signal() 系统调用和 Upstart 的 sigaction()
注册处理程序时,系统调用会设置 SA_RESTART 标志。环境
该标志表示当进程正在接收信号时
在某些系统调用(读、写、等待、
nanosleep 等),信号处理程序完成后,系统调用应该是
自动重新启动。应用程序不会意识到这一点。

当Python的信号模块注册一个处理程序时,它会将SA_RESTART清零
通过调用 siginterrupt(signum, 1) 进行标记。这对系统说“当
信号处理程序完成后,系统调用被信号中断
将 errno 设置为 EINTR 并从系统调用返回”。这由开发人员自行决定
处理这个问题并决定是否重新启动系统调用。

您可以通过以下方式注册信号来设置 SA_RESTART 标志:

import signal
signal.signal(signal.SIGHUP, handler)
signal.siginterrupt(signal.SIGHUP, False)

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:

strace output

Which results in a new process running (pid 23) which is a clone of init.sig-mod:

clone of init as pid 23

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:

  • Installing a handler via Python's signal module.
  • Upstart's signal handlers.
  • Using ctypes to call the signal() syscall directly.
  • Some quick tests in C.

Both the ctypes-invoked signal() system call and Upstart's sigaction()
syscalls set the SA_RESTART flag when the handler is registered. Setting
this 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 a
system 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:

import signal
signal.signal(signal.SIGHUP, handler)
signal.siginterrupt(signal.SIGHUP, False)
番薯 2024-11-11 04:07:12

该问题是使用旧 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文