输入/输出

外部设备、接口部件、总线以及相应的管理软件统称为计算机的输入/输出系统,简称I/O系统

操作系统IO接口对于所有设备都应该是相同的,即设备无关性

基本功能

特点

交换数据的过程

输入过程:

输出过程:

性能

按照主要完成的工作可以分为以下二类:

I/O系统的性能对CPU的性能有很大的影响,若两者的性能不匹配,I/O系统就有可能成为整个系统的瓶颈

评价参数

I/O 硬件管理

I/O设备

设备控制器

IO设备由机械部件与电子部件构成 电子部件称为设备控制器或者适配器

控制器的任务: 将串行的比特流转换为字节块,并完成纠错工作

无条件IO

在程序的适当位置直接安排I/O指令,当程序执行到这些I/O指令时,CPU默认外设始终是准备就绪的(I/O总是准备好接收CPU的输出数据,或总是准备好向CPU输入数据),无需检查I/O的状态,就进行数据的传输;

硬件接口电路和软件控制程序都比较简单。输入时,必须确保CPU执行I/O指令读取数据时,外设已将数据准备好;输出时,必须确保外部设备的数据锁存器为空,即外设已将上次的数据取走,等待接收新的数据,否则会导致数据传送出错,但一般的外设难以满足这种要求

内存映射IO

控制器使用寄存器或者一块操作系统可以操作的RAM进行通信

给控制寄存器分配一个IO端口,所有IO端口形成IO端口空间吗,可以通过一条特殊的指令来来对控制寄存器进行读取或写入

IN REG, PORT ; CPU从指定IO端口读取数据到REG寄存器OUT PORT, REG ; CPU写入数据

另外一种方式是将IO设备寄存器映射到内存上,对内存的读写都会通过总线信号映射到IO设备的寄存器。内存映射IO优点:

缺点:

计算机如何确定一个内存地址对应的是内存还是IO设备寄存器?现代的计算机大都包含高速内存总线,对内存的读写可以通过专用总线来进行,而对IO设备的读写则可以通过通用总线。

第二种设计是通过一个内存总线探查设备,该设备会对IO设备访问放行。

第三种设计则是对内存地址进行过滤,规定一个区域的地址为IO内存映射。

中断

屏幕截图 2021-01-07 163944

中断: 是指CPU在正常运行程序时,由于内部/外部事件(或由程序)引起CPU中断正在运行的程序,而转到为中断事件服务的程序中去,服务完毕,再返回执行原程序的这一过程。中断具有 随机性

当设备发起一个中断信号,这个信号会被中断控制器芯片检测到。如果有设备同时发起中断,此时该设备中断不会被处理,不被处理的设备会不断发起信号,直至得到CPU的服务。

中断控制器在放置一个数字信号表明需要处理哪个设备,这个数字信号被称为中断向量,中断向量是一个服务过程的程序地址,CPU需要保存相关好相关信息,如PC,然后跳转到指定地址指向相应的服务过程。

中断的作用:

由于现代CPU都会采用流水线技术,所以中断后一些指令执行了一部分,怎么确保这个状态是明确的

屏幕截图 2021-01-07 164930

中断的类型

中断的基本功能

批注 2020-02-20 191316

中断号: 是系统分配给每个中断源的代号,以便识别和处理。中断号在中断处理过程中起到很重要的作用。

中断触发方式: 是指外设以什么逻辑信号去申请中断,即边沿触发和电平触发两种方式

中断排队方式: 当系统有多个中断源时,就可能出现同时有几个中断源都申请中断,而处理器在一个时刻只能响应并处理一个中断请求;为此,要进行中断排队。处理器按“优先级高的先服务”的原则提供服务

当CPU正在处理某个中断时,会出现优先级更高的中断源申请中断;为了使更紧急的、级别更高的中断源及时得到服务,需要暂时打断(挂起)当前正在执行的中断服务程序,去处理级别更高的中断请求,处理完成后再返回被打断了的中断服务程序继续执行

但级别相同或级别低的中断源不能打断级别高的中断服务,这就是所谓的中断嵌套

可屏蔽中断可以进行中断嵌套。NMI不可以进行中断嵌套

