Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
rcore-os
RCore Tutorial Book V3
提交
763e4e6d
R
RCore Tutorial Book V3
项目概览
rcore-os
/
RCore Tutorial Book V3
9 个月 前同步成功
通知
3
Star
938
Fork
153
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
RCore Tutorial Book V3
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
763e4e6d
编写于
7月 22, 2023
作者:
W
wyfcyx
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
deploy:
2e993ce5
上级
4dfea73b
变更
25
展开全部
显示空白变更内容
内联
并排
Showing
25 changed file
with
626 addition
and
626 deletion
+626
-626
_sources/chapter1/5support-func-call.rst.txt
_sources/chapter1/5support-func-call.rst.txt
+2
-2
_sources/chapter3/3multiprogramming.rst.txt
_sources/chapter3/3multiprogramming.rst.txt
+1
-1
_sources/chapter4/6multitasking-based-on-as.rst.txt
_sources/chapter4/6multitasking-based-on-as.rst.txt
+2
-2
_sources/chapter5/4scheduling.rst.txt
_sources/chapter5/4scheduling.rst.txt
+590
-590
_sources/chapter5/5exercise.rst.txt
_sources/chapter5/5exercise.rst.txt
+1
-1
_sources/chapter6/1fs-interface.rst.txt
_sources/chapter6/1fs-interface.rst.txt
+1
-1
_sources/chapter7/2pipe.rst.txt
_sources/chapter7/2pipe.rst.txt
+1
-1
_sources/chapter8/1thread-kernel.rst.txt
_sources/chapter8/1thread-kernel.rst.txt
+1
-1
_sources/chapter8/5concurrency-problem.rst.txt
_sources/chapter8/5concurrency-problem.rst.txt
+1
-1
_sources/chapter9/2device-driver-1.rst.txt
_sources/chapter9/2device-driver-1.rst.txt
+1
-1
_sources/chapter9/2device-driver-2.rst.txt
_sources/chapter9/2device-driver-2.rst.txt
+5
-5
_sources/chapter9/2device-driver-3.rst.txt
_sources/chapter9/2device-driver-3.rst.txt
+2
-2
chapter1/5support-func-call.html
chapter1/5support-func-call.html
+1
-1
chapter3/3multiprogramming.html
chapter3/3multiprogramming.html
+1
-1
chapter4/6multitasking-based-on-as.html
chapter4/6multitasking-based-on-as.html
+1
-1
chapter5/4scheduling.html
chapter5/4scheduling.html
+1
-1
chapter5/5exercise.html
chapter5/5exercise.html
+1
-1
chapter6/1fs-interface.html
chapter6/1fs-interface.html
+1
-1
chapter7/2pipe.html
chapter7/2pipe.html
+1
-1
chapter8/1thread-kernel.html
chapter8/1thread-kernel.html
+1
-1
chapter8/5concurrency-problem.html
chapter8/5concurrency-problem.html
+1
-1
chapter9/2device-driver-1.html
chapter9/2device-driver-1.html
+1
-1
chapter9/2device-driver-2.html
chapter9/2device-driver-2.html
+5
-5
chapter9/2device-driver-3.html
chapter9/2device-driver-3.html
+2
-2
searchindex.js
searchindex.js
+1
-1
未找到文件。
_sources/chapter1/5support-func-call.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -210,7 +210,7 @@
它的开头和结尾分别在 sp(x2) 和 fp(s0) 所指向的地址。按照地址从高到低分别有以下内容,它们都是通过 ``sp`` 加上一个偏移量来访问的:
- ``ra`` 寄存器保存其返回之后的跳转地址,是一个调用者保存寄存器;
- ``ra`` 寄存器保存其返回之后的跳转地址,是一个
被
调用者保存寄存器;
- 父亲栈帧的结束地址 ``fp`` ,是一个被调用者保存寄存器;
- 其他被调用者保存寄存器 ``s1`` ~ ``s11`` ;
- 函数所使用到的局部变量。
...
...
_sources/chapter3/3multiprogramming.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -12,7 +12,7 @@
- 任务运行状态:任务从开始到结束执行过程中所处的不同运行状态:未初始化、准备执行、正在执行、已退出
- 任务控制块:管理程序的执行过程的任务上下文,控制程序的执行与暂停
- 任务相关系统调用:应用程序和操作系统
直接
的接口,用于程序主动暂停 ``sys_yield`` 和主动退出 ``sys_exit``
- 任务相关系统调用:应用程序和操作系统
之间
的接口,用于程序主动暂停 ``sys_yield`` 和主动退出 ``sys_exit``
这些都是三叠纪“始初龙”协作式操作系统 [#eoraptor]_ 需要具有的功能。本节的代码可以在 ``ch3-coop`` 分支上找到。
...
...
_sources/chapter4/6multitasking-based-on-as.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -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 处理的实现
------------------------------------
...
...
_sources/chapter5/4scheduling.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -516,7 +516,7 @@ EDF调度算法按照进程的截止时间的早晚来分配优先级,截止
.. note::
并行处理需要了解更多的硬件并行架构问题和软件的同步互斥等技术,而深入的硬件并行架构目前不在本书的范畴之内,同步互斥等技术在后续章节才介绍。按道理需要先学习这些内容才能真正和深入理解本小节的内容,但本小节的内容在逻辑上都属于进程调度的范畴,所以就放在这里了。建议可以先大致学习本小节内容,在掌握了进程间
同学、同步互斥等技术后,再回头重新学习一些本小节内容。
并行处理需要了解更多的硬件并行架构问题和软件的同步互斥等技术,而深入的硬件并行架构目前不在本书的范畴之内,同步互斥等技术在后续章节才介绍。按道理需要先学习这些内容才能真正和深入理解本小节的内容,但本小节的内容在逻辑上都属于进程调度的范畴,所以就放在这里了。建议可以先大致学习本小节内容,在掌握了进程间
通信、同步互斥等技术后,再回头重新学习一些本小节内容。
约束条件
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
...
_sources/chapter5/5exercise.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -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) 请阅读下列代码,并分析程序的输出,假定不发生运行错误,不考虑行缓冲:
...
...
_sources/chapter6/1fs-interface.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -195,7 +195,7 @@ Blocks 给出 ``os`` 目录也占用 8 个块进行存储。实际上目录也
文件关闭
++++++++++++++++++++++++++++++++++++++++++++++++++
在打开文件,对文件完成了读写操作后,还需要关闭文件,这样才让进程释放被这个文件
所占用的内核资源。 ``close`` 的调用参数是文件描述符,但文件被关闭后,
文件在内核中的资源会被释放,文件描述符会被回收。这样,进程就不能继续使用该文件描述符进行文件读写了。
在打开文件,对文件完成了读写操作后,还需要关闭文件,这样才让进程释放被这个文件
占用的内核资源。 ``close`` 的调用参数是文件描述符,当文件被关闭后,该
文件在内核中的资源会被释放,文件描述符会被回收。这样,进程就不能继续使用该文件描述符进行文件读写了。
.. code-block:: rust
...
...
_sources/chapter7/2pipe.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -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 ,也就意味着管道所有写端都被关闭了,从而管道中的数据不会再得到补充,待管道中仅剩的数据被读取完毕之后,管道就可以被销毁了。
...
...
_sources/chapter8/1thread-kernel.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -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`` 函数,但接受的参数有所不同。
编译运行,一种可能的输出为:
...
...
_sources/chapter8/5concurrency-problem.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -193,7 +193,7 @@ A是共享变量。粗略地看,可以估计执行流程为:线程thr1先被
状态是安全的,是指存在一个资源分配/线程执行序列使得所有的线程都能获取其所需资源并完成线程的工作。如果找不到这样的资源分配/线程执行序列,那么状态是不安全的。这里把线程的执行过程简化为:申请资源、释放资源的一系列资源操作。这意味这线程执行完毕后,会释放其占用的所有资源。
我们需要知道,不安全状态并不等于死锁,而是指有死锁的可能性。安全
全
状态和不安全状态的区别是:从安全状态出发,操作系统通过调度线程执行序列,能够保证所有线程都能完成,一定不会出现死锁;而从不安全状态出发,就没有这样的保证,可能出现死锁。
我们需要知道,不安全状态并不等于死锁,而是指有死锁的可能性。安全状态和不安全状态的区别是:从安全状态出发,操作系统通过调度线程执行序列,能够保证所有线程都能完成,一定不会出现死锁;而从不安全状态出发,就没有这样的保证,可能出现死锁。
.. chyyuu 有一个安全,不安全,死锁的图???
...
...
_sources/chapter9/2device-driver-1.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -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让给其它就绪进程执行。对于串口输入的处理,由于要考虑中断,相对就要复杂一些。读字符串的代码如下所示:
...
...
_sources/chapter9/2device-driver-2.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -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是virt
q
ueues的主要组成部分,是达成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:
...
...
_sources/chapter9/2device-driver-3.rst.txt
浏览文件 @
763e4e6d
...
...
@@ -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
...
...
chapter1/5support-func-call.html
浏览文件 @
763e4e6d
...
...
@@ -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>
...
...
chapter3/3multiprogramming.html
浏览文件 @
763e4e6d
...
...
@@ -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>
...
...
chapter4/6multitasking-based-on-as.html
浏览文件 @
763e4e6d
...
...
@@ -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"
>
...
...
chapter5/4scheduling.html
浏览文件 @
763e4e6d
...
...
@@ -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>
...
...
chapter5/5exercise.html
浏览文件 @
763e4e6d
...
...
@@ -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>
...
...
chapter6/1fs-interface.html
浏览文件 @
763e4e6d
...
...
@@ -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>
...
...
chapter7/2pipe.html
浏览文件 @
763e4e6d
...
...
@@ -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>
...
...
chapter8/1thread-kernel.html
浏览文件 @
763e4e6d
...
...
@@ -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>
...
...
chapter8/5concurrency-problem.html
浏览文件 @
763e4e6d
...
...
@@ -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>
...
...
chapter9/2device-driver-1.html
浏览文件 @
763e4e6d
...
...
@@ -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>
...
...
chapter9/2device-driver-2.html
浏览文件 @
763e4e6d
...
...
@@ -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是virt
q
ueues的主要组成部分,是达成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"
>
<
</span><span
class=
"n"
>
H
</span>
:
<span
class=
"nc"
>
Hal
</span><span
class=
"o"
>
>
</span><span
class=
"w"
>
</span><span
class=
"n"
>
VirtIOBlk
</span><span
class=
"o"
>
<'
</span><span
class=
"nb"
>
_
</span><span
class=
"p"
>
,
</span><span
class=
"w"
>
</span><span
class=
"n"
>
H
</span><span
class=
"o"
>
>
</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"
>
&
</span><span
class=
"k"
>
mut
</span><span
class=
"w"
>
</span><span
class=
"bp"
>
self
</span><span
class=
"p"
>
)
</span><span
class=
"w"
>
</span>
-
>
<span
class=
"kt"
>
bool
</span>
<span
class=
"p"
>
{
</span>
...
...
chapter9/2device-driver-3.html
浏览文件 @
763e4e6d
...
...
@@ -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
<
u16,
</span>
<span
class=
"pre"
>
Condvar
>
</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::
<
VirtioHal
>
::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
<
u16,
</span>
<span
class=
"pre"
>
Condvar
>
</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::
<
VirtioHal
>
::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>
-
>
<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>
...
...
searchindex.js
浏览文件 @
763e4e6d
此差异已折叠。
点击以展开。
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录