43-打通你的任督二脉-信号处理函数的执行期



可能有些同学会对信号处理函数是如何被操作系统调用的十分感兴趣,当然也有一部分同学不感兴趣的你可以跳过此篇啦。

本文可能会涉及一些内核,x86 保护模式相关的知识,所以要求还是蛮高。不过我会尽量用最朴素的语言讲清楚。

在这之前,有些概念需要简单的提一下。

1. 系统调用

当你使用系统调用的时候,一般就意味着你要进入操作系统内核了。比如 write 函数,read 函数,再比如 sigaction 函数等等。

基本上你调用大多数系统调用的时候,都会发生进程或线程调度(有点扯太远)。所谓的调度就是进入某些系统调用后,因为某种原因(^_^)你的进程就暂停了,CPU 转而执行另一个进程(这只是调度的一种,另外一种是因为时间片耗尽而发生调度)。

当你的系统调用请求的资源已经完成的时候(也可能还没完成,比如被信号打断),CPU 就会有机会回来再次执行你的程序了。

假设系统里只有 2 个进程,那么就有可能会是这样的:


这里写图片描述
图1 执行系统调用后,由于阻塞主动让出 cpu

进程 A 在执行系统调用后比如 read,因为发现想要的资源还没到来(用户迟迟不输入数据),于是进程 A 执行调度程序,主动让出了 CPU,切换到了进程B。(有些同学可能觉得是被一个称作调度进程的程序给切换走了,实际上这是不对的)。

2. 系统调用如何进退内核

系统调用通常是通过 cpu 提供的指令进入内核的。这需要 cpu 硬件的支持!

在老的系统中,x86 cpu 是通过 int 中断指令进入内核态的(这个 int 不是 c 语言里那个 int 整型,而是 cpu 汇编指令)。内核态,指的是 cpu 的状态,不是操作系统的状态(不过有时候我不区分这个),处于内核态的 cpu 可以执行一切特权指令,读写任意内存(说任意有点夸张,起码要读写的地方应该被挂上物理页才行)。比如 linux 系统是通过 int 0x80 中断指令让 cpu 变成内核态。

现代操作系统都使用了 x86 的另一个指令称为 sysenter 指令进入内核,该指令的速度比中断指令更快。

介绍了进入内核,当然要谈谈如何退出内核。与 int 中断指令对应的是,必须使用 iret 指令让 cpu 退出内核态,进入用户态。与 sysenter 指令对应的退出指令是 sysexit.

好了,有关这些知识不细讲,如果你对这些知识点的细节很感兴趣,请参数另一篇笔记《os 学习笔记》

3. 信号处理函数是如何被调用的

请允许我直接上图。同样假设系统里只有两个进程。该执行流程参考了 linux 0.11 内核代码(高版本的linux实现可能与此有些出入,但是大同小异。简单的掌握了,才有信心去研究高版本源码是不?)。


这里写图片描述
图2 信号处理函数是如何被调用的

下面具体分析下执行流程:

  • 进程 A 执行到 read 里的时候(题外话,本质上 read 函数只是个外壳而已,真正的系统调用是 sys_read,这已经位于内核了),然后执行了中断指令 int 0x80,进入到了内核态。

  • 接下来执行真正的系统调用 sys_read,由于请求的资源没有到来,sys_read 调用了 schedule 函数,这是真正的进程调度函数。进程调度函数执行到某个位置后,找到了下一个要运行的进程(进程 B),然后切换进程上下文,转而执行进程 B。

  • 进程 B 运行到某个位置,也发生了和进程 A 一样的遭遇,也调用了某个系统调用而阻塞而切换到了进程 A

注意:如果进程 A 的资源没有准备好,同时进程 A没有收到信号是不会被调度到的。只要进程 A 收到信号了,同时该信号未被阻塞,进程 A 就会被调度到,资源没有准备好也没关系。

  • 当再次回到进程 A 的时候,进程 A 接着从 schedule 函数切到进程 B 那行代码的下一行继续执行,完成 sys_read 的功能。sys_read 完成后,准备退出内核态。

  • 在退出内核态前,会执行 do_signal 函数检查是否有信号,如果有信号 do_signal 会找到信号对应的信号处理函数(你使用 signal 或者 sigaction 注册的)。

  • 接下来执行 do_signal

    • 如果进程没有收到信号,do_signal 函数直接返回,执行 iret 后直接返回到 read 函数。图中没有画线条,因为这种情况比较简单。

    • 执行如果进程收到了信号,do_signal 函数会修改用户栈,为信号处理函数创建一个函数执行现场,这将导致系统调用在执行 iret 函数的时候不是返回到 read 函数,而是返回到信号处理函数!!!

  • do_signal 函数创建完对应的信号处理函数的执行现场后,执行 iret,返回到了信号处理函数。信号处理函数执行完后,才返回到 read 函数。

  • 接下来,read 函数返回。

实际上,系统进程内核态,除了执行系统调用,也可以通过时钟中断进入内核。只不过这种方式是被动的。进入时钟中断后,会检查进程时间片是否用完,如果用完,照样会执行 schedule 调度程序转移到别的进程。

4. 总结

  • 理解系统调用是什么
  • 理解进程是如何被调度的
  • 理解信号处理函数是何时被执行的

题外话,schedule 函数就是进程或者线程的调度算法,在 linux 0.11 中,linus 大神仅用约30 行左右的代码完成了它,堪称经典,有兴趣的小伙伴可以研究研究它^_^。

猜你喜欢

转载自blog.csdn.net/weixin_38054045/article/details/80898195