Pulpcode

捕获,搅碎,拼接,吞咽

0%

一次完整的阻塞调用到底是怎样的

作为一个程序员,你一定知道这个简单的模型,你想从文件中读取数据,你需要调用read函数,然后read函数会阻塞直到有数据返回给你,之后你就可以进行下一步操作了。一个简单的模型,就像是一个远景画,而你是否了解细节,在于当你把镜头拉近的时候,你能看到的是清晰的细节还是一堆马赛克。

所以在这个基础之上,你可能理解的比这更复杂一些,你知道有些资源只能在核心态才能被访问。知道进程缓冲区和内核缓冲区。那么这个模型在你眼里就变成了这个样子:

blocking-io

也就是说在系统调用中,内核先把数据从硬盘读取到了内核缓冲区,然后又从内核缓冲区读到用户进程自己的进程缓冲区,最后才返回给用户程序控制权,整个操作就像是一个耗时的系统调用一样。

而且《深入理解计算机系统》中,介绍过异常机制是“你可以停下手头的工作去干另一件事情”的基础。并提到了一种叫“陷阱”(trap)的异常。为用户程序和内核之间提供了一个像过程一样的接口,这就是系统调用。用于那些读取文件,创建进程的操作。系统需要切换到内核模式,才能执行这些受限制的操作。

而关键就在于,这种操作可以让你停下手中的事(保护当前的上下文),将控制权转移,并在调用结束后,再次获得执行点,执行下一条程序。

trap

以上那个模型是针对当前进程进行系统调用的,那么操作系统是如何控制磁盘读取文件的,而文件在读取成功之后,又是如何通知操作系统,文件已经准备好了?

首先对于文件读写这个模型,cpu并不是直接控制读取,而是通过DMA控制读取的。

以下是从wiki中查到有关DMA的内容。

DMA意为”存储器直接访问“ ,是指外部设备不通过CPU而直接与系统内存交换数据的接口技术。要把外设的数据读入内存或把内存的数据传送到外设,一般都要通过CPU控制完成,如CPU程序查询或中断方式。利用中断进行数据传送,可以大大提高CPU的利用率。 但是采用中断传送有它的缺点,对于一个高速I/O设备,以及批量交换数据的情况,只能采用DMA方式,才能解决效率和速度问题。DMA在外设与内存间直接进行数据交换,而不通过CPU,这样数据传送的速度就取决于存储器和外设的工作速度。
整个数据传输操作在一个称为“DMA控制器”的控制下进行的。CPU除了在数据传输开始和结束时作一点处理外,在传输过程中CPU可以进行其它的工作。这样在大部分时间里,CPU和输入输出都处在并行操作。因此,使整个计算机系统的效率大大提高

那么简单的说,其实内存也有一个属于自己的小控制器(cpu)来控制读取,然后在读取完成后,向真正的cpu发起一个中断。

前面提到系统调用是一种异常控制流(陷阱trap),而cpu中断,也是一种异常控制流,而且是唯一一种异步的异常。之所以异步的异常,是因为你并不知道这个异常是什么时候出发的,或者说,你并没有维护一个上下文,去准备迎接这个异常,其它同步的异常都是你主动发起的中断。所以cpu在收到这个中断的时候,会停止当前进程的执行,当然,其实cpu并没有线程进程的概念,是操作系统负责将当前的上下文保存,也就是说当前运行的进程被冻结了,之后CPU开始执行内核中的中断处理程序。这些处理程序和中断号一一映射,在内核启动的时候,就被加载进来了。

看上去是没问题,但如果cpu在处理中断的时候,又来了别的中断,那么cpu要怎么办?

其实中断的模型比我们之前提到的要复杂一点。

trap

在设备和cpu之间有一种被称为中断控制器的东西。来对多个设备的中断,进行采样,分发和排队。而且可以对中断控制器进行关中断,来让中断控制器不要给CPU发送中断信号。所以在cpu处理此中断的时候,直到cpu主动对中断控制器发送EOI操作,才会容许下一个中断信号去激发CPU。

那么cpu在执行中断处理程序的时候很慢怎么办?这样别的中断信号不就不能被CPU及时处理了么?

实际上设备中断CPU的过程分为两部分,硬中断和软中断,上半部和下半部。硬中断就是执行关键步骤到给中断控制器发送EOI的过程,剩余的中断处理过程就是软中断,而这些软中断的程序和普通程序的执行就没什么两样了。

然后呢,如何通知进程?它怎么知道是哪个进程的数据准备好了,还有阻塞的进程用什么接受通知?这个通知是指操作系统的什么操作?进程又是如何被唤醒的?

之前已经说了,CPU并不知道线程,进程,更不知道是哪个进程的数据准备好了。这一切都是操作系统做的。
操作系统会在内核维护许多队列,而因中断而被保存挂起的进程,会根据中断类型,加入到对应的队列中,比如等待IO的进程就加入到IO队列。

同时,在注册中断处理函数的时候,此函数就已经有了进程的信息,所以自然也知道在完事后,向哪个进程发起通知了。还要注意的是,多个进程对同一文件进行读写的时候,不同的设备,处理方式是不同的,比如有的设备,同一时间只允许注册一个中断处理函数。

所以中断处理函数,会在完成后向进程发送信号,唤醒进程。具体是什么信号我并没有查到,不过我觉得很可能是 SIGCONT。

当然,仅仅是被唤醒(变为就绪态)而已,被唤醒的进程到底会不会马上执行,那就要看调度算法了,不过一般来说, Linux会把因为IO挂起的进程优先级稍微调高一点让它们立即抢占进来,提高IO效率。

那这次我就到此为止了,如果再放大,还有更多没想过的问题。不过现在这个模型的理解,对我来说足够了。