简体   繁体   English

Linux:为什么从 Python3(或其他解释器)启动 /bin/bash 作为子进程会使父进程不受 SIGINT(ctrl-c)的影响?

[英]Linux: why does launching /bin/bash as a subprocess from Python3 (or other interpreters) make the parent process immune to SIGINT (ctrl-c)?

Can someone explain what is going on here at a low level?有人可以在低级别解释这里发生了什么吗? Why does launching a shell (bash, ksh) as a child subprocess from Python3, Rake, Ruby or Make, cause the terminal to behave like the parent process does not receive the SIGINT generated by a ctrl-c?为什么从 Python3、Rake、Ruby 或 Make 启动一个 shell(bash、ksh)作为子进程,会导致终端的行为就像父进程没有收到由 ctrl-c 生成的 SIGINT 一样? The parent certainly does interrupt when the subprocess is not a shell, so what is really going on?当子进程不是 shell 时,父进程肯定会中断,那么到底发生了什么? I'm seeing that the child and parent are both part of the same process group throughout execution.我看到子进程和父进程在整个执行过程中都是同一个进程组的一部分。

How does the child make the parent immune to SIGINT?孩子如何让父母对 SIGINT 免疫? If this is accomplished with channel rerouting or some other fancy trickery, please explain how this is done with an example.如果这是通过通道重新路由或其他一些花哨的技巧来完成的,请通过示例解释这是如何完成的。 I'd like to fix what I'm seeing as inconsistent behavior between EDA tools that have CLI's;我想修复我所看到的具有 CLI 的 EDA 工具之间的不一致行为; some act as Bash does in a subproc, others will be left as orphans.有些像 Bash 在 subproc 中所做的那样,其他人将成为孤儿。

Test code : This code will first launch bash as a subprocess that can't be interrupted with ctrl-c, despite being wrapped by Python3;测试代码:这段代码首先将bash作为子进程启动,尽管被Python3包裹,但不能被ctrl-c中断; you will have to exit to return back to the parent.您将不得不exit以返回给父母。 The code will then launch child.py which has an interrupt handler;然后代码将启动具有中断处理程序的 child.py; when you ctrl-c inside the child, you will see that both parent.py raise and child.py interrupt at the same time, leaving child.py in orphaned state while its SIGINT handler spews to stdout;当您在 child 中按 ctrl-c 时,您会看到 parent.py raise 和 child.py 同时中断,使 child.py 处于孤立状态,而其 SIGINT 处理程序喷出到 stdout; the exit status is that of parent;退出状态为父级; the child's status is lost.孩子的身份丢失了。

parent.py:父母.py:

#!/usr/bin/env python3
import os, sys
import subprocess
import signal
import time

PID = os.getpid()
PGID = os.getpgid(PID)
PROC_LABEL = f'PARENT: pid={PID}, pgid={PGID}'

print(f"{PROC_LABEL}: spawning bash...")
proc = subprocess.Popen(['bash'])
ret = proc.wait()
print(f"{PROC_LABEL}: child exit status:", proc.returncode)

print(f"{PROC_LABEL}: spawning ./child.py...")
proc = subprocess.Popen(['./child.py'])
proc.wait()
print(f"{PROC_LABEL}: child exit status:", proc.returncode)
sys.exit(0)

child.py孩子.py

#!/usr/bin/env python3
import os, sys
import signal
import time

PID = os.getpid()
PGID = os.getpgid(PID)
PROC_LABEL = f"CHILD : pid={PID}; pgid={PGID}"

def intr_handler(sig, frame):
  print(f'\n{PROC_LABEL}: Trapped: {signal.Signals(sig).name}')
  for idx in range(3,0,-1):
    print(f"{PROC_LABEL}: sleeping for {idx} seconds")
    time.sleep(1)
  print(f"{PROC_LABEL}: bye")
  sys.exit(100)
signal.signal(signal.SIGINT, intr_handler)

ret = input(f"{PROC_LABEL}: type something: ")
print("input:", ret)
sys.exit(0)

execution:执行:

