linux信号(15)——SIGTERM:进程世界里的“温馨提示“
Linux SIGTERM信号深度解析:从理论到实践 摘要:本文系统解析Linux中的SIGTERM信号机制,将其比喻为"温柔的进程终止请求"。不同于强制终止信号,SIGTERM允许进程执行清理操作后优雅退出。文章首先介绍其POSIX标准定位及相关头文件,重点剖析kill()和sigaction()等关键函数的参数与返回值。通过三个典型应用案例(包括基础信号捕获、服务管理应用和
<摘要>
本文将围绕Linux中的SIGTERM信号展开深度解析。首先明确SIGTERM并非函数,而是一种用于请求进程正常终止的信号,如同生活中"温柔的下班提醒"。文中会介绍其基本概念与用途,说明它在POSIX标准中的定位及相关头文件;详解发送SIGTERM的函数(如kill())的参数、返回值;通过三个典型案例(基础信号捕获、服务管理工具应用、多进程资源清理)展示其实际应用,每个案例包含完整代码、流程图、编译及运行说明;最后用图示总结SIGTERM的工作流程,帮助读者全面理解这一重要信号的机制与实践价值。
<正文>
嘿,朋友!今天咱们要聊的不是一个普通的函数,而是Linux世界里一个特别重要的"信使"——SIGTERM。你可能会问,信号和函数有啥关系?别急,虽然SIGTERM本身不是函数,但它的发送、捕获和处理都离不开函数的帮忙。咱们就从这个"温柔的终止信号"说起,一点点揭开它的神秘面纱。
一、SIGTERM:进程世界里的"温馨提示"
想象一下,你在一家24小时便利店打工,到了凌晨5点,店长走过来说:"小王啊,今天差不多了,整理下货架,把收银机对账清楚,然后就可以下班啦。"你听到后,不会立刻扔下手里的活儿就走,而是会按部就班地完成收尾工作——这就是SIGTERM在进程世界里的作用。
在Linux系统中,进程就像一个个在工作的员工,有的负责处理网络请求,有的负责计算数据,有的负责管理文件。当我们需要让一个进程停止运行时,有两种常见方式:一种是"粗暴解雇"(比如SIGKILL信号),不管进程正在做什么,立刻强制终止;另一种就是SIGTERM这种"温馨提示",它会告诉进程:“你该结束工作了,请做好收尾,然后体面退出哦。”
SIGTERM的全称为"Signal Terminate"(终止信号),它的核心用途是请求进程正常终止。和那些强制终止的信号不同,SIGTERM是可以被进程"听到"并"回应"的——进程可以选择捕获这个信号,然后执行一系列清理操作(比如保存数据、释放网络连接、关闭打开的文件等),最后再自行退出。如果进程没有专门处理SIGTERM,系统会执行默认操作——终止进程,但这个过程依然比强制终止更"温和"。
常见的使用场景可太多了:
- 系统关机时,init进程(或systemd)会向所有非关键进程发送SIGTERM,给它们几秒钟时间清理,之后再发送SIGKILL强制结束没反应的进程;
- 用服务管理工具(如systemctl、service)停止服务时(比如
systemctl stop nginx),工具会向服务进程发送SIGTERM; - 开发者在调试程序时,想让程序优雅退出以保存中间状态,会手动发送SIGTERM;
- 容器管理工具(如Docker)停止容器时,默认也会先发送SIGTERM,等待一段时间后再强制终止。
二、SIGTERM的"出身"与相关工具
SIGTERM不是某个程序员拍脑袋发明的,它属于POSIX标准定义的信号之一(POSIX是一套操作系统接口标准,保证了不同类Unix系统的兼容性)。在Linux系统中,它的相关定义和处理函数都在signal.h头文件里,属于glibc库(GNU C库)的一部分。
要和SIGTERM打交道,咱们得认识几个关键的"工具人"函数:
kill():用于向指定进程发送信号(包括SIGTERM);signal()或sigaction():用于注册信号处理函数,让进程知道收到SIGTERM后该做什么;pthread_kill():向线程发送信号(但SIGTERM通常作用于整个进程)。
这些函数就像传递消息的快递员、接收消息的前台和处理消息的后台专员,共同构成了SIGTERM的工作流程。
三、与SIGTERM相关的函数:参数与返回值
既然SIGTERM的发送和处理都依赖函数,那咱们就得好好说说这些函数的参数和返回值。这里重点介绍最常用的kill()和sigaction()。
1. kill()函数:发送SIGTERM的"快递员"
kill()函数的作用是向进程(或进程组)发送指定信号,它的声明长这样:
#include <signal.h>
int kill(pid_t pid, int sig);
参数详解:
pid_t pid:目标进程的ID(进程号),它的取值很灵活:- 当
pid > 0时:向进程号为pid的单个进程发送信号; - 当
pid = 0时:向当前进程所在进程组的所有进程发送信号; - 当
pid = -1时:向系统中所有有权限发送的进程发送信号(除了init进程等特殊进程); - 当
pid < -1时:向进程组ID为-pid的所有进程发送信号。
- 当
int sig:要发送的信号,发送SIGTERM时,这里就填SIGTERM(它在signal.h中被定义为15,所以也可以填15,但用宏更易读)。
返回值含义:
- 成功发送信号时,返回
0; - 失败时,返回
-1,并设置errno来指示错误原因:ESRCH:找不到pid对应的进程(比如进程已经退出了);EPERM:没有权限向目标进程发送信号(比如普通用户不能向root进程发信号);EINVAL:sig是无效的信号编号(比如填了个负数)。
举个例子,如果你想给进程号为1234的程序发送SIGTERM,用kill(1234, SIGTERM)就行;如果在命令行里,直接用kill -15 1234(15是SIGTERM的编号)或者kill -TERM 1234。
2. sigaction()函数:处理SIGTERM的"前台专员"
sigaction()用于设置进程收到特定信号后的处理方式(比如注册一个函数,让进程收到SIGTERM时就执行它)。声明如下:
#include <signal.h>
int sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact);
参数详解:
int sig:要设置处理方式的信号,这里填SIGTERM;const struct sigaction *restrict act:指向struct sigaction结构体的指针,里面存放新的信号处理方式。这个结构体的关键成员是:void (*sa_handler)(int):信号处理函数的指针,当收到信号时,系统会调用这个函数;sigset_t sa_mask:在处理信号期间,需要阻塞的其他信号(比如处理SIGTERM时,不想被SIGINT打断);int sa_flags:处理信号的标志,比如SA_RESTART表示被信号打断的系统调用会自动重启。
struct sigaction *restrict oact:如果不为NULL,会保存之前的信号处理方式(相当于"备份")。
返回值含义:
- 成功设置时,返回
0; - 失败时,返回
-1,并设置errno(比如EINVAL表示sig是无效信号)。
sigaction()比旧的signal()函数更灵活、更可靠,所以推荐用它来处理SIGTERM。
四、实例与应用场景:让SIGTERM"动"起来
光说理论太枯燥,咱们来三个真实案例,看看SIGTERM在实际中是怎么用的。
案例一:基础版——捕获SIGTERM并优雅退出
应用场景:一个简单的后台程序,需要在退出前保存数据(比如记录程序运行了多久)。如果直接被强制终止,数据会丢失,所以用SIGTERM来触发保存操作。
完整代码(sigterm_demo1.c):
/**
* @brief 演示SIGTERM信号的捕获与处理
*
* 程序启动后会循环运行,每秒打印一次运行状态。
* 当收到SIGTERM信号时,会执行处理函数:保存运行时间到文件,然后退出。
*
* @in:
* - 无命令行参数
*
* @out:
* - 控制台:每秒打印运行状态
* - 文件"run_time.log":保存程序运行的总秒数
*
* 返回值说明:
* 程序正常退出时返回0,处理信号失败时返回1
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
// 全局变量:记录程序启动时间
time_t start_time;
// 标志:是否收到退出信号
volatile sig_atomic_t exit_flag = 0;
/**
* @brief SIGTERM信号处理函数
*
* 计算程序运行时间,保存到文件,然后设置退出标志。
*
* @param sig:收到的信号编号(此处应为SIGTERM)
*/
void sigterm_handler(int sig) {
// 计算运行时间(秒)
time_t current_time = time(NULL);
double run_seconds = difftime(current_time, start_time);
// 保存到文件
FILE *fp = fopen("run_time.log", "w");
if (fp) {
fprintf(fp, "程序运行了 %.0f 秒\n", run_seconds);
fclose(fp);
printf("\n收到SIGTERM信号,已保存运行时间到run_time.log\n");
} else {
printf("\n收到SIGTERM信号,但保存文件失败!\n");
}
// 设置退出标志(volatile sig_atomic_t确保多线程/信号安全)
exit_flag = 1;
}
int main() {
// 记录启动时间
start_time = time(NULL);
if (start_time == (time_t)-1) {
perror("获取时间失败");
return 1;
}
// 准备sigaction结构体,设置SIGTERM处理函数
struct sigaction sa;
// 清空结构体
sigemptyset(&sa.sa_mask);
// 设置处理函数
sa.sa_handler = sigterm_handler;
// 标志:不使用特殊处理
sa.sa_flags = 0;
// 注册SIGTERM信号处理函数
if (sigaction(SIGTERM, &sa, NULL) == -1) {
perror("注册SIGTERM处理函数失败");
return 1;
}
printf("程序启动(PID:%d),等待SIGTERM信号...(按Ctrl+C退出或用kill发送SIGTERM)\n", getpid());
// 主循环:每秒打印一次状态,直到收到退出信号
int count = 0;
while (!exit_flag) {
printf("运行中... 第 %d 秒\n", count);
count++;
sleep(1); // 休眠1秒
}
printf("程序优雅退出\n");
return 0;
}
程序流程图:
Makefile:
# 编译sigterm_demo1.c
sigterm_demo1: sigterm_demo1.c
gcc -o sigterm_demo1 sigterm_demo1.c -Wall
# 清理编译产物
clean:
rm -f sigterm_demo1 run_time.log
编译与运行步骤:
- 保存代码为
sigterm_demo1.c,Makefile为Makefile; - 在终端执行
make,编译生成可执行文件sigterm_demo1; - 运行程序:
./sigterm_demo1,此时程序会每秒打印运行状态,并显示自己的PID(比如12345); - 打开另一个终端,发送SIGTERM信号:
kill -TERM 12345(把12345换成实际的PID); - 观察第一个终端的输出,程序会提示保存了运行时间,然后退出;
- 查看生成的
run_time.log文件,确认运行时间被正确记录。
运行结果解读:
- 程序启动后,会持续输出"运行中… 第X秒",说明主循环在正常工作;
- 当收到SIGTERM后,终端会显示"收到SIGTERM信号,已保存运行时间到run_time.log",然后主循环退出,输出"程序优雅退出";
- 打开
run_time.log,会看到类似"程序运行了5秒"的内容,说明清理操作(保存数据)成功执行; - 如果没收到SIGTERM,程序会一直运行,直到被其他信号(如Ctrl+C发送的SIGINT)终止。
案例二:进阶版——服务管理中的SIGTERM(模拟Nginx停止)
应用场景:Web服务器(如Nginx)在收到SIGTERM时,会停止接收新请求,处理完当前所有请求后再退出。咱们用一个简化程序模拟这个过程。
完整代码(sigterm_demo2.c):
/**
* @brief 模拟Web服务器接收SIGTERM后的优雅关闭
*
* 程序模拟Web服务器:启动后监听"请求"(实际是定时生成模拟请求),
* 维护一个请求队列。收到SIGTERM后,停止接收新请求,处理完队列中
* 剩余请求后退出。
*
* @in:
* - 无命令行参数
*
* @out:
* - 控制台:打印服务器状态、请求处理情况、关闭过程
*
* 返回值说明:
* 服务器正常关闭返回0,初始化失败返回1
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
// 模拟请求结构体
typedef struct {
int id; // 请求ID
time_t create_time; // 创建时间
} Request;
// 服务器状态
typedef struct {
pthread_mutex_t mutex; // 互斥锁,保护队列操作
Request *queue; // 请求队列
int queue_size; // 队列当前大小
int max_queue; // 队列最大容量
int running; // 服务器是否运行(1:运行,0:准备关闭)
int next_req_id; // 下一个请求ID
} Server;
// 全局服务器实例
Server server;
// 标志:是否收到SIGTERM
volatile sig_atomic_t term_received = 0;
/**
* @brief 初始化服务器
*
* 初始化请求队列、互斥锁、服务器状态变量。
*
* @return 成功返回0,失败返回-1
*/
int server_init() {
server.max_queue = 10;
server.queue = malloc(sizeof(Request) * server.max_queue);
if (!server.queue) {
perror("分配队列内存失败");
return -1;
}
server.queue_size = 0;
server.running = 1;
server.next_req_id = 1;
// 初始化互斥锁
if (pthread_mutex_init(&server.mutex, NULL) != 0) {
perror("初始化互斥锁失败");
free(server.queue);
return -1;
}
return 0;
}
/**
* @brief 模拟接收新请求(添加到队列)
*
* 只有服务器处于运行状态(running=1)时,才会接收新请求。
*/
void accept_request() {
pthread_mutex_lock(&server.mutex);
if (server.running && server.queue_size < server.max_queue) {
Request req = {
.id = server.next_req_id++,
.create_time = time(NULL)
};
server.queue[server.queue_size++] = req;
printf("[%ld] 接收新请求 #%d,当前队列大小:%d\n",
time(NULL), req.id, server.queue_size);
}
pthread_mutex_unlock(&server.mutex);
}
/**
* @brief 处理队列中的请求
*
* 从队列头部取出请求,模拟处理过程(耗时1秒),然后移除请求。
*/
void process_requests() {
while (1) {
pthread_mutex_lock(&server.mutex);
if (server.queue_size == 0) {
// 队列空时,检查是否需要退出
if (!server.running) {
pthread_mutex_unlock(&server.mutex);
break;
}
pthread_mutex_unlock(&server.mutex);
usleep(100000); // 休眠0.1秒,减少CPU占用
continue;
}
// 取出队列头部的请求
Request req = server.queue[0];
// 队列元素前移
for (int i = 0; i < server.queue_size - 1; i++) {
server.queue[i] = server.queue[i + 1];
}
server.queue_size--;
pthread_mutex_unlock(&server.mutex);
// 模拟处理请求(耗时1秒)
printf("[%ld] 开始处理请求 #%d...\n", time(NULL), req.id);
sleep(1);
printf("[%ld] 请求 #%d 处理完成\n", time(NULL), req.id);
}
}
/**
* @brief SIGTERM信号处理函数
*
* 收到信号后,设置服务器状态为"停止接收新请求",并标记信号已收到。
*
* @param sig:信号编号(SIGTERM)
*/
void sigterm_handler(int sig) {
printf("\n[%ld] 收到SIGTERM信号,准备关闭服务器...\n", time(NULL));
pthread_mutex_lock(&server.mutex);
server.running = 0; // 停止接收新请求
pthread_mutex_unlock(&server.mutex);
term_received = 1;
}
/**
* @brief 清理服务器资源
*
* 释放队列内存,销毁互斥锁。
*/
void server_cleanup() {
free(server.queue);
pthread_mutex_destroy(&server.mutex);
printf("[%ld] 服务器资源清理完成\n", time(NULL));
}
int main() {
// 初始化服务器
if (server_init() != 0) {
return 1;
}
// 注册SIGTERM处理函数
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_handler = sigterm_handler;
sa.sa_flags = 0;
if (sigaction(SIGTERM, &sa, NULL) == -1) {
perror("注册SIGTERM处理函数失败");
server_cleanup();
return 1;
}
printf("模拟Web服务器启动(PID:%d)\n", getpid());
printf("服务器开始接收请求...(发送SIGTERM停止服务器)\n");
// 创建处理请求的线程
pthread_t process_thread;
if (pthread_create(&process_thread, NULL, (void *(*)(void *))process_requests, NULL) != 0) {
perror("创建处理线程失败");
server_cleanup();
return 1;
}
// 主线程:模拟定时接收新请求(每2秒一个)
while (!term_received) {
accept_request();
sleep(2);
}
// 等待处理线程处理完剩余请求
printf("[%ld] 等待剩余请求处理完成...\n", time(NULL));
pthread_join(process_thread, NULL);
// 清理资源并退出
server_cleanup();
printf("[%ld] 服务器已优雅关闭\n", time(NULL));
return 0;
}
程序流程图:
Makefile:
# 编译sigterm_demo2.c(需要链接pthread库)
sigterm_demo2: sigterm_demo2.c
gcc -o sigterm_demo2 sigterm_demo2.c -Wall -lpthread
# 清理编译产物
clean:
rm -f sigterm_demo2
编译与运行步骤:
- 保存代码为
sigterm_demo2.c,Makefile为Makefile; - 终端执行
make,生成sigterm_demo2(注意链接pthread库,否则编译失败); - 运行程序:
./sigterm_demo2,观察服务器启动并开始接收请求; - 等待3-5秒(让队列积累几个请求),在另一个终端发送SIGTERM:
kill -TERM 服务器PID; - 观察服务器输出,直到完全关闭。
运行结果解读:
- 程序启动后,每2秒会接收一个新请求(如"接收新请求 #1,当前队列大小:1"),处理线程会每秒处理一个请求(如"开始处理请求 #1…“→"请求 #1 处理完成”);
- 收到SIGTERM后,服务器会打印"准备关闭服务器…",并停止接收新请求(此时主线程的
accept_request不再添加新请求); - 处理线程会继续处理队列中剩余的请求(比如队列里还有#3、#4请求,会依次处理完);
- 所有请求处理完毕后,处理线程退出,主线程执行清理操作(释放内存、销毁锁),最后打印"服务器已优雅关闭";
- 这个过程完美模拟了Web服务器的优雅关闭:不丢弃已有请求,也不接收新请求,保证了服务的完整性。
案例三:高级版——多进程程序中的SIGTERM传递
应用场景:一个多进程程序(如父进程管理多个子进程工作),当父进程收到SIGTERM时,需要先向所有子进程发送SIGTERM,等待它们退出后,父进程再清理资源退出。
完整代码(sigterm_demo3.c):
/**
* @brief 多进程程序中SIGTERM的传递与处理
*
* 父进程创建3个子进程,每个子进程模拟执行任务。当父进程收到SIGTERM时,
* 会向所有子进程发送SIGTERM,等待子进程退出后,父进程清理资源并退出。
* 子进程收到SIGTERM后,会执行自己的清理操作(如保存任务进度)。
*
* @in:
* - 无命令行参数
*
* @out:
* - 控制台:打印父进程、子进程的状态、信号处理情况
* - 子进程会生成"child_X_progress.log"(X为子进程ID)
*
* 返回值说明:
* 父进程正常退出返回0,初始化失败返回1;子进程正常退出返回0
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <time.h>
#include <string.h>
#define NUM_CHILDREN 3 // 子进程数量
// 父进程中:子进程PID数组
pid_t child_pids[NUM_CHILDREN];
// 父进程中:已退出的子进程数量
int exited_children = 0;
// 父进程中:是否收到SIGTERM
volatile sig_atomic_t parent_term = 0;
/**
* @brief 子进程的SIGTERM处理函数
*
* 子进程收到SIGTERM后,保存当前任务进度到日志文件,然后退出。
*
* @param sig:信号编号(SIGTERM)
*/
void child_sigterm_handler(int sig) {
char log_file[32];
sprintf(log_file, "child_%d_progress.log", getpid());
FILE *fp = fopen(log_file, "w");
if (fp) {
time_t now = time(NULL);
fprintf(fp, "子进程 %d 在 %s 收到终止信号,任务进度已保存\n",
getpid(), ctime(&now));
fclose(fp);
printf("[子进程 %d] 收到SIGTERM,已保存进度到 %s\n", getpid(), log_file);
} else {
printf("[子进程 %d] 收到SIGTERM,但保存进度失败!\n", getpid());
}
exit(0); // 子进程退出
}
/**
* @brief 子进程的任务函数
*
* 模拟执行任务:每3秒打印一次进度,持续运行直到收到SIGTERM。
*
* @param id:子进程编号(0~NUM_CHILDREN-1)
*/
void child_task(int id) {
// 子进程注册自己的SIGTERM处理函数
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_handler = child_sigterm_handler;
sa.sa_flags = 0;
if (sigaction(SIGTERM, &sa, NULL) == -1) {
perror("[子进程] 注册SIGTERM处理函数失败");
exit(1);
}
// 模拟任务执行
int progress = 0;
while (1) {
printf("[子进程 %d(编号%d)] 任务进度:%d%%\n", getpid(), id, progress);
progress += 10;
if (progress > 100) progress = 0;
sleep(3); // 每3秒更新一次进度
}
}
/**
* @brief 父进程的SIGCHLD处理函数
*
* 当子进程退出时,父进程会收到SIGCHLD信号,此函数负责回收子进程资源,
* 并记录已退出的子进程数量。
*
* @param sig:信号编号(SIGCHLD)
*/
void parent_sigchld_handler(int sig) {
pid_t pid;
int status;
// 循环回收所有已退出的子进程(避免信号丢失)
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
exited_children++;
printf("[父进程 %d] 子进程 %d 已退出,已退出数量:%d/%d\n",
getpid(), pid, exited_children, NUM_CHILDREN);
}
}
/**
* @brief 父进程的SIGTERM处理函数
*
* 父进程收到SIGTERM后,向所有子进程发送SIGTERM,然后等待它们退出。
*
* @param sig:信号编号(SIGTERM)
*/
void parent_sigterm_handler(int sig) {
printf("\n[父进程 %d] 收到SIGTERM,开始通知所有子进程...\n", getpid());
// 向每个子进程发送SIGTERM
for (int i = 0; i < NUM_CHILDREN; i++) {
if (child_pids[i] > 0) { // 确保子进程存在
if (kill(child_pids[i], SIGTERM) == 0) {
printf("[父进程 %d] 已向子进程 %d 发送SIGTERM\n", getpid(), child_pids[i]);
} else {
perror("[父进程] 发送SIGTERM失败");
}
}
}
parent_term = 1;
}
int main() {
pid_t parent_pid = getpid();
printf("[父进程 %d] 启动,准备创建 %d 个子进程...\n", parent_pid, NUM_CHILDREN);
// 父进程注册SIGTERM和SIGCHLD处理函数
struct sigaction sa_term, sa_chld;
// 处理SIGTERM
sigemptyset(&sa_term.sa_mask);
sa_term.sa_handler = parent_sigterm_handler;
sa_term.sa_flags = 0;
if (sigaction(SIGTERM, &sa_term, NULL) == -1) {
perror("[父进程] 注册SIGTERM处理函数失败");
return 1;
}
// 处理SIGCHLD(子进程退出时收到)
sigemptyset(&sa_chld.sa_mask);
sa_chld.sa_handler = parent_sigchld_handler;
sa_chld.sa_flags = 0;
if (sigaction(SIGCHLD, &sa_chld, NULL) == -1) {
perror("[父进程] 注册SIGCHLD处理函数失败");
return 1;
}
// 创建子进程
for (int i = 0; i < NUM_CHILDREN; i++) {
pid_t pid = fork();
if (pid == -1) {
perror("[父进程] 创建子进程失败");
// 清理已创建的子进程
for (int j = 0; j < i; j++) {
kill(child_pids[j], SIGKILL);
waitpid(child_pids[j], NULL, 0);
}
return 1;
} else if (pid == 0) {
// 子进程:执行任务
child_task(i);
// 子进程任务函数不会返回,这里只是保险
exit(0);
} else {
// 父进程:记录子进程PID
child_pids[i] = pid;
printf("[父进程 %d] 已创建子进程 %d(编号%d)\n", parent_pid, pid, i);
}
}
// 父进程等待:直到所有子进程退出
printf("[父进程 %d] 等待子进程运行,发送SIGTERM给我来终止所有进程...\n", parent_pid);
while (!parent_term || exited_children < NUM_CHILDREN) {
sleep(1);
}
// 所有子进程退出后,父进程清理并退出
printf("[父进程 %d] 所有子进程已退出,开始清理资源...\n", parent_pid);
printf("[父进程 %d] 清理完成,退出\n", parent_pid);
return 0;
}
程序流程图:
Makefile:
# 编译sigterm_demo3.c
sigterm_demo3: sigterm_demo3.c
gcc -o sigterm_demo3 sigterm_demo3.c -Wall
# 清理编译产物和日志文件
clean:
rm -f sigterm_demo3 child_*.log
编译与运行步骤:
- 保存代码为
sigterm_demo3.c,Makefile为Makefile; - 终端执行
make,生成sigterm_demo3; - 运行程序:
./sigterm_demo3,观察父进程创建3个子进程,子进程开始打印任务进度; - 等待5-10秒,在另一个终端发送SIGTERM给父进程:
kill -TERM 父进程PID; - 观察程序输出,直到父进程完全退出;
- 查看生成的
child_X_progress.log文件(X为子进程PID)。
运行结果解读:
- 父进程启动后,会创建3个子进程,每个子进程有自己的编号,每3秒打印一次任务进度(0%→10%→…→100%→0%循环);
- 当父进程收到SIGTERM后,会打印"开始通知所有子进程…",并向每个子进程发送SIGTERM;
- 子进程收到SIGTERM后,会各自生成日志文件(如
child_12345_progress.log),记录收到信号的时间和进度,然后退出; - 每个子进程退出时,父进程会收到SIGCHLD信号,触发
parent_sigchld_handler,回收子进程资源并计数; - 当所有3个子进程都退出后,父进程会打印"所有子进程已退出,开始清理资源…",最后退出;
- 这个案例展示了SIGTERM在多进程协作中的重要性:通过信号传递,实现了"自上而下"的优雅终止,确保每个进程都能完成自己的清理工作。
五、SIGTERM的"朋友圈":与其他信号的区别
聊了这么多SIGTERM,咱们再看看它的"亲戚们",理解它们的区别能更好地掌握SIGTERM的特性:
-
SIGTERM vs SIGKILL(信号9):
- SIGTERM是"请求终止",可以被捕获、忽略或处理;
- SIGKILL是"强制终止",不能被捕获、忽略,进程会立即退出,没有清理机会;
- 场景:如果进程收到SIGTERM后无响应(比如卡死),可以用SIGKILL强制杀死。
-
SIGTERM vs SIGINT(信号2,Ctrl+C触发):
- 两者默认行为都是终止进程,但语义不同;
- SIGINT通常是"用户中断"(比如用户按Ctrl+C);
- SIGTERM通常是"系统/程序请求终止"(比如服务管理工具);
- 很多程序会对两者做相同处理,但也可以分开处理(比如SIGINT保存临时数据,SIGTERM做完整清理)。
-
SIGTERM vs SIGQUIT(信号3,Ctrl+\触发):
- SIGQUIT默认会终止进程并生成核心转储文件(core dump),用于调试;
- SIGTERM不会生成核心转储,更侧重"正常退出"。
记住:SIGTERM的核心是"优雅",给进程一个体面退出的机会。
六、总结:SIGTERM的一生
让我们用一张图总结SIGTERM从发送到处理的完整流程:
从图中可以看到,SIGTERM的旅程始于发送者,经过内核传递,最终由进程决定如何响应——是执行自定义清理,还是接受默认终止。这个过程体现了Unix/Linux系统设计的哲学:给程序足够的自主权,让它们能以最合理的方式结束自己的工作。
七、写在最后
SIGTERM虽然只是一个信号,但它背后反映的是系统设计中"优雅"与"秩序"的追求。理解并正确使用SIGTERM,能让你的程序更健壮、更友好——想象一下,如果每个程序都能在退出前认真清理自己的"烂摊子",系统会稳定得多。
希望通过今天的讲解,你不仅知道了SIGTERM是什么,更能在实际开发中用好它。下次当你需要停止一个程序时,不妨先试试发送SIGTERM,给它一个体面告别的机会——毕竟,谁不想优雅地结束呢?
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)