目录

操作系统笔记王道考研-第五章输入输出IO管理

操作系统笔记(王道考研) 第五章:输入输出(I/O)管理

大部分内容基于中国大学MOOC的2021考研数据结构课程所做的笔记,后续又根据2023年考研的大纲增加了一些内容,主要有操作系统引导、虚拟机、多级队列调度算法、互斥锁、调度器和闲逛进程、内存映射文件、文件系统的全局结构、虚拟文件系统、固态硬盘SSD、输入输出应用程序接口 、驱动程序接口等等。

感谢我的室友HXN,他帮我写了一部分第五章的内容。

课程内容和西电平时讲课的内容大致重合,西电可能每章会多讲一点UNIX系统的实例,可以听完这课再快速过一遍老师的课件防止漏掉什么内容。

这门课讲的其实不算特别硬核,没怎么涉及具体的代码。不过我其实感觉操作系统是个大无底洞,能学到多深基本取决于愿意花多少时间和精力。如果有闲心,推荐看下南大蒋炎岩老师的《操作系统:设计与实现》和哈工大李治军老师的《操作系统》,讲的更深入,当然难度也相应的大的多。

其他各章节的链接如下:

输入输出(IO)管理

I/O 设备基本概念与分类

https://i-blog.csdnimg.cn/blog_migrate/5153ba3bd895e65d07b6cad2dc633cbd.png

什么是 I/O 设备

“I/O” 就是 “输入/输出”(Input/Output) I/O 设备就是可以将数据输入到计算机,或者可以接收计算机输出数据的外部设备,属于计算机中的硬件部件

  • 鼠标、键盘——典型的输入型设备
  • 显示器——输出型设备
  • 移动硬盘——即可输入、又可输出的设备

UNIX系统将外部设备抽象为一种特殊的文件,用户可以使用与文件操作相同的方式对外部设备进行操作。

  • Write操作:向外部设备写出数据
  • Read操作:从外部设备读入数据
I/O 设备的分类 —— 按使用特性

https://i-blog.csdnimg.cn/blog_migrate/27b037fcdcb12c62ed73301d8e55ae02.png

I/O 设备的分类 —— 按传输速率分类

不常做考点考察

https://i-blog.csdnimg.cn/blog_migrate/8b69578e37cb6c69a75995d808e5a4b8.png

I/O 设备的分类 —— 按信息交换的单位分类

https://i-blog.csdnimg.cn/blog_migrate/af078d1fc983f27d15fe77e7a5188987.png

可寻址:这种设备可以随机地读写任意一块

I/O 控制器

https://i-blog.csdnimg.cn/blog_migrate/c7d128faaf4300ccde726b9feb781693.png

I/O 设备由机械部件和电子部件组成

I/O 设备的机械部件

I/O 设备的机械部件主要用来执行具体I/O 操作。 如我们看得见摸得着的鼠标/键盘的按钮;显示器的LED屏;移动硬盘的磁臂、磁盘盘面。

I/O 设备的电子部件通常是一块插入主板扩充槽的印刷电路板。

I/O 设备的电子部件( I/O 控制器)

https://i-blog.csdnimg.cn/blog_migrate/c22d1e46e577d5eb8e0c3a44355a81ee.png

I/O控制器的组成

https://i-blog.csdnimg.cn/blog_migrate/9c8c4c7aa5aaaa403bb49d5c6bfff2ab.png

CPU通过控制线向I/O 设备发出具体的I/O指令,同时CPU还会在地址线上说明自己要操纵哪一个设备

CPU此时发出的I/O 指令可能会有一些相应的参数,这些参数会被放到数据寄存器当中

https://i-blog.csdnimg.cn/blog_migrate/fbe51c3de9d00c47981d49cb263143a8.png

内存映像 I/O v.s. 寄存器独立编址

https://i-blog.csdnimg.cn/blog_migrate/3fbdc3e29f06a7b27e13b14681813f27.png

如果采用寄存器独立编址,这些寄存器和内存的地址空间并不统一,它们是两个独立的体系

