最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

输入输出.

运维笔记admin15浏览0评论

输入输出.

输入输出.

什么是输入输出

计算机归根结底是为人类服务的,这就要求计算机必须提供某种机制使得人可以向计算机发出命令或操纵计算机。也就说计算机与人之间必须存在某种沟通的机制。 这种沟通的机制就是计算机的输入输出机制。

输入提供的是一个“人→计算机”的通道。即人或外部世界通过输人向计算机发出命令或提供数据。输出提供的则是相反方向的通道,即“计算机→人”的通道。计算机通过这个通道向人或外部世界输出自己的计算结果,包括对其他设备的控制操纵命令。

显然,输入输出的存在才使得计算机的存在有了意义。就像一个人,如果没有输入输出,即他不能与外部世界打交道,则这个人通常被认为是痴呆或白痴。即使这个人实际上是一个天才,情况也是如此。同样,一台没有输人输出的计算机,不管其运算功能多么强大,也是废铁一堆(对于计算机外的世界,或者人类来说)。由此,输入输出也就成为操纵系统设计时的一个重要考虑。

对于操作系统设计人员来说,从高层设计来看,关于输入输出我们要问的问题有两个:

  • 输人输出要达到什么目的?
  • 操作系统是如何实现输人输出功能的?

输入输出的目的

输入输出的目的,从简单来说,就是提供一个人机交互的通道,使得人与计算机能够进行沟通。但这是抽象的层次。具体来说,输入输出的目的是什么呢?

我们在前面讲述进程、内存和文件时说过,操作系统是一个魔术师和管理者。对于输入输出这部分功能来说,也不例外。操纵系统要管理的自然是输入输出设备
而魔幻则是提供一个统一的界面来屏蔽输入输出设备的差异,使得数据的表示能够在不同设备之间相互转换而无需用户的操心。那么到这里,我们就可以得出操作系统输入输出的目的是,

  • 屏敝输入输出设备的差异
  • 在不同设计之间进行数据表示的转换

达到上述目的需要的机制,仿照我们前面的模式,是:设备独立与设备保护。

这里的设备独立指的是输入输出不以设备的不同而转移,即不管输入输出设备是否更好或更新,我们进行输入输出的模式和方法保持不变。而设备保护则是一个输入输出设备的操作不会影响另一个输入输出设备的操作。

那么要想实现设备独立和设备保护,我们需要从硬件和软件两个方面出发进行考虑。下面我们先来探讨一下硬件层面的输人输出,然后再探讨软件层面的输入输出。

输入输出硬件

输入输出设备种类繁多,功能各不相同,操控也不尽相同。对于普通人或者电气工程师来说,输入输出设备呈现的首要特征是其物理组件:芯片、布线、能量供应、电机等诸如此类的东西。而对于软件工程师或程序员来说,输入输出设备呈现的则是程序员或用户界面:可接受的命令、能提供的功能、错误处理机制等。毫无悬念,我们采取的自然是从软件工程师的角度,尤其是操作系统设计人品的视角来对待输人输出。

从程序员或操作系统设计人员的视角来看。所有的输入输出设备可以(大概)划分为两个大类。块设备和字符设备。这种划分是以设备存储和传输数据的方式来决定!块设备,顾名思义,就是以数据块为单位存储和传输数据的输入输出设备,如磁盘、光盘、U盘、磁带等。而字符设备自然是将数据按照字符(字节)为单位来存放和传输数据的设备,如鼠标、键盘、打印机、网络界面等。

当然,上述分类并不是绝对的。例如,一个设备可以同时作为块和字符设备。例如,网络界面通常被认为是一个字符输入输出设备但在某些时候可以与内存进行DMA,从而看上去更像一个块设备。而另外的设备,如时钟,则不属于这两种中的任何一种(时钟是输入输出设备吗?)

字符设备和块设备的最大不同是在寻址。块设备自数据按数据块为单位进行处理,而每块数据块都有一个唯一的磁盘地址。也就是说数据块是可寻址的,而字符设备里的字符是不可寻址的。 当然,由于一个字符占一个字节(对于ASCII码来说),而字节是可以寻址的,很多人会认为字符因此也是可寻址的。但这个理解是不正确的。(为什么?)

输入输出设备的差异性