处理器用指令来控制中断屏蔽触发器的状态,从而控制是否接受某个特殊外设的中断请求

处理器内部也有一个中断允许触发器,只有当其为“1”(即开中断),CPU才能响应外部中断

指CPU响应和处理中断请求的先后次序

硬件响应优先序:未被屏蔽的几个中断源同时提出申请时,CPU选择服务对象的顺序由硬件电路实现,用户不能修改

软件服务优先序:在各中断服务程序开头,用软件设置自己的中断屏蔽字,以此改变实际服务顺序

当CPU收到外设的中断请求后,如果当前一条指令已执行完,且允许中断,CPU进入中断响应周期,发出中断应答信号完成一个中断响应周期

读取中断源的中断号,完成中断申请与中断响应的握手过程

批注 2020-02-20 193226

IO软件原理

IO软件的目标

程序控制IO

让CPU做全部工作 程序控制IO伪代码:

copy_from_user(buffer,p,count);for(i = 0;i<count;i++){ // 循环写操作    while (*p_regs != READY){ // 检查IO设备是否就绪        *p_regd = p[i]; // 向IO设备写入数据    }}return_to_user();

这是一种早期计算机采用的输入/输出方式,数据在计算机和外设之间的传送全部靠计算机程序控制;计算机执行I/O指令时,先获取外设状态,并根据外设的状态决定下一步操作

批注 2020-02-20 185443

何时对何设备进行输入输出操作完全受CPU控制,外围设备与CPU处于异步工作关系,数据的输入/输出都要经过CPU

优点: 计算机和外设之间能够同步,控制简单,硬件简单。

缺点: 要占用CPU的大量时间用来查询外设的状态。

设备状态字寄存器:用来标志设备的工作状态,以便接口对外部设备进行监视

中断驱动IO

程序控制IO浪费的地方在于如果IO设备不就绪,CPU的时间就浪费在无谓的循环的上面了。

中断驱动IO改进的地方在于当IO设备就绪,由IO设备主动产生一个中断,CPU在中断之前可以去调度其他进程,CPU接到外设的通知后暂停现行的工作,转入中断服务程序,和外设交换数据,等中断程序处理完毕后,再返回到被中断的原程序中继续以前被暂停的工作

优点: 节约CPU时间,实时性好。

缺点: 控制电路相对复杂,服务开销较大(现场和断点的保护)。

应用场合: 实时性要求高,且数据传输量又不大的场合。

使用DMA的IO

是一种完全由硬件执行的I/O交换方式,让DMA控制数据传输,而不必打扰CPU。本质上还是程序控制IO,只不过使用了DMA后CPU可以每次写入一整个缓冲区的数据,加入一个中间层 从CPU-设备 变成 CPU - DMA - 设备

屏幕截图 2021-01-07 162819

当外设准备好后,通知DMA控制器,DMA控制器从CPU接管总线,并完成外设和内存之间的大量数据传输;传输完成后DMA控制器将总线控制权交还给CPU,整个数据交换的过程不需要CPU参与

设备控制器内部缓存区存在的原因

优点: 既有中断的优点,同时又降低了服务开销。

缺点: 控制电路更加复杂。

应用场合: 高速、大批量数据传输。

DMA控制器的两种工作状态

传输步骤

申请阶段:一个设备接口试图通过总线直接向另一个设备发送数据(一般是大批量的数据),它会先向CPU发送DMA请求信号

响应阶段:CPU收到DMA请求信号后,在当前的总线周期结束后,会按DMA信号的优先级和提出DMA请求的先后顺序响应DMA信号

数据传送阶段:CPU对某个设备接口响应DMA请求时,会让出总线控制权;于是在DMA控制器的管理下,外设和存储器直接进行数据交换,而不需CPU干预

传送结束阶段:数据传送完毕后,设备接口会向CPU发送DMA结束信号,交还总线控制权

操作类型

数据传送:把源地址的数据传输到目的地址去(存储器或I/O)

数据校验:不进行数据传输,只对数据块内部的每个字节进行某种校验;这种数据校验一般安排在读数据块之后,以便校验所读的数据是否有效

数据检索:不进行数据传输,只是在指定的内存区域内查找某个关键字节或某几个数据位是否存在

操作方式

零拷贝

对IO设备的读写,不将IO设备的数据先复制到内核空间,然后再复制到用户空间,以此获得更高的读写性能

这就是由 DMA 所完成的

用户态直接IO

硬件上的数据直接拷贝至了用户空间,不经过内核空间

屏幕截图 2022-06-14 153015

mmap+write

实现内核缓冲区与应用程序内存的共享,省去了将数据从内核读缓冲区(read buffer)拷贝到用户缓冲区(user buffer)的过程,然而内核读缓冲区(read buffer)仍需将数据到内核写缓冲区(socket buffer)

屏幕截图 2022-06-14 153136

主要的用处是提高 I/O 性能,特别是针对大文件。对于小文件,内存映射文件反而会导致碎片空间的浪费

sendfile

#include ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

简化通过网络在两个通道之间进行的数据传输过程。sendfile 系统调用的引入,不仅减少了 CPU 拷贝的次数,还减少了上下文切换的次数

屏幕截图 2022-06-14 154509

sendfile 存在的问题是用户程序不能对数据进行修改

sendfile+ DMA gather copy

将内核空间(kernel space)的读缓冲区(read buffer)中对应的数据描述信息(内存地址、地址偏移量)记录到相应的网络缓冲区( socket buffer)中,由 DMA 根据内存地址、地址偏移量将数据批量地从读缓冲区(read buffer)拷贝到网卡设备中

屏幕截图 2022-06-14 154734

同样也不能对数据进行修改

splice

splice(fd_in, off_in, fd_out, off_out, len, flags);

跟sendfile很像

屏幕截图 2022-06-14 155235

写时复制

当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么就需要将其拷贝到自己的进程地址空间中

缓冲区共享

每个进程都维护着一个缓冲区池,这个缓冲区池能被同时映射到用户空间(user space)和内核态(kernel space),内核和用户共享这个缓冲区池,这样就避免了一系列的拷贝操作

屏幕截图 2022-06-14 155416

通道和I/O处理机

在复杂的计算机系统中,外围设备的台数一般比较多,设备的种类、工作方式和工作速度的差别很大,为了把对外围设备的管理工作从CPU中分离出来,采用通道或I/O处理机方式

通道是能够专门执行I/O指令的处理机,它可以实现对外围设备的统一管理,以及外设与主存之间的数据传输

I/O处理机是通道方式的进一步发展,它的结构更接近于一般处理机。

IO软件层次

屏幕截图 2021-01-13 162227

中断处理程序

在响应一个特定中断的时候,内核会执行一个函数,该函数叫中断处理程序

隐藏中断的最好方式是将会引起中断的操作阻塞起来。但中断处理需要花费相当多的CPU指令。

设备驱动程序

每个链接到计算机上的IO设备都需要某些设备特定代码进行控制,称之为设备驱动程序

USB设备驱动通过堆栈式来达到不同的设备基于同样的技术效果。为了访问设备的硬件,大多数操作系统都要求驱动程序运行在内核中,这也是系统崩溃的一个源头之一。

可重入性:驱动必须意识到第一次调用完成之前第二次调用会到来

与设备无关的IO软件

对于每种设备类型类型,操作系统定义一组驱动程序必须支持的函数,设备名可以使用设备类型+次版本号来编码,同样,对文件系统的保护规则也适用于设备。

屏幕截图 2021-01-14 155141

双缓冲:使用两个缓冲区交替使用,当一个满了之后直接复制到用户空间,另外一个接替上

环形缓冲:通过两个前后指针不断往前走实现一个逻辑上无限的缓冲区

对于编程错误,如向一个输入设备发出了一个输出请求,操作系统直接返回错误码即可。

但对于实际的IO错误,应由驱动程序决定做什么,驱动程序解决不了,再向高层传递。但错误抛到高层,操作系统实际上除了返回一个错误码并失败外,并不存在其他多少事情可以做。

对于只允许有限数量进程使用的设备,操作系统必须对设备的使用请求进行检查,可以将得不到设备的进程调用失败掉。另外一种方式可以对得到设备的进程调用进行阻塞,而不是让其失败。

上层软件屏蔽掉不同设备的的块大小不一致

用户空间的IO软件

部分IO操作在用户空间完成,这是通过库过程来实现,也有通过假脱机的方式及守护进程的方式实现。

假脱机:IO设备通过一个文件来代表IO处理,用户进程直接处理这个文件来实现IO。

守护进程:用户进程通过将文件放置于某个目录下的,该目录称为假脱机目录,只有一个允许访问IO设备的进程,来读取这些文件进行操作,这个进程就是守护进程。

屏幕截图 2021-01-14 170929

IO模型

sequenceDiagram  title BIO  进程 ->> 操作系统: read  loop    操作系统 ->> 操作系统: 等待数据  end  操作系统 ->> 进程: 返回数据
sequenceDiagram  title 多路复用非阻塞IO  进程 ->> 操作系统: epoll  loop    操作系统 ->> 操作系统: 等待通道数据就绪  end  操作系统 ->> 进程: 返回数据就绪的通道  进程 -->> 通道1: 非阻塞read  通道1 -->> 进程: 有没数据都立即返回  进程 -->> 通道2: 非阻塞read  通道2 -->> 进程: 有没数据都立即返回
sequenceDiagram  title 异步IO  进程 ->> 操作系统: read  操作系统 ->> 进程: 立即返回  loop    操作系统 ->> 操作系统: 准备数据  end  操作系统 -->> 进程: 异步通知进程数据到达

阻塞与非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

Linux 默认的 read 与 write 系统调用就是阻塞的,但如果设置了 O_NONBLOCK O_NONBLOCK 标志,read 如果没有读到数据,则会简单返回 -EAGAIN,不像默认的 read 会等待直至有数据进来

同步与异步

同步和异步关注的是消息通信机制

所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。

而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

异步 IO 的实现有 Windows 的 IOCP、C++的 Boost asio、Linux 下的 aio

IO 多路复用

特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回

epoll select poll 都属于这种机制

/* 函数中定义了几个描述符集合,然后内核会关注这些描述符集合中哪些描述符就绪了,然后返回已就绪的描述符个数,业务程序需要遍历所有的描述符集合来找到可处理的描述符集合 */int select (int __nfds, fd_set *__readfds, fd_set *__writefds, fd_set *__exceptfds, struct timeval *__timeout)/* 和 select 函数相比,poll 函数的改进之处主要就在于它允许一次监听超过 1024 个文件描述符 */int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout);