I/O 控制方式

https://i-blog.csdnimg.cn/blog_migrate/758a14c0c7ab461d7d5085387c7ad094.png

一个通道可以控制多个I/O控制器,而一个I/O控制器又可以控制多个IO设备

I/O控制方式即:用什么样的方式来控制I/O设备的数据读/写

程序直接控制方式

https://i-blog.csdnimg.cn/blog_migrate/1f9130556f1646758079db3f6268b628.png

1.完成一次读/写操作的流程(以读操作为例)

https://i-blog.csdnimg.cn/blog_migrate/b2bc42d8451e4173e28823f22d13c75f.png

如果I/O设备出错,也会在I/O设备的状态寄存器当中写入相应的代码

使用

p r i n t f printf

p

r

in

t

f 时把内存当中存储的这些变量的数据拿出来经过CPU再输出到输出设备上

https://i-blog.csdnimg.cn/blog_migrate/44a4bf2ebced4131158c049258e68a8a.png

中断驱动方式

https://i-blog.csdnimg.cn/blog_migrate/d1af66c792a6ee6976856d307310c881.png

https://i-blog.csdnimg.cn/blog_migrate/69e91709aa670952da7b0b7fad69729e.png

去看看B站蛋黄派大师兄“操作系统运行机制(小补充)”

DMA方式

https://i-blog.csdnimg.cn/blog_migrate/bf50b227bb8f53f8eacc5dc35b250011.png

CPU会给I/O模块发出一个读或者写一个块的指令,之后CPU就可以转头做其他事情,DMA控制器会根据CPU发出的这些命令,参数来完成CPU指定的一系列读写工作。当CPU指定的这些块读完或者写完之后又会由DMA控制器向CPU发出一个中断信号,然后CPU又介入处理这个中断

DMA控制器

https://i-blog.csdnimg.cn/blog_migrate/f39ef4014c37098849adff8feae58625.png

为了实现控制器和CPU之间的通信,会在主机-控制器接口这设置一系列的寄存器,CPU可以通过系统总线来读或者写其中的某一些寄存器当中的内容来达到控制I/O设备的目的

系统总线还会把DMA控制器和内存连接在一起,所以DMA控制器和内存之间可以直接进行数据的读写,不再需要经过CPU

DMA控制器并不是每次直接读入一整块的数据然后直接把一整块放到内存当中。其实DMA在读入数据的过程当中也是一个字一个字读入的,每次读入的一个字都是先存放在DR,再从DR写入到内存当中。用这样一个字一个字的方式最终就可以完成一整块的数据读入工作

https://i-blog.csdnimg.cn/blog_migrate/ab487f9af97466f94a8ce5cb915e6f77.png

通道控制方式

https://i-blog.csdnimg.cn/blog_migrate/0f428ef6b06ce802ae390284d9afaca1.png

https://i-blog.csdnimg.cn/blog_migrate/63a4fb7b350e76f4e5c4951507b08060.png

I/O软件层次结构

https://i-blog.csdnimg.cn/blog_migrate/43cc9432d93c40a4c3b1d95399b984f9.png

https://i-blog.csdnimg.cn/blog_migrate/df749a94edae2c08da70f40483847cea.png

用户层软件

https://i-blog.csdnimg.cn/blog_migrate/d5cf30abf10fb06204de8d749aa239c4.png

既然需要使用I/O设备进行输出操作,用户层软件肯定需要请求操作系统提供服务,因为只有操作系统才有对硬件操作的权力

用户层软件会使用设备独立性软件这一层向上提供的系统调用接口来请求操作系统内核的服务

设备独立性软件

设备独立性软件,又称设备无关性软件。与设备的硬件特性无关的功能几乎都在这一层实现

主要实现的功能:

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

https://i-blog.csdnimg.cn/blog_migrate/df10873a3d335d04191879ee34f345f9.png

2.设备的保护

https://i-blog.csdnimg.cn/blog_migrate/e6e532ed68ab662aaaec1575b7529c54.png

