信号
信号是一个软件中断,用来通知进程发生了某个事件,中断当前进程正在执行的操作,去处理这个事件。信号就代表着一个事件的发生。信号有多种,格子表示不同的事件。
信号的生命周期:信号的产生、在进程中注册、信号的销毁与处理。
信号的种类:
kill -l 查看信号种类:
信号共有62种,1-31号为非可靠信号,可能造成事件丢失;34-64号为可靠信号,不会造成事件丢失。
信号产生
硬件产生:由硬件信号产生软件信号发送给进程。ctrl+C 2号信号,中断当前进程;ctrl+| 3号信号,退出信号,让当前进程退出;ctrl+Z 20号信号,停止当前进程,将其转入后台。
软件产生: kill -signum pid 给pid进程发送signum号信号
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
向任意pid进程,发送任意信号signo,是一个系统调用函数。
#include <signal.h>
int raise(int sig);
给进程自身发送信号sig。
#include <stdlib.h>
void abort(void);
给进程自身发送SIGABRT信号即6号信号,引发进程非正常终止。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
定时器,经过seconds秒后,进程退出。
ulimit -a 查看进程中的一些限制信息,例如:核心文件大小(默认为0)、数据段大小、调度优先级、文件大小、待处理信号、最大锁定内存、最大内存大小、打开的文件、管道大小、POSIX消息队列、实时优先级、堆栈大小、cpu时间、最大用户进程、虚拟内存、文件锁。
core dump 核心转储,当程序异常退出时保存程序的运行信息,用于事后调试,事后调试也是使用gdb进行,gdb ./main -> core-file core文件。
ulimit -c 设置core dump文件的最大大小,默认为0,处于关闭状态。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
int main()
{
kill(getpid(), SIGQUIT); // 向任意进程发送任意信号
raise(SIGQUIT); // 给进程自身发送信号
abort(); // 给自身发送SIGABRT信号,引发进程非正常终止
alarm(5);
while(1)
{
printf("--------------------\n");
sleep(10);
}
return 0;
}
信号注册
信号在文件中注册是操作系统修改进程pcb中的一个信号标志位(pending位图),是一个未决信号集合(未决:从产生到处理之间信号所出的一种状态)。
注册即在未决信号集合中修改对应的位图,并且向sigquque链表中添加信号信息节点到这个双向链表中。
非可靠信号:若当前未决信号集合中指定信号已经注册,则什么都不做,直接返回,即一个信号只有一个sigqueue节点。
可靠信号:无论当前信号是否注册,都会去修改位图,并且添加一个新的sigqueue节点。
信号注销
信号的注销同样是修改未决信号集合。注销是删除当前信号的一个sigqueue节点,并且修改位图。
非可靠信号:删除节点之后,直接将信号位图表示该信号的位置0。
可靠信号:删除节点后,判断是否还有相同节点,若没有则将信号位图表示该信号的位置0。
信号处理
信号处理又称信号的递达。处理方式有三种:默认、忽略。自定义。
#include <signal.h>
typedef void (*sighandler_t)(int); // 函数指针
sighandler_t signal(int signum, sighandler_t handler);
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
int sigemptyset(sigset_t *set); // 将信号集初始化为空
signum:信号值;
handler:修改的信号处理方式,SIG_IGN为忽略,SIG_DFL为默认,同时这里可以传一个用户自己写的回调函数;
act:signum当前要修改的新动作;
oldact:用于获取signum信号原有的动作,便于之后还原回去;
信号的处理发生在从内核态返回用户态的时候。
默认、忽略的处理方式在内核中完成。
自定义处理方式的信号捕捉流程:
- 程序在主控流程中由于中断、异常、系统调用而进入内核态运行
- 当程序在内核态处理完功能后,从内核态返回用户态的之前处理信号
- 由于自定义信号处理函数是程序员自己写的,因此运行在用户态,于是切换到用户态运行回调函数
- 回调函数运行完毕,程序返回内核态,继续处理信号,若没有信号则返回用户态的主控流程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
struct sigaction oldact;
void sigcb(int signum)
{
printf("recv a signal: %d\n", signum);
sigaction(signum, &oldact, NULL);
}
int main()
{
//signal(2, sigcb); // 将2号信号处理方式改为自写的回调函数
struct sigaction newact;
newact.sa_handler = sigcb; // 设置自定义回调函数
newact.sa_flags = 0; // 默认使用sa_handler回调函数
sigemptyset(&newact.sa_mask); // 清空临时要阻塞的函数集合
sigaction(2, &newact, &oldact);
while(1)
{
printf("_______________\n");
sleep(10);
}
return 0;
}
信号的阻塞
阻塞是阻值一个信号被递达,被阻塞的信号仍然可以注册,只是暂时不被处理。
阻塞一个信号是在pcb中讲这个信号在阻塞集合中标记出来。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); // 设置信号掩码
how:SIG_BLOCK 添加阻塞,SIG_UNBLOCK 解除阻塞,SIG_SETMASK 设置阻塞。
在所有信号中,9号信号SIGKILL和19号信号SIGSTOP无法被阻塞,无法自定义,无法被忽略。
信号block,pending,handler
每个信号都有:block、pending、handler
block:表示信号是否被阻塞。数据类型sigset_t,0表示不被阻塞,1表示被阻塞。被阻塞的信号在产生时保持未决状态,直到进程解除对信号的阻塞,才执行该信号的动作。
pending:未决标志。信号产生还未处理为1,直到信号被处理才清除该标志。数据类型sigset_t。
handler:存放信号处理函数的函数指针。
函数的可重入与不可重入
竞态条件:在多个执行流中对同一段代码进行竞争执行。
函数的重入:在多个执行流中,重复进入一个函数执行同一段的代码。
可重入:函数重入后不会出现任何影响,程序运行依旧正确。
不可重入:如果一个函数重入后有可能造成数据二义性或程序的逻辑混乱,则该函数是不可重入的。
可重入与不可重入的判断依据:该函数中是否对全局变量进行了非原子安全操作。如果进行了非原子安全操作则该函数是不可重入的,反之则是可重入的。但是当用户自己编写函数或调用别人的函数时,还需要根据使用的场景考虑函数重入情况,主要需要考虑重入后程序的逻辑是否会出现混乱。