epoll 的优化点在于 epoll_wait 会返回就绪的描述符,而未就绪的描述符不返回

磁盘

磁盘驱动器不本身就包含一个微控制器,这允许磁盘驱动器发出一些诸如高速缓存、坏块重映射等高级命令

结构

2020321928

RAID

Redundant Array of Independent Disks,独立磁盘冗余阵列。通过将多个磁盘组合起来,使用RAID控制器代替磁盘驱动器以完成常规的操作。

屏幕截图 2021-01-15 161911

磁盘格式化

低级格式化:对每个扇区设置前导码,ECC,由于前导码与ECC需要占用一定空间,所以可用磁盘容量总比宣传的小

磁盘分区:0扇区包含主引导记录(MBR),MBR包含一些引导代码及分区表

高级格式化:设置引导块、空闲存储管理、根目录、文件系统

系统的启动流程:BIOS运行->读入MBR并跳转->执行引导程序->找到操作系统内核载入内存执行

磁盘臂调度算法

读写磁盘块所需要的时间多少由以下三个因素决定:

调度算法:

旋转时间与寻道时间十分影响性能,所以一次只读取一个或者两个扇区效率很低。现代的磁盘控制器都拥有高速缓存,每次读取多个扇区,并将其缓存。

错误处理

对于磁盘坏块的处理,可以在控制器或者在操作系统对他们进行处理。

