输入/输出

外部设备、接口部件、总线以及相应的管理软件统称为计算机的输入/输出系统,简称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内存映射。

中断

sequenceDiagram    participant 设备 as 设备    participant 中断控制器 as 中断控制器    participant CPU as CPU    设备 ->> 中断控制器: 1. 设备完成工作    中断控制器 ->> CPU: 2. 中断控制器发出中断    CPU ->> 中断控制器: 3. CPU响应中断

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

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

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

中断的作用:

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

屏幕截图 2021-01-07 164930

中断的类型

中断的基本功能

批注 2020-02-20 191316

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

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

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

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

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

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

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

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

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

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

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

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

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

flowchart TD    主程序指令 --> 判断{是否有中断请求}    判断 -- N --> 主程序指令    判断 -- Y --> 关闭中断及中断响应 --> 中断识别 --> 保护现场 --> 中断服务 --> 恢复现场 --> 开中断 --> 中断返回 --> 主程序指令

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  操作系统 -->> 进程: 异步通知进程数据到达

磁盘 IO 机制

标准IO

数据写入高速页缓存 应用就认为写入已经完成 此时操作系统再异步写入磁盘或者调用sync强制写入

直接IO

DBMS就采用的此种方式读写数据

这种方式如果访问的数据不在应用缓存中 则每次都需要访问磁盘

只有当数据被成功写入磁盘方法才会返回 一般用在对数据安全性较高的场景

读数据写数据不会阻塞 读数据调用之后会马上返回 应用需要通过轮询等方式来询问数据是否就绪以获取

屏幕截图 2020-09-28 132039

将内存中的一块区域与磁盘中的文件关联起来 将应用对内存的访问映射为对磁盘的访问

阻塞与非阻塞

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

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

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

同步与异步

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

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

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

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

同步异步 阻塞非阻塞

组合方式性能分析
同步阻塞最常用的一种用法,使用也是最简单的,但是IO性能一般很差,CPU大部分处于空闲状态
同步非阻塞提升IO性能的常用手段,就是将IO的阻塞改成非阻塞方式,尤其在网络IO是长连接同时传输数据也不是很多的情况下,提升性能非常有效。这种方式通常能提升/O性能,但是会增加CPU消耗,要考虑增加的IO性能能不能补偿CPU的消耗,也就是系统的瓶颈是在IO上还是在CPU上
异步阻塞这种方式在分布式数据库中经常用到,例如,在一个分布式数据库中写一条记录,通常会有一份是同步阻塞的记录,还有2~3份备份记录会写到其他机器上,这些备份记录通常都采用异步阻塞的方式写I/O。异步阻塞对网络O能够提升效率,尤其像上面这种同时写多份相同数据的情况
异步非阻塞这种组合方式用起来比较复杂,只有在一些非常复杂的分布式情况下使用,集群之间的消息同步机制一般用这种IO组合方式。如Cassandra的Gossip通信机制就采用异步非阻塞的方式它适合同时要传多份相同的数据到集群中不同的机器,同时数据的传输量虽然不大却非常频繁的情况。这种网络IO用这种方式性能能达到最高

异步与非阻塞虽然能能提高IO性能 但是线程数量的增加会增加CPU的消耗 并且会导致设计复杂度的上升

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);

selec 系统调用会传入fd列表,内核返回准备好的fd列表,select 是在用户空间对fd轮询,弊端在于需要在内核与用户空间之间拷贝fd

select 调用

epoll 的优化点在于 fd 是在用户态与内核态的共享空间中,同时 epoll_wait 会返回就绪的描述符,而未就绪的描述符不返回

批注 2020-06-18 144854

时钟

时钟硬件

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

屏幕截图 2021-01-18 160445

时钟软件

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

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

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

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

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

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

软定时器

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

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

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

外设

输入软件

键盘软件

鼠标软件

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

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

输出软件

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

瘦客户机

电源管理

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

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

硬件问题

网络IO