使用sudo时,父进程退出为何会终止已分离的子进程?
一、为什么sudo会导致子进程随父进程终止?
主要有两个核心原因:
1. 代码中的进程组设置错误
你代码里的if (auto pid = fork() == 0)存在运算符优先级问题:==的优先级高于=,所以pid实际接收的是fork() == 0的布尔结果(0或1),而非子进程的真实PID。这导致父进程中调用setpgid(pid, 0)完全无效——你传入的不是子进程PID,子进程根本没有成功脱离原进程组。
2. sudo的PTY机制与SIGHUP信号
sudo默认启用use_pty配置(大部分发行版默认开启),它会为运行的程序创建一个独立的伪终端(PTY)。当你的父进程退出后:
- sudo会关闭这个PTY,子进程的标准输入/输出/错误流都关联到已关闭的PTY;
- 终端会向原前台进程组发送
SIGHUP信号(默认行为是终止进程); - 由于子进程没成功脱离原进程组(代码bug导致),会收到这个SIGHUP信号,直接被终止。
而用su切换root后运行程序时,程序直接在原终端执行,没有额外的PTY隔离,父进程退出后,终端不会向非前台进程组发送SIGHUP,所以子进程能继续运行。
二、如何追踪分离的子进程?
给你几个实用的调试方法:
记录子进程PID到文件:在子进程代码开头添加写PID的逻辑,比如:
std::ofstream pid_file("/tmp/child_pid.txt"); pid_file << getpid() << std::endl;然后用
ps -ef | grep $(cat /tmp/child_pid.txt)查看进程状态,或者用strace -p $(cat /tmp/child_pid.txt)追踪系统调用。忽略SIGHUP信号验证:在子进程中添加
signal(SIGHUP, SIG_IGN);,如果子进程能正常运行,就说明是SIGHUP导致的终止,进一步验证上述原因。用gdb实时附加:在子进程开头加
sleep(10),给你留足够时间用gdb -p <子进程PID>附加进去,断点调试子进程的执行流程。查看进程树:用
pstree -p查看整个系统的进程树,找到你的子进程及其归属的进程组,确认是否成功脱离原组。
修复代码的关键点
先修正fork的赋值逻辑,确保正确获取子进程PID:
#include <iostream> #include <fstream> #include <unistd.h> #include <signal.h> int main() { std::cout << "start: " << getuid() << std::endl; pid_t pid = fork(); // 先赋值,再判断 if (pid == 0) { setpgid(0, 0); // 子进程将自己设为新进程组的组长 signal(SIGHUP, SIG_IGN); // 可选:忽略SIGHUP防止被终端信号杀死 std::cout << "fork has detached" << std::endl; sleep(1); std::ofstream test("/root/test.txt"); std::cout << "fork: " << getuid() << " " << test.is_open() << std::endl; } else if (pid > 0) { // 父进程短暂等待,避免子进程还没完成setpgid就退出的竞态 usleep(100); std::cout << "parent: " << getuid() << std::endl; } return 0; }
内容的提问来源于stack exchange,提问作者Maxime Beluguet