在控制器中,处理的思想都是一样的,都是使用备用块来替代坏块。

而在操作系统级别进行处理,操作系统必须获的所有坏块列表,并将其进行重映射。

稳定存储器

保持磁盘的数据一致性。

屏幕截图 2021-01-18 155800

Partial Stroking

磁盘的两个时间:

  1. 平均延时:把盘面旋转,把几何扇区对准悬臂位置的时间
  2. 平均寻道时间:盘面旋转之后,悬臂定位到扇区的的时间

磁盘转速越高,平均延时就更低,如果不进行寻道,就可以剩下寻道的时间,同时越在磁盘外圈的数据访问越快,相同的角速度下,越外圈线速度越快,同样的时间扫过的扇区就越多

SSD

不同的颗粒可以存储更多的数据,但同时也更慢

SLC 的芯片,可以擦除的次数大概在 10 万次,MLC 就在 1 万次左右,而 TLC 和 QLC 就只在几千次

SSD会进行磁盘整理

磨损均衡

某些块写入比其他块更加频繁,这些块寿命会更短

如果一个物理块被擦写的次数多了,FTL 就可以将这个物理块,挪到一个擦写次数少的物理块上

TRIM命令

为了避免磨损均衡在搬运很多已经删除了的数据,现在的操作系统和 SSD 的主控芯片,都支持 TRIM 命令。这个命令可以在文件被删除的时候,让操作系统去通知 SSD 硬盘,对应的逻辑块已经标记成已删除了

