Linux:为什么从Python3(或其他解释器)将/bin/bash作为子进程启动会使父进程不受SIGINT(ctrl-c)的影响?
有人能解释一下这里到底发生了什么吗?为什么从Python3、Rake、Ruby或Make作为子进程启动shell(bash、ksh)会导致终端表现得像父进程不接收由ctrl-c生成的SIGINT一样?当子进程不是shell时,父进程当然会中断,那么到底发生了什么?我看到在整个执行过程中,子进程组和父进程组都是同一进程组的一部分 孩子如何使父母对SIGINT免疫?如果这是通过通道重新路由或其他一些花哨的伎俩实现的,请通过一个示例说明如何实现。我想修复我所看到的具有CLI的EDA工具之间的不一致行为;有些操作与Bash在子程序中的操作相同,而另一些则作为孤儿 测试代码: 这段代码将首先启动bash作为一个子进程,尽管它被Python3包装,但不能被ctrl-c中断;您必须退出才能返回到父级。然后,代码将启动child.py,它有一个中断处理程序;当在子级中按住ctrl-c键时,您将看到parent.py raise和child.py同时中断,使child.py处于孤立状态,而其SIGINT处理程序向stdout发出指令;退出状态为父级;孩子的身份丢失了 parent.py:Linux:为什么从Python3(或其他解释器)将/bin/bash作为子进程启动会使父进程不受SIGINT(ctrl-c)的影响?,python,bash,subprocess,signals,posix,Python,Bash,Subprocess,Signals,Posix,有人能解释一下这里到底发生了什么吗?为什么从Python3、Rake、Ruby或Make作为子进程启动shell(bash、ksh)会导致终端表现得像父进程不接收由ctrl-c生成的SIGINT一样?当子进程不是shell时,父进程当然会中断,那么到底发生了什么?我看到在整个执行过程中,子进程组和父进程组都是同一进程组的一部分 孩子如何使父母对SIGINT免疫?如果这是通过通道重新路由或其他一些花哨的伎俩实现的,请通过一个示例说明如何实现。我想修复我所看到的具有CLI的EDA工具之间的不一致行为
#!/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
#!/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
$./parent.py
父项:pid=312412,pgid=312412:生成bash。。。
bash>^C
bash>退出0
出口
父项:pid=312412,pgid=312412:子项退出状态:0
父项:pid=312412,pgid=312412:繁殖。/child.py。。。
子项:pid=3121728;pgid=312412:键入某些内容:^C
子项:pid=3121728;pgid=312412:陷阱:SIGINT
子项:pid=3121728;pgid=312412:睡眠3秒
回溯(最近一次呼叫最后一次):
文件“/parent.py”,第18行,在
进程等待()
文件“/m/tools/lang/python/pyenv/versions/3.7.6/lib/python3.7/subprocess.py”,第1019行,正在等待
返回自我。\u等待(超时=超时)
文件“/m/tools/lang/python/pyenv/versions/3.7.6/lib/python3.7/subprocess.py”,第1653行,在等待中
(pid,sts)=self.\u try\u wait(0)
文件“/m/tools/lang/python/pyenv/versions/3.7.6/lib/python3.7/subprocess.py”,第1611行,在“try\u wait”中
(pid,sts)=os.waitpid(self.pid,wait_标志)
键盘中断
$CHILD:pid=3121728;pgid=312412:睡眠2秒
子项:pid=3121728;pgid=312412:睡眠1秒
子项:pid=3121728;pgid=312412:再见
回声$?
1.
在与您的进程不同的进程组中运行,并作为前台进程接管,使其接收信号,而父进程不接收信号
“诸如bash(1)
之类的程序使用setpgid()
和getpgrp()
调用来创建进程组,以实现shell作业控制。”
您可以使用ps o pid,pgrp
检查流程组。对于常规子进程,您将看到脚本和子进程的相同进程组,而一些程序(如)创建新的进程组
还安装自己的信号处理程序
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
如果现在将此程序放在
/parent.py
脚本中,您将看到类似的行为。非常感谢您的详细回答。起初我以为它们一定在不同的进程组中,因为我对信号的理解,但在查看python和bash进程时,我把ps命令搞砸了;我要求的是“gid”,而不是“pgid”。谢谢@user3286792很好,它很有帮助!不客气!
// 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