0


linux信号 | 学习信号四步走 | 透析信号是如何被处理的?

    **前言:**本节内容讲述linux信号的捕捉。 我们通过前面的学习, 已经学习了信号的概念, 信号的产生, 信号的保存。 只剩下信号的处理。 而信号的处理我们应该着重注意的是第三种处理方式——信号的捕捉。 也就是说, 这篇文章其实最核心的部分就是在解释信号的捕捉。 但是大的篇幅还是在前面的预备知识上面。现在, 开始我们的学习吧!

** ps: 本节内容适合了解信号概念, 信号产生, 信号保存的友友们观看。**

信号是什么时候被处理的呢?

    我们要处理一个信号, **首先**是我们自己**知道自己收到信号**了。 那么知道自己收到信号了,** 进程**就必须得**在合适的时候查一查**我们对应的**pending位图, block位图以及handler数组**。 而这**三个表**都属于**内核数据结构**,** 进程**不会, 也**不能直接访问**他们。  而**解决方法**就**是让进程处于**一种**内核状态**。 当**进程从内核态转回用户态**的时候就会**对信号做检测并处理**。
    关于**内核态和用户态, 是我们从学习linux到现在, 第一次听到的名词**。一般我们自己进程在进行的时候, **cpu**在**进行调度的时候不仅仅运行我们自己写的代码**。 比如我们的代码里还有系统调用或者库函数调用。 对应着就是我们自己写的代码, 库自己写的代码, 操作系统曾经写的代码。 ——这些cpu都要跑。 就比如我们进程等待wait函数, 我们创建子进程的fork, 我们打印数据printf。 这些操作我们都没有自己进行, 都是通过系统调用或者库函数直接进行调用。 
    那么, 我们的操作系统既然不相信我们的用户, 所以在很多场景下, 它是需要我们的用户做一下相关的身份切换的, 才允许我们执行对应的代码。 **一般我们在执行库函数的时候, 执行我们自己的代码的时候, 一般都是在用户态直接执行的**。 **还有一种是进程在cpu调度之下, 要陷入到操作系统内部, 执行对应的任务——最典型的就是在调用系统调用的时候**。——这里要知道的是, 当我们**调用系统调用的时候, 操作系统会自动把我们的用户的身份进行转变, 转变为内核身份**。 然后**由操作系统帮我们把函数执行完毕**。 当**返回时, 再把我们的身份从内核身份转化为用户身份。 此时才能够允许我们执行系统调用**。 ——这里我们只需要知道, **系统调用除了调用函数, 也是需要进行身份变换的。 这里的身份就两个, 一个是用户态, 一个是内核态。 **

** 也就是说, 调用系统调用, 操作系统是自动会做身份切换的。 (从用户到内核, 或者从内核到用户)**

重谈地址空间

    我们其实之前学到的进程相关的知识, 其实都是和我们上图中的用户空间相关联(0~3GB), 但是在**3~4GB**, **还有一个内核地址空间**。**这个内核地址空间其实映射的就是操作系统的代码和数据**。 而且**因为操作系统是最先被加载进进程的程序, 所以它的代码和数据应该在内存中的较为底部的位置**。 同时**映射的时候需要有一个内核级页表进行映射**。 

    那么这里问题就来了, 如果我们的系统中有很多进程的话, 用户页表有几份呢 ? 内核级页表有几份呢 ?
  • ** 对于用户级页表: 有几个进程就有几份用户级页表, 因为进程具有独立性。 **
  • ** 对于内核级页表: 有一份。**
    所以, 我们的每一个进程, 看到的都是3 ~ 4GB内核空间里面的内容, 看到的内核级页表里面的内容, 看到的物理内存里面的操作系统的代码和数据的内容都是一样的!!

    要知道, 我们的各种系统调用的方法, 比如write, read, wait, fork, sigset_t等等都是在内核空间的。 

    所以, 以后**我们进程在代码区进行系统调用的时候, 就直接找自己内核地址空间里面的系统方法, 然后返回到代码区。**

    就如同在自己的地址空间里面直接调用
  • **——在进程的视角: 我们调用系统当中的方法, 就是在我们自己的地址空间中进行执行的。 **
  • **——在OS的视角: 在任何一个时刻, 都有进程在执行。 我们想执行操作系统的代码, 就可以随时执行。 **

