目录
1 什么是实时信号
2 sigqueue函数
3 sigpending()函数
1 什么是实时信号
等待信号集只是一个掩码,它并不追踪信号的发生次数。这意味着,如果相同的信号在被阻塞的状态下多次产生,它只会在信号集中被记录一次,并且在信号集被检查时仅被视为一个事件。这是标准信号的缺点之一。
与标准信号不同,实时信号可以排队,如果同一个实时信号多次发送给同一个进程,它们将被排队处理,而不是像标准信号那样只能触发一次。实时信号较之于标准信号,其优势如下:
- 可排队: 实时信号可以排队等待处理,如果一个信号多次到达,它只会被处理一次,除非它被显式地从队列中移除并重新发送。
-
优先级队列: 实时信号具有与信号相关联的默认实时优先级。如果一个实时信号被发送,它将按照其优先级顺序处理,这有助于确保高优先级的信号能够及时被处理。
-
应用定义的处理: 实时信号可以有应用程序定义的处理函数,类似于传统信号,但是它们可以安全地使用在多线程环境中。
-
不干扰传统信号: 实时信号与标准信号(如
SIGKILL
和SIGSTOP
)分开处理,因此不会受到这些信号的影响。
Linux 内核定义了 31 个不同的实时信号,信号编号范围为 34~64,使用 SIGRTMIN 表示编号最小的实时信号,使用 SIGRTMAX 表示编号最大的实时信号,其它信号编号可使用这两个宏加上一个整数或减去一个整数。
应用程序当中使用实时信号,需要有以下的两点要求:
-
发送进程使用 sigqueue()系统调用向另一个进程发送实时信号以及伴随数据。
-
接收实时信号的进程要为该信号建立一个信号处理函数,使用sigaction函数为信号建立处理函数,并加入 SA_SIGINFO,这样信号处理函数才能够接收到实时信号以及伴随数据,也就是要使用sa_sigaction 指针指向的处理函数,而不是 sa_handler,当然允许应用程序使用 sa_handler,但这样就不能获取到实时信号的伴随数据了。
2 sigqueue函数
sigqueue()
函数是用于发送实时信号给指定进程的 POSIX 函数。实时信号是 POSIX 1003.1b 标准的一部分,它们允许发送者指定信号的属性,如信号值和与之关联的数据。函数原型如下:
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
pid
:接收信号的进程的进程 ID。如果pid
为负值,信号将发送给指定会话中的所有进程。signum
:要发送的信号的编号。它应该是SIGRTMIN
到SIGRTMAX
之间的值,表示一个实时信号。value
:参数 value 指定了信号的伴随数据, union sigval 数据类型。- 返回值:成功时返回 0,失败时返回 -1,并设置
errno
以指示错误。
union sigval 数据类型(共用体) 如下所示:
typedef union sigval
{
int sival_int;
void *sival_ptr;
} sigval_t;
下面的示例代码是一个基本的父子进程通信程序,其中父进程使用 sigqueue
函数向子进程发送一个信号和附加数据。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid = fork(); // 创建子进程
if (pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程
printf("Child process is waiting for signal...\n");
pause(); // 子进程挂起,等待信号
exit(EXIT_FAILURE); // 确保子进程在接收信号前不会退出
} else {
// 父进程
printf("Parent process is sending signal to child...\n");
sleep(2); // 让子进程先打印等待信息
// 声明sigval变量并初始化
union sigval value;
value.sival_ptr = "Hello from parent!"; // 设置信号值
if (sigqueue(pid, SIGUSR1, value) == -1) {
perror("sigqueue failed");
exit(EXIT_FAILURE);
}
printf("Signal sent successfully.\n");
int status;
waitpid(pid, &status, 0); // 等待子进程结束
if (WIFEXITED(status)) {
printf("Child exited with status %d\n", WEXITSTATUS(status));
}
}
return 0;
}
在这段代码中,父进程首先创建了一个子进程,然后子进程调用 pause
进入等待状态,直到接收到信号。父进程在稍作延时后使用 sigqueue
发送 SIGUSR1
信号给子进程,并附带一个 sigval
类型的联合体,其中 sival_ptr
成员指向一个字符串 "Hello from parent!"。如果 sigqueue
调用成功,父进程将打印一条成功消息,并等待子进程结束。父进程随后检查子进程的退出状态,并打印出来。
3 sigpending()函数
sigpending
函数用于查询系统中挂起(pending)的信号集,挂起的信号是指那些已经发送给进程,但尚未被进程处理的信号。函数原型如下:
#include <signal.h>
int sigpending(set_t *set);
set_t
:处于等待状态的信号会存放在参数 set 所指向的信号集中。
下面是一个简单的使用 sigpending
函数的示例:
#include <stdio.h>
#include <signal.h>
#include <errno.h>
int main()
{
sigset_t pending_set;
// 检查错误
if (sigpending(&pending_set) == -1) {
perror("sigpending failed");
return 1;
}
// 检查特定信号是否挂起
if (sigismember(&pending_set, SIGINT)) {
printf("SIGINT is pending.\n");
} else {
printf("SIGINT is not pending.\n");
}
return 0;
}
在这个示例中,程序首先调用 sigpending
来获取当前挂起的信号集,然后使用 sigismember
函数检查 SIGINT
是否在挂起信号集中。如果 SIGINT
信号挂起,程序将打印相应的消息。如果 sigpending
调用失败,程序将打印错误信息并返回。