输入输出设备由于种类不同,制造商不同,技术标准不同,其特性可以有巨大的不同。而这种不同越是明显,对操作系统的设计的挑战就越大。因为屏蔽这些巨大的不同,使得不同的设备相互共存并转不是一件容易的事情。

其中最为明显的一种差异是其数据传输的速度。输入输出设备的传输速度涵盖范围从每秒十个字节到几兆个字节。下表给出的是较为常见的一些设备的数据传输速率。

设备控制器

输入输出设备本身并不是一个不可分割的整体!而是由不同的部件构成。一般来说,一个输入输出设备至少可以分为两个部分:机械部分和电子部分。

机械部分自然是设备的物理硬件部分,而电子部分则是设备的控制器。
控制器有时也称为适配器,通常为一块印刷电路板。控制器可以处理多个设备,或者说多个同类的设备可以共用一个控制器。下图描述的就是输入输出设备和它们的控制器。

设备控制器的任务可以简单地分为如下几项:

  • 控制设备的物理运行。
  • 将序列字位流转化为字节块流。
  • 进行纠错操作。

设备控制器与CPU之间的数据交互通过设备寄存器进行。设备寄存器附着在设备控制器上。通过向这些寄存器进行写入,操作系统可以向设备发出输人输出命令或把设备关闭或打开。而通过读取这些寄存器的内容,操作系统可以获得设备的状态信息

为了提高与CPU交互数据的效率,输入输出设备通常还备有数据缓冲区。列如,视频控制器通常带有自己的视频RAM。CPU通过与视频RAM进行数据交互,就可以传输巨大的数据量。

物理I/O模式

计算机在输入输出时可以选择的模式很多。这些选择的不同体现在CPU和内存的使用不同上。通常来说,物理输入模式可以按照两种考虑进行分类:

  • 根据CPU访问I/O设备的方式进程分类。
  • 根据 CPU在I/O过程中的涉入程度进行分类。

根据CPU与设备控制器沟通方式以及与内存的不同关系,物理I/О模式可以分为以下三种:

  • 专有通道的I/O
  • 内存映射的I/O
  • 混合I/O

专有通道I/O

在专有通道I/O模式下,I/O与内存是完全脱离的。每个控制寄存器被赋予一个I/O端口,这个I/O端口就是一个9位或者l6位的一个整数,这个端口和内存地址没有任何关系。

而正是由于I/О端口地址与内存地址没有任何关系,或者说I/О端口地址不是内存地址,操作系统必须使用专门的输入输出特殊指令来进行数据的读写

专有通道模式的优点是与内存分开,输入输出操作不会影响或干扰内存操作。尤其是输入输出软件的可靠性通常不如内存管理软件,这种分离就显得更加有价值。但俗话说,成也萧何,败也萧何。这个优点恰恰也是其缺点。由于与内存分开,输入输出指令与内存访问指令自然也不相同,因此,进行内存访问与进行输入输出的指令互不相同。事实上,正如上一段所描述的,这种模式下的输入输出需要使用专门的 IN/OUT 指令来进行。

由于高级语言不支持这种低级指令,从面形成高级语言屏蔽的一个漏洞。程序员如果想进行I/O,则必须使用其他语言,如使用IN或OUT指令,而这将增大程序设计的难度和可靠性。
专有通道I/O还有一个问题,就是保护问题。这是因为由于使用低级语言进行I/O,而低级语言的保护作用非常有限。 例如,低级语言一般不具备访问控制和授权能力,使用这种模式进行访问的主体可以对I/O设备进行任意操作,包括刻意制造故障。

内存映射的I/O

内存映射的I/O,顾名思来,就是将I/O映射到内存里面,从而使得I/O相内存管理得到统一。 其体来说,就是将I/O设备的每个控制寄存器和后备缓冲区寄存器赋子一个唯一的内存地址。对这些地址的访问就是对输入输出设备的访问。这些地址的访问从逻辑上看,就是内存访问。

第1个问题是缓存使用而产生的问题。由于使用内存映射,I/О控制寄存器被当做内存来看待,而内存里面的内容则有可能缓存起来。对于一般的数据来说,缓存提供的是优势。但对于控制寄存器来说,则是一个大缺点。因为操作系统在读控制寄存器时需要的是最新的内容,而不是缓存的内容。I/O设备的任何变化都将在控制寄存器内容上体现。但这个内容却不一定在缓存体现。这样,如果读操作在缓存命中,操作系统获得的系统状态将是以前的状态,这将直接影响I/O操作的执行。