写入放大

当 SSD 硬盘的存储空间被占用得越来越多,每一次写入新数据,可能不得不去进行垃圾回收,合并一些块里面的页,然后再擦除掉一些页

解决写入放大,需要在后台定时进行垃圾回收,在硬盘比较空闲的时候,就把搬运数据、擦除数据、留出空白的块的工作做完

最大化SSD使用效率

AeroSpike 这个 KV 数据库利用了 SSD的一些特性来加快读写速度:

  1. 尽可能去写一个较大的数据块,而不是频繁地去写很多小的数据块
  2. 读取数据的时候就可以读取小数据,不用担心擦写寿命问题
  3. 持续地进行磁盘碎片整理,一旦一个物理块里面的数据碎片超过 50%,就把这个物理块搬运压缩,然后进行数据擦除,确保磁盘始终有足够的空间可以写入,保 SSD 硬盘的写放大效应尽可能小

此外:

  1. 对于SSD而言,没了HDD的寻址代价,随机读写和顺序读写性能类似,就地更新不会获得任何 IOPS 优
  2. SSD对于空间局部性也没有优势,预取这种额外的数据访问,不仅会浪费 I/O 带宽,而且会不必要地磨损 SSD
  3. SSD可以执行并行小IO充分利用内部的并行性

时钟

时钟硬件

可编程时钟:石英晶体每次震荡会将递减计数器,计数器到0时会触发一个中断,软件可以自定义这个计数器来实现对时钟的编程。

屏幕截图 2021-01-18 160445

时钟软件

通过时钟来维护现在的时间,为了防止32位内存溢出,可以使用64位计数器,但代价过高。也可使用以秒为单位。

时钟的每次中断就将时间片-1,当时间片为0,就得重新调度程序。

每次滴答对进程表项的某个域+1来实现记录进程运行时间。

进程可以请求操作系统在一定的时间间隔后向它报警。

一个物理时钟为了模拟出多个时钟,可以通过维护一张表,每次时间发生更新就查找是否达到表中所需要的时刻,如果达到了,就进行触发。然后继续重复这个步骤。

检测死机之类的问题。如果操作系统可以定时清除计数器,当某个时刻计数器超过某个阈值,就可以确定已经死机了,此时软件介入处理。

软定时器

IO有两种方式:中断和轮询。

现代CPU的中断开销是很大的,但轮询的响应时间又会比较高。

所以为了达到一个取舍,可以使用一个软定时器定时中断来进行IO。

外设

输入软件

键盘软件

鼠标软件

鼠标发送的消息包含:$\Delta$x $\Delta$y 按钮 ,通常为3字节

鼠标单击与双击则是由GUI来进行区分的

输出软件

为了控制终端进行文字输出,程序使用了一种被称为“转义序列”的东西来控制终端。

瘦客户机

电源管理

有两种方法减少电量消耗:

  1. 关闭不用的某些计算机硬件
  2. 使应用程序耗能更低

硬件问题

网络IO