当前位置: 首页 > news >正文

Linux——信号(2)信号保存与捕捉

一、信号的保存

上次我们说到,捕捉一个信号后有三种处理方式:默认、忽略、自定义,其中自定义我们用signal系统调用完成,至于忽略信号,也需要signal实现,比如我现在想忽略2号信号,则:
 

::signal(2,SIG_IGN);  (::表示系统调用)

忽略本身就是信号捕捉的一种方法,动作是忽略。

恢复某种信号的默认行为 :  ::signal(num,SIG_DFL);

二、信号相关的概念

1.信号递达:实际执行信号的处理动作

2.信号未决:信号从产生到递达之间的状态

3.进程可以选择阻塞某个信号,若该信号产生,一定会把信号进行未决状态,永远不递达,除非我们解除阻塞,阻塞和忽略是不同的,忽略是信号已经递达的一种方式。

三、PCB中信号的表

上次我们提到,PCB中有一个记录信号的表(位图),每一个比特位记录着对应的信号,这个表叫pending,除此之外还有两个表handler和block,handler就是记录每一个信号的递达方法,是一个函数指针数组,每一个下标代表着每一个信号的处理方式。

至于block表,也是一个位图,比特位的位置代表着信号编号,但内容表示当前信号是否被阻塞/屏蔽。

四、sigset_t

从图中来看,每个信号只有一个bit位的未决标志,阻塞标志也是如此,不记录该信号产生多少次,因此,未决和阻塞标志可以用相同数据类型sigset_t存储,称为信号集,阻塞信号集也称当前进程的信号屏蔽字。

有了这个数据类型,我们就可以用相关接口实现对信号表的增删查改

五、信号的修改函数

1.sigprocmask

这个函数是用来修改block表的

第一个参数是用来传宏的:

SIG_BLOCK:表示在block表中添加对某信号的屏蔽

SIG_UNBLOCK:表示在block表中解除对某信号的屏蔽

SIG_SETMASK:覆盖掉之前所标记(屏蔽)的信号,并标记新的信号状态。

第二个参数就是传指定进程的block集

第三个参数是输出型参数,用于信号状态的恢复。

2.sigpending

用于获取当前进程的pending表,因此也是输出型参数

六、信号的捕捉过程

上图就是大致的信号自定义捕捉的过程,其中,上半部分是用户态,下半部分是内核态,那什么是用户态和内核态?

七、用户态与内核态

1.补充内容

(1)硬件中断

当我们从外部访问操作系统时,其实并不能让操作系统定期轮询去访问这些外设的状态,所以真实的情况是这样的:
就以键盘为例,当我们的按键按下了,键盘的外设就会发起中断,实际上我们的外设并没有直接连接CPU,而是连接到了一个叫中断控制器的东西,当收到中断时,它就会知道是哪个设备发出并知道中断号,然后控制器通知CPU发生中断,此时CPU就可以获得对应的中断号。而为了处理这些设备,操作系统在设计的时候有一个东西叫中断向量表,我们就当成函数指针数组,这些下标就是中断号。所以中断号对应的中断方法都是设计好的。但此时CPU可能在执行某些进程,因收到中断,它就会把进程的相关数据放在相关寄存器内形成中断的上下文,此过程我们称CPU现场保护。然后CPU就有空间来处理这个中断:根据中断号来查表并实行对应方法——中断处理例程。

所以,操作系统就不用轮询检测外设,只要外设有需求直接随时把中断交给控制器即可。

(2)时钟中断

我们发现,任何进程都在操作系统的指挥下调度、执行,但是,谁来控制的操作系统?

实际上,我们的外设部分还有一个东西——时间源,它就像闹钟一样,每隔一段时间会像外设一样发送中断(只不过间隔的时间非常非常短),而且它也有自己的中断号和对应的方法,它的对应方法就是进程调度!但调度不意味着切换进程,还记得我们讲过的时间片,每个进程都有自己的时间片,到了就切换程序,现在看来,这个时间片更像是一个计数器,然后时间源每发送一次中断计数器则--,每减一次相当于一次调度,当减到0时才进行进程切换。

所以,操作系统就可以在硬件的推动下自己调度!因此实质上,操作系统可以进行躺平,只要想实现什么功能直接加到中断向量表中即可,我们也可以把操作系统理解为一个死循环。

(3)软中断

上面的中断的触发,都是靠硬件设备才能发生。那有没有靠软件发生的中断呢?有!