3.差错处理

https://i-blog.csdnimg.cn/blog_migrate/325ffbf9985ff967d04ccbb508d983d1.png

4.设备的分配与回收

5.数据缓冲区管理

https://i-blog.csdnimg.cn/blog_migrate/250ed19c684d0fc4adc0b6bf548f3d92.png

6.建立逻辑设备名到物理设备名的映射关系;根据设备类型选择调用相应的驱动程序

https://i-blog.csdnimg.cn/blog_migrate/80c41bd15f2f0040990e353cafc9b53c.png

所谓逻辑设备名就是用户在请求使用一个设备时提供的名字,也就是用户所看到的设备名,操作系统对这些设备进行管理在背后还会有物理设备名,所以当选择某一个逻辑设备的时候操作系统需要知道逻辑设备具体对应的到底是哪一个物理设备

https://i-blog.csdnimg.cn/blog_migrate/6c7e894006560eed29037d34b4df80f9.png

很多操作系统都会把设备当作一种特殊的文件,所以这个文件当然也会有存储的路径

各种设备内部的硬件特性不同,因此必须执行与它对应的特定的驱动程序才可以正常地完成对这个设备硬件的控制

思考:为何不同的设备需要不同的设备驱动程序?
  • 各式各样的设备,外形不同,其内部的电子部件(I/O控制器)也有可能不同
  • 不同设备的内部硬件特性也不同,这些特性只有厂家才知道,因此厂家须提供与设备相对应的驱动程序,CPU执行驱动程序的指令序列,来完成设置设备寄存器,检查设备状态等工作
设备驱动程序

https://i-blog.csdnimg.cn/blog_migrate/336041390cb9d791f69988ec51cfa94c.png

中断处理程序

IO控制器

≈ \approx

≈ 设备控制器

https://i-blog.csdnimg.cn/blog_migrate/dac16a318f5a2f0122050031cda988f2.png

https://i-blog.csdnimg.cn/blog_migrate/9a30d7a36cc7ef1d661f4da506883db1.png

输入输出应用程序接口 & 驱动程序接口

https://i-blog.csdnimg.cn/blog_migrate/59edfabcecac247c18f3a4b2617a2397.png

输入输出应用程序接口

https://i-blog.csdnimg.cn/blog_migrate/af30af015ad0e0cf90afe0a9b76f309d.png

https://i-blog.csdnimg.cn/blog_migrate/4cb0658551f30d936eb49b05d3dd4a0c.png

用户进程可以使用网络设备相关的系统调用接口来创建一个套接字对象。套接字和套接字之间需要建立点对点连接,每一个套接字会绑定一个本机的端口,通过主机IP地址和套接字绑定的端口就可以找到全世界任何一个套接字对象

P1和P3进程建立套接字连接:

https://i-blog.csdnimg.cn/blog_migrate/128c0614abf7d286cd96e989da60cc40.png

P3先使用socket系统调用创建一个网络套接字对象,socket系统调用返回用户一个描述符,有了套接字对象之后还要使用bind系统调用将套接字绑定到本地端口6666。这样主机2的套接字就可以等待着被连接。主机1进行相同的操作

不妨将网络套接字简单地理解为要申请一块内核存储空间用于接收或发送数据,返回的描述符理解为指向套接字的一个指针

接下来P1进程使用connect系统调用指明要把fd所指向的套接字连接到主机2IP地址的6666端口,这个系统调用就会使得这两个套接字之间建立起应用层连接

它们在传输层可以指定使用TCP或者UDP协议

https://i-blog.csdnimg.cn/blog_migrate/d9c9f2934839ab1376dd6cfda623aa97.png

接下来两台主机就可以通过套接字进行通信。比如P1想给P3发送数据包,P1首先在自身用户区准备好数据,然后使用write系统调用指明要往fd所指向的套接字当中写入数据,设备独立性软件接收到write系统调用后就会把P1准备好的数据复制到套接字所对应的这片内核缓冲区当中

