uCore-Tutorial-Guide-2023S/source/chapter2/0intro.rst

115 lines
8.6 KiB
ReStructuredText
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

引言
================================
本章导读
---------------------------------
..
chyyuu有一个ascii图画出我们做的OS。
本章展现了操作系统一系列功能:
- 通过批处理支持多个程序的自动加载和运行
- 操作系统利用硬件特权级机制,实现对操作系统自身的保护
上一章,我们在 RV64 裸机平台上成功运行起来了 ``Hello, world!`` 并成功实现了染色的过程 。看起来这个过程非常顺利,只需要一条命令就能全部完成。但实际上,在那个计算机刚刚诞生的年代,很多事情并不像我们想象的那么简单。当时,程序被记录在打孔的卡片上,使用汇编语言甚至机器语言来编写。而稀缺且昂贵的计算机由专业的管理员负责操作,就和我们在上一章所做的事情一样,他们手动将卡片输入计算机,等待程序运行结束或者终止程序的运行。最后,他们从计算机的输出端——也就是打印机中取出程序的输出并交给正在休息室等待的程序提交者。
实际上,这样做是一种对于珍贵的计算资源的浪费。因为当时的计算机和今天的个人计算机不同,它的体积极其庞大,能够占满一整个空调房间,像巨大的史前生物。管理员在房间的各个地方跑来跑去、或是等待打印机的输出的这些时间段,计算机都并没有在工作。于是,人们希望计算机能够不间断的工作且专注于计算任务本身。
.. _term-batch-system:
**批处理系统** (Batch System) 应运而生。它的核心思想是:将多个程序打包到一起输入计算机。而当一个程序运行结束后,计算机会 *自动* 加载下一个程序到内存并开始执行。这便是最早的真正意义上的操作系统。
.. _term-privilege:
程序总是难免出现错误。但人们希望一个程序的错误不要影响到操作系统本身,它只需要终止出错的程序,转而运行执行序列中的下一个程序即可。如果后面的程序都无法运行就太糟糕了。这种 *保护* 操作系统不受有意或无意出错的程序破坏的机制被称为 **特权级** (Privilege) 机制,它实现了用户态和内核态的隔离,需要软件和硬件的共同努力。
本章我们的主要目的也是设计一个批处理的操作系统。毕竟将待执行的程序嵌入main.c之中是十分粗暴的也不符合我们对操作系统的认知。这同时也意味着我们将开始使用独立的测例文件并把它们打包到os之中。
.. image:: deng-fish.png
:align: center
:name: fish-os
实践体验
---------------------------
本章我们引入了用户程序,我们可以通过 ``make user`` 生成用户程序,最终将 ``.bin`` 文件放在 ``user/target/bin`` 目录下。
.. code-block:: console
$ git checkout ch2
.. code-block:: console
$ make user BASE=1 CHAPTER=2
$ make run
也可以直接运行打包好的测试程序。make test 会完成 make user 和 make run 两个步骤(自动设置 CHAPTER我们可以通过 BASE 控制是否生成留做练习的测例。
.. code-block:: console
$ make test BASE=1
如果你发现自己的 user 目录是空的,这是由于在 clone 的时候没有增加 ``--recursive`` 参数导致 submodule 没有初始化。解决方案如下:
.. code-block:: console
$ git submodule init
$ git submodule update
如果顺利的话,我们可以看到批处理系统自动加载并运行所有的程序并且正确在程序出错的情况下保护了自身:
.. code-block:: bash
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Platform: QEMU (Version 0.1.0)
[rustsbi] misa: RV64ACDFIMSU
[rustsbi] mideleg: 0x222
[rustsbi] medeleg: 0xb1ab
[rustsbi-dtb] Hart count: cluster0 with 1 cores
[rustsbi] Kernel entry: 0x80200000
hello wrold!
Hello world from user mode program!
Test hello_world OK!
3^10000=5079
3^20000=8202
3^30000=8824
3^40000=5750
3^50000=3824
3^60000=8516
3^70000=2510
3^80000=9379
3^90000=2621
3^100000=2749
Test power OK!
string from data section
strinstring from stack section
strin
Test write1 OK!
ALL DONE
可以看到 4 个基础测试程序都可以正常运行。
本章代码导读
-----------------------------------------------------
相比于上一章的操作系统,本章操作系统有两个最大的不同之处,一个是支持应用程序在用户态运行,且能完成应用程序发出的系统调用;另一个是能够一个接一个地自动运行不同的应用程序。所以,我们需要对操作系统和应用程序进行修改,也需要对应用程序的编译生成过程进行修改。
首先改进应用程序,让它能够在用户态执行,并能发出系统调用。这其实就是上一章中 :ref:`构建用户态执行环境 <term-print-userminienv>` 小节介绍内容的进一步改进。具体而言,编写多个应用小程序,修改编译应用所需的 ``linker.ld`` 文件来 :ref:`调整程序的内存布局 <term-app-mem-layout>` ,让操作系统能够把应用加载到指定内存地址后顺利启动并运行应用程序。
应用程序运行中,操作系统要支持应用程序的输出功能,并还能支持应用程序退出。这需要完成 ``sys_write````sys_exit`` 系统调用访问请求的实现。 具体实现涉及到内联汇编的编写以及应用与操作系统内核之间系统调用的参数传递的约定。为了让应用在还没实现操作系统之前就能进行运行测试我们采用了Linux on RISC-V64 的系统调用参数约定。具体实现可参看 :ref:`系统调用 <term-call-syscall>` 小节中的内容。 这样写完应用小例子后,就可以通过 ``qemu-riscv64`` 模拟器进行测试了。
写完应用程序后,还需实现支持多个应用程序轮流启动运行的操作系统。这里首先能把本来相对松散的应用程序执行代码和操作系统执行代码连接在一起,便于 ``qemu-system-riscv64`` 模拟器一次性地加载二者到内存中并让操作系统能够找到应用程序的位置。为把二者连在一起需要对生成的应用程序进行改造首先是把应用程序执行文件从ELF执行文件格式变成Binary格式通过 ``rust-objcopy`` 可以轻松完成然后这些Binary格式的文件通过编译器辅助脚本 ``scripts/pack.py`` 生成 ``os/link_app.S`` 这个汇编文件并生成各个Binary应用的辅助信息便于操作系统能够找到应用的位置。同时makefile也会调用另外一个脚本``scripts/kernellld.py``来生一个新的规定程序空间的kernel_app.ld取代之前的kernel.ld。编译器会把把操作系统的源码和 ``os/link_app.S`` 合在一起,编译出操作系统+Binary应用的ELF执行文件并进一步转变成Binary格式。
操作系统本身需要完成对Binary应用的位置查找找到后通过 ``os/link_app.S`` 中的变量和标号信息完成会把Binary应用拷贝到 ``os/kernel_app.ld`` 指定的物理内存位置OS的加载应用功能
更加详细的内容,主要在 :ref:`实现批处理操作系统 <term-batchos>` 小节中讲解。
为了让Binary应用能够启动和运行操作系统还需给Binary应用分配好执行环境所需一系列的资源。这主要包括设置好用户栈和内核栈在应用在用户态和内核在内核态需要有各自的栈实现Trap 上下文的保存与恢复让应用能够在发出系统调用到内核态后还能回到用户态继续执行完成Trap 分发与处理等工作。由于涉及用户态与内核态之间的特权级切换细节的汇编代码,与硬件细节联系紧密,所以 :ref:`这部分内容 <term-trap-handle>` 是本章中理解比较困难的地方。如果要了解清楚需要对涉及到的CSR寄存器的功能有清楚的认识。这就需要看看 `RISC-V手册 <http://crva.ict.ac.cn/documents/RISC-V-Reader-Chinese-v2p1.pdf>`_ 的第十章或更加详细的RISC-V的特权级规范文档了。有了上面的实现后就剩下最后一步实现 **执行应用程序** 的操作系统功能,其主要实现在 ``run_next_app`` 函数中 。