282 lines
15 KiB
ReStructuredText
282 lines
15 KiB
ReStructuredText
引言
|
||
========================================
|
||
|
||
本章导读
|
||
--------------------------
|
||
|
||
|
||
..
|
||
chyyuu:有一个ascii图,画出我们做的OS。
|
||
|
||
|
||
本章展现了操作系统一系列功能:
|
||
|
||
- 通过提前加载应用程序到内存,减少应用程序切换开销
|
||
- 通过协作机制支持程序主动放弃处理器,提高系统执行效率
|
||
- 通抢占机制支持程序被动放弃处理器,提高不同程序对处理器资源使用的公平性,也进一步提高了应用对I/O事件的响应效率
|
||
|
||
|
||
上一章,我们实现了一个简单的批处理系统。首先,它能够自动按照顺序加载并运行序列中的每一个应用,当一个应用运行结束之后无需操作员的手动替换;另一方面,在硬件提供的特权级机制的帮助下,运行在更高特权级的它不会受到有意或者无意出错的应用的影响,可以全方位监控运行在用户态特权级的应用的执行,一旦应用越过了硬件所设置特权级界限或主动申请获得操作系统的服务,就会触发 Trap 并进入到批处理系统中进行处理。无论原因是应用出错或是应用声明自己执行完毕,批处理系统都只需要加载序列中的下一个应用并进入执行。可以看到批处理系统的特性是:在内存中同一时间最多只需驻留一个应用。这是因为只有当一个应用出错或退出之后,批处理系统才会去将另一个应用加载到相同的一块内存区域。
|
||
|
||
而计算机硬件在快速发展,内存容量在逐渐增大,处理器的速度也在增加,外设IO性能方面的进展不大。这就使得以往内存只能放下一个程序的情况得到很大改善,但处理器的空闲程度加大了。于是科学家就开始考虑在内存中尽量同时驻留多个应用,这样处理器的利用率就会提高。但只有一个程序执行完毕后或主动放弃执行,处理器才能执行另外一个程序。这种运行方式称为 **多道程序** 。
|
||
|
||
|
||
协作式操作系统
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
早期的计算机系统大部分是单处理器计算机系统。当处理器进一步发展后,它与IO的速度差距也进一步拉大。这时计算机科学家发现,在 **多道程序** 运行方式下,一个程序如果不让出处理器,其他程序是无法执行的。如果一个应用由于IO操作让处理器空闲下来或让处理器忙等,那其他需要处理器资源进行计算的应用还是没法使用空闲的处理器资源。于是就想到,让应用在执行IO操作时,可以主动 **释放处理器** ,让其他应用继续执行。当然执行 **放弃处理器** 的操作算是一种对处理器资源的直接管理,所以应用程序可以发出这样的系统调用,让操作系统来具体完成。这样的操作系统就是支持 **多道程序** 协作式操作系统。
|
||
|
||
抢占式操作系统
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
计算机科学家很快发现,编写应用程序的科学家(简称应用程序员)来自不同的领域,他们不一定有友好互助的意识,也不了解其他程序的执行情况,很难(也没必要)有提高整个系统利用率上的大局观。在他们的脑海里,整个计算机就应该是为他们自己的应用准备的,不用考虑其他程序的运行。这导致应用程序员在编写程序时,无法做到在程序的合适位置放置 **放弃处理器的系统调用请求** ,这样系统的整体利用率还是无法提高。
|
||
|
||
所以,站在系统的层面,还是需要有一种办法能强制打断应用程序的执行,来提高整个系统的效率,让在整个系统中执行的多个程序之间占用计算机资源的情况相对公平一些。根据计算机系统的硬件设计,为提高I/O效率,外设可以通过硬件中断机制来与处理机进行I/O交互操作。这种硬件中断机制·可随时打断应用程序的执行,并让操作系统来完成对外设的I/O响应。
|
||
|
||
而操作系统可进一步利用某种以固定时长为时间间隔的外设中断(比如时钟中断)来强制打断一个程序的执行,这样一个程序只能运行一段时间(可以简称为一个时间片, Time Slice)就一定会让出处理器,且操作系统可以在处理外设的I/O响应后,让不同应用程序分时占用处理器执行,并可通过程序占用处理器的总执行时间来评估运行的程序对处理器资源的消耗。
|
||
|
||
.. _term-task:
|
||
|
||
我们可以把一个程序在一个时间片上占用处理器执行的过程称为一个 **任务** (Task),让操作系统对不同程序的 **任务** 进行管理。通过平衡各个程序在整个时间段上的任务数,就达到一定程度的系统公平和高效的系统效率。在一个包含多个时间片的时间段上,会有属于不同程序的多个任务在轮流占用处理器执行,这样的操作系统就是支持 **分时多任务** 的抢占式操作系统。
|
||
|
||
|
||
本章所介绍的多道程序和分时多任务系统都有一些共同的特点:在内存中同一时间可以驻留多个应用。所有的应用都是在系统启动的时候分别加载到内存的不同区域中。由于目前计算机系统中只有一个处理器,则同一时间最多只有一个应用在执行,剩下的应用则处于就绪状态,需要内核将处理器分配给它们才能开始执行。一旦应用开始执行,它就处于运行状态了。
|
||
|
||
本章我们主要是着眼于抢占式的支持多进程的操作系统,通过时钟中断来达到进程的切换。当然,我们的OS也支持进程主动放弃执行。具体请阅读之后的章节。
|
||
|
||
|
||
.. note::
|
||
|
||
读者也许会有疑问:由于只有一个 处理器,即使这样做,同一时间最多还是只能运行一个应用,还浪费了更多的内存来把所有
|
||
的应用都加载进来。那么这样做有什么意义呢?
|
||
|
||
读者可以带着这个问题继续看下去。后面我们会介绍这样做到底能够解决什么问题。
|
||
|
||
实践体验
|
||
-------------------------------------
|
||
|
||
.. _term-multiprogramming:
|
||
.. _term-time-sharing-multitasking:
|
||
|
||
获取分时多任务系统的代码:
|
||
|
||
.. code-block:: console
|
||
|
||
$ git checkout ch3
|
||
|
||
在 qemu 模拟器上运行本章代码:
|
||
|
||
.. code-block:: console
|
||
|
||
$ cd os
|
||
$ make run
|
||
|
||
将 Maix 系列开发板连接到 PC,并在上面运行本章代码:
|
||
|
||
.. code-block:: console
|
||
|
||
$ cd os
|
||
$ make run BOARD=k210
|
||
|
||
多道程序的应用分别会输出一个不同的字母矩阵。当他们交替执行的时候,以 k210 平台为例,我们将看到字母行的交错输出:
|
||
|
||
.. code-block::
|
||
|
||
[rustsbi] Version 0.1.0
|
||
.______ __ __ _______.___________. _______..______ __
|
||
| _ \ | | | | / | | / || _ \ | |
|
||
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
|
||
| / | | | | \ \ | | \ \ | _ < | |
|
||
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
|
||
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
|
||
|
||
[rustsbi] Platform: K210
|
||
[rustsbi] misa: RV64ACDFIMSU
|
||
[rustsbi] mideleg: 0x222
|
||
[rustsbi] medeleg: 0x1ab
|
||
[rustsbi] Kernel entry: 0x80020000
|
||
[kernel] Hello, world!
|
||
AAAAAAAAAA [1/5]
|
||
BBBBBBBBBB [1/2]
|
||
CCCCCCCCCC [1/3]
|
||
AAAAAAAAAA [2/5]
|
||
BBBBBBBBBB [2/2]
|
||
CCCCCCCCCC [2/3]
|
||
AAAAAAAAAA [3/5]
|
||
Test write_b OK!
|
||
[kernel] Application exited with code 0
|
||
CCCCCCCCCC [3/3]
|
||
AAAAAAAAAA [4/5]
|
||
Test write_c OK!
|
||
[kernel] Application exited with code 0
|
||
AAAAAAAAAA [5/5]
|
||
Test write_a OK!
|
||
[kernel] Application exited with code 0
|
||
[kernel] Panicked at src/task/mod.rs:97 All applications completed!
|
||
[rustsbi] reset triggered! todo: shutdown all harts on k210; program halt
|
||
|
||
分时多任务系统应用分为两种。编号为 00/01/02 的应用分别会计算质数 3/5/7 的幂次对一个大质数取模的余数,并会将结果阶段性输出。编号为 03 的
|
||
应用则会等待三秒钟之后再退出。以 k210 平台为例,我们将会看到 00/01/02 三个应用分段完成它们的计算任务,而应用 03 由于等待时间过长总是
|
||
最后一个结束执行。
|
||
|
||
.. code-block::
|
||
|
||
[rustsbi] RustSBI version 0.1.1
|
||
.______ __ __ _______.___________. _______..______ __
|
||
| _ \ | | | | / | | / || _ \ | |
|
||
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
|
||
| / | | | | \ \ | | \ \ | _ < | |
|
||
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
|
||
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
|
||
|
||
[rustsbi] Platform: K210 (Version 0.1.0)
|
||
[rustsbi] misa: RV64ACDFIMSU
|
||
[rustsbi] mideleg: 0x22
|
||
[rustsbi] medeleg: 0x1ab
|
||
[rustsbi] Kernel entry: 0x80020000
|
||
[kernel] Hello, world!
|
||
power_3 [10000/200000]
|
||
power_3 [20000/200000]
|
||
power_3 [30000/200000power_5 [10000/140000]
|
||
power_5 [20000/140000]
|
||
power_5 [30000/140000power_7 [10000/160000]
|
||
power_7 [20000/160000]
|
||
power_7 [30000/160000]
|
||
]
|
||
power_3 [40000/200000]
|
||
power_3 [50000/200000]
|
||
power_3 [60000/200000]
|
||
power_5 [40000/140000]
|
||
power_5 [50000/140000]
|
||
power_5 [60000/140000power_7 [40000/160000]
|
||
power_7 [50000/160000]
|
||
power_7 [60000/160000]
|
||
]
|
||
power_3 [70000/200000]
|
||
power_3 [80000/200000]
|
||
power_3 [90000/200000]
|
||
power_5 [70000/140000]
|
||
power_5 [80000/140000]
|
||
power_5 [90000/140000power_7 [70000/160000]
|
||
power_7 [80000/160000]
|
||
power_7 [90000/160000]
|
||
]
|
||
power_3 [100000/200000]
|
||
power_3 [110000/200000]
|
||
power_3 [120000/]
|
||
power_5 [100000/140000]
|
||
power_5 [110000/140000]
|
||
power_5 [120000/power_7 [100000/160000]
|
||
power_7 [110000/160000]
|
||
power_7 [120000/160000200000]
|
||
power_3 [130000/200000]
|
||
power_3 [140000/200000]
|
||
power_3 [150000140000]
|
||
power_5 [130000/140000]
|
||
power_5 [140000/140000]
|
||
5^140000 = 386471875]
|
||
power_7 [130000/160000]
|
||
power_7 [140000/160000]
|
||
power_7 [150000/160000/200000]
|
||
power_3 [160000/200000]
|
||
power_3 [170000/200000]
|
||
power_3 [
|
||
Test power_5 OK!
|
||
[kernel] Application exited with code 0
|
||
]
|
||
power_7 [160000/160000]
|
||
7180000/200000]
|
||
power_3 [190000/200000]
|
||
power_3 [200000/200000]
|
||
3^200000 = 871008973^160000 = 667897727
|
||
Test power_7 OK!
|
||
[kernel] Application exited with code 0
|
||
|
||
Test power_3 OK!
|
||
[kernel] Application exited with code 0
|
||
Test sleep OK!
|
||
[kernel] Application exited with code 0
|
||
[kernel] Panicked at src/task/mod.rs:98 All applications completed!
|
||
[rustsbi] reset triggered! todo: shutdown all harts on k210; program halt. Type: 0, reason: 0
|
||
|
||
输出结果看上去有一些混乱,原因是用户程序的每个 ``println!`` 往往会被拆分成多个 ``sys_write`` 系统调用提交给内核。有兴趣的同学可以参考 ``println!`` 宏的实现。
|
||
|
||
另外需要说明的是一点是:与上一章不同,应用的编号不再决定其被加载运行的先后顺序,而仅仅能够改变应用被加载到内存中的位置。
|
||
|
||
本章代码树
|
||
---------------------------------------------
|
||
|
||
.. code-block::
|
||
:linenos:
|
||
:emphasize-lines: 14
|
||
|
||
./os/src
|
||
Rust 16 Files 481 Lines
|
||
Assembly 3 Files 84 Lines
|
||
|
||
├── bootloader
|
||
│ ├── rustsbi-k210.bin
|
||
│ └── rustsbi-qemu.bin
|
||
├── LICENSE
|
||
├── os
|
||
│ ├── build.rs
|
||
│ ├── Cargo.toml
|
||
│ ├── Makefile
|
||
│ └── src
|
||
│ ├── batch.rs(移除:功能分别拆分到 loader 和 task 两个子模块)
|
||
│ ├── config.rs(新增:保存内核的一些配置)
|
||
│ ├── console.rs
|
||
│ ├── entry.asm
|
||
│ ├── lang_items.rs
|
||
│ ├── link_app.S
|
||
│ ├── linker-k210.ld
|
||
│ ├── linker-qemu.ld
|
||
│ ├── loader.rs(新增:将应用加载到内存并进行管理)
|
||
│ ├── main.rs(修改:主函数进行了修改)
|
||
│ ├── sbi.rs(修改:引入新的 sbi call set_timer)
|
||
│ ├── syscall(修改:新增若干 syscall)
|
||
│ │ ├── fs.rs
|
||
│ │ ├── mod.rs
|
||
│ │ └── process.rs
|
||
│ ├── task(新增:task 子模块,主要负责任务管理)
|
||
│ │ ├── context.rs(引入 Task 上下文 TaskContext)
|
||
│ │ ├── mod.rs(全局任务管理器和提供给其他模块的接口)
|
||
│ │ ├── switch.rs(将任务切换的汇编代码解释为 Rust 接口 __switch)
|
||
│ │ ├── switch.S(任务切换的汇编代码)
|
||
│ │ └── task.rs(任务控制块 TaskControlBlock 和任务状态 TaskStatus 的定义)
|
||
│ ├── timer.rs(新增:计时器相关)
|
||
│ └── trap
|
||
│ ├── context.rs
|
||
│ ├── mod.rs(修改:时钟中断相应处理)
|
||
│ └── trap.S
|
||
├── README.md
|
||
├── rust-toolchain
|
||
├── tools
|
||
│ ├── kflash.py
|
||
│ ├── LICENSE
|
||
│ ├── package.json
|
||
│ ├── README.rst
|
||
│ └── setup.py
|
||
└── user
|
||
├── build.py(新增:使用 build.py 构建应用使得它们占用的物理地址区间不相交)
|
||
├── Cargo.toml
|
||
├── Makefile(修改:使用 build.py 构建应用)
|
||
└── src
|
||
├── bin(修改:换成第三章测例)
|
||
│ ├── 00power_3.rs
|
||
│ ├── 01power_5.rs
|
||
│ ├── 02power_7.rs
|
||
│ └── 03sleep.rs
|
||
├── console.rs
|
||
├── lang_items.rs
|
||
├── lib.rs
|
||
├── linker.ld
|
||
└── syscall.rs
|
||
|
||
|
||
本章代码导读
|
||
-----------------------------------------------------
|
||
|
||
随着章节的推进,我们的OS从测例嵌入main.c到批处理的OS系统,再到本章的多进程OS,可以说我们的OS已经初具雏形了。在进入本章的内容之前,大家需要对进程有一个清晰的认识。进程就是“执行中的程序”,因此每一个进程需要有程序运行所需要的资源。每一个进程又有着自己的优先级,使得进程在调度的时候存在某种机制使得高优先级的进程能够优先执行,而低优先级的进程又不能永远不执行。我们本章就要设计一个支持优先级进程调度的OS。
|
||
|
||
同时,进程的引入也意味着同时存在多个app在运行,这意味着我们需要一次将多个测例的bin文件移动到指定的内存位置之中,并且为每一个bin都做好配套的初始化。 |