信号的概念和机制
1.信号的概念和机制
理解信号可以参考生活中,烽火、狼烟等
信号的特点:1.简单;2.不能携带大量信息;3.满足某个特设条件才发送
1.1.信号的机制
信号时软件层面的“中断”,信号VS中断VS异常,三个概念可以一起学习
每个进程收到的所有信号,都是由内核负责发送、内核处理的
简单地说,unix的信号机制,是一种软中断
1.2.与信号相关的概念
1.2.1.信号产生
- 按键,如:Ctrl + c、Ctrl + z、Ctrl + \
- 系统调用,如:
kill raise abort
- 软件条件,如:定时器
alarm
- 硬件异常,如:非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误)
- 命令产生,如:
kill
1.2.2.信号的状态
- 信号产生/发送
- 未决
- 递达
1.2.3.信号的处理方式
- 执行默认工作(大部分信号时终止当前进程)
- 忽略(丢弃)
- 捕捉(执行用户处理函数)
1.3.信号屏蔽字与未决信号集
linux内核的进程控制块PCB是一个结构体,处理包含进程id、状态、工作目录、用户id、文件描述符表等,
还包括了信号相关的信息,主要是信号屏蔽字和未决信号集(本质是bitmap)
- 信号屏蔽字(阻塞信号集):将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到x信号,x信号的处理将推后(解决屏蔽后)
- 未决信号集:
- 信号产生,未决信号集中描述该信号的位立刻翻转为1,表示该信号处于未决状态。当信号被处理对应位翻转为0。这一时刻往往非常短暂
- 信号产生后由于某些原因(一般是阻塞)不能递达。这类信号的集合称为未决信号集。在屏蔽解除前,信号一直处于未决状态
1.4.信号四要素与常见信号
1.4.1.信号四要素
man 7 signal
查看
-
编号
-
名称(不重要,代码开发中使用信号对应的宏)
-
事件(产生信号的事件)
-
默认处理动作
1.4.1.常见信号
linux终端输入:kill -l
-
1-31信号:常规信号,有默认事件和处理动作
-
34-64:实时信号,没有默认事件
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
1.5.kill函数和kill命令
- kill是发送一个信号给进程
man kill
-->命令
man 2 kill
-->函数
- kill的第二个入参
pid
,传不同的值有不同的作用:
- 一个例子
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid > 0) {
while (1) {
printf("parent pid = %d\n", getpid());
sleep(1);
}
} else if (pid == 0) {
printf("******child pid = %d, ppid = %d\n", getpid(), getppid());
sleep(2);
printf("******child process will kill parent process = %d, ... ...\n", getppid());
sleep(1);
kill(getppid(), SIGKILL); // SIGKILL 默认动作:终止进程
// kill(getppid(), SIGCHLD); // SIGCHLD 子进程状态发送变化给父进程发送该信号,默认进程:忽略该信号
}
return 0;
}
-
其他发信号函数
int raise(int sig);
void abort(void);
1.6.两个闹钟(定时器)函数
1.6.1.alarm函数—自然定时
设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14) SIGALRM
信号。进程收到该信号,默认动作终止进程。
每个进程有且仅有一个定时器。
- 常用:取消定时器
alarm(0)
,返回定时器剩余秒数
int main(int argc, char *argv[])
{
int cnt = 0;
alarm(1); // 1秒后发送14) SIGALRM信号。进程收到该信号,默认动作终止进程
while (1) {
cnt++;
printf("%d\n", cnt);
}
return 0;
}
-
利用time指令执行程序:
time a.out
,发现:real
> (user
+sys
),其中等待时间是由于printf
调用write
系统调用(进行标准输出),进行IO操作,比较耗时(程序运行的瓶颈在于IO)
real
:程序实际运行时间
user
:用户态时间
sys
:内核态时间
- 如果把IO到终端的打印重定向,
time a.out > out
,看看时间,1秒钟的计数:
1秒的计数值比之前大了很多很多:
1.6.2.setitimer
代替alarm
,时间精度更高
1.7.信号集操作函数
1.7.1.set增删改查
自定义一个sigset_t
类型的set(bitmap)
,信号集操作函数,对bitmap进行增删改查
1.7.2.sigprocmask函数——屏蔽信号、解除屏蔽
sigprocmask() is used to fetch and/or change the signal mask of the calling thread.
注意:屏蔽信号只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢弃处理
how的取值:
-
SIG_BLOCK
:阻塞 -
SIG_UNBLOCK
:解除阻塞 -
SIG_SETMASK
:覆盖
1.7.3.sigpending函数——读取进程的未决信号集
1.7.4.信号集操作函数使用原理分析
自定义信号集set,去操作信号屏蔽字,进而影响未决信号集
set增删改查 函数
—>sigprocmask 屏蔽或解除屏蔽
1.7.5.一个例子
void PrintSet(const sigset_t *set)
{
int i;
// 打印【1-32信号】
for (i = 1; i < 32; i++) {
if (sigismember(set, i)) {
putchar('1');
} else {
putchar('0');
}
}
printf("\n");
}
int main(int argc, char *argv[])
{
sigset_t set, oldset, pedset;
// 操作自定义的信号集set
sigemptyset(&set);
sigaddset(&set, SIGINT);
// 用自定义set,操作pcb中的信号屏蔽字
sigprocmask(SIG_BLOCK, &set, &oldset);
while (1) {
// 读取未决信号集:前面对信号屏蔽字的SIGINT信号设置了阻塞,当SIGINT信号产生时,未决信号集SIGINT信号bit会置1,且不会恢复为0,直至SIGINT信号解除
// 其他信号同理,处理不能被阻塞/屏蔽的信号
sigpending(&pedset);
// 循环打印未决信号集
PrintSet(&pedset);
sleep(1);
}
return 0;
}
1.8.信号捕捉
1.8.1.signal函数
简单了解,非posix标准,使用sigaction替代
1.8.2.sigaction函数
- 重点关注结构体
sigaction
:
struct sigaction {
void (*sa_handler)(int); // 捕捉到x信号对应的回调函数
void (*sa_sigaction)(int, siginfo_t *, void *); // 信号携带结构体等复杂参数
sigset_t sa_mask; // 重点:区别于pcb中的信号屏蔽字;这里的sa_mask只工作与信号捕捉函数执行期间
int sa_flags;
void (*sa_restorer)(void);
};
- sa_mask
sa_mask specifies a mask of signals which should be blocked (i.e., added to the signal mask of the thread in which
the signal handler is invoked) during execution of the signal handler. In addition, the signal which triggered
the handler will be blocked, unless the SA_NODEFER flag is used.
翻译一下:
sa_mask指定在信号处理程序的执行期间应该被阻塞的信号的掩码(即,添加到调用信号处理程序所在线程的信号掩码)。此外,触发处理程序的信号将被阻止,除非使用SA_NODEFER标志。
- 一个例子
void callback(int signum)
{
if (signum == SIGINT) {
printf("catch SIGINT signum=%d\n", signum);
} else if (signum == SIGQUIT) {
printf("catch SIGQUIT signum=%d\n", signum);
}
}
int main(int argc, char *argv[])
{
struct sigaction act, old;
act.sa_handler = callback; // 设置信号捕捉到的回调函数
sigemptyset(&act.sa_mask); // 初始化sa_mask,sa_mask只在信号回调函数执行期间生效,目的是为了防止回调函数死循环
act.sa_flags = 0; // 默认传0
sigaction(SIGINT, &act, &old); // 注册SIGINT信号的捕捉函数
sigaction(SIGQUIT, &act, &old); // 注册SIGQUIT信号的捕捉函数
while (1);
return 0;
}