You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用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

问题根源分析

  1. 全局变量未加volatile修饰:is_active没有用volatile关键字声明,编译器会将其优化为寄存器变量,导致信号处理函数修改is_active后,子进程的while(is_active)循环无法读取到内存中的最新值,子进程会一直处于忙等状态,无法退出。多次重启后系统会堆积大量未退出的子进程,耗尽CPU资源导致崩溃。
  2. 信号处理函数调用非安全函数:cout不属于POSIX定义的信号安全函数,在信号处理函数中调用会引发未定义行为,可能导致程序或系统异常。
  3. 未可靠等待子进程退出:使用sleep(3)等待子进程退出不可靠,无法确保子进程在fork新进程前完全退出,且未调用waitpid回收子进程PID,可能导致僵尸进程或PID复用问题。
  4. 子进程终止方式错误:子进程执行完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;
}

关键修改点说明

  1. 添加volatile修饰is_active:确保编译器每次从内存读取变量值,信号处理函数修改后能立即被while循环感知,子进程可正常退出。
  2. 替换信号处理函数中的cout为write:write是POSIX定义的信号安全函数,避免未定义行为。
  3. 子进程使用_exit(0)终止:直接终止子进程,避免回到父进程的main函数执行后续代码,防止意外创建更多进程。
  4. 用waitpid替代sleep等待子进程退出:可靠等待子进程退出并回收PID,避免僵尸进程和PID复用问题。
  5. 添加kill和waitpid的错误检查:及时处理调用失败的情况,避免误操作。
  6. 子进程用pause替代忙等:减少CPU占用,子进程在收到信号前会进入休眠状态。

内容的提问来源于stack exchange,提问作者robybert

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 23:39:55