提交 763e4e6d 编写于 作者: W wyfcyx

deploy: 2e993ce5

上级 4dfea73b
......@@ -210,7 +210,7 @@
它的开头和结尾分别在 sp(x2) 和 fp(s0) 所指向的地址。按照地址从高到低分别有以下内容,它们都是通过 ``sp`` 加上一个偏移量来访问的:
- ``ra`` 寄存器保存其返回之后的跳转地址,是一个调用者保存寄存器;
- ``ra`` 寄存器保存其返回之后的跳转地址,是一个调用者保存寄存器;
- 父亲栈帧的结束地址 ``fp`` ,是一个被调用者保存寄存器;
- 其他被调用者保存寄存器 ``s1`` ~ ``s11`` ;
- 函数所使用到的局部变量。
......@@ -361,4 +361,4 @@
C 语言中的指针相当于 Rust 中的裸指针,它无所不能但又太过于灵活,程序员对其不谨慎的使用常常会引起很多内存不安全问题,最常见的如悬垂指针和多次回收的问题,Rust 编译器没法确认程序员对它的使用是否安全,因此将其划到 unsafe Rust 的领域。在 safe Rust 中,我们有引用 ``&/&mut`` 以及各种功能各异的智能指针 ``Box<T>/RefCell<T>/Rc<T>`` 可以使用,只要按照 Rust 的规则来使用它们便可借助编译器在编译期就解决很多潜在的内存不安全问题。
本节我们介绍了函数调用和栈的背景知识,通过分配栈空间并正确设置栈指针在内核中使能了函数调用并成功将控制权转交给 Rust 代码,从此我们终于可以利用强大的 Rust 语言来编写内核的各项功能了。下一节中我们将进行构建“三叶虫”操作系统的最后一个步骤:即基于 RustSBI 提供的服务成功在屏幕上打印 ``Hello, world!`` 。
\ No newline at end of file
本节我们介绍了函数调用和栈的背景知识,通过分配栈空间并正确设置栈指针在内核中使能了函数调用并成功将控制权转交给 Rust 代码,从此我们终于可以利用强大的 Rust 语言来编写内核的各项功能了。下一节中我们将进行构建“三叶虫”操作系统的最后一个步骤:即基于 RustSBI 提供的服务成功在屏幕上打印 ``Hello, world!`` 。
......@@ -12,7 +12,7 @@
- 任务运行状态:任务从开始到结束执行过程中所处的不同运行状态:未初始化、准备执行、正在执行、已退出
- 任务控制块:管理程序的执行过程的任务上下文,控制程序的执行与暂停
- 任务相关系统调用:应用程序和操作系统直接的接口,用于程序主动暂停 ``sys_yield`` 和主动退出 ``sys_exit``
- 任务相关系统调用:应用程序和操作系统之间的接口,用于程序主动暂停 ``sys_yield`` 和主动退出 ``sys_exit``
这些都是三叠纪“始初龙”协作式操作系统 [#eoraptor]_ 需要具有的功能。本节的代码可以在 ``ch3-coop`` 分支上找到。
......
......@@ -560,7 +560,7 @@
TASK_MANAGER.get_current_trap_cx()
}
通过 ``current_user_token`` 和 ``current_trap_cx`` 分别可以获得当前正在执行的应用的地址空间的 token 和可以在内核地址空间中修改位于该应用地址空间中的 Trap 上下文的可变引用
通过 ``current_user_token`` 可以获得当前正在执行的应用的地址空间的 token 。同时,该应用地址空间中的 Trap 上下文很关键,内核需要访问它来拿到应用进行系统调用的参数并将系统调用返回值写回,通过 ``current_trap_cx`` 内核可以拿到它访问这个 Trap 上下文的可变引用并进行读写
改进 Trap 处理的实现
------------------------------------
......@@ -737,4 +737,4 @@
如果同学能想明白如何插入/删除页表;如何在 ``trap_handler`` 下处理 ``LoadPageFault`` ;以及 ``sys_get_time`` 在使能页机制下如何实现,那就会发现下一节的实验练习也许 **就和lab1一样** 。
.. [#tutus] 头甲龙最早出现在1.8亿年以前的侏罗纪中期,是身披重甲的食素恐龙,尾巴末端的尾锤,是防身武器。
\ No newline at end of file
.. [#tutus] 头甲龙最早出现在1.8亿年以前的侏罗纪中期,是身披重甲的食素恐龙,尾巴末端的尾锤,是防身武器。
......@@ -109,7 +109,7 @@ challenge: 支持多核。
(1) fork + exec 的一个比较大的问题是 fork 之后的内存页/文件等资源完全没有使用就废弃了,针对这一点,有什么改进策略?
(2) [选做,不占分]其实使用了题(1)的策略之后,fork + exec 所带来的无效资源的问题已经基本被解决了,但是年来 fork 还是在被不断的批判,那么到底是什么正在"杀死"fork?可以参考 `论文 <https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf>`_ 。
(2) [选做,不占分]其实使用了题(1)的策略之后,fork + exec 所带来的无效资源的问题已经基本被解决了,但是年来 fork 还是在被不断的批判,那么到底是什么正在"杀死"fork?可以参考 `论文 <https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf>`_ 。
(3) 请阅读下列代码,并分析程序的输出,假定不发生运行错误,不考虑行缓冲:
......
......@@ -195,7 +195,7 @@ Blocks 给出 ``os`` 目录也占用 8 个块进行存储。实际上目录也
文件关闭
++++++++++++++++++++++++++++++++++++++++++++++++++
在打开文件,对文件完成了读写操作后,还需要关闭文件,这样才让进程释放被这个文件所占用的内核资源。 ``close`` 的调用参数是文件描述符,但文件被关闭后,文件在内核中的资源会被释放,文件描述符会被回收。这样,进程就不能继续使用该文件描述符进行文件读写了。
在打开文件,对文件完成了读写操作后,还需要关闭文件,这样才让进程释放被这个文件占用的内核资源。 ``close`` 的调用参数是文件描述符,当文件被关闭后,该文件在内核中的资源会被释放,文件描述符会被回收。这样,进程就不能继续使用该文件描述符进行文件读写了。
.. code-block:: rust
......
......@@ -312,7 +312,7 @@
``PipeRingBuffer::read_byte`` 方法可以从管道中读取一个字节,注意在调用它之前需要确保管道缓冲区中不是空的。它会更新循环队列队头的位置,并比较队头和队尾是否相同,如果相同的话则说明管道的状态变为空 ``EMPTY`` 。仅仅通过比较队头和队尾是否相同不能确定循环队列是否为空,因为它既有可能表示队列为空,也有可能表示队列已满。因此我们需要在 ``read_byte`` 的同时进行状态更新。
``PipeRingBuffer::available_read`` 可以计算管道中还有多少个字符可以读取。我们首先需要需要判断队列是否为空,因为队头和队尾相等可能表示队列为空或队列已满,两种情况 ``available_read`` 的返回值截然不同。如果队列为空的话直接返回 0,否则根据队头和队尾的相对位置进行计算。
``PipeRingBuffer::available_read`` 可以计算管道中还有多少个字符可以读取。我们首先需要判断队列是否为空,因为队头和队尾相等可能表示队列为空或队列已满,两种情况 ``available_read`` 的返回值截然不同。如果队列为空的话直接返回 0,否则根据队头和队尾的相对位置进行计算。
``PipeRingBuffer::all_write_ends_closed`` 可以判断管道的所有写端是否都被关闭了,这是通过尝试将管道中保存的写端的弱引用计数升级为强引用计数来实现的。如果升级失败的话,说明管道写端的强引用计数为 0 ,也就意味着管道所有写端都被关闭了,从而管道中的数据不会再得到补充,待管道中仅剩的数据被读取完毕之后,管道就可以被销毁了。
......
......@@ -85,7 +85,7 @@
- 第 4~7 行我们声明线程函数接受的参数类型为一个名为 ``FuncArguments`` 的结构体,内含 ``x`` 和 ``y`` 两个字段。
- 第 15 行我们创建并默认初始化三个 ``pthread_t`` 实例 ``t0`` 、 ``t1`` 和 ``t2`` ,分别代表我们接下来要创建的三个线程。
- 第 16 行在主线程的栈上给出三个线程接受的参数。
- 第 9~12 行实现线程运行的函数 ``func`` ,可以看到它的函数签名符合要求。它实际接受的参数类型应该为我们之前定义的 ``FuncArguments`` 类型的指针,但是在函数签名中是一个 ``void *`` ,所以在第 10 行我们我们首先进行类型转换得到 ``FuncArguments*`` ,而后才能访问 ``x`` 和 ``y`` 两个字段并打印出来。
- 第 9~12 行实现线程运行的函数 ``func`` ,可以看到它的函数签名符合要求。它实际接受的参数类型应该为我们之前定义的 ``FuncArguments`` 类型的指针,但是在函数签名中是一个 ``void *`` ,所以在第 10 行我们首先进行类型转换得到 ``FuncArguments*`` ,而后才能访问 ``x`` 和 ``y`` 两个字段并打印出来。
- 第 17~19 行使用 ``pthread_create`` 创建三个线程,分别绑定到 ``t0~t2`` 三个 ``pthread_t`` 实例上。它们均执行 ``func`` 函数,但接受的参数有所不同。
编译运行,一种可能的输出为:
......
......@@ -193,7 +193,7 @@ A是共享变量。粗略地看,可以估计执行流程为:线程thr1先被
状态是安全的,是指存在一个资源分配/线程执行序列使得所有的线程都能获取其所需资源并完成线程的工作。如果找不到这样的资源分配/线程执行序列,那么状态是不安全的。这里把线程的执行过程简化为:申请资源、释放资源的一系列资源操作。这意味这线程执行完毕后,会释放其占用的所有资源。
我们需要知道,不安全状态并不等于死锁,而是指有死锁的可能性。安全状态和不安全状态的区别是:从安全状态出发,操作系统通过调度线程执行序列,能够保证所有线程都能完成,一定不会出现死锁;而从不安全状态出发,就没有这样的保证,可能出现死锁。
我们需要知道,不安全状态并不等于死锁,而是指有死锁的可能性。安全状态和不安全状态的区别是:从安全状态出发,操作系统通过调度线程执行序列,能够保证所有线程都能完成,一定不会出现死锁;而从不安全状态出发,就没有这样的保证,可能出现死锁。
.. chyyuu 有一个安全,不安全,死锁的图???
......
......@@ -156,7 +156,7 @@
.. chyyuu 在我们的具体实现中,与上述的一般中断处理过程不太一样。首先操作系统通过自定义的 ``SBI_DEVICE_HANDLER`` SBI调用,告知RustSBI在收到外部中断后,要跳转到的操作系统中处理外部中断的函数 ``device_trap_handler`` 。这样,在外部中断产生后,先由RustSBI在M Mode下接收的,并转到S Mode,交由 ``device_trap_handler`` 内核函数进一步处理。
在以往的操作系统实现中,当一个进程通过 ``sys_read`` 系统调用来获取串口字符时,并没有用上中断机制。但一个进程读不到字符的时候,将会被操作系统调度到就绪队列的尾部,等待下一次执行的时刻。这其实就是一种变相的轮询方式来获取串口的输入字符。这里其实是可以对进程管理做的一个改进,来避免进程通过轮询的方式检查串口字符输入。既然我们已经在上一章设计实现了让用户态线程挂起的同步互斥机制,我们就可以把自然地把这种机制也用来内核中,在外设不能及时提供资源的情况下,让想获取资源的线程或进程挂起,知道外设提供了资源,再唤醒进程继续执行。
在以往的操作系统实现中,当一个进程通过 ``sys_read`` 系统调用来获取串口字符时,并没有用上中断机制。但一个进程读不到字符的时候,将会被操作系统调度到就绪队列的尾部,等待下一次执行的时刻。这其实就是一种变相的轮询方式来获取串口的输入字符。这里其实是可以对进程管理做的一个改进,来避免进程通过轮询的方式检查串口字符输入。既然我们已经在上一章设计实现了让用户态线程挂起的同步互斥机制,我们就可以把这种机制也用在内核中,在外设不能及时提供资源的情况下,让想获取资源的线程或进程挂起,直到外设提供了资源,再唤醒线程或进程继续执行。
目前,支持中断的驱动可有效地支持等待的进程唤醒的操作。以串口为例,如果一个进程通过系统调用想获取串口输入,但此时串口还没有输入的字符,那么操作系统就设置一个进程等待串口输入的条件变量(条件变量包含一个等待队列),然后把当前进程设置等待状态,并挂在这个等待队列上,再把CPU让给其它就绪进程执行。对于串口输入的处理,由于要考虑中断,相对就要复杂一些。读字符串的代码如下所示:
......
......@@ -176,7 +176,7 @@ virtio设备状态表示包括在设备初始化过程中用到的设备状态
virtio设备进行I/O传输过程中,设备驱动会指出 `I/O请求` 队列的当前位置状态信息,这样设备能查到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_blk设备驱动发出一个读设备块的I/O请求,并在某确定位置给出这个I/O请求的地址,然后给设备发出'kick'通知(读或写相关I/O寄存器映射的内存地址),此时处于I/O请求状态;设备在得到通知后,此时处于 `I/O处理` 状态,它解析这个I/O请求,完成这个I/O请求的处理,即把磁盘块内容读入到内存中,并给出读出的块数据的内存地址,再通过中断通知设备驱动,此时处于 `I/O完成` 状态;如果磁盘块读取发生错误,此时处于 `I/O错误` 状态;设备驱动通过中断处理例程,此时处于 `I/O后续处理` 状态,设备驱动知道设备已经完成读磁盘块操作,会根据磁盘块数据所在内存地址,把数据传递给文件系统进行进一步处理;如果设备驱动发现磁盘块读错误,则会进行错误恢复相关的后续处理。
......@@ -184,7 +184,7 @@ virtio设备进行I/O传输过程中,设备驱动会指出 `I/O请求` 队列
virtio设备交互机制
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
virtio设备交互机制包括基于Notifications的事件通知和基于virtqueue虚拟队列的数据传输。事件通知是指设备和驱动程序必须通知对方,它们有数据需要对方处理。数据传输是指设备和驱动程序之间进行进行I/O数据(如磁盘块数据、网络包)传输。
virtio设备交互机制包括基于Notifications的事件通知和基于virtqueue虚拟队列的数据传输。事件通知是指设备和驱动程序必须通知对方,它们有数据需要对方处理。数据传输是指设备和驱动程序之间进行I/O数据(如磁盘块数据、网络包)传输。
**Notification通知**
......@@ -205,7 +205,7 @@ virtio协议中一个关键部分是virtqueue,在virtio规范中,virtqueue
操作系统在Qemu上运行时,virtqueue是 virtio 驱动程序和 virtio 设备访问的同一块内存区域。
当涉及到 virtqueue 的描述时,有很多不一致的地方。有将其与vring(virtio-rings或VRings)等同表示,也有将二者分别单独描述为不同的对象。我们将在这里单独描述它们,因为vring是virtueues的主要组成部分,是达成virtio设备和驱动程序之间数据传输的数据结构, vring本质是virtio设备和驱动程序之间的共享内存,但 virtqueue 不仅仅只有vring。
当涉及到 virtqueue 的描述时,有很多不一致的地方。有将其与vring(virtio-rings或VRings)等同表示,也有将二者分别单独描述为不同的对象。我们将在这里单独描述它们,因为vring是virtqueues的主要组成部分,是达成virtio设备和驱动程序之间数据传输的数据结构, vring本质是virtio设备和驱动程序之间的共享内存,但 virtqueue 不仅仅只有vring。
......@@ -214,7 +214,7 @@ virtqueue由三部分组成(如下图所示):
- 描述符表 Descriptor Table:描述符表是描述符为组成元素的数组,每个描述符描述了一个内存buffer 的address/length。而内存buffer中包含I/O请求的命令/数据(由virtio设备驱动填写),也可包含I/O完成的返回结果(由virtio设备填写)等。
- 可用环 Available Ring:一种vring,记录了virtio设备驱动程序发出的I/O请求索引,即被virtio设备驱动程序更新的描述符索引的集合,需要virtio设备进行读取并完成相关I/O操作;
- 已用环 Used Ring:另一种vring,记录了virtio设备发出的I/O完成索引,即被virtio设备更新的描述符索引d 集合,需要vrtio设备驱动程序进行读取并对I/O操作结果进行进一步处理。
- 已用环 Used Ring:另一种vring,记录了virtio设备发出的I/O完成索引,即被virtio设备更新的描述符索引集合,需要vrtio设备驱动程序进行读取并对I/O操作结果进行进一步处理。
.. image:: ../../os-lectures/lec13/figs/virtqueue-arch.png
......@@ -509,7 +509,7 @@ idx总是递增,并在到达 ``qsz`` 后又回到0:
**接收设备I/O响应的操作**
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
一旦设备完成了I/O请求,形成I/O响应,就会更新描述符所指向的缓冲区,并向驱动程序发送已用缓冲区通知(used buffer notification)。一般会采用中断这种更加高效的通知机制。设备驱动程序在收到中断后,就会更加I/O响应信息进行后续处理。相关的伪代码如下所示:
一旦设备完成了I/O请求,形成I/O响应,就会更新描述符所指向的缓冲区,并向驱动程序发送已用缓冲区通知(used buffer notification)。一般会采用中断这种更加高效的通知机制。设备驱动程序在收到中断后,就会I/O响应信息进行后续处理。相关的伪代码如下所示:
.. code-block:: Rust
:linenos:
......
......@@ -56,7 +56,7 @@ virtio-blk设备的关键数据结构
last_used_idx: u16, //上次已用环的索引值
}
其中成员变量 ``free_head`` 指空闲描述符链表头,初始时所有描述符通过 ``next`` 指针依次相连形成空闲链表,成员变量 ``last_used_idx`` 是指设备上次已取的已用环元素位置。成员变量 ``avail_idx`` 是指设备上次已取的已用环元素位置
其中成员变量 ``free_head`` 指空闲描述符链表头,初始时所有描述符通过 ``next`` 指针依次相连形成空闲链表,成员变量 ``last_used_idx`` 是指设备上次已取的已用环元素位置。成员变量 ``avail_idx`` 是可用环的索引值
.. _term-virtio-hal:
......@@ -188,7 +188,7 @@ virtio-blk设备驱动程序了解了virtio-blk设备的扇区个数,扇区大
}
从上面的代码可以看到,操作系统中表示表示virtio_blk设备的全局变量 ``BLOCK_DEVICE`` 的类型是 ``VirtIOBlock`` ,封装了来自virtio_derivers 模块的 ``VirtIOBlk`` 类型。这样,操作系统内核就可以通过 ``BLOCK_DEVICE`` 全局变量来访问virtio_blk设备了。而 ``VirtIOBlock`` 中的 ``condvars: BTreeMap<u16, Condvar>`` 条件变量结构,是用于进程在等待 I/O读或写操作完全前,通过条件变量让进程处于挂起状态。当virtio_blk设备完成I/O操作后,会通过中断唤醒等待的进程。而操作系统对virtio_blk设备的初始化除了封装 ``VirtIOBlk`` 类型并调用 ``VirtIOBlk::<VirtioHal>::new()`` 外,还需要初始化 ``condvars`` 条件变量结构,而每个条件变量对应着一个虚拟队列条目的编号,这意味着每次I/O请求都绑定了一个条件变量,让发出请求的线程/进程可以被挂起。
从上面的代码可以看到,操作系统中表示virtio_blk设备的全局变量 ``BLOCK_DEVICE`` 的类型是 ``VirtIOBlock`` ,封装了来自virtio_derivers 模块的 ``VirtIOBlk`` 类型。这样,操作系统内核就可以通过 ``BLOCK_DEVICE`` 全局变量来访问virtio_blk设备了。而 ``VirtIOBlock`` 中的 ``condvars: BTreeMap<u16, Condvar>`` 条件变量结构,是用于进程在等待 I/O读或写操作完全前,通过条件变量让进程处于挂起状态。当virtio_blk设备完成I/O操作后,会通过中断唤醒等待的进程。而操作系统对virtio_blk设备的初始化除了封装 ``VirtIOBlk`` 类型并调用 ``VirtIOBlk::<VirtioHal>::new()`` 外,还需要初始化 ``condvars`` 条件变量结构,而每个条件变量对应着一个虚拟队列条目的编号,这意味着每次I/O请求都绑定了一个条件变量,让发出请求的线程/进程可以被挂起。
.. code-block:: Rust
......
......@@ -545,7 +545,7 @@
</div>
<p>它的开头和结尾分别在 sp(x2) 和 fp(s0) 所指向的地址。按照地址从高到低分别有以下内容,它们都是通过 <code class="docutils literal notranslate"><span class="pre">sp</span></code> 加上一个偏移量来访问的:</p>
<ul class="simple">
<li><p><code class="docutils literal notranslate"><span class="pre">ra</span></code> 寄存器保存其返回之后的跳转地址,是一个调用者保存寄存器;</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">ra</span></code> 寄存器保存其返回之后的跳转地址,是一个调用者保存寄存器;</p></li>
<li><p>父亲栈帧的结束地址 <code class="docutils literal notranslate"><span class="pre">fp</span></code> ,是一个被调用者保存寄存器;</p></li>
<li><p>其他被调用者保存寄存器 <code class="docutils literal notranslate"><span class="pre">s1</span></code> ~ <code class="docutils literal notranslate"><span class="pre">s11</span></code></p></li>
<li><p>函数所使用到的局部变量。</p></li>
......
......@@ -394,7 +394,7 @@
<ul class="simple">
<li><p>任务运行状态:任务从开始到结束执行过程中所处的不同运行状态:未初始化、准备执行、正在执行、已退出</p></li>
<li><p>任务控制块:管理程序的执行过程的任务上下文,控制程序的执行与暂停</p></li>
<li><p>任务相关系统调用:应用程序和操作系统直接的接口,用于程序主动暂停 <code class="docutils literal notranslate"><span class="pre">sys_yield</span></code> 和主动退出 <code class="docutils literal notranslate"><span class="pre">sys_exit</span></code></p></li>
<li><p>任务相关系统调用:应用程序和操作系统之间的接口,用于程序主动暂停 <code class="docutils literal notranslate"><span class="pre">sys_yield</span></code> 和主动退出 <code class="docutils literal notranslate"><span class="pre">sys_exit</span></code></p></li>
</ul>
<p>这些都是三叠纪“始初龙”协作式操作系统 <a class="footnote-reference brackets" href="#eoraptor" id="id4">1</a> 需要具有的功能。本节的代码可以在 <code class="docutils literal notranslate"><span class="pre">ch3-coop</span></code> 分支上找到。</p>
</div>
......
......@@ -866,7 +866,7 @@
<span class="linenos">23</span><span class="p">}</span>
</pre></div>
</div>
<p>通过 <code class="docutils literal notranslate"><span class="pre">current_user_token</span></code> <code class="docutils literal notranslate"><span class="pre">current_trap_cx</span></code> 分别可以获得当前正在执行的应用的地址空间的 token 和可以在内核地址空间中修改位于该应用地址空间中的 Trap 上下文的可变引用</p>
<p>通过 <code class="docutils literal notranslate"><span class="pre">current_user_token</span></code> 可以获得当前正在执行的应用的地址空间的 token 。同时,该应用地址空间中的 Trap 上下文很关键,内核需要访问它来拿到应用进行系统调用的参数并将系统调用返回值写回,通过 <code class="docutils literal notranslate"><span class="pre">current_trap_cx</span></code> 内核可以拿到它访问这个 Trap 上下文的可变引用并进行读写</p>
</div>
</div>
<div class="section" id="id13">
......
......@@ -768,7 +768,7 @@
<p>之前提到的调度策略/算法都是面向单处理器的,如果把这些策略和算法扩展到多处理器环境下,是否需要解决新问题?</p>
<div class="admonition note">
<p class="admonition-title">注解</p>
<p>并行处理需要了解更多的硬件并行架构问题和软件的同步互斥等技术,而深入的硬件并行架构目前不在本书的范畴之内,同步互斥等技术在后续章节才介绍。按道理需要先学习这些内容才能真正和深入理解本小节的内容,但本小节的内容在逻辑上都属于进程调度的范畴,所以就放在这里了。建议可以先大致学习本小节内容,在掌握了进程间同学、同步互斥等技术后,再回头重新学习一些本小节内容。</p>
<p>并行处理需要了解更多的硬件并行架构问题和软件的同步互斥等技术,而深入的硬件并行架构目前不在本书的范畴之内,同步互斥等技术在后续章节才介绍。按道理需要先学习这些内容才能真正和深入理解本小节的内容,但本小节的内容在逻辑上都属于进程调度的范畴,所以就放在这里了。建议可以先大致学习本小节内容,在掌握了进程间通信、同步互斥等技术后,再回头重新学习一些本小节内容。</p>
</div>
<div class="section" id="id29">
<h3>约束条件<a class="headerlink" href="#id29" title="永久链接至标题">#</a></h3>
......
......@@ -499,7 +499,7 @@
<h3>问答作业<a class="headerlink" href="#id10" title="永久链接至标题">#</a></h3>
<ol class="arabic">
<li><p>fork + exec 的一个比较大的问题是 fork 之后的内存页/文件等资源完全没有使用就废弃了,针对这一点,有什么改进策略?</p></li>
<li><p>[选做,不占分]其实使用了题(1)的策略之后,fork + exec 所带来的无效资源的问题已经基本被解决了,但是年来 fork 还是在被不断的批判,那么到底是什么正在”杀死”fork?可以参考 <a class="reference external" href="https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf">论文</a></p></li>
<li><p>[选做,不占分]其实使用了题(1)的策略之后,fork + exec 所带来的无效资源的问题已经基本被解决了,但是年来 fork 还是在被不断的批判,那么到底是什么正在”杀死”fork?可以参考 <a class="reference external" href="https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf">论文</a></p></li>
<li><p>请阅读下列代码,并分析程序的输出,假定不发生运行错误,不考虑行缓冲:</p>
<div class="highlight-c notranslate"><div class="highlight"><pre><span></span><span class="kt">int</span><span class="w"> </span><span class="nf">main</span><span class="p">(){</span>
<span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">val</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span>
......
......@@ -545,7 +545,7 @@
</div>
<div class="section" id="sys-close">
<span id="id10"></span><h3>文件关闭<a class="headerlink" href="#sys-close" title="永久链接至标题">#</a></h3>
<p>在打开文件,对文件完成了读写操作后,还需要关闭文件,这样才让进程释放被这个文件所占用的内核资源。 <code class="docutils literal notranslate"><span class="pre">close</span></code> 的调用参数是文件描述符,但文件被关闭后,文件在内核中的资源会被释放,文件描述符会被回收。这样,进程就不能继续使用该文件描述符进行文件读写了。</p>
<p>在打开文件,对文件完成了读写操作后,还需要关闭文件,这样才让进程释放被这个文件占用的内核资源。 <code class="docutils literal notranslate"><span class="pre">close</span></code> 的调用参数是文件描述符,当文件被关闭后,该文件在内核中的资源会被释放,文件描述符会被回收。这样,进程就不能继续使用该文件描述符进行文件读写了。</p>
<div class="highlight-rust notranslate"><div class="highlight"><pre><span></span><span class="sd">/// 功能:当前进程关闭一个文件。</span>
<span class="sd">/// 参数:fd 表示要关闭的文件的文件描述符。</span>
<span class="sd">/// 返回值:如果成功关闭则返回 0 ,否则返回 -1 。可能的出错原因:传入的文件描述符并不对应一个打开的文件。</span>
......
......@@ -652,7 +652,7 @@ who<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">PipeRingBuffer::read_byte</span></code> 方法可以从管道中读取一个字节,注意在调用它之前需要确保管道缓冲区中不是空的。它会更新循环队列队头的位置,并比较队头和队尾是否相同,如果相同的话则说明管道的状态变为空 <code class="docutils literal notranslate"><span class="pre">EMPTY</span></code> 。仅仅通过比较队头和队尾是否相同不能确定循环队列是否为空,因为它既有可能表示队列为空,也有可能表示队列已满。因此我们需要在 <code class="docutils literal notranslate"><span class="pre">read_byte</span></code> 的同时进行状态更新。</p>
<p><code class="docutils literal notranslate"><span class="pre">PipeRingBuffer::available_read</span></code> 可以计算管道中还有多少个字符可以读取。我们首先需要需要判断队列是否为空,因为队头和队尾相等可能表示队列为空或队列已满,两种情况 <code class="docutils literal notranslate"><span class="pre">available_read</span></code> 的返回值截然不同。如果队列为空的话直接返回 0,否则根据队头和队尾的相对位置进行计算。</p>
<p><code class="docutils literal notranslate"><span class="pre">PipeRingBuffer::available_read</span></code> 可以计算管道中还有多少个字符可以读取。我们首先需要判断队列是否为空,因为队头和队尾相等可能表示队列为空或队列已满,两种情况 <code class="docutils literal notranslate"><span class="pre">available_read</span></code> 的返回值截然不同。如果队列为空的话直接返回 0,否则根据队头和队尾的相对位置进行计算。</p>
<p><code class="docutils literal notranslate"><span class="pre">PipeRingBuffer::all_write_ends_closed</span></code> 可以判断管道的所有写端是否都被关闭了,这是通过尝试将管道中保存的写端的弱引用计数升级为强引用计数来实现的。如果升级失败的话,说明管道写端的强引用计数为 0 ,也就意味着管道所有写端都被关闭了,从而管道中的数据不会再得到补充,待管道中仅剩的数据被读取完毕之后,管道就可以被销毁了。</p>
<p>下面是 <code class="docutils literal notranslate"><span class="pre">Pipe</span></code><code class="docutils literal notranslate"><span class="pre">read</span></code> 方法的实现:</p>
<div class="highlight-rust notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="c1">// os/src/fs/pipe.rs</span>
......
......@@ -453,7 +453,7 @@
<li><p>第 4~7 行我们声明线程函数接受的参数类型为一个名为 <code class="docutils literal notranslate"><span class="pre">FuncArguments</span></code> 的结构体,内含 <code class="docutils literal notranslate"><span class="pre">x</span></code><code class="docutils literal notranslate"><span class="pre">y</span></code> 两个字段。</p></li>
<li><p>第 15 行我们创建并默认初始化三个 <code class="docutils literal notranslate"><span class="pre">pthread_t</span></code> 实例 <code class="docutils literal notranslate"><span class="pre">t0</span></code><code class="docutils literal notranslate"><span class="pre">t1</span></code><code class="docutils literal notranslate"><span class="pre">t2</span></code> ,分别代表我们接下来要创建的三个线程。</p></li>
<li><p>第 16 行在主线程的栈上给出三个线程接受的参数。</p></li>
<li><p>第 9~12 行实现线程运行的函数 <code class="docutils literal notranslate"><span class="pre">func</span></code> ,可以看到它的函数签名符合要求。它实际接受的参数类型应该为我们之前定义的 <code class="docutils literal notranslate"><span class="pre">FuncArguments</span></code> 类型的指针,但是在函数签名中是一个 <code class="docutils literal notranslate"><span class="pre">void</span> <span class="pre">*</span></code> ,所以在第 10 行我们我们首先进行类型转换得到 <code class="docutils literal notranslate"><span class="pre">FuncArguments*</span></code> ,而后才能访问 <code class="docutils literal notranslate"><span class="pre">x</span></code><code class="docutils literal notranslate"><span class="pre">y</span></code> 两个字段并打印出来。</p></li>
<li><p>第 9~12 行实现线程运行的函数 <code class="docutils literal notranslate"><span class="pre">func</span></code> ,可以看到它的函数签名符合要求。它实际接受的参数类型应该为我们之前定义的 <code class="docutils literal notranslate"><span class="pre">FuncArguments</span></code> 类型的指针,但是在函数签名中是一个 <code class="docutils literal notranslate"><span class="pre">void</span> <span class="pre">*</span></code> ,所以在第 10 行我们首先进行类型转换得到 <code class="docutils literal notranslate"><span class="pre">FuncArguments*</span></code> ,而后才能访问 <code class="docutils literal notranslate"><span class="pre">x</span></code><code class="docutils literal notranslate"><span class="pre">y</span></code> 两个字段并打印出来。</p></li>
<li><p>第 17~19 行使用 <code class="docutils literal notranslate"><span class="pre">pthread_create</span></code> 创建三个线程,分别绑定到 <code class="docutils literal notranslate"><span class="pre">t0~t2</span></code> 三个 <code class="docutils literal notranslate"><span class="pre">pthread_t</span></code> 实例上。它们均执行 <code class="docutils literal notranslate"><span class="pre">func</span></code> 函数,但接受的参数有所不同。</p></li>
</ul>
<p>编译运行,一种可能的输出为:</p>
......
......@@ -532,7 +532,7 @@
<h3>死锁避免<a class="headerlink" href="#id7" title="永久链接至标题">#</a></h3>
<p>计算机科学家Dijkstra在1965年为THE操作系统设计提出的一种死锁避免(avoidance)的调度算法,称为银行家算法(banker’s algorithm)算法的核心是判断满足线程的资源请求是否会导致整个系统进入不安全状态。如果是,就拒绝线程的资源请求;如果满足请求后系统状态仍然是安全的,就分配资源给线程。</p>
<p>状态是安全的,是指存在一个资源分配/线程执行序列使得所有的线程都能获取其所需资源并完成线程的工作。如果找不到这样的资源分配/线程执行序列,那么状态是不安全的。这里把线程的执行过程简化为:申请资源、释放资源的一系列资源操作。这意味这线程执行完毕后,会释放其占用的所有资源。</p>
<p>我们需要知道,不安全状态并不等于死锁,而是指有死锁的可能性。安全状态和不安全状态的区别是:从安全状态出发,操作系统通过调度线程执行序列,能够保证所有线程都能完成,一定不会出现死锁;而从不安全状态出发,就没有这样的保证,可能出现死锁。</p>
<p>我们需要知道,不安全状态并不等于死锁,而是指有死锁的可能性。安全状态和不安全状态的区别是:从安全状态出发,操作系统通过调度线程执行序列,能够保证所有线程都能完成,一定不会出现死锁;而从不安全状态出发,就没有这样的保证,可能出现死锁。</p>
<div class="section" id="id8">
<h4>银行家算法的数据结构<a class="headerlink" href="#id8" title="永久链接至标题">#</a></h4>
<p>为了描述操作系统中可利用的资源、所有线程对资源的最大需求、系统中的资源分配,以及所有线程还需要多少资源的情况,需要定义对应的四个数据结构:</p>
......
......@@ -555,7 +555,7 @@ uart@10000000<span class="w"> </span><span class="o">{</span>
<span class="linenos">17</span><span class="w"> </span><span class="p">}</span>
</pre></div>
</div>
<p>在以往的操作系统实现中,当一个进程通过 <code class="docutils literal notranslate"><span class="pre">sys_read</span></code> 系统调用来获取串口字符时,并没有用上中断机制。但一个进程读不到字符的时候,将会被操作系统调度到就绪队列的尾部,等待下一次执行的时刻。这其实就是一种变相的轮询方式来获取串口的输入字符。这里其实是可以对进程管理做的一个改进,来避免进程通过轮询的方式检查串口字符输入。既然我们已经在上一章设计实现了让用户态线程挂起的同步互斥机制,我们就可以把自然地把这种机制也用来内核中,在外设不能及时提供资源的情况下,让想获取资源的线程或进程挂起,知道外设提供了资源,再唤醒进程继续执行。</p>
<p>在以往的操作系统实现中,当一个进程通过 <code class="docutils literal notranslate"><span class="pre">sys_read</span></code> 系统调用来获取串口字符时,并没有用上中断机制。但一个进程读不到字符的时候,将会被操作系统调度到就绪队列的尾部,等待下一次执行的时刻。这其实就是一种变相的轮询方式来获取串口的输入字符。这里其实是可以对进程管理做的一个改进,来避免进程通过轮询的方式检查串口字符输入。既然我们已经在上一章设计实现了让用户态线程挂起的同步互斥机制,我们就可以把这种机制也用在内核中,在外设不能及时提供资源的情况下,让想获取资源的线程或进程挂起,直到外设提供了资源,再唤醒线程或进程继续执行。</p>
<p>目前,支持中断的驱动可有效地支持等待的进程唤醒的操作。以串口为例,如果一个进程通过系统调用想获取串口输入,但此时串口还没有输入的字符,那么操作系统就设置一个进程等待串口输入的条件变量(条件变量包含一个等待队列),然后把当前进程设置等待状态,并挂在这个等待队列上,再把CPU让给其它就绪进程执行。对于串口输入的处理,由于要考虑中断,相对就要复杂一些。读字符串的代码如下所示:</p>
<div class="highlight-Rust notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="c1">//os/src/fs/stdio.rs</span>
<span class="linenos"> 2</span><span class="k">impl</span><span class="w"> </span><span class="n">File</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">Stdin</span><span class="w"> </span><span class="p">{</span>
......
......@@ -497,11 +497,11 @@
<p><strong>I/O传输状态</strong></p>
<p>设备驱动程序控制virtio设备进行I/O传输过程中,会经历一系列过程和执行状态,包括 <cite>I/O请求</cite> 状态、 <cite>I/O处理</cite> 状态、 <cite>I/O完成</cite> 状态、 <cite>I/O错误</cite> 状态、 <cite>I/O后续处理</cite> 状态等。设备驱动程序在执行过程中,需要对上述状态进行不同的处理。</p>
<p>virtio设备进行I/O传输过程中,设备驱动会指出 <cite>I/O请求</cite> 队列的当前位置状态信息,这样设备能查到I/O请求的信息,并根据 <cite>I/O请求</cite> 进行I/O传输;而设备会指出 <cite>I/O完成</cite> 队列的当前位置状态信息,这样设备驱动通过读取 <cite>I/O完成</cite> 数据结构中的状态信息,就知道设备是否完成I/O请求的相应操作,并进行后续事务处理。</p>
<p>比如,virtio_blk设备驱动发出一个读设备块的I/O请求,并在某确定位置给出这个I/O请求的地址,然后给设备发出’kick’通知(读或写相关I/O寄存器映射的内存地址),此时处于I/O请求状态;设备在得到通知后,此时处于 <cite>I/O处理</cite> 状态,它解析个I/O请求,完成个I/O请求的处理,即把磁盘块内容读入到内存中,并给出读出的块数据的内存地址,再通过中断通知设备驱动,此时处于 <cite>I/O完成</cite> 状态;如果磁盘块读取发生错误,此时处于 <cite>I/O错误</cite> 状态;设备驱动通过中断处理例程,此时处于 <cite>I/O后续处理</cite> 状态,设备驱动知道设备已经完成读磁盘块操作,会根据磁盘块数据所在内存地址,把数据传递给文件系统进行进一步处理;如果设备驱动发现磁盘块读错误,则会进行错误恢复相关的后续处理。</p>
<p>比如,virtio_blk设备驱动发出一个读设备块的I/O请求,并在某确定位置给出这个I/O请求的地址,然后给设备发出’kick’通知(读或写相关I/O寄存器映射的内存地址),此时处于I/O请求状态;设备在得到通知后,此时处于 <cite>I/O处理</cite> 状态,它解析这个I/O请求,完成这个I/O请求的处理,即把磁盘块内容读入到内存中,并给出读出的块数据的内存地址,再通过中断通知设备驱动,此时处于 <cite>I/O完成</cite> 状态;如果磁盘块读取发生错误,此时处于 <cite>I/O错误</cite> 状态;设备驱动通过中断处理例程,此时处于 <cite>I/O后续处理</cite> 状态,设备驱动知道设备已经完成读磁盘块操作,会根据磁盘块数据所在内存地址,把数据传递给文件系统进行进一步处理;如果设备驱动发现磁盘块读错误,则会进行错误恢复相关的后续处理。</p>
</div>
<div class="section" id="id9">
<h3>virtio设备交互机制<a class="headerlink" href="#id9" title="永久链接至标题">#</a></h3>
<p>virtio设备交互机制包括基于Notifications的事件通知和基于virtqueue虚拟队列的数据传输。事件通知是指设备和驱动程序必须通知对方,它们有数据需要对方处理。数据传输是指设备和驱动程序之间进行进行I/O数据(如磁盘块数据、网络包)传输。</p>
<p>virtio设备交互机制包括基于Notifications的事件通知和基于virtqueue虚拟队列的数据传输。事件通知是指设备和驱动程序必须通知对方,它们有数据需要对方处理。数据传输是指设备和驱动程序之间进行I/O数据(如磁盘块数据、网络包)传输。</p>
<p><strong>Notification通知</strong></p>
<p>驱动程序和设备在交互过程中需要相互通知对方:驱动程序组织好相关命令/信息要通知设备去处理I/O事务,设备处理完I/O事务后,要通知驱动程序进行后续事务,如回收内存,向用户进程反馈I/O事务的处理结果等。</p>
<p>驱动程序通知设备可用``门铃 doorbell``机制,即采用PIO或MMIO方式访问设备特定寄存器,QEMU进行拦截再通知其模拟的设备。设备通知驱动程序一般用中断机制,即在QEMU中进行中断注入,让CPU响应并执行中断处理例程,来完成对I/O执行结果的处理。</p>
......@@ -512,12 +512,12 @@
<span id="term-virtqueue"></span><h3><strong>virtqueue虚拟队列</strong><a class="headerlink" href="#virtqueue" title="永久链接至标题">#</a></h3>
<p>virtio协议中一个关键部分是virtqueue,在virtio规范中,virtqueue是virtio设备上进行批量数据传输的机制和抽象表示。在设备驱动实现和Qemu中virtio设备的模拟实现中,virtqueue是一种数据结构,用于设备和驱动程序中执行各种数据传输操作。</p>
<p>操作系统在Qemu上运行时,virtqueue是 virtio 驱动程序和 virtio 设备访问的同一块内存区域。</p>
<p>当涉及到 virtqueue 的描述时,有很多不一致的地方。有将其与vring(virtio-rings或VRings)等同表示,也有将二者分别单独描述为不同的对象。我们将在这里单独描述它们,因为vring是virtueues的主要组成部分,是达成virtio设备和驱动程序之间数据传输的数据结构, vring本质是virtio设备和驱动程序之间的共享内存,但 virtqueue 不仅仅只有vring。</p>
<p>当涉及到 virtqueue 的描述时,有很多不一致的地方。有将其与vring(virtio-rings或VRings)等同表示,也有将二者分别单独描述为不同的对象。我们将在这里单独描述它们,因为vring是virtqueues的主要组成部分,是达成virtio设备和驱动程序之间数据传输的数据结构, vring本质是virtio设备和驱动程序之间的共享内存,但 virtqueue 不仅仅只有vring。</p>
<p>virtqueue由三部分组成(如下图所示):</p>
<ul class="simple">
<li><p>描述符表 Descriptor Table:描述符表是描述符为组成元素的数组,每个描述符描述了一个内存buffer 的address/length。而内存buffer中包含I/O请求的命令/数据(由virtio设备驱动填写),也可包含I/O完成的返回结果(由virtio设备填写)等。</p></li>
<li><p>可用环 Available Ring:一种vring,记录了virtio设备驱动程序发出的I/O请求索引,即被virtio设备驱动程序更新的描述符索引的集合,需要virtio设备进行读取并完成相关I/O操作;</p></li>
<li><p>已用环 Used Ring:另一种vring,记录了virtio设备发出的I/O完成索引,即被virtio设备更新的描述符索引d 集合,需要vrtio设备驱动程序进行读取并对I/O操作结果进行进一步处理。</p></li>
<li><p>已用环 Used Ring:另一种vring,记录了virtio设备发出的I/O完成索引,即被virtio设备更新的描述符索引集合,需要vrtio设备驱动程序进行读取并对I/O操作结果进行进一步处理。</p></li>
</ul>
<img alt="../_images/virtqueue-arch.png" class="align-center" id="virtqueue-arch" src="../_images/virtqueue-arch.png" />
<p><strong>描述符表 Descriptor Table</strong></p>
......@@ -719,7 +719,7 @@
</div>
<div class="section" id="id14">
<h3><strong>接收设备I/O响应的操作</strong><a class="headerlink" href="#id14" title="永久链接至标题">#</a></h3>
<p>一旦设备完成了I/O请求,形成I/O响应,就会更新描述符所指向的缓冲区,并向驱动程序发送已用缓冲区通知(used buffer notification)。一般会采用中断这种更加高效的通知机制。设备驱动程序在收到中断后,就会更加I/O响应信息进行后续处理。相关的伪代码如下所示:</p>
<p>一旦设备完成了I/O请求,形成I/O响应,就会更新描述符所指向的缓冲区,并向驱动程序发送已用缓冲区通知(used buffer notification)。一般会采用中断这种更加高效的通知机制。设备驱动程序在收到中断后,就会I/O响应信息进行后续处理。相关的伪代码如下所示:</p>
<div class="highlight-Rust notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="c1">// virtio_drivers/src/blk.rs</span>
<span class="linenos"> 2</span><span class="k">impl</span><span class="o">&lt;</span><span class="n">H</span>: <span class="nc">Hal</span><span class="o">&gt;</span><span class="w"> </span><span class="n">VirtIOBlk</span><span class="o">&lt;&#39;</span><span class="nb">_</span><span class="p">,</span><span class="w"> </span><span class="n">H</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span>
<span class="linenos"> 3</span><span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">ack_interrupt</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span>
......
......@@ -429,7 +429,7 @@
<span class="linenos">13</span><span class="p">}</span>
</pre></div>
</div>
<p>其中成员变量 <code class="docutils literal notranslate"><span class="pre">free_head</span></code> 指空闲描述符链表头,初始时所有描述符通过 <code class="docutils literal notranslate"><span class="pre">next</span></code> 指针依次相连形成空闲链表,成员变量 <code class="docutils literal notranslate"><span class="pre">last_used_idx</span></code> 是指设备上次已取的已用环元素位置。成员变量 <code class="docutils literal notranslate"><span class="pre">avail_idx</span></code>指设备上次已取的已用环元素位置</p>
<p>其中成员变量 <code class="docutils literal notranslate"><span class="pre">free_head</span></code> 指空闲描述符链表头,初始时所有描述符通过 <code class="docutils literal notranslate"><span class="pre">next</span></code> 指针依次相连形成空闲链表,成员变量 <code class="docutils literal notranslate"><span class="pre">last_used_idx</span></code> 是指设备上次已取的已用环元素位置。成员变量 <code class="docutils literal notranslate"><span class="pre">avail_idx</span></code>可用环的索引值</p>
<p id="term-virtio-hal">这里出现的 <code class="docutils literal notranslate"><span class="pre">Hal</span></code> trait是 <cite>virtio_drivers</cite> 库中定义的一个trait,用于抽象出与具体操作系统相关的操作,主要与内存分配和虚实地址转换相关。这里我们只给出trait的定义,对应操作系统的具体实现在后续的章节中会给出。</p>
<div class="highlight-Rust notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="k">pub</span><span class="w"> </span><span class="k">trait</span><span class="w"> </span><span class="n">Hal</span><span class="w"> </span><span class="p">{</span>
<span class="linenos"> 2</span><span class="w"> </span><span class="sd">/// Allocates the given number of contiguous physical pages of DMA memory for virtio use.</span>
......@@ -524,7 +524,7 @@
<span class="linenos">17</span><span class="p">}</span>
</pre></div>
</div>
<p>从上面的代码可以看到,操作系统中表示表示virtio_blk设备的全局变量 <code class="docutils literal notranslate"><span class="pre">BLOCK_DEVICE</span></code> 的类型是 <code class="docutils literal notranslate"><span class="pre">VirtIOBlock</span></code> ,封装了来自virtio_derivers 模块的 <code class="docutils literal notranslate"><span class="pre">VirtIOBlk</span></code> 类型。这样,操作系统内核就可以通过 <code class="docutils literal notranslate"><span class="pre">BLOCK_DEVICE</span></code> 全局变量来访问virtio_blk设备了。而 <code class="docutils literal notranslate"><span class="pre">VirtIOBlock</span></code> 中的 <code class="docutils literal notranslate"><span class="pre">condvars:</span> <span class="pre">BTreeMap&lt;u16,</span> <span class="pre">Condvar&gt;</span></code> 条件变量结构,是用于进程在等待 I/O读或写操作完全前,通过条件变量让进程处于挂起状态。当virtio_blk设备完成I/O操作后,会通过中断唤醒等待的进程。而操作系统对virtio_blk设备的初始化除了封装 <code class="docutils literal notranslate"><span class="pre">VirtIOBlk</span></code> 类型并调用 <code class="docutils literal notranslate"><span class="pre">VirtIOBlk::&lt;VirtioHal&gt;::new()</span></code> 外,还需要初始化 <code class="docutils literal notranslate"><span class="pre">condvars</span></code> 条件变量结构,而每个条件变量对应着一个虚拟队列条目的编号,这意味着每次I/O请求都绑定了一个条件变量,让发出请求的线程/进程可以被挂起。</p>
<p>从上面的代码可以看到,操作系统中表示virtio_blk设备的全局变量 <code class="docutils literal notranslate"><span class="pre">BLOCK_DEVICE</span></code> 的类型是 <code class="docutils literal notranslate"><span class="pre">VirtIOBlock</span></code> ,封装了来自virtio_derivers 模块的 <code class="docutils literal notranslate"><span class="pre">VirtIOBlk</span></code> 类型。这样,操作系统内核就可以通过 <code class="docutils literal notranslate"><span class="pre">BLOCK_DEVICE</span></code> 全局变量来访问virtio_blk设备了。而 <code class="docutils literal notranslate"><span class="pre">VirtIOBlock</span></code> 中的 <code class="docutils literal notranslate"><span class="pre">condvars:</span> <span class="pre">BTreeMap&lt;u16,</span> <span class="pre">Condvar&gt;</span></code> 条件变量结构,是用于进程在等待 I/O读或写操作完全前,通过条件变量让进程处于挂起状态。当virtio_blk设备完成I/O操作后,会通过中断唤醒等待的进程。而操作系统对virtio_blk设备的初始化除了封装 <code class="docutils literal notranslate"><span class="pre">VirtIOBlk</span></code> 类型并调用 <code class="docutils literal notranslate"><span class="pre">VirtIOBlk::&lt;VirtioHal&gt;::new()</span></code> 外,还需要初始化 <code class="docutils literal notranslate"><span class="pre">condvars</span></code> 条件变量结构,而每个条件变量对应着一个虚拟队列条目的编号,这意味着每次I/O请求都绑定了一个条件变量,让发出请求的线程/进程可以被挂起。</p>
<div class="highlight-Rust notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="k">impl</span><span class="w"> </span><span class="n">VirtIOBlock</span><span class="w"> </span><span class="p">{</span>
<span class="linenos"> 2</span><span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">new</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span>
<span class="linenos"> 3</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">virtio_blk</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">unsafe</span><span class="w"> </span><span class="p">{</span>
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册