进程的信号处理函数如何定义?
美国、香港服务器
进程的信号处理函数如何定义?
08-19 来源:
在 Linux 系统中,进程的信号处理函数通过系统调用注册,用于自定义进程收到特定信号时的行为(替代内核默认处理)。定义信号处理函数的核心是函数原型符合标准,并通过 signal() 或 sigaction() 等系统调用将函数与目标信号绑定。以下是详细方法:
一、信号处理函数的原型(必须遵守)
信号处理函数的原型有严格规定,必须为:
c
运行
void handler(int signum);
参数 signum:接收的信号编号(如 SIGINT 对应 2,SIGUSR1 对应 10),用于区分不同信号。
返回值:void(无返回值)。
示例:定义一个处理 SIGINT(Ctrl+C 触发)的函数:
c
运行
#include
#include
// 信号处理函数:收到信号时打印信号编号
void sigint_handler(int signum) {
printf("收到信号 %d(SIGINT),不退出程序!\n", signum);
}
二、注册信号处理函数(绑定信号与函数)
定义函数后,需通过系统调用将其与目标信号绑定,常用方法有两种:
方法 1:使用 signal() 函数(简单但功能有限)
signal() 是早期的信号注册函数,接口简单,适合基础场景。
函数原型:
c
运行
#include
void (*signal(int signum, void (*handler)(int)))(int);
参数 1:signum 为目标信号(如 SIGINT、SIGUSR1)。
参数 2:handler 为信号处理函数的指针,或特殊值:
SIG_IGN:忽略该信号。
SIG_DFL:恢复默认处理行为。
返回值:成功返回之前的处理函数指针,失败返回 SIG_ERR。
示例:将 sigint_handler 绑定到 SIGINT 信号:
c
运行
int main() {
// 注册SIGINT信号的处理函数
if (signal(SIGINT, sigint_handler) == SIG_ERR) {
perror("signal注册失败");
return 1;
}
// 循环等待信号(避免程序退出)
while (1) {
printf("等待信号...(按Ctrl+C测试)\n");
sleep(1);
}
return 0;
}
特点:
优点:用法简单,适合快速注册信号处理。
缺点:行为在不同系统中可能不一致(如某些系统会自动重置信号处理为默认值),不支持传递额外信号信息。
方法 2:使用 sigaction() 函数(推荐,功能全面)
sigaction() 是 POSIX 标准的信号注册函数,功能更强大,支持设置信号处理的细节(如是否阻塞其他信号、是否传递信号上下文)。
函数原型:
c
运行
#include
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数 1:signum 为目标信号。
参数 2:act 是结构体指针,定义新的信号处理方式(可为 NULL 表示不修改)。
参数 3:oldact 用于保存旧的信号处理方式(可为 NULL 表示不获取)。
返回值:成功返回 0,失败返回 - 1。
核心结构体 struct sigaction:
c
运行
struct sigaction {
void (*sa_handler)(int); // 信号处理函数(与signal()的handler相同)
sigset_t sa_mask; // 处理信号时临时阻塞的信号集
int sa_flags; // 信号处理的标志位(如SA_RESTART重启被中断的系统调用)
void (*sa_sigaction)(int, siginfo_t *, void *); // 高级处理函数(需配合SA_SIGINFO标志)
};
示例:用 sigaction() 注册 SIGINT 处理函数,设置额外参数:
c
运行
#include
#include
#include
void sigint_handler(int signum) {
printf("收到信号 %d(SIGINT),不退出程序!\n", signum);
}
int main() {
struct sigaction act;
// 初始化sigaction结构体
act.sa_handler = sigint_handler; // 指定处理函数
sigemptyset(&act.sa_mask); // 清空阻塞信号集(处理时不额外阻塞其他信号)
act.sa_flags = 0; // 无特殊标志
// 注册SIGINT信号
if (sigaction(SIGINT, &act, NULL) == -1) {
perror("sigaction注册失败");
return 1;
}
// 循环等待信号
while (1) {
printf("等待信号...(按Ctrl+C测试)\n");
sleep(1);
}
return 0;
}
特点:
优点:行为标准统一,支持设置阻塞信号集、重启系统调用等高级功能,推荐优先使用。
支持 sa_sigaction 高级处理函数(需设置 sa_flags = SA_SIGINFO),可获取信号发送者 PID、信号值等详细信息。
三、信号处理函数的注意事项(关键)
函数内操作限制
信号处理函数运行在 “信号上下文” 中,应避免调用非异步安全(non-async-signal-safe) 的函数(如 printf、malloc、fopen 等),可能导致死锁或数据损坏。
安全函数列表:write、_exit、sigprocmask 等(详见 man 7 signal-safety)。
示例:用 write 替代 printf 更安全:
c
运行
void sigint_handler(int signum) {
const char* msg = "收到SIGINT信号\n";
write(STDOUT_FILENO, msg, strlen(msg)); // write是异步安全的
}
信号重入问题
若同一信号在处理过程中再次发生(未设置 SA_RESETHAND 标志),可能导致处理函数重入,需确保函数是 “可重入的”(如不修改全局变量,或使用原子操作)。
信号掩码与阻塞
通过 sa_mask 可设置处理信号时临时阻塞的信号(如处理 SIGINT 时阻塞 SIGQUIT),避免信号嵌套导致逻辑混乱。
默认行为与忽略
注册 SIG_IGN 可忽略信号(如 signal(SIGPIPE, SIG_IGN) 忽略管道破裂信号)。
注册 SIG_DFL 可恢复默认行为(如 signal(SIGINT, SIG_DFL) 恢复 Ctrl+C 终止程序的默认行为)。
四、完整示例:处理自定义信号 SIGUSR1
c
运行
#include
#include
#include
#include
// 处理SIGUSR1信号(用户自定义信号1)
void sigusr1_handler(int signum) {
printf("收到自定义信号 %d(SIGUSR1)\n", signum);
}
int main() {
printf("进程PID:%d,发送信号:kill -USR1 %d\n", getpid(), getpid());
// 用sigaction注册SIGUSR1
struct sigaction act;
act.sa_handler = sigusr1_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (sigaction(SIGUSR1, &act, NULL) == -1) {
perror("sigaction失败");
exit(1);
}
// 等待信号
while (1) {
sleep(1);
}
return 0;
}
测试方法:
编译运行程序,获取 PID(如输出 进程PID:1234)。
另开终端发送信号:kill -USR1 1234,程序会打印信号处理信息。
总结
信号处理函数必须遵循 void (*)(int) 原型。
注册函数优先使用 sigaction()(功能全面、行为标准),简单场景可用 signal()。
处理函数内避免调用非异步安全函数,注意可重入性和信号阻塞逻辑。
通过这种方式,进程可灵活自定义对信号的响应,实现如优雅退出、日志轮转、状态更新等功能。
三二互联专业提供香港VPS,美国VPS主机,香港云服务器租用等业务香港美国到大陆CN2 GIA速度最快