内存映射的第2个问题是总线竞争。在单总线系统里,内存和设备均需要对总线上的数据进行监听,以确认命令是否针对自己。这样将产生总线竞争面降低系统效率,如图a所示。如果是多总线系统,则I/O设备有可能看不到总线上的地址,如图b所示。

对于内存映射的问题,我们也有一定的应对办法。对第1个问题,我们可以使用缓存禁止位。对于多总线数据迷失问题,解决的办法还有多个:

  • 失败与再试:如果一个请求没有得到内存响应,则将数据发到另外的总线。
  • 窃听:在总线上安装一个窃听装置,负责对数据进行分发。
  • 地址过滤:使用一个过滤装置把地址自动过滤到合适的地方。

但对于单总线竞争问题,尚无有效的解决办法。

复合I/O模式

当然也可以使用上述两种方式的组合,即复合I/О模式。在这种复合模式下,数据缓冲区为内存映射,但是输入输出端口为分开的部分。即设备控制器寄存器需要使用特殊命令来访问。

根据CPU在I/O过程中的涉入程度进行分类

按照CPU在I/O过程中的涉入程度来分类,物理I/О模式可以分为:

  • 繁忙等待访问
  • 直接内存访问

繁忙等待访问

不管是否使用内存映射的输入输出,处理器均需要与I/О控制器和数据缓冲区进行数据交换。而这种交换既可以按字节进行,也可以按数据块进行。如果按字节进行,CPU当然需要在整个过程中介入,即CPU在I/O过程中一直处于繁忙状态。

显然,让CPU在I/O过程中一直保持繁忙不是一件好事。如果要传输的数据是一片连续的内存空间,则我们就可以把CPU 从I/O中解脱出来。解脱出来的办法就是直接内存访问,(DMA,Direct Memory Access )。

DMA访问

DMA的工作原理是,如果按数据块进行I/O,即需要传输大量数据时,就无需CPU的介入。在这种情况下,我们可以让I/O设备与计算机内存进行直接数据交换。而CPU则可以去忙别的事情,这种将CPU的介入减少的输人输出模式称为直接内存访问。

问题是,将CPU从繁忙等待中解脱出来,难道DMA的整个数据读写过程不需要使用处理器的功能吗?当然不是的。数据传输当然使用CPU,只不过这里使用的CPU不是计算机里面所有进程共享的CPU,而是由另外一个CPU来负责数据传输。这个另外的CPU就是 DMA控制器。(只有控制数据传输的功能,其他复杂功能都不要,便宜,简单)

DMA输入输出的过程如下:

  • 1)CPU对DMA进行设置,告诉其l/O的起始地址和数据长度。
  • 2)启动DMA过程。
  • 3)DMA进行数据传输。
  • 4)DMA结束后发出中断。
  • 5)CPU响应中断并处理结束事宜。

DMA控制器依不同生产厂商的不同而有很大的不同。最简单的DMA控制器在一个时间只能处理一个I/O,即不能并发;复杂的DMA控制器可以同时处理多个I/O,即它能够提供多个I/O通道,每个通道可以对付一个I/O设备。

输入输出软件

操作系统的角色就是魔幻和管理。在这里也不例外。I/O软件的目的就是魔幻和管理。魔幻是将不同I/O设备的差异屏蔽,使它们看上去似乎是一样的东西,都具有令人爽心悦目的界面;而管理自然是对这些设备进行管理,该独享的独享,该共用的共用,需要缓冲的缓冲,并对设备进行实际的驱动(发出读写命令)。

I/O软件的目的

从最高层来说,I/O软件的目的我们已经说过,就是魔幻和管理。而具体来说,其目标有如下几个:

  • 设备独立
  • 统一命名
  • 错误处理
  • 数据传输
  • 缓冲
  • 共用与独享

设备独立指的自然是程序对I/О设备的访问不依赖于设备的物理特征,且在输入输出程序的编写时无需事先指定I/О设备。

统一命名指的是设备或文件的命名不依赖于具体的计算机,这样使用名字将使程序可以在任何机器上运行。