$ ./parent.py 
PARENT: pid=3121412, pgid=3121412: spawning bash...
bash> ^C
bash> exit 0
exit
PARENT: pid=3121412, pgid=3121412: child exit status: 0
PARENT: pid=3121412, pgid=3121412: spawning ./child.py...
CHILD : pid=3121728; pgid=3121412: type something: ^C
CHILD : pid=3121728; pgid=3121412: Trapped: SIGINT
CHILD : pid=3121728; pgid=3121412: sleeping for 3 seconds
Traceback (most recent call last):
  File "./parent.py", line 18, in <module>
    proc.wait()
  File "/m/tools/lang/python/pyenv/versions/3.7.6/lib/python3.7/subprocess.py", line 1019, in wait
    return self._wait(timeout=timeout)
  File "/m/tools/lang/python/pyenv/versions/3.7.6/lib/python3.7/subprocess.py", line 1653, in _wait
    (pid, sts) = self._try_wait(0)
  File "/m/tools/lang/python/pyenv/versions/3.7.6/lib/python3.7/subprocess.py", line 1611, in _try_wait
    (pid, sts) = os.waitpid(self.pid, wait_flags)
KeyboardInterrupt
$ CHILD : pid=3121728; pgid=3121412: sleeping for 2 seconds
CHILD : pid=3121728; pgid=3121412: sleeping for 1 seconds
CHILD : pid=3121728; pgid=3121412: bye
echo $?
1

runs in a different process group than your process and takes over as the foreground process which makes it receive the signals while the parent doesn't. 在与您的进程不同的进程组中运行,并作为前台进程接管,这使它在父进程不接收信号的情况下接收信号。

" The setpgid() and getpgrp() calls are used by programs such as bash(1) to create process groups in order to implement shell job control. " " setpgid()getpgrp()调用被诸如bash(1)用来创建进程组以实现 shell 作业控制。 "

You can check the process group with ps o pid,pgrp <python-pid> <subprocess-pid> .您可以使用ps o pid,pgrp <python-pid> <subprocess-pid>检查进程组。 For a regular sub-process, you'll see the same process group for both the script and the sub-process, while some programs, like , creates a new process group.对于常规子进程,您将看到脚本和子进程的进程组相同,而某些程序(如 )会创建一个新进程组。

also installs its own signal handlers. 还安装了自己的信号处理程序。

Example on Linux: Linux 上的示例:

root# grep ^Sig /proc/$SUBPROCESS_PID/status
SigPnd: 0000000000000000                     # pending
SigBlk: 0000000000000000                     # blocked
SigIgn: 0000000000380004                     # ignored
SigCgt: 000000004b817efb                     # caught

The SigCgt field is the interesting. SigCgt字段很有趣。 It's a bitmask:这是一个位掩码:

$ bc <<< "ibase=16; obase=2; 4B817EFB"
1001011100000010111111011111011
                             |
                            SIGINT

You can create a program the does the same thing as .您可以创建一个与执行相同操作的程序。 Example:例子:

// sig.cpp

#include <unistd.h>

#include <cerrno>
#include <csignal>
#include <cstring>
#include <iostream>
#include <stdexcept>

static int gsig = -1; // the latest caught signal

static void sighandler(int sig) {
    gsig = sig;
}

int check(int val) {
    if(val) std::runtime_error(std::strerror(errno));
    return val;
}

int main() {
    try {
        // catch a lot...
        for(auto sig : {SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM}) {
            check(std::signal(sig, sighandler)==SIG_ERR);
        }

        /* ignore terminal settings changes */
        check(signal(SIGTTOU, SIG_IGN)==SIG_ERR);

        // create new process group
        check(::setpgrp());

        // get the created process group
        pid_t pgrp = ::getpgrp();

        // set forground process group to the created process group
        check(::tcsetpgrp(::fileno(stdin), pgrp));

        std::cout << "-- waiting --" << std::endl;

        while(true) {
            ::pause();
            std::cout << "got signal " << gsig << std::endl;
        }
    } catch(const std::exception& ex) {
        std::cerr << "Exception: " << ex.what() << std::endl;
    }
}

Compile编译

$ g++ -o sig sig.cpp -std=c++11 -O3

If you now put this program in your ./parent.py script, you'll see a similar behavior as that of .如果您现在将此程序放在./parent.py脚本中,您将看到与类似的行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM