关于操作系统I/O的一些基础知识

1. 操作系统是如何与设备交互的

首先看一下典型系统的架构,如图。其中,CPU通过某种内存总线(memory bus)连接到系统内存。图像或者其他高性能I/O设备通过常规的I/O总线(I/O bus)连接到系统。最后,更下面是外围总线外围总线将最慢的设备连接到系统,包括磁盘、鼠标及其他类似设备。

对于操作系统来说,它要连接设备,并控制设备。对于设备来说,一方面它要有具体的机制实现自己的功能(比如打印机要实现本身打印的功能),另一方面它要提供接口供操作系统调用(不然无法使用它的功能呀),同软件一样,硬件也需要一些接口,让系统软件来控制它的操作。因此,所有设备都有自己的特定接口和交互协议。

有了接口之后,操作系统是如何控制设备的行为的呢?如上图所示,一个(简化的)设备接口包含3个寄存器:状态寄存器、命令寄存器、数据寄存器。通过读写这些寄存器,操作系统可以控制设备的行为。

  • 状态(status)寄存器:通过它可以读取并查看设备的当前状态;
  • 命令(command)寄存器:操作系统将命令写入该寄存器,用于通知设备执行某个具体任务;
  • 数据(data)寄存器:将数据传给设备或从设备接收数据。

操作系统与设备交互的交互过程又是如何的呢?

  • 第1步,操作系统通过反复读取状态寄存器,等待设备进入可以接收命令的就绪状态。我们称之为轮询(polling)设备(就是问它正在干嘛)。
  • 第2步,操作系统下发数据到数据寄存器。
  • 第3步,操作系统将命令写入命令寄存器;这样设备就知道数据已经准备好了(已经写入到它的数据寄存器了),它应该开始执行命令。
  • 第4步,操作系统再次通过不断轮询设备,等待并判断设备是否执行完成命令(有可能得到一个指示成功或失败的错误码)。

这个过程的好处是足够简单并且有效。但是低效。导致低效的原因就是轮询过程,在等待设备执行完成命令时浪费大量CPU时间,如果此时操作系统可以切换执行下一个就绪进程,就可以大大提高CPU的利用率。但是如果不轮询的话,操作系统如何能知道设备何时就绪(空闲)呢?一种方法是让轮询的间隔大一些,但是这样会造成数据丢失。比如有的设备要求你快速取走数据,否则会被后面来的新数据覆盖掉。

那么现在要思考的问题就是,如何减少轮询开销。操作系统检查设备状态时如何避免频繁轮询,从而降低管理设备的CPU开销

2. 利用中断减少CPU开销

多年前,工程师们发明了中断(interrupt)来减少CPU开销。有了中断后,CPU 不再需要不断轮询设备,而是向设备发出一个请求,然后就可以让对应进程睡眠,并切换执行其他任务。当设备完成了自身操作,会抛出一个硬件中断,引发CPU跳转执行操作系统预先定义好的中断处理程序(interrupt handler)。中断处理程序是一小段操作系统代码,它会执行必要的处理,并且唤醒等待I/O的进程继续执行。

我们再站在CPU的角度阐述一下中断机制。CPU有一条线,称作中断请求线(IRL)。CPU在执行完每条指令后,都会检测IRL。当CPU检测到有设备已在IRL发出了一个中断信号时,CPU执行状态保存并跳到内存中的固定位置的中断处理程序。中断处理程序确定中断原因,执行必要的处理。

因此,中断允许计算与I/O重叠,这是提高CPU利用率的关键。从图中直观地看一下。

其中,进程1在CPU上运行一段时间(对应CPU那一行上重复的1),然后发出一个读取数据的I/O请求给磁盘。如果没有中断,那么操作系统就会简单自旋,不断轮询设备状态,直到设备完成I/O操作(对应其中的p)。当设备完成请求的操作后,进程1又可以继续运行。

有了中断机制后,在磁盘处理进程1的请求时,操作系统在CPU上运行进程2。磁盘处理完成后,触发一个中断,然后操作系统唤醒进程1继续运行。这样,在这段时间,无论CPU还是磁盘都可以有效地利用。

注意,使用中断并非总是最佳方案。假如有一个非常高性能的设备,它处理请求很快(通常在CPU第一次轮询时就可以返回结果)。此时如果使用中断,反而会使系统变慢,因为要切换到其他进程,处理中断,再切换回之前的进程代价不小。因此,如果设备非常快,那么最好的办法反而是轮询。如果设备比较慢,那么采用允许发生重叠的中断更好。如果设备的速度未知,或者时快时慢,可以考虑使用混合策略,先尝试轮询一小段时间,如果设备没有完成操作,此时再使用中断。这种两阶段的办法时一种折中,可以实现两种方法的好处。

3. DMA(直接存储器访问,Direct Memory Access)

首先介绍一下PIO(Programmed I/O),指的是由CPU实现设备和内存之间的数据传送。CPU会因为这些琐碎的任务而变得负载很重,浪费了时间和算力。

如上图,进程1在运行过程中需要向磁盘写一些数据,所以它开始进行I/O操作,将数据从内存拷贝到磁盘(其中标示c的过程)。拷贝结束后,磁盘上的I/O操作开始执行,此时CPU才可以处理其他请求。可见,使用PIO的方式,CPU的时间会浪费在向设备传输数据或从设备传出数据的过程中。如何才能分离这项工作,从而提高CPU的利用率?

解决方案就是使用DMA。DMA引擎是系统中的一个特殊设备,它可以协调完成内存和设备间的数据传递,不需要CPU介入

DMA工作过程如下。为了能够将数据传送给设备,操作系统会通过编程告诉DMA引擎数据在内存的位置,要拷贝的大小以及要拷贝到哪个设备。在此之后,操作系统就可以处理其他请求了。当DMA的任务完成后,DMA控制器会抛出一个中断来告诉操作系统自己已经完成数据传输。如下图,可以看到,数据的拷贝工作都是由DMA控制器来完成的,此时空闲的CPU做一些其他事情。

4. 设备驱动程序

每个设备都有非常具体的接口,如何将它们纳入操作系统,并向操作系统隐藏细节。即如何实现一个设备无关的操作系统,如何保持操作系统的大部分与设备无关,从而对操作系统的主要子系统隐藏设备交互的细节?

这个问题可以通过古老的抽象技术来解决。在最底层,操作系统的一部分软件清楚地知道设备如何工作,我们将这部分软件称为设备驱动程序(device driver),所有设备交互的细节都封装在其中。

有趣的是,因为所有需要插入系统的设备都需要安装对应的驱动程序,所以久而久之,驱动程序的代码在整个内核代码中的占比越来越大。查看Linux内核代码会发现,超过70%的代码都是各种驱动程序。在Windows系统中,这样的比例同样很高。因此,如果有人跟你说操作系统包含上百万行代码,实际的意思是包含上百万行驱动程序代码。当然,任何安装进操作系统的驱动程序,大部分默认都不是激活状态(只有一小部分设备是在系统刚开启时就需要连接)。更加令人沮丧的是,因为驱动程序的开发者大部分是“业余的”(不是全职内核开发者),所以他们更容易写出缺陷,因此这些开发者是内核崩溃的主要贡献者。

参考书籍

《操作系统导论》雷姆兹·H.阿帕希杜塞尔、安德莉亚·C.阿帕希杜塞尔

《操作系统概念精要》亚伯拉罕·西尔伯沙茨等

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值