为了让OS支持进行系统调用,CPU设计了对应的汇编指令(int或syscall),可以让CPU内部触发中断。这便是软中断。

在中断向量表中我们也发现了有对应软中断的方法,其中在其处理的内部有一个系统调用表,里面记录着各种系统调用,而我们实际调用他们本质是通过数组下标!但是我们用户是如何把系统调用号给操作系统的?放在寄存器,然后通过int 0x80,syscall(触发软中断)陷入内核,然后CPU就会自动执行系统调用的处理方法,并用系统调用号依据系统调用表来找到具体的系统调用。(决定系统调用号的就是我们传的参数,每个调用传什么参数以及约定好了)

所以,系统调用接口,并不是C语言的函数,而是系统调用号和约定的参数+内部的表和陷入内核触发的中断以及返回值的寄存器共同组成的!

但我们用的系统调用往往都有返回值,这些返回值是通过寄存器或用户传入的缓冲区地址来返回给我们的。

既然系统调用是靠中断完成,那么一定是靠汇编语言,但我们实际用的调用貌似没有那么麻烦,这是因为glibc把这些系统调用进行了封装,也就是我们一直使用的都是C语言封装的系统调用(但其本身并和C无关)。

我们之前提到过的缺页中断、内存碎片处理、除0、野指针等错误,全都会转化成软中断,然后传递中断号,执行对应的解决方法。包括我们的虚拟地址空间也会触发中断,解决方法就是申请对应的物理内存。

因此,操作系统就是躺在中断处理例程上的代码块! CPU内部的软中断,比如int0x80或者syscall,我们叫做陷阱 。CPU内部的软中断,比如除零/野指针等,我们叫做异常。

2.回归正题

这是我们的虚拟地址空间,分为了用户页表和内核页表,其中用户页表每个进程都有一份,但内核页表整个操作系统只有一份,所以无论如何切换进程,都可以找到同一个操作系统。所以OS系统调用的执行,就是在进程的地址空间中执行的。

简单来讲就是,用户态就是指我们在访问自己的代码时所处的状态,内核态就是CPU经过系统调用访问操作系统时的状态。

八、信号的捕捉操作

1.sigaction

这个接口和我们的signal类似以下是演示代码

void handler(int signo)
{std::cout<<"新的处理方法"<<std::endl;exit(1);
}int main()
{struct sigaction act,oact;act.sa_handler= handler;::sigaction(2,&act,&oact);
}

在处理信号的期间,是有可能进行陷入内核的,所以在此期间如果我们再次传该信号时就会形成嵌套递归,而OS是不允许信号处理方法嵌套的,因此当进行处理信号时会把对应信号的block表置为1,等到信号处理完会自动解除。

相关文章:

  • CompletableFuture并行处理任务
  • 《MySQL:MySQL表的基本查询操作CRUD》
  • ros2 humble moveit调试笔记
  • docker基本命令1
  • Day-1 漏洞攻击实战
  • QT:Qt5 串口模块 (QSerialPort) 在 VS2015 中正确关闭串口避免被占用
  • 推荐系统/业务,相关知识/概念1
  • Sentinel源码—7.参数限流和注解的实现一
  • 如何在白平衡标定种构建不同类型的白平衡色温坐标系
  • 基于语义网络表示的不确定性推理
  • 从 0 到 1 转型 AI:突破技术壁垒的 5 大核心策略与实战路径
  • RK3588上编译opencv 及基于c++实现图像的读入
  • Java写数据结构:栈
  • Nebula图数据库
  • 富诺健康旗下运动营养品牌力爆(LIPOW):以冠军精神定义运动营养新时代
  • 论文分享:【2024 CVPR】Vision-and-Language Navigation via Causal Learning
  • NLTK 基础入门:用 Python 解锁自然语言处理
  • Redis 的单线程模型对微服务意味着什么?需要注意哪些潜在瓶颈?
  • Ansys-FLUENT-笔记1
  • yum如果备份已经安装的软件?
  • 专家学者视角下的乡村教育:目标与出路并非“走出大山”
  • 世界读书日丨这50本书,商务印书馆推荐给教师
  • 服务业扩大开放,金融、医疗等多领域明确155项试点任务
  • 纪念沈渭滨︱在恩师沈渭滨老师指导下走上学术研究之路
  • 海南一男子涨潮时赶海与同伴走散,警民协同3小时将其救上岸
  • 纪念沈渭滨︱志于道而游曳于士和氓间的晚年沈先生