错误处理指的是对输入输出过程中产生的数据错误进行侦测与纠正,而且纠错应该在最靠近硬件的层面上进行。

数据传输指的是实际操控数据在主机和外设之间的传递,例如支持同步(阻塞传输)和异步(中断驱动)数据传输。

缓冲是为数据传输提供一个临时存放地,然后在方便的时候将数据拷贝到最后目的地。

共用与独享指的是将设备尽量变为共享,以增大资源利用率和降低死锁发生的概率。例如,将磁盘、打印机变为共享。

逻辑I/О模式

在从硬件角度讨论I/O时,我们说过可以有不同的I/О模式。那么从软件角度来看I/O,也存在不同的模式,而且这些模式与硬件VO模式遥相呼应。
从CPU的涉人程度来分,可以分为可编程I/О和中断驱动I/O,这两种分别对应硬件原理的繁忙等待I/O和直接内存访问。

可编程/O原理

在可编程模式下,CPU等待I/O的完成,即CPU涉入程度很深。这种模式也称为轮询,或者繁忙等待。下图给出的是打印一串字符的可编程I/О程序片段。

copy_from_user(buffer,p,count) //从缓冲区buffer中,接收count个字符到p
For (i =0 ; i <count;i ++)
{while(* printer_status_reg! = READY) ; //轮询检查打印机状态*printer_data_register = p[i];//打印一个字符
}
Return_to_user();

显然,在等待打印机状态变为就绪的过程中,CPU不能干任何事情。而每次等待后做的事情只不过是将一个字符发送到打印机的数据寄存器里面。这对于CPU来说,真有点“大材小用”了。而且,我们前面说过,大材小用或者浪费资源并不是繁忙等待的唯一甚至最严重的缺陷。最严重的缺陷还有可能造成优先级倒挂,进而造成死锁。

解决的方法就是不繁忙等待,而是使用中断。

中断驱动O原理

中断驱动就是将CPU从繁忙等待中解脱出来。在发送好一个或一批数据后,CPU就去忙别的事情。I/О设备处理完这批数据后,向CPU发出中断。CPU 响应中断后再发送下一批数据。具体来说,中断驱动过程如下:

  • 1)CPU初始化I/O并启动第一次I/O操作。
  • 2)CPU 去忙别的事情。
  • 3)当I/O完成时,CPU将被中断。
  • 4)CPU处理中断。
  • 5)CPU恢复被中断的程序。

系统调用片段对应上面所列步骤的前面两个步骤,中断响应部分对应后面三个步骤。在系统调用片段,我们首先将需要打印的数据从用户空间复制到内核空间,然后启用中断(如果中断本来就是启动状态,这个操作也不会产生任何副作用)。在此之后,等待打印机状态变为就绪,然后发生第一个字符。之后在打印机处理这个字符的输出时,操作系统调用调度器来选取另一个程序来执行。

在中断响应片段,首先检查是否打印完毕,如果是则将用户叫醒。否则,发生下面一个要打印的字符,将仍然需要打印字符数减一。然后回应中断,并从中断处理程序返回(到被中断的程序)。

这里请注意,在系统调用部分的初始设置时,操作系统采用轮询等待打印机变为就绪,而不是使用中断。这是因为,操作系统还没有交给打印机任何数据进行打印。

直接内存访问I/O原理

中断驱动的I/O由于在I/O设备工作时无需繁忙等待,其效率比可编程I/O要高。但是它毕竟需要CPU周期性地中断来发送或接收后续的数据。这种频繁中断对于系统效率来说并不是什么好事。

这样,虽然较可编程I/O效率高,但总体来说还是有很大的改进余地。那么怎么改进呢?
当然是降低CPU响应中断的频率:从一个I/O任务多次中断变为一个I/O只有一次中断。这就是直接内存访问!我们在硬件角度看I/О时已经讲过了DMA的原理。现在从软件的角度看就更加清楚了。由于一个I/O任务不一定可以一次将数据传输完毕,因此中间还是需要CPU来进行处理,但为了将主CPU 从I/O的繁琐事情中解脱出来,我们另外设立一个CPU来响应一个任务中间的中断。而这个CPU就是DMA控制器。

