前言
本篇文章我们来介绍一下sigacation函数和sigqueue函数。
一、sigaction
sigaction 是一个用于设置和检查信号处理程序的函数。它允许我们指定信号的处理方式,包括指定一个函数作为信号处理程序、设置标志位以及指定信号处理程序执行期间的信号屏蔽字等信息。
下面是 sigaction 函数的原型:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum:要操作的信号的编号。可以是 POSIX 信号常量(如 SIGINT、SIGTERM 等)或自定义信号的编号。
act:一个指向 struct sigaction 结构的指针,包含了要设置的信号的处理方式和信号标志等信息。
oldact:一个可选的指向 struct sigaction 结构的指针。如果不为 NULL,将返回之前的信号处理程序的信息。
struct sigaction 结构体是用来描述信号的处理方式和信号标志等信息的,其定义如下:
struct sigaction { void (*sa_handler)(int); // 信号处理程序的函数指针 sigset_t sa_mask; // 信号屏蔽字 int sa_flags; // 信号标志 void (*sa_sigaction)(int, siginfo_t *, void *); // 使用 sa_sigaction 代替 sa_handler };
sa_handler:一个函数指针,指向信号处理程序的函数。可以是一个用户定义的函数,也可以是特殊的值 SIG_DFL(默认处理程序)或 SIG_IGN(忽略处理程序)。
sa_mask:一个信号集,用来指定在信号处理程序执行期间要屏蔽的信号。它影响信号处理程序的上下文,可以通过 sigaddset、sigemptyset 等函数进行设置。
sa_flags:一组标志位,用于指定信号处理程序的行为。常见的标志位包括 SA_RESTART(在被信号中断的系统调用后自动重启)和 SA_NODEFER(在调用信号处理程序时不将本信号添加到进程的信号屏蔽字中)等。
sa_sigaction:一个函数指针,用于指定信号的扩展处理程序。它支持更多的参数,包括一个 siginfo_t 类型的结构体和一个 void * 类型的指针,用于传递关于信号的更多信息。
通过使用 sigaction 函数,我们可以设置信号处理程序的行为,包括调用用户定义的函数、忽略信号、重新启动系统调用等,并可以设置信号屏蔽字,以控制信号传递的方式。这使得我们可以对不同的信号进行个性化的处理。
siginfo_t 结构体是在信号处理程序中提供有关接收到信号的更详细信息的数据结构。它定义在 <signal.h> 头文件中,用于在处理程序中获取有关信号的附加信息。
下面是 siginfo_t 结构体的基本定义:
typedef struct siginfo { 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 信号中使用) struct sigval si_value; // 附加的数据值 union sigval si_utime; // 用户CPU时间的计数值(仅在 SIGCHLD 信号中使用) union sigval si_stime; // 系统CPU时间的计数值(仅在 SIGCHLD 信号中使用) } siginfo_t;
二、sigqueue函数
sigqueue 函数用于向指定的进程发送特定的信号,并且可以传递一个额外的数据值。它提供了比 kill 函数更丰富的功能,可以用于进程间的高级通信。
以下是 sigqueue 函数的基本定义:
int sigqueue(pid_t pid, int sig, const union sigval value);
pid 是接收信号的进程的进程ID。
sig 是要发送的信号的编号,可以是标准信号(如 SIGTERM,SIGINT 等)或用户定义的信号。
value 是一个 union sigval 类型的结构体,用于传递附加的数据值。
使用 sigqueue 函数发送信号的步骤如下:
1.创建一个 union sigval 结构体并设置相应的值,以便将额外的数据传递给接收进程。
2.调用 sigqueue 函数,将目标进程的进程ID、信号编号和数据值作为参数传递给它。
3.如果成功发送信号,sigqueue 函数将返回0;如果出错,返回-1,并设置相应的错误码。
通过使用 sigqueue 函数发送带有附加数据的信号,接收进程可以从信号处理程序中获取这些附加数据,并根据需要采取相应的操作。这对于进程间的通信很有用,可以用于传递消息、触发特定的操作,或者向其他进程传递数据。
需要注意的是,接收进程必须正确设置信号处理程序来处理通过 sigqueue 函数发送的信号,并正确解析读取附加数据。
三、代码示例
接收信号处理函数:
#include <stdio.h> #include <string.h> #include <signal.h> #include <unistd.h> #include <sys/types.h> #include <unistd.h> void signal_Handle(int sig, siginfo_t* info, void* ucontext) { printf("handler : sig = %d\n", sig); printf("handler : info->si_signo = %d\n", info->si_signo); printf("handler : info->si_code = %d\n", info->si_code); printf("handler : info->si_pid = %d\n", info->si_pid); printf("handler : info->si_value = %d\n", info->si_value.sival_int); } int main(int argc, char** argv) { printf("pid :%d\n", getpid()); struct sigaction act = {0}; act.sa_sigaction = signal_Handle; act.sa_flags = SA_RESTART | SA_SIGINFO; /* 添加信号屏蔽字 */ /* 下面信号在信号处理程序执行时会被暂时阻塞 */ sigaddset(&act.sa_mask, 40); sigaddset(&act.sa_mask, SIGINT); /* 设置信号的处理行为,设置后40和SIGINT信号将由act里面的信号处理函数处理 */ sigaction(40, &act, NULL); sigaction(SIGINT, &act, NULL); while(1) { sleep(1); } return 0; }
发送信号处理函数:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> int main(int argc, char** argv) { pid_t pid = atoi(argv[1]); union sigval sv = {123456}; sigqueue(pid, 40, sv); raise(SIGINT); return 0; }
运行结果:
从结果中我们可以看出接收到信号后打印出了对应的数据。
总结
本篇文章主要讲解了sigacation函数和sigqueue函数,相比于signal和kill函数,使用这两个函数来发送信号是比较灵活的。