文章目录
- 前言
- 铺垫
- 信号
- 信号的产生
- 1、终端按钮产生信号
- 2、调用系统函数向进程发送信号
- 3、软件条件产生信号
- 4、硬件异常产生信号
- 信号的保存
- 补充:位图数据结构
- 信号的处理
- 结语
前言
铺垫
1、日常中我们能经常感受到信号的存在:红灯停绿灯行、三更鸡鸣、妈妈的一通电话,这些都是信号,当我们遇到这些信号的时候,我们都知道接下来要做什么,进程也知道接下来要做什么,因为程序员在设计进程的时候就告诉他了。
2、信号可能随时产生,有可能进程在做优先级更高的事情,因此从进程产生到进程执行有一个时间窗口,同时进程也要有能够记录下这个信号的能力
3、信号的产生对于进程来说是异步的。
4、进程由操作系统通过pcb来管理,在对应进程的pcb里的某个位图结构记录着信号是否发生。
5、正因为是由操作系统维护,因此无论是通过哪种方法,都必须通过OS来写入!
信号
可以使用kill -l命令查看系统定义的信号列表,34号以后是实时信号,暂不讨论。
信号的产生
1、终端按钮产生信号
键盘在按下后会通过硬件单元告知cpu的特定针脚引发硬件中断,某个寄存器会存入9号信息,再通过中断向量表和中断号找到对应的方法,通过驱动程序来读取键盘的数据,再把数据给OS,OS会将其转化成2号信号再写入对应的位图结构,如下图:
2、调用系统函数向进程发送信号
alarm
#include <unistd.h>
功能:计时器
int alarm(int seconds);
参数:
seconds:秒数
返回值:
返回0,如果进程被提前叫醒,就返回上次闹钟剩余的秒数
3、软件条件产生信号
先来学习一个函数:signal,我们知道进程拿到信号后会执行相应的动作,默认一般是退出,可以通过这个函数修改其执行动作。
#include <signal.h>
typedef void(* sighander_t)(int);
sighander_t signal(int signum,sighander_t handler);
作用:
设置某一个信号的默认动作
参数;
signum:要处理的信号类型
handler:函数指针,要设置的对应动作
返回值:
返回信号处理函数指针
#include <signal.h>
#include <sys/types.h>
int kill(pid_t pid, int sig)
作用:控制进程信号
参数:
pid:哪个进程
sig:发送几号信号
返回值:
4、硬件异常产生信号
除0的本质,就是触发硬件异常。
信号的保存
通过上面的四种方式,信号被OS写入到pcb对应的位图结构内,接下来我们再来看一下信号保存的相关细节:
在学习进程控制程序退出和异常的时候我们知道status的低七位是异常信号,低第八位称为core dump位,当时没有解释这一位代表什么,现在说明一下:
程序因信号退出会有两种方式:
1、Term:直接退出
2、Core:会先进行核心转储,再退出
OS可以在该进程异常的时候,核心代码部分进行核心转储,将内存中进程相关数据,全都dump到磁盘中。黑核心转储文件方便在异常后进行调试,可以用ulimit -a来查看是否开启。
像我这台服务器就没有打开,可以使用ulimit -c [size] 来指定核心转储文件的大小,一般云服务器都默认不开启,因为云服务器一般作为生产环境。
言归正传,信号是如何保存在对应的数据结构里的呢?
pending表:位图结构,比特位位置表示哪一个信号,内容表示是否收到该信号
block表: 位图结构,比特位位置表示哪一个信号,内容表示是否阻塞该信号
handler表: 函数指针数组,下标表示信号编号,内容表示该信号传递的动作
补充:位图数据结构
//TODO…
信号的处理
首先我们要先明确一个问题:信号可以被立即处理吗?
信号不是被立即处理的,而是在合适的时候,信号的产生是异步的,当前进程可能正在做更重要的事情,那什么时候合适呢?当进程从内核态切换回用户态的时候,进程会在OS的指导下,进行信号的检测与处理。
这里提到了一个概念:什么是内核态和用户态?
用户态:执行用户写的代码时,进程所处的状态
内核态:执行OS的代码时,进程所处的状态
为什么要有这样的区分呢?又是如何做到这样的区分呢?
在上次讲解进程地址空间的时候,我们知道进程地址空间通过一张 页表
来建立虚拟地址空间和物理内存之间的联系,其实这个页表分为两种:上次介绍的页表是用户级页表,还有一张内核级页表,一个进程的虚拟地址空间由两部分构成,其中0-3G为用户虚拟地址,3-4G是内核的虚拟地址,正式通过这张内核级页表,可以让进程完成系统调用。
总结:
所有的进程都有自己的用户级页表;但所有的进程都可以看到同一张内核级页表,这样就支持所有进程通过统一窗口,看到同一个OS,所以OS的本质都是在进程的地址空间内运行的,因此,系统调用的本质其实就是在自己的地址空间跳转并返回。
那么问题来了,进程如何完成上述跳转呢?进程又是如何被调度的呢?
一、如何完成上述跳转?
实际上在CPU内部有一个叫做CR3的寄存器
3、0分别表征当前运行的进程是用户态、内核态,当然用户是不可以直接更改系统级别的,在系统调用的时候,内部光遇在正式调用逻辑的时候,修改执行级别。
详细流程:
当一个进程的时间片到了或者要进行系统调用的时候,这个进程的状态就会由用户态切换成内核态,在内核态的进程就会开始检查是否有信号需要处理,当检查到有信号需要处理,进程就会再由内核态切换成用户态,完成用户自定义的方法,完成这个方法后,进程应当重新嵌入内核,再调用sys_sigreturn回到用户态执行后续代码。
二、进程是如何被调度的?
不知道大家有没有想过一个问题,每次电脑关机后,断电一段时间,即使不联网也可以获得当前的时间,原因在于位于PC主板CMOS内的时钟硬件!
回到这个问题,首先我们要清楚:进程是由操作系统管理的,操作系统是一个软件,本质是一个死循环,而OS时钟硬件,每隔一段很短的时间就会向OS发送时钟中断,OS就要执行对应的中断方法,然后检查当前进程的时间片,通过系统调用函数:schedule() ;如果发现进程的时间片到了,就将进程对应的上下文进行保存并切换,再选择合适的进程。
结语
以上就是进程间信号的全部内容了,感谢收看,这里是蓝色学者,我们下次再见。