https://i-blog.csdnimg.cn/blog_migrate/8063c1c6cb5a0aca8c0e7750fa6b5eee.png

接下来设备独立性软件调用网络控制器的驱动程序处理这片数据,驱动程序负责把准备好的数据输出到网络设备上

https://i-blog.csdnimg.cn/blog_migrate/1759596b3c5c385d52827748fb7c04d0.png

接下来网络控制器就可以把这些数据包发送到网络上,最后发到主机2的网络控制器,这个网络控制器接收到数据包后会向主机2发送一个中断信号。主机2的中断处理程序发现中断信号来自于网络控制器,调用网络控制器驱动程序,让驱动程序把网络程序收到的这些数据复制到6666端口所对应的内核缓冲区中

https://i-blog.csdnimg.cn/blog_migrate/6cfa882b5b64334df6a012923af9951b.png

P3要接收网络数据包只需要使用read系统调用,指明要从fd所指的套接字对象当中读出数据包,设备独立性软件会从缓冲区里边把这些数据复制到P3的用户区当中

https://i-blog.csdnimg.cn/blog_migrate/cde50d6d3ff3033975bc651b388ea340.png

P2进程也可以使用socket系统调用申请一个新的套接字并绑定另一个端口。不同的套接字绑定不同的端口,因此网卡接收到许多数据包之后才可以根据数据包里面指明的端口信息把数据包放到对应的套接字对象这

https://i-blog.csdnimg.cn/blog_migrate/a87458eaa21557dd8272e0323443cb1b.png

阻塞/非阻塞IO

https://i-blog.csdnimg.cn/blog_migrate/a1bda9e2464545a9abe6177f0b8363f2.png

scanf等待键盘输入,只要不输入进程就无法继续向下执行

进程准备的数据在用户区。发出write系统调用想要把数据写入磁盘,即便磁盘正在忙碌,设备独立性软件也会迅速响应系统调用请求先把数据复制到内核区,用户进程只要完成数据复制就可以继续往下执行,内核再慢慢把这些数据写入磁盘

统一标准的设备驱动程序接口

https://i-blog.csdnimg.cn/blog_migrate/7a6eebb1314dbbf2e36c105816d87b28.png

要调用不同公司编写的驱动程序还得修改函数调用的代码,也就是要频繁地修改操作系统内核,这显然不科学

https://i-blog.csdnimg.cn/blog_migrate/666f4e08a0cd98f76eea26228bf886c7.png

https://i-blog.csdnimg.cn/blog_migrate/66fd3060630a74af950ab963fcbbfd85.png

I/O 核心子系统

https://i-blog.csdnimg.cn/blog_migrate/8c67c15029984298728e1b89ccaf1779.png

https://i-blog.csdnimg.cn/blog_migrate/f8617224db67dd80b54b1a097ac943cb.png

这些功能要在哪个层次实现?

https://i-blog.csdnimg.cn/blog_migrate/2f06fdc1521b6cf389ab1f9e7a48e0fb.png

I/O 调度

https://i-blog.csdnimg.cn/blog_migrate/aa7aa62730868a2fb0aa0c37f6cd0d47.png

设备保护

https://i-blog.csdnimg.cn/blog_migrate/ce971775e34d2dedf45efa8b2201a7f2.png

假脱机技术(SPOOLing技术)

https://i-blog.csdnimg.cn/blog_migrate/eca512db87aa9b2e6904ee45651ca999.png

什么是脱机技术

https://i-blog.csdnimg.cn/blog_migrate/921fde1f63a78f3cc45f86eaab79a195.png

https://i-blog.csdnimg.cn/blog_migrate/cdb5282409a8fe9651717ee387a2c2fc.png

假脱机技术 —— 输入井和输出井

https://i-blog.csdnimg.cn/blog_migrate/f871b439a8d2930e7943f5b60e4b0d5d.png

https://i-blog.csdnimg.cn/blog_migrate/717da4706083a9e40e84b659a6028ec2.png

https://i-blog.csdnimg.cn/blog_migrate/dab796ec6bb8e497ff25fadde7ce6b66.png

