更新于 

设备独立性软件

I/O软件层次结构

从上图我们可以从总体上看出一个I/O设备相应请求时的全过程,分别经过了以下几个过程。

用户层软件

用户层软件实现了与用户交互的接口,用户可以直接使用该层提供的、与I/O操作相关的库函数对设备进行操作。比如库函数提供的printf()函数,他会被翻译成等价的write系统调用,用户层软件会在系统调用时填入相应参数,这样就可以通过系统调用的方式实现I/O请求。

windows操作系统会向外提供一系列系统调用,但是由于系统调用的格式严格,使用麻烦,所以在用户层上封装了一系列更加方便的库函数接口供用户使用就比如C库等。

设备独立性软件

又称为设备无关性软件,因为与设备的硬件特性无关的功能几乎都在这一层实现。他有以下几个功能:

  1. 向上层提供一些统一的调用接口(如read/write系统调用)

  2. 原理类似于文件保护,设备被看成是一种特殊的文件,不同用户对各个文件的访问权限不一样。同理也就实现了对设备的访问权限不同,保护设备不会被恶意文件修改

  3. 差错处理,设备独立性软件需要对一些设备的错误进行处理。

  4. 设备的分配与回收

  5. 数据缓冲区的管理,可以通过缓冲技术屏蔽设备之间数据交换单位大小和传输速度的差异

  6. 建立逻辑设备名到物理设备名的映射关系,根据设备类型选择调用相应的驱动程序。用户和用户层软件发出I/O操作相关系统调用的时,需要指明此次要操作的I/O设备的逻辑设备名。设备独立性软件通过“逻辑设备表(LUT,Logical Unit Table)”来确定逻辑设备对应的物理设备,并且找到该设备对应的设备驱动程序。

    操作系统可以采用两种方式管理逻辑设备表LUT:

    ①整个系统就设置一张LUT,这就意味着所有用户不能使用相同的逻辑设备名,因此这种方式只适用于单用户操作系统。

    ②为每一个用户都设置一个LUT,这样各个用户使用的逻辑设备名可以重复,适用于多用户操作系统,系统在用户登录时为其建立一个用户管理进程,而LUT就存放在用户管理进程的PCB中。

思考:为什么不同的设备需要不同的设备驱动程序?

我们前面提到过,不同的厂商提供的设备信号规则不同,有的厂商对于生产的设备0代表空闲1代表忙碌,但是有的厂商生产的设备0代表忙碌1代表空闲。所以根据不同的信号需要正确识别信息,而这些不同设备的内部硬件特性只有厂商才知道,所以厂商需要提供与设备的对应的驱动程序,这样cpu才能够正确执行驱动程序的指令序列,来完成设置设备寄存器,检查设备状态等工作。(如果你不能够理解,那么就以这个例子为比喻:你通过介绍人录用了一个外国小伙当你的公司总监,但是你们之间的语言不通所以你无法知道他所返还的信息,所以介绍人在介绍外国小伙的同时还需要提供一个中间翻译即设备驱动程序,他能够为你们两个之间的信息交流提供翻译桥梁)。

设备驱动程序

设备驱动程序主要负责对硬件设备的具体控制,将上层发出的一系列命令(如read/write)转换成特定设备“能够听懂”的一系列指令操作,包括设置设备寄存器,检查设备状态等。不同的I/O设备有不同的硬件特性,具体细节只有设备的厂家才知道,因此厂家需要根据设备的硬件特性设计并提供相应的设备驱动程序。

中断处理程序

当I/O任务完成后,I/O控制器会发送一个中断信号,系统会根据中断信号类型找到相对应的中断处理程序并执行。中断处理程序的流程如下:

所以我们以一个I/O请求任务为例分别经过一下啊几个阶段才能够完成这次任务相应:

  1. 用户通过用户层软件提供的库函数发出的I/O请求
  2. 用户层软件通过“系统调用”请求设备独立性软件层的服务
  3. 驱动程序向I/O控制器发出具体命令
  4. 等待I/O设备完成的进程应该被阻塞,因此需要进程切换,而进程切换必然需要中断处理

总结

I/O软件各个层次之间的顺序要理解,要能够推理判断出某个处理属于哪个层次,通常直接涉及到硬件具体细节。且和中断无关的操作肯定是在设备驱动程序层完成的,没有涉及到硬件。对各个设备都需要进行的管理工作都是在设备独立性软件层完成的。

I/O核心子系统

I/O核心子系统是属于操作系统内核的一部分,所以肯定涉及到了调度,设备保护还有互斥等问题,下面就详细介绍这几种功能的具体实现

功能所属层次

