簡體   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)?

有人可以在低級別解釋這里發生了什么嗎? 為什么從 Python3、Rake、Ruby 或 Make 啟動一個 shell(bash、ksh)作為子進程,會導致終端的行為就像父進程沒有收到由 ctrl-c 生成的 SIGINT 一樣? 當子進程不是 shell 時,父進程肯定會中斷,那么到底發生了什么? 我看到子進程和父進程在整個執行過程中都是同一個進程組的一部分。

孩子如何讓父母對 SIGINT 免疫? 如果這是通過通道重新路由或其他一些花哨的技巧來完成的,請通過示例解釋這是如何完成的。 我想修復我所看到的具有 CLI 的 EDA 工具之間的不一致行為; 有些像 Bash 在 subproc 中所做的那樣,其他人將成為孤兒。

測試代碼:這段代碼首先將bash作為子進程啟動,盡管被Python3包裹,但不能被ctrl-c中斷; 您將不得不exit以返回給父母。 然后代碼將啟動具有中斷處理程序的 child.py; 當您在 child 中按 ctrl-c 時,您會看到 parent.py raise 和 child.py 同時中斷,使 child.py 處於孤立狀態,而其 SIGINT 處理程序噴出到 stdout; 退出狀態為父級; 孩子的身份丟失了。

父母.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)

孩子.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)

執行:

$ ./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

在與您的進程不同的進程組中運行,並作為前台進程接管,這使它在父進程不接收信號的情況下接收信號。

" setpgid()getpgrp()調用被諸如bash(1)用來創建進程組以實現 shell 作業控制。 "

您可以使用ps o pid,pgrp <python-pid> <subprocess-pid>檢查進程組。 對於常規子進程,您將看到腳本和子進程的進程組相同,而某些程序(如 )會創建一個新進程組。

還安裝了自己的信號處理程序。

Linux 上的示例:

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

SigCgt字段很有趣。 這是一個位掩碼:

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

您可以創建一個與執行相同操作的程序。 例子:

// 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;
    }
}

編譯

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

如果您現在將此程序放在./parent.py腳本中,您將看到與類似的行為。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM