信号是进程间通信唯一的异步方式,是对中断的一种软件模拟。
信号的触发方式
1、终端触发。如CTRL+C产生SIGINT
2、硬件异常。如对无效内存操作触发SIGSEGV
3、kill函数、命令触发。
4、软件事件相关信号。如写一个已经关闭的管道或socket触发SIGPIPE;闹钟超时信号SIGALRM。
信号的操作方式
1、忽略信号:即内核将会丢弃该信号,但不会对目标进程产生任何影响。但SIGKILL和SIGSTOP信号不可以忽略,这两种信号向超级用户提供了终止或停止进程的可靠方法。
2、捕捉信号。用户可以自定义对信号的响应操作,但SIGKILL和SIGSTOP信号除外。
3、执行系统默认动作。
信号的种类
1、不可靠信号
不可靠指所发送的信号不一定被内核传递给目标进程,即信号可能会丢失。信号值在[1, 31]之间的所有信号。
2、不可靠信号
信号值在[SIGRTMIN,SIGRTMAX]之间的信号。
注意:32号信号和33号信号。这两个信号(SIGCANCEL和SIGSETXID)已被NPTL线程库使用了,用来实现线程的取消。
内核对信号的处理方式
可靠信号与不可靠信号的根本差异在于,内核收到信号后的处理方式。
不可靠信号,内核采用位图来记录该信号是否处于挂起状态。内核收到某不可靠信号,如果该信号已经处于未决状态,将丢弃。发送不可靠信号,信号可能会丢失,即内核递交给目标进程的次数,可能小于信号的发送次数。
可靠信号,内核采用队列来维护,如果收到可靠信号,内核将信号挂起到相应队列。不会丢失。但内核挂起信号的个数也有一定的限制,在一定的范围内,可靠信号不会被丢弃。
信号的可靠性实验
信号接收端:signal_receiver.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
static int sig_cnt[NSIG];
static volatile sig_atomic_t get_SIGINT = 0;
void handler(int signo)
{
if (signo == SIGINT)
get_SIGINT = 1;
else
sig_cnt[signo]++;
}
int main(int argc, char *argv[])
{
int i = 0;
sigset_t blockall_mask;
sigset_t empty_mask;
printf("%s: PID is %d\n", argv[0], getpid());
for (i = 1; i < NSIG; i++)
{
if (i == SIGKILL || i == SIGSTOP || i == 32 || i == 33)
{
continue;
}
if (signal(i, &handler) == SIG_ERR)
{
fprintf(stderr, "signal for signo(%d) failed (%s)\n", i, strerror(errno));
}
}
if (argc > 1)
{
int sleep_time = atoi(argv[1]);
sigfillset(&blockall_mask);
if (sigprocmask(SIG_SETMASK, &blockall_mask, NULL) == -1)
{
fprintf(stderr, "setprocmask to block all signal failed(%s)\n", strerror(errno));
return -2;
}
printf("I will sleep %d second\n", sleep_time);
sleep(sleep_time);
sigemptyset(&empty_mask);
if (sigprocmask(SIG_SETMASK, &empty_mask, NULL) == -1)
{
fprintf(stderr, "setprocmask to release all signal failed(%s)\n", strerror(errno));
return -3;
}
}
while (!get_SIGINT)
{
continue;
}
printf("%-10s%-10s\n", "signo", "times");
printf("===================\n");
for (i = 1; i < NSIG; i++)
{
if (sig_cnt[i] != 0)
{
printf("%-10d%-10d\n", i, sig_cnt[i]);
}
}
return 0;
}```
信号发送端:signal_sender
```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
void usage(void)
{
fprintf(stderr, "USAGE:\n");
fprintf(stderr, "=============================\n");
fprintf(stderr, "signal_sender pid signo times\n");
}
int main(int argc, char *argv[])
{
pid_t pid = -1;
int signo = -1;
int times = -1;
int i;
if (argc < 4)
{
usage();
return -1;
}
pid = atol(argv[1]);
signo = atoi(argv[2]);
times = atoi(argv[3]);
if (pid <= 0 || times < 0 || signo < 1 || signo >= 64 || signo == 32 || signo == 33)
{
usage();
return -1;
}
printf("pid = %d, signo = %d, times = %d\n", pid, signo, times);
for (i = 0; i < times; i++)
{
if (kill(pid, signo) == -1)
{
fprintf(stderr, "send signo(%d) to pid(%d) failed, reason(%s)\n", signo, pid, strerror(errno));
return -2;
}
}
fprintf(stdout, "done\n");
return 0;
}
测试结果
如图,分别向接收端发送非实时信号(20)和实时信号(38)各10000次。
如图。接收端分别收到非实时信号(20)989次,实时信号(38)10000次,由此可见非实时信号会丢失。