操作系统的执行逻辑

    **执行逻辑就是——基于时钟中断的一个死循环。 **
    在我们计算机里面都有一个芯片单元, **每隔很短很短的时间, 向计算机发送时钟中断**。** 接收**到所谓的**时钟中断**后, 就要**执行中断所对应的方法**, 也就**是操作系统的代码**。 也就是说我们的计算机内部有一个芯片单元, 它每隔很短的时间就给cpu发送一次时钟中断, 这个就叫做一次滴答。 然后我们操作系统, 就被动的被时钟中断推着向后执行。 

    然后操作系统将前期的工作做完之后, 往后的过程中就是执行一个死循环, 在死循环中检测有没有时钟到来, 如果有时钟到来, cpu就去执行时钟中断对应的方法。具体的操作系统如何做的, 看下图:

    ![](https://i-blog.csdnimg.cn/direct/4389a2b024fa4272a5c398c6068f545a.png)

这张图里面有些字段已经被博主标记出来了, 但是有些字段涉及的内容太多, 无法全部截图, 所以这里直接叙述:

    首先说这个**for(; ;) pause(); **这个是一个**死循环**,说明**操作系统执行到这里后就什么都没干, 一直执行一个pause()**。 **pause的意思是暂停, 为什么暂停? 因为操作系统往后的动作, 都靠着时钟中断来驱动, 也就是说, 有外设中断,操作系统就去执行对应的中断。 **

    也就是说, 操作系统最后会卡在for(; ;) pause();  什么都不去做, 一直暂停在这里相应中断。 而我们的操作系统响应中断, 就一直在执行相应的调用。 所以, 我们的硬件一直在推着操作系统在走。 所以, 我们的操作系统才一直推着我们的进程在走。 所以我们的代码才得以推进。 

** ps:我们的计算机天然就有计算时间的能力, 就比如我们的台式电脑即便断电, 再次开机的时间也能够正确。 这是因为计算机内部可以续电, 可能是纽扣电池, 然后我们计算机能够通过一种硬件单元进行时间的计算。 **

    然后要着重说一下的就是这个调度程序初始化:

    这个timer_interrupt, 如果没有中断就绪, 就会执行这个。 这个东西是用来相应每隔很小时间发来的中断的, 比如执行或者调度等操作。 

cpu与用户态和内核态

我们说过, 么一个当前正在调度的进程, 对应的这个进程有自己的页表。 并且, cpu中有一个CR3寄存器, 这个寄存器直接指向当前进程所对应的用户级页表

    同时有一个**ECS寄存器**。 我们**怎么知道我们当前是用户态还是内核态呢? 进程**如果在**执行用户的代码**的时候, **ECS一定是指向用户态**。 **进程**如果在**执行系统的代码**的时候, **ECS**一定是**指向内核态**。 关键在于, 在**ECS寄存器**里面,** 最后有着两个比特位。 这两个比特位有四种表示方式:00、01、10、11**. linux内核中, 对于cpu常见的有**两种工作模式**, 一种叫做**内核态00**, 一种叫做**用户态01**。 所以如果今天想访问内核态对应的代码, 必须想办法将ECS的低两位由三置为0. 那么谁能做到呢? ——cpu必须给我们提供一个方法, 能够修改自己的工作状态, 即**int 80**陷入内核。
  • ** 内核态: 允许访问操作系统的代码和数据。 **
  • ** 用户态: 只能访问用户自己的代码和数据。**

捕捉过程

要理解信号的捕捉过程, 我们可以先看下面这张图。 接下来就是对这张图进行解释:

  •     首先, 信号的处理必须是操作系统要先检测到信号。 那么这个检测的时机就是上面图中的红圈圈。 上面这张图是一个循环的, 但是进程的开始要从绿色的位置执行。 然后一开始在用户态, 在用户态执行main函数的时候, 遇到异常或者其他触发信号的调用, 就进入内核态。 也就是经过第一个蓝色圈圈, 到了下层。 然后操作系统就进行检测。 
    
  •     现在谈一谈这个检测:做检测, 首先要遍历pending列表。 看看有没有信号,再看看有没有block。 没有就正常执行, 有的话就不做处理, 继续看下一个。 直到遇到1, 并且block没有被阻塞就执行信号动作。那么此时如果这个信号是DFL,就是默认动作, 该怎么样就怎么样。 如果是IGN, 就是忽略, 什么都不做。 并且在执行这个信号动作之前将信号的pending由零变成1。 这两种都是直接在内核态执行的。
    
  •     但是问题来了, 如果不是忽略或者默认, 而是自定义动作呢? ——要知道, 我们的自定义动作是用户代码,执行它就需要进入用户态。 那么我们为什么要使用用户态而不直接使用内核态呢? 因为如果我们调用的是非法代码。 盗取了我们计算机内的重要信息, 此时就会出现危险。 所以我们必须返回用户态。 然后从用户态处理完之后, 就会原路返回, 也就是利用sigreturn系统调用回归内核态, 再利用sys_sigreturn返回main。 
    
  •     那么进程我们知道是会被调度的, 只要他再跑, cpu就会调度它的进程。 只要需要调度, 时间片必然会消耗完毕。 消耗完毕就必然将时间片从cpu上剥离下来, 当下次调用进程的时候, 必然要把进程的各种队列放到寄存器里面跑, 而这个过程中, 见数据恢复到cpu中的过程一定在内核态, 而开始执行进程代码时一定是在用户态。 所以, 在进程调度的时候, 有无数次机会从进程用户态到内核态, 从内核态到用户态。
    

——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!

标签: linux 服务器 后端

本文转载自: https://blog.csdn.net/strive_mianyang/article/details/142718336
版权归原作者 打鱼又晒网 所有, 如有侵权,请联系我们删除。

“linux信号 | 学习信号四步走 | 透析信号是如何被处理的?”的评论:

还没有评论