信号概述
像上课铃声这种信号,我们识别接收后,知道该上课了,这是我们后天学习养成的默认意识
在进程收到信号后,它是知道该怎么做的 ,程序员内置了默认的处理行为
进程的运行跟信号的产生属于异步关系: 1.进程不一定立刻去处理已经到来的信号 2.如果进程在处理优先级更高的事情,可以暂时不处理信号,等到合适的时候再处理。 3.会用某种方式记录下已经到来的但没处理的信号,以便在空闲的时候处理这些信号
异步:二者之间互不影响 同步:二者之间相互影响
处理信号的三种方式: 1.默认行为
2.提供信号处理函数,要求内核在处理该信号时切换到用户态来执行这个处理函数,称为捕捉信号
3.忽略
kill -l 查看信号 总共有62个信号(1-31普通信号,34-64实时信号)
信号的记录与发送
信号是在进程的task_struct中记录的,通过位图来记录是否产生信号
所以进程收到信号,本质是操作系统修改了进程中的信号位图(只能是OS)
操作系统是软硬件资源的管理者
信号的产生
signal函数(自定义行为)
示例: ctrl c(2号信号) 通过键盘输入ctrl+c(2号信号)来给进程发送信号
ctrl c(键盘产生的信号)只能发送给前台进程,一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。 可以用kill发送信号给后台进程 并不是所有的信号都能捕捉,比如9号就不能(全部捕捉就意味着可能不能杀死进程)
产生信号
Core Dump
SIGINT(Term)的默认处理动作是终止进程 SIGQUIT(Core)的默认处理动作是终止进程并且Core Dump
用3号信号关掉进程的时候,会进行核心转储
查看系统当中的系统资源:ulimit - a 云服务器下的核心转储是默认关闭的(why:每次运行程序挂掉都会在磁盘产生不小的core.pid文件)
我们把它打开: 再次运行程序向进程发出三号信号(Ctrl+\) 多产生一个core文件,5865是发生核心转储的进程ID
核心转储: 代码运行中出错时,将进程内存中的核心数据转储到磁盘上,生成core.pid文件 目的是为了调试定位问题
程序异常产生信号
利用core文件进行事后调试除0异常 使用命令core-file core.pid 查看错误信息 可以看到被8号信号(SIGFPE)终止,15行报错 进程为什么会崩溃 程序崩溃的本质是收到了OS发送的信号
进程为什么会收到信号 当程序发生某种错误时,一定会在硬件层面上有所表现,进而被OS识别,向该进程发出信号
status(16位)
子进程正常退出: 子进程异常退出:
子进程收到8号信号,并且coredump为1,说明运行时程序崩溃时core dump了
ctrl+z信号
ctrl+z(20号信号)暂停进程,把进程放到后台
jobs:查看后台进程 fg 序号 将后台恢复到前台
系统调用产生信号
kill
模拟实现kill函数(利用命令行参数)
命令行参数: 将字符串转为int 可以看到通过命令行参数,在程序里面进行系统调用kill,依然产生了信号
raise
作用:自己给自己发信号
示例:
abort
abort函数使当前进程接收到信号而异常终止 特性:就像exit函数一样,abort函数总是会成功的,所以没有返回值
示例:
软件条件产生的信号
SIGPIPE是一种由软件条件产生的信号
alarm
在传入时间后,向进程发送14号信号 不像abort,捕获后并没有终止进程 每隔一秒打印递增的count: 每次alarm发出信号捕获后,有设置新的闹钟,就会一直打印
信号的保存
1.实际执行信号的处理动作称为信号递达 2.信号从产生到递达之间的状态,称为信号未决 3.进程可以选择阻塞某个信号 4.被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
收到信号后: 所以在阻塞时(1到31号信号)收到多次该信号只处理一次
修改位图
sigset_t(信号集) 作用:用于描述进程的block位图,pending位图的信号集
#include <signal.h> int sigemptyset(sigset_t *set);//清空 int sigfillset(sigset_t *set);// 全部置1 int sigaddset (sigset_t *set, int signo);//添加信号到信号集 int sigdelset(sigset_t *set, int signo);//删除信号到信号集 int sigismember(const sigset_t *set, int signo);//判断信号是否存在
系统调用让设置能够修改PCB里面的内容:
①sigprocmask
sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 返回值:若成功则为0,若出错则为-1
how:如何修改当前信号集
SIG_BLOCK | 添加set中的包含的信号到信号集 |
---|
SIG_UNBLOCK | 解除信号集中set所包含的信号 | SIG_SETMASK | 将set拷贝给当期阻塞信号集 |
set:用set信号集来修改当前阻塞信号集 oset:阻塞信号集会先备份到oset里面,是输出型参数,方便恢复信号集
②sigpending
获取当前进程的未决信号集,通过set参数传出(输出型参数)。调用成功则返回0,出错则返回-1
示例:
可以看出:先屏蔽了2号信号,想进程发送2号信号不会被递达,pending对应位置修改为1
示例二:
先阻塞了2号信号,发送二号信号,pending修改,count==6时恢复了2号信号,2号信号递达,执行捕捉代码,最后信号执行完,对应pending修改为0
信号的处理
上面提到进程收到信号之后,不是立即处理信号,而是在合适的时候 这个合适的时候就是内核态切换回用户态的时候
内核态通常执行OS代码,权限优先级非常高 用户态执行普通用户的代码的状态,受OS的管理
自定义处理函数状态切换4次 默认,忽略只切换了两次
sigaction
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact); 作用:自定义捕捉信号 类似于sigprocmask
struct sigaction: sa_sigaction:处理实时信号的接口 sa_handler:捕捉执行的方法(如果设置成SIG_DFL表示执行系统默认动作) sa_flags:通常设置为0 sa_mask:说明需要额外屏蔽的信号
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字(自动添加到mask中),当信号处理函数返回时自动恢复原来的信号屏蔽字
示例:
volatile关键字
让以下代码在优化级别-O3 下运行
加上volatile后 就会捕获信号退出循环 加入volatile后,不会将flag先置入寄存器,而是在读取内存中的值到寄存器中再检测
volatile的作用:保证了内存可见性
|