C语言如何向运行超时的子进程定时发送SIGABRT信号
C语言多子进程运行时长监控及超时终止实现
核心实现思路
- 父进程维护子进程信息列表,每个条目存储子进程PID、启动时间戳、允许的最大运行时长
- 使用
CLOCK_MONOTONIC单调时钟计算运行时长,不受系统时间修改的影响 - 父进程通过非阻塞的
waitpid轮询回收已退出的子进程,同时核对运行中进程的时长,超时则发送SIGABRT信号 - 及时回收退出的子进程资源,避免产生僵尸进程
完整可运行示例
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <time.h> #include <signal.h> #include <string.h> #define MAX_CHILD_NUM 3 #define DEFAULT_TIMEOUT 5 // 默认超时时间5秒 // 子进程信息结构体 typedef struct { pid_t pid; struct timespec start_time; int timeout_sec; int is_running; } ChildProcess; int main() { ChildProcess children[MAX_CHILD_NUM] = {0}; int child_count = 0; // 启动3个测试子进程,分别执行sleep 10、sleep 3、sleep 8 int sleep_times[] = {10, 3, 8}; for (int i = 0; i < MAX_CHILD_NUM; i++) { pid_t pid = fork(); if (pid < 0) { perror("fork failed"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程逻辑 char sleep_str[16]; snprintf(sleep_str, sizeof(sleep_str), "%d", sleep_times[i]); execlp("sleep", "sleep", sleep_str, NULL); perror("execlp failed"); exit(EXIT_FAILURE); } else { // 父进程记录子进程信息 children[child_count].pid = pid; clock_gettime(CLOCK_MONOTONIC, &children[child_count].start_time); children[child_count].timeout_sec = DEFAULT_TIMEOUT; children[child_count].is_running = 1; child_count++; printf("启动子进程 %d,预设休眠时长 %d 秒,超时阈值 %d 秒\n", pid, sleep_times[i], DEFAULT_TIMEOUT); } } // 父进程监控循环 int running_count = child_count; while (running_count > 0) { // 先回收所有已退出的子进程 int status; pid_t exited_pid; while ((exited_pid = waitpid(-1, &status, WNOHANG)) > 0) { for (int i = 0; i < child_count; i++) { if (children[i].pid == exited_pid && children[i].is_running) { children[i].is_running = 0; running_count--; if (WIFSIGNALED(status)) { printf("子进程 %d 被信号 %d 终止\n", exited_pid, WTERMSIG(status)); } else if (WIFEXITED(status)) { printf("子进程 %d 正常退出,退出码 %d\n", exited_pid, WEXITSTATUS(status)); } } } } // 检查所有运行中的子进程是否超时 struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); for (int i = 0; i < child_count; i++) { if (!children[i].is_running) continue; long run_sec = now.tv_sec - children[i].start_time.tv_sec; if (run_sec >= children[i].timeout_sec) { printf("子进程 %d 运行超时(已运行 %ld 秒),发送 SIGABRT 信号\n", children[i].pid, run_sec); kill(children[i].pid, SIGABRT); } } // 休眠100毫秒,降低CPU占用 struct timespec sleep_interval = {0, 100000000}; nanosleep(&sleep_interval, NULL); } printf("所有子进程处理完毕,父进程退出\n"); return 0; }
效果说明
编译运行上述代码后会输出类似日志:
启动子进程 12345,预设休眠时长 10 秒,超时阈值 5 秒 启动子进程 12346,预设休眠时长 3 秒,超时阈值 5 秒 启动子进程 12347,预设休眠时长 8 秒,超时阈值 5 秒 子进程 12346 正常退出,退出码 0 子进程 12345 运行超时(已运行 5 秒),发送 SIGABRT 信号 子进程 12347 运行超时(已运行 5 秒),发送 SIGABRT 信号 子进程 12345 被信号 6 终止 子进程 12347 被信号 6 终止 所有子进程处理完毕,父进程退出
其中信号6就是SIGABRT,符合预期效果。
优化建议
- 若子进程数量不固定,可以用链表代替固定数组存储子进程信息,支持动态增删
- 若需要更高精度的超时控制,可以纳秒为单位计算时间差,调整超时判断逻辑
- 若父进程需要同时处理其他IO任务,可以改用
timerfd+epoll的事件驱动模式,避免轮询带来的CPU开销
内容的提问来源于stack exchange,提问作者Mully5474
相关产品推荐
相关产品推荐