显然“输入进程”和“输出进程”肯定需要和用户进程并发地执行才可以完成这种模拟脱机输入和脱机输出的过程,因此SPOOLing技术肯定需要有多道程序技术的支持

https://i-blog.csdnimg.cn/blog_migrate/a8ac9a6fcd911074086c24a584c74530.png

https://i-blog.csdnimg.cn/blog_migrate/8033dcf247a158d10bb81a8e12ca86c5.png

共享打印机原理分析

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

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

https://i-blog.csdnimg.cn/blog_migrate/74ab445dbfc41ba5d9f70c2820f0261a.png

https://i-blog.csdnimg.cn/blog_migrate/6d36636b073fcf3019f10b867621b039.png

这个表就是用来说明用户的打印数据放在哪个缓冲区里,存放在什么地方等等这一系列信息

https://i-blog.csdnimg.cn/blog_migrate/8bf625e9ce2ed7d70053c52a30844cde.png

设备的分配与回收

https://i-blog.csdnimg.cn/blog_migrate/4cd6477772ea9db3268b91fee5bb7115.png

设备分配时应考虑的因素

设备分配时应考虑的因素:设备的固有属性,设备分配算法,设备分配中的安全性

设备的固有属性可分为三种:独占设备、共享设备、虚拟设备

独占设备——一个时段只能分配给一个进程(如打印机)

共享设备——可同时分配给多个进程使用(如磁盘),各进程往往是宏观上同时共享使用设备,而微观上交替使用

虚拟设备——采用 SPOOLing 技术将独占设备改造成虚拟的共享设备,可同时分配给多个进程使用(如采用 SPOOLing 技术实现的共享打印机)

设备的分配算法:先来先服务,优先级高者优先,短任务优先 ……

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

安全分配方式:为进程分配一个设备后就将进程阻塞,本次I/O完成后才将进程唤醒。(eg:考虑

进程请求打印机打印输出的例子)

一个时段内每个进程只能使用一个设备

优点:破坏了“请求和保持”条件,不会死锁

缺点:对于一个进程来说,CPU和I/O设备只能串行工作

不安全分配方式:进程发出I/O请求后,系统为其分配I/O设备,进程可继续执行,之后还可以发出新的I/O请求。只有某个I/O请求得不到满足时才将进程阻塞

一个进程可以同时使用多个设备

优点:进程的计算任务和I/O任务可以并行处理,使进程迅速推进

缺点:有可能发生死锁(死锁避免、死锁的检测和解除)

静态分配和动态分配

静态分配:进程运行前为其分配全部所需资源,运行结束后归还资源

破坏了“请求和保持”条件,不会发生死锁

动态分配:进程运行过程中动态申请设备资源

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

https://i-blog.csdnimg.cn/blog_migrate/d2886d13001eb75cd4c53cae1875cc64.png

一个系统中可能会有多个通道

1.DCT

https://i-blog.csdnimg.cn/blog_migrate/f8304e4fe7d3b4636e477feb239f9884.png

2.COCT

https://i-blog.csdnimg.cn/blog_migrate/fcb54d162916d0cc05e8838f95e27766.png

3.CHCT

https://i-blog.csdnimg.cn/blog_migrate/5ad37ba5470dda40de7ecb37dec9d646.png

4.SDT

https://i-blog.csdnimg.cn/blog_migrate/dafdab88dcf7ed8e540ae07268a97c50.png

设备分配的步骤

1.根据进程请求的物理设备名查找SDT

https://i-blog.csdnimg.cn/blog_migrate/fd96817d47326ccb0f6349b7ac85b577.png

2.根据SDT找到DCT,若设备忙碌则将进程PCB挂到设备等待队列中,不忙碌则将设备分配给进程

https://i-blog.csdnimg.cn/blog_migrate/58af4797171ca45abc33caf82b73ef65.png

除了分配这个设备之外,还需要把这个设备对应的控制器也分配给这个进程,所以系统会根据“指向控制器表的指针”这个字段找到这个设备对应的控制器控制表COCT

