调用kill()终止子进程两次致Linux桌面崩溃问题求助
问题:fork子进程二次kill后系统崩溃至登录界面
使用fork创建子进程,通过kill(pid,SIGINT)终止后重启。第一次重启操作正常,但第二次执行kill操作后,Linux系统(或GNOME窗口管理器)会崩溃至登录界面。已通过gdb定位到崩溃发生在kill()调用处,但崩溃不会立即出现,而是在执行kill()后约5秒发生。
导致问题的代码
C++代码
#include <iostream> #include <signal.h> #include <thread> const int CHILDREN = 3; bool is_active; using namespace std; void signalHandler(int signum) { cout << "Interrupt signal (" << signum << ") received.\n"; is_active = false; } pid_t fork_process(int process_nr) { pid_t c_pid; c_pid = fork(); if (c_pid == -1) { perror("failure to fork new process"); return -1; } else if (c_pid == 0) { // child process signal(SIGINT, signalHandler); is_active = true; while (is_active) { } cout << "child " << process_nr << " EXIT" << endl; return -1; } return c_pid; } pid_t restart_process(int process_nr, pid_t pid) { cout << "restart process " << process_nr << endl; pid_t new_c_pid; kill(pid, SIGINT);//THIS IS WHERE THE CRASH HAPPENS sleep(3); new_c_pid = fork_process(process_nr); return new_c_pid; } int main(int argc, char const *argv[]) { int ret; pid_t c_pid[CHILDREN]; for (int i = 0; i < CHILDREN; i++) { ret = fork_process(i); if (ret == -1) return 0; c_pid[i] = ret; cout << "parent created child " << i << " with c_pid " << c_pid[i] << endl; } int to_restart = 1; this_thread::sleep_for(chrono::seconds(16)); // restart the selected process c_pid[to_restart] = restart_process(to_restart, c_pid[to_restart]); this_thread::sleep_for(chrono::seconds(16)); // restart the selected process c_pid[to_restart] = restart_process(to_restart, c_pid[to_restart]);//THIS IS WHERE THE CRASH HAPPENS this_thread::sleep_for(chrono::seconds(16)); return 0; }
Makefile
CC = g++ CFLAGS = -Wall -g output: main.o $(CC) $(CFLAGS) -o output main.o main.o: main.cpp $(CC) $(CFLAGS) -c main.cpp clean: rm *.o output
问题根源分析
- 全局变量未加volatile修饰:
is_active没有用volatile关键字声明,编译器会将其优化为寄存器变量,导致信号处理函数修改is_active后,子进程的while(is_active)循环无法读取到内存中的最新值,子进程会一直处于忙等状态,无法退出。多次重启后系统会堆积大量未退出的子进程,耗尽CPU资源导致崩溃。 - 信号处理函数调用非安全函数:
cout不属于POSIX定义的信号安全函数,在信号处理函数中调用会引发未定义行为,可能导致程序或系统异常。 - 未可靠等待子进程退出:使用
sleep(3)等待子进程退出不可靠,无法确保子进程在fork新进程前完全退出,且未调用waitpid回收子进程PID,可能导致僵尸进程或PID复用问题。 - 子进程终止方式错误:子进程执行完
fork_process后返回-1,会回到父进程的main函数继续执行后续代码,可能意外创建更多子进程。
修复方案
修改后的代码
#include <iostream> #include <signal.h> #include <thread> #include <unistd.h> #include <sys/wait.h> #include <cstring> const int CHILDREN = 3; volatile bool is_active; // 添加volatile修饰,禁止编译器优化 using namespace std; void signalHandler(int signum) { // 使用信号安全的write函数输出信息 char msg[64]; int len = snprintf(msg, sizeof(msg), "Interrupt signal (%d) received.\n", signum); write(STDERR_FILENO, msg, len); is_active = false; } pid_t fork_process(int process_nr) { pid_t c_pid; c_pid = fork(); if (c_pid == -1) { perror("failure to fork new process"); return -1; } else if (c_pid == 0) { // child process signal(SIGINT, signalHandler); is_active = true; while (is_active) { pause(); // 替换忙等为pause,减少CPU占用 } char exit_msg[64]; int len = snprintf(exit_msg, sizeof(exit_msg), "child %d EXIT\n", process_nr); write(STDERR_FILENO, exit_msg, len); _exit(0); // 子进程直接调用_exit终止,避免回到父进程main函数 } return c_pid; } pid_t restart_process(int process_nr, pid_t pid) { cout << "restart process " << process_nr << endl; pid_t new_c_pid; // 检查kill调用是否成功 if (kill(pid, SIGINT) == -1) { perror("kill failed"); return -1; } // 等待子进程退出,回收PID,避免僵尸进程 int status; if (waitpid(pid, &status, 0) == -1) { perror("waitpid failed"); return -1; } new_c_pid = fork_process(process_nr); return new_c_pid; } int main(int argc, char const *argv[]) { int ret; pid_t c_pid[CHILDREN]; for (int i = 0; i < CHILDREN; i++) { ret = fork_process(i); if (ret == -1) return 1; c_pid[i] = ret; cout << "parent created child " << i << " with c_pid " << c_pid[i] << endl; } int to_restart = 1; this_thread::sleep_for(chrono::seconds(16)); // restart the selected process c_pid[to_restart] = restart_process(to_restart, c_pid[to_restart]); if (c_pid[to_restart] == -1) { cerr << "Failed to restart process " << to_restart << endl; return 1; } this_thread::sleep_for(chrono::seconds(16)); // restart the selected process c_pid[to_restart] = restart_process(to_restart, c_pid[to_restart]); if (c_pid[to_restart] == -1) { cerr << "Failed to restart process " << to_restart << endl; return 1; } this_thread::sleep_for(chrono::seconds(16)); // 退出前回收所有子进程 for (int i = 0; i < CHILDREN; i++) { kill(c_pid[i], SIGINT); waitpid(c_pid[i], nullptr, 0); } return 0; }
关键修改点说明
- 添加volatile修饰is_active:确保编译器每次从内存读取变量值,信号处理函数修改后能立即被while循环感知,子进程可正常退出。
- 替换信号处理函数中的cout为write:
write是POSIX定义的信号安全函数,避免未定义行为。 - 子进程使用_exit(0)终止:直接终止子进程,避免回到父进程的main函数执行后续代码,防止意外创建更多进程。
- 用waitpid替代sleep等待子进程退出:可靠等待子进程退出并回收PID,避免僵尸进程和PID复用问题。
- 添加kill和waitpid的错误检查:及时处理调用失败的情况,避免误操作。
- 子进程用pause替代忙等:减少CPU占用,子进程在收到信号前会进入休眠状态。
内容的提问来源于stack exchange,提问作者robybert
相关产品推荐
相关产品推荐

