Linux - SIGSTOP的原因以及如何处理它?

时间:2016-09-13 15:29:46

标签: python linux bash shell

我有一个运行bash脚本的Python脚本。我需要能够杀死bash脚本,如果它似乎是无限的,它也必须在chroot jail中运行,因为脚本可能是危险的。我用psutil.Popen()运行它并让它运行两秒钟。如果它没有自然结束,我会向它及其所有可能的孩子发送SIGKILL

问题是,如果由于超时执行而导致一个脚本被杀死并运行另一个脚本,那么主(Python)脚本会收到SIGSTOP。在我的本地机器上,我做了一个非常愚蠢的解决方案:Python脚本在启动时将其PID写入文件,然后我运行另一个脚本,该脚本每秒发送SIGCONT到存储在文件中的PID。这有两个问题:它真的很愚蠢,但更糟糕的是它拒绝在服务器上运行 - SIGCONT只是在那里什么也没做。

顺序是:Python脚本运行响应jail的bash脚本,并且bash脚本运行可能危险和/或无限的脚本。这个脚本也可能有一些孩子。

代码的相关部分:

主要python脚本

    p = psutil.Popen(["bash", mode, script_path, self.TESTENV_ROOT])
    start = time.time()

    while True:
        if p.status() == psutil.STATUS_ZOMBIE:
            # process ended naturally
            duration = time.time() - start
            self.stdout.write("Script finished, execution time: {}s".format(duration))
            break

        if time.time() > start + run_limit:
            children = p.children(recursive=True)
            for child in children:
                child.kill()
            p.kill()
            duration = None
            self.stdout.write("Script exceeded maximum time ({}s) and was killed.".format(run_limit))
            break

        time.sleep(0.01)

    os.kill(os.getpid(), 17)  # SIGCHLD
    return duration

在chroot中运行脚本($ 1是要在chroot jail中运行的脚本,$ 2是jail路径)

#!/usr/bin/env bash

# copy script to chroot environment
cp "$1" "$2/prepare.sh"

# run script
chmod u+x "$2/prepare.sh"
echo './prepare.sh' | chroot "$2"
rm "$2/prepare.sh"

示例prepare.sh脚本

#!/bin/bash
echo asdf > file

我花了一些时间试图解决这个问题。我发现这个脚本(不使用chroot jail来运行bash脚本)运行得很完美:

import psutil
import os
import time

while True:
    if os.path.exists("infinite.sh"):
        p = psutil.Popen(["bash","infinite.sh"])
        start = time.time()

        while True:
            if p.status() == psutil.STATUS_ZOMBIE:
                # process ended naturally
                break

            if time.time() > start + 2:
                # process needs too much time and has to be killed
                children = p.children(recursive=True)
                for child in children:
                    child.kill()

                p.kill()
                break

        os.remove("infinite.sh")
        os.kill(os.getpid(), 17)

我的问题是:

  • 为什么我收到SIGSTOP?是由于chroot监狱?
  • 有没有更好的方法来解决我的问题,而不是运行“醒来”脚本?

感谢您的想法。

编辑:我发现在我杀死加时赛之后,当我运行第一个脚本的那一刻,我感到心悸。无论我使用os.system还是psutil.Popen

EDIT2:我在控制chroot jail的bash脚本中做了更多调查,关键行是echo './prepare.sh' | chroot "$2"。现在的问题是,它到底出了什么问题?

EDIT3: This可能是一个相关问题,如果有人帮助的话。

3 个答案:

答案 0 :(得分:2)

我很确定你在Mac OS而不是Linux上运行它。为什么?您正在向主python进程发送信号17,而不是使用:

import signal
signal.SIGCHLD

我相信你有一个信号17的处理程序,它应该重新生成被监禁的进程以响应这个信号。
但是Linux上的signal.SIGCHLD == 17和Mac OS上的signal.SIGCHLD == 20

现在你的问题的答案是:
Mac OS signal.SIGSTOP == 17 是的,您的流程通过SIGSTOP发送os.kill(os.getpid(), 17)给自己 Mac OS signal man page

修改
实际上它也可以在Linux上发生,因为Linux signal man page表示POSIX标准允许信号17SIGUSR2SIGCHLDSIGSTOP。因此,我强烈建议使用标准库的signal模块中的常量而不是硬编码的信号编号。

答案 1 :(得分:1)

好的,我终于找到了解决方案。问题实际上是在bash脚本中的chroot行:

echo './prepare.sh' | chroot "$2"

由于某种原因,这似乎不正确。在chroot中运行命令的正确方法是:

chroot chroot_path shell -c command

例如:

chroot '/home/chroot_jail' '/bin/sh' -c 'rm -rf /'

希望这有助于某人。

答案 2 :(得分:1)

这个帖子有点老了,但我相信我知道你问题的原因(有类似的问题):

来自here它说:

  

Linux支持下面列出的标准信号。 [...]首先是原始POSIX.1-1990标准中描述的信号。

  Signal     Value     Action   Comment
   ──────────────────────────────────────────────────────────────────────
   SIGHUP        1       Term    Hangup detected on controlling terminal
                                 or death of controlling process
   SIGINT        2       Term    Interrupt from keyboard
   SIGQUIT       3       Core    Quit from keyboard
   SIGILL        4       Core    Illegal Instruction
   SIGABRT       6       Core    Abort signal from abort(3)
   SIGFPE        8       Core    Floating-point exception
   SIGKILL       9       Term    Kill signal
   SIGSEGV      11       Core    Invalid memory reference
   SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                 readers; see pipe(7)
   SIGALRM      14       Term    Timer signal from alarm(2)
   SIGTERM      15       Term    Termination signal
   SIGUSR1   30,10,16    Term    User-defined signal 1
   SIGUSR2   31,12,17    Term    User-defined signal 2
   SIGCHLD   20,17,18    Ign     Child stopped or terminated
   SIGCONT   19,18,25    Cont    Continue if stopped
   SIGSTOP   17,19,23    Stop    Stop process
   SIGTSTP   18,20,24    Stop    Stop typed at terminal
   SIGTTIN   21,21,26    Stop    Terminal input for background process
   SIGTTOU   22,22,27    Stop    Terminal output for background process

它表明,当收到SIGTSTP,SIGTTIN或SIGTTOU信号时,进程(每个默认操作)也会停止。

page解释了:

  

[SIGTTIN和SIGTTOU]是发送到后台进程的信号,它们尝试从(SIGTTIN)读取或写入(SIGTTOU)它们的控制   终端(或tty)。   
...
  [...] [后台进程]更改终端设置确实会导致发送SIGTTOU

我使用sudo strace -tt -o [trace_output_file] -p [pid]查看哪个信号触发了我的进程停止。

如何解决问题?遗憾的是,我无法让你的缩减示例工作:你的infinite.sh看起来如何?为什么要在执行期间删除它? 我建议重定向stdin和stdout。你试过以下这个吗?

from subprocess import DEVNULL
p = psutil.Popen(["bash", mode, script_path, self.TESTENV_ROOT],
                 stdout=DEVNULL, stderr=DEVNULL, STDIN=DEVNULL)

您当然也可以使用subprocess.PIPE来处理Python代码中的输出或简单地重定向到文件。我不知道如何处理未经授权的修改tty设置的尝试。