更进一步来看,既然有一个单独的CPU来处理I/O,也就没有必要使用中断。就让这个额外的CPU轮询也不会降低系统效率。因此 DMA模式下的I/О通常是轮询。X下图给出的是使用DMA打印时的程序片段。

图a是 DMA设置部分。该部分首先将需要打印的数据从用户空间拷贝到内核空间,然后设置DMA控制器,包括起始地址和数据量,并启动DMA。然后调用调度器来选择另一个程序执行。

图b是DMA结束部分。DMA结束后会向主CPU发出中断。一旦收到这个中断请求,CPU可以立即回应,而无需检查VО任务是否完成。因为 DMA模式下,DMA 控制器只有在I/O完毕时才会发出中断。回应后叫醒用户,然后从中断响应程序返回。

I/O软件分层

从前面所述可以看出,输入输出是一件颇为繁琐的事情。它牵涉到用户空间和内核空间的数据交换、I/O设备的设置与启动、中断响应与返回,而且整个I/O需要提供一个与I/O设备无关的统一界面。为了完成一个繁琐的工作,人们通常会将其分为更小的任务来处理。 在I/O软件上自然不例外。I/O软件通常按照I/O功能进行分层,每一层提供独特的功能,并与相邻的层面间设计有标准界面。当然,不同的操作系统这种分层也是不同的。但一般,都会有图中的几层。

下面我们对每一层的功能进行解说。

中断服务程序

由于大多数I/O均为中断驱动,中断驱动服务程序就成为绝大部分I/О软件的不可分割的部分。中断服务程序由于直接与硬件相关,针对不同的I/О硬件,中断响应的处理也不尽相同。因此,中断服务程序是I/О软件系统分层里面的最底层。而为了降低操作系统的复杂性,中断服务程序的暴露窗口应该越小越好,与其打交道的OS部分也是越小越好。

而降低暴露窗口的最好办法是让设备驱动程序负责中断响应。即设备驱动程序启动I/О操作后阻塞,然后等待中断。而一个设备驱动程序可以通过操作信号量或睡觉来进行阻塞。当收到中断请求后,中断服务程序先执行,然后将处于阻塞状态的设备驱动程序解锁。这种解锁可以发送信号或者对信号量进行操作。下面为中断处理的步骤:

  • 1)保存没有被中断硬件保存的相关寄存器。
  • 2)设置中断服务程序的上下文。
  • 3)设置中断服务程序的栈。
  • 4)回应中断控制器。
  • 5)重开中断。
  • 6)从保存处恢复寄存器。
  • 7)执行服务程序。
  • 8)设置MMU以执行下一个进程。
  • 9)设置新进程的寄存器。
  • 10)开始执行新进程。

设备驱动程序

设备驱动程序,顾名思义,就是直接驱动I/О设备进行输入输出操作的软件。它属于与设备控制器直接联系的I/О软件部分。它与具体的I/O设备直接相关,并针对每个特定的I/O设备进行过优化。设备控制程序通常由设备制造商提供,但归属于操作系统内核。正因为这一属性,设备驱动程序是操作系统安全的一个巨大隐患。

由于需要直接驱动设备的运行,设备驱动程序必须清楚设备的所有细节。例如,磁盘驱动程序必须清楚什么是扇面、磁道、磁柱、磁头、磁臂、电机、电机驱动等信息;鼠标驱动程序自然需要具备辨认是哪个键被按下所具有的能力。

设备驱动程序需要完成的任务包括;

  • 从上层接收抽象的读写请求。
  • 确保读写操作被完成。
  • 初始化设备,开、关设备。
  • 对设备的功能进行管理。

多数操作系统都定义一个标准的块设备界面和字符设备界面。每种界面由一组子程序组成。这组子程序可以由操作系统的其他部分调用。

设备驱动程序操作

在收到一个I/O请求后,设备驱动程序做的第一件事情是检查输入参数是否合法。如果不合法,则返回错误。如果参数正常,则将I/О请求的抽象表示转换为设备能够认识的具体表示,如将线性数据块号转换为磁头、磁道、扇面等。然后,设备驱动程序需要检查设备状态以确认其是否处于闲置状态。如果设备繁忙,则将I/О请求送入该设备的等待队列里面以待处理。如果设备没有工作,则驱动设备运行并启动电机。

