本章任务
让应用能便捷地访问外设
侏罗猎龙
进一步增加了多种设备驱动程序的侏罗盗龙操作系统 – DeviceOS的总体结构如下图所示
设备驱动程序
CPU访问外设的方式
在RISC-V环境中,把外设相关的控制寄存器映射为某特定的内存区域(即MMIO映射方式),然后CPU通过读写这些特殊区域来访问外设(即PIO访问方式)
外设可以通过DMA来读写主机内存中的数据,并可通过中断来通知CPU。外设并不直接连接CPU,这就需要了解RISC-V中的平台级中断控制器(Platform-Level Interrupt Controller,PLIC),它管理并收集各种外设中断信息,并传递给CPU。
裸机设备驱动程序
会有发现外设、初始化外设和I/O读写与控制等操作
-
发现外设
-
初始化外设
-
准备发给设备的命令:初始化完毕后,设备驱动在收到上层内核发出的读写扇区/磁盘块的请求后,就能通过
virtqueue
传输通道发出virtio-blk
设备能接收的I/O命令和I/O buffer的区域信息。 -
通知设备
-
接收设备通知
virtio-blk
设备收到信息后,会通过DMA操作完成磁盘数据的读写,然后通过中断或其他方式让设备驱动知道命令完成或命令执行失败。由于中断处理例程的完整操作与操作系统内核相关性较大,所以在裸机设备驱动中,没有实现这部分的完整功能,而只是提供了表示收到中断的操作。
操作系统设备驱动程序
由于有了裸机设备驱动程序的实现,对于操作系统设备程序,可以直接访问裸机设备驱动程序的各种功能。这样操作系统设备驱动程序的复杂性和代码量大大降低
不过还需解决以下问题:
- 发现具体计算机(如
virt machine
)中的设备(即与设备交互的设备控制寄存器的MMIO内存地址); - 与其它操作系统内核模块(如文件系统、同步互斥、进程管理等)的对接;
- 封装裸机设备驱动程序,提供操作系统层面的I/O设备访问能力(初始化、读写、控制等操作)。
另外,操作系统还需满足裸机设备驱动程序对操作系统的需求,并能对各种外设进行统一的管理,这主要集中在硬件平台级的支持。主要的服务能力包括:
- 在硬件平台层面发现具体计算机(如
virt machine
)中的各种外设的能力; - 在硬件平台层面的外设中断总控能力,即在外设中断产生后,能分析出具体是哪个外设产生的中断,并进行相应的处理;
- 给裸机设备驱动程序提供操作系统级别的服务能力,以
virtio-drivers
为例,OS需要提供HAL
Trait的具体实现,即驱动进行I/O操作所需的内存动态分配。
I/O传输方式
1、Programmed I/O
PIO指CPU通过发出I/O指令的方式来进行数据传输
PIO方式可以进一步细分为基于Memory-mapped的PIO(简称MMIO)和Port-mapped的PIO(简称PMIO)
-
MMIO是将I/O设备物理地址映射到内存地址空间,这样CPU就可以通过普通访存指令将数据送到I/O设备在主存上的位置,从而完成数据传输。
-
对于采用PMIO方式的I/O设备,它们具有自己独立的地址空间,与内存地址空间分离。CPU若要访问I/O设备,则需要使用特殊的I/O指令,如x86处理器中的
IN
、OUT
指令,这样CPU直接使用I/O指令,就可以通过PMIO方式访问设备。
2、Interrupt based I/O
如果采用PIO方式让CPU来获取外设的执行结果,那么这样的I/O软件中有一个CPU读外设相关寄存器的循环,直到CPU收到可继续执行I/O操作的外设信息后,CPU才能进一步做其它事情。当外设(如串口)的处理速度远低于CPU的时候,将使CPU处于忙等的低效状态中。
3、Direct Memory Access
如果外设每传一个字节都要产生一次中断,那系统执行效率还是很低。DMA(Direct Memory Access)是一种用于在计算机系统中进行快速数据传输的技术。它允许设备直接将数据传输到内存中,而不必通过CPU来直接处理。这样使得CPU从I/O任务中解脱出来,从而提高了系统的整体性能。
基于virtio的I/O设备抽象
随着互联网和云计算的兴起,在数据中心的物理服务器上通过虚拟机技术(Virtual Machine Monitor, Hypervisor等),运行多个虚拟机(Virtual Machine),并在虚拟机中运行guest操作系统的模式成为一种主流。
但当时存在多种虚拟机技术,如Xen、VMware、KVM等,要支持虚拟化x86、Power等不同的处理器和各种具体的外设,并都要求让以Linux为代表的guest OS能在其上高效的运行。这对于虚拟机和操作系统来说,实在是太繁琐和困难了。
IBM资深工程师 Rusty Russell 提出了一组通用I/O设备的抽象 – virtio规范。虚拟机(VMM或Hypervisor)提供virtio设备的实现,virtio设备有着统一的virtio接口,guest操作系统只要能够实现这些通用的接口,就可以管理和控制各种virtio设备。而虚拟机与guest操作系统的virtio设备驱动程序间的通道是基于共享内存的异步访问方式来实现的,效率很高。虚拟机会进一步把相关的virtio设备的I/O操作转换成物理机上的物理外设的I/O操作。这就完成了整个I/O处理过程。
由于virtio设备的设计,使得虚拟机不用模拟真实的外设,从而可以设计一种统一和高效的I/O操作规范来让guest操作系统处理各种I/O操作。这种I/O操作规范其实就形成了基于virtio的I/O设备抽象,并逐渐形成了事实上的虚拟I/O设备的标准。
I/O执行模型
堵塞IO
非堵塞IO
- 用户进程发出
read
系统调用; - 内核发现所需数据没在I/O缓冲区中,需要向磁盘驱动程序发出I/O操作,并不会让用户进程处于阻塞状态,而是立刻返回一个error;
- 用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作(这一步操作可以重复多次);
- 磁盘驱动程序把数据从磁盘传到I/O缓冲区后,通知内核(一般通过中断机制),内核在收到通知且再次收到了用户进程的system call后,会马上把数据从I/O缓冲区拷贝到用户进程的buffer中;
- 内核从内核态返回到用户态的进程,此时
read
系统调用完成。
在非阻塞式IO的特点是用户进程不会被内核阻塞,而是需要用户进程不断的主动询问内核所需数据准备好了没有。非阻塞系统调用相比于阻塞系统调用的的差异在于在被调用之后会立即返回。
特点: 用户进程进行轮询
多路复用IO
IO multiplexing对应的I/O系统调用是 select
和 epoll
等,也称这种IO方式为事件驱动IO(event driven IO)。 select
和 epoll
的优势在于,采用单进程方式就可以同时处理多个文件或网络连接的I/O操作。其基本工作机制就是通过 select
或 epoll
系统调用来不断的轮询用户进程关注的所有文件句柄或socket,当某个文件句柄或socket有数据到达了,select
或 epoll
系统调用就会返回到用户进程,用户进程再调用 read
系统调用,让内核将数据从内核的I/O缓冲区拷贝到用户进程的buffer中。
在多路复用IO模型中,对于用户进程关注的每一个文件句柄或socket,一般都设置成为non-blocking,只是用户进程是被 select
或 epoll
系统调用阻塞住了。select/epoll
的优势并不会导致单个文件或socket的I/O访问性能更好,而是在有很多个文件或socket的I/O访问情况下,其总体效率会高。
信号驱动
当进程发出一个 read
系统调用时,会向内核注册一个信号处理函数,然后系统调用返回。进程不会被阻塞,而是继续执行。当内核中的IO数据就绪时,会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。此模型的特点是,采用了回调机制,这样开发和调试应用的难度加大。
异步IO
用户进程发起 async_read
异步系统调用之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度看,当它收到一个 async_read
异步系统调用之后,首先它会立刻返回,所以不会对用户进程产生任何阻塞情况。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会通知用户进程,告诉它read操作完成了。
PLIC
平台级中断控制器
在RISC-V中,与外设连接的I/O控制器的一个重要组成是平台级中断控制器(Platform-Level Interrupt Controller,PLIC),它的一端汇聚了各种外设的中断信号,另一端连接到CPU的外部中断引脚上。当一个外部设备发出中断请求时,PLIC 会将其转发给 RISC-V CPU, CPU 会执行对应的中断处理程序来响应中断。通过RISC-V的 mie
寄存器中的 meie
位,可以控制这个引脚是否接收外部中断信号。当然,通过RISC-V中M Mode的中断委托机制,也可以在RISC-V的S Mode下,通过 sie
寄存器中的 seie
位,对中断信号是否接收进行控制
计算机中的中断控制器是一种硬件,可帮助处理器处理来自多个不同I/O设备的中断请求(Interrupt Request,简称IRQ)。这些中断请求可能同时发生,并首先经过中断控制器的处理,即中断控制器根据 IRQ 的优先级对同时发生的中断进行排序,然后把优先级最高的IRQ传给处理器,让操作系统执行相应的中断处理例程 (Interrupt Service Routine,简称ISR)。
CPU可以通过MMIO方式来对PLIC进行管理,下面是一些与PLIC相关的寄存器:
寄存器 地址 功能描述
Priority 0x0c00_0000 设置特定中断源的优先级
Pending 0x0c00_1000 包含已触发(正在处理)的中断列表
Enable 0x0c00_2000 启用/禁用某些中断源
Threshold 0x0c20_0000 设置中断能够触发的阈值
Claim 0x0c20_0004 按优先级顺序返回下一个中断
Complete 0x0c20_0004 写操作表示完成对特定中断的处理
但外设产生中断后,CPU并不知道具体是哪个设备传来的中断,这可以通过读PLIC的 Claim 寄存器来了解。 Claim 寄存器会返回PLIC接收到的优先级最高的中断;如果没有外设中断产生,读 Claim 寄存器会返回 0。
一些名词
1、UART : 串口
串行数据传输是逐位(bit)顺序发送数据的过程
2、块设备(virtio-blk)
3、网络设备(virtio-net)
4、键盘鼠标类设备(virtio-input)
5、显示设备(virtio-gpu)的识别、初始化和初步的操作。
6、驱动信息
virtio-GPU Drv
:图形显示驱动ns16550a Drv
:串口驱动virtio-input Drv
:键盘鼠标驱动virtio-Block Drv
:块设备驱动PLIC drv
:平台级中断控制器驱动Virt Machine Conf
:virt
计算机系统配置信息(可以理解为平台级配置驱动)
7、设备树二进制对象(DTB,Device Tree Blob)
设备树中的节点是用来描述硬件设备的信息的。 一个设备树节点包含了一个或多个属性,每个属性都是一个键-值对,用来描述设备的某一特定信息。而操作系统就是通过这些节点上的信息来实现对设备的识别和初始化。具体而言,一个设备节点上会有一些常见的属性:
- compatible:表示设备的类型,可以是设备的厂商名、产品名等,如 “virtio,mmio” 指的是这个设备通过 virtio 协议、MMIO(内存映射 I/O)方式来驱动
- reg:表示设备在系统中的地址空间位置
- interrupts:表示设备支持的中断信号
virtio设备
1、虚拟机(Virtual Machine,VM)
虚拟机监视器 Hypervisor
虚拟机监视器(Hypervisor或Virtual Machine Monitor,简称VMM)是创造并且运行虚拟机的软件、固件、或者硬件。这样主机硬件上能同时运行一个至多个虚拟机,这些虚拟机能高效地分享主机硬件资源。
2、数据面(Data Plane)
设备与处理器之间的I/O数据传输相关的数据设定(地址、布局等)与传输方式(基于内存或寄存器等)
控制平面(Control Plane)
处理器发现设备、配置设备、管理设备等相关的操作,以及处理器和设备之间的相互通知机制。
virtio设备
从本质上讲,virtio是一个接口,允许运行在虚拟机上的操作系统和应用软件通过访问 virtio 设备使用其主机的设备。
Virtio设备并不直接模拟硬件,而是提供了一种高效的抽象机制,使虚拟机(VM)能够与宿主机的物理或虚拟化资源进行交互。
各种类型的virtio设备,如块设备(virtio-blk)、网络设备(virtio-net)、键盘鼠标类设备(virtio-input)、显示设备(virtio-gpu)具有共性特征和独有特征。对于共性特征,virtio设计了各种类型设备的统一抽象接口,而对于独有特征,virtio尽量最小化各种类型设备的独有抽象接口。这样,virtio就形成了一套通用框架和标准接口(协议)来屏蔽各种hypervisor的差异性,实现了guest VM和不同hypervisor之间的交互过程。
从本质上讲,virtio是一个接口,允许运行在虚拟机上的操作系统和应用软件通过访问 virtio 设备使用其主机的设备。这些 virtio 设备具备功能最小化的特征,Guest VM中的设备驱动程序(Front-end drivers)只需实现基本的发送和接收I/O数据即可,而位于Hypervisor中的Back-end drivers和设备模拟部分让主机处理其实际物理硬件设备上的大部分设置、维护和处理。这种设计方案极大减轻了virtio驱动程序的复杂性。
virtio架构
总体上看,virtio 架构可以分为上中下三层
- 上层包括运行在QEMU模拟器上的前端操作系统中各种驱动程序(Front-end drivers);
- 下层是在QEMU中模拟的各种虚拟设备 Device;
- 中间层是传输(transport)层,就是驱动程序与虚拟设备之间的交互接口,包含两部分:上半部是virtio接口定义,即I/O数据传输机制的定义:virtio 虚拟队列(virtqueue);下半部是virtio接口实现,即I/O数据传输机制的具体实现:virtio-ring,主要由环形缓冲区和相关操作组成,用于保存驱动程序和虚拟设备之间进行命令和数据交互的信息。
操作系统中virtio 驱动程序的主要功能包括:
-
接受来自用户进程或操作系统其它组件发出的 I/O 请求
-
将这些 I/O 请求通过virqueue发送到相应的 virtio 设备
-
通过中断或轮询等方式查找并处理相应设备完成的I/O请求
Qemu或Hypervisor中virtio 设备的主要功能包括:
-
通过virqueue接受来自相应 virtio 驱动程序的 I/O 请求
-
通过设备仿真模拟或将 I/O 操作卸载到主机的物理硬件来处理I/O请求,使处理后的I/O数据可供 virtio 驱动程序使用
-
通过寄存器、内存映射或中断等方式通知virtio 驱动程序处理已完成的I/O请求
运行在Qemu中的操作系统中的virtio 驱动程序和Qemu模拟的virtio设备驱动的关系如下图所示:
I/O设备基本组成结构
virtio设备代表了一类I/O通用设备,为了让设备驱动能够管理和使用设备。在程序员的眼里,I/O设备基本组成结构包括如下恩利:
-
呈现模式:设备一般通过寄存器、内存或特定I/O指令等方式让设备驱动能看到和访问到设备;
-
特征描述:让设备驱动能够了解设备的静态特性(可通过软件修改),从而决定是否或如何使用该设备;
-
状态表示:让设备驱动能够了解设备的当前动态状态,从而确定如何进行设备管理或I/O数据传输;
-
交互机制:交互包括事件通知和数据传输;对于事件通知,让设备驱动及时获知设备的状态变化的机制(可基于中断等机制),以及让设备及时获得设备驱动发出的I/O请求(可基于寄存器读写等机制);对于数据传输,让设备驱动能处理设备给出的数据,以及让设备能处理设备驱动给出的数据,如(可基于DMA或virtqueue等机制)。
呈现模式
virtio设备支持三种设备呈现模式:
-
Virtio Over MMIO,虚拟设备直接挂载到系统总线上,我们实验中的虚拟计算机就是这种呈现模式;
-
Virtio Over PCI BUS,遵循PCI规范,挂在到PCI总线上,作为virtio-pci设备呈现,在QEMU虚拟的x86计算机上采用的是这种模式;
-
Virtio Over Channel I/O:主要用在虚拟IBM s390计算机上,virtio-ccw使用这种基于channel I/O的机制。
在Qemu模拟的RISC-V计算机 – virt 上,采用的是Virtio Over MMIO的呈现模式。这样在实现设备驱动时,我们只需要找到相应virtio设备的I/O寄存器等以内存形式呈现的地址空间,就可以对I/O设备进行初始化和管理了。
特征描述
virtio设备特征描述包括设备特征位和设备配置空间。
- 特征位
特征位用于表示VirtIO设备具有的各种特性和功能。其中bit0 – 23是特定设备可以使用的feature bits, bit24 – 37预给队列和feature协商机制,bit38以上保留给未来其他用途。驱动程序与设备对设备特性进行协商,形成一致的共识,这样才能正确的管理设备。
- 设备配置空间
设备配置空间通常用于配置不常变动的设备参数(属性),或者初始化阶段需要设置的设备参数。设备的特征位中包含表示配置空间是否存在的bit位,并可通过在特征位的末尾添加新的bit位来扩展配置空间。
设备驱动程序在初始化virtio设备时,需要根据virtio设备的特征位和配置空间来了解设备的特征,并对设备进行初始化。
virtio设备状态表示
virtio设备状态表示包括在设备初始化过程中用到的设备状态域,以及在设备进行I/O传输过程中用到的I/O数据访问状态信息和I/O完成情况等。
- 设备状态域
设备状态域包含对设备初始化过程中virtio设备的6种状态:
ACKNOWLEDGE(1):驱动程序发现了这个设备,并且认为这是一个有效的virtio设备;
DRIVER (2) : 驱动程序知道该如何驱动这个设备;
FAILED (128) : 由于某种错误原因,驱动程序无法正常驱动这个设备;
FEATURES_OK (8) : 驱动程序认识设备的特征,并且与设备就设备特征协商达成一致;
DRIVER_OK (4) : 驱动程序加载完成,设备可以正常工作了;
DEVICE_NEEDS_RESET (64) :设备触发了错误,需要重置才能继续工作。
在设备驱动程序对virtio设备初始化的过程中,需要经历一系列的初始化阶段,这些阶段对应着设备状态域的不同状态。
- I/O传输状态
设备驱动程序控制virtio设备进行I/O传输过程中,会经历一系列过程和执行状态,包括 I/O请求 状态、 I/O处理 状态、 I/O完成 状态、 I/O错误 状态、 I/O后续处理 状态等。设备驱动程序在执行过程中,需要对上述状态进行不同的处理。
比如,virtio_blk设备驱动发出一个读设备块的I/O请求,并在某确定位置给出这个I/O请求的地址,然后给设备发出’kick’通知(读或写相关I/O寄存器映射的内存地址),此时处于I/O请求状态;设备在得到通知后,此时处于 I/O处理 状态,它解析这个I/O请求,完成这个I/O请求的处理,即把磁盘块内容读入到内存中,并给出读出的块数据的内存地址,再通过中断通知设备驱动,此时处于 I/O完成 状态;如果磁盘块读取发生错误,此时处于 I/O错误 状态;设备驱动通过中断处理例程,此时处于 I/O后续处理 状态,设备驱动知道设备已经完成读磁盘块操作,会根据磁盘块数据所在内存地址,把数据传递给文件系统进行进一步处理;如果设备驱动发现磁盘块读错误,则会进行错误恢复相关的后续处理。
virtio设备交互机制
virtio设备交互机制包括基于Notifications的事件通知和基于virtqueue虚拟队列的数据传输。事件通知是指设备和驱动程序必须通知对方,它们有数据需要对方处理。数据传输是指设备和驱动程序之间进行I/O数据(如磁盘块数据、网络包)传输。
- Notification通知
驱动程序和设备在交互过程中需要相互通知对方:驱动程序组织好相关命令/信息要通知设备去处理I/O事务,设备处理完I/O事务后,要通知驱动程序进行后续事务,如回收内存,向用户进程反馈I/O事务的处理结果等
- virtqueue虚拟队列
在virtio设备上进行批量数据传输的机制被称为虚拟队列(virtqueue),virtio设备的虚拟队列(virtqueue)可以由各种数据结构(如数组、环形队列等)来具体实现
virtqueue虚拟队列
基于MMIO方式的virtio设备
基于MMIO方式的virtio设备没有基于总线的设备探测机制。 所以操作系统采用Device Tree的方式来探测各种基于MMIO方式的virtio设备,从而操作系统能知道与设备相关的寄存器和所用的中断。
基于MMIO方式的virtio设备提供了一组内存映射的控制寄存器,后跟一个设备特定的配置空间,在形式上是位于一个特定地址上的内存区域。一旦操作系统找到了这个内存区域,就可以获得与这个设备相关的各种寄存器信息。
比如,我们在 virtio-drivers crate 中就定义了基于MMIO方式的virtio设备的寄存器区域:
//virtio-drivers/src/header.rs
pub struct VirtIOHeader {
magic: ReadOnly<u32>, //魔数 Magic value
...
//设备初始化相关的特征/状态/配置空间对应的寄存器
device_features: ReadOnly<u32>, //设备支持的功能
device_features_sel: WriteOnly<u32>,//设备选择的功能
driver_features: WriteOnly<u32>, //驱动程序理解的设备功能
driver_features_sel: WriteOnly<u32>, //驱动程序选择的设备功能
config_generation: ReadOnly<u32>, //配置空间
status: Volatile<DeviceStatus>, //设备状态
//virtqueue虚拟队列对应的寄存器
queue_sel: WriteOnly<u32>, //虚拟队列索引号
queue_num_max: ReadOnly<u32>,//虚拟队列最大容量值
queue_num: WriteOnly<u32>, //虚拟队列当前容量值
queue_notify: WriteOnly<u32>, //虚拟队列通知
queue_desc_low: WriteOnly<u32>, //设备描述符表的低32位地址
queue_desc_high: WriteOnly<u32>,//设备描述符表的高32位地址
queue_avail_low: WriteOnly<u32>,//可用环的低32位地址
queue_avail_high: WriteOnly<u32>,//可用环的高32位地址
queue_used_low: WriteOnly<u32>,//已用环的低32位地址
queue_used_high: WriteOnly<u32>,//已用环的高32位地址
//中断相关的寄存器
interrupt_status: ReadOnly<u32>, //中断状态
interrupt_ack: WriteOnly<u32>, //中断确认
}
virtio设备驱动程序
设备的初始化
操作系统通过某种方式(设备发现,基于设备树的查找等)找到virtio设备后,驱动程序进行设备初始化的常规步骤如下所示:
1、重启设备状态,设置设备状态域为0
2、设置设备状态域为 ACKNOWLEDGE ,表明当前已经识别到了设备
3、设置设备状态域为 DRIVER ,表明驱动程序知道如何驱动当前设备
4、进行设备特定的安装和配置,包括协商特征位,建立virtqueue,访问设备配置空间等, 设置设备状态域为 FEATURES_OK
5、设置设备状态域为 DRIVER_OK 或者 FAILED (如果中途出现错误)
注意,上述的步骤不是必须都要做到的,但最终需要设置设备状态域为 DRIVER_OK ,这样驱动程序才能正常访问设备
在 virtio_driver 模块中,我们实现了通用的virtio驱动程序框架,各种virtio设备驱动程序的共同的初始化过程为:
1、确定协商特征位,调用 VirtIOHeader 的 begin_init 方法进行virtio设备初始化的第1-4步骤;
2、读取配置空间,确定设备的配置情况;
3、建立虚拟队列1~n个virtqueue;
4、调用 VirtIOHeader finish_init 方法进行virtio设备初始化的第5步骤。
驱动程序与设备之间的交互
1、驱动程序与外设可以共同访问约定的virtqueue,virtqueue将保存设备驱动的I/O请求信息和设备的I/O响应信息。virtqueue由描述符表(Descriptor Table)、可用环(Available Ring)和已用环(Used Ring)组成。在上述的设备驱动初始化过程描述中已经看到了虚拟队列的创建过程。
2、当驱动程序向设备发送I/O请求(由命令/数据组成)时,它会在buffer(设备驱动申请的内存空间)中填充命令/数据,各个buffer所在的起始地址和大小信息放在描述符表的描述符中,再把这些描述符链接在一起,形成描述符链。
3、而描述符链的起始描述符的索引信息会放入一个称为环形队列的数据结构中。该队列有两类,一类是包含由设备驱动发出的I/O请求所对应的描述符索引信息,即可用环。另一类由包含由设备发出的I/O响应所对应的描述符索引信息,即已用环。
一个用户进程发起的I/O操作的处理过程大致可以分成如下四步:
-
用户进程发出I/O请求,经过层层下传给到驱动程序,驱动程序将I/O请求信息放入虚拟队列virtqueue的可用环中,并通过某种通知机制(如写某个设备寄存器)通知设备;
-
设备收到通知后,解析可用环和描述符表,取出I/O请求并在内部进行实际I/O处理;
-
设备完成I/O处理或出错后,将结果作为I/O响应放入已用环中,并以某种通知机制(如外部中断)通知CPU;
-
驱动程序解析已用环,获得I/O响应的结果,在进一步处理后,最终返回给用户进程。
发出I/O请求的过程
虚拟队列的相关操作包括两个部分:向设备提供新的I/O请求信息(可用环–>描述符–>缓冲区),以及处理设备使用的I/O响应(已用环–>描述符–>缓冲区)。 比如,virtio-blk块设备具有一个虚拟队列来支持I/O请求和I/O响应。在驱动程序进行I/O请求和I/O响应的具体操作过程中,需要注意如下一些细节。
驱动程序给设备发出I/O请求信息的具体步骤如下所示:
1、将包含一个I/O请求内容的缓冲区的地址和长度信息放入描述符表中的空闲描述符中,并根据需要把多个描述符进行链接,形成一个描述符链(表示一个I/O操作请求);
2、驱动程序将描述符链头的索引放入可用环的下一个环条目中;
如果可以进行批处理(batching),则可以重复执行步骤1和2,这样通过(可用环–>描述符–>缓冲区)来添加多个I/O请求;
3、根据添加到可用环中的描述符链头的数量,更新可用环;
4、将”有可用的缓冲区”的通知发送给设备。
注:在第3和第4步中,都需要指向适当的内存屏障操作(Memory Barrier),以确保设备能看到更新的描述符表和可用环。
内存屏障 (Memory Barrier)
大多数现代计算机为了提高性能而采取乱序执行,这使得内存屏障在某些情况下成为必须要执行的操作。内存屏障是一类同步屏障指令,它使得 CPU 或编译器在对内存进行操作的时候, 严格按照一定的顺序来执行, 也就是说在内存屏障之前的指令和内存屏障之后的指令不会由于系统优化等原因而导致乱序。内存屏障分为写屏障(Store Barrier)、读屏障(Load Barrier)和全屏障(Full Barrier),其作用是:
-
防止指令之间的重排序
-
保证数据的可见性
接收设备I/O响应的操作
一旦设备完成了I/O请求,形成I/O响应,就会更新描述符所指向的缓冲区,并向驱动程序发送已用缓冲区通知(used buffer notification)。一般会采用中断这种更加高效的通知机制。设备驱动程序在收到中断后,就会对I/O响应信息进行后续处理
virtio-blk设备的I/O操作者
1、一个完整的virtio-blk的I/O写请求由三部分组成,包括表示I/O写请求信息的结构 BlkReq ,要传输的数据块 buf,一个表示设备响应信息的结构 BlkResp 。这三部分需要三个描述符来表示;
2、(驱动程序处理)接着调用 VirtQueue.add 函数,从描述符表中申请三个空闲描述符,每项指向一个内存块,填写上述三部分的信息,并将三个描述符连接成一个描述符链表;
3、(驱动程序处理)接着调用 VirtQueue.notify 函数,写MMIO模式的 queue_notify 寄存器,即向 virtio-blk设备发出通知;
4、(设备处理)virtio-blk设备收到通知后,通过比较 last_avail (初始为0)和 AvailRing 中的 idx 判断是否有新的请求待处理(如果 last_vail 小于 AvailRing 中的 idx ,则表示有新请求)。如果有,则 last_avail 加1,并以 last_avail 为索引从描述符表中找到这个I/O请求对应的描述符链来获知完整的请求信息,并完成存储块的I/O写操作;
5、(设备处理)设备完成I/O写操作后(包括更新包含 BlkResp 的Descriptor),将已完成I/O的描述符放入UsedRing对应的ring项中,并更新idx,代表放入一个响应;如果设置了中断机制,还会产生中断来通知操作系统响应中断;
6、(驱动程序处理)驱动程序可用轮询机制查看设备是否有响应(持续调用 VirtQueue.can_pop 函数),通过比较内部的 VirtQueue.last_used_idx 和 VirtQueue.used.idx 判断是否有新的响应。如果有,则取出响应(并更新 last_used_idx ),将完成响应对应的三项Descriptor回收,最后将结果返回给用户进程。当然,也可通过中断机制来响应。
virtio-gpu
virtio-gpu设备驱动程序的设计与实现。主要包括如下内容
1、virtio-gpu设备的关键数据结构
2、初始化virtio-gpu设备
3、操作系统对接virtio-gpu设备初始化
4、virtio-gpu设备的I/O操作
5、操作系统对接virtio-gpu设备I/O操作
问答题
1、字符设备的特点是什么?
2、块设备的特点是什么?
字符设备信息传输以字符为单位。属于无结构类型,数据传输率低,不可寻址
块设备信息交换以数据块为单位,属于有结构设备,数据传输率高,可以寻址
3、网络设备的特点是什么?
网络设备通常处理的是封装成网络包的数据,这些数据在网络中被发送和接收。而字符型设备处理的是连续的字符流,块设备则处理的是固定大小的数据块。
网络设备依赖于网络协议
网络设备支持远程连接和数据交换,可以跨越广阔的地理区域
4、阻塞I/O、非阻塞I/O、多路复用 I/O、信号驱动 I/O、异步I/O这几种I/O方式的特点和区别是?
- 堵塞I/O: 在I/O操作进行时,调用它的进程会被挂起(阻塞),直到I/O操作完成。这意味着在等待I/O完成期间,进程不能执行其他任务。
- 非堵塞I/O: 进程发起I/O操作后,不会被挂起,即使数据还未准备好。如果数据未准备好,调用会立即返回一个标志(如EWOULDBLOCK),进程可以继续执行其他任务。
- 多路复用I/O: 使用select、poll或epoll等机制,使单个线程能够监视多个文件描述符,一旦某个文件描述符就绪(例如,数据可读),相应的操作可以立即执行。
- 信号驱动 I/O: 进程可以继续执行,直到I/O操作准备就绪并通过信号通知进程。进程在接收到通知之后才开始I/O操作。
- 异步I/O: 进程发起I/O操作后可以立即继续执行后续任务。I/O操作的完成(包括数据传输)是完全由系统自动处理的,完成后会通知进程。
5、IO数据传输有哪几种?各自的特征是什么?
- Programmed I/O
PIO指CPU通过发出I/O指令的方式来进行数据传输
- Interrupt based I/O
如果采用PIO方式让CPU来获取外设的执行结果,那么这样的I/O软件中有一个CPU读外设相关寄存器的循环,直到CPU收到可继续执行I/O操作的外设信息后,CPU才能进一步做其它事情。当外设(如串口)的处理速度远低于CPU的时候,将使CPU处于忙等的低效状态中。
- Direct Memory Access
如果外设每传一个字节都要产生一次中断,那系统执行效率还是很低。DMA(Direct Memory Access)是一种用于在计算机系统中进行快速数据传输的技术。它允许设备直接将数据传输到内存中,而不必通过CPU来直接处理。这样使得CPU从I/O任务中解脱出来,从而提高了系统的整体性能。
6、描述磁盘I/O操作时间组成。其中的瓶颈是哪部分?
寻道时间、旋转延迟时间、传输时间、排队时间
一般来说寻道时间与旋转延迟时间占大头,可以通过一定的方法削减,但传输时间是磁盘本身性质所
决定的,不能通过一定的措施减少。因此我认为传输时间是瓶颈
7、RISC-V中的异常,中断的区别是啥?有几类中断?每类中断有哪些具体的常见中断实例?PLIC/CLINT的具体功能是啥?中断可否从M态响应委托给S态响应?S态响应可否委托给U态响应?与中断相关的M态/S态寄存器有哪些,这些寄存器的功能是啥?外设产生一个中断后,PLIC/CPU/OS如何协同进行响应处理的?
(1)、异常通常是由程序内部的错误或非法操作引发的事件,如除以零或无效的内存访问。它们是程序执行期间的事件,通常由CPU自身检测和处理,中断是来自外部设备或其他来源的异步事件,它们可以随时打断正在执行的程序。中断请求由外部硬件或设备发出,需要CPU进行响应。
(2)、外中断与内中断
(3)、常见的外部中断实例包括时钟中断、外部设备中断等。软件中断通常是通过ecall
指令触发,用于进行系统调用。
(4)、PLIC是一个硬件模块,用于管理外部中断。它将外部中断分配给不同的CPU核心,以协调中断处理。
CLINT是每个核心的本地中断控制器,用于处理本地中断,如时钟中断。它也包含一个全局定时器。
CLINT和PLIC最大的区别在于,CLINT没有仲裁,包括software和Timer,一有中断马上响应(software中断怎么产生的:用软件直接写一个寄存器当作软件中断)。PLIC需要一个仲裁决定谁先中断,存在个优先级的问题。
(5)、可以进行中断委托
(6)、
-
M-mode 的寄存器
mstatus`,`mtvec`,`medeleg`,`mideleg`,`mip`,`mie`,`mepc`,`mcause`,`mtval
-
S-mode 的寄存器
sstatus`,`stvec`,`sip`,`sie`,`sepc`,`scause`,`stval`,`satp
xstatus:它用于管理和控制处理器的状态和特权级别
xtvec: 记录的是异常处理函数的起始地址
medeleg: 用于指示转发哪些异常到 S-mode
mideleg: 用于指示转发哪些中断到 S-mode。
xip
与 xie
是分别用于保存 pending interrupt 和 pending interrupt enable bits
xpec: 当 trap 陷入到 x-mode 时,xepc
会被 CPU 自动写入引发 trap 的指令的虚拟地址或者是被中断的指令的虚拟地址
satp
:记录的是根页表物理地址的页帧号
mcause: 异常/中断产生的原因
mtval: 当 trap 陷入到 M-mode 时,mtval
会被置零或者被写入与异常相关的信息来辅助处理 trap。当触发硬件断点、地址未对齐、access fault、page fault 时,mtval
记录的是引发这些问题的虚拟地址。
8、是否可以把设备抽象为文件?如果可以,那用户进程对设备发出IO控制命令,如何通过系统调用实现?
可以
9、GPU是外设吗?GPU与CPU交互和数据传输的方式是什么?(需要查看一下相关GPU工作过程的信息)
是,
实验题
贪吃蛇不是已经实现了嘛,没懂让做什么
分析了一下virto串口、blk、gpu的底层实现,都是向特定的物理地址上读取/写入数据,包括一些状态、标志、数据
标签:virtio,驱动程序,rCore,中断,Lab9,CPU,外设,设备 From: https://www.cnblogs.com/lordtianqiyi/p/18000247