信号详细讲解
一、信号定义
信号是进程发送的,用来进行一个时间的异步通知,中断正在做的某个事情。
1.信号处理,进程在信号没有产生的时候就知道这个信号意味着什么
2.信号的处理不是立即处理而是可以在合适的时候进行处理
3.进程是已经被程序员内置了识别和处理信号的方式。
4.信号源很多->给进程产生信号的信号源也非常多。
二、信号的产生
第一种方式-键盘产生
1.用键盘产生信号,ctrl+c就是给目标进程发送信号的。相当一部分进程的处理动作就是让进程终止!但是信号的处理也是分3种情况的:1.默认处理动作 2.更改默认处理,自定义处理方式->自定义捕捉 3.忽略
如何去自定义处理方式。
前台和后台进程
后台可以有多个,前台只能有一个。前台进程就是用来接收和处理标准输入的信号的。
eg:fork创建子进程是,当父进程先退出了之后,子进程就变成孤儿进程。ctrl+c杀不掉,被1号进程领养了,其实也是因为这个子进程变成了后台进程,接收不到键盘输入的信号。
如何将后台进程杀掉?
再打开一个命令行,查找该后台进程的pid,kill -9 pid就能杀掉后台进程。
把后台命令提到前台fg+任务号,ctrl+c杀掉即可
前后台如何移动
如何发信号
当我给一个进程发信号,这个进程是需要在合适的时候去处理这个信号的。那么这个进程是如何记住这个信号的呢?
在进程的task_struct中有一个unsigned int sigs,以32位位图的方式进行记录信号。所以发信号的本质就是向目标进程写信号,修改位图。
但是task_struct是操作系统内的数据结构对象,修改位图本质就是修改内核数据。所以不管产生信号的方式有多少种,发信号的过程必须让OS自己操作。所以kill的底层也是封装了系统调用。
信号产生的第二种方式-系统调用
小结论:9号信号无法被自定义捕捉
自己给自己发信号raise()
通过硬件异常产生信号
cpu拿着这个野指针去MMU中进行转换,转化失败,硬件报错,OS作为软硬件资源管理者,捕捉到这个硬件报错信号,直接发送11号信号,终止进程。
操作系统怎么知道硬件报错类型?term && core
软件条件报错
比如一个进程向另一个进程通过管道发送消息,将读端关闭,写端一直写就会崩溃,发送SIGPIPE信号终止进程。
alarm()默认经过n秒终止进程,13号信号SIGALM。IO效率低下验证。
每个进程都可以设置闹钟,要对这些闹钟进行管理,先描述在组织。这个闹钟里有时间片(这个闹钟对应的方法要执行的次数,执行一次,时间片--),时间节点,对应的方法。闹钟底层是用堆方法实现的。这就是闹钟的数据结构管理。
模仿OS,在没有时钟信号到来之前,一直都是挂机状态,就是死循环。pause().
等到一个时钟信号进行唤醒,执行对应的动作。
三、信号的保存
信号未决:接受信号,记录下来,等待执行中的状态
信号递达:处理信号的动作
信号阻塞:接受信号,但是永远不会被递达;只有接触阻塞才能被递达;和信号忽略不一样,阻塞是在递达之前的动作,忽略是在递达之后的一种选择状态,只不过不按照默认状态去执行。
三张变表:block(32位图表,表示是否阻塞),pending(32位图表,表示是否存在未决状态),header表(函数指针数组)
设置block表的接口:sigprocmask
获取pending表的接口:sigpending
整合demo:
清空block,man 3 sigismember,
9号信号不可被捕捉不可被屏蔽,是管理员信号。
当我们准备递达的时候,就要先清空pending信号集对应的信号位图1->0
core和term的对比
云服务器是生产环境,core dump功能是被禁掉的。因为进程没挂一次,都会dump一次,额外占用磁盘空间。
为什么核心转储?主要是支持debug,当一个大型文件出现bug,但是找不到在哪一行出错,开启core dump,运行崩溃生成core文件,gdb进行调试,gdb core-file core,定位到具体哪一行出现bug。
四、信号的处理
我们之前说要在合适的时候处理信号,这个合适的时候就是进程从内核态转到用户态做的信号检查。处理信号默认和忽略方法比自定义方法更加容易。
内核态和用户态
硬件中断--操作系统是怎么运行的
外设上有数据时候发送高电平到中断控制器上,绕后随之通知cpu,生成一个中断号,cpu知道有外设已就绪,但不知到是哪一个设备所以去中断控制器获取中断信号,cpu拿着中断号访问查询中断向量表,进而执行对应的方法。执行完毕恢复原来的上下文。这个方法是操作系统提供的。
操作系统就是基于外部中断运行的。时间源集成到cpu内部,每隔固定的时间(如1ns)向cpu发送中断信号,这就是主频信号,操作系统拿着这个中断号执行schedule方法,进行已注册好的进程调度算法。所以操作系统就是一个死循环,等着时间中断信号到达,更新时间片,检查该进程时间片,这个时间片一到,切换下一个进程。所以死循环也可能跑不完。
比如除0错误,访问野指针等这些,(软件导致硬件先出错了)cpu自己能触发一个中断信号,然后去访问查询对应的信号中断表,执行对应的方法。这就是为什么操作系统会知道进程在哪里出错了。
软中断
由cpu通过软件触发的中断。向我们之前的系统调用,在内核中会有一个系统调用表,是一个系统调用函数指针表,实质上操作系统不提供任何系统调用接口,只提供系统调用号。像我们之前使用的fopen,close这些接口实际上都是glibc封装的。底层是用汇编实现的,会给cpu发送系统调用号,触发中断,OS拿着系统调用号,执行对应的系统调用。两个汇编指令32位下的int 指令,64位下的syscall,cpu识别到这两个指令就可以执行中断信号。
系统调用的过程都是在进程空间里完成的,所有的函数调用都是地址空间的跳转。系统调用就是基于int 0x80这种软中断来写的。
详谈用户态和内核态
有多少个进程就会有多少个用户页表。系统只有一份内核页表,所有进程共用一份。
cpu内部有cs段寄存器寄存器,标志位(由CS 段寄存器的低2 位决定的)会记录当前状态是用户态还是内核态。如果这个标志位是3就是用户态,只能访问cs段寄存器代码段中的用户区【0-3GB】,标志位是0,就是内核态,以系统调用的方式将系统调用号传进寄存器,切换地址空间映射内核页表,查找对应的系统调用函数,访问【3-4GB】
CPL(Current Privilege Level):当前权限级别
0:内核
3:用户
捕捉信号的另一种方法:sigaction()捕捉信号时个我们提供了屏蔽其他信号的能力,包括自己。当信号处理完成时,自动解除屏蔽
可重入函数
某一个函数方法被2个及以上执行流同时进入,这个函数方法就被称为重入函数,分为可重入函数和不可重入函数。
不可重入函数:一般函数体里面调用了malloc free的,malloc也是用全局链表来管理堆的。
调用了标准IO库函数,绝大多数IO库的实现都是以不可重入的方式使用全局数据结构。
可重入函数:使用自己的临时变量,不影响全局。
volatile
不管编译器怎么优化,只要带上volatile关键字修饰的变量,都要保证该变量内存空间的可见性。因为在编译器优化级别较高的情况下,有的变量直接register到寄存器中,不在进行访问其物理内存,内存空间不可见。