在 C 语言的 Linux 环境中,注册异常处理函数通常是通过 signal () 或 sigaction () 系统调用来完成的。这些函数可以捕获特定的信号,并指定当信号发生时应该执行的处理函数。
使用 signal () 注册信号处理函数
#include <stdio.h>
#include <signal.h>
void signal_handler (int signum) {
printf ("Caught signal %d\n", signum);
// 在这里可以添加自定义的信号处理逻辑
}
int main () {
// 注册 SIGSYS 信号处理函数
signal (SIGSYS, signal_handler);
// 模拟等待信号
while (1) {
sleep(1);
}
return 0;
}
使用 sigaction ()
注册信号处理函数
相比 signal ()
,sigaction ()
是更推荐的方式,因为它提供了更多的控制和功能(例如可以重新定义信号处理行为,并保证信号的可靠传递)。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void signal_handler (int signum, siginfo_t *info, void *context) {
printf ("Caught signal %d\n", signum);
// 处理 SIGSYS 信号或其他信号
}
int main () {
struct sigaction sa;
// 清除 sigaction 结构体
memset(&sa, 0, sizeof(sa));
// 设置处理函数
sa.sa_sigaction = signal_handler;
// 使用 SA_SIGINFO 标志,以便获取更多信息
sa.sa_flags = SA_SIGINFO;
// 注册 SIGSYS 信号
sigaction(SIGSYS, &sa, NULL);
// 模拟等待信号
while (1) {
sleep(1);
}
return 0;
}
两种方法的区别
signal ()
是传统的方法,使用起来简单,但行为在不同的平台和情况下可能不一致。
sigaction ()
提供了更强的控制能力和一致性,特别是在处理复杂信号时。比如,sigaction ()
可以指定额外的信息(如 siginfo_t)并提供更多的处理选项。
在上述例子中,当进程收到 SIGSYS 信号时,信号处理函数将被调用。
sigaction()
是一个功能强大的函数,用于在 Linux/Unix 系统中处理信号。与传统的 signal()
相比,sigaction()
提供了更多的功能和更精确的控制。
sigaction()
函数原型
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum
:要捕获的信号编号,如SIGINT
、SIGTERM
、SIGSYS
等。act
:指向包含新信号处理动作的sigaction
结构体的指针。oldact
:如果不为NULL
,则存储旧的信号处理动作,允许恢复原有的信号处理函数。
sigaction
结构体
struct sigaction {
void (*sa_handler)(int); // 传统的信号处理函数指针
void (*sa_sigaction)(int, siginfo_t *, void *); // 带扩展信息的信号处理函数
sigset_t sa_mask; // 在信号处理程序执行期间阻塞的信号集
int sa_flags; // 影响信号处理行为的标志
};
sa_handler
:指向信号处理函数的指针。当信号触发时,会调用这个函数。它只接受信号编号作为参数。sa_sigaction
:指向带扩展信息的信号处理函数,它允许接收更多参数,例如信号的来源和上下文信息。要使用这个字段,需要设置sa_flags
标志为SA_SIGINFO
。sa_mask
:定义在处理当前信号时,哪些其他信号将被阻塞。sa_flags
:用于设置信号处理的一些行为,常用的标志有:SA_SIGINFO
:表示使用sa_sigaction
代替sa_handler
。SA_RESTART
:表示被信号中断的系统调用应自动重启。SA_NODEFER
:在信号处理期间,不自动阻塞正在处理的信号。SA_NOCLDWAIT
:当处理SIGCHLD
时,子进程终止后不产生僵尸进程。
示例:使用 sigaction()
捕获信号
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
// 信号处理函数,带有详细信息
void signal_handler(int signum, siginfo_t *info, void *context) {
printf("Received signal: %d\n", signum);
printf("Signal sent by process: %d\n", info->si_pid); // 打印发送信号的进程 ID
}
int main() {
struct sigaction sa;
// 清空 sa 结构体
memset(&sa, 0, sizeof(sa));
// 设置信号处理函数
sa.sa_sigaction = signal_handler;
// 使用 SA_SIGINFO 来使用 sa_sigaction 而不是 sa_handler
sa.sa_flags = SA_SIGINFO;
// 捕获 SIGINT (通常是 Ctrl+C)
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
// 无限循环,等待信号
printf("Waiting for signal... Press Ctrl+C to send SIGINT\n");
while (1) {
sleep(1); // 持续等待信号
}
return 0;
}
代码详解
-
memset(&sa, 0, sizeof(sa));
:首先清空sigaction
结构体,确保所有字段初始化为零。 -
sa.sa_sigaction = signal_handler;
:将sa_sigaction
设置为自定义的信号处理函数signal_handler
,以便在接收到信号时调用。 -
sa.sa_flags = SA_SIGINFO;
:使用SA_SIGINFO
标志,以便sigaction()
调用sa_sigaction
而不是sa_handler
。通过这种方式,信号处理函数可以接收更多的上下文信息(例如,哪个进程发送了信号)。 -
sigaction(SIGINT, &sa, NULL);
:调用sigaction()
注册信号处理程序,捕获SIGINT
信号(当用户按下 Ctrl+C 时会触发)。 -
while(1)
:程序进入一个无限循环,等待信号。每次收到信号时,会调用signal_handler
并打印相关信息。
siginfo_t
结构体
当使用 SA_SIGINFO
标志时,信号处理函数的第二个参数是指向 siginfo_t
结构体的指针,包含了关于信号的详细信息:
typedef struct {
int si_signo; // 信号编号
int si_errno; // 错误编号
int si_code; // 信号产生的代码
pid_t si_pid; // 发送信号的进程 ID
uid_t si_uid; // 发送信号的用户 ID
void *si_addr; // 导致故障的内存地址(例如,段错误时)
int si_status; // 子进程的退出状态(适用于 SIGCHLD)
long si_band; // 描述信号带数据(适用于 SIGPOLL)
} siginfo_t;
常用标志和行为
-
SA_RESTART
:让被信号中断的系统调用自动重启,而不是返回错误。这对于保持程序流畅运行非常有用。 -
SA_NODEFER
:默认情况下,当信号处理函数运行时,处理该信号的信号会被阻塞。设置此标志可以让信号处理函数在运行期间接受相同的信号。 -
SA_NOCLDWAIT
:如果进程捕获SIGCHLD
信号,此标志会禁止生成僵尸进程,而不需要调用wait()
。
使用 sigaction()
的优点
-
更强的控制:通过
sa_mask
可以定义在处理特定信号时应该阻塞哪些其他信号。 -
可靠性:与
signal()
相比,sigaction()
在某些复杂情况下更加可靠,不会因为内核版本或平台不同而行为不一致。 -
支持信号信息:通过
siginfo_t
提供更多关于信号的详细信息。
总结
sigaction()
是一个功能强大且灵活的信号处理机制,允许你在捕获信号时做更多的事情,如访问发送信号的进程信息、阻塞其他信号、自动重启被中断的系统调用等。