3.根据DCT找到COCT,若控制器忙碌则将进程PCB挂到控制器等待队列中,不忙碌则将控制器分配给进程

https://i-blog.csdnimg.cn/blog_migrate/e66d6a94a9afe73d02819d32e7c45ddc.png

4.根据COCT找到CHCT,若通道忙碌则将进程PCB挂到通道等待队列中,不忙碌则将通道分配给进程

https://i-blog.csdnimg.cn/blog_migrate/add16e4476d70094f8261070968082b6.png

设备分配步骤的改进

缺点:

用户编程时必须使用“物理设备名”,底层细节对用户不透 明,不方便编程

若换了一个物理设备,则程序无法运行

若进程请求的物理设备正在忙碌,则即使系统中还有同类型 的设备,进程也必须阻塞等待

改进方法:

建立逻辑设备名与物理设备名的映射机制,用户编程时只需提供逻辑设备名

https://i-blog.csdnimg.cn/blog_migrate/a570e351f7a5a30059ba6d971a03582e.png

https://i-blog.csdnimg.cn/blog_migrate/434547a86063fac2d826d0d4d82a6311.png

缓冲区管理

https://i-blog.csdnimg.cn/blog_migrate/dcab1a1171681a6d9c82e9fcea6c5468.png

什么是缓冲区?有什么作用?

https://i-blog.csdnimg.cn/blog_migrate/922b7e0fed78cf38079cd1314f807eb6.png

联想寄存器中的寄存器也称为快表

缓冲区有什么作用?

https://i-blog.csdnimg.cn/blog_migrate/c0b97d54947086c383857384bfed507b.png

单缓冲

https://i-blog.csdnimg.cn/blog_migrate/d2ac18669e9518969b4625520a946e34.png

https://i-blog.csdnimg.cn/blog_migrate/488eba9f6b2ecc94667b1a7356e11294.png

https://i-blog.csdnimg.cn/blog_migrate/9dddb5196a2e84e384d862da326bd68c.png

https://i-blog.csdnimg.cn/blog_migrate/f0ee9336e67818d76b28cce2778ff525.png

双缓冲

https://i-blog.csdnimg.cn/blog_migrate/4d7b7a4ac81d95c610532158f49a01c1.png

https://i-blog.csdnimg.cn/blog_migrate/f2f78eb08e150877ed10677272903189.png

如果采用双缓冲结构,并且 T<C+M 的话,那很难找到一个和刚开始的这种初始状态一模一样的状态

结论:采用双缓冲策略,处理一个数据块的平均耗时为 Max (T, C+M)

使用单 / 双缓冲在通信时的区别

https://i-blog.csdnimg.cn/blog_migrate/18647f4433a59abef8a53e189d5e7757.png

https://i-blog.csdnimg.cn/blog_migrate/dd41e5d20e8cd9f3daacc8a6a289cff5.png

循环缓冲区

https://i-blog.csdnimg.cn/blog_migrate/2c99fdfe158da981d4f7a2d6542261ca.png

缓冲池

https://i-blog.csdnimg.cn/blog_migrate/919dba364f9722d02253c310e0e9da46.png

1.输入进程请求输入数据

从空缓冲队列中取出一块作为收容输入数据的工作缓冲区(hin)。冲满数据后将缓冲区挂到输入队列队尾

2.计算进程想要取得一块输入数据

从输入队列中取得一块冲满输入数据的缓冲区作为“提取输入数据的工作缓冲区(sin)”。缓冲区读空后挂到空缓冲区队列

3.计算进程想要将准备好的数据冲入缓冲区

从空缓冲队列中取出一块作为“收容输出数据的工作缓冲区(hout)”。数据冲满后将缓冲区挂到输出队列队尾

4.输出进程请求输出数据

从输出队列中取得一块冲满输出数据的缓冲区作为“提取输出数据的工作缓冲区(sout)”。缓冲区读空后挂到空缓冲区队列