Linux:为什么将 /bin/bash 作为 Python3(或其他解释器)的子进程启动会使 parent 进程不受 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) 作为 child 子进程,导致终端的行为类似于parent 进程没有收到 ctrl-c 生成的 SIGINT?当子进程不是 shell 时,parent 肯定会中断,那么到底发生了什么?我看到 child 和 parent 在整个执行过程中都是同一个进程组的一部分。
child 是如何让 parent 免疫信号情报的?如果这是通过通道重新路由或其他一些花哨的技巧来完成的,请通过示例解释这是如何完成的。 我想修复我所看到的具有 CLI 的 EDA 工具之间的不一致行为;有些像 Bash 在 subproc 中做的那样,其他的将作为孤儿离开。
测试代码:
此代码将首先启动 bash 作为子进程,尽管被 Python3 包裹,但不能被 ctrl-c 中断;你将不得不 exit
到 return 回到 parent。然后代码将启动具有中断处理程序的 child.py;当你在 child 中 ctrl-c 时,你会看到 parent.py raise 和 child.py 同时中断,使 child.py 处于孤立状态,而其 SIGINT处理程序喷出到标准输出;退出状态为 parent; child 的状态丢失。
parent.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
#!/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
bash runs in a different process group than your python 处理并接管前台进程,使其接收信号而父进程不接收信号。
"setpgid()
和getpgrp()
调用被bash(1)
等程序用来创建进程组以实现shell作业控制."
您可以使用ps o pid,pgrp <python-pid> <subprocess-pid>
查看进程组。对于常规 sub-process,您会看到两个 python script and the sub-process, while some programs, like bash 的相同进程组,创建一个新的进程组。
bash 还安装了自己的信号处理程序。
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
您可以创建一个与 bash 功能相同的程序。示例:
// 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
脚本中,您将看到与 bash.
类似的行为
有人可以解释一下这里发生了什么吗?为什么从 Python3、Rake、Ruby 或 Make 启动 shell (bash, ksh) 作为 child 子进程,导致终端的行为类似于parent 进程没有收到 ctrl-c 生成的 SIGINT?当子进程不是 shell 时,parent 肯定会中断,那么到底发生了什么?我看到 child 和 parent 在整个执行过程中都是同一个进程组的一部分。
child 是如何让 parent 免疫信号情报的?如果这是通过通道重新路由或其他一些花哨的技巧来完成的,请通过示例解释这是如何完成的。 我想修复我所看到的具有 CLI 的 EDA 工具之间的不一致行为;有些像 Bash 在 subproc 中做的那样,其他的将作为孤儿离开。
测试代码:
此代码将首先启动 bash 作为子进程,尽管被 Python3 包裹,但不能被 ctrl-c 中断;你将不得不 exit
到 return 回到 parent。然后代码将启动具有中断处理程序的 child.py;当你在 child 中 ctrl-c 时,你会看到 parent.py raise 和 child.py 同时中断,使 child.py 处于孤立状态,而其 SIGINT处理程序喷出到标准输出;退出状态为 parent; child 的状态丢失。
parent.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
#!/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
bash runs in a different process group than your python 处理并接管前台进程,使其接收信号而父进程不接收信号。
"setpgid()
和getpgrp()
调用被bash(1)
等程序用来创建进程组以实现shell作业控制."
您可以使用ps o pid,pgrp <python-pid> <subprocess-pid>
查看进程组。对于常规 sub-process,您会看到两个 python script and the sub-process, while some programs, like bash 的相同进程组,创建一个新的进程组。
bash 还安装了自己的信号处理程序。
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
您可以创建一个与 bash 功能相同的程序。示例:
// 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
脚本中,您将看到与 bash.