在做完上述各项工作后,剩下的就是真正的I/O操作了。设备驱动程序通过发出一系列的输入输出命令到设备控制寄存器里面来进行真正的数据传输工作。如果需要,设备驱动程序需要阻塞并等待中断。而每次从中断醒过来,则需要检查I/О是否正确完成,再将数据传输到上面调用设备驱动程序的应用。

在所有数据传输完毕后,设备驱动程序将返回到调用者那里。

设备独立的操作系统软件

一般来说,设备驱动程序并不直接从用户处接受I/О请求,而是通过另外—层中介获得用户请求。这层介于设备驱动程序与用户程序之间的中介就是设备独立的操作系统软件。操作系统在设计时之所以有这层软件是因为I/О软件的一部分与设备有关,一部分与设备无关。而如果与设备无关,就可以将这部分共用起来,放置在设备驱动程序之上,为用户提供一个统一的I/O界面。

这种对于所有I/О设备都一样的操作包括诸如缓冲、错误报告、分配与释放独享设备、提供设备独立的数据块尺寸等。设备独立的OS软件与设备驱动软件之间的分界自然与每个设备有关。

统一界面

设备独立的操作系统软件的一个重要目标是提供一个统一的I/О界面。即让所有的I/О设备看上去一样或者相似,这是操作系统经常扮演的角色。使用的办法则是将设备驱动界面进行标准化:规定一个设备驱动程序必须提供的功能清单;规定内核为设备驱动程序提供的功能清单和界面。

缓冲

缓冲是几乎所有I/О设备都需要的一种操作。缓冲的目的有两个:一是桥接速度不同的设备,使之可以沟通同步;二是提供灵活的健壮机制,因为在每个缓冲层都可以进行一些健壮性、可靠性、安全性处理。而第一点又有两层意思,一是提高数据传输速度,因为快速设备不必等待慢速设备;二是防止溢出,因为慢速设备来不及处理的数据可以存放在缓冲区而不会丢失。

当然,缓冲也有缺点:就是降低了数据传输的时效性。因为数据层层缓冲处理是需要时间的。如果一个系统的时效性非常重要,则最好不要使用缓冲,而是在通信双方之间创建一个没有缓冲的直接通道,这样,数据从一方发出后,另一方将马上收到。

错误报告

在I/О操作中,错误是难免的。这是因为I/О需要与计算机外面的世界打交道。而外面的世界自然不如计算机内部的世界井然有序。统计数据表明,计算机壳里面发生数据传输错误的概率很低,而在计算机壳外部进行数据传输发生错误的概率相当高。二者可以相差几百万倍。因此,在I/O中进行错误处理是件十分重要的事情。

如何进行错误处理取决于错误的类型。一般来说,错误可以分为程序错误和真正的I/О错误。程序错误就是用户要求设备做一件该设备无法做到的事情,例如从输出设备上读数据。真正的I/О错误当然是指数据读写过程中发生的错误,例如数据读错了,或者磁盘盘片损坏了等。

对于程序设计错误来说,I/O软件除了将错误报告给用户外,似乎不能做任何别的事情了。但如果是I/О错误,那I/O软件则需要进行适当的纠错操作,看能否消除错误。如果不能消除错误,可以询问调用者如何处理,或者干脆返回一个错误码给用户。

用户层I/O软件

前面说过,设备驱动程序从设备独立的操作系统软件层接受I/О请求。而设备独立的操作系统软件则从用户或应用软件处接受指令。但是用户也好,应用软件也好,如果需要向I/О软件发出指令,得需要一个发出指令的界面。这个用户层可以直接使用的I/О界面就是用户层I/O软件。这一层既然可由用户直接操控,它自然运行在用户空间。

例如,很多读者都见过的Count = write (fd,buffer,nbytes)命令就是用户层I/O软件的一部分。这条指令里面的write被很多人误认为是操作系统的系统调用,而实际上并不是。这是一个由高级语言提供的库函数,它将操作系统的相关系统调用包裹起来。用户与这个库函数打交道,而这个库函数在编译出来后,会变成一系列指令,来完成系统调用过程。

除此之外,放在用户层I/О软件里面的I/O功能包括格式化、假脱机等。

发布评论

评论列表(0)

  1. 暂无评论