我们知道这些功能都是由I/O核心子系统实现的,并且I/O核心子系统是由设备独立性软件、设备驱动程序、中断处理程序三个层次组成的,所以理论上这些功能肯定都是属于这三个层次。

但是实际上假脱机技术即SPOOLING技术(前面讲过是一种解决死锁的方法,即让个进程都产生这个临界区是自己的从而实现临界资源共享打破互斥条件来解决死锁)需要请求“磁盘设备”的设备独立性软件的服务,因此一般来说假脱机技术是在用户软件成实现的,但是408大纲又将假脱机技术归为了“I/O核心子系统”的功能,所以这里我们也认为假脱机技术是I/O核心子系统的功能。

I/O调度

与进程调度类似,I/O之间肯定也是需要有调度算法的,毕竟I/O设备就那么多,进程之间肯定是需要等待轮流使用I/O设备的。我们这里实际上已经学过了一些I/O调度算法,比如磁盘调度算法(FIFO算法,最短寻道优先算法,SCAN算法,C-SCAN算法, LOOK算法,C-LOOK算法等)。当多个磁盘I/O请求到来时,用某种调度算法确定满足I/O请求的顺序。同理打印机等设备肯定也有类似的FIFO,优先级算法,短作业优先等算法来确定I/O调度顺序。

设备保护

操作系统需要实现文件保护功能,不同的用户对各个文件有不同的访问权限(如:只读、读和写等)在UNIX系统中,设备被看作是一种特殊的文件,每个设备也会有对应的FCB(文件控制块,存储文件在磁盘中的相关信息)。当影虎情趣访问某个设备时,系统会根据FCB中记录的信息来判断该用户是否有相关的访问权限,以此实现“设备保护”的功能。

总结

这里只是介绍了部分简单的功能,下面将逐一介绍比较复杂的功能。

假脱机技术(SPOOLING技术)

产生的原因

在手工操作阶段主机直接从I/O设备获得数据,由于设备速度慢,主机速度快,人际速度矛盾明显,主机需要浪费很多时间来等待设备。而在批处理阶段,就引入了假脱机技术,缓解了cpu与慢速I/O设备之间的速度矛盾,另一方面,即使cpu在忙碌,也可以提前将数据输入到磁带,技术速度慢的输出设备正在忙碌,也可以提前将数据输出到磁带。

输入井和输出井

假脱机技术又称为"SPOOLING技术",使用软件方式模拟脱机技术,SPOOLING系统的组成如下:

这样主机就不在需要长时间等待输入了,而是直接从磁盘即输入井中拿取数据,而慢速的技术输入就是将数据放入到磁盘中,这样就类似于吃自助餐,服务员(用户层软件)将数据直接提前放到餐台(磁盘)上,而餐客(设备)需要数据时就直接从餐台上拿取相应的数据了,输出亦是如此,这样就减少了长时间的数据等待时间了。

输入/输出缓冲区

实际上就是上面所讲的餐台。

共享打印机原理

这个就是我们之前讲的实现数据区共享以防止死锁的技术应用。

独占式设备:只允许各个进程串行使用设备,一段时间内只可以满足一个进程的请求。

共享设备:允许多个进程“同时”使用设备(宏观上是同时使用,实际上微观上是交替使用,即并发使用)可以满足多个进程的使用请求。

当多个用户进程提出要输出打印的请求时,系统会答应他们的请求,但是并不是真正把打印机分配给他们,而是由假脱机管理进程为每个进程做两件事:

  1. 在磁盘输出井中为每一个进程分配一个空闲缓冲区(也就是说,这个缓冲区是在磁盘上的),并将要打印的数据送入其中。
  2. 为用户进程申请一张空白的打印请求表,并将用户的打印请求填入表中(其实就是用来说明用户的答应数据存放位置等信息的),再将该表挂载到假脱机文件队列上。
  3. 当打印机空闲时,输出进程会从文件队列的队头取出一张打印请求表,并且根据表中的要求将要打印的数据从输出井传送到输出缓冲区,在输出到打印机进行打印。用这种方式可依次处理完全部的打印任务。

因此假脱机文件队列实际上就是打印任务队列,假脱机技术(SPOOLING技术)可以把一台物理设备虚拟成逻辑上的多台设备,可将独占式设备改造成共享设备。

总结

设备的分配与回收

设备分配时应考虑的因素

设备的固有属性可以分为三种:

  1. 独占设备:一个时段只能分配给一个进程(如打印机)
  2. 共享设备:可同时分配给多个进程使用(如磁盘),各进程往往是宏观上同时共享使用设备,而微观上是交替使用,即并发性的特点。
  3. 虚拟设备:采用SPOOLING技术将独占设备改造成虚拟的共享设备,可同时分配给多个进程使用(如采用SPOOLING技术实现的共享打印机)

