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

Linux下跨线程安全中断fork()/system()创建进程的方法咨询

问题分析与解决方案

当前示例代码的核心问题

  1. 脚本未处理SIGINT信号
    bash默认会在收到SIGINT时终止,但脚本中的sleep命令是前台进程,SIGINT会被sleep捕获并终止,而bash会继续执行后续循环。必须显式在脚本中添加信号处理逻辑,才能触发优雅退出与清理。

  2. 阻塞式waitpid导致主线程无法及时响应中断
    原mysystem中使用阻塞waitpid,主线程会卡在等待子进程结束的状态,即便子线程设置了done标志,也无法立即处理中断请求。

  3. 潜在PID复用风险
    虽然用互斥锁保护了forked_process变量,但子进程退出后到重置变量的窗口内,若线程发送信号,可能命中被系统复用的PID,引发意外问题。


1. 让脚本响应SIGINT并优雅退出

修改bash脚本,添加trap捕获SIGINT,执行清理逻辑后退出:

#!/bin/bash

# 定义清理函数
cleanup() {
    echo "Received SIGINT, starting cleanup..."
    # 在这里添加你的清理操作:删除临时升级包、恢复备份、重置设备状态等
    exit 1
}

# 绑定SIGINT信号到清理函数
trap cleanup SIGINT

for i in {1..20}
do
   echo "Sleeping in script PID = $$"
   sleep 1
done
echo "Upgrade completed successfully"

2. 安全中断子进程的改进实现

以下是修复竞态条件、支持主线程及时响应中断的C代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <sys/wait.h>
#include <stdatomic.h>
#include <errno.h>

pthread_mutex_t lock;
pid_t forked_process = 0;
atomic_bool need_exit = ATOMIC_VAR_INIT(false);

void *connection_monitor(void *args) {
    // 模拟客户端断开检测
    sleep(5);
    atomic_store(&need_exit, true);
    
    pthread_mutex_lock(&lock);
    if (forked_process != 0) {
        // 发送SIGINT给子进程组(确保脚本及其子进程都收到信号)
        killpg(forked_process, SIGINT);
    }
    pthread_mutex_unlock(&lock);
    return NULL;
}

int interruptible_system(const char *command) {
    pid_t pid;
    char *argp[] = {"sh", "-c", NULL, NULL};
    argp[2] = (char *)command;
    int status = -1;

    switch (pid = fork()) {
        case -1:
            return -1;
        case 0:
            // 子进程设置为新进程组组长,确保信号能覆盖所有子进程
            setpgid(0, 0);
            // 恢复SIGINT默认处理逻辑
            signal(SIGINT, SIG_DFL);
            execv("/bin/sh", argp);
            _exit(127);
    }

    pthread_mutex_lock(&lock);
    forked_process = pid;
    pthread_mutex_unlock(&lock);

    // 非阻塞等待子进程,同时轮询中断标志
    while (!atomic_load(&need_exit)) {
        if (waitpid(pid, &status, WNOHANG) == pid) {
            // 子进程正常退出,跳出循环
            break;
        }
        usleep(100000); // 100ms轮询一次,平衡响应速度与CPU占用
    }

    // 若触发中断,主动终止子进程并等待其清理退出
    if (atomic_load(&need_exit)) {
        pthread_mutex_lock(&lock);
        if (forked_process == pid) {
            killpg(pid, SIGINT);
            waitpid(pid, &status, 0);
        }
        pthread_mutex_unlock(&lock);
    }

    pthread_mutex_lock(&lock);
    forked_process = 0;
    pthread_mutex_unlock(&lock);

    return status;
}

int main(int argc, char const *argv[]) {
    pthread_t monitor_thread;
    int ret = 0;

    if (pthread_mutex_init(&lock, NULL)) {
        printf("Mutex init failed\n");
        return -1;
    }

    pthread_create(&monitor_thread, NULL, connection_monitor, NULL);
    ret = interruptible_system("./script.sh");

    printf("Server exiting, upgrade status: %d\n", ret);
    pthread_join(monitor_thread, NULL);
    pthread_mutex_destroy(&lock);
    return 0;
}

关键改进点:

  • 使用killpg替代kill:向整个进程组发送信号,确保脚本启动的所有子进程(如sleep)都能收到中断信号
  • 非阻塞waitpid轮询:主线程不再阻塞,能及时响应need_exit标志
  • 子进程设置进程组:避免信号只传递给bash进程,而遗漏其启动的子进程
  • 原子变量+互斥锁:确保子进程PID的读写安全,避免PID复用风险

3. 其他可行方向

  • 使用管道/事件FD替代信号:子线程检测到连接断开时,向管道写入数据,主线程通过select监听管道与子进程状态,这种方式比信号更稳定,避免信号竞态
  • 拆分升级步骤:将长时脚本拆分为多个短步骤,主线程在每个步骤执行前检查need_exit标志,无需中断正在运行的子进程
  • 直接用C实现升级逻辑:完全替代bash脚本,在代码中插入中断检查点,实现更精细的控制(适合对中断响应要求极高的场景)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 12:37:02