设备的分配算法:FIFO,优先级算法,短作业优先SJF等。

从进程的安全性上考虑,设备分配有两种方式:

  1. 安全分配方式:为进程分配一个设备后就将进程阻塞,本次I/O完成后才将进程唤醒。这样一个时间段内每个进程只能使用一个设备,优点是破坏了请求和保持条件,不会死锁,缺点是对于一个进程来说,cpu和I/O设备只能串行工作,效率较低。
  2. 不安全分配方式:进程发出I/O请求后,系统为其分配I/O设备,进程可以继续执行,之后还可以发出新的I/O请求,只要某个I/O请求得不到满足时才将进程阻塞。这样一个进程可以同时使用多个不同的I/O设备,优点是进程的计算任务和I/O任务可以并行处理,是进程迅速推进,缺点是有可能发生死锁(死锁避免,死锁的检测和解除来解决此问题)。

静态分配和动态分配

静态分配:进程运行前为其分配全部所需资源,运行结束后归还资源,破坏了请求和保持条件,不会发生死锁。

动态分配:进程运行期间动态申请设备资源,但是可能会发生死锁。

设备分配管理中的数据结构

一个管道可以控制多个设备控制器,每个设备控制器可以控制多个设备。其中每个层次都会有自己的信息表如下:

设备控制表(DCT)

在进程管理中我们知道系统会根据阻塞原因的不同,将进程PCB挂到不同的阻塞队列中。因此设备队列的队首指针指向的一定是因为等待这个设备而导致阻塞的PCB队列。

控制器控制表(COCT)

每个设备控制器都会对应着一张COCT,操作系统会根据COCT的信息对控制器进行操作和管理。

通道控制表(CHCT)

每一个通道也都会对应着一个CHCT,操作系统会根据CHCT的信息对通道进行操作和管理。

系统设备表(SDT)

记录了系统中全部设备的情况,每一个设备对应一个表目。

设备分配的步骤

  1. 根据进程请求的物理设备名查找SDT(注意物理设备名是进程请求分配设备时提供的参数)
  2. 根据SDT找到DCT,若设备忙碌则将进程挂到设备等待队列中,不忙碌则将设备分配给进程
  3. 根据DCT找到COCT,若控制器忙碌则将进程PCB挂到控制器等待队列,不忙碌则将控制器分配给进程。
  4. 根据COCT找到CHCT,若通道忙碌则将进程PCB挂到控制器等待队列,不忙碌则将通道分配给进程。

只有设备、控制器、通道三者都分配成功时,这次设备分配才算成功,之后便可以启动I/O设备进行数据传送了。

有没有什么缺陷?如何解决?

有,我们发现进程请求时需要提供“物理设备名”,但是底层细节对用户不透明,不方便编程,因此如果一旦换了物理设备,那么这个程序就无法运行了,并且如果进程请求的物理设备正在忙碌,那么即使系统中还有同类型的设备,这个进程也会阻塞等待。改进方法就是建立逻辑设备和物理设备之间的映射机制,用户编程时只需提供逻辑设备名。

设备分配步骤的改进

  1. 根据进程请求的逻辑设备名查找SDT(注意物理设备名是进程请求分配设备时提供的参数)
  2. 根据SDT找到DCT,若设备忙碌则将进程挂到设备等待队列中,不忙碌则将设备分配给进程
  3. 根据DCT找到COCT,若控制器忙碌则将进程PCB挂到控制器等待队列,不忙碌则将控制器分配给进程。
  4. 根据COCT找到CHCT,若通道忙碌则将进程PCB挂到控制器等待队列,不忙碌则将通道分配给进程。

逻辑设备表LUT建立了逻辑设备名和物理设备名之间的映射关系,当某个用户进程第一次使用设备时使用逻辑设备名向操作系统发出请求,操作系统会根据用户进程指定的设备类型(逻辑设备名)查找系统设备表,找到一个空闲设备分配给进程,并在LUT中增加相应表项。如果之后用户进程再次通过相同的逻辑设备名请求使用设备,则操作系统会通过LUT表即可知道用户进程实际要使用的是哪个物理设备了,并且也能知道该设备的驱动程序入口地址了。但是我们前面也讨论过:如果整个系统就一张LUT,那么各个用户所使用的逻辑设备名不允许重复,适用于单用户操作系统,而每个用户都拥有一种LUT,那么不同用户的设备逻辑名可以重复,使用于多用户操作系统。

总结