├── .gitignore ├── README.md ├── SUMMARY.md ├── _config.yml ├── appendix ├── inline_asm.md ├── install_rust.md └── introduction.md ├── backup.sh ├── book.json ├── build.sh ├── build_marp.sh ├── chapter0 └── introduction.md ├── chapter1 ├── introduction.md ├── part1.md ├── part2.md ├── part3.md ├── part4.md └── part5.md ├── chapter10 └── introduction.md ├── chapter13 ├── figures │ └── hhh.jpeg ├── introduction.md ├── part1.md ├── part2.md ├── part3.md └── part4.md ├── chapter2 ├── figures │ ├── privilege_levels.png │ └── program_memory_layout.png ├── introduction.md ├── part1.md ├── part2.md ├── part3.md ├── part4.md ├── part5.md ├── part6.md ├── part7.md └── part8.md ├── chapter3 ├── introduction.md ├── part1.md ├── part2.md ├── part3.md ├── part4.md ├── part5.md └── part6.md ├── chapter4 ├── introduction.md ├── part1.md ├── part2.md └── part3.md ├── chapter5 ├── figures │ ├── memory_handler.jpg │ ├── memory_set.jpg │ ├── rcore_memlayout.png │ ├── sv39_addr.png │ ├── sv39_pagetable.jpg │ ├── sv39_pte.jpg │ ├── sv39_rwx.jpg │ └── sv39_satp.jpg ├── introduction.md ├── part1.md ├── part2.md ├── part3.md ├── part4.md ├── part5.md ├── part6.md └── part7.md ├── chapter6 ├── introduction.md ├── part1.md ├── part2.md ├── part3.md ├── part4.md └── part5.md ├── chapter7 ├── introduction.md ├── part1.md ├── part2.md ├── part3.md ├── part4.md └── part5.md ├── chapter8 ├── introduction.md ├── part1.md ├── part1_1.md ├── part2.md ├── part3.md ├── part4.md └── part5.md ├── chapter9 ├── figures │ ├── rcore_fs_analysis.pdf │ └── xv6_fs_analysis.pdf ├── introduction.md ├── part1.md ├── part2.md ├── part3.md ├── part4.md └── part5.md ├── commit_ids.txt ├── docs ├── .gitignore ├── _config.yml ├── appendix │ ├── inline_asm.html │ ├── install_rust.html │ └── introduction.html ├── backup.sh ├── build.sh ├── build_marp.sh ├── chapter0 │ └── introduction.html ├── chapter1 │ ├── introduction.html │ ├── part1.html │ ├── part2.html │ ├── part3.html │ ├── part4.html │ └── part5.html ├── chapter10 │ └── introduction.html ├── chapter13 │ ├── figures │ │ └── hhh.jpeg │ ├── introduction.md │ ├── part1.md │ ├── part2.md │ ├── part3.md │ └── part4.md ├── chapter2 │ ├── figures │ │ ├── privilege_levels.png │ │ └── program_memory_layout.png │ ├── introduction.html │ ├── part1.html │ ├── part2.html │ ├── part3.html │ ├── part4.html │ ├── part5.html │ ├── part6.html │ ├── part7.html │ └── part8.html ├── chapter3 │ ├── introduction.html │ ├── part1.html │ ├── part2.html │ ├── part3.html │ ├── part4.html │ ├── part5.html │ └── part6.html ├── chapter4 │ ├── introduction.html │ ├── part1.html │ ├── part2.html │ └── part3.html ├── chapter5 │ ├── figures │ │ ├── memory_handler.jpg │ │ ├── memory_set.jpg │ │ ├── rcore_memlayout.png │ │ ├── sv39_addr.png │ │ ├── sv39_pagetable.jpg │ │ ├── sv39_pte.jpg │ │ ├── sv39_rwx.jpg │ │ └── sv39_satp.jpg │ ├── introduction.html │ ├── part1.html │ ├── part2.html │ ├── part3.html │ ├── part4.html │ ├── part5.html │ ├── part6.html │ └── part7.html ├── chapter6 │ ├── introduction.html │ ├── part1.html │ ├── part2.html │ ├── part3.html │ ├── part4.html │ └── part5.html ├── chapter7 │ ├── introduction.html │ ├── part1.html │ ├── part2.html │ ├── part3.html │ ├── part4.html │ └── part5.html ├── chapter8 │ ├── introduction.html │ ├── part1.html │ ├── part1_1.html │ ├── part2.html │ ├── part3.html │ ├── part4.html │ └── part5.html ├── chapter9 │ ├── figures │ │ ├── rcore_fs_analysis.pdf │ │ └── xv6_fs_analysis.pdf │ ├── introduction.html │ ├── part1.html │ ├── part2.html │ ├── part3.html │ ├── part4.html │ └── part5.html ├── commit_ids.txt ├── exercise │ ├── code │ │ ├── mutex.rs │ │ └── timer.rs │ ├── introduction.html │ ├── part1.html │ ├── part2.html │ ├── part3.html │ ├── part4.html │ ├── part5.html │ ├── part6.html │ ├── part7.html │ └── part8.html ├── extensions │ ├── comment │ │ └── gitalk.html │ ├── fill_commit_id.py │ └── highlight │ │ ├── add_code_style.py │ │ ├── add_riscv_component.py │ │ └── prism-riscv.js ├── gitbook │ ├── fonts │ │ └── fontawesome │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ ├── gitbook-plugin-alerts │ │ ├── plugin.js │ │ └── style.css │ ├── gitbook-plugin-chapter-fold │ │ ├── chapter-fold.css │ │ └── chapter-fold.js │ ├── gitbook-plugin-emphasize │ │ └── plugin.css │ ├── gitbook-plugin-fontsettings │ │ ├── fontsettings.js │ │ └── website.css │ ├── gitbook-plugin-hide-element │ │ └── plugin.js │ ├── gitbook-plugin-katex │ │ ├── fonts │ │ │ ├── KaTeX_AMS-Regular.eot │ │ │ ├── KaTeX_AMS-Regular.ttf │ │ │ ├── KaTeX_AMS-Regular.woff │ │ │ ├── KaTeX_AMS-Regular.woff2 │ │ │ ├── KaTeX_Caligraphic-Bold.eot │ │ │ ├── KaTeX_Caligraphic-Bold.ttf │ │ │ ├── KaTeX_Caligraphic-Bold.woff │ │ │ ├── KaTeX_Caligraphic-Bold.woff2 │ │ │ ├── KaTeX_Caligraphic-Regular.eot │ │ │ ├── KaTeX_Caligraphic-Regular.ttf │ │ │ ├── KaTeX_Caligraphic-Regular.woff │ │ │ ├── KaTeX_Caligraphic-Regular.woff2 │ │ │ ├── KaTeX_Fraktur-Bold.eot │ │ │ ├── KaTeX_Fraktur-Bold.ttf │ │ │ ├── KaTeX_Fraktur-Bold.woff │ │ │ ├── KaTeX_Fraktur-Bold.woff2 │ │ │ ├── KaTeX_Fraktur-Regular.eot │ │ │ ├── KaTeX_Fraktur-Regular.ttf │ │ │ ├── KaTeX_Fraktur-Regular.woff │ │ │ ├── KaTeX_Fraktur-Regular.woff2 │ │ │ ├── KaTeX_Main-Bold.eot │ │ │ ├── KaTeX_Main-Bold.ttf │ │ │ ├── KaTeX_Main-Bold.woff │ │ │ ├── KaTeX_Main-Bold.woff2 │ │ │ ├── KaTeX_Main-Italic.eot │ │ │ ├── KaTeX_Main-Italic.ttf │ │ │ ├── KaTeX_Main-Italic.woff │ │ │ ├── KaTeX_Main-Italic.woff2 │ │ │ ├── KaTeX_Main-Regular.eot │ │ │ ├── KaTeX_Main-Regular.ttf │ │ │ ├── KaTeX_Main-Regular.woff │ │ │ ├── KaTeX_Main-Regular.woff2 │ │ │ ├── KaTeX_Math-BoldItalic.eot │ │ │ ├── KaTeX_Math-BoldItalic.ttf │ │ │ ├── KaTeX_Math-BoldItalic.woff │ │ │ ├── KaTeX_Math-BoldItalic.woff2 │ │ │ ├── KaTeX_Math-Italic.eot │ │ │ ├── KaTeX_Math-Italic.ttf │ │ │ ├── KaTeX_Math-Italic.woff │ │ │ ├── KaTeX_Math-Italic.woff2 │ │ │ ├── KaTeX_Math-Regular.eot │ │ │ ├── KaTeX_Math-Regular.ttf │ │ │ ├── KaTeX_Math-Regular.woff │ │ │ ├── KaTeX_Math-Regular.woff2 │ │ │ ├── KaTeX_SansSerif-Bold.eot │ │ │ ├── KaTeX_SansSerif-Bold.ttf │ │ │ ├── KaTeX_SansSerif-Bold.woff │ │ │ ├── KaTeX_SansSerif-Bold.woff2 │ │ │ ├── KaTeX_SansSerif-Italic.eot │ │ │ ├── KaTeX_SansSerif-Italic.ttf │ │ │ ├── KaTeX_SansSerif-Italic.woff │ │ │ ├── KaTeX_SansSerif-Italic.woff2 │ │ │ ├── KaTeX_SansSerif-Regular.eot │ │ │ ├── KaTeX_SansSerif-Regular.ttf │ │ │ ├── KaTeX_SansSerif-Regular.woff │ │ │ ├── KaTeX_SansSerif-Regular.woff2 │ │ │ ├── KaTeX_Script-Regular.eot │ │ │ ├── KaTeX_Script-Regular.ttf │ │ │ ├── KaTeX_Script-Regular.woff │ │ │ ├── KaTeX_Script-Regular.woff2 │ │ │ ├── KaTeX_Size1-Regular.eot │ │ │ ├── KaTeX_Size1-Regular.ttf │ │ │ ├── KaTeX_Size1-Regular.woff │ │ │ ├── KaTeX_Size1-Regular.woff2 │ │ │ ├── KaTeX_Size2-Regular.eot │ │ │ ├── KaTeX_Size2-Regular.ttf │ │ │ ├── KaTeX_Size2-Regular.woff │ │ │ ├── KaTeX_Size2-Regular.woff2 │ │ │ ├── KaTeX_Size3-Regular.eot │ │ │ ├── KaTeX_Size3-Regular.ttf │ │ │ ├── KaTeX_Size3-Regular.woff │ │ │ ├── KaTeX_Size3-Regular.woff2 │ │ │ ├── KaTeX_Size4-Regular.eot │ │ │ ├── KaTeX_Size4-Regular.ttf │ │ │ ├── KaTeX_Size4-Regular.woff │ │ │ ├── KaTeX_Size4-Regular.woff2 │ │ │ ├── KaTeX_Typewriter-Regular.eot │ │ │ ├── KaTeX_Typewriter-Regular.ttf │ │ │ ├── KaTeX_Typewriter-Regular.woff │ │ │ └── KaTeX_Typewriter-Regular.woff2 │ │ └── katex.min.css │ ├── gitbook-plugin-lunr │ │ ├── lunr.min.js │ │ └── search-lunr.js │ ├── gitbook-plugin-mermaid-gb3 │ │ ├── book │ │ │ └── plugin.js │ │ └── mermaid │ │ │ ├── mermaid.css │ │ │ ├── mermaid.forest.css │ │ │ └── mermaid.min.js │ ├── gitbook-plugin-prism │ │ ├── prism-coy.css │ │ ├── prism-dark.css │ │ ├── prism-funky.css │ │ ├── prism-okaidia.css │ │ ├── prism-solarizedlight.css │ │ ├── prism-tomorrow.css │ │ ├── prism-twilight.css │ │ └── prism.css │ ├── gitbook-plugin-search │ │ ├── lunr.min.js │ │ ├── search-engine.js │ │ ├── search.css │ │ └── search.js │ ├── gitbook-plugin-sharing │ │ └── buttons.js │ ├── gitbook.js │ ├── images │ │ ├── apple-touch-icon-precomposed-152.png │ │ └── favicon.ico │ ├── style.css │ └── theme.js ├── index.html ├── os2atc2019 │ ├── .gitignore │ ├── figures │ │ ├── github.png │ │ ├── init_stack.png │ │ ├── memory_handler.png │ │ ├── memory_set.png │ │ ├── sie.png │ │ ├── software-stacks.png │ │ ├── sv39_addr.png │ │ ├── sv39_pte.png │ │ ├── sv39_rwx.png │ │ ├── switch.png │ │ └── thread-arch.png │ └── os2atc.md ├── plugin_examples.txt └── search_index.json ├── exercise ├── code │ ├── mutex.rs │ └── timer.rs ├── introduction.md ├── part1.md ├── part2.md ├── part3.md ├── part4.md ├── part5.md ├── part6.md ├── part7.md └── part8.md ├── extensions ├── comment │ └── gitalk.html ├── fill_commit_id.py └── highlight │ ├── add_code_style.py │ ├── add_riscv_component.py │ └── prism-riscv.js ├── os2atc2019 ├── .gitignore ├── figures │ ├── github.png │ ├── init_stack.png │ ├── memory_handler.png │ ├── memory_set.png │ ├── sie.png │ ├── software-stacks.png │ ├── sv39_addr.png │ ├── sv39_pte.png │ ├── sv39_rwx.png │ ├── switch.png │ └── thread-arch.png └── os2atc.md └── plugin_examples.txt /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | _book/ 3 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## **_最新通知_** 2 | 3 | 由于本文档还不稳定,有时会有更新,更新信息会第一时间放在这里。由于文档不稳定引起的问题不会导致扣分。 4 | 5 | > **[info] 最后更新日期:2020-09-09** 6 | > 7 | > **2020-09-09** 8 | > 9 | > 本文档基本上已经停止维护,我们会将开发的重心放在[第三版 tutorial](https://rcore-os.github.io/rCore-Tutorial-deploy/) 上面,欢迎大家来围观! 10 | > 11 | > **2020-04-25** 12 | > 13 | > 将第九章中所引用的 rcore-fs 系列 crate 的版本更新为 7f5eeac 。 14 | > 15 | > **2020-04-08** 16 | > 17 | > 感谢 @LyricZhao 同学的帮助,更新了 lab5 的一些文字描述。 18 | > 19 | > **2020-03-26** 20 | > 21 | > github.io 疑似被和谐,目前在 rcore-tutorial-doc.netlify.com 维护自动更新的镜像。之后有时间的话更新配置文件。 22 | > 23 | > **2020-03-15** 24 | > 25 | > 对于 lab8 , 补充说明了 ``sys_read`` 的语义:即在管道为空的时候应阻塞调用它的进程。 26 | > 27 | > **2020-03-09** 28 | > 29 | > lab7 的题目终于出完了,大家可以去做了。。。QAQ 30 | > 31 | > **2020-03-08** 32 | > 33 | > 如果有 `dd` 命令报错的同学请注意:在第九章第四小节中,MacOS 平台的同学在通过 `dd` 命令将 `temp` 文件打包到用户镜像中的时候,应该使用 `bs=1k` 而非 `bs=1K` 。该问题已在 master 分支和文档中修改。 34 | > 35 | > 对于在做 lab2/3 而 tutorial 进度已经达到第八章第二小节的同学,在 [练习题的主页](./exercise/introduction.md) 提供了一种修改原版代码的方式,以通过评测脚本测试 lab2/3 。 36 | > 37 | > **2020-03-07** 38 | > 39 | > 将 rCore_tutorial 的 master 分支更新到第九章第四小节。 40 | > 41 | > 对于 lab2/3/5/6/8 以及即将完成的 lab7 新增了统一的评测脚本以及对应的使用说明,可以不必从 master 分支开始实验而是按照 tutorial 从零开始,只要评测脚本能得出正确的结果即会被认可。lab2/3/5/6/8 的评测方式以及测试程序的说明也在其自身的页面中更新了。 42 | > 43 | > 对于 lab2/3,不再统一使用一个 `init.rs` 而是拆分为两个内核态测试程序,移除了其中多余的初始化代码,只依赖中断和内存管理。 44 | > 45 | > **2020-03-06** 46 | > 47 | > 修复了第九章第四小节一些明显的错误,完善了文件读写测试程序 `write.rs`。 48 | > 49 | > 感谢 @Liurunda 同学提供的大量勘误。 50 | > 51 | > **2020-03-05** 52 | > 53 | > 为了显得很轻量级,在首页更新了目前的总代码行数。 54 | > 55 | > 正文方面:新增了第九章第四小节作为练习八的基础,同时在第九章 intro 部分上传了 rCore 和 xv6 文件系统的分析文档。 56 | > 57 | > 练习方面: 58 | > 59 | > 在练习五的描述中,将 `sys_fork` 的 syscall id 设定为 $$220$$; 60 | > 61 | > 在练习六中增加了测试文件需要用到的 syscall id; 62 | > 63 | > 重写了练习八,新增了练习八的测试程序。 64 | > 65 | > **2020-03-04** 66 | > 67 | > 调换了练习五和练习六的顺序。 68 | > 69 | > 修改了练习八的要求,强制要求实现 `sys_fork` 。 70 | > 71 | > 在 `exercise/introduction` 中增加了测评的详细说明。 72 | > 73 | > 删除了从零开始复现的要求,只对练习进行检察。 74 | > 75 | > **2020-03-03** 76 | > 77 | > 更新了第五章第一节的小节部分以及第二节的前半部分,指出了必要的链接脚本的改动,进行了更加流畅的衔接; 78 | > 79 | > 更新了第九章第一、二小节,使得记事本和用户终端两个应用程序支持退格键。 80 | > 81 | > **2020-03-02** 82 | > 83 | > 更新了实验报告的命名要求与放置位置。 84 | > 85 | > **2020-02-27** 86 | > 87 | > 修改了 `2. 物理内存管理` 章节中的 `实验要求 2(问答题)`。 88 | > 89 | > 优化了 `1. 中断异常` 章节中的 `实验要求 3(问答题)` 的描述。 90 | > 91 | > **2020-02-26** 92 | > 93 | > 简化了 `5. CPU 调度` 章节中的测试用例,删掉了对 `sys_wait` 等系统调用的要求,并且降低了对输出数据的的要求。 94 | > 95 | > **2020-02-20** 96 | > 97 | > 如果在编译 _buddy_system_allocator_ 时报错,可能是本地 crate 版本未更新,只需进入 _os/,usr/_ 文件夹下分别 `cargo update -p buddy_system_allocator` ,并重新编译即可。 98 | > 99 | > 如果内核在输出 `setup process!` 后 panic 报 page fault ,则很有可能将 `timer::init` 函数中的 `TICKS = 0;` 注释掉即可正常运行。 100 | 101 | # rCore Tutorial 102 | 103 | 这是一个展示如何从零开始用 Rust 语言写一个基于 64 位 RISC-V 架构的操作系统的教程。完成这个教程后,你将可以在内核上运行用户态终端,并在终端内输入命令运行其他程序。 104 | 105 | 我们很轻量级!截至目前的代码行数分布(ch9-pa4 分支)为: 106 | 107 | ``` 108 | ------------------------------------------------------------------------------- 109 | Language files blank comment code 110 | ------------------------------------------------------------------------------- 111 | Rust 42 315 21 2178 112 | D 120 256 0 633 113 | Assembly 4 16 2 185 114 | JSON 116 0 0 116 115 | Markdown 1 4 0 55 116 | make 2 17 0 54 117 | TOML 2 5 2 22 118 | Bourne Shell 1 0 0 1 119 | ------------------------------------------------------------------------------- 120 | SUM: 288 613 25 3244 121 | ------------------------------------------------------------------------------- 122 | ``` 123 | 124 | ## 代码仓库 125 | 126 | 左侧章节目录中含有一对方括号"[ ]"的小节表示这是一个存档点,即这一节要对最近几节的代码进行测试。所以我们对每个存档点都设置了一个 commit 保存其完整的状态以供出现问题时参考。 127 | 128 | 与章节相对应的代码可以很容易的找到。章节标题下提供了指向下一个存档点代码状态的链接。 129 | 130 | ## 阅读在线文档并进行实验 131 | 132 | - [实验 ppt:rcore step-by-step tutorial](https://rcore-os.github.io/rCore_tutorial_doc/os2atc2019/os2atc.html) 133 | - [实验文档:rcore step-by-step tutorial](https://rcore-os.github.io/rCore_tutorial_doc/) 134 | - [实验代码:rcore step-by-step code](https://github.com/rcore-os/rCore_tutorial/) 135 | 136 | ## 评论区 137 | 138 | 对于章节内容有任何疑问及建议,请在对应页面最下面的评论区中发表观点。注意需要用 Github ID 登录后才能评论。 139 | 140 | 好了,那就让我们正式开始! 141 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Introduction](README.md) 4 | - [第零章:实验环境说明](chapter0/introduction.md) 5 | - [第一章:独立式可执行程序](chapter1/introduction.md) 6 | - [[安装 nightly rust]](chapter1/part1.md) 7 | - [使用包管理器 cargo 创建 rust binary 项目](chapter1/part2.md) 8 | - [移除标准库依赖](chapter1/part3.md) 9 | - [[移除 runtime 依赖]](chapter1/part4.md) 10 | - [总结与展望](chapter1/part5.md) 11 | - [第二章:最小化内核](chapter2/introduction.md) 12 | - [使用目标三元组描述目标平台](chapter2/part1.md) 13 | - [编译、生成内核镜像](chapter2/part2.md) 14 | - [使用链接脚本指定内存布局](chapter2/part3.md) 15 | - [[重写程序入口点 \-start]](chapter2/part4.md) 16 | - [[使用 Qemu 运行内核]](chapter2/part5.md) 17 | - [封装 SBI 接口](chapter2/part6.md) 18 | - [[实现格式化输出]](chapter2/part7.md) 19 | - [总结与展望](chapter2/part8.md) 20 | - [第三章:中断](chapter3/introduction.md) 21 | - [rv64 中断介绍](chapter3/part1.md) 22 | - [[手动触发断点中断]](chapter3/part2.md) 23 | - [程序运行上下文环境](chapter3/part3.md) 24 | - [[实现上下文环境保存与恢复]](chapter3/part4.md) 25 | - [[时钟中断]](chapter3/part5.md) 26 | - [总结与展望](chapter3/part6.md) 27 | - [第四章:内存管理](chapter4/introduction.md) 28 | - [[物理内存探测与管理]](chapter4/part1.md) 29 | - [[动态内存分配]](chapter4/part2.md) 30 | - [总结与展望](chapter4/part3.md) 31 | - [第五章:内存虚拟化](chapter5/introduction.md) 32 | - [页表:从虚拟内存到物理内存](chapter5/part1.md) 33 | - [[内核初始映射]](chapter5/part2.md) 34 | - [内核重映射](chapter5/part3.md) 35 | - [内核重映射实现之一:页表](chapter5/part4.md) 36 | - [内核重映射实现之二:MemorySet](chapter5/part5.md) 37 | - [[内核重映射实现之三:完结]](chapter5/part6.md) 38 | - [总结与展望](chapter5/part7.md) 39 | - [第六章:内核线程](chapter6/introduction.md) 40 | - [线程状态与保存](chapter6/part1.md) 41 | - [线程切换](chapter6/part2.md) 42 | - [内核线程初始化](chapter6/part3.md) 43 | - [[内核线程创建与切换测试]](chapter6/part4.md) 44 | - [总结与展望](chapter6/part5.md) 45 | - [第七章:线程调度](chapter7/introduction.md) 46 | - [线程池与线程管理](chapter7/part1.md) 47 | - [内核调度线程 idle](chapter7/part2.md) 48 | - [线程调度之 Round Robin 算法](chapter7/part3.md) 49 | - [[线程调度测试]](chapter7/part4.md) 50 | - [总结与展望](chapter7/part5.md) 51 | - [第八章:进程](chapter8/introduction.md) 52 | - [[编写用户程序]](chapter8/part1.md) 53 | - [合并内核与应用程序](chapter8/part1_1.md) 54 | - [在内核中实现系统调用](chapter8/part2.md) 55 | - [创建虚拟内存空间](chapter8/part3.md) 56 | - [[创建进程]](chapter8/part4.md) 57 | - [总结与展望](chapter8/part5.md) 58 | - [第九章:文件系统](chapter9/introduction.md) 59 | - [[使用文件系统]](chapter9/part1.md) 60 | - [[实现记事本]](chapter9/part2.md) 61 | - [[实现终端]](chapter9/part3.md) 62 | - [[文件读写]](chapter9/part4.md) 63 | - [总结与展望](chapter9/part5.md) 64 | - [第十章:同步互斥](chapter10/introduction.md) 65 | 70 | - [练习题](./exercise/introduction.md) 71 | - [1. 中断异常](./exercise/part1.md) 72 | - [2. 物理内存管理](./exercise/part2.md) 73 | - [3. 虚拟内存管理](./exercise/part3.md) 74 | - [4. 线程管理](./exercise/part4.md) 75 | - [5. 用户进程(+ 虚拟内存管理 + 线程管理)](./exercise/part5.md) 76 | - [6. CPU 调度](./exercise/part6.md) 77 | - [7. 同步互斥](./exercise/part7.md) 78 | - [8. 文件系统](./exercise/part8.md) 79 | - [附录](./appendix/introduction.md) 80 | - [内联汇编](appendix/inline_asm.md) 81 | - [安装 rust](appendix/install_rust.md) 82 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /appendix/inline_asm.md: -------------------------------------------------------------------------------- 1 | # 内联汇编 2 | 3 | Rust 通过 asm!宏来支持使用内联汇编。语法与 GCC & Clang 的内联汇编格式类似: 4 | 5 | ```rust 6 | asm!(assembly template 7 | : output operands 8 | : input operands 9 | : clobbers 10 | : options 11 | ); 12 | ``` 13 | 14 | 任何 asm 的使用是特征封闭的(需要允许 **#![feature(asm)]** ),当然需要一个 **unsafe** 块。 15 | 16 | ## 汇编模板 17 | 18 | 汇编模板(assembly template )是唯一所需的参数,它必须是一个文字字符串(例如,""): 19 | 20 | ```rust 21 | #![feature(asm)] 22 | 23 | fn foo() { 24 | unsafe { 25 | asm!("nop"); 26 | } 27 | } 28 | ``` 29 | 30 | ## 操作数 31 | 32 | 输入操作数(input operands)和输出操作数(output operands)遵循相同的格式:"constraints1"(expr1), "constraints2"(expr2), ..."。输出操作数表达式必须是可变左值,或者没有分配内存: 33 | 34 | ```rust 35 | // Returns the current link register 36 | pub fn lr() -> usize { 37 | let ptr: usize; 38 | unsafe { 39 | asm!("mv $0, ra" : "=r"(ptr)); 40 | } 41 | ptr 42 | } 43 | ``` 44 | 45 | 如果你想在这个位置上使用真正的操作数,然而,你需要把花括号 { } 放在你想要的的寄存器两边,你需要加具体操作数的大小。对于低水平的编程这是非常有用的,在程序中使用哪个寄存器很重要: 46 | 47 | ```rust 48 | fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize { 49 | let ret; 50 | unsafe { 51 | asm!("ecall" 52 | : "={x10}" (ret) 53 | : "{x10}" (arg0), "{x11}" (arg1), "{x12}" (arg2), "{x17}" (which) 54 | : "memory" 55 | : "volatile"); 56 | } 57 | ret 58 | } 59 | ``` 60 | 61 | ## Clobbers 62 | 63 | 一些指令会修改有可能持有不同值的寄存器 X,所以我们使用破坏列表(clobbers list)来指示编译器不能保证之前加载载到寄存器 X 的值将保持有效(因为会被指令修改)。 64 | 65 | ```rust 66 | // Put the content in addr x0100 into x10 67 | asm!("ld x10, (0x100)" : /* no outputs */ : /* no inputs */ : "{x10}"); 68 | ``` 69 | 70 | 输入和输出寄存器不需要被列出来,因为信息已经被给定约束传达。否则,任何其他被隐式或显式地使用的寄存器应该列出。如果内联会修改内存,memory 还应该被指定。 71 | 72 | ## 选择项 73 | 74 | 最后一部分,options 是 Rust 特有的。形式是逗号分隔字符串(例如::"foo", "bar", "baz")。这是用于指定内联汇编的一些额外的信息: 75 | 76 | 当前有效的选项是: 77 | 78 | 1. _volatile_ 这类似于在 gcc/clang 中指定\_ \_asm\_\_ **volatile**(...) 。 79 | 2. _alignstack_ 某些指定堆的对齐某种方式(例如,SSE)的指令并说明这个指示编译器插入其通常堆栈对齐的代码的指令。 80 | -------------------------------------------------------------------------------- /appendix/install_rust.md: -------------------------------------------------------------------------------- 1 | # 安装 rust 2 | 3 | ## 安装 rustup 4 | 5 | `rustup` 是 rust 的工具链管理器,通过它可以下载 rust 工具链(类似 `apt-get install gcc` 的感觉)。如果官方途径下载遇到了困难,可以尝试以下方法: 6 | 7 | ```bash 8 | export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static 9 | export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup 10 | curl https://sh.rustup.rs -sSf | sh 11 | ``` 12 | 13 | 如果还是失败了,手动下载安装脚本:在浏览器里输入 `https://sh.rustup.rs` ,将下载的脚本中 `RUSTUP_UPDATE_ROOT:-https://static.rust-lang.org/rustup` 改为 `RUSTUP_UPDATE_ROOT:-https://mirrors.ustc.edu.cn/rust-static/rustup` (科大源),运行脚本即可。 14 | 15 | ## rustup 换源 16 | 17 | 参考:https://mirrors.tuna.tsinghua.edu.cn/help/rustup/ 18 | 19 | ## crate.io 换源 20 | 21 | 新建文件 `~/.cargo/config` ,在里面输入如下内容: 22 | 23 | ``` 24 | [source.crates-io] 25 | registry = "https://github.com/rust-lang/crates.io-index" 26 | replace-with = 'ustc' 27 | [source.ustc] 28 | registry = "git://mirrors.ustc.edu.cn/crates.io-index" 29 | ``` 30 | -------------------------------------------------------------------------------- /appendix/introduction.md: -------------------------------------------------------------------------------- 1 | # 附录 2 | -------------------------------------------------------------------------------- /backup.sh: -------------------------------------------------------------------------------- 1 | sudo rm -r ../rCore_tutorial_doc_backup 2 | sudo cp -r ../rCore_tutorial_doc ../rCore_tutorial_doc_backup 3 | 4 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "hide-element", 4 | "chapter-fold", 5 | "katex", 6 | "alerts", 7 | "emphasize", 8 | "-highlight", 9 | "prism", 10 | "localized-footer", 11 | "mermaid-gb3" 12 | ], 13 | "pluginsConfig": { 14 | "hide-element": { 15 | "elements": [".gitbook-link"] 16 | }, 17 | "prism": { 18 | "css": ["prismjs/themes/prism-tomorrow.css"] 19 | }, 20 | "localized-footer": { 21 | "filename": "extensions/comment/gitalk.html" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | # gitbook install 2 | cp extensions/highlight/prism-riscv.js node_modules/prismjs/components/ 3 | python3 extensions/highlight/add_riscv_component.py 4 | rm -rf docs/ 5 | gitbook build 6 | mv _book/ docs/ 7 | python3 extensions/highlight/add_code_style.py 8 | -------------------------------------------------------------------------------- /build_marp.sh: -------------------------------------------------------------------------------- 1 | # update os2atc2019 os2atc.md, then 2 | marp -I os2atc2019 -o docs/os2atc2019 3 | cp -r os2atc2019/figures docs/os2atc2019/ 4 | # finally, git push && firefox https://rcore-os.github.io/rCore_tutorial_doc/os2atc2019/os2atc.html -------------------------------------------------------------------------------- /chapter0/introduction.md: -------------------------------------------------------------------------------- 1 | # 第零章:实验环境说明 2 | 3 | ## 本章概要 4 | 5 | 这一章主要包括: 6 | 7 | - 在线实验环境的使用说明 8 | - docker 实验环境的使用说明 9 | - 本地实验环境的使用说明 10 | 11 | 下面的实验环境建立方式由简单到相对复杂一些,同学们可以基于自己的情况选择合适的实验方式。 12 | 13 | ## 在线环境下运行实验 14 | 15 | 目前在线实验环境是[基于实验楼的在线实验环境](https://www.shiyanlou.com/courses/1481)。用户只需有一个能够上网的 browser 即可进行实验。首先需要在[实验楼](https://www.shiyanlou.com/)上注册一个账号,然后在[rcore 在线实验环境](https://www.shiyanlou.com/courses/1481)的网页上输入验证码:wfkblCQp 就可以进入在线的实验环境。尝试执行下面的命令就开始进行实验了。 16 | 17 | ```bash 18 | # 编译 19 | cd rCore_tutorial; git checkout master; make all 20 | # 运行 21 | make run 22 | ``` 23 | 24 | ## docker 环境下运行实验 25 | 26 | 我们支持 docker 环境下进行实现,在 docker hub 上已有可用的 docker 环境,在当前目录下运行 `make docker` 将会从云端拉取 docker 镜像,并将当前目录挂载到 `/mnt` 位置。 27 | 28 | ```bash 29 | # 启动docker环境 30 | make docker # 会进入docker中的终端 31 | cd /mnt 32 | # 然后可以进行编译/qemu中运行实验。例如: 33 | # 编译用户态app组成的image 34 | cd usr 35 | make user_img 36 | # 编译内核 37 | cd ../os 38 | make build 39 | # 运行 40 | make run 41 | ``` 42 | 43 | 如有兴趣,也可以自行构建/调整 docker 镜像,相关的 Dockerfile 文件在当前目录下,我们提供了 `make docker_build` 命令来帮助构建,详情请看 Dockerfile 和 Makefile 44 | 45 | ## 本地 Linux 环境下运行实验 46 | 47 | 我们也支持本地 Linux 环境下开展实验,不过需要提前安装相关软件包,如 rustc nightly,qemu-4.1.0+,device-tree-compiler 等(后续章节会提供安装教程)。具体细节可参考 [支持 docker 建立的 Dockerfile](https://github.com/rcore-os/rCore_tutorial/blob/master/Dockerfile) 和 [支持 github 自动测试的 main.yml](https://github.com/rcore-os/rCore_tutorial/blob/master/.github/workflows/main.yml) 。假定安装好了相关软件,直接只需下面的命令,即可进行实验: 48 | 49 | ```bash 50 | # 在把实验代码下载到本地 51 | git clone https://github.com/rcore-os/rCore_tutorial.git 52 | # 编译 53 | cd rCore_tutorial; git checkout master; make all 54 | # 运行 55 | make run 56 | # 如果一切正常,则qemu模拟的risc-v64计算机将输出 57 | 58 | OpenSBI v0.4 (Jul 2 2019 11:53:53) 59 | ____ _____ ____ _____ 60 | / __ \ / ____| _ \_ _| 61 | | | | |_ __ ___ _ __ | (___ | |_) || | 62 | | | | | '_ \ / _ \ '_ \ \___ \| _ < | | 63 | | |__| | |_) | __/ | | |____) | |_) || |_ 64 | \____/| .__/ \___|_| |_|_____/|____/_____| 65 | | | 66 | |_| 67 | 68 | Platform Name : QEMU Virt Machine 69 | Platform HART Features : RV64ACDFIMSU 70 | Platform Max HARTs : 8 71 | Current Hart : 0 72 | Firmware Base : 0x80000000 73 | Firmware Size : 112 KB 74 | Runtime SBI Version : 0.1 75 | 76 | PMP0: 0x0000000080000000-0x000000008001ffff (A) 77 | PMP1: 0x0000000000000000-0xffffffffffffffff (A,R,W,X) 78 | switch satp from 0x8000000000080255 to 0x800000000008100e 79 | ++++ setup memory! ++++ 80 | ++++ setup interrupt! ++++ 81 | available programs in rust/ are: 82 | . 83 | .. 84 | user_shell 85 | notebook 86 | hello_world 87 | model 88 | ++++ setup fs! ++++ 89 | ++++ setup process! ++++ 90 | ++++ setup timer! ++++ 91 | Rust user shell 92 | >> 93 | ``` 94 | -------------------------------------------------------------------------------- /chapter1/introduction.md: -------------------------------------------------------------------------------- 1 | # 第一章:独立化可执行程序 2 | 3 | ## 本章概要 4 | 5 | 这一章你将会学到: 6 | 7 | - 安装 nightly Rust 8 | - 使用 cargo(包管理器) 创建 Rust 项目 9 | - 移除 Rust 程序对操作系统的依赖,构建一个独立化可执行的程序 10 | -------------------------------------------------------------------------------- /chapter1/part1.md: -------------------------------------------------------------------------------- 1 | ## 安装 nightly Rust 2 | 3 | - [代码][code] 4 | 5 | 我们首先使用如下命令安装 Rust 工具链管理器 rustup 和 Rust 包管理器 cargo: 6 | 7 | ```bash 8 | $ curl https://sh.rustup.rs -sSf | sh 9 | ``` 10 | 11 | > 如果安装 rust 的过程中出现了困难,可以阅读 [附录:安装 rust](../appendix/install_rust.md) 12 | 13 | Rust 包含:stable、beta、nightly 三个版本。默认情况下我们安装的是 stable 稳定版。由于在编写操作系统时需要使用 Rust 的一些不稳定的实验功能,因此我们使用 nightly 每日构建版。 14 | 15 | 但是,由于官方不保证 nightly 版本的 ABI 稳定性,也就意味着今天写的代码用未来的 nightly 可能无法编译通过,因此一般在使用 nightly 时应该锁定一个日期。 16 | 17 | 我们在工作目录下创建一个名为 `rust-toolchain` 的文件(无后缀),并在其中写入所需的工具链版本: 18 | 19 | ``` 20 | nightly-2020-01-27 21 | ``` 22 | 23 | 今后所有在这个目录下使用 Rust 时都会自动切换到这个版本的工具链。 24 | 25 | > 随着日后的更新,后面的日期可能会变化,请以 [GitHub](https://github.com/rcore-os/rCore_tutorial/blob/master/rust-toolchain) 上的版本为准 26 | 27 | 我们可以使用 `rustc --version` 或者 `rustup show` 查看当前 Rust 的版本,确认我们已经切换到了 nightly 版本。 28 | 29 | ```bash 30 | $ rustc --version 31 | rustc 1.42.0-nightly (6d3f4e0aa 2020-01-25) 32 | ``` 33 | 34 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch1-pa4 35 | -------------------------------------------------------------------------------- /chapter1/part2.md: -------------------------------------------------------------------------------- 1 | ## 使用包管理器 cargo 创建 Rust binary 项目 2 | 3 | - [代码][code] 4 | 5 | 使用 `cargo new` 创建一个新的 Rust binary 项目,命令如下: 6 | 7 | ```sh 8 | $ cargo new os --bin 9 | ``` 10 | 11 | | `cargo new` 的参数 | 含义 | 12 | | ------------------ | -------------------------------------- | 13 | | `os` | 项目的名称 | 14 | | `--bin` | 可执行项目,和其相对的是库项目 `--lib` | 15 | 16 | 创建完成后,整个项目的文件结构如下: 17 | 18 | ``` 19 | os 20 | ├── Cargo.toml 项目配置文件 21 | └── src 源代码路径 22 | └── main.rs 源程序 23 | ``` 24 | 25 | 接下来我们进入 `os` 项目文件夹,并尝试构建、运行项目: 26 | 27 | ```sh 28 | $ cargo run 29 | ... 30 | Hello, world! 31 | ``` 32 | 33 | 打开 `os/src/main.rs` 发现里面确实只是输出了一行 `Hello, world!` 。这个应用可以正常运行,但是即使只是这么一个简单的功能,也离不开所在操作系统(Ubuntu)的帮助。我们既然要写一个新的操作系统,就不能依赖于任何已有操作系统!接下来我们尝试移除该应用对于操作系统的依赖。 34 | 35 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch1-pa4 36 | -------------------------------------------------------------------------------- /chapter1/part3.md: -------------------------------------------------------------------------------- 1 | ## 移除标准库依赖 2 | 3 | - [代码][code] 4 | 5 | 项目默认是链接 rust 标准库 std 的,它依赖于操作系统,因此我们需要显式将其禁用: 6 | 7 | ```rust 8 | // src/main.rs 9 | // 之后出现的所有代码块内的路径都放在 os 文件夹下 10 | 11 | #![no_std] 12 | fn main() { 13 | println!("Hello, world!"); 14 | } 15 | ``` 16 | 17 | 我们使用 `cargo build` 构建项目,会出现下面的错误: 18 | 19 | > **[danger] cargo build error** 20 | > 21 | > ```rust 22 | > error: cannot find macro `println` in this scope 23 | > --> src/main.rs:3:5 24 | > | 25 | > 3 | println!("Hello, world!"); 26 | > | ^^^^^^^ 27 | > error: `#[panic_handler]` function required, but not found 28 | > error: language item required, but not found: `eh_personality 29 | > ``` 30 | 31 | 接下来,我们依次解决这些问题。 32 | 33 | ### println! 34 | 35 | 第一个错误是说 `println!` 宏未找到,实际上这个宏属于 Rust 标准库 std,由于它被我们禁用了当然就找不到了。我们暂时将其删除,之后给出不依赖操作系统的实现。 36 | 37 | > **[info] `println!`哪里依赖了操作系统** 38 | > 39 | > 这个宏会输出到 **标准输出** ,而这需要操作系统的支持。 40 | 41 | ### panic_handler 42 | 43 | 第二个错误是说需要一个函数作为 `panic_handler` ,这个函数负责在程序 `panic` 时调用。它默认使用标准库 std 中实现的函数,由于我们禁用了标准库,因此只能自己实现它: 44 | 45 | ```rust 46 | // src/main.rs 47 | 48 | use core::panic::PanicInfo; 49 | // This function is called on panic. 50 | #[panic_handler] 51 | fn panic(_info: &PanicInfo) -> ! { 52 | loop {} 53 | } 54 | ``` 55 | 56 | > **[info] panic** 57 | > 58 | > panic 在 Rust 中表明程序遇到了不可恢复的错误,只能被迫停止运行。 59 | 60 | 程序在 panic 后就应该结束,不过我们暂时先让这个 handler 卡在一个死循环里。因此这个 handler 不会结束,我们用`!`类型的返回值表明这个函数不会返回。 61 | 62 | 这里我们用到了核心库 `core` ,与标准库 `std` 不同,这个库不需要操作系统的支持,下面我们还会与它打交道。 63 | 64 | ### eh_personality 65 | 66 | 第三个错误提到了语义项 (language item) ,它是编译器内部所需的特殊函数或类型。刚才的 `panic_handler` 也是一个语义项,我们要用它告诉编译器当程序 panic 之后如何处理。 67 | 68 | 而这个错误相关语义项 `eh_personality` ,其中 `eh` 是 `exception handling` 的缩写,它是一个标记某函数用来实现 **堆栈展开** 处理功能的语义项。这个语义项也与 `panic` 有关。 69 | 70 | > **[info] 堆栈展开 (stack unwinding) ** 71 | > 72 | > 通常,当程序出现了异常 (这里指类似 Java 中层层抛出的异常),从异常点开始会沿着 caller 调用栈一层一层回溯,直到找到某个函数能够捕获 (catch) 这个异常。这个过程称为 堆栈展开。 73 | > 74 | > 当程序出现不可恢复错误时,我们需要沿着调用栈一层层回溯上去回收每个 caller 中定义的局部变量 **避免造成内存溢出** 。这里的回收包括 C++ 的 RAII 的析构以及 Rust 的 drop。 75 | > 76 | > 而在 Rust 中,panic 证明程序出现了不可恢复错误,我们则会对于每个 caller 函数调用依次这个被标记为堆栈展开处理函数的函数。 77 | > 78 | > 这个处理函数是一个依赖于操作系统的复杂过程,在标准库中实现,我们禁用了标准库使得编译器找不到该过程的实现函数了。 79 | 80 | 简单起见,我们暂时不考虑内存溢出,设置当程序 panic 时不做任何清理工作,直接退出程序即可。这样堆栈展开处理函数不会被调用,编译器也就不会去寻找它的实现了。 81 | 82 | 因此,我们在项目配置文件中直接将 dev (use for `cargo build`) 和 release (use for `cargo build --release`) 的 panic 的处理策略设为 abort。 83 | 84 | ```rust 85 | // Cargo.toml 86 | 87 | [profile.dev] 88 | panic = "abort" 89 | 90 | [profile.release] 91 | panic = "abort" 92 | ``` 93 | 94 | 此时,我们 `cargo build` ,但是又出现了新的错误... 95 | 96 | > **[danger] cargo build error** 97 | > 98 | > `` error: requires `start` lang_item `` 99 | 100 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch1-pa4 101 | -------------------------------------------------------------------------------- /chapter1/part4.md: -------------------------------------------------------------------------------- 1 | ## 移除 runtime 依赖 2 | 3 | - [代码][code] 4 | 5 | 对于大多数语言,他们都使用了 **运行时系统(runtime system)** ,这导致 main 并不是他们执行的第一个函数。 6 | 7 | 以 Rust 语言为例:一个典型的链接了标准库的 Rust 程序会首先跳转到 C runtime library 中的 **crt0(C runtime zero)** 进入 C runtime 设置 C 程序运行所需要的环境(比如:创建堆栈,设置寄存器参数等)。 8 | 9 | 然后 C runtime 会跳转到 Rust runtime 的 **入口点(entry point)** 进入 Rust runtime 继续设置 Rust 运行环境,而这个入口点就是被 `start` 语义项标记的。Rust runtime 结束之后才会调用 main 进入主程序。 10 | 11 | C runtime 和 Rust runtime 都需要标准库支持,我们的程序无法访问。如果覆盖了 `start` 语义项,仍然需要 `crt0`,并不能解决问题。所以需要重写覆盖 `crt0` 入口点: 12 | 13 | ```rust 14 | // src/main.rs 15 | 16 | #![no_std] // don't link the Rust standard library 17 | #![no_main] // disable all Rust-level entry points 18 | 19 | use core::panic::PanicInfo; 20 | // This function is called on panic. 21 | #[panic_handler] 22 | fn panic(_info: &PanicInfo) -> ! { 23 | loop {} 24 | } 25 | 26 | #[no_mangle] // don't mangle the name of this function 27 | pub extern "C" fn _start() -> ! { 28 | // this function is the entry point, since the linker looks for a function named `_start` by default 29 | loop {} 30 | } 31 | ``` 32 | 33 | 我们加上 `#![no_main]` 告诉编译器我们不用常规的入口点。 34 | 35 | 同时我们实现一个 `_start` 函数,并加上 `#[no_mangle]` 告诉编译器对于此函数禁用 name mangling ,确保编译器生成一个名为 `_start` 的函数,而非为了保证函数名字唯一性而生成的形如 `_ZN3blog_os4_start7hb173fedf945531caE` 乱码般的名字。由于 `_start` 是大多数系统的默认入口点名字,所以我们要确保它不会发生变化。 36 | 37 | 接着,我们使用 `extern "C"` 描述 `_start` 函数,这是 Rust 中的 FFI (Foreign Function Interface, 语言交互接口) 语法,表示此函数是一个 C 函数而非 Rust 函数。由于 `_start` 是作为 C runtime 的入口点,看起来合情合理。 38 | 39 | 返回值类型为 `!` 表明这个函数不允许返回。由于这个函数被操作系统或 bootloader 直接调用,这样做是必须的。为了从入口点函数退出,我们需要通过 `exit` 系统调用,但我们目前还没法做到这一步,因此就让它在原地转圈吧。 40 | 41 | 由于程序会一直停在 C runtime crt0 的入口点,我们可以移除没用的 `main` 函数,并加上 `![no_main]` 表示不用不使用普通的入口点那套理论。 42 | 43 | 再次 `cargo build` ,我们即将面对这一章中的最后一个错误! 44 | 45 | > **[danger] cargo build error** 46 | > 47 | > `` linking with `cc` failed: exit code: 1 `` 48 | 49 | 这个错误同样与 C runtime 有关,尽管 C runtime 的入口点已经被我们覆盖掉了,我们的项目仍默认链接 C runtime,因此需要一些 C 标准库 (libc) 的内容,由于我们禁用了标准库,我们也同样需要禁用常规的 C 启动例程。 50 | 51 | 将 `cargo build` 换成以下命令: 52 | 53 | > **[success] build passed** 54 | > 55 | > ```bash 56 | > $ cargo rustc -- -C link-arg=-nostartfiles 57 | > Compiling os v0.1.0 ... 58 | > Finished dev [unoptimized + debuginfo] target(s) in 4.87s 59 | > ``` 60 | 61 | 我们终于构建成功啦!虽然最后这个命令之后并不会用到,但是暂时看到了一个 success 不也很好吗? 62 | 63 | 构建得到的可执行文件位置放在 `os/target/debug/os` 中。 64 | 65 | 迄今为止的代码可以在[这里][code]找到,构建出现问题的话可以参考。 66 | 67 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch1-pa4 68 | -------------------------------------------------------------------------------- /chapter1/part5.md: -------------------------------------------------------------------------------- 1 | ## 总结与展望 2 | 3 | 这一章我们配置了 Rust 开发环境,使用包管理器 cargo 创建了一个二进制项目。作为一个新的操作系统,我们需要移除它对已有的操作系统的依赖,实际上我们分别通过移除标准库依赖与移除运行环境依赖,最终成功构建,得到了一个独立式可执行程序。 4 | 5 | 下一章我们将在这一章的基础上,针对目标硬件平台构建我们的内核镜像,使用 OpenSBI 进行启动,同时使用硬件模拟器 Qemu 模拟启动流程,并实现在屏幕上进行格式化输出。从而我们得到一个最小化内核作为后续开发的基础。 6 | -------------------------------------------------------------------------------- /chapter10/introduction.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | graph TB 3 | subgraph dependence 4 | interrupt 5 | thread 6 | end 7 | subgraph sync 8 | SpinLock --> interrupt 9 | Condvar --> SpinLock 10 | Condvar --> thread 11 | Mutex --> Condvar 12 | Monitor --> Condvar 13 | Semaphore --> Condvar 14 | Semaphore --> SpinLock 15 | end 16 | subgraph test 17 | Dining_Philosophers --> Mutex 18 | Dining_Philosophers --> Monitor 19 | end 20 | ``` 21 | -------------------------------------------------------------------------------- /chapter13/figures/hhh.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter13/figures/hhh.jpeg -------------------------------------------------------------------------------- /chapter13/introduction.md: -------------------------------------------------------------------------------- 1 | ## 第十三章:进程管理:fork and execute 2 | 3 | ![hhh](./figures/hhh.jpeg) 4 | 5 | ### 本章概要 6 | 7 | `sys_fork` 用于复制当前进程,`sys_exec` 用于将一个进程的内容修改为一个新的程序。在 99% 的情况下,fork 之后会立刻调用 exec 。Linux 便是这样创建进程的。 8 | 9 | 由于我们并没有做进程拆分,所以一个用户进程只有一个线程,于是就偷懒用 `Thread` 描述用户进程了。 10 | 11 | > 有没有觉得这样创建进程十分别扭,先复制一遍自身,然后用别的东西覆盖。。。那复制这些东西干啥。。。 12 | > 明明在前面的章节我们已经能够通过 `new_user_thread` 直接创建新进程(线程)了。。。 13 | 14 | 本章你将会学到: 15 | 16 | - fork 的功能 17 | - 如何描述一个正在运行的进程(线程) 18 | - 如何完全复制一个正在运行的进程(线程) 19 | - TODO:写 execute 20 | -------------------------------------------------------------------------------- /chapter13/part1.md: -------------------------------------------------------------------------------- 1 | ## fork 介绍 2 | 3 | fork 的功能是复制一个运行中的程序,具体来说就是一个程序在某一时刻发起 sys_fork 进入中断,由操作系统将此时的程序复制。从中断返回后,两个程序都会继续执行 fork 的下一条指令。 4 | 5 | fork 产生的新线程,除了返回值不一样,其它都完全一样。通过返回值,我们可以让两个线程进行不同的操作。 6 | 7 | **fork 的返回值:** 8 | 9 | - 如果是父线程(原线程),则返回子线程(新线程)的 tid 10 | - 如果是子线程(新线程),则 0 11 | 12 | 规范和细节听起来很麻烦,我们直接看例子: 13 | 14 | - `usr/rust/src/syscall.rs` 15 | 16 | ```rust 17 | enum SyscallId { 18 | Fork = 57, 19 | ... 20 | } 21 | 22 | pub fn sys_fork() -> i64 { 23 | sys_call(SyscallId::Fork, 0, 0, 0, 0) 24 | } 25 | ``` 26 | 27 | - `usr/rust/src/bin/fork.rs` 28 | 29 | ```rust 30 | #![no_std] 31 | #![no_main] 32 | 33 | #[macro_use] 34 | extern crate user; 35 | 36 | use user::syscall::sys_fork; 37 | 38 | #[no_mangle] 39 | pub fn main() -> usize { 40 | let tid = sys_fork(); 41 | let tid = sys_fork(); 42 | if tid == 0 { 43 | println!("I am child"); 44 | } else { 45 | println!("I am father"); 46 | } 47 | println!("ret tid is: {}", tid); 48 | 0 49 | } 50 | ``` 51 | 52 | - 输出 53 | 54 | ```bash 55 | I am child 56 | ret tid is: 0 57 | thread 3 exited, exit code = 0 58 | I am father 59 | ret tid is: 3 60 | thread 2 exited, exit code = 0 61 | I am child 62 | ret tid is: 0 63 | thread 4 exited, exit code = 0 64 | I am father 65 | ret tid is: 4 66 | thread 1 exited, exit code = 0 67 | ``` 68 | 69 | 从结果来看,一共退出了四次程序,所以一共进行了三次 fork : 70 | 71 | 1. 第三行,`thread 1` fork 产生 `thread 2` 72 | 2. `thread 1` 执行第四行,产生 `thread 3` 73 | 3. `thread 2` 执行第四行,产生 `thread 4` 74 | 75 | 每个线程都只输出两行,以及一行程序退出时由操作系统输出的信息。可以看出 `thread 1` 和 `thread 2` 都声称自己是 father ,这是由于它们在第四行 fork 之后,分别成为了 `thread 3` 和 `thread 4` 的 father 。需要注意的是,`thread 1` 还是 `thread 2` 的 father 哦。至于线程的执行顺序,那就看调度器算法咯。。。 76 | -------------------------------------------------------------------------------- /chapter13/part2.md: -------------------------------------------------------------------------------- 1 | ## fork 实现思路 2 | 3 | 在前面的章节,我们就已经实现了 Thread 结构体,为了满足新的需求,我们需要加上一行: 4 | 5 | ```diff 6 | + use alloc::sync::Arc; 7 | + use spin::Mutex; 8 | 9 | pub struct Thread { 10 | pub context: Context, // 程序切换产生的上下文所在栈的地址(指针) 11 | pub kstack: KernelStack, // 保存程序切换产生的上下文的栈 12 | pub wait: Option, // 等待队列 13 | + pub vm: Option>>, // 页表 14 | } 15 | ``` 16 | 17 | 为什么需要保存一个页表呢?这是因为 fork 复制了当前线程,这包括了它的运行栈。这里的运行栈就包括在了页表里。由于我们使用了虚拟地址,所以只要保证访问的虚拟地址能映射到正确的物理地址即可。所以,为了能够知道原线程都用了哪些虚拟地址,我们需要保存每一个线程的页表,供其它线程复制。 18 | 19 | 由于只有用户程序会进行 fork ,所以我们只为用户程序保存 vm ,内核线程的 vm 直接赋为 None 。 20 | 21 | - `struct.rs` 22 | 23 | ```rust 24 | impl Thread { 25 | pub fn new_kernel(entry: usize) -> Box { 26 | unsafe { 27 | let kstack_ = KernelStack::new(); 28 | Box::new(Thread { 29 | context: Context::new_kernel_thread(entry, kstack_.top(), satp::read().bits()), 30 | kstack: kstack_, 31 | wait: None, 32 | vm: None, 33 | }) 34 | } 35 | } 36 | 37 | pub fn get_boot_thread() -> Box { 38 | Box::new(Thread { 39 | context: Context::null(), 40 | kstack: KernelStack::new_empty(), 41 | wait: None, 42 | vm: None, 43 | }) 44 | } 45 | 46 | pub unsafe fn new_user(data: &[u8], wait_thread: Option) -> Box { 47 | ... 48 | Box::new(Thread { 49 | context: Context::new_user_thread(entry_addr, ustack_top, kstack.top(), vm.token()), 50 | kstack: kstack, 51 | wait: wait_thread, 52 | vm: Some(Arc::new(Mutex::new(vm))), 53 | }) 54 | } 55 | } 56 | ``` 57 | 58 | 复制线程的工作看起来十分简单,把所有东西都 clone 一遍就好了: 59 | 60 | - `struct.rs` 61 | 62 | ```rust 63 | use crate::context::{Context, TrapFrame}; 64 | 65 | impl Thread { 66 | /// Fork a new process from current one 67 | pub fn fork(&self, tf: &TrapFrame) -> Box { 68 | let kstack = KernelStack::new(); // 分配新的栈 69 | let vm = self.vm.as_ref().unwrap().lock().clone(); // 为变量分配内存,将虚拟地址映射到新的内存上(尚未实现) 70 | let vm_token = vm.token(); 71 | let context = unsafe { Context::new_fork(tf, kstack.top(), vm_token) }; // 复制上下文到 kernel stack 上(尚未实现) 72 | Box::new(Thread { 73 | context, 74 | kstack, 75 | wait: self.wait.clone(), 76 | vm: Some(Arc::new(Mutex::new(vm))), 77 | }) 78 | } 79 | } 80 | ``` 81 | 82 | 线程的 tid 是在 `thread_pool.add` 里进行分配的,由于 fork 需要为父线程返回子线程的 tid ,所以这里需要为 `thread_pool.add` 增加返回值: 83 | 84 | - `process/mod.rs` 85 | 86 | ```rust 87 | pub fn add_thread(thread: Box) -> usize { 88 | CPU.add_thread(thread) 89 | } 90 | ``` 91 | 92 | - `process/processor.rs` 93 | 94 | ```rust 95 | pub fn add_thread(&self, thread: Box) -> Tid { 96 | self.inner().pool.add(thread) 97 | } 98 | ``` 99 | 100 | - `process/thread_pool.rs` 101 | 102 | ```rust 103 | pub fn add(&mut self, _thread: Box) -> Tid { 104 | let tid = self.alloc_tid(); 105 | self.threads[tid] = Some(ThreadInfo { 106 | status: Status::Ready, 107 | thread: Some(_thread), 108 | }); 109 | self.scheduler.push(tid); 110 | return tid; 111 | } 112 | ``` 113 | 114 | 最后,实现 syscall 的代码就只有下面十几行: 115 | 116 | - `process/mod.rs` 117 | 118 | ```rust 119 | pub fn current_thread() -> &'static Box { 120 | CPU.current_thread() 121 | } 122 | ``` 123 | 124 | - `process/processor` 125 | 126 | ```rust 127 | impl Processor { 128 | pub fn current_thread(&self) -> &Box { 129 | &self.inner().current.as_mut().unwrap().1 130 | } 131 | } 132 | ``` 133 | 134 | - `syscall.rs` 135 | 136 | ```rust 137 | pub const SYS_FORK: usize = 57; 138 | 139 | pub fn syscall(id: usize, args: [usize; 3], tf: &mut TrapFrame) -> isize { 140 | match id { 141 | SYS_FORK => sys_fork(tf), 142 | ... 143 | } 144 | } 145 | 146 | fn sys_fork(tf: &mut TrapFrame) -> isize { 147 | let new_thread = process::current_thread().fork(tf); 148 | let tid = process::add_thread(new_thread); 149 | tid as isize 150 | } 151 | ``` 152 | 153 | > 吐槽一下,我最开始写 `current_thread` 的时候,返回的时候需要 clone 一下,感觉这样安全一些,省的外部不小心把 thread 修改了。 154 | > 结果这导致了一个很严重而且很隐蔽的问题:thread 的 kernel stack 被释放了。。。 155 | > 花了半天才找到问题,这是由于 Thread 有一个成员 kernel stack ,kernel stack 实现了 Drop trait ,析构的时候会把占用的内存一起释放掉。 156 | > 而由于 kernel stack 存的是指针(首地址) ,clone 后的指针和原指针指向的是同一个地方! 157 | > 所以在析构的时候,会把原来的 stack 也释放了。。。 158 | > awsl 159 | 160 | anyway ,fork 的实现思路大概就是这样。注意到前面有几个标注了“尚未实现”的函数,接下来我们来实现它们。 161 | 162 | > 出于偷懒我并没有维护这两个线程的父子关系,感兴趣的同学可以自 bang 行 wo 实现(逃 163 | -------------------------------------------------------------------------------- /chapter13/part3.md: -------------------------------------------------------------------------------- 1 | ## 复制线程上下文 2 | 3 | 这个比较简单,先写这个吧。 4 | 5 | - `context.rs` 6 | 7 | ```rust 8 | impl Context { 9 | pub unsafe fn new_fork(tf: &TrapFrame, kstack_top: usize, satp: usize) -> Context { 10 | ContextContent::new_fork(tf, kstack_top, satp) 11 | } 12 | } 13 | 14 | impl ContextContent { 15 | unsafe fn new_fork(tf: &TrapFrame, kstack_top: usize, satp: usize) -> Context { 16 | ContextContent { 17 | ra: __trapret as usize, 18 | satp, 19 | s: [0; 12], 20 | tf: { 21 | let mut tf = tf.clone(); 22 | // fork function's ret value, the new process is 0 23 | tf.x[10] = 0; // a0 24 | tf 25 | }, 26 | } 27 | .push_at(kstack_top) 28 | } 29 | } 30 | ``` 31 | 32 | 由于将 `ra` 赋值为 `__trapret` ,所以在 `switch` 最后执行 ret 的时候,内核会跳转到 `__trapret` ,因为 `tf` 保存了所有的上下文(包含了 `s[0..12]`),所以无需在 `new_fork` 中为 s 寄存器赋值。 33 | 34 | 将复制好的上下文放入新创建的 kstack 就可以啦。 35 | -------------------------------------------------------------------------------- /chapter13/part4.md: -------------------------------------------------------------------------------- 1 | ## 复制页表 2 | 3 | 前面我们使用了 `MemorySet.clone` ,但是我们并没有实现。实际上页表的复制并不像一般的元素那样简单。要做的事情有: 4 | 5 | 1. 创建一个新的页目录 6 | 2. 原线程每有一个页,就为新新线程分配一个页 7 | 3. 页的内容进行复制并映射 8 | 9 | - `memory/memory_set/mod.rs` 10 | 11 | ```rust 12 | use crate::memory::paging::{PageRange, PageTableImpl}; 13 | 14 | impl MemorySet { 15 | pub fn clone(&mut self) -> Self { 16 | // 创建一个新的页目录 17 | let mut new_page_table = PageTableImpl::new_bare(); 18 | let Self { 19 | ref mut page_table, 20 | ref areas, 21 | .. 22 | } = self; 23 | // 遍历自己的所有页面 24 | for area in areas.iter() { 25 | for page in PageRange::new(area.start, area.end) { 26 | // 创建一个新的页 27 | // 将原页的内容复制到新页,同时进行映射 28 | area.handler 29 | .clone_map(&mut new_page_table, page_table, page, &area.attr); 30 | } 31 | } 32 | MemorySet { 33 | areas: areas.clone(), 34 | page_table: new_page_table, 35 | } 36 | } 37 | } 38 | 39 | ``` 40 | 41 | 修改一下 `MemoryArea` 成员的访问权限: 42 | 43 | - `memory/memory_set/area.rs` 44 | 45 | ```rust 46 | pub struct MemoryArea { 47 | pub start: usize, 48 | pub end: usize, 49 | pub handler: Box, 50 | pub attr: MemoryAttr, 51 | } 52 | ``` 53 | 54 | 对于内核,我们采用线性映射。而对于用户程序,我们采用普通映射,即物理地址和虚拟地址没有什么关系,虚拟地址对应的物理内存无法通过简单计算得出,必须通过页表转换,所以所有程序的 handler 都是 `ByFrame` 类型而不是 `Linear` 类型。 55 | 56 | 在 `self.map` 中,会分配一个物理帧,并将其映射到指定的虚拟页上。然后将原页面的内容读出,复制到新页面上。这样,新旧线程访问同一个虚拟地址的时候,真实访问到的就是不同物理地址下相同数值的对象: 57 | 58 | - `memory/memory_set/handler.rs` 59 | 60 | ```rust 61 | impl MemoryHandler for ByFrame { 62 | fn clone_map( 63 | &self, 64 | pt: &mut PageTableImpl, 65 | src_pt: &mut PageTableImpl, 66 | vaddr: usize, 67 | attr: &MemoryAttr, 68 | ) { 69 | self.map(pt, vaddr, attr); 70 | let data = src_pt.get_page_slice_mut(vaddr); 71 | pt.get_page_slice_mut(vaddr).copy_from_slice(data); 72 | } 73 | } 74 | ``` 75 | 76 | 但是有一个问题,我们如果读取到原页表里的元素呢?我们现在在内核里,内核使用的是线性映射。所以我们需要: 77 | 78 | - 通过复杂的过程通过原页表得到虚拟地址对应的物理地址 79 | - 将这个物理地址转换为内核可访问的虚拟地址 80 | 81 | 上面的两步就是 `get_page_slice_mut` 做的事情,然后它把得到的虚拟地址转换成 u8 数组(方便操作): 82 | 83 | - `memory/paging.rs` 84 | 85 | ```rust 86 | impl PageTableImpl { 87 | pub fn get_page_slice_mut<'a>(&mut self, vaddr: usize) -> &'a mut [u8] { 88 | let frame = self 89 | .page_table 90 | .translate_page(Page::of_addr(VirtAddr::new(vaddr))) 91 | .unwrap(); 92 | let vaddr = frame.start_address().as_usize() + PHYSICAL_MEMORY_OFFSET; 93 | unsafe { core::slice::from_raw_parts_mut(vaddr as *mut u8, 0x1000) } 94 | } 95 | } 96 | ``` 97 | 98 | > `translate_page` 不是我实现的,我也懒得看具体细节了,反正用着挺好使,不管了(x) 99 | 100 | 最后要在 `MemoryHandler` 中声明 `clone_map` 成员函数,同时为 `Linear` 实现 `clone_map` : 101 | 102 | - `memory/memory_set/handler.rs` 103 | 104 | ```rust 105 | pub trait MemoryHandler: Debug + 'static { 106 | ... 107 | fn clone_map( 108 | &self, 109 | pt: &mut PageTableImpl, 110 | src_pt: &mut PageTableImpl, 111 | vaddr: usize, 112 | attr: &MemoryAttr, 113 | ); 114 | } 115 | 116 | impl MemoryHandler for Linear { 117 | fn clone_map( 118 | &self, 119 | pt: &mut PageTableImpl, 120 | _src_pt: &mut PageTableImpl, 121 | vaddr: usize, 122 | attr: &MemoryAttr, 123 | ) { 124 | self.map(pt, vaddr, attr); 125 | } 126 | } 127 | ``` 128 | 129 | 由于 `Linear` 的虚拟地址和物理地址是一对一的,所以简单的进行线性映射就好啦。。。 130 | -------------------------------------------------------------------------------- /chapter2/figures/privilege_levels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter2/figures/privilege_levels.png -------------------------------------------------------------------------------- /chapter2/figures/program_memory_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter2/figures/program_memory_layout.png -------------------------------------------------------------------------------- /chapter2/introduction.md: -------------------------------------------------------------------------------- 1 | # 第二章:最小化内核 2 | 3 | ## 本章概要 4 | 5 | 在上一章中,我们移除了程序中所有对于已有操作系统的依赖。但是我们的内核开发仍然需要依赖硬件平台。现在让我们来看一看怎样才能让我们的内核在硬件平台上跑起来。 6 | 7 | 本章你将会学到: 8 | 9 | - 使用 **目标三元组** 描述目标平台 10 | - 使用 **链接脚本** 描述内存布局 11 | - 进行 **交叉编译** 生成可执行文件,进而生成内核镜像 12 | - 使用 OpenSBI 作为 bootloader 加载内核镜像,并使用 Qemu 进行模拟 13 | - 使用 OpenSBI 提供的服务,在屏幕上格式化打印字符串用于以后调试 14 | -------------------------------------------------------------------------------- /chapter2/part1.md: -------------------------------------------------------------------------------- 1 | ## 使用目标三元组描述目标平台 2 | 3 | - [代码][code] 4 | 5 | cargo 在编译项目时,可以附加目标参数 `--target ` 设置项目的目标平台。平台包括硬件和软件支持,事实上,**目标三元组(target triple)** 包含:cpu 架构、供应商、操作系统和 [ABI](https://stackoverflow.com/questions/2171177/what-is-an-application-binary-interface-abi/2456882#2456882) 。 6 | 7 | 安装 Rust 时,默认编译后的可执行文件要在本平台上执行,我们可以使用 8 | 9 | `rustc --version --verbose` 来查看 Rust 的默认目标三元组: 10 | 11 | ```sh 12 | $ rustc --version --verbose 13 | rustc 1.42.0-nightly (859764425 2020-01-07) 14 | binary: rustc 15 | commit-hash: 85976442558bf2d09cec3aa49c9c9ba86fb15c1f 16 | commit-date: 2020-01-07 17 | host: x86_64-unknown-linux-gnu 18 | release: 1.42.0-nightly 19 | LLVM version: 9.0 20 | ``` 21 | 22 | 在 `host` 处可以看到默认的目标三元组, cpu 架构为 `x86_64` ,供应商为 `unknown` ,操作系统为 `linux` ,ABI 为 `gnu` 。由于我们是在 64 位 ubuntu 上安装的 Rust ,这个默认目标三元组的确描述了本平台。 23 | 24 | 官方对一些平台提供了默认的目标三元组,我们可以通过以下命令来查看完整列表: 25 | 26 | ```sh 27 | $ rustc --print target-list 28 | ``` 29 | 30 | ### 目标三元组 JSON 描述文件 31 | 32 | 除了默认提供的以外,Rust 也允许我们用 JSON 文件定义自己的目标三元组。 33 | 34 | 首先我们来看一下默认的目标三元组 **x86_64-unknown-linux-gnu** 的 **JSON** 文件描述,输入以下命令: 35 | 36 | ```sh 37 | $ rustc -Z unstable-options --print target-spec-json --target x86_64-unknown-linux-gnu 38 | ``` 39 | 40 | 可以得到如下输出: 41 | 42 | ```json 43 | // x86_64-unknown-linux-gnu.json 44 | { 45 | "arch": "x86_64", 46 | "cpu": "x86-64", 47 | "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", 48 | "dynamic-linking": true, 49 | "env": "gnu", 50 | "executables": true, 51 | "has-elf-tls": true, 52 | "has-rpath": true, 53 | "is-builtin": true, 54 | "linker-flavor": "gcc", 55 | "linker-is-gnu": true, 56 | "llvm-target": "x86_64-unknown-linux-gnu", 57 | "max-atomic-width": 64, 58 | "os": "linux", 59 | "position-independent-executables": true, 60 | "pre-link-args": { 61 | "gcc": ["-Wl,--as-needed", "-Wl,-z,noexecstack", "-m64"] 62 | }, 63 | "relro-level": "full", 64 | "stack-probes": true, 65 | "target-c-int-width": "32", 66 | "target-endian": "little", 67 | "target-family": "unix", 68 | "target-pointer-width": "64", 69 | "vendor": "unknown" 70 | } 71 | ``` 72 | 73 | 可以看到里面描述了架构、 CPU 、操作系统、 ABI 、端序、字长等信息。 74 | 75 | 我们现在想基于 64 位 RISCV 架构开发内核,就需要一份 `riscv64` 的目标三元组。幸运的是,目前 Rust 编译器已经内置了一个可用的目标:`riscv64imac-unknown-none-elf`。 76 | 77 | 我们查看一下它的 JSON 描述文件: 78 | 79 | ```sh 80 | $ rustc -Z unstable-options --print target-spec-json --target riscv64imac-unknown-none-elf 81 | ``` 82 | 83 | ```json 84 | // riscv64imac-unknown-none-elf.json 85 | { 86 | "abi-blacklist": [ 87 | "cdecl", 88 | "stdcall", 89 | "fastcall", 90 | "vectorcall", 91 | "thiscall", 92 | "aapcs", 93 | "win64", 94 | "sysv64", 95 | "ptx-kernel", 96 | "msp430-interrupt", 97 | "x86-interrupt", 98 | "amdgpu-kernel" 99 | ], 100 | "arch": "riscv64", 101 | "code-model": "medium", 102 | "cpu": "generic-rv64", 103 | "data-layout": "e-m:e-p:64:64-i64:64-i128:128-n64-S128", 104 | "eliminate-frame-pointer": false, 105 | "emit-debug-gdb-scripts": false, 106 | "env": "", 107 | "executables": true, 108 | "features": "+m,+a,+c", 109 | "is-builtin": true, 110 | "linker": "rust-lld", 111 | "linker-flavor": "ld.lld", 112 | "llvm-target": "riscv64", 113 | "max-atomic-width": 64, 114 | "os": "none", 115 | "panic-strategy": "abort", 116 | "relocation-model": "static", 117 | "target-c-int-width": "32", 118 | "target-endian": "little", 119 | "target-pointer-width": "64", 120 | "vendor": "unknown" 121 | } 122 | ``` 123 | 124 | 我们来看它与默认的目标三元组有着些许不同的地方: 125 | 126 | ```json 127 | "panic-strategy": "abort", 128 | ``` 129 | 130 | 这个描述了 `panic` 时采取的策略。回忆上一章中,我们在 `Cargo.toml` 中设置程序在 `panic` 时直接 `abort` ,从而不必调用堆栈展开处理函数。由于目标三元组中已经包含了这个参数,我们可以将 `Cargo.toml` 中的设置删除了: 131 | 132 | ```diff 133 | -[profile.dev] 134 | -panic = "abort" 135 | 136 | -[profile.release] 137 | -panic = "abort" 138 | ``` 139 | 140 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch2-pa4 141 | -------------------------------------------------------------------------------- /chapter2/part4.md: -------------------------------------------------------------------------------- 1 | ## 重写程序入口点 `_start` 2 | 3 | - [代码][code] 4 | 5 | 我们在第一章中,曾自己重写了一个 C runtime 的入口点 `_start` ,在那里我们仅仅只是让它死循环。但是现在,类似 C runtime ,我们希望这个函数可以为我们设置内核的运行环境(不妨称为 kernel runtime ) 。随后,我们才真正开始执行内核的代码。 6 | 7 | 但是具体而言我们需要设置怎样的运行环境呢? 8 | 9 | > **[info] 第一条指令** 10 | > 11 | > 在 CPU 加电或 reset 后,它首先会进行 **自检 (POST, Power-On Self-Test)** ,通过自检后会跳转到 **启动代码(bootloader)** 的入口。在 bootloader 中,我们进行外设探测,并对内核的运行环境进行初步设置。随后, bootloader 会将内核代码从硬盘 load 到内存中,并跳转到内核入口,正式进入内核。 12 | > 13 | > 所以 CPU 所执行的第一条指令是指 bootloader 的第一条指令。 14 | 15 | 幸运的是, 我们已经有现成的 bootloader 实现 -- [OpenSBI](https://github.com/riscv/opensbi) 固件(firmware)。 16 | 17 | > **[info] firmware 固件** 18 | > 19 | > 在计算中,固件是一种特定的计算机软件,它为设备的特定硬件提供低级控制进一步加载其他软件的功能。固件可以为设备更复杂的软件(如操作系统)提供标准化的操作环境,或者,对于不太复杂的设备,充当设备的完整操作系统,执行所有控制、监视和数据操作功能。 在基于 x86 的计算机系统中, BIOS 或 UEFI 是一种固件;在基于 riscv 的计算机系统中,OpenSBI 是一种固件。 20 | 21 | OpenSBI 固件运行在特权级别很高的计算机硬件环境中,即 riscv64 cpu 的 M Mode (CPU 加电后也就运行在 M Mode) ,我们将要实现的 OS 内核运行在 S Mode , 而我们要支持的用户程序运行在 U Mode 。在开发过程中我们重点关注 S Mode 。 22 | 23 | > **[info] riscv64 的特权级** 24 | > 25 | > 如图所示,共有如下几个特权级: 26 | > 27 | > ![](figures/privilege_levels.png) 28 | > 29 | > 从 U 到 S 再到 M,权限不断提高,这意味着你可以使用更多的特权指令,访需求权限更高的寄存器等等。我们可以使用一些指令来修改 CPU 的**当前特权级**。而当当前特权级不足以执行特权指令或访问一些寄存器时,CPU 会通过某种方式告诉我们。 30 | 31 | OpenSBI 所做的一件事情就是把 CPU 从 M Mode 切换到 S Mode ,接着跳转到一个固定地址 0x80200000,开始执行内核代码。 32 | 33 | > **[info] riscv64 的 M Mode** 34 | > 35 | > M-mode(机器模式,缩写为 M 模式)是 RISC-V 中 hart(hardware thread,硬件线程)可以执行的最高权限模式。在 M 模式下运行的 hart 对内存,I/O 和一些对于启动和配置系统来说必要的底层功能有着完全的使用权。 36 | > 37 | > **riscv64 的 S Mode** 38 | > 39 | > S-mode(监管者模式,缩写为 S 模式)是支持现代类 Unix 操作系统的权限模式,支持现代类 Unix 操作系统所需要的基于页面的虚拟内存机制是其核心。 40 | > 41 | 42 | 43 | 接着我们要在 `_start` 中设置内核的运行环境了,我们直接来看代码: 44 | 45 | ```riscv 46 | # src/boot/entry64.asm 47 | 48 | .section .text.entry 49 | .globl _start 50 | _start: 51 | la sp, bootstacktop 52 | call rust_main 53 | 54 | .section .bss.stack 55 | .align 12 56 | .global bootstack 57 | bootstack: 58 | .space 4096 * 4 59 | .global bootstacktop 60 | bootstacktop: 61 | ``` 62 | 63 | 可以看到之前未被定义的 *.bss.stack* 段出现了,我们只是在这里分配了一块 $$4096\times{4}\text{Bytes}=\text{16KiB}$$ 的内存作为内核的栈。之前的 *.text.entry* 也出现了:我们将 `_start` 函数放在了 *.text* 段的开头。 64 | 65 | 我们看看 `_start` 里面做了什么: 66 | 67 | 1. 修改栈指针寄存器 `sp` 为 *.bss.stack* 段的结束地址,由于栈是从高地址往低地址增长,所以高地址是栈顶; 68 | 2. 使用 `call` 指令跳转到 `rust_main` 。这意味着我们的内核运行环境设置完成了,正式进入内核。 69 | 70 | 我们将 `src/main.rs` 里面的 `_start` 函数删除,并换成 `rust_main` : 71 | 72 | ```rust 73 | // src/main.rs 74 | 75 | #![feature(global_asm)] 76 | 77 | global_asm!(include_str!("boot/entry64.asm")); 78 | 79 | #[no_mangle] 80 | pub extern "C" fn rust_main() -> ! { 81 | loop {} 82 | } 83 | ``` 84 | 85 | 到现在为止我们终于将一切都准备好了,接下来就要配合 OpenSBI 运行我们的内核! 86 | 87 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch2-pa4 88 | -------------------------------------------------------------------------------- /chapter2/part7.md: -------------------------------------------------------------------------------- 1 | ## 实现格式化输出 2 | 3 | - [代码][code] 4 | 5 | 只能使用 `console_putchar` 这种苍白无力的输出手段让人头皮发麻。如果我们能使用 `print!` 宏的话该有多好啊!于是我们就来实现自己的 `print!`宏! 6 | 7 | 我们将这一部分放在 `src/io.rs` 中,先用 `console_putchar` 实现两个基础函数: 8 | 9 | ```rust 10 | // src/lib.rs 11 | 12 | // 由于使用到了宏,需要进行设置 13 | // 同时,这个 module 还必须放在其他 module 前 14 | #[macro_use] 15 | mod io; 16 | 17 | // src/io.rs 18 | 19 | use crate::sbi; 20 | 21 | // 输出一个字符 22 | pub fn putchar(ch: char) { 23 | sbi::console_putchar(ch as u8 as usize); 24 | } 25 | 26 | // 输出一个字符串 27 | pub fn puts(s: &str) { 28 | for ch in s.chars() { 29 | putchar(ch); 30 | } 31 | } 32 | ``` 33 | 34 | 而关于格式化输出, rust 中提供了一个接口 `core::fmt::Write` ,你需要实现函数 35 | 36 | ```rust 37 | // required 38 | fn write_str(&mut self, s: &str) -> Result 39 | ``` 40 | 41 | 随后你就可以调用如下函数(会进一步调用`write_str` 实现函数)来进行显示。 42 | 43 | ```rust 44 | // provided 45 | fn write_fmt(mut self: &mut Self, args: Arguments<'_>) -> Result 46 | ``` 47 | 48 | `write_fmt` 函数需要处理 `Arguments` 类封装的输出字符串。而我们已经有现成的 `format_args!` 宏,它可以将模式字符串+参数列表的输入转化为 `Arguments` 类!比如 `format_args!("{} {}", 1, 2)` 。 49 | 50 | 因此,我们的 `print!` 宏的实现思路便为: 51 | 52 | 1. 解析传入参数,转化为 `format_args!` 可接受的输入(事实上原封不动就行了),并通过 `format_args!` 宏得到 `Arguments` 类; 53 | 2. 调用 `write_fmt` 函数输出这个类; 54 | 55 | 而为了调用 `write_fmt` 函数,我们必须实现 `write_str` 函数,而它可用 `puts` 函数来实现。支持`print!`宏的代码片段如下: 56 | 57 | ```rust 58 | // src/io.rs 59 | 60 | use core::fmt::{ self, Write }; 61 | 62 | struct Stdout; 63 | 64 | impl fmt::Write for Stdout { 65 | fn write_str(&mut self, s: &str) -> fmt::Result { 66 | puts(s); 67 | Ok(()) 68 | } 69 | } 70 | 71 | pub fn _print(args: fmt::Arguments) { 72 | Stdout.write_fmt(args).unwrap(); 73 | } 74 | 75 | #[macro_export] 76 | macro_rules! print { 77 | ($($arg:tt)*) => ({ 78 | $crate::io::_print(format_args!($($arg)*)); 79 | }); 80 | } 81 | 82 | #[macro_export] 83 | macro_rules! println { 84 | () => ($crate::print!("\n")); 85 | ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); 86 | } 87 | ``` 88 | 89 | 由于并不是重点就不在这里赘述宏的语法细节了(实际上我也没弄懂),总之我们实现了 `print!, println!` 两个宏,现在是时候看看效果了! 90 | 首先,我们在 `panic` 时也可以看看到底发生了什么事情了! 91 | 92 | ```rust 93 | // src/lang_items.rs 94 | 95 | #[panic_handler] 96 | fn panic(info: &PanicInfo) -> ! { 97 | println!("{}", info); 98 | loop {} 99 | } 100 | ``` 101 | 102 | 其次,我们可以验证一下我们之前为内核分配的内存布局是否正确: 103 | 104 | ```rust 105 | // src/init.rs 106 | 107 | use crate::io; 108 | use crate::sbi; 109 | 110 | #[no_mangle] 111 | pub extern "C" fn rust_main() -> ! { 112 | extern "C" { 113 | fn _start(); 114 | fn bootstacktop(); 115 | } 116 | println!("_start vaddr = 0x{:x}", _start as usize); 117 | println!("bootstacktop vaddr = 0x{:x}", bootstacktop as usize); 118 | println!("hello world!"); 119 | panic!("you want to do nothing!"); 120 | loop {} 121 | } 122 | ``` 123 | 124 | `make run` 一下,我们可以看到输出为: 125 | 126 | > **[success] 格式化输出通过** 127 | > 128 | > ```rust 129 | > _start vaddr = 0x80200000 130 | > bootstacktop vaddr = 0x80208000 131 | > hello world! 132 | > panicked at 'you want to do nothing!', src/init.rs:15:5 133 | > ``` 134 | 135 | 我们看到入口点的地址确实为我们安排的 `0x80200000` ,同时栈的地址也与我们在内存布局中看到的一样。更重要的是,我们现在能看到内核 `panic` 的位置了!这将大大有利于调试。 136 | 137 | 目前所有的代码可以在[这里][code]找到。 138 | 139 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch2-pa7 140 | -------------------------------------------------------------------------------- /chapter2/part8.md: -------------------------------------------------------------------------------- 1 | ## 总结与展望 2 | 3 | 这一章我们主要做的事情是为内核提供硬件平台支持。 4 | 5 | 首先要让我们的内核有可能在指定的平台上运行。而那与我们当前所在的并非一个平台,指令集并不相通。为此我们使用 **交叉编译** 将内核编译到用 **目标三元组** 描述的目标平台上,还使用 **链接脚本** 指定了其内存布局,将内核的代码、数据均放在高地址。 6 | 7 | 然而编译好了之后它也就静止地放在那里而已。为了让它启动起来,我们使用 **bootloader(OpenSBI)** 将内核加载进来并运行。同时,我们发现 OpenSBI 的能力比我们想象中要强大,我们简单地通过 **内联汇编** 请求 OpenSBI 向我们提供的服务,实现了 **格式化输出** 。当然出于方便及节约成本,这一切都是在 **模拟器** Qemu 上进行的。 8 | 9 | 到这里我们终于有了一个内核,而且它能在特定平台上运行了! 10 | -------------------------------------------------------------------------------- /chapter3/introduction.md: -------------------------------------------------------------------------------- 1 | # 第三章:中断 2 | 3 | ## 本章概要 4 | 5 | 操作系统是计算机系统的监管者,必须能对计算机系统状态的突发变化做出反应,这些系统状态可能是程序执行出现异常,或者是突发的外设请求。当计算机系统遇到突发情况时,不得不停止当前的正常工作,应急响应一下,这是需要操作系统来接管,并跳转到对应处理函数进行处理,处理结束后再回到原来的地方继续执行指令。这个过程就是中断处理过程。 6 | 7 | > **[info] 中断分类** 8 | > 9 | > **异常(Exception)**,指在执行一条指令的过程中发生了错误,此时我们通过中断来处理错误。最常见的异常包括:访问无效内存地址、执行非法指令(除零)、发生缺页等。他们有的可以恢复(如缺页),有的不可恢复(如除零),只能终止程序执行。 10 | > 11 | > **陷入(Trap)**,指我们主动通过一条指令停下来,并跳转到处理函数。常见的形式有通过`ecall`进行**系统调用(syscall)**,或通过`ebreak`进入**断点(breakpoint)**。 12 | > 13 | > **外部中断(Interrupt)**,简称中断,指的是 CPU 的执行过程被外设发来的信号打断,此时我们必须先停下来对该外设进行处理。典型的有定时器倒计时结束、串口收到数据等。 14 | > 15 | > 外部中断是**异步(asynchronous)**的,CPU 并不知道外部中断将何时发生。CPU 也并不需要一直在原地等着外部中断的发生,而是执行代码,有了外部中断才去处理。我们知道,CPU 的主频远高于 I/O 设备,这样避免了 CPU 资源的浪费。 16 | 17 | 本章你将会学到: 18 | 19 | - riscv 的中断相关知识 20 | - 中断前后如何进行上下文环境的保存与恢复 21 | - 处理最简单的断点中断和时钟中断。 22 | 23 | > **[info] 为何先学习中断?** 24 | > 25 | > 我们在实现操作系统过程中,会出现各种不可预知的异常错误,且系统一般都会当机(挂了),让开发者不知所措。如果我们实现的 OS 有了中断(包括异常)处理能力,那么在由于某种编程失误产生异常时,OS 能感知到异常,并能提供相关信息(比如异常出现的原因,异常产生的地址等)给开发者,便于开发者修改程序。 26 | > 27 | > 另外,中断机制(特别是时钟中断)是实现后续进程切换与调度、系统服务机制等的基础。 28 | -------------------------------------------------------------------------------- /chapter3/part1.md: -------------------------------------------------------------------------------- 1 | ## rv64 中断介绍 2 | 3 | ### 再看 rv64 权限模式 4 | 5 | > **[info] 再看 riscv64 的 M Mode** 6 | > 7 | > M-mode(机器模式,缩写为 M 模式)是 RISC-V 中 hart(hardware thread,硬件线程)可以执行的最高权限模式。在 M 模式下运行的 hart 对内存,I/O 和一些对于启动和配置系统来说必要的底层功能有着完全的使用权。默认情况下,发生所有异常(不论在什么权限模式下)的时候,控制权都会被移交到 M 模式的异常处理程序。它是唯一所有标准 RISC-V 处理器都必须实现的权限模式。 8 | > 9 | > **再看 riscv64 的 S Mode** 10 | > 11 | > S-mode(监管者模式,缩写为 S 模式)是支持现代类 Unix 操作系统的权限模式,支持基于页面的虚拟内存机制是其核心。 Unix 系统中的大多数例外都应该进行 S 模式下的系统调用。M 模式的异常处理程序可以将异常重新导向 S 模式,也支持通过异常委托机制(Machine Interrupt Delegation,机器中断委托)选择性地将中断和同步异常直接交给 S 模式处理,而完全绕过 M 模式。 12 | 13 | ### rv64 中断相关寄存器 14 | 15 | 下面的寄存器主要用于设置或保存中断相关的静态或动态信息。 16 | 17 | > ** [info] 中断相关寄存器 ** 18 | > 19 | > 当我们触发中断进入 S 态进行处理时,以下寄存器会被硬件自动设置: 20 | > 21 | > **sepc**(exception program counter),它会记录触发中断的那条指令的地址; 22 | > 23 | > **scause**,它会记录中断发生的原因,还会记录该中断是不是一个外部中断; 24 | > 25 | > **stval**,它会记录一些中断处理所需要的辅助信息,比如取指、访存、缺页异常,它会把发生问题的目标地址记录下来,这样我们在中断处理程序中就知道处理目标了。 26 | > 27 | > 还有一些中断配置的寄存器: 28 | > 29 | > **stvec**,设置如何寻找 S 态中断处理程序的起始地址,保存了中断向量表基址 BASE,同时还有模式 MODE。 30 | > 31 | > 当$$\text{MODE}=0$$,设置为 Direct 模式时,无论中断因何发生我们都直接跳转到基址$$\text{pc}\leftarrow\text{BASE}$$。 32 | > 33 | > 当$$\text{MODE}=1$$时,设置为 Vectored 模式时,遇到中断我们会进行跳转如下:$$\text{pc}\leftarrow\text{BASE}+4\times\text{cause}$$。而这样,我们只需将各中断处理程序放在正确的位置,并设置好 stvec ,遇到中断的时候硬件根据中断原因就会自动跳转到对应的中断处理程序了; 34 | > 35 | > **sstatus**,S 态控制状态寄存器。保存全局中断使能标志,以及许多其他的状态。可设置此寄存器来中断使能与否。 36 | 37 | ### rv64 中断相关特权指令 38 | 39 | 我们再来看一下中断相关的指令。 40 | 41 | > ** [info] 中断相关指令 ** 42 | > 43 | > **ecall**(environment call),当我们在 S 态执行这条指令时,会触发一个 ecall-from-s-mode-exception,从而进入 M 模式中的中断处理流程(如设置定时器等);当我们在 U 态执行这条指令时,会触发一个 ecall-from-u-mode-exception,从而进入 S 模式中的中断处理流程(常用来进行系统调用)。 44 | > 45 | > **sret**,用于 S 态中断返回到 U 态,实际作用为$$\text{pc}\leftarrow\text{sepc}$$,回顾**sepc**定义,返回到通过中断进入 S 态之前的地址。 46 | > 47 | > **ebreak**(environment break),执行这条指令会触发一个断点中断从而进入中断处理流程。 48 | > 49 | > **mret**,用于 M 态中断返回到 S 态或 U 态,实际作用为$$\text{pc}\leftarrow\text{mepc}$$,回顾**sepc**定义,返回到通过中断进入 M 态之前的地址。(一般不用涉及) 50 | -------------------------------------------------------------------------------- /chapter3/part2.md: -------------------------------------------------------------------------------- 1 | ## 手动触发断点中断 2 | 3 | - [代码][code] 4 | 5 | 如要让 OS 正确处理各种中断,首先 OS 在初始化时,需要设置好中断处理程序的起始地址,并使能中断。 6 | 7 | 我们引入一个对寄存器进行操作的库,这样就可以不用自己写了。 8 | 9 | ```rust 10 | // Cargo.toml 11 | 12 | [dependencies] 13 | riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] } 14 | ``` 15 | 16 | ### 设置中断处理程序起始地址 17 | 18 | 为了方便起见,我们先将 stvec 设置为 Direct 模式跳转到一个统一的处理程序。 19 | 20 | ```rust 21 | // src/lib.rs 22 | 23 | mod interrupt; 24 | 25 | // src/interrupt.rs 26 | 27 | use riscv::register::{ 28 | scause, 29 | sepc, 30 | stvec, 31 | sscratch 32 | }; 33 | 34 | pub fn init() { 35 | unsafe { 36 | sscratch::write(0); 37 | stvec::write(trap_handler as usize, stvec::TrapMode::Direct); 38 | } 39 | println!("++++ setup interrupt! ++++"); 40 | } 41 | 42 | fn trap_handler() -> ! { 43 | let cause = scause::read().cause(); 44 | let epc = sepc::read(); 45 | println!("trap: cause: {:?}, epc: 0x{:#x}", cause, epc); 46 | panic!("trap handled!"); 47 | } 48 | ``` 49 | 50 | 这里我们通过设置 stvec 使得所有中断都跳转到 `trap_handler` 并将其作为中断处理程序。而这个中断处理程序仅仅输出了一下中断原因以及中断发生的地址,就匆匆 panic 了事。 51 | 52 | > **[info] 初始化时为何将`sscratch`寄存器置 0?** 53 | > 54 | > 将`sscratch`寄存器置 0 也许让人费解,我们会在[**part4 实现上下文环境保存与恢复**](part4.md)中 j 进一步详细分析它的作用。简单地说,这里的设置是为了在产生中断是根据 sscratch 的值是否为 0 来判断是在 S 态产生的中断还是 U 态(用户态)产生的中断。由于这里还没有 U 态的存在,所以这里是否置 0 其实并无影响。 55 | 56 | 我们在主函数中通过汇编指令手动触发断点中断: 57 | 58 | ```rust 59 | // src/init.rs 60 | 61 | #[no_mangle] 62 | pub extern "C" fn rust_main() -> ! { 63 | crate::interrupt::init(); 64 | unsafe { 65 | asm!("ebreak"::::"volatile"); 66 | } 67 | panic!("end of rust_main"); 68 | } 69 | ``` 70 | 71 | 使用 `make run`构建并运行,你可能能看到以下的正确结果: 72 | 73 | > **[success] trap handled** 74 | > 75 | > ```rust 76 | > ++++ setup interrupt! ++++ 77 | > trap: cause: Exception(Breakpoint), epc: 0x0x80200022 78 | > panicked at 'trap handled!', src/interrupt.rs:20:5 79 | > ``` 80 | 81 | 但是很不巧,你有差不多相同的概率看到以下和我们预期不同的的结果: 82 | 83 | > **[danger] 非预期的显示结果** 84 | 85 | > ```rust 86 | > ++++ setup interrupt! ++++ 87 | > ++++ setup interrupt! ++++ 88 | > ...... 89 | > ``` 90 | 91 | 内核进入了 Boot loop? 92 | 93 | ### 保证异常处理入口对齐 94 | 95 | 为何没有异常处理程序的显示,而是 qemu 模拟的 riscv 计算机不断地重新启动?根据 RV64 ISA,异常处理入口必须按照四字节对齐,但是我们现在的代码并没有保证这一点。因此我们在设置 stvec 的时候,事实上最低两位地址被置零了,发生异常的时候可能直接跳转到了我们的异常处理程序的第一条指令中间。显然,这很大概率会导致各种各样的奇怪条件,之后跑飞。 96 | 97 | 很遗憾是,Rust 没有简单地办法保证一个符号的对齐,此外使用纯 Rust 实现 Trap handler 还有一些其他的问题:Rust 会在函数的开始和结尾加入一些额外的指令,控制栈寄存器等。因此如果要完成保存现场等工作,以便在异常处理程序完成后返回,Rust 单独是难以完成的。接下来几节中我们将通过提供使用汇编代码编写的异常处理程序来解决这些问题。 98 | 99 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch3-pa2 100 | -------------------------------------------------------------------------------- /chapter3/part3.md: -------------------------------------------------------------------------------- 1 | ## 程序运行上下文环境 2 | 3 | - [代码][code] 4 | 5 | 考虑在中断发生之前,程序的程序运行上下文环境(也称运行状态,程序运行中的中间结果)保存在一些寄存器中。而中断发生时,硬件仅仅帮我们设置中断原因、中断地址,随后就根据 `stvec` 直接跳转到中断处理程序。而中断处理程序可能会修改了那个保存了重要结果的寄存器,而后,即使处理结束后使用 `sret` 指令跳回到中断发生的位置,原来的程序也会一脸懵逼:这个中间结果怎么突然变了? 6 | 7 | > **[info] 函数调用与调用约定(calling convention)** 8 | > 9 | > 其实中断处理也算是一种函数调用,而我们必须保证在函数调用前后上下文环境(包括各寄存器的值)不发生变化。而寄存器分为两种,一种是**调用者保存(caller-saved)**,也就是子程序可以肆无忌惮的修改这些寄存器而不必考虑后果,因为在进入子程序之前他们已经被保存了;另一种是**被调用者保存(callee-saved)**,即子程序必须保证自己被调用前后这些寄存器的值不变。 10 | > 11 | > 函数调用还有一些其它问题,比如参数如何传递——是通过寄存器传递还是放在栈上。这些标准由指令集在[调用约定(calling convention)](https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf)中规定,并由操作系统和编译器实现。 12 | > 13 | > 调用约定(calling convention) 是**二进制接口(ABI, Application Binary Interface)**的一个重要方面。在进行多语言同时开发时尤其需要考虑。设想多种语言的函数互相调来调去,那时你就只能考虑如何折腾寄存器和栈了。 14 | 15 | 简单起见,在中断处理前,我们把全部寄存器都保存在栈上,并在中断处理后返回到被打断处之前还原所有保存的寄存器,这样总不会出错。我们使用一个名为**中断帧(TrapFrame)**的结构体来记录这些寄存器的值: 16 | 17 | ```rust 18 | // src/lib.rs 19 | 20 | mod context; 21 | 22 | // src/context.rs 23 | 24 | use riscv::register::{ 25 | sstatus::Sstatus, 26 | scause::Scause, 27 | }; 28 | 29 | #[repr(C)] 30 | pub struct TrapFrame { 31 | pub x: [usize; 32], // General registers 32 | pub sstatus: Sstatus, // Supervisor Status Register 33 | pub sepc: usize, // Supervisor exception program counter 34 | pub stval: usize, // Supervisor trap value 35 | pub scause: Scause, // Scause register: record the cause of exception/interrupt/trap 36 | } 37 | ``` 38 | 39 | 我们将$$32$$个通用寄存器全保存下来,同时还之前提到过的进入中断之前硬件会自动设置的三个寄存器,还有状态寄存器 `sstatus` 也会被修改。 40 | 41 | 其中属性`#[repr(C)]`表示对这个结构体按照 C 语言标准进行内存布局,即从起始地址开始,按照字段的声明顺序依次排列,如果不加上这条属性的话,Rust 编译器对结构体的内存布局是不确定的(Rust 语言标准没有结构体内存布局的规定),我们就无法使用汇编代码对它进行正确的读写。 42 | 43 | 如何在中断处理过程中保存与恢复程序的上下文环境?请看下一节。 44 | 45 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch3-pa4 46 | -------------------------------------------------------------------------------- /chapter3/part6.md: -------------------------------------------------------------------------------- 1 | ## 总结与展望 2 | 3 | 通过本章的学习,我们了解了 riscv 的中断处理机制、相关寄存器与指令。我们知道在中断前后需要恢复上下文环境,用 一个名为中断帧(TrapFrame)的结构体存储了要保存的各寄存器,并用了很大篇幅解释如何通过精巧的汇编代码实现上下文环境保存与恢复机制。最终,我们通过处理断点和时钟中断验证了我们正确实现了中断机制。 4 | 5 | 从下章开始,我们介绍操作系统是如何管理我们的内存资源的。 6 | -------------------------------------------------------------------------------- /chapter4/introduction.md: -------------------------------------------------------------------------------- 1 | # 第四章:内存管理 2 | 3 | ## 本章概要 4 | 5 | 本章你将会学到: 6 | 7 | - 物理内存的探测、分配和管理 8 | - 内核内部动态分配内存 9 | -------------------------------------------------------------------------------- /chapter4/part3.md: -------------------------------------------------------------------------------- 1 | ## 总结与展望 2 | 3 | 本章我们介绍了物理内存管理:即物理页帧分配、回收;以及内核内部的动态内存分配,在 $$\text{.bss}$$ 端上一段预留的内存上进行。后面各章都会使用到这两个工具。 4 | -------------------------------------------------------------------------------- /chapter5/figures/memory_handler.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter5/figures/memory_handler.jpg -------------------------------------------------------------------------------- /chapter5/figures/memory_set.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter5/figures/memory_set.jpg -------------------------------------------------------------------------------- /chapter5/figures/rcore_memlayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter5/figures/rcore_memlayout.png -------------------------------------------------------------------------------- /chapter5/figures/sv39_addr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter5/figures/sv39_addr.png -------------------------------------------------------------------------------- /chapter5/figures/sv39_pagetable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter5/figures/sv39_pagetable.jpg -------------------------------------------------------------------------------- /chapter5/figures/sv39_pte.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter5/figures/sv39_pte.jpg -------------------------------------------------------------------------------- /chapter5/figures/sv39_rwx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter5/figures/sv39_rwx.jpg -------------------------------------------------------------------------------- /chapter5/figures/sv39_satp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter5/figures/sv39_satp.jpg -------------------------------------------------------------------------------- /chapter5/introduction.md: -------------------------------------------------------------------------------- 1 | # 第五章:内存虚拟化 2 | 3 | 本章你将会学到: 4 | 5 | - 虚拟内存和物理内存的概念 6 | - 如何使用页表完成虚拟地址到物理地址的映射 7 | - 解释内核初始映射,进行内核重映射 8 | 9 | -------------------------------------------------------------------------------- /chapter5/part2.md: -------------------------------------------------------------------------------- 1 | ## “魔法”——内核初始映射 2 | 3 | - [代码][code] 4 | 5 | 之前的内核实现并未使能页表机制,实际上内核是直接在物理地址空间上运行的。这样虽然比较简单,但是为了后续能够支持多个用户进程能够在内核中并发运行,满足隔离等性质,我们要先运用学过的页表知识,把内核的运行环境从物理地址空间转移到虚拟地址空间,为之后的功能打好铺垫。 6 | 7 | 更具体的,我们现在想将内核代码放在虚拟地址空间中以 ``0xffffffffc0200000`` 开头的一段高地址空间中。因此,我们将下面的参数修改一下: 8 | 9 | ```diff 10 | # src/boot/linker64.ld 11 | -BASE_ADDRESS = 0x80200000; 12 | +BASE_ADDRESS = 0xffffffffc0200000; 13 | 14 | # src/consts.rs 15 | -pub const KERNEL_BEGIN_VADDR: usize = 0x80200000; 16 | +pub const KERNEL_BEGIN_VADDR: usize = 0xffffffffc0200000; 17 | ``` 18 | 19 | 我们修改了链接脚本中的链接开头地址。但是这样做的话,就能从物理地址空间转移到虚拟地址空间了吗?让我们回顾一下在相当于 bootloader 的 OpenSBI 结束后,我们要面对的是怎样一种局面: 20 | 21 | - 物理内存状态:OpenSBI 代码放在 `[0x80000000,0x80200000)` 中,内核代码放在以 `0x80200000` 开头的一块连续物理内存中。 22 | - CPU 状态:处于 S Mode ,寄存器 `satp` 的 $$\text{MODE}$$ 被设置为 `Bare` ,即无论取指还是访存我们通过物理地址直接访问物理内存。 $$\text{PC}=0\text{x}80200000$$ 指向内核的第一条指令。栈顶地址 $$\text{SP}$$ 处在 OpenSBI 代码内。 23 | - 内核代码:由于改动了链接脚本的起始地址,认为自己处在以虚拟地址 ``0xffffffffc0200000`` 开头的一段连续虚拟地址空间中。 24 | 25 | 26 | 接下来,我们在入口点 ``entry64.asm`` 中所要做的事情是:将 $$\text{SP}$$ 寄存器指向的栈空间从 OpenSBI 某处移到我们的内核定义的某块内存区域中,使得我们可以完全支配启动栈;同时需要跳转到函数 `rust_main` 中。 27 | 28 | 在之前的实现中,我们已经在 [`src/boot/entry64.asm`](https://github.com/rcore-os/rCore_tutorial/blob/ch5-pa2/os/src/boot/entry64.asm#L19) 中自己分配了一块 $$16\text{KiB}$$ 的内存用来做启动栈: 29 | 30 | ```riscv 31 | # src/boot/entry64.asm 32 | 33 | .section .text.entry 34 | .globl _start 35 | _start: 36 | la sp, bootstacktop 37 | call rust_main 38 | 39 | .section .bss.stack 40 | .align 12 41 | .global bootstack 42 | bootstack: 43 | .space 4096 * 4 44 | .global bootstacktop 45 | bootstacktop: 46 | ``` 47 | 48 | 符号 `bootstacktop` 就是我们需要的栈顶地址!同样符号 `rust_main` 也代表了我们要跳转到的地址。直接将 `bootstacktop` 的值给到 $$\text{SP}$$, 再跳转到 `rust_main` 就行了。看起来原来的代码仍然能用啊? 49 | 50 | 问题在于,由于我们修改了链接脚本的起始地址,编译器和链接器认为内核开头地址为 ``0xffffffffc0200000``,因此这两个符号会被翻译成比这个开头地址还要高的绝对虚拟地址。而我们的 CPU 目前处于 `Bare` 模式,会将地址都当成物理地址处理。这样,我们跳转到 `rust_main` 就会跳转到 `0xffffffffc0200000+` 的一个物理地址,物理地址都没有这么多位!这显然是会出问题的。 51 | 52 | 于是,我们只能想办法利用刚学的页表知识,帮内核将需要的虚拟地址空间构造出来。 53 | 54 | 观察可以发现,同样的一条指令,其在虚拟内存空间中的虚拟地址与其在物理内存中的物理地址有着一个固定的**偏移量**。比如内核的第一条指令,虚拟地址为 `0xffffffffc0200000` ,物理地址为 `0x80200000` ,因此,我们只要将虚拟地址减去 `0xffffffff40000000` ,就得到了物理地址。 55 | 56 | 使用上一节页表的知识,我们只需要做到当访问内核里面的一个虚拟地址 $$\text{va}$$ 时,我们知道 $$\text{va}$$ 处的代码或数据放在物理地址为 `pa = va - 0xffffffff40000000` 处的物理内存中,我们真正所要做的是要让 CPU 去访问 $$\text{pa} $$。因此,我们要通过恰当构造页表,来对于内核所属的虚拟地址,实现这种 $$\text{va}\rightarrow\text{pa}$$ 的映射。 57 | 58 | 我们先使用一种最简单的页表构造方法,还记得上一节中所讲的大页吗?那时我们提到,将一个三级页表项的标志位 $$\text{R,W,X}$$ 不设为全 $$0$$ ,可以将它变为一个叶子,从而获得大小为 $$1\text{GiB}$$ 的一个大页。 59 | 60 | 我们假定内核大小不超过 $$1\text{GiB}$$,因此通过一个大页,将虚拟地址区间 `[0xffffffffc0000000,0xffffffffffffffff]` 映射到物理地址区间 `[0x80000000,0xc0000000)`,而我们只需要分配一页内存用来存放三级页表,并将其最后一个页表项(这个虚拟地址区间明显对应三级页表的最后一个页表项),进行适当设置即可。 61 | 62 | 因此,[实现的汇编代码](https://github.com/rcore-os/rCore_tutorial/blob/ch5-pa2/os/src/boot/entry64.asm)为: 63 | 64 | ```riscv 65 | # src/boot/entry64.asm 66 | 67 | .section .text.entry 68 | .globl _start 69 | _start: 70 | # t0 := 三级页表的虚拟地址 71 | lui t0, %hi(boot_page_table_sv39) 72 | # t1 := 0xffffffff40000000 即虚实映射偏移量 73 | li t1, 0xffffffffc0000000 - 0x80000000 74 | # t0 减去虚实映射偏移量 0xffffffff40000000,变为三级页表的物理地址 75 | sub t0, t0, t1 76 | # t0 >>= 12,变为三级页表的物理页号 77 | srli t0, t0, 12 78 | 79 | # t1 := 8 << 60,设置 satp 的 MODE 字段为 Sv39 80 | li t1, 8 << 60 81 | # 将刚才计算出的预设三级页表物理页号附加到 satp 中 82 | or t0, t0, t1 83 | # 将算出的 t0(即新的MODE|页表基址物理页号) 覆盖到 satp 中 84 | csrw satp, t0 85 | # 使用 sfence.vma 指令刷新 TLB 86 | sfence.vma 87 | # 从此,我们给内核搭建出了一个完美的虚拟内存空间! 88 | 89 | # 我们在虚拟内存空间中:随意将 sp 设置为虚拟地址! 90 | lui sp, %hi(bootstacktop) 91 | 92 | # 我们在虚拟内存空间中:随意跳转到虚拟地址! 93 | # 跳转到 rust_main 94 | lui t0, %hi(rust_main) 95 | addi t0, t0, %lo(rust_main) 96 | jr t0 97 | 98 | .section .data 99 | # 由于我们要把这个页表放到一个页里面,因此必须 12 位对齐 100 | .align 12 101 | # 分配 4KiB 内存给预设的三级页表 102 | boot_page_table_sv39: 103 | # 0xffffffff_c0000000 map to 0x80000000 (1G) 104 | # 前 511 个页表项均设置为 0 ,因此 V=0 ,意味着是空的(unmapped) 105 | .zero 8 * 511 106 | # 设置最后一个页表项,PPN=0x80000,标志位 VRWXAD 均为 1 107 | .quad (0x80000 << 10) | 0xcf # VRWXAD 108 | ``` 109 | 110 | 总结一下,要进入虚拟内存访问方式,需要如下步骤: 111 | 112 | 1. 分配页表所在内存空间并初始化页表; 113 | 2. 设置好页基址寄存器(指向页表起始地址); 114 | 3. 刷新 TLB。 115 | 116 | 到现在为止我们终于理解了自己是如何做起白日梦——进入那看似虚无缥缈的虚拟内存空间的。 117 | 118 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch5-pa2 119 | -------------------------------------------------------------------------------- /chapter5/part3.md: -------------------------------------------------------------------------------- 1 | ## 内核重映射 2 | 3 | 上一节中,我们虽然构造了一个简单映射使得内核能够运行在虚拟空间上,但是这个映射是比较粗糙的。 4 | 5 | 我们知道一个程序通常含有下面几段: 6 | 7 | - $$\text{.text}$$ 段:存放代码,需要是可读、可执行的,但不可写。 8 | - $$\text{.rodata}$$ 段:存放只读数据,顾名思义,需要可读,但不可写亦不可执行。 9 | - $$\text{.data}$$ 段:存放经过初始化的数据,需要可读、可写。 10 | - $$\text{.bss}$$ 段:存放经过零初始化的数据,需要可读、可写。与 $$\text{.data}$$ 段的区别在于由于我们知道它被零初始化,因此在可执行文件中可以只存放该段的开头地址和大小而不用存全为 $$0$$ 的数据。在执行时由操作系统进行处理。 11 | 12 | 我们看到各个段之间的访问权限是不同的。在现在的映射下,我们甚至可以修改内核 $$\text{.text}$$ 段的代码!因为我们通过一个标志位 $$\text{W}=1$$ 的页表项完成映射。而这会带来一个埋藏极深的隐患。 13 | 14 | 因此,我们考虑对这些段分别进行重映射,使得他们的访问权限被正确设置。虽然还是每个段都还是映射以同样的偏移量映射到相同的地方,但实现需要更加精细。 15 | 16 | ### 新建页表并插入映射 17 | 18 | 我们决定放弃现有的页表建一个新的页表,在那里完成重映射。一个空的页表唯一需求的是一个三级页表作为根,我们要为这个三级页表申请一个物理页帧,并把三级页表放在那里。我们正好实现了物理页帧的分配 `alloc_frame()` ! 19 | 20 | 一个空空如也的页表还不够。我们现在要插入映射 $$\text{VPN}\rightarrow\text{PPN}$$ ,这次我们真的要以一页 ($$4\text{KiB}$$) 为单位而不是以一大页 ($$1\text{GiB}$$) 为单位构造映射了。那就走流程,一级一级来。首先我们在这个三级页表中根据 $$\text{VPN}[2]$$ 索引三级页表项,发现其 $$\text{V}=0$$ ,说明它指向一个空页表,然后理所当然是新建一个二级页表,申请一个物理页帧放置它,然后修改三级页表项的物理页号字段为这个二级页表所在的物理页号,然后进入这个二级页表进入下一级处理... 21 | 22 | 等等!我们好像忽略了什么东西。我们对着三级页表又读又写,然而自始至终我们只知道它所在的物理页号即物理地址! 23 | 24 | ### 如何读写一个页表 25 | 26 | 在我们的程序中,能够直接访问的只有虚拟地址。如果想要访问物理地址的话,我们需要有一个虚拟地址映射到该物理地址,然后我们才能通过访问这个虚拟地址来访问物理地址。那么我们现在做到这一点了吗? 27 | 28 | 幸运的是我们确实做到了。我们通过一个大页映射了 $$1\text{GiB}$$ 的内存,包括了所有可用的物理地址。因此,我们如果想访问一个物理地址的话,我们知道这个物理地址加上偏移量得到的虚拟地址已经被映射到这个物理地址了,因此可以使用这个虚拟地址访问该物理地址。 29 | 30 | 为了让我们能够一直如此幸运,我们得让新的映射也具有这种访问物理内存的能力。在这里,我们使用一种最简单的方法,即**映射整块物理内存**。即选择一段虚拟内存区间与整块物理内存进行映射。这样整块物理内存都可以用这段区间内的虚拟地址来访问了。 31 | 32 | 我们使用一种较为精确的方法,即: 33 | 34 | 整块物理内存指的是“物理内存探测与管理”一节中所提到的我们能够自由分配的那些物理内存。我们用和内核各段同样的偏移量来进行映射。但这和内核各段相比,出发点是不同的: 35 | 36 | - 内核各段:为了实现在程序中使用虚拟地址访问虚拟内存的效果而构造映射; 37 | - 物理内存映射:为了通过物理地址访问物理内存,但是绕不开页表映射机制,因此只能通过构造映射使用虚拟地址来访问物理内存。 38 | 39 | 不过从结果上来看,它和内核中的各段没有什么区别,甚至和 $$\text{.data}$$ 段相同,都是将许可要求设置为可读、可写即可。 40 | 41 | > **[danger] 内存消耗问题** 42 | > 43 | > 在一个新页表中,新建一个映射我们要分配三级页表、二级页表、一级页表各一个物理页帧。而现在我们基本上要给整个物理内存建立映射,且不使用大页,也就是说物理内存中每有一个 $$4\text{KiB}$$ 的页,我们都要建立一个映射,要分配三个物理页帧。那岂不是我们还没把整个物理内存都建立映射,所有物理页帧就都耗尽了? 44 | > 45 | > 事实上这个问题是不存在的。关键点在于,我们要映射的是一段**连续**的虚拟内存**区间**,因此,每连续建立 $$512$$ 页的映射才会新建一个一级页表,每连续建立 $$512^2$$ 页的映射才会新建一个二级页表,而三级页表最多只新建一个。因此这样进行映射花费的总物理页帧数约占物理内存中物理页帧总数的约 $$\frac{1}{512}\simeq 0.2\%$$ 。 46 | 47 | 这样想来,无论切换页表前后,我们都可以使用一个固定的偏移量来通过虚拟地址访问物理内存,此问题得到了解决。 48 | 49 | 现在我们明白了为何要进行内核重映射,并讨论了一些细节。我们将在下一节进行具体实现。 50 | -------------------------------------------------------------------------------- /chapter5/part6.md: -------------------------------------------------------------------------------- 1 | ## 内核重映射实现之三:完结 2 | 3 | - [代码][code] 4 | 5 | 在内存模块初始化时,我们新建一个精细映射的 `MemorySet` 并切换过去供内核使用。 6 | 7 | ```rust 8 | // src/memory/mod.rs 9 | ...... 10 | pub fn init(l: usize, r: usize) { 11 | FRAME_ALLOCATOR.lock().init(l, r); 12 | init_heap(); 13 | // 内核重映射 14 | kernel_remap(); 15 | println!("++++ setup memory! ++++"); 16 | } 17 | pub fn kernel_remap() { 18 | let mut memory_set = MemorySet::new(); 19 | extern "C" { 20 | fn bootstack(); //定义在src/boot/entry64.asm 21 | fn bootstacktop(); //定义在src/boot/entry64.asm 22 | } 23 | // 將启动栈 push 进来 24 | memory_set.push( 25 | bootstack as usize, 26 | bootstacktop as usize, 27 | MemoryAttr::new(), 28 | Linear::new(PHYSICAL_MEMORY_OFFSET), 29 | ); 30 | unsafe { 31 | memory_set.activate(); 32 | } 33 | } 34 | ``` 35 | 36 | 这里要注意的是,我们不要忘了将启动栈加入实际可用的虚拟内存空间。因为我们现在仍处于启动过程中,因此离不开启动栈。 37 | 38 | 主函数里则是: 39 | 40 | ```rust 41 | // src/init.rs 42 | 43 | #[no_mangle] 44 | pub extern "C" fn rust_main() -> ! { 45 | crate::interrupt::init(); 46 | 47 | extern "C" { 48 | fn end(); 49 | } 50 | crate::memory::init( 51 | ((end as usize - KERNEL_BEGIN_VADDR + KERNEL_BEGIN_PADDR) >> 12) + 1, 52 | PHYSICAL_MEMORY_END >> 12 53 | ); 54 | 55 | crate::timer::init(); 56 | loop {} 57 | } 58 | ``` 59 | 60 | 运行一下,可以发现屏幕上仍在整齐的输出着 `100 ticks`! 61 | 我们回过头来验证一下关于读、写、执行的权限是否被正确处理了。 62 | 写这么几个测试函数: 63 | 64 | ```rust 65 | // 只读权限,却要写入 66 | fn write_readonly_test() { 67 | extern "C" { 68 | fn srodata(); 69 | } 70 | unsafe { 71 | let ptr = srodata as usize as *mut u8; 72 | *ptr = 0xab; 73 | } 74 | } 75 | 76 | // 不允许执行,非要执行 77 | fn execute_unexecutable_test() { 78 | extern "C" { 79 | fn sbss(); 80 | } 81 | unsafe { 82 | asm!("jr $0" :: "r"(sbss as usize) :: "volatile"); 83 | } 84 | } 85 | 86 | // 找不到页表项 87 | fn read_invalid_test() { 88 | println!("{}", unsafe { *(0x12345678 as usize as *const u8) }); 89 | } 90 | ``` 91 | 92 | 在 `memory::init` 后面调用任一个测试函数,都会发现内核 `panic` 并输出: 93 | 94 | > **[danger]** undefined trap 95 | > 96 | > ```rust 97 | > panicked at 'undefined trap!', src/interrupt.rs:40:14 98 | > ``` 99 | 100 | 这说明内核意识到出了某些问题进入了中断,但我们并没有加以解决。 101 | 我们在中断处理里面加上对应的处理方案: 102 | 103 | ```rust 104 | // src/interrupt.rs 105 | 106 | #[no_mangle] 107 | pub fn rust_trap(tf: &mut TrapFrame) { 108 | match tf.scause.cause() { 109 | Trap::Exception(Exception::Breakpoint) => breakpoint(&mut tf.sepc), 110 | Trap::Interrupt(Interrupt::SupervisorTimer) => super_timer(), 111 | Trap::Exception(Exception::InstructionPageFault) => page_fault(tf), 112 | Trap::Exception(Exception::LoadPageFault) => page_fault(tf), 113 | Trap::Exception(Exception::StorePageFault) => page_fault(tf), 114 | _ => panic!("undefined trap!") 115 | } 116 | } 117 | 118 | fn page_fault(tf: &mut TrapFrame) { 119 | println!("{:?} va = {:#x} instruction = {:#x}", tf.scause.cause(), tf.stval, tf.sepc); 120 | panic!("page fault!"); 121 | } 122 | ``` 123 | 124 | 我们再依次运行三个测试,会得到结果为: 125 | 126 | > **[success] 权限测试** 127 | > 128 | > ```rust 129 | > // read_invalid_test Result 130 | > Exception(LoadPageFault) va = 0x12345678 instruction = 0xffffffffc020866c 131 | > panicked at 'page fault!', src/interrupt.rs:65:5 132 | > // execute_unexecutable_test Result 133 | > Exception(InstructionPageFault) va = 0xffffffffc021d000 instruction = 0xffffffffc021d000 134 | > panicked at 'page fault!', src/interrupt.rs:65:5 135 | > // write_readonly_test Result 136 | > Exception(StorePageFault) va = 0xffffffffc0213000 instruction = 0xffffffffc020527e 137 | > panicked at 'page fault!', src/interrupt.rs:65:5 138 | > ``` 139 | 140 | 从中我们可以清楚的看出内核成功的找到了错误的原因,内核各段被成功的设置了不同的权限。我们达到了内核重映射的目的!目前的代码能在[这里][code]找到。 141 | 142 | > **[info] 如何找到产生错误的源码位置** 143 | > 144 | > 在上面的三个测试中,虽然可以看到出错的指令的虚拟地址,但还是不能很直接地在源码级对应到出错的地方。这里有两个方法可以做到源码级错误定位,一个是 Qemu+GDB 的动态调试方法(这里不具体讲解),另外一个是通过`addr2line`工具来帮助我们根据指令的虚拟地址来做到源码的位置,具体方法如下: 145 | > 146 | > ```bash 147 | > #先找到编译初的ELF格式的OS 148 | > $ cd rCore_tutorial/os/target/riscv64imac-unknown-none-elf/debug 149 | > $ file os # 这个就是我们要分析的目标 150 | > os: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, with debug_info, not stripped 151 | > $ riscv64-unknown-elf-addr2line -e os 0xffffffffc020527e 152 | > rCore_tutorial/os/src/init.rs:35 153 | > #查看rCore_tutorial/os/src/init.rs第35行的位置,可以看到 154 | > 29: fn write_readonly_test() { 155 | > 30: extern "C" { 156 | > 31: fn srodata(); 157 | > 32: } 158 | > 33: unsafe { 159 | > 34: let ptr = srodata as usize as *mut u8; 160 | > 35: *ptr = 0xab; 161 | > 36: } 162 | > 37: } 163 | > #可以轻松定位到出错的语句``*ptr = 0xab;`` 164 | > ``` 165 | 166 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch5-pa6 167 | -------------------------------------------------------------------------------- /chapter5/part7.md: -------------------------------------------------------------------------------- 1 | ## 总结与展望 2 | 3 | 本章我们区分了物理内存和虚拟内存,并利用页表在他们中间建立联系。我们分析了内核初始映射的代码,并希望通过更加精细的映射使各段具有不同的权限。 4 | 5 | 我们使用 `MemorySet -> MemoryArea -> MemoryHandler` ,来以不同的方式调用页表 `PageTableImpl` 的接口,使得各段的映射方式不同。 6 | 7 | `MemorySet` 是内核给程序分配的虚拟内存空间,现在它只是给自己分配了一个,之后还会给其他用户程序分配。 8 | -------------------------------------------------------------------------------- /chapter6/introduction.md: -------------------------------------------------------------------------------- 1 | # 第六章:内核线程 2 | 3 | > **[info]线程与进程** 4 | > 5 | > 从**源代码**经过编译器一系列处理(编译、链接、优化等)得到的可执行文件,我们称为**程序**。 6 | > 7 | > 而简单地说,**进程 (Process) **就是使用正在运行并使用资源的程序,与放在磁盘中一动不动的程序不同,首先,进程得到了操作系统的**资源**支持:程序的代码、数据段被加载到**内存**中,程序所需的虚拟内存空间被真正构建出来。同时操作系统还给进程分配了程序所要求的各种**其他资源**,最典型的当属文件、网络等。 8 | > 9 | > 然而如果仅此而已,进程还尚未体现出其“**正在运行**”的特性。而正在运行意味着 **CPU** 要去执行程序代码段中的代码,为了能够进行函数调用,我们还需要一点额外的内存:即**栈**(stack)。如果要进行[动态内存分配](../chapter4/part2.md),我们还需要另外一些额外的内容:即**堆**(heap)。 10 | > 11 | > 出于种种目的,我们通常将“正在运行”的特性从进程中剥离出来,这样的一个借助 **CPU + 栈**的执行流,我们称之为**线程 (Thread)** 。一个进程可以有多个线程,也可以如传统进程一样只有一个线程。 12 | > 13 | > 这样,进程虽然仍是代表一个正在运行的程序,但是其主要功能是作为**资源管理的单位**,管理内存、文件、网络等资源。而一个进程的多个线程则共享这些资源,专注于执行,从而作为**执行流调度的单位**。 14 | > 15 | > 现代的**处理器 (Processor)**,往往都具有多个核(**核、Core** 其实都是一个概念),从而可以在同一时间运行多个线程(可能来自于同个进程,也可能不同)。因此基于多线程的程序,则可以在占据同样资源的情况下,充分利用多核来同时执行更多的指令,宏观上提高整个程序的运行速度。 16 | 17 | 在本教程中,出于简化,进程的概念被弱化。我们主要讨论线程以及基于线程进行执行流调度。 18 | 本章你将会学到: 19 | 20 | - 内核线程的概念 21 | - 线程执行的状态表示与保存 22 | - 线程切换 23 | -------------------------------------------------------------------------------- /chapter6/part1.md: -------------------------------------------------------------------------------- 1 | ## 线程状态与保存 2 | 3 | - [代码][code] 4 | 5 | 如果将整个运行中的内核看作一个**内核进程**,那么一个**内核线程**只负责内核进程中**执行**的部分。虽然我们之前从未提到过内核线程的概念,但是在我们设置完启动栈,并跳转到 `rust_main` 之后,我们的第一个内核线程——**内核启动线程**就已经在运行了! 6 | 7 | ### 线程的状态 8 | 9 | 想想一个线程何以区别于其他线程。由于线程是负责“执行”,因此我们要通过线程当前的**执行状态(也称线程上下文,线程状态,Context)**来描述线程的当前执行情况(也称执行现场)。也就包括: 10 | 11 | - CPU 各寄存器的状态: 12 | 13 | 简单想想,我们会特别关心程序运行到了哪里:即 $$\text{PC}$$ ;还有栈顶的位置:即 $$\text{SP}$$ 。 14 | 15 | 当然,其他所有的寄存器都是一样重要的。 16 | 17 | - 线程的栈里面的内容: 18 | 19 | 首先,我们之前提到过,寄存器和栈支持了函数调用与参数传递机制; 20 | 21 | 其次,我们在函数中用到的局部变量其实都是分配在栈上的。它们在进入函数时被压到栈上,在从函数返回时被回收。而事实上,这些变量的局部性不只限于这个函数,还包括执行函数代码的线程。 22 | 23 | 这是因为,同个进程的多个线程使用的是不同的栈,因此分配在一个线程的栈上的那些变量,都只有这个线程自身会访问。(通常,虽然理论上一个线程可以访问其他线程的栈,但由于并无什么意义,我们不会这样做) 24 | 25 | 与之相比,放在程序的数据段中的全局变量(或称静态变量)则是所有线程都能够访问。数据段包括只读数据段 $$\text{.rodata}$$ ,可读可写的 $$\text{.data,.bss}$$ 。在线程访问这些数据时一定要多加小心,因为你并不清楚是不是有其他线程同时也在访问,这会带来一系列问题。 26 | 27 | ### 线程状态的保存 28 | 29 | 一个线程不会总是占据 CPU 资源,因此在执行过程中,它可能会被切换出去;之后的某个时刻,又从其他线程切换回来,为了线程能够像我们从未将它切换出去过一样继续正常执行,我们要保证切换前后**线程的执行状态不变**。 30 | 31 | 其他线程不会修改当前线程的栈,因此栈上的内容保持不变;但是 CPU 跑去执行其他代码去了,CPU 各寄存器的状态势必发生变化,所以我们要将 CPU 当前的状态(各寄存器的值)保存在当前线程的栈上,以备日后恢复。但是我们也并不需要保存所有的寄存器,事实上只需保存: 32 | 33 | - 返回地址 $$\text{ra}$$ 34 | - 页表寄存器 $$\text{satp}$$(考虑到属于同一进程的线程间共享一个页表,这一步不是必须的) 35 | - 被调用者保存寄存器 $$\text{s}_0\sim\text{s}_{11}$$ 36 | 37 | 这与线程切换的实现方式有关,我们到时再进行说明。 38 | 39 | ### 线程的实现 40 | 41 | 首先是线程在栈上保存的内容: 42 | 43 | ```rust 44 | // src/context.rs 45 | 46 | // 回忆属性 #[repr(C)] 是为了让 rust 编译器以 C 语言的方式 47 | // 按照字段的声明顺序分配内存 48 | // 从而可以利用汇编代码正确地访问它们 49 | #[repr(C)] 50 | pub struct ContextContent { 51 | pub ra: usize, 52 | satp: usize, 53 | s: [usize; 12], 54 | tf: TrapFrame, 55 | } 56 | ``` 57 | 58 | 前三个分别对应 $$\text{ra,satp,s}_0\sim\text{s}_{11}$$,那最后为什么还有个中断帧呢?实际上,我们通过中断帧,来利用中断机制的一部分来进行线程初始化。我们马上就会看到究竟是怎么回事。 59 | 60 | ```rust 61 | // src/context.rs 62 | 63 | #[repr(C)] 64 | pub struct Context { 65 | pub content_addr: usize, 66 | } 67 | ``` 68 | 69 | 对于一个被切换出去的线程,为了能够有朝一日将其恢复回来,由于它的状态已经保存在它自己的栈上,我们唯一关心的就是其栈顶的地址。我们用结构体 `Context` 来描述被切换出去的线程的状态。 70 | 71 | 随后开一个新的 `process` mod ,在里面定义线程结构体 `Thread` 。 72 | 73 | ```rust 74 | // src/process/structs.rs 75 | pub struct Thread { 76 | // 线程的状态 77 | pub context: Context, 78 | // 线程的栈 79 | pub kstack: KernelStack, 80 | } 81 | ``` 82 | 83 | `Thread`里面用到了内核栈 `KernelStack` : 84 | 85 | ```rust 86 | // src/consts.rs 87 | pub const KERNEL_STACK_SIZE: usize = 0x80000; 88 | 89 | // src/process/structs.rs 90 | pub struct KernelStack(usize); 91 | impl KernelStack { 92 | pub fn new() -> Self { 93 | let bottom = unsafe { 94 | alloc(Layout::from_size_align(KERNEL_STACK_SIZE, KERNEL_STACK_SIZE).unwrap()) as usize 95 | }; 96 | KernelStack(bottom) 97 | } 98 | } 99 | impl Drop for KernelStack { 100 | fn drop(&mut self) { 101 | ...... 102 | dealloc( 103 | self.0 as _, 104 | Layout::from_size_align(KERNEL_STACK_SIZE, KERNEL_STACK_SIZE).unwrap(), 105 | ); 106 | ...... 107 | } 108 | } 109 | ``` 110 | 111 | 在使用 `KernelStack::new` 新建一个内核栈时,我们使用第四章所讲的动态内存分配,从堆上分配一块虚拟内存作为内核栈。然而 `KernelStack` 本身只保存这块内存的起始地址。其原因在于当线程生命周期结束后,作为 `Thread` 一部分的 `KernelStack` 实例被回收时,由于我们实现了 `Drop` Trait ,该实例会调用 `drop` 函数将创建时分配的那块虚拟内存回收,从而避免内存溢出。当然。如果是空的栈就不必回收了。 112 | 113 | 因此,我们是出于自动回收内核栈的考虑将 `KernelStack` 放在 `Thread` 中。另外,需要注意**压栈操作导致栈指针是从高地址向低地址变化;出栈操作则相反**。 114 | 115 | 下一节,我们来看如何进行线程切换。 116 | 117 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch6-pa4 118 | -------------------------------------------------------------------------------- /chapter6/part2.md: -------------------------------------------------------------------------------- 1 | ## 线程切换 2 | 3 | - [代码][code] 4 | 5 | 我们要用这个函数完成线程切换: 6 | 7 | ```rust 8 | // src/process/structs.rs 9 | impl Thread { 10 | pub fn switch_to(&mut self, target: &mut Thread) { 11 | unsafe { self.context.switch(&mut target.context); } 12 | } 13 | } 14 | ``` 15 | 16 | 通过调用 `switch_to` 函数将当前正在执行的线程切换为另一个线程。实现方法是两个 `Context` 的切换。 17 | 18 | ```rust 19 | // src/lib.rs 20 | 21 | #![feature(naked_functions)] 22 | 23 | // src/context.rs 24 | 25 | impl Context { 26 | #[naked] 27 | #[inline(never)] 28 | pub unsafe extern "C" fn switch(&mut self, target: &mut Context) { 29 | asm!(include_str!("process/switch.asm") :::: "volatile"); 30 | } 31 | } 32 | ``` 33 | 34 | 这里需要对两个宏进行一下说明: 35 | 36 | - `#[naked]` ,告诉 rust 编译器不要给这个函数插入任何开场白 (prologue) 以及结语 (epilogue) 。 37 | 我们知道,一般情况下根据 [函数调用约定(calling convention)](https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf) ,编译器会自动在函数开头为我们插入设置寄存器、栈(比如保存 callee-save 寄存器,分配局部变量等工作)的代码作为开场白,结语则是将开场白造成的影响恢复。 38 | 39 | - `#[inline(never)]` ,告诉 rust 编译器永远不要将该函数**内联**。 40 | 41 | 内联 (inline) 是指编译器对于一个函数调用,直接将函数体内的代码复制到调用函数的位置。而非像经典的函数调用那样,先跳转到函数入口,函数体结束后再返回。这样做的优点在于避免了跳转;但却加大了代码容量。 42 | 43 | 有时编译器在优化中会将未显式声明为内联的函数优化为内联的。但是我们这里要用到调用-返回机制,因此告诉编译器不能将这个函数内联。 44 | 45 | 这个函数我们用汇编代码 `src/process/switch.asm` 实现。 46 | 47 | 由于[函数调用约定(calling convention)](https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf) ,我们知道的是寄存器 $$a_0,a_1$$ 分别保存“当前线程栈顶地址”所在的地址,以及“要切换到的线程栈顶地址”所在的地址。 48 | 49 | > **[info]RISC-V 函数调用约定(Calling Convention)** 50 | > 51 | > | 寄存器 | ABI 名称 | 描述 | Saver | 52 | > | ------ | -------- | -------------------------------- | ------ | 53 | > | x0 | zero | Hard-wired zero | ------ | 54 | > | x1 | ra | Return address | Caller | 55 | > | x2 | sp | Stack pointer | Callee | 56 | > | x3 | gp | Global pointer | ------ | 57 | > | x4 | tp | Thread pointer | ------ | 58 | > | x5-7 | t0-2 | Temporaries | Caller | 59 | > | x8 | s0/fp | Saved register/frame pointer | Callee | 60 | > | x9 | s1 | Saved register | Callee | 61 | > | x10-11 | a0-1 | Function arguments/return values | Caller | 62 | > | x12-17 | a2-7 | Function arguments | Caller | 63 | > | x18-27 | s2-11 | Saved registers | Callee | 64 | > | x28-31 | t3-6 | Temporaries | Caller | 65 | > 66 | > 我们切换进程时需要保存 Callee-saved registers 以及`ra`。 67 | 68 | 所以要做的事情是: 69 | 70 | 1. 将当前的 CPU 状态保存到当前栈上,并更新“当前线程栈顶地址”,通过写入寄存器 $$a_0$$ 值所指向的内存; 71 | 2. 读取寄存器 $$a_1$$ 值所指向的内存获取“要切换到的线程栈顶地址”,切换栈,并从栈上恢复 CPU 状态 72 | 73 | ```riscv 74 | # src/process/switch.asm 75 | 76 | .equ XLENB, 8 77 | .macro Load a1, a2 78 | ld \a1, \a2*XLENB(sp) 79 | .endm 80 | .macro Store a1, a2 81 | sd \a1, \a2*XLENB(sp) 82 | .endm 83 | # 入栈,即在当前栈上分配空间保存当前 CPU 状态 84 | addi sp, sp, -14*XLENB 85 | # 更新“当前线程栈顶地址” 86 | sd sp, 0(a0) 87 | # 依次保存各寄存器的值 88 | Store ra, 0 89 | Store s0, 2 90 | ...... 91 | Store s11, 13 92 | csrr s11, satp 93 | Store s11, 1 94 | # 当前线程状态保存完毕 95 | 96 | # 准备恢复到“要切换到的线程” 97 | # 读取“要切换到的线程栈顶地址”,并直接换栈 98 | ld sp, 0(a1) 99 | # 依序恢复各寄存器 100 | Load s11, 1 101 | # 恢复页表寄存器 satp,别忘了使用屏障指令 sfence.vma 刷新 TLB 102 | csrw satp, s11 103 | sfence.vma 104 | Load ra, 0 105 | Load s0, 2 106 | ...... 107 | Load s11, 13 108 | # 各寄存器均被恢复,恢复过程结束 109 | # “要切换到的线程” 变成了 “当前线程” 110 | # 出栈,即在当前栈上回收用来保存线程状态的内存 111 | addi sp, sp, 14*XLENB 112 | 113 | # 将“当前线程的栈顶地址”修改为 0 114 | # 这并不会修改当前的栈 115 | # 事实上这个值只有当对应的线程暂停(sleep)时才有效 116 | # 防止别人企图 switch 到它,把它的栈进行修改 117 | sd zero, 0(a1) 118 | ret 119 | ``` 120 | 121 | 这里需要说明的是: 122 | 123 | 1. 我们是如何利用函数调用及返回机制的 124 | 125 | 我们说为了线程能够切换回来,我们要保证切换前后线程状态不变。这并不完全正确,事实上程序计数器 $$\text{PC}$$ 发生了变化:在切换回来之后我们需要从 `switch_to` 返回之后的第一条指令继续执行! 126 | 127 | 因此可以较为巧妙地利用函数调用及返回机制:在调用 `switch_to` 函数之前编译器会帮我们将 $$\text{ra}$$ 寄存器的值改为 `switch_to` 返回后第一条指令的地址。所以我们恢复 $$\text{ra}$$ ,再调用 $$\text{ret: pc}\leftarrow\text{ra}$$ ,这样会跳转到返回之后的第一条指令。 128 | 129 | 2. 为何不必保存全部寄存器 130 | 131 | 因此这是一个函数调用,由于[函数调用约定(calling convention)](https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf) ,编译器会自动生成代码在调用前后帮我们保存、恢复所有的 caller-saved 寄存器。于是乎我们需要手动保存所有的 callee-saved 寄存器 $$\text{s}_0\sim\text{s}_{11}$$ 。这样所有的寄存器都被保存了。 132 | 133 | 下面一节我们来研究如何进行线程初始化。 134 | 135 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch6-pa4 136 | -------------------------------------------------------------------------------- /chapter6/part3.md: -------------------------------------------------------------------------------- 1 | ## 内核线程初始化 2 | 3 | - [代码][code] 4 | 5 | 回忆一下我们如何进行启动线程的初始化?无非两步:设置栈顶地址、跳转到内核入口地址。从而变为启动线程的初始状态,并准备开始运行。 6 | 7 | 其他线程的初始化也差不多。事实上我们要构造一个停止的线程状态,使得一旦其他的进程切换到它,就立刻变为我们想要的该线程的初始状态,并可以往下运行。 8 | 9 | ### 构造线程状态信息 10 | 11 | 首先是要新建一个内核栈,然后在栈上压入我们精心构造的线程状态信息。 12 | 13 | ```rust 14 | // src/context.rs 15 | impl ContextContent { 16 | // 为一个新内核线程构造栈上的初始状态信息 17 | // 其入口点地址为 entry ,其内核栈栈顶地址为 kstack_top ,其页表为 satp 18 | fn new_kernel_thread( 19 | entry: usize, 20 | kstack_top: usize, 21 | satp: usize, 22 | ) -> ContextContent { 23 | 24 | let mut content = ContextContent { 25 | ra: __trapret as usize, 26 | satp, 27 | s: [0; 12], 28 | tf: { 29 | let mut tf: TrapFrame = unsafe { zeroed() }; 30 | tf.x[2] = kstack_top; 31 | tf.sepc = entry; 32 | tf.sstatus = sstatus::read(); 33 | tf.sstatus.set_spp(sstatus::SPP::Supervisor); 34 | tf.sstatus.set_spie(true); 35 | tf.sstatus.set_sie(false); 36 | tf 37 | } 38 | }; 39 | content 40 | } 41 | } 42 | ``` 43 | 44 | 首先 $$\text{satp}$$ 在 `switch_to` 中被正确设置。这里 $$\text{ra}$$ 的值为 `__trapret` ,因此当 `switch_to` 使用 `ret` 退出后会跳转到 `__trapret` 。而它是我们在中断处理返回时用来[恢复中断上下文](../chapter6/part4.md)的!实际上这里用 `__trapret` 仅仅是利用它来设置寄存器的初始值,而不是说它和中断有什么关系。 45 | 46 | 从 `switch_to` 返回之后,原栈顶的 $$\text{ra,satp,s}_0\sim\text{s}_{11}$$ 被回收掉了。因此现在栈顶上恰好保存了一个中断帧。那么我们从中断返回的视角来看待:栈顶地址会被正确设置为 `kstack_top` ,由于将中断帧的 $$\text{sepc}$$ 设置为线程入口点,因此中断返回后会通过 `sret` 跳转到线程入口点。 47 | 48 | 注意中断帧中 $$\text{sstatus}$$ 的设置: 49 | 50 | - 将 $$\text{SPP}$$ 设置为 Supervisor ,使得使用 `sret` 返回后 CPU 的特权级为 S Mode 。 51 | - 设置 $$\text{SIE,SPIE}$$,这里的作用是 `sret` 返回后,在内核线程中使能异步中断。详情请参考[RISC-V 特权指令集文档](https://riscv.org/specifications/privileged-isa/)。 52 | 53 | 我们还希望能够给线程传入参数,这只需要修改中断帧中的$$x_{10},x_{11},...,x_{17} $$(即参数$$a_0,a_1,...,a_7$$ )即可,`__trapret` 函数可以协助完成参数传递。 54 | 55 | ```rust 56 | // src/context.rs 57 | 58 | impl Context { 59 | pub unsafe fn new_kernel_thread( 60 | entry: usize, 61 | kstack_top: usize, 62 | satp: usize 63 | ) -> Context { 64 | ContextContent::new_kernel_thread(entry, kstack_top, satp).push_at(kstack_top) 65 | } 66 | pub unsafe fn append_initial_arguments(&self, args: [usize; 3]) { 67 | let contextContent = &mut *(self.content_addr as *mut ContextContent); 68 | contextContent.tf.x[10] = args[0]; 69 | contextContent.tf.x[11] = args[1]; 70 | contextContent.tf.x[12] = args[2]; 71 | } 72 | } 73 | impl ContextContent { 74 | // 将自身压到栈上,并返回 Context 75 | unsafe fn push_at(self, stack_top: usize) -> Context { 76 | let ptr = (stack_top as *mut ContextContent).sub(1); 77 | *ptr = self; 78 | Context { content_addr: ptr as usize } 79 | } 80 | } 81 | ``` 82 | 83 | ### 创建新线程 84 | 85 | 接下来就是线程的创建: 86 | 87 | ```rust 88 | // src/process/structs.rs 89 | impl Thread { 90 | // 创建一个新线程,放在堆上 91 | pub fn new_kernel(entry: usize) -> Box { 92 | unsafe { 93 | let kstack_ = KernelStack::new(); 94 | Box::new(Thread { 95 | // 内核线程共享内核资源,因此用目前的 satp 即可 96 | context: Context::new_kernel_thread(entry, kstack_.top(), satp::read().bits()), kstack: kstack_, 97 | }) 98 | } 99 | } 100 | // 为线程传入初始参数 101 | pub fn append_initial_arguments(&self, args: [usize; 3]) { 102 | unsafe { self.context.append_initial_arguments(args); } 103 | } 104 | } 105 | ``` 106 | 107 | 下一节我们终于能拨云见日,写一个测试看看我们的线程实现究竟有无问题了! 108 | 109 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch6-pa4 110 | -------------------------------------------------------------------------------- /chapter6/part4.md: -------------------------------------------------------------------------------- 1 | ## 测试线程创建与切换 2 | 3 | - [代码][code] 4 | 5 | 我们想做的事情是:新建一个临时线程,从启动线程切换到临时线程,再切换回来。 6 | 7 | 临时线程入口点: 8 | 9 | ```rust 10 | // src/process/mod.rs 11 | #[no_mangle] 12 | pub extern "C" fn temp_thread(from_thread: &mut Thread, current_thread: &mut Thread) { 13 | println!("I'm leaving soon, but I still want to say: Hello world!"); 14 | current_thread.switch_to(from_thread); 15 | } 16 | ``` 17 | 18 | 传入的参数中有一个 `from_thread` ,它本应代表启动线程。但是身处启动线程中,我们如何构造一个 `Thread` 实例表示其自身呢? 19 | 20 | ```rust 21 | // src/context.rs 22 | impl Context { 23 | pub fn null() -> Context { 24 | Context { content_addr: 0, } 25 | } 26 | } 27 | 28 | // src/process/structs.rs 29 | impl Thread { 30 | pub fn get_boot_thread() -> Box { 31 | Box::new(Thread { 32 | context: Context::null(), 33 | kstack: KernelStack::new_empty(), 34 | }) 35 | } 36 | } 37 | ``` 38 | 39 | 其实作为一个正在运行的线程,栈早就开好了,我们什么都不用做啦!一切都被我们的线程切换机制搞定了。 40 | 41 | 下面正式开始测试: 42 | 43 | ```rust 44 | // src/process/mod.rs 45 | 46 | pub fn init() { 47 | 48 | let mut boot_thread = Thread::get_boot_thread(); 49 | let mut temp_thread = Thread::new_kernel(temp_thread as usize); 50 | 51 | unsafe { 52 | // 对于放在堆上的数据,我只想到这种比较蹩脚的办法拿到它所在的地址... 53 | temp_thread.append_initial_arguments([&*boot_thread as *const Thread as usize, &*temp_thread as *const Thread as usize, 0]); 54 | } 55 | boot_thread.switch_to(&mut temp_thread); 56 | 57 | println!("switched back from temp_thread!"); 58 | loop {} 59 | } 60 | ``` 61 | 62 | 终于能够 `make run` 看一下结果啦! 63 | 64 | > **[success] 内核线程切换与测试** 65 | > 66 | > ``` 67 | > I'm leaving soon, but I still want to say: Hello world! 68 | > switched back from temp_thread! 69 | > ``` 70 | 71 | 可见我们切换到了临时线程,又切换了回来!测试成功! 72 | 73 | 截至目前所有的代码可以在[这里][code]找到以供参考。 74 | 75 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch6-pa4 76 | -------------------------------------------------------------------------------- /chapter6/part5.md: -------------------------------------------------------------------------------- 1 | ## 总结与展望 2 | 3 | 本章我们介绍了进程和线程的概念,由于进程管理的资源事实上仅有虚拟内存,而它用一个 $$\text{satp}$$ 寄存器的值即可描述。因此我们弱化进程概念,只研究线程。但是要注意二者的区别:对于实际上的内核,情况可完全不是这样! 4 | 5 | 接着,处于要将线程切换出去的目的,我们讨论如何表达线程的运行状态,以及如何用栈实现线程状态的保存与恢复,进而实现了线程切换。 6 | 7 | 最终,我们初始化一个临时线程(注意利用 `__trapret` 初始化寄存器的小技巧),并从启动线程切换过去并切换回来。 8 | 9 | 如果同时有多个线程需要执行,我们需要公平合理地分配 CPU 资源给这些线程,让它们都能被运行到,这就是下一章所要讲的**线程调度**。 10 | -------------------------------------------------------------------------------- /chapter7/introduction.md: -------------------------------------------------------------------------------- 1 | # 第七章:线程调度 2 | 3 | ## 本章概要 4 | 5 | 上一章我们已经支持内核线程的创建及切换。然而,为了支持多个线程并发运行,我们应当如何选择线程间切换的时机,更加合理地分配 CPU 资源呢? 6 | 7 | 本章你将会学到: 8 | 9 | - 使用线程池对线程进行管理 10 | - 创建后台的内核调度线程 idle 用于线程调度 11 | - 基于时钟中断定期进行线程调度 12 | -------------------------------------------------------------------------------- /chapter7/part3.md: -------------------------------------------------------------------------------- 1 | ## 线程调度之 Round Robin 算法 2 | 3 | - [代码][code] 4 | 5 | 时间片轮转调度算法(Round Robin)的基本思想是让每个线程在就绪队列中的等待时间与占用 CPU 的执行时间成正比例。其大致实现是: 6 | 7 | 1. 将系所有的就绪线程按照 FCFS 原则,排成一个就绪队列。 8 | 2. 每次调度时将 CPU 分派(dispatch)给队首进程,让其执行一个时间片。 9 | 3. 在时钟中断时,统计比较当前线程时间片是否已经用完。 10 |  - 如用完,则调度器(scheduler)暂停当前进程的执行,将其送到就绪队列的末尾,并通过切换执行就绪队列的队首进程。 11 |  - 如没用完,则线程继续使用。 12 | 13 | 对于不同的调度算法,我们实现了一个调度接口框架如下: 14 | 15 | ```rust 16 | pub trait Scheduler { 17 | fn push(&mut self, tid: Tid);    //把Tid线程放入就绪队列 18 | fn pop(&mut self) -> Option;  //从就绪队列取出线程 19 | fn tick(&mut self) -> bool;     //时钟tick(代表时间片)处理 20 | fn exit(&mut self, tid: Tid);    //线程退出 21 | } 22 | ``` 23 | 24 | 时间片轮转调度算法对上述四个函数接口有具体的实现。这里我们直接给出时间片轮转调度算法的实现代码,有兴趣者可自行去研究算法细节。 25 | 26 | ```rust 27 | // src/process/scheduler.rs 28 | 29 | use alloc::vec::Vec; 30 | 31 | #[derive(Default)] 32 | struct RRInfo { 33 | valid: bool, 34 | time: usize, 35 | prev: usize, 36 | next: usize, 37 | } 38 | 39 | pub struct RRScheduler { 40 | threads: Vec, 41 | max_time: usize, 42 | current: usize, 43 | } 44 | 45 | impl RRScheduler { 46 | // 设置每个线程连续运行的最大 tick 数 47 | pub fn new(max_time_slice: usize) -> Self { 48 | let mut rr = RRScheduler { 49 | threads: Vec::default(), 50 | max_time: max_time_slice, 51 | current: 0, 52 | }; 53 | rr.threads.push( 54 | RRInfo { 55 | valid: false, 56 | time: 0, 57 | prev: 0, 58 | next: 0, 59 | } 60 | ); 61 | rr 62 | } 63 | } 64 | impl Scheduler for RRScheduler { 65 | // 分为 1. 新线程 2. 时间片耗尽被切换出的线程 两种情况 66 | fn push(&mut self, tid : Tid) { 67 | let tid = tid + 1; 68 | if tid + 1 > self.threads.len() { 69 | self.threads.resize_with(tid + 1, Default::default); 70 | } 71 | 72 | if self.threads[tid].time == 0 { 73 | self.threads[tid].time = self.max_time; 74 | } 75 | 76 | let prev = self.threads[0].prev; 77 | self.threads[tid].valid = true; 78 | self.threads[prev].next = tid; 79 | self.threads[tid].prev = prev; 80 | self.threads[0].prev = tid; 81 | self.threads[tid].next = 0; 82 | } 83 | 84 | fn pop(&mut self) -> Option { 85 | let ret = self.threads[0].next; 86 | if ret != 0 { 87 | let next = self.threads[ret].next; 88 | let prev = self.threads[ret].prev; 89 | self.threads[next].prev = prev; 90 | self.threads[prev].next = next; 91 | self.threads[ret].prev = 0; 92 | self.threads[ret].next = 0; 93 | self.threads[ret].valid = false; 94 | self.current = ret; 95 | Some(ret-1) 96 | }else{ 97 | None 98 | } 99 | } 100 | 101 | // 当前线程的可用时间片 -= 1 102 | fn tick(&mut self) -> bool{ 103 | let tid = self.current; 104 | if tid != 0 { 105 | self.threads[tid].time -= 1; 106 | if self.threads[tid].time == 0 { 107 | return true; 108 | }else{ 109 | return false; 110 | } 111 | } 112 | return true; 113 | } 114 | 115 | fn exit(&mut self, tid : Tid) { 116 | let tid = tid + 1; 117 | if self.current == tid { 118 | self.current = 0; 119 | } 120 | } 121 | } 122 | ``` 123 | 124 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch7-pa4 125 | -------------------------------------------------------------------------------- /chapter7/part4.md: -------------------------------------------------------------------------------- 1 | ## 线程调度测试 2 | 3 | - [代码][code] 4 | 5 | 我们终于可以来测试一下这一章的代码实现的有没有问题了! 6 | 7 | ```rust 8 | // src/process/mod.rs 9 | 10 | use scheduler::RRScheduler; 11 | use thread_pool::ThreadPool; 12 | use alloc::boxed::Box; 13 | 14 | pub fn init() { 15 | // 使用 Round Robin Scheduler 16 | let scheduler = RRScheduler::new(1); 17 | // 新建线程池 18 | let thread_pool = ThreadPool::new(100, Box::new(scheduler)); 19 | // 新建内核线程 idle ,其入口为 Processor::idle_main 20 | let idle = Thread::new_kernel(Processor::idle_main as usize); 21 | // 我们需要传入 CPU 的地址作为参数 22 | idle.append_initial_arguments([&CPU as *const Processor as usize, 0, 0]); 23 | // 初始化 CPU 24 | CPU.init(idle, Box::new(thread_pool)); 25 | 26 | // 依次新建 5 个内核线程并加入调度单元 27 | for i in 0..5 { 28 | CPU.add_thread({ 29 | let thread = Thread::new_kernel(hello_thread as usize); 30 | // 传入一个编号作为参数 31 | thread.append_initial_arguments([i, 0, 0]); 32 | thread 33 | }); 34 | } 35 | println!("++++ setup process! ++++"); 36 | } 37 | 38 | pub fn run() { 39 | CPU.run(); 40 | } 41 | 42 | // src/process/processor.rs 43 | 44 | impl Processor { 45 | pub fn run(&self) { 46 | // 运行,也就是从启动线程切换到调度线程 idle 47 | Thread::get_boot_thread().switch_to(&mut self.inner().idle); 48 | } 49 | } 50 | ``` 51 | 52 | 内核线程的入口点是: 53 | 54 | ```rust 55 | // src/process/mod.rs 56 | 57 | #[no_mangle] 58 | pub extern "C" fn hello_thread(arg: usize) -> ! { 59 | println!("begin of thread {}", arg); 60 | for i in 0..800 { 61 | print!("{}", arg); 62 | } 63 | println!("\nend of thread {}", arg); 64 | // 通知 CPU 自身已经退出 65 | CPU.exit(0); 66 | loop {} 67 | } 68 | ``` 69 | 70 | 随后我们在`rust_main`主函数里添加调用`crate::process::init()`函数和`crate::process::run()`函数: 71 | 72 | ```rust 73 | // src/init.rs 74 | 75 | #[no_mangle] 76 | pub extern "C" fn rust_main() -> ! { 77 | crate::interrupt::init(); 78 | 79 | extern "C" { 80 | fn end(); 81 | } 82 | crate::memory::init( 83 | ((end as usize - KERNEL_BEGIN_VADDR + KERNEL_BEGIN_PADDR) >> 12) + 1, 84 | PHYSICAL_MEMORY_END >> 12 85 | ); 86 | crate::process::init(); 87 | crate::timer::init(); 88 | crate::process::run(); 89 | loop {} 90 | } 91 | 92 | ``` 93 | 94 | `make run` 一下,终于可以看到结果了! 95 | 96 | 这里开始就已经没有确定性的运行显示结果了,一个参考结果如下: 97 | 98 | > **[success] 线程调度成功** 99 | > 100 | > ```rust 101 | > ++++ setup interrupt! ++++ 102 | > switch satp from 0x8000000000080221 to 0x8000000000080a37 103 | > ++++ setup memory! ++++ 104 | > ++++ setup process! ++++ 105 | > ++++ setup timer! ++++ 106 | > 107 | > >>>> will switch_to thread 0 in idie_main! 108 | > begin of thread 0 109 | > 0000000000000000000000000000000000000000000000000000000000000000000000000 110 | > 0000000000000000000000000000000000000000000000000000000000000000000000000 111 | > 0000000000000000000000000000000000000000000000000000000000000000000000000 112 | > 0000000000000000000000000000000000000000000000000000000000000000000000000 113 | > 0000000000000000000000000000000000000000000000000000000000000000000000000 114 | > 000000000000 115 | > <<<< switch_back to idle in idle_main! 116 | > 117 | > >>>> will switch_to thread 1 in idie_main! 118 | > begin of thread 1 119 | > 1111111111111111111111111111111111111111111111111111111111111111111111111 120 | > 1111111111111111111111111111111111111111111111111111111111111111111111111 121 | > 1111111111111111111111111111111111111111111111111111111111111111111111111 122 | > 1111111111111111111111111111111111111111111111111111111111111111111111111 123 | > 1111111111111111111111111111111111111111111111111111111111111111111111111 124 | > 1111111111111111111111111111111111111111111111111111111111111111111111111 125 | > 11111111111111111111111 126 | > <<<< switch_back to idle in idle_main! 127 | > ...... 128 | > ``` 129 | 130 | 我们可以清楚的看到在每一个时间片内每个线程所做的事情。 131 | 132 | 如果结果不对的话,[这里][code]可以看到至今的所有代码。 133 | 134 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch7-pa4 135 | -------------------------------------------------------------------------------- /chapter7/part5.md: -------------------------------------------------------------------------------- 1 | ## 总结与展望 2 | 3 | 这一章我们介绍了如何借助时钟中断实现周期性的线程调度,合理分配 CPU 资源给每个线程。 4 | 5 | 我们在后台运行一个内核线程 idle 来进行线程的调度。需要尤其注意异步中断的屏蔽与恢复。 6 | 7 | 不过,目前为止我们所涉及到的线程全都是所谓的内核线程,它们共享内核(进程)的资源,也即经过重映射之后的虚拟内存空间。当然,每个线程都有仅属于它们自己的一个内核栈。 8 | 9 | 下一章,我们考虑编写并在我们的内核上运行**用户态程序**。 10 | -------------------------------------------------------------------------------- /chapter8/introduction.md: -------------------------------------------------------------------------------- 1 | # 第八章:用户进程 2 | 3 | 这一章我们终于要在自己的内核上跑用户程序啦! 4 | 5 | 本章你将会学到: 6 | 7 | - 使用系统调用为用户程序提供服务 8 | - 解析 ELF 格式的用户程序 9 | - 为用户程序创建虚拟内存空间 10 | - 创建并运行进程 11 | -------------------------------------------------------------------------------- /chapter8/part1_1.md: -------------------------------------------------------------------------------- 1 | ## 合并内核与应用程序 2 | 3 | - [代码][code] 4 | 5 | 到目前为止我们的 OS 还没有文件系统,所以我们只需将最终得到的应用程序可执行文件直接链接到内核中,合并在一起,形成一个 image,这样让 bootloader 一开始就把内核和应用程序一并加载到内存中。 6 | 7 | 这里的实现有一些技巧,我们先写一个[编译脚本](https://doc.rust-lang.org/cargo/reference/build-scripts.html) `build.rs`。注意是直接放在项目文件夹 `os` 中,而不是源码文件夹 `src`: 8 | 9 | ```rust 10 | // build.rs 11 | 12 | use std::fs::File; 13 | use std::io::{Result, Write}; 14 | 15 | fn main() { 16 | println!("cargo:rerun-if-env-changed=USER_IMG"); 17 | if let Ok(user_img) = std::env::var("USER_IMG") { 18 | println!("cargo:rerun-if-changed={}", user_img); 19 | } 20 | gen_link_user_asm().unwrap(); 21 | } 22 | 23 | /// Generate assembly file for linking user image 24 | fn gen_link_user_asm() -> Result<()> { 25 | let mut f = File::create("src/link_user.S").unwrap(); 26 | let user_img = std::env::var("USER_IMG").unwrap(); 27 | 28 | writeln!(f, "# generated by build.rs - do not edit")?; 29 | writeln!(f, r#" 30 | .section .data 31 | .global _user_img_start 32 | .global _user_img_end 33 | _user_img_start: 34 | .incbin "{}" 35 | _user_img_end: 36 | "#, user_img)?; 37 | Ok(()) 38 | } 39 | ``` 40 | 41 | 然后在 `init.rs` 中加入: 42 | 43 | ```rust 44 | // init.rs 45 | 46 | global_asm!(include_str!("link_user.S")); 47 | ``` 48 | 49 | 这段编译脚本会在每次编译的**最开始**运行。它的作用是生成一段汇编代码,将用户程序可执行文件原封不动地链接到内核的 $$\text{.data}$$ 段中。这段汇编被生成到 `src/link_user.S` 文件中,然后我们在 `init.rs` 里把它导入进来。此后可以在其它地方通过 `_user_img_start` 和 `_user_img_end` 这两个符号得知它所在的虚拟地址。 50 | 51 | 我们用一个环境变量 `USER_IMG` 记录用户程序可执行文件的路径,编译脚本在执行时,会将这个字符串填入生成的汇编中。所以我们只需在编译之前利用 `export` 修改环境变量 `USER_IMG` 为我们最终得到的可执行文件的路径即可。 52 | 53 | 最后让我们关注一开始的两条奇怪语句: 54 | 55 | ```rust 56 | println!("cargo:rerun-if-env-changed=USER_IMG"); 57 | println!("cargo:rerun-if-changed={}", user_img); 58 | ``` 59 | 60 | 这是编译脚本发送给构建工具 cargo 的特殊指令,含义是:当检测到环境变量 `USER_IMG` 或者它所指向的文件发生变化时,就强制重新编译。并且每次编译时,都会生成一个新的 `link_user.S` 文件。 61 | 62 | 这波操作要解决的问题是:由于编译器具有**自动增量构建**的特性,会导致当用户镜像发生变化时,编译器无法自动感知到,最后链接的还是以前的版本,使得我们不得不手动 `cargo clean` 清理干净中间产物后重新编译。 63 | 64 | 现在,我们每次更新并编译生成用户程序执行文件后,都可以放心地直接 `make run` 了! 65 | 66 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch8-pa4 67 | -------------------------------------------------------------------------------- /chapter8/part2.md: -------------------------------------------------------------------------------- 1 | ## 在内核中实现系统调用 2 | 3 | - [代码][code] 4 | 5 | 上一节中描述的`Hello World`应用程序会发出两个系统调用请求,我们的 OS 当然也就需要实现这两个系统调用: 6 | 7 | 1. 在屏幕上输出一个字符 8 | 2. 结束运行,退出当前线程 9 | 10 | ### 改进中断服务例程 11 | 12 | 这些功能其实我们的内核都已经实现完毕,因此重点是将系统调用这条调用链建立起来。 13 | 14 | ```rust 15 | // src/interrupt.rs 16 | 17 | #[no_mangle] 18 | pub fn rust_trap(tf: &mut TrapFrame) { 19 | match tf.scause.cause() { 20 | ... 21 | Trap::Exception(Exception::UserEnvCall) => syscall(tf), 22 | ... 23 | } 24 | } 25 | ``` 26 | 27 | 首先是发现中断原因是在用户态执行 `ecall` 指令时,说明用户程序向我们请求服务,我们转入 `syscall` 函数。 28 | 29 | ```rust 30 | // src/interrupt.rs 31 | 32 | fn syscall(tf: &mut TrapFrame) { 33 | // 返回后跳转到 ecall 下一条指令 34 | tf.sepc += 4; 35 | let ret = crate::syscall::syscall( 36 | tf.x[17], 37 | [tf.x[10], tf.x[11], tf.x[12]], 38 | tf 39 | ); 40 | tf.x[10] = ret as usize; 41 | } 42 | ``` 43 | 44 | 我们从中断帧中取出中断之前的寄存器 $$a_7,a_0,a_1,a_2$$ 的值,分别表示 syscall id 以及传入的参数。这是通过用户态的内联汇编 `ecall` 传给我们的。 45 | 46 | ### 添加 syscall 处理 47 | 48 | 我们将系统调用单开一个模块来实现: 49 | 50 | ```rust 51 | // src/syscall.rs 52 | 53 | use crate::context::TrapFrame; 54 | use crate::process; 55 | 56 | pub const SYS_WRITE: usize = 64; 57 | pub const SYS_EXIT: usize = 93; 58 | 59 | pub fn syscall(id: usize, args: [usize; 3], tf: &mut TrapFrame) -> isize { 60 | match id { 61 | SYS_WRITE => { 62 | print!("{}", args[0] as u8 as char); 63 | 0 64 | }, 65 | SYS_EXIT => { 66 | sys_exit(args[0]); 67 | 0 68 | }, 69 | _ => { 70 | panic!("unknown syscall id {}", id); 71 | }, 72 | } 73 | } 74 | 75 | fn sys_exit(code: usize) { 76 | process::exit(code); 77 | } 78 | ``` 79 | 80 | 不必花太多功夫,我们就在内核中支持了两个系统调用! 81 | 82 | [code]: https://github.com/rcore-os/rCore_tutorial/tree/ch8-pa4 83 | -------------------------------------------------------------------------------- /chapter8/part5.md: -------------------------------------------------------------------------------- 1 | ## 总结与展望 2 | 3 | 这一章我们成功在内核上跑起来了我们自己的用户程序! 4 | -------------------------------------------------------------------------------- /chapter9/figures/rcore_fs_analysis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter9/figures/rcore_fs_analysis.pdf -------------------------------------------------------------------------------- /chapter9/figures/xv6_fs_analysis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/chapter9/figures/xv6_fs_analysis.pdf -------------------------------------------------------------------------------- /chapter9/introduction.md: -------------------------------------------------------------------------------- 1 | # 第九章:文件系统 2 | 3 | ## 本章概要 4 | 5 | 之前我们只能在内核代码中硬编码跑什么用户程序,现在我们实现一个简单的终端,可以由我们自己输入跑什么程序!这说明我们要同时将多个程序组成的镜像链接进内核,于是我们使用文件系统来打包镜像,在内核中解析镜像取出单个用户程序。 6 | 7 | 本章你将会学到: 8 | 9 | - 为文件系统开发最简单的设备驱动 10 | - 如何实现线程的阻塞与唤醒 11 | - 用缓冲区描述标准输入,并利用线程阻塞提高 CPU 利用率 12 | - 实现用户态终端程序 13 | 14 | ## 参考资料 15 | 1. [rCore 文件系统分析](figures/rcore_fs_analysis.pdf) 16 | 2. [xv6 文件系统分析](figures/xv6_fs_analysis.pdf) 17 | -------------------------------------------------------------------------------- /chapter9/part5.md: -------------------------------------------------------------------------------- 1 | ## 总结与展望 2 | 3 | 感谢你,能陪我们一直走到这里。 4 | 5 | 不过这仅仅是一个开始,我们现在只涉及了很少一部分内容。像是进程与进程间通信、多核支持、为真实设备开发驱动等等都是需要我们继续探索的。 6 | 7 | 但愿这篇小小的 tutorial ,能给你带来一点小小的帮助! 8 | -------------------------------------------------------------------------------- /commit_ids.txt: -------------------------------------------------------------------------------- 1 | chapter1/part1: d2a80549 2 | chapter1/part2: 77ecc4e8 3 | chapter1/part3: 77ecc4e8 4 | chapter1/part4: 77ecc4e8 5 | chapter2/part1: 08991c79 6 | chapter2/part2: 08991c79 7 | chapter2/part3: 08991c79 8 | chapter2/part4: 08991c79 9 | chapter2/part5: 9387bd50 10 | chapter2/part6: ad57b607 11 | chapter2/part7: ad57b607 12 | chapter3/part1: 952e1f1c 13 | chapter3/part2: 952e1f1c 14 | chapter3/part3: 5d09d5eb 15 | chapter3/part4: 5d09d5eb 16 | chapter3/part5: 57734e33 17 | chapter4/part1: 695d8a2c 18 | chapter4/part2: 345dc90b 19 | chapter5/part2: 28bde442 20 | chapter5/part4: e54dce49 21 | chapter5/part5: e54dce49 22 | chapter5/part6: e54dce49 23 | chapter6/part1: 8cb1a4be 24 | chapter6/part2: 8cb1a4be 25 | chapter6/part3: 8cb1a4be 26 | chapter6/part4: 8cb1a4be 27 | chapter7/part1: 75d4ed97 28 | chapter7/part2: 75d4ed97 29 | chapter7/part3: 75d4ed97 30 | chapter7/part4: 75d4ed97 31 | chapter8/part1: 99fa0b58 32 | chapter8/part2: 86abde4d 33 | chapter8/part3: 86abde4d 34 | chapter8/part4: 86abde4d 35 | chapter9/part1: 443465c1 36 | chapter9/part2: ea2acb44 37 | chapter9/part3: f408f075 38 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | _book/ 3 | .DS_Store -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /docs/backup.sh: -------------------------------------------------------------------------------- 1 | sudo rm -r ../rCore_tutorial_doc_backup 2 | sudo cp -r ../rCore_tutorial_doc ../rCore_tutorial_doc_backup 3 | 4 | -------------------------------------------------------------------------------- /docs/build.sh: -------------------------------------------------------------------------------- 1 | # gitbook install 2 | cp extensions/highlight/prism-riscv.js node_modules/prismjs/components/ 3 | python3 extensions/highlight/add_riscv_component.py 4 | rm -rf docs/ 5 | gitbook build 6 | mv _book/ docs/ 7 | python3 extensions/highlight/add_code_style.py 8 | -------------------------------------------------------------------------------- /docs/build_marp.sh: -------------------------------------------------------------------------------- 1 | # update os2atc2019 os2atc.md, then 2 | marp -I os2atc2019 -o docs/os2atc2019 3 | cp -r os2atc2019/figures docs/os2atc2019/ 4 | # finally, git push && firefox https://rcore-os.github.io/rCore_tutorial_doc/os2atc2019/os2atc.html -------------------------------------------------------------------------------- /docs/chapter13/figures/hhh.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter13/figures/hhh.jpeg -------------------------------------------------------------------------------- /docs/chapter13/introduction.md: -------------------------------------------------------------------------------- 1 | ## 第十三章:进程管理:fork and execute 2 | 3 | ![hhh](./figures/hhh.jpeg) 4 | 5 | ### 本章概要 6 | 7 | `sys_fork` 用于复制当前进程,`sys_exec` 用于将一个进程的内容修改为一个新的程序。在 99% 的情况下,fork 之后会立刻调用 exec 。Linux 便是这样创建进程的。 8 | 9 | 由于我们并没有做进程拆分,所以一个用户进程只有一个线程,于是就偷懒用 `Thread` 描述用户进程了。 10 | 11 | > 有没有觉得这样创建进程十分别扭,先复制一遍自身,然后用别的东西覆盖。。。那复制这些东西干啥。。。 12 | > 明明在前面的章节我们已经能够通过 `new_user_thread` 直接创建新进程(线程)了。。。 13 | 14 | 本章你将会学到: 15 | 16 | - fork 的功能 17 | - 如何描述一个正在运行的进程(线程) 18 | - 如何完全复制一个正在运行的进程(线程) 19 | - TODO:写 execute 20 | -------------------------------------------------------------------------------- /docs/chapter13/part1.md: -------------------------------------------------------------------------------- 1 | ## fork 介绍 2 | 3 | fork 的功能是复制一个运行中的程序,具体来说就是一个程序在某一时刻发起 sys_fork 进入中断,由操作系统将此时的程序复制。从中断返回后,两个程序都会继续执行 fork 的下一条指令。 4 | 5 | fork 产生的新线程,除了返回值不一样,其它都完全一样。通过返回值,我们可以让两个线程进行不同的操作。 6 | 7 | **fork 的返回值:** 8 | 9 | - 如果是父线程(原线程),则返回子线程(新线程)的 tid 10 | - 如果是子线程(新线程),则 0 11 | 12 | 规范和细节听起来很麻烦,我们直接看例子: 13 | 14 | - `usr/rust/src/syscall.rs` 15 | 16 | ```rust 17 | enum SyscallId { 18 | Fork = 57, 19 | ... 20 | } 21 | 22 | pub fn sys_fork() -> i64 { 23 | sys_call(SyscallId::Fork, 0, 0, 0, 0) 24 | } 25 | ``` 26 | 27 | - `usr/rust/src/bin/fork.rs` 28 | 29 | ```rust 30 | #![no_std] 31 | #![no_main] 32 | 33 | #[macro_use] 34 | extern crate user; 35 | 36 | use user::syscall::sys_fork; 37 | 38 | #[no_mangle] 39 | pub fn main() -> usize { 40 | let tid = sys_fork(); 41 | let tid = sys_fork(); 42 | if tid == 0 { 43 | println!("I am child"); 44 | } else { 45 | println!("I am father"); 46 | } 47 | println!("ret tid is: {}", tid); 48 | 0 49 | } 50 | ``` 51 | 52 | - 输出 53 | 54 | ```bash 55 | I am child 56 | ret tid is: 0 57 | thread 3 exited, exit code = 0 58 | I am father 59 | ret tid is: 3 60 | thread 2 exited, exit code = 0 61 | I am child 62 | ret tid is: 0 63 | thread 4 exited, exit code = 0 64 | I am father 65 | ret tid is: 4 66 | thread 1 exited, exit code = 0 67 | ``` 68 | 69 | 从结果来看,一共退出了四次程序,所以一共进行了三次 fork : 70 | 71 | 1. 第三行,`thread 1` fork 产生 `thread 2` 72 | 2. `thread 1` 执行第四行,产生 `thread 3` 73 | 3. `thread 2` 执行第四行,产生 `thread 4` 74 | 75 | 每个线程都只输出两行,以及一行程序退出时由操作系统输出的信息。可以看出 `thread 1` 和 `thread 2` 都声称自己是 father ,这是由于它们在第四行 fork 之后,分别成为了 `thread 3` 和 `thread 4` 的 father 。需要注意的是,`thread 1` 还是 `thread 2` 的 father 哦。至于线程的执行顺序,那就看调度器算法咯。。。 76 | -------------------------------------------------------------------------------- /docs/chapter13/part2.md: -------------------------------------------------------------------------------- 1 | ## fork 实现思路 2 | 3 | 在前面的章节,我们就已经实现了 Thread 结构体,为了满足新的需求,我们需要加上一行: 4 | 5 | ```diff 6 | + use alloc::sync::Arc; 7 | + use spin::Mutex; 8 | 9 | pub struct Thread { 10 | pub context: Context, // 程序切换产生的上下文所在栈的地址(指针) 11 | pub kstack: KernelStack, // 保存程序切换产生的上下文的栈 12 | pub wait: Option, // 等待队列 13 | + pub vm: Option>>, // 页表 14 | } 15 | ``` 16 | 17 | 为什么需要保存一个页表呢?这是因为 fork 复制了当前线程,这包括了它的运行栈。这里的运行栈就包括在了页表里。由于我们使用了虚拟地址,所以只要保证访问的虚拟地址能映射到正确的物理地址即可。所以,为了能够知道原线程都用了哪些虚拟地址,我们需要保存每一个线程的页表,供其它线程复制。 18 | 19 | 由于只有用户程序会进行 fork ,所以我们只为用户程序保存 vm ,内核线程的 vm 直接赋为 None 。 20 | 21 | - `struct.rs` 22 | 23 | ```rust 24 | impl Thread { 25 | pub fn new_kernel(entry: usize) -> Box { 26 | unsafe { 27 | let kstack_ = KernelStack::new(); 28 | Box::new(Thread { 29 | context: Context::new_kernel_thread(entry, kstack_.top(), satp::read().bits()), 30 | kstack: kstack_, 31 | wait: None, 32 | vm: None, 33 | }) 34 | } 35 | } 36 | 37 | pub fn get_boot_thread() -> Box { 38 | Box::new(Thread { 39 | context: Context::null(), 40 | kstack: KernelStack::new_empty(), 41 | wait: None, 42 | vm: None, 43 | }) 44 | } 45 | 46 | pub unsafe fn new_user(data: &[u8], wait_thread: Option) -> Box { 47 | ... 48 | Box::new(Thread { 49 | context: Context::new_user_thread(entry_addr, ustack_top, kstack.top(), vm.token()), 50 | kstack: kstack, 51 | wait: wait_thread, 52 | vm: Some(Arc::new(Mutex::new(vm))), 53 | }) 54 | } 55 | } 56 | ``` 57 | 58 | 复制线程的工作看起来十分简单,把所有东西都 clone 一遍就好了: 59 | 60 | - `struct.rs` 61 | 62 | ```rust 63 | use crate::context::{Context, TrapFrame}; 64 | 65 | impl Thread { 66 | /// Fork a new process from current one 67 | pub fn fork(&self, tf: &TrapFrame) -> Box { 68 | let kstack = KernelStack::new(); // 分配新的栈 69 | let vm = self.vm.as_ref().unwrap().lock().clone(); // 为变量分配内存,将虚拟地址映射到新的内存上(尚未实现) 70 | let vm_token = vm.token(); 71 | let context = unsafe { Context::new_fork(tf, kstack.top(), vm_token) }; // 复制上下文到 kernel stack 上(尚未实现) 72 | Box::new(Thread { 73 | context, 74 | kstack, 75 | wait: self.wait.clone(), 76 | vm: Some(Arc::new(Mutex::new(vm))), 77 | }) 78 | } 79 | } 80 | ``` 81 | 82 | 线程的 tid 是在 `thread_pool.add` 里进行分配的,由于 fork 需要为父线程返回子线程的 tid ,所以这里需要为 `thread_pool.add` 增加返回值: 83 | 84 | - `process/mod.rs` 85 | 86 | ```rust 87 | pub fn add_thread(thread: Box) -> usize { 88 | CPU.add_thread(thread) 89 | } 90 | ``` 91 | 92 | - `process/processor.rs` 93 | 94 | ```rust 95 | pub fn add_thread(&self, thread: Box) -> Tid { 96 | self.inner().pool.add(thread) 97 | } 98 | ``` 99 | 100 | - `process/thread_pool.rs` 101 | 102 | ```rust 103 | pub fn add(&mut self, _thread: Box) -> Tid { 104 | let tid = self.alloc_tid(); 105 | self.threads[tid] = Some(ThreadInfo { 106 | status: Status::Ready, 107 | thread: Some(_thread), 108 | }); 109 | self.scheduler.push(tid); 110 | return tid; 111 | } 112 | ``` 113 | 114 | 最后,实现 syscall 的代码就只有下面十几行: 115 | 116 | - `process/mod.rs` 117 | 118 | ```rust 119 | pub fn current_thread() -> &'static Box { 120 | CPU.current_thread() 121 | } 122 | ``` 123 | 124 | - `process/processor` 125 | 126 | ```rust 127 | impl Processor { 128 | pub fn current_thread(&self) -> &Box { 129 | &self.inner().current.as_mut().unwrap().1 130 | } 131 | } 132 | ``` 133 | 134 | - `syscall.rs` 135 | 136 | ```rust 137 | pub const SYS_FORK: usize = 57; 138 | 139 | pub fn syscall(id: usize, args: [usize; 3], tf: &mut TrapFrame) -> isize { 140 | match id { 141 | SYS_FORK => sys_fork(tf), 142 | ... 143 | } 144 | } 145 | 146 | fn sys_fork(tf: &mut TrapFrame) -> isize { 147 | let new_thread = process::current_thread().fork(tf); 148 | let tid = process::add_thread(new_thread); 149 | tid as isize 150 | } 151 | ``` 152 | 153 | > 吐槽一下,我最开始写 `current_thread` 的时候,返回的时候需要 clone 一下,感觉这样安全一些,省的外部不小心把 thread 修改了。 154 | > 结果这导致了一个很严重而且很隐蔽的问题:thread 的 kernel stack 被释放了。。。 155 | > 花了半天才找到问题,这是由于 Thread 有一个成员 kernel stack ,kernel stack 实现了 Drop trait ,析构的时候会把占用的内存一起释放掉。 156 | > 而由于 kernel stack 存的是指针(首地址) ,clone 后的指针和原指针指向的是同一个地方! 157 | > 所以在析构的时候,会把原来的 stack 也释放了。。。 158 | > awsl 159 | 160 | anyway ,fork 的实现思路大概就是这样。注意到前面有几个标注了“尚未实现”的函数,接下来我们来实现它们。 161 | 162 | > 出于偷懒我并没有维护这两个线程的父子关系,感兴趣的同学可以自 bang 行 wo 实现(逃 163 | -------------------------------------------------------------------------------- /docs/chapter13/part3.md: -------------------------------------------------------------------------------- 1 | ## 复制线程上下文 2 | 3 | 这个比较简单,先写这个吧。 4 | 5 | - `context.rs` 6 | 7 | ```rust 8 | impl Context { 9 | pub unsafe fn new_fork(tf: &TrapFrame, kstack_top: usize, satp: usize) -> Context { 10 | ContextContent::new_fork(tf, kstack_top, satp) 11 | } 12 | } 13 | 14 | impl ContextContent { 15 | unsafe fn new_fork(tf: &TrapFrame, kstack_top: usize, satp: usize) -> Context { 16 | ContextContent { 17 | ra: __trapret as usize, 18 | satp, 19 | s: [0; 12], 20 | tf: { 21 | let mut tf = tf.clone(); 22 | // fork function's ret value, the new process is 0 23 | tf.x[10] = 0; // a0 24 | tf 25 | }, 26 | } 27 | .push_at(kstack_top) 28 | } 29 | } 30 | ``` 31 | 32 | 由于将 `ra` 赋值为 `__trapret` ,所以在 `switch` 最后执行 ret 的时候,内核会跳转到 `__trapret` ,因为 `tf` 保存了所有的上下文(包含了 `s[0..12]`),所以无需在 `new_fork` 中为 s 寄存器赋值。 33 | 34 | 将复制好的上下文放入新创建的 kstack 就可以啦。 35 | -------------------------------------------------------------------------------- /docs/chapter13/part4.md: -------------------------------------------------------------------------------- 1 | ## 复制页表 2 | 3 | 前面我们使用了 `MemorySet.clone` ,但是我们并没有实现。实际上页表的复制并不像一般的元素那样简单。要做的事情有: 4 | 5 | 1. 创建一个新的页目录 6 | 2. 原线程每有一个页,就为新新线程分配一个页 7 | 3. 页的内容进行复制并映射 8 | 9 | - `memory/memory_set/mod.rs` 10 | 11 | ```rust 12 | use crate::memory::paging::{PageRange, PageTableImpl}; 13 | 14 | impl MemorySet { 15 | pub fn clone(&mut self) -> Self { 16 | // 创建一个新的页目录 17 | let mut new_page_table = PageTableImpl::new_bare(); 18 | let Self { 19 | ref mut page_table, 20 | ref areas, 21 | .. 22 | } = self; 23 | // 遍历自己的所有页面 24 | for area in areas.iter() { 25 | for page in PageRange::new(area.start, area.end) { 26 | // 创建一个新的页 27 | // 将原页的内容复制到新页,同时进行映射 28 | area.handler 29 | .clone_map(&mut new_page_table, page_table, page, &area.attr); 30 | } 31 | } 32 | MemorySet { 33 | areas: areas.clone(), 34 | page_table: new_page_table, 35 | } 36 | } 37 | } 38 | 39 | ``` 40 | 41 | 修改一下 `MemoryArea` 成员的访问权限: 42 | 43 | - `memory/memory_set/area.rs` 44 | 45 | ```rust 46 | pub struct MemoryArea { 47 | pub start: usize, 48 | pub end: usize, 49 | pub handler: Box, 50 | pub attr: MemoryAttr, 51 | } 52 | ``` 53 | 54 | 对于内核,我们采用线性映射。而对于用户程序,我们采用普通映射,即物理地址和虚拟地址没有什么关系,虚拟地址对应的物理内存无法通过简单计算得出,必须通过页表转换,所以所有程序的 handler 都是 `ByFrame` 类型而不是 `Linear` 类型。 55 | 56 | 在 `self.map` 中,会分配一个物理帧,并将其映射到指定的虚拟页上。然后将原页面的内容读出,复制到新页面上。这样,新旧线程访问同一个虚拟地址的时候,真实访问到的就是不同物理地址下相同数值的对象: 57 | 58 | - `memory/memory_set/handler.rs` 59 | 60 | ```rust 61 | impl MemoryHandler for ByFrame { 62 | fn clone_map( 63 | &self, 64 | pt: &mut PageTableImpl, 65 | src_pt: &mut PageTableImpl, 66 | vaddr: usize, 67 | attr: &MemoryAttr, 68 | ) { 69 | self.map(pt, vaddr, attr); 70 | let data = src_pt.get_page_slice_mut(vaddr); 71 | pt.get_page_slice_mut(vaddr).copy_from_slice(data); 72 | } 73 | } 74 | ``` 75 | 76 | 但是有一个问题,我们如果读取到原页表里的元素呢?我们现在在内核里,内核使用的是线性映射。所以我们需要: 77 | 78 | - 通过复杂的过程通过原页表得到虚拟地址对应的物理地址 79 | - 将这个物理地址转换为内核可访问的虚拟地址 80 | 81 | 上面的两步就是 `get_page_slice_mut` 做的事情,然后它把得到的虚拟地址转换成 u8 数组(方便操作): 82 | 83 | - `memory/paging.rs` 84 | 85 | ```rust 86 | impl PageTableImpl { 87 | pub fn get_page_slice_mut<'a>(&mut self, vaddr: usize) -> &'a mut [u8] { 88 | let frame = self 89 | .page_table 90 | .translate_page(Page::of_addr(VirtAddr::new(vaddr))) 91 | .unwrap(); 92 | let vaddr = frame.start_address().as_usize() + PHYSICAL_MEMORY_OFFSET; 93 | unsafe { core::slice::from_raw_parts_mut(vaddr as *mut u8, 0x1000) } 94 | } 95 | } 96 | ``` 97 | 98 | > `translate_page` 不是我实现的,我也懒得看具体细节了,反正用着挺好使,不管了(x) 99 | 100 | 最后要在 `MemoryHandler` 中声明 `clone_map` 成员函数,同时为 `Linear` 实现 `clone_map` : 101 | 102 | - `memory/memory_set/handler.rs` 103 | 104 | ```rust 105 | pub trait MemoryHandler: Debug + 'static { 106 | ... 107 | fn clone_map( 108 | &self, 109 | pt: &mut PageTableImpl, 110 | src_pt: &mut PageTableImpl, 111 | vaddr: usize, 112 | attr: &MemoryAttr, 113 | ); 114 | } 115 | 116 | impl MemoryHandler for Linear { 117 | fn clone_map( 118 | &self, 119 | pt: &mut PageTableImpl, 120 | _src_pt: &mut PageTableImpl, 121 | vaddr: usize, 122 | attr: &MemoryAttr, 123 | ) { 124 | self.map(pt, vaddr, attr); 125 | } 126 | } 127 | ``` 128 | 129 | 由于 `Linear` 的虚拟地址和物理地址是一对一的,所以简单的进行线性映射就好啦。。。 130 | -------------------------------------------------------------------------------- /docs/chapter2/figures/privilege_levels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter2/figures/privilege_levels.png -------------------------------------------------------------------------------- /docs/chapter2/figures/program_memory_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter2/figures/program_memory_layout.png -------------------------------------------------------------------------------- /docs/chapter5/figures/memory_handler.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter5/figures/memory_handler.jpg -------------------------------------------------------------------------------- /docs/chapter5/figures/memory_set.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter5/figures/memory_set.jpg -------------------------------------------------------------------------------- /docs/chapter5/figures/rcore_memlayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter5/figures/rcore_memlayout.png -------------------------------------------------------------------------------- /docs/chapter5/figures/sv39_addr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter5/figures/sv39_addr.png -------------------------------------------------------------------------------- /docs/chapter5/figures/sv39_pagetable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter5/figures/sv39_pagetable.jpg -------------------------------------------------------------------------------- /docs/chapter5/figures/sv39_pte.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter5/figures/sv39_pte.jpg -------------------------------------------------------------------------------- /docs/chapter5/figures/sv39_rwx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter5/figures/sv39_rwx.jpg -------------------------------------------------------------------------------- /docs/chapter5/figures/sv39_satp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter5/figures/sv39_satp.jpg -------------------------------------------------------------------------------- /docs/chapter9/figures/rcore_fs_analysis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter9/figures/rcore_fs_analysis.pdf -------------------------------------------------------------------------------- /docs/chapter9/figures/xv6_fs_analysis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/chapter9/figures/xv6_fs_analysis.pdf -------------------------------------------------------------------------------- /docs/commit_ids.txt: -------------------------------------------------------------------------------- 1 | chapter1/part1: d2a80549 2 | chapter1/part2: 77ecc4e8 3 | chapter1/part3: 77ecc4e8 4 | chapter1/part4: 77ecc4e8 5 | chapter2/part1: 08991c79 6 | chapter2/part2: 08991c79 7 | chapter2/part3: 08991c79 8 | chapter2/part4: 08991c79 9 | chapter2/part5: 9387bd50 10 | chapter2/part6: ad57b607 11 | chapter2/part7: ad57b607 12 | chapter3/part1: 952e1f1c 13 | chapter3/part2: 952e1f1c 14 | chapter3/part3: 5d09d5eb 15 | chapter3/part4: 5d09d5eb 16 | chapter3/part5: 57734e33 17 | chapter4/part1: 695d8a2c 18 | chapter4/part2: 345dc90b 19 | chapter5/part2: 28bde442 20 | chapter5/part4: e54dce49 21 | chapter5/part5: e54dce49 22 | chapter5/part6: e54dce49 23 | chapter6/part1: 8cb1a4be 24 | chapter6/part2: 8cb1a4be 25 | chapter6/part3: 8cb1a4be 26 | chapter6/part4: 8cb1a4be 27 | chapter7/part1: 75d4ed97 28 | chapter7/part2: 75d4ed97 29 | chapter7/part3: 75d4ed97 30 | chapter7/part4: 75d4ed97 31 | chapter8/part1: 99fa0b58 32 | chapter8/part2: 86abde4d 33 | chapter8/part3: 86abde4d 34 | chapter8/part4: 86abde4d 35 | chapter9/part1: 443465c1 36 | chapter9/part2: ea2acb44 37 | chapter9/part3: f408f075 38 | -------------------------------------------------------------------------------- /docs/exercise/code/mutex.rs: -------------------------------------------------------------------------------- 1 | use crate::interrupt::{disable_and_store, restore}; 2 | use crate::process::yield_now; 3 | use core::cell::UnsafeCell; 4 | use core::default::Default; 5 | use core::marker::Sync; 6 | use core::ops::{Deref, DerefMut, Drop}; 7 | 8 | /// This type provides MUTual EXclusion based on spinning. 9 | pub struct Mutex { 10 | lock: UnsafeCell, 11 | data: UnsafeCell, 12 | } 13 | 14 | /// A guard to which the protected data can be accessed 15 | /// 16 | /// When the guard falls out of scope it will release the lock. 17 | #[derive(Debug)] 18 | pub struct MutexGuard<'a, T: ?Sized + 'a> { 19 | lock: &'a mut bool, 20 | data: &'a mut T, 21 | } 22 | 23 | // Same unsafe impls as `std::sync::Mutex` 24 | unsafe impl Sync for Mutex {} 25 | unsafe impl Send for Mutex {} 26 | 27 | impl Mutex { 28 | /// Creates a new spinlock wrapping the supplied data. 29 | pub const fn new(user_data: T) -> Mutex { 30 | Mutex { 31 | lock: UnsafeCell::new(false), 32 | data: UnsafeCell::new(user_data), 33 | } 34 | } 35 | 36 | /// Consumes this mutex, returning the underlying data. 37 | pub fn into_inner(self) -> T { 38 | // We know statically that there are no outstanding references to 39 | // `self` so there's no need to lock. 40 | let Mutex { data, .. } = self; 41 | data.into_inner() 42 | } 43 | } 44 | 45 | impl Mutex { 46 | fn obtain_lock(&self) { 47 | // TODO 48 | // try to get lock 49 | // what to do if get fail? 50 | } 51 | 52 | /// Locks the spinlock and returns a guard. 53 | /// 54 | /// The returned value may be dereferenced for data access 55 | /// and the lock will be dropped when the guard falls out of scope. 56 | pub fn lock(&self) -> MutexGuard { 57 | self.obtain_lock(); 58 | MutexGuard { 59 | lock: unsafe { &mut *self.lock.get() }, 60 | data: unsafe { &mut *self.data.get() }, 61 | } 62 | } 63 | } 64 | 65 | impl Default for Mutex { 66 | fn default() -> Mutex { 67 | Mutex::new(Default::default()) 68 | } 69 | } 70 | 71 | impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> { 72 | type Target = T; 73 | fn deref(&self) -> &T { 74 | &*self.data 75 | } 76 | } 77 | 78 | impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> { 79 | fn deref_mut(&mut self) -> &mut T { 80 | &mut *self.data 81 | } 82 | } 83 | 84 | impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> { 85 | /// The dropping of the MutexGuard will release the lock it was created from. 86 | fn drop(&mut self) { 87 | // TODO 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /docs/exercise/code/timer.rs: -------------------------------------------------------------------------------- 1 | //! A naive timer 2 | 3 | use alloc::{boxed::Box, collections::BinaryHeap}; 4 | use core::cmp::Ordering; 5 | 6 | /// The type of callback function. 7 | type Callback = Box; 8 | 9 | struct Node(u64, Callback); 10 | 11 | impl Ord for Node { 12 | fn cmp(&self, other: &Self) -> Ordering { 13 | if self.0 < other.0 { 14 | return Ordering::Greater; 15 | } else if self.0 > other.0 { 16 | return Ordering::Less; 17 | } else { 18 | return Ordering::Equal; 19 | } 20 | } 21 | } 22 | 23 | impl Eq for Node {} 24 | 25 | impl PartialOrd for Node { 26 | fn partial_cmp(&self, other: &Self) -> Option { 27 | Some(self.cmp(other)) 28 | } 29 | } 30 | 31 | impl PartialEq for Node { 32 | fn eq(&self, other: &Self) -> bool { 33 | self.0 == other.0 34 | } 35 | } 36 | 37 | /// A naive timer 38 | #[derive(Default)] 39 | pub struct Timer { 40 | events: BinaryHeap, 41 | } 42 | 43 | impl Timer { 44 | /// Add a timer with given `deadline`. 45 | /// 46 | /// The `callback` will be called on timer expired. 47 | pub fn add(&mut self, deadline: u64, callback: impl FnOnce() + Send + Sync + 'static) { 48 | self.events.push(Node(deadline, Box::new(callback))); 49 | } 50 | 51 | /// Called on each tick. 52 | /// 53 | /// The caller should give the current time `now`, and all expired timer will be trigger. 54 | pub fn tick(&mut self, now: u64) { 55 | while let Some(event) = self.events.peek() { 56 | if event.0 > now { 57 | return; 58 | } 59 | let callback = self.events.pop().unwrap().1; 60 | callback(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /docs/extensions/comment/gitalk.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 27 | -------------------------------------------------------------------------------- /docs/extensions/fill_commit_id.py: -------------------------------------------------------------------------------- 1 | from posix import system 2 | 3 | BASE_URL = 'https://github.com/rcore-os/rCore_tutorial/tree/' 4 | 5 | for line in open('commit_ids.txt').readlines(): 6 | path, commit_id = line[:-1].split(': ') 7 | path = path + '.md' 8 | find = r'^\[CODE\].*' 9 | replace = '[CODE]: {}{}'.format(BASE_URL, commit_id) 10 | system("sed -i '' -E 's#{}#{}#g' {}".format(find, replace, path)) 11 | -------------------------------------------------------------------------------- /docs/extensions/highlight/add_code_style.py: -------------------------------------------------------------------------------- 1 | s = open('docs/gitbook/style.css').read() 2 | code = 'markdown-section code{' 3 | color_red = 'color:#bf616a;' 4 | code_in_pre = 'markdown-section pre>code{' 5 | color_inherit = 'color:#ccc;' 6 | s = s.replace(code, code + color_red) 7 | s = s.replace(code_in_pre, code_in_pre + color_inherit) 8 | with open('docs/gitbook/style.css', 'w') as f: 9 | f.write(s) 10 | 11 | -------------------------------------------------------------------------------- /docs/extensions/highlight/add_riscv_component.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | json_path = 'node_modules/prismjs/components.json' 4 | 5 | data = json.load(open(json_path)) 6 | data['languages']['riscv'] = {'title': 'RISC-V', 'owner': 'shinbokuow2'} 7 | with open(json_path, 'w') as f: 8 | f.write(json.dumps(data, sort_keys = True, indent = 4)) 9 | -------------------------------------------------------------------------------- /docs/extensions/highlight/prism-riscv.js: -------------------------------------------------------------------------------- 1 | Prism.languages.riscv = { 2 | 'comment': /#.*\n/, 3 | 4 | 'general-registers' : { 5 | pattern: /\b(?:x[1-2]?[0-9]|x30|x31|zero|ra|sp|gp|tp|fp|t[0-6]|s[0-9]|s1[0-1]|a[0-7]|pc)\b/, 6 | alias: 'class-name' 7 | }, 8 | 's-mode-csrs' : { 9 | pattern: /\bs(?:status|tvec|ip|ie|counteren|scratch|epc|cause|tval|atp|)\b/, 10 | alias: 'class-name' 11 | }, 12 | 13 | /* timer & monitor csrs are not included yet */ 14 | 'm-mode-csrs' : { 15 | pattern: /\bm(?:isa|vendorid|archid|hardid|status|tvec|ideleg|ip|ie|counteren|scratch|epc|cause|tval)\b/, 16 | alias: 'class-name' 17 | }, 18 | 19 | 20 | 'rv32/64i-instructions': { 21 | pattern: /\b(?:(addi?w?)|(slti?u?)|(?:and|or|xor)i?|(?:sll|srl|sra)i?w?|lui|auipc|subw?|jal|jalr|beq|bne|bltu?|bgeu?|s[bhwd]|(l[bhw]u?)|ld)\b/, 22 | alias: 'keyword' 23 | }, 24 | 'csr-instructions': { 25 | pattern: /\b(?:csrr?[rws]i?)\b/, 26 | alias: 'keyword' 27 | }, 28 | 'privilege-instructions': { 29 | pattern: /\b(?:ecall|ebreak|[msu]ret|wfi|sfence.vma)\b/, 30 | alias: 'keyword' 31 | }, 32 | 'pseudo-instructions': { 33 | pattern: /\b(?:nop|li|la|mv|not|neg|negw|sext.w|seqz|snez|sltz|sgtz|f(?:mv|abs|neg).(?:s|d)|b(?:eq|ne|le|ge|lt)z|bgt|ble|bgtu|bleu|j|jr|ret|call)\b/, 34 | alias: 'important' 35 | }, 36 | 37 | 'relocation-functions': { 38 | pattern: /(?:%hi|%lo|%pcrel_hi|%pcrel_lo|%tprel_(?:hi|lo|add))/, 39 | alias: 'important' 40 | }, 41 | 42 | /* 'function': /function/, */ 43 | 44 | 'operator': /operator/, 45 | 'data-emitting-directives': { 46 | pattern: /(?:.2byte|.4byte|.8byte|.quad|.half|.word|.dword|.byte|.dtpreldword|.dtprelword|.sleb128|.uleb128|.asciz|.string|.incbin|.zero)/, 47 | alias: 'tag' 48 | }, 49 | 'alignment-directives': { 50 | pattern: /(?:.align|.balign|.p2align)/, 51 | alias: 'tag' 52 | }, 53 | 'symbol-directives': { 54 | pattern: /(?:.globl|.local|.equ)/, 55 | alias: 'tag' 56 | }, 57 | 'section-directives': { 58 | pattern: /(?:.text|.data|.rodata|.bss|.comm|.common|.section)/, 59 | alias: 'tag' 60 | }, 61 | 'miscellaneous-directives': { 62 | pattern: /(?:.option|.macro|.endm|.file|.ident|.size|.type)/, 63 | alias: 'tag' 64 | }, 65 | 66 | 'labels': { 67 | pattern: /\S*:/, 68 | alias: 'operator' 69 | }, 70 | 'number': /\b(?:(?:0x|)[\da-f]+|(?:0o|)[0-7]+|\d+)\b/, 71 | 'last-literals': { 72 | pattern: /\b\S*\b/, 73 | alias: 'operator', 74 | }, 75 | 76 | }; 77 | -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/fonts/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/fonts/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/fonts/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-alerts/plugin.js: -------------------------------------------------------------------------------- 1 | // script to style blockquotes for info, warning, success and danger 2 | 3 | styleMap = { 4 | '[info]': { 5 | htmlStr: '', 6 | className: 'info', 7 | }, 8 | '[warning]': { 9 | htmlStr: '', 10 | className: 'warning' 11 | }, 12 | '[danger]': { 13 | htmlStr: '', 14 | className: 'danger' 15 | }, 16 | '[success]': { 17 | htmlStr: '', 18 | className: 'success' 19 | } 20 | } 21 | 22 | require(["gitbook", "jQuery"], function(gitbook, $) { 23 | // Load 24 | gitbook.events.bind("page.change", function(e, config) { 25 | bqs = $('blockquote'); 26 | bqs.each(function(index) { 27 | 28 | for (key in styleMap) { 29 | htmlStr = $(this).html() 30 | 31 | if (htmlStr.indexOf(key) > 0) { 32 | // remove key from text 33 | var style = styleMap[key]; 34 | 35 | htmlStr = htmlStr.replace(key, style.htmlStr); 36 | $(this).html(htmlStr); 37 | 38 | // set style 39 | $(this).addClass(style.className) 40 | } 41 | } 42 | 43 | }) 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-alerts/style.css: -------------------------------------------------------------------------------- 1 | .info { 2 | background: #eff5ff; 3 | border-color: #42acf3; 4 | color: #444; 5 | } 6 | .info strong { 7 | color: #42acf3; 8 | } 9 | .warning { 10 | background: #fcf8f2; 11 | border-color: #f0ad4e; 12 | color: #444; 13 | } 14 | .warning strong { 15 | color: #f0ad4e; 16 | } 17 | .danger { 18 | background: #fdf7f7; 19 | border-color: #d9534f; 20 | color: #444; 21 | } 22 | .danger strong { 23 | color: #d9534f; 24 | } 25 | .success { 26 | background: #f3f8f3; 27 | border-color: #50af51; 28 | color: #444; 29 | } 30 | .success strong { 31 | color: #50af51; 32 | } -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-chapter-fold/chapter-fold.css: -------------------------------------------------------------------------------- 1 | .book .book-summary .chapter > .articles { 2 | overflow: hidden; 3 | max-height: 0px; 4 | } 5 | 6 | .book .book-summary .chapter.expanded > .articles { 7 | max-height: 9999px; 8 | } 9 | 10 | .book .book-summary .exc-trigger { 11 | position: absolute; 12 | left: 12px; 13 | top: 12px; 14 | } 15 | 16 | .book .book-summary ul.summary li a, 17 | .book .book-summary ul.summary li span { 18 | padding-left: 30px; 19 | } 20 | 21 | .book .book-summary .exc-trigger:before { 22 | content: "\f105"; 23 | } 24 | 25 | .book .book-summary .expanded > a .exc-trigger:before, 26 | .book .book-summary .expanded > span .exc-trigger:before { 27 | content: "\f107"; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-chapter-fold/chapter-fold.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jQuery'], function(gitbook, $) { 2 | var TOGGLE_CLASSNAME = 'expanded', 3 | CHAPTER = '.chapter', 4 | ARTICLES = '.articles', 5 | TRIGGER_TEMPLATE = '', 6 | LS_NAMESPACE = 'expChapters'; 7 | var init = function () { 8 | // adding the trigger element to each ARTICLES parent and binding the event 9 | var chapterLink = $(ARTICLES).parent(CHAPTER).children('a'); 10 | chapterLink.append($(TRIGGER_TEMPLATE)); 11 | chapterLink.on('click', function (e) { 12 | e.preventDefault(); 13 | //e.stopPropagation(); 14 | toggle($(e.target).closest(CHAPTER)); 15 | }); 16 | 17 | expand(lsItem()); 18 | //expand current selected chapter with it's parents 19 | collapse($(CHAPTER)); 20 | var activeChapter = $(CHAPTER + '.active'); 21 | expand(activeChapter); 22 | expand(activeChapter.parents(CHAPTER)); 23 | } 24 | //on page.change will happend the function. 25 | 26 | var toggle = function ($chapter) { 27 | if ($chapter.hasClass('expanded')) { 28 | collapse($chapter); 29 | } else { 30 | expand($chapter); 31 | //$chapter.addClass('active').siblings().removeClass('active'); 32 | } 33 | } 34 | var collapse = function ($chapter) { 35 | if ($chapter.length && $chapter.hasClass(TOGGLE_CLASSNAME)) { 36 | $chapter.removeClass(TOGGLE_CLASSNAME); 37 | lsItem($chapter); 38 | } 39 | } 40 | var expand = function ($chapter) { 41 | if ($chapter.length && !$chapter.hasClass(TOGGLE_CLASSNAME)) { 42 | $chapter.addClass(TOGGLE_CLASSNAME); 43 | lsItem($chapter); 44 | } 45 | } 46 | var lsItem = function () { 47 | var map = JSON.parse(localStorage.getItem(LS_NAMESPACE)) || {} 48 | if (arguments.length) { 49 | var $chapters = arguments[0]; 50 | $chapters.each(function (index, element) { 51 | var level = $(this).data('level'); 52 | var value = $(this).hasClass(TOGGLE_CLASSNAME); 53 | map[level] = value; 54 | }) 55 | localStorage.setItem(LS_NAMESPACE, JSON.stringify(map)); 56 | } else { 57 | return $(CHAPTER).map(function(index, element){ 58 | if (map[$(this).data('level')]) { 59 | return this; 60 | } 61 | }) 62 | } 63 | } 64 | gitbook.events.bind('page.change', function() { 65 | init() 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-emphasize/plugin.css: -------------------------------------------------------------------------------- 1 | .pg-emphasize { 2 | border-radius: 2px; 3 | background: #FFFF88; 4 | padding:1px; 5 | } 6 | 7 | .pg-emphasize.pg-emphasize-red { 8 | background: #ffecec; 9 | } 10 | 11 | .pg-emphasize.pg-emphasize-green { 12 | background: #eaffea; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-hide-element/plugin.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | var opts; 3 | 4 | gitbook.events.bind('start', function(e, config) { 5 | opts = config['hide-element'].elements; 6 | }); 7 | 8 | gitbook.events.bind('page.change', function() { 9 | $.map(opts, function(ele) { 10 | $(ele).hide(); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_AMS-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_AMS-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_AMS-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_AMS-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_AMS-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_AMS-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_AMS-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_AMS-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Bold.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Bold.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Bold.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Bold.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Caligraphic-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Bold.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Bold.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Bold.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Bold.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Fraktur-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Bold.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Bold.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Bold.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Bold.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Italic.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Italic.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Italic.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Italic.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Main-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-BoldItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-BoldItalic.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-BoldItalic.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-BoldItalic.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-BoldItalic.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Italic.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Italic.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Italic.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Italic.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Math-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Bold.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Bold.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Bold.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Bold.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Italic.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Italic.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Italic.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Italic.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_SansSerif-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Script-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Script-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Script-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Script-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Script-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Script-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Script-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Script-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size1-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size1-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size1-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size1-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size1-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size1-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size1-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size1-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size2-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size2-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size2-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size2-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size2-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size2-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size2-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size2-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size3-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size3-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size3-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size3-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size3-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size3-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size3-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size3-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size4-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size4-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size4-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size4-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size4-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size4-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size4-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Size4-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Typewriter-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Typewriter-Regular.eot -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Typewriter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Typewriter-Regular.ttf -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Typewriter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Typewriter-Regular.woff -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Typewriter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/gitbook-plugin-katex/fonts/KaTeX_Typewriter-Regular.woff2 -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-lunr/search-lunr.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Define global search engine 6 | function LunrSearchEngine() { 7 | this.index = null; 8 | this.store = {}; 9 | this.name = 'LunrSearchEngine'; 10 | } 11 | 12 | // Initialize lunr by fetching the search index 13 | LunrSearchEngine.prototype.init = function() { 14 | var that = this; 15 | var d = $.Deferred(); 16 | 17 | $.getJSON(gitbook.state.basePath+'/search_index.json') 18 | .then(function(data) { 19 | // eslint-disable-next-line no-undef 20 | that.index = lunr.Index.load(data.index); 21 | that.store = data.store; 22 | d.resolve(); 23 | }); 24 | 25 | return d.promise(); 26 | }; 27 | 28 | // Search for a term and return results 29 | LunrSearchEngine.prototype.search = function(q, offset, length) { 30 | var that = this; 31 | var results = []; 32 | 33 | if (this.index) { 34 | results = $.map(this.index.search(q), function(result) { 35 | var doc = that.store[result.ref]; 36 | 37 | return { 38 | title: doc.title, 39 | url: doc.url, 40 | body: doc.summary || doc.body 41 | }; 42 | }); 43 | } 44 | 45 | return $.Deferred().resolve({ 46 | query: q, 47 | results: results.slice(0, length), 48 | count: results.length 49 | }).promise(); 50 | }; 51 | 52 | // Set gitbook research 53 | gitbook.events.bind('start', function(e, config) { 54 | var engine = gitbook.search.getEngine(); 55 | if (!engine) { 56 | gitbook.search.setEngine(LunrSearchEngine, config); 57 | } 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-mermaid-gb3/book/plugin.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook' 3 | ], function (gitbook) { 4 | gitbook.events.bind('page.change', function () { 5 | mermaid.init(); 6 | }); 7 | }); -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-prism/prism-dark.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js Dark theme for JavaScript, CSS and HTML 3 | * Based on the slides of the talk “/Reg(exp){2}lained/” 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: white; 10 | background: none; 11 | text-shadow: 0 -.1em .2em black; 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | font-size: 1em; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | @media print { 32 | code[class*="language-"], 33 | pre[class*="language-"] { 34 | text-shadow: none; 35 | } 36 | } 37 | 38 | pre[class*="language-"], 39 | :not(pre) > code[class*="language-"] { 40 | background: hsl(30, 20%, 25%); 41 | } 42 | 43 | /* Code blocks */ 44 | pre[class*="language-"] { 45 | padding: 1em; 46 | margin: .5em 0; 47 | overflow: auto; 48 | border: .3em solid hsl(30, 20%, 40%); 49 | border-radius: .5em; 50 | box-shadow: 1px 1px .5em black inset; 51 | } 52 | 53 | /* Inline code */ 54 | :not(pre) > code[class*="language-"] { 55 | padding: .15em .2em .05em; 56 | border-radius: .3em; 57 | border: .13em solid hsl(30, 20%, 40%); 58 | box-shadow: 1px 1px .3em -.1em black inset; 59 | white-space: normal; 60 | } 61 | 62 | .token.comment, 63 | .token.prolog, 64 | .token.doctype, 65 | .token.cdata { 66 | color: hsl(30, 20%, 50%); 67 | } 68 | 69 | .token.punctuation { 70 | opacity: .7; 71 | } 72 | 73 | .token.namespace { 74 | opacity: .7; 75 | } 76 | 77 | .token.property, 78 | .token.tag, 79 | .token.boolean, 80 | .token.number, 81 | .token.constant, 82 | .token.symbol { 83 | color: hsl(350, 40%, 70%); 84 | } 85 | 86 | .token.selector, 87 | .token.attr-name, 88 | .token.string, 89 | .token.char, 90 | .token.builtin, 91 | .token.inserted { 92 | color: hsl(75, 70%, 60%); 93 | } 94 | 95 | .token.operator, 96 | .token.entity, 97 | .token.url, 98 | .language-css .token.string, 99 | .style .token.string, 100 | .token.variable { 101 | color: hsl(40, 90%, 60%); 102 | } 103 | 104 | .token.atrule, 105 | .token.attr-value, 106 | .token.keyword { 107 | color: hsl(350, 40%, 70%); 108 | } 109 | 110 | .token.regex, 111 | .token.important { 112 | color: #e90; 113 | } 114 | 115 | .token.important, 116 | .token.bold { 117 | font-weight: bold; 118 | } 119 | .token.italic { 120 | font-style: italic; 121 | } 122 | 123 | .token.entity { 124 | cursor: help; 125 | } 126 | 127 | .token.deleted { 128 | color: red; 129 | } 130 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-prism/prism-funky.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js Funky theme 3 | * Based on “Polyfilling the gaps” talk slides http://lea.verou.me/polyfilling-the-gaps/ 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 10 | font-size: 1em; 11 | text-align: left; 12 | white-space: pre; 13 | word-spacing: normal; 14 | word-break: normal; 15 | word-wrap: normal; 16 | line-height: 1.5; 17 | 18 | -moz-tab-size: 4; 19 | -o-tab-size: 4; 20 | tab-size: 4; 21 | 22 | -webkit-hyphens: none; 23 | -moz-hyphens: none; 24 | -ms-hyphens: none; 25 | hyphens: none; 26 | } 27 | 28 | /* Code blocks */ 29 | pre[class*="language-"] { 30 | padding: .4em .8em; 31 | margin: .5em 0; 32 | overflow: auto; 33 | background: url('data:image/svg+xml;charset=utf-8,%0D%0A%0D%0A%0D%0A<%2Fsvg>'); 34 | background-size: 1em 1em; 35 | } 36 | 37 | code[class*="language-"] { 38 | background: black; 39 | color: white; 40 | box-shadow: -.3em 0 0 .3em black, .3em 0 0 .3em black; 41 | } 42 | 43 | /* Inline code */ 44 | :not(pre) > code[class*="language-"] { 45 | padding: .2em; 46 | border-radius: .3em; 47 | box-shadow: none; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: #aaa; 56 | } 57 | 58 | .token.punctuation { 59 | color: #999; 60 | } 61 | 62 | .token.namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.boolean, 69 | .token.number, 70 | .token.constant, 71 | .token.symbol { 72 | color: #0cf; 73 | } 74 | 75 | .token.selector, 76 | .token.attr-name, 77 | .token.string, 78 | .token.char, 79 | .token.builtin { 80 | color: yellow; 81 | } 82 | 83 | .token.operator, 84 | .token.entity, 85 | .token.url, 86 | .language-css .token.string, 87 | .token.variable, 88 | .token.inserted { 89 | color: yellowgreen; 90 | } 91 | 92 | .token.atrule, 93 | .token.attr-value, 94 | .token.keyword { 95 | color: deeppink; 96 | } 97 | 98 | .token.regex, 99 | .token.important { 100 | color: orange; 101 | } 102 | 103 | .token.important, 104 | .token.bold { 105 | font-weight: bold; 106 | } 107 | .token.italic { 108 | font-style: italic; 109 | } 110 | 111 | .token.entity { 112 | cursor: help; 113 | } 114 | 115 | .token.deleted { 116 | color: red; 117 | } 118 | 119 | /* Plugin styles: Diff Highlight */ 120 | pre.diff-highlight.diff-highlight > code .token.deleted:not(.prefix), 121 | pre > code.diff-highlight.diff-highlight .token.deleted:not(.prefix) { 122 | background-color: rgba(255, 0, 0, .3); 123 | display: inline; 124 | } 125 | 126 | pre.diff-highlight.diff-highlight > code .token.inserted:not(.prefix), 127 | pre > code.diff-highlight.diff-highlight .token.inserted:not(.prefix) { 128 | background-color: rgba(0, 255, 128, .3); 129 | display: inline; 130 | } 131 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-prism/prism-okaidia.css: -------------------------------------------------------------------------------- 1 | /** 2 | * okaidia theme for JavaScript, CSS and HTML 3 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 4 | * @author ocodia 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: #f8f8f2; 10 | background: none; 11 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | font-size: 1em; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | border-radius: 0.3em; 37 | } 38 | 39 | :not(pre) > code[class*="language-"], 40 | pre[class*="language-"] { 41 | background: #272822; 42 | } 43 | 44 | /* Inline code */ 45 | :not(pre) > code[class*="language-"] { 46 | padding: .1em; 47 | border-radius: .3em; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: #8292a2; 56 | } 57 | 58 | .token.punctuation { 59 | color: #f8f8f2; 60 | } 61 | 62 | .token.namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.constant, 69 | .token.symbol, 70 | .token.deleted { 71 | color: #f92672; 72 | } 73 | 74 | .token.boolean, 75 | .token.number { 76 | color: #ae81ff; 77 | } 78 | 79 | .token.selector, 80 | .token.attr-name, 81 | .token.string, 82 | .token.char, 83 | .token.builtin, 84 | .token.inserted { 85 | color: #a6e22e; 86 | } 87 | 88 | .token.operator, 89 | .token.entity, 90 | .token.url, 91 | .language-css .token.string, 92 | .style .token.string, 93 | .token.variable { 94 | color: #f8f8f2; 95 | } 96 | 97 | .token.atrule, 98 | .token.attr-value, 99 | .token.function, 100 | .token.class-name { 101 | color: #e6db74; 102 | } 103 | 104 | .token.keyword { 105 | color: #66d9ef; 106 | } 107 | 108 | .token.regex, 109 | .token.important { 110 | color: #fd971f; 111 | } 112 | 113 | .token.important, 114 | .token.bold { 115 | font-weight: bold; 116 | } 117 | .token.italic { 118 | font-style: italic; 119 | } 120 | 121 | .token.entity { 122 | cursor: help; 123 | } 124 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-prism/prism-solarizedlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | Solarized Color Schemes originally by Ethan Schoonover 3 | http://ethanschoonover.com/solarized 4 | 5 | Ported for PrismJS by Hector Matos 6 | Website: https://krakendev.io 7 | Twitter Handle: https://twitter.com/allonsykraken) 8 | */ 9 | 10 | /* 11 | SOLARIZED HEX 12 | --------- ------- 13 | base03 #002b36 14 | base02 #073642 15 | base01 #586e75 16 | base00 #657b83 17 | base0 #839496 18 | base1 #93a1a1 19 | base2 #eee8d5 20 | base3 #fdf6e3 21 | yellow #b58900 22 | orange #cb4b16 23 | red #dc322f 24 | magenta #d33682 25 | violet #6c71c4 26 | blue #268bd2 27 | cyan #2aa198 28 | green #859900 29 | */ 30 | 31 | code[class*="language-"], 32 | pre[class*="language-"] { 33 | color: #657b83; /* base00 */ 34 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 35 | font-size: 1em; 36 | text-align: left; 37 | white-space: pre; 38 | word-spacing: normal; 39 | word-break: normal; 40 | word-wrap: normal; 41 | 42 | line-height: 1.5; 43 | 44 | -moz-tab-size: 4; 45 | -o-tab-size: 4; 46 | tab-size: 4; 47 | 48 | -webkit-hyphens: none; 49 | -moz-hyphens: none; 50 | -ms-hyphens: none; 51 | hyphens: none; 52 | } 53 | 54 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 55 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 56 | background: #073642; /* base02 */ 57 | } 58 | 59 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 60 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 61 | background: #073642; /* base02 */ 62 | } 63 | 64 | /* Code blocks */ 65 | pre[class*="language-"] { 66 | padding: 1em; 67 | margin: .5em 0; 68 | overflow: auto; 69 | border-radius: 0.3em; 70 | } 71 | 72 | :not(pre) > code[class*="language-"], 73 | pre[class*="language-"] { 74 | background-color: #fdf6e3; /* base3 */ 75 | } 76 | 77 | /* Inline code */ 78 | :not(pre) > code[class*="language-"] { 79 | padding: .1em; 80 | border-radius: .3em; 81 | } 82 | 83 | .token.comment, 84 | .token.prolog, 85 | .token.doctype, 86 | .token.cdata { 87 | color: #93a1a1; /* base1 */ 88 | } 89 | 90 | .token.punctuation { 91 | color: #586e75; /* base01 */ 92 | } 93 | 94 | .token.namespace { 95 | opacity: .7; 96 | } 97 | 98 | .token.property, 99 | .token.tag, 100 | .token.boolean, 101 | .token.number, 102 | .token.constant, 103 | .token.symbol, 104 | .token.deleted { 105 | color: #268bd2; /* blue */ 106 | } 107 | 108 | .token.selector, 109 | .token.attr-name, 110 | .token.string, 111 | .token.char, 112 | .token.builtin, 113 | .token.url, 114 | .token.inserted { 115 | color: #2aa198; /* cyan */ 116 | } 117 | 118 | .token.entity { 119 | color: #657b83; /* base00 */ 120 | background: #eee8d5; /* base2 */ 121 | } 122 | 123 | .token.atrule, 124 | .token.attr-value, 125 | .token.keyword { 126 | color: #859900; /* green */ 127 | } 128 | 129 | .token.function, 130 | .token.class-name { 131 | color: #b58900; /* yellow */ 132 | } 133 | 134 | .token.regex, 135 | .token.important, 136 | .token.variable { 137 | color: #cb4b16; /* orange */ 138 | } 139 | 140 | .token.important, 141 | .token.bold { 142 | font-weight: bold; 143 | } 144 | .token.italic { 145 | font-style: italic; 146 | } 147 | 148 | .token.entity { 149 | cursor: help; 150 | } 151 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-prism/prism-tomorrow.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML 3 | * Based on https://github.com/chriskempson/tomorrow-theme 4 | * @author Rose Pritchard 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: #ccc; 10 | background: none; 11 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 12 | font-size: 1em; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | word-break: normal; 17 | word-wrap: normal; 18 | line-height: 1.5; 19 | 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | 24 | -webkit-hyphens: none; 25 | -moz-hyphens: none; 26 | -ms-hyphens: none; 27 | hyphens: none; 28 | 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | } 37 | 38 | :not(pre) > code[class*="language-"], 39 | pre[class*="language-"] { 40 | background: #2d2d2d; 41 | } 42 | 43 | /* Inline code */ 44 | :not(pre) > code[class*="language-"] { 45 | padding: .1em; 46 | border-radius: .3em; 47 | white-space: normal; 48 | } 49 | 50 | .token.comment, 51 | .token.block-comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: #999; 56 | } 57 | 58 | .token.punctuation { 59 | color: #ccc; 60 | } 61 | 62 | .token.tag, 63 | .token.attr-name, 64 | .token.namespace, 65 | .token.deleted { 66 | color: #e2777a; 67 | } 68 | 69 | .token.function-name { 70 | color: #6196cc; 71 | } 72 | 73 | .token.boolean, 74 | .token.number, 75 | .token.function { 76 | color: #f08d49; 77 | } 78 | 79 | .token.property, 80 | .token.class-name, 81 | .token.constant, 82 | .token.symbol { 83 | color: #f8c555; 84 | } 85 | 86 | .token.selector, 87 | .token.important, 88 | .token.atrule, 89 | .token.keyword, 90 | .token.builtin { 91 | color: #cc99cd; 92 | } 93 | 94 | .token.string, 95 | .token.char, 96 | .token.attr-value, 97 | .token.regex, 98 | .token.variable { 99 | color: #7ec699; 100 | } 101 | 102 | .token.operator, 103 | .token.entity, 104 | .token.url { 105 | color: #67cdcc; 106 | } 107 | 108 | .token.important, 109 | .token.bold { 110 | font-weight: bold; 111 | } 112 | .token.italic { 113 | font-style: italic; 114 | } 115 | 116 | .token.entity { 117 | cursor: help; 118 | } 119 | 120 | .token.inserted { 121 | color: green; 122 | } 123 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-prism/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: black; 10 | background: none; 11 | text-shadow: 0 1px white; 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | font-size: 1em; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 33 | text-shadow: none; 34 | background: #b3d4fc; 35 | } 36 | 37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 38 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 39 | text-shadow: none; 40 | background: #b3d4fc; 41 | } 42 | 43 | @media print { 44 | code[class*="language-"], 45 | pre[class*="language-"] { 46 | text-shadow: none; 47 | } 48 | } 49 | 50 | /* Code blocks */ 51 | pre[class*="language-"] { 52 | padding: 1em; 53 | margin: .5em 0; 54 | overflow: auto; 55 | } 56 | 57 | :not(pre) > code[class*="language-"], 58 | pre[class*="language-"] { 59 | background: #f5f2f0; 60 | } 61 | 62 | /* Inline code */ 63 | :not(pre) > code[class*="language-"] { 64 | padding: .1em; 65 | border-radius: .3em; 66 | white-space: normal; 67 | } 68 | 69 | .token.comment, 70 | .token.prolog, 71 | .token.doctype, 72 | .token.cdata { 73 | color: slategray; 74 | } 75 | 76 | .token.punctuation { 77 | color: #999; 78 | } 79 | 80 | .token.namespace { 81 | opacity: .7; 82 | } 83 | 84 | .token.property, 85 | .token.tag, 86 | .token.boolean, 87 | .token.number, 88 | .token.constant, 89 | .token.symbol, 90 | .token.deleted { 91 | color: #905; 92 | } 93 | 94 | .token.selector, 95 | .token.attr-name, 96 | .token.string, 97 | .token.char, 98 | .token.builtin, 99 | .token.inserted { 100 | color: #690; 101 | } 102 | 103 | .token.operator, 104 | .token.entity, 105 | .token.url, 106 | .language-css .token.string, 107 | .style .token.string { 108 | color: #9a6e3a; 109 | /* This background color was intended by the author of this theme. */ 110 | background: hsla(0, 0%, 100%, .5); 111 | } 112 | 113 | .token.atrule, 114 | .token.attr-value, 115 | .token.keyword { 116 | color: #07a; 117 | } 118 | 119 | .token.function, 120 | .token.class-name { 121 | color: #DD4A68; 122 | } 123 | 124 | .token.regex, 125 | .token.important, 126 | .token.variable { 127 | color: #e90; 128 | } 129 | 130 | .token.important, 131 | .token.bold { 132 | font-weight: bold; 133 | } 134 | .token.italic { 135 | font-style: italic; 136 | } 137 | 138 | .token.entity { 139 | cursor: help; 140 | } 141 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-search/search-engine.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Global search objects 6 | var engine = null; 7 | var initialized = false; 8 | 9 | // Set a new search engine 10 | function setEngine(Engine, config) { 11 | initialized = false; 12 | engine = new Engine(config); 13 | 14 | init(config); 15 | } 16 | 17 | // Initialize search engine with config 18 | function init(config) { 19 | if (!engine) throw new Error('No engine set for research. Set an engine using gitbook.research.setEngine(Engine).'); 20 | 21 | return engine.init(config) 22 | .then(function() { 23 | initialized = true; 24 | gitbook.events.trigger('search.ready'); 25 | }); 26 | } 27 | 28 | // Launch search for query q 29 | function query(q, offset, length) { 30 | if (!initialized) throw new Error('Search has not been initialized'); 31 | return engine.search(q, offset, length); 32 | } 33 | 34 | // Get stats about search 35 | function getEngine() { 36 | return engine? engine.name : null; 37 | } 38 | 39 | function isInitialized() { 40 | return initialized; 41 | } 42 | 43 | // Initialize gitbook.search 44 | gitbook.search = { 45 | setEngine: setEngine, 46 | getEngine: getEngine, 47 | query: query, 48 | isInitialized: isInitialized 49 | }; 50 | }); -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-search/search.css: -------------------------------------------------------------------------------- 1 | /* 2 | This CSS only styled the search results section, not the search input 3 | It defines the basic interraction to hide content when displaying results, etc 4 | */ 5 | #book-search-results .search-results { 6 | display: none; 7 | } 8 | #book-search-results .search-results ul.search-results-list { 9 | list-style-type: none; 10 | padding-left: 0; 11 | } 12 | #book-search-results .search-results ul.search-results-list li { 13 | margin-bottom: 1.5rem; 14 | padding-bottom: 0.5rem; 15 | /* Highlight results */ 16 | } 17 | #book-search-results .search-results ul.search-results-list li p em { 18 | background-color: rgba(255, 220, 0, 0.4); 19 | font-style: normal; 20 | } 21 | #book-search-results .search-results .no-results { 22 | display: none; 23 | } 24 | #book-search-results.open .search-results { 25 | display: block; 26 | } 27 | #book-search-results.open .search-noresults { 28 | display: none; 29 | } 30 | #book-search-results.no-results .search-results .has-results { 31 | display: none; 32 | } 33 | #book-search-results.no-results .search-results .no-results { 34 | display: block; 35 | } 36 | -------------------------------------------------------------------------------- /docs/gitbook/gitbook-plugin-sharing/buttons.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | var SITES = { 3 | 'facebook': { 4 | 'label': 'Facebook', 5 | 'icon': 'fa fa-facebook', 6 | 'onClick': function(e) { 7 | e.preventDefault(); 8 | window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href)); 9 | } 10 | }, 11 | 'twitter': { 12 | 'label': 'Twitter', 13 | 'icon': 'fa fa-twitter', 14 | 'onClick': function(e) { 15 | e.preventDefault(); 16 | window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href)); 17 | } 18 | }, 19 | 'google': { 20 | 'label': 'Google+', 21 | 'icon': 'fa fa-google-plus', 22 | 'onClick': function(e) { 23 | e.preventDefault(); 24 | window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href)); 25 | } 26 | }, 27 | 'weibo': { 28 | 'label': 'Weibo', 29 | 'icon': 'fa fa-weibo', 30 | 'onClick': function(e) { 31 | e.preventDefault(); 32 | window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)); 33 | } 34 | }, 35 | 'instapaper': { 36 | 'label': 'Instapaper', 37 | 'icon': 'fa fa-instapaper', 38 | 'onClick': function(e) { 39 | e.preventDefault(); 40 | window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href)); 41 | } 42 | }, 43 | 'vk': { 44 | 'label': 'VK', 45 | 'icon': 'fa fa-vk', 46 | 'onClick': function(e) { 47 | e.preventDefault(); 48 | window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href)); 49 | } 50 | } 51 | }; 52 | 53 | 54 | 55 | gitbook.events.bind('start', function(e, config) { 56 | var opts = config.sharing; 57 | 58 | // Create dropdown menu 59 | var menu = $.map(opts.all, function(id) { 60 | var site = SITES[id]; 61 | 62 | return { 63 | text: site.label, 64 | onClick: site.onClick 65 | }; 66 | }); 67 | 68 | // Create main button with dropdown 69 | if (menu.length > 0) { 70 | gitbook.toolbar.createButton({ 71 | icon: 'fa fa-share-alt', 72 | label: 'Share', 73 | position: 'right', 74 | dropdown: [menu] 75 | }); 76 | } 77 | 78 | // Direct actions to share 79 | $.each(SITES, function(sideId, site) { 80 | if (!opts[sideId]) return; 81 | 82 | gitbook.toolbar.createButton({ 83 | icon: site.icon, 84 | label: site.text, 85 | position: 'right', 86 | onClick: site.onClick 87 | }); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /docs/gitbook/images/apple-touch-icon-precomposed-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/images/apple-touch-icon-precomposed-152.png -------------------------------------------------------------------------------- /docs/gitbook/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/gitbook/images/favicon.ico -------------------------------------------------------------------------------- /docs/os2atc2019/.gitignore: -------------------------------------------------------------------------------- 1 | os2atc 2 | *.pdf 3 | *.html -------------------------------------------------------------------------------- /docs/os2atc2019/figures/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/os2atc2019/figures/github.png -------------------------------------------------------------------------------- /docs/os2atc2019/figures/init_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/os2atc2019/figures/init_stack.png -------------------------------------------------------------------------------- /docs/os2atc2019/figures/memory_handler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/os2atc2019/figures/memory_handler.png -------------------------------------------------------------------------------- /docs/os2atc2019/figures/memory_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/os2atc2019/figures/memory_set.png -------------------------------------------------------------------------------- /docs/os2atc2019/figures/sie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/os2atc2019/figures/sie.png -------------------------------------------------------------------------------- /docs/os2atc2019/figures/software-stacks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/os2atc2019/figures/software-stacks.png -------------------------------------------------------------------------------- /docs/os2atc2019/figures/sv39_addr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/os2atc2019/figures/sv39_addr.png -------------------------------------------------------------------------------- /docs/os2atc2019/figures/sv39_pte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/os2atc2019/figures/sv39_pte.png -------------------------------------------------------------------------------- /docs/os2atc2019/figures/sv39_rwx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/os2atc2019/figures/sv39_rwx.png -------------------------------------------------------------------------------- /docs/os2atc2019/figures/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/os2atc2019/figures/switch.png -------------------------------------------------------------------------------- /docs/os2atc2019/figures/thread-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/docs/os2atc2019/figures/thread-arch.png -------------------------------------------------------------------------------- /docs/plugin_examples.txt: -------------------------------------------------------------------------------- 1 | This text is {% em %}highlighted!{% endem %} 2 | 3 | > **[info] For info** 4 | > 5 | > This is a info! 6 | > 7 | 8 | 23333 9 | 10 | *.json 11 | 12 | $$a+b=c$$ -------------------------------------------------------------------------------- /exercise/code/mutex.rs: -------------------------------------------------------------------------------- 1 | use crate::interrupt::{disable_and_store, restore}; 2 | use crate::process::yield_now; 3 | use core::cell::UnsafeCell; 4 | use core::default::Default; 5 | use core::marker::Sync; 6 | use core::ops::{Deref, DerefMut, Drop}; 7 | 8 | /// This type provides MUTual EXclusion based on spinning. 9 | pub struct Mutex { 10 | lock: UnsafeCell, 11 | data: UnsafeCell, 12 | } 13 | 14 | /// A guard to which the protected data can be accessed 15 | /// 16 | /// When the guard falls out of scope it will release the lock. 17 | #[derive(Debug)] 18 | pub struct MutexGuard<'a, T: ?Sized + 'a> { 19 | lock: &'a mut bool, 20 | data: &'a mut T, 21 | } 22 | 23 | // Same unsafe impls as `std::sync::Mutex` 24 | unsafe impl Sync for Mutex {} 25 | unsafe impl Send for Mutex {} 26 | 27 | impl Mutex { 28 | /// Creates a new spinlock wrapping the supplied data. 29 | pub const fn new(user_data: T) -> Mutex { 30 | Mutex { 31 | lock: UnsafeCell::new(false), 32 | data: UnsafeCell::new(user_data), 33 | } 34 | } 35 | 36 | /// Consumes this mutex, returning the underlying data. 37 | pub fn into_inner(self) -> T { 38 | // We know statically that there are no outstanding references to 39 | // `self` so there's no need to lock. 40 | let Mutex { data, .. } = self; 41 | data.into_inner() 42 | } 43 | } 44 | 45 | impl Mutex { 46 | fn obtain_lock(&self) { 47 | // TODO 48 | // try to get lock 49 | // what to do if get fail? 50 | } 51 | 52 | /// Locks the spinlock and returns a guard. 53 | /// 54 | /// The returned value may be dereferenced for data access 55 | /// and the lock will be dropped when the guard falls out of scope. 56 | pub fn lock(&self) -> MutexGuard { 57 | self.obtain_lock(); 58 | MutexGuard { 59 | lock: unsafe { &mut *self.lock.get() }, 60 | data: unsafe { &mut *self.data.get() }, 61 | } 62 | } 63 | } 64 | 65 | impl Default for Mutex { 66 | fn default() -> Mutex { 67 | Mutex::new(Default::default()) 68 | } 69 | } 70 | 71 | impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> { 72 | type Target = T; 73 | fn deref(&self) -> &T { 74 | &*self.data 75 | } 76 | } 77 | 78 | impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> { 79 | fn deref_mut(&mut self) -> &mut T { 80 | &mut *self.data 81 | } 82 | } 83 | 84 | impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> { 85 | /// The dropping of the MutexGuard will release the lock it was created from. 86 | fn drop(&mut self) { 87 | // TODO 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /exercise/code/timer.rs: -------------------------------------------------------------------------------- 1 | //! A naive timer 2 | 3 | use alloc::{boxed::Box, collections::BinaryHeap}; 4 | use core::cmp::Ordering; 5 | 6 | /// The type of callback function. 7 | type Callback = Box; 8 | 9 | struct Node(u64, Callback); 10 | 11 | impl Ord for Node { 12 | fn cmp(&self, other: &Self) -> Ordering { 13 | if self.0 < other.0 { 14 | return Ordering::Greater; 15 | } else if self.0 > other.0 { 16 | return Ordering::Less; 17 | } else { 18 | return Ordering::Equal; 19 | } 20 | } 21 | } 22 | 23 | impl Eq for Node {} 24 | 25 | impl PartialOrd for Node { 26 | fn partial_cmp(&self, other: &Self) -> Option { 27 | Some(self.cmp(other)) 28 | } 29 | } 30 | 31 | impl PartialEq for Node { 32 | fn eq(&self, other: &Self) -> bool { 33 | self.0 == other.0 34 | } 35 | } 36 | 37 | /// A naive timer 38 | #[derive(Default)] 39 | pub struct Timer { 40 | events: BinaryHeap, 41 | } 42 | 43 | impl Timer { 44 | /// Add a timer with given `deadline`. 45 | /// 46 | /// The `callback` will be called on timer expired. 47 | pub fn add(&mut self, deadline: u64, callback: impl FnOnce() + Send + Sync + 'static) { 48 | self.events.push(Node(deadline, Box::new(callback))); 49 | } 50 | 51 | /// Called on each tick. 52 | /// 53 | /// The caller should give the current time `now`, and all expired timer will be trigger. 54 | pub fn tick(&mut self, now: u64) { 55 | while let Some(event) = self.events.peek() { 56 | if event.0 > now { 57 | return; 58 | } 59 | let callback = self.events.pop().unwrap().1; 60 | callback(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /exercise/introduction.md: -------------------------------------------------------------------------------- 1 | ## 练习说明 2 | 3 | 1. 所有题目分数总和:140 ,满分 100 ,超出 100 按 100 计算。 4 | 2. 可以把 https://github.com/rcore-os/rCore_tutorial 的 master 分支作为起点,逐步完成所有 8 个实验;也可以按照 tutorial 一步一步完善内核的功能,每完成若干个章节,去做实验作为练习。两种做实验的方式都是允许的,只要能够通过评测脚本的测试即可。 5 | 6 | ## 实验报告要求 7 | 8 | 1. 对于实验$$X(1\leq X\leq 8)$$,使用 markdown 格式编写实验报告并命名为`labX.md`(如 `lab1.md`),在项目根目录下创建 `report` 文件夹并将该实验报告放在其中。不接受其他命名/格式的实验报告。 9 | 2. 不要在报告里大段粘贴代码,讲清楚实验过程和思路即可。 10 | 3. 有需要的话可以新建分支或者保留 commit ,独立检查每个功能。 11 | 4. 每道题的报告均会进行字数统计,字数超过 `平均字数 * 3` 或低于 `平均字数 / 3` 的同学可能被酌情扣分。(求求你们别卷了) 12 | 5. 提交到 `git.tsinghua` 。 13 | 6. **注意:完成后,请把代码和文档在 Deadline 之前提交到规定的地方。不接受迟交和晚交的情况。** 14 | 15 | ## 测评方式 16 | 17 | ### 整体情况 18 | 19 | 目前为止(2020-03-07): 20 | 21 | - lab1/4 不进行任何测试 22 | - lab2/3/7 进行内核态测试 23 | - lab5/6/8 进行用户态测试 24 | 25 | 我们将测试流程打包成了一个脚本方便同学们自我检测。 26 | 27 | ### 评测脚本使用方法 28 | 29 | 在使用评测脚本之前,请确保自己的代码目录结构与 [master 分支](https://github.com/rcore-os/rCore_tutorial/tree/master) 基本一致,并将其中的 [评测脚本 `test.py`](https://github.com/rcore-os/rCore_tutorial/blob/master/test.py) 与 [放置测试程序的目录 `test/`](https://github.com/rcore-os/rCore_tutorial/tree/master/test) 还有 [Makefile](https://github.com/rcore-os/rCore_tutorial/blob/master/Makefile) 置于你的代码仓库的根目录中。 30 | 测评脚本使用方法如下: 31 | `python3 test.py labX` ,其中$$X\in\{2,3,5,6,7,8\}$$,可以自动完成代码的替换工作(内核态、用户态的替换方式的细节参见下面)并进行评测,最后将运行结果放在 `labX.result` 文件中。如果这个过程没有出现错误(如编译错误、或环境配置有问题等),评测脚本还会直接打开 `labX.result` 文件查看运行结果。不必担心替换会污染代码,脚本会自动完成备份和恢复工作。 32 | 33 | ### 内核态测试 34 | 35 | 在运行内核态测试 (lab2/3/7) 之前,请确保 `os/src/init.rs` 存在且完成的是内核初始化的工作。 36 | 评测脚本会直接将 `os/src/init.rs` 替换为对应的内核态测试程序 `test/XX_test.rs` 并 `make run` 。 37 | 38 | > **[info] 对于在做 lab2/3 而 tutorial 进度已经到第八章第二小节的同学** 39 | > 40 | > lab2/3 的测试程序中去掉了第八章之后 `init.rs` 中嵌入用户镜像的引用代码如下: 41 | > 42 | > ```rust 43 | > // os/src/init.rs 44 | > global_asm!(include_str!("link_user.S")); 45 | > ``` 46 | > 47 | > 因此,为了能够通过测试脚本测试 lab2/3 ,一种可行的对于原版代码的修改方式为: 48 | > 49 | > 将下面的代码注释掉: 50 | > 51 | > ```rust 52 | > // os/src/fs/mod.rs 53 | > extern "C" { 54 | > fn _user_img_start(); 55 | > fn _user_img_end(); 56 | > }; 57 | > let start = _user_img_start as usize; 58 | > let end = _user_img_end as usize; 59 | > Arc::new(unsafe { device::MemBuf::new(start, end) }) 60 | > ``` 61 | > 62 | > 并替换为 63 | > 64 | > ```rust 65 | > // os/src/fs/mod.rs 66 | > Arc::new(unsafe { device::MemBuf::new(0, 0) }) 67 | > ``` 68 | 69 | ### 用户态测试 70 | 71 | 在运行用户态测试 (lab5/6/8) 之前,请确保 `os/src/process/mod.rs` 存在,且在 `process::init()` 函数中会通过 `execute('rust/user_shell', None)` 会将用户终端加载到内存并放入进程池。 72 | 如果用户态测试程序为 `test/usr/XX_test.rs`,评测脚本会将上述提到的 `rust/user_shell` 替换为 `rust/XX_test`,即不经过用户终端直接 `make run` 运行用户程序。目前脚本的功能并不完善,无法在所有进程结束后自动退出,因此我们等待数秒钟通过 `C-a + x` 退出 Qemu 让脚本继续运行。 73 | 74 | > **[info] 提前实现 execute** 75 | > 76 | > 注意到,`execute` 直到第九章的第三小节才完全实现。然而做 lab5/6 只要求做完前八章。为了支持用户态测试,一种可行的方法是: 77 | > 78 | > 1. 完成第九章第一小节; 79 | > 2. 然后跳过第九章第二小节,直接把第九章第三小节的 `execute` 函数移植过来; 80 | > 3. 最后将代码略作修改,调用 `execute` 函数完成 `process::init()`,使得 lab5/6 的用户态测试可以正常运行。 81 | -------------------------------------------------------------------------------- /exercise/part1.md: -------------------------------------------------------------------------------- 1 | # 1. 中断异常 2 | 3 | ## 实验要求 4 | 5 | 1. 阅读理解文档 1~3 章,并完成环境搭建。 6 | 2. 回答:详细描述 rcore 中处理中断异常的流程(从异常的产生开始)。(2 分) 7 | 3. 回答:对于任何中断,`__alltraps` 中都需要保存所有寄存器吗?请说明理由。(2 分) 8 | 4. 编程:在任意位置触发一条非法指令异常(如:mret),在 `rust_trap` 中捕获并对其进行处理(简单 `print & panic` 即可)。(6 分) 9 | 10 | ## 实验帮助 11 | 12 | - 参考资料 13 | 14 | - [RV 硬件简要手册-中文](http://crva.ict.ac.cn/documents/RISC-V-Reader-Chinese-v2p1.pdf) :重点第 10 章 15 | - [RV 硬件规范手册-英文](https://riscv.org/specifications/privileged-isa/) 16 | 17 | - 非法指令可以加在任意位置,比如在通过内联汇编加入,也可以直接修改汇编。 18 | - 查阅参考资料,判断自己触发的异常属于什么类型的,在 `rust_trap` 中完善 `match` 的情况。 19 | -------------------------------------------------------------------------------- /exercise/part2.md: -------------------------------------------------------------------------------- 1 | # 2. 物理内存管理 2 | 3 | ## 实验要求 4 | 5 | 1. 阅读理解文档第四章。 6 | 2. 回答:如果 OS 无法提前知道当前硬件的可用物理内存范围,请问你有何办法让 OS 获取可用物理内存范围?(2 分) 7 | 3. 编程:实现 `FirstFitAllocator` ,接口参考 `SegmentTreeAllocator` ,并完成内部实现(可参考 [ucore](https://github.com/LearningOS/ucore_os_lab/blob/master/labcodes_answer/lab2_result/kern/mm/default_pmm.c#L122) 中的算法)。(8 分) 8 | 9 | ## 实验指导 10 | 11 | - First Fit 就是蛮力寻找第一块大小合适的连续内存进行分配。 12 | - 这里可以简单的用一维数组维护。由于没有性能要求,$$O(n^2)$$ 查找都行。(可以参考 ucore ,不过那个相对复杂一些) 13 | - 测试方法:``python3 test.py lab2``,结果保存在 `lab2.result` 文件中。 14 | 15 | **说明:需要参考 `init.rs` 增加部分接口** 16 | 17 | ```rust 18 | pub fn init_allocator(l: usize, r: usize) { 19 | FRAME_ALLOCATOR.lock().init(l, r); 20 | } 21 | 22 | pub fn alloc_frame() -> Option { 23 | alloc_frames(1) 24 | } 25 | 26 | // 分配 cnt 块连续的帧 27 | pub fn alloc_frames(cnt: usize) -> Option { 28 | if let Some(frame) = FRAME_ALLOCATOR.lock().alloc(cnt) { 29 | return Some(Frame::of_ppn(frame)); 30 | } 31 | return None; 32 | } 33 | 34 | pub fn dealloc_frame(f: Frame) { 35 | dealloc_frames(f, 1) 36 | } 37 | 38 | // 释放以 f 为起始地址,cnt 块连续的帧 39 | pub fn dealloc_frames(f: Frame, cnt: usize) { 40 | FRAME_ALLOCATOR.lock().dealloc(f.number(), cnt) 41 | } 42 | ``` 43 | 44 | > [测试文件](https://github.com/rcore-os/rCore_tutorial/blob/master/test/pmm_test.rs) 45 | > 46 | > 如果输出了 `8/8` ,则表示通过测试,基 ying 本 gai 没有问题 47 | -------------------------------------------------------------------------------- /exercise/part3.md: -------------------------------------------------------------------------------- 1 | # 3. 虚拟内存管理 2 | 3 | ## 实验要求 4 | 5 | 1. 阅读理解文档第五章,并完成编译运行五章代码。 6 | 2. 回答问题: 7 | 1. 现有页面替换算法框架的实现存在问题,请解释为什么,并提供你的解决方案(自然语言表述即可,无需编程实现) (10分) 8 | 3. 编程实现(20分): 9 | 编程解决:实现时钟页面替换算法。详见下文实验指导 10 | > 当前框架从master分支出发,实现了用于用户进程的fifo页面替换算法。请同学**自行merge** lab3-base分支的代码,然后在**仅修改fifo.rs**的要求下,实现时钟页面替换算法。 11 | 12 | ## 实验指导 13 | 14 | - 思考:在进行重映射之前的页表是在哪创建的,什么样子的? 15 | - 思考:页表中保存了下一级页表的权限和物理地址(PPN),权限在哪里设置的?物理地址在哪分配的? 16 | 17 | 1. 从当前master分支实现页面替换算法的过程。 18 | 在当前master分支的基础上,为了实现页面替换,我们主要进行了如下三方面的拓展。 19 | * 页面替换接口设计 20 | 我们将页面替换算法的对外接口进行了一定的抽象,组织成一个trait PageReplace: 21 | ```rust 22 | // os/src/memory/page_replace/mod.rs 23 | pub trait PageReplace: Send { 24 | fn push_frame(&mut self, vaddr: usize, pt: Arc>); 25 | fn choose_victim(&mut self) -> Option<(usize, Arc>)>; 26 | fn swap_out_one(&mut self) -> Option; 27 | fn do_pgfault(&mut self, entry: &mut PageTableEntry, vaddr: usize); 28 | fn tick(&self); 29 | } 30 | ``` 31 | 其中: 32 | * push_frame用于加入物理页帧到算法中。 33 | * choose_victim用于选择出一个用于交换的物理页帧。 34 | * swap_out_one接口将会调用choose_victim,选择一个物理页帧,将其内容写入到磁盘,并修改页表项,返回一个可用的物理页帧。 35 | * do_pgfault用于处理缺页中断,将磁盘中特定位置的物理页帧内容写回到内存中,并修复映射。 36 | * tick接口则视为动态页面替换算法设计的接口。 37 | 上述五个接口中,do_pgfault以及swap_out_one已经有默认实现,当实现某一特定页面算法时,仅需对另外三个接口给出特定实现。更详细的代码细节可以参考现有的fifo算法实现。 38 | * 模拟交换分区 39 | 由于当前master分支不包含磁盘驱动。所以在fs/mod.rs中,我们在编译时分配了一个2M大小(512个页)的u8数组,作为模拟的交换分区。并同时实现对外的两个接口: 40 | * disk_page_write:将给定物理页`page`的内容复制到交换分区中的某一位置,并返回该页在交换分区中的具体位置。 41 | * disk_page_read:从给定的磁盘分区位置`pos`中读取一个页的内容到给定的物理页面`page`, 42 | * 缺页中断处理 43 | 当发生缺页中断时,我们借助硬件提供的异常信息,获取当前发生缺页异常的虚拟地址,从而获取该虚拟地址所指向的页表项,将页表项传递给全局页面替换管理器PAGE_REPLACE_HANDLER的do_pgfault接口进行处理,修复映射关系。 44 | 2. 测试 45 | [测试文件](https://github.com/rcore-os/rCore_tutorial/blob/master/test/vm_test.rs) 46 | 为MemorySet添加一个get_table接口(参考[这里](https://github.com/rcore-os/rCore_tutorial/blob/pgreplace_test/os/src/memory/memory_set/mod.rs#L112))。 47 | 48 | ``python3 test.py lab3`` 即可测试。 49 | -------------------------------------------------------------------------------- /exercise/part4.md: -------------------------------------------------------------------------------- 1 | # 4. 线程管理 2 | 3 | ## 实验要求 4 | 5 | 1. 阅读文档第六章。 6 | 2. 回答:详细描述第六章文档中 `process::init` 的执行过程。(4 分) 7 | 3. 回答:给出 `switch` 时,重要寄存器的使用情况,画出栈的使用情况。(6 分) 8 | 4. 本章无编程练习,将在下一节与用户进程一同考察。 9 | 10 | ## 实验指导 11 | 12 | - 注意 RISCV 规范,程序在不同函数之间的跳转情况(关注 ra 寄存器的赋值)。 13 | -------------------------------------------------------------------------------- /exercise/part5.md: -------------------------------------------------------------------------------- 1 | # 5. 用户进程(+ 虚拟内存管理 + 线程管理) 2 | 3 | ## 实验要求 4 | 5 | 1. 阅读理解文档第八章。 6 | 2. 编程实现:为 rcore 增加 `sys_fork` 。(20 分) 7 | 8 | ## 实验指导 9 | 10 | - [测试文件](https://github.com/rcore-os/rCore_tutorial/blob/master/test/usr/fork_test.rs) 11 | 12 | - 测试方法:`python3 test.py lab5` 13 | 14 | - 参考输出: 15 | 16 | - 该输出仅供参考; 17 | - 因为调度的不确定性,线程结束的顺序甚至申请到的 `TID` 会有不同; 18 | - 故你的输出不需要和该输出完全一样; 19 | - 在评阅过程中,我们会人工抽查运行结果。 20 | 21 | ```rust 22 | I am child 23 | ret tid is: 0 24 | thread 2 exited, exit code = 0 25 | I am father 26 | ret tid is: 2 27 | thread 1 exited, exit code = 0 28 | I am child 29 | ret tid is: 0 30 | thread 3 exited, exit code = 0 31 | I am father 32 | ret tid is: 3 33 | thread 0 exited, exit code = 0 34 | ``` 35 | 36 | 思考以下问题: 37 | 38 | 1. 如何控制子进程的返回值?(线程管理) 39 | 40 |

修改上下文中的 a0 寄存器。

41 | 2. 目前尚未实现进程切分,是否可以偷懒把线程当进程用? 42 | 43 |

目前,可以。(出于偷懒甚至不需要维护进程的父子关系)

44 | 3. 如何复制一个线程?(虚拟内存管理) 45 | 46 |

分配新的栈、新的页表,并将页表的内容进行复制和映射。

47 | 4. 为什么这道题这么难分值还和其它题一样? 48 |

因为有现成的代码可以参考呀(小声)

49 |

GitHub: rcore-os/rCore

50 | 51 | 一些可能有用的函数实现(仅供参考): 52 | 53 | ```rust 54 | // in syscall.rs 55 | pub const SYS_FORK: usize = 220; 56 | fn sys_fork(tf: &mut TrapFrame) -> isize { 57 | let new_thread = process::current_thread().fork(tf); 58 | let tid = process::add_thread(new_thread); 59 | tid as isize 60 | } 61 | 62 | // in paging.rs 63 | impl PageTableImpl { 64 | pub fn get_page_slice_mut<'a>(&mut self, vaddr: usize) -> &'a mut [u8] { 65 | let frame = self 66 | .page_table 67 | .translate_page(Page::of_addr(VirtAddr::new(vaddr))) 68 | .unwrap(); 69 | let vaddr = frame.start_address().as_usize() + PHYSICAL_MEMORY_OFFSET; 70 | unsafe { core::slice::from_raw_parts_mut(vaddr as *mut u8, 0x1000) } 71 | } 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /exercise/part6.md: -------------------------------------------------------------------------------- 1 | # 6. CPU 调度 2 | 3 | > **[info] 文档更新** 4 | > 5 | > 2020-02-26 号进行了一些更新 6 | > 修改了测试文件 7 | > 删掉了对 `sys_wait` 的需求 8 | > 在文件末尾增加了测评方式 9 | 10 | ## 实验要求 11 | 12 | 1. 阅读理解文档第七章。 13 | 2. 理解 rcore 中实现的 Round Robin 调度算法。 14 | 3. 编程:将 `Round Robin 调度算法` 替换为 `Stride 调度算法` 。(20 分) 15 | 16 | ## 实验指导 17 | 18 | - 认真阅读 [ucore doc](https://learningos.github.io/ucore_os_webdocs/lab6/lab6_3_6_1_basic_method.html) 中 stride 调度算法部分。 19 | - 在 `process/scheduler.rs` 中创建 `StrideScheduler` ,为其实现 `Scheduler trait` 。 20 | 21 | ```rust 22 | // 测试文件需要用到的 syscall id 23 | pub const SYS_SETPRIORITY: usize = 140; 24 | pub const SYS_TIMES: usize = 153; 25 | ``` 26 | 27 | > [stride 测试文件(依赖 sys_fork,sys_gettime)](https://github.com/rcore-os/rCore_tutorial/blob/master/test/usr/stride_test.rs) 28 | > 29 | > `sys_fork` 为上一章要求实现的系统调用,如果未能实现,请向老师/助教提供无需 `sys_fork` 的测试用例(我没 xiang 想 yao 出 mo 优 yu 雅 bu 的 xiang 写 xie 法 le ,所以在这向大家征集了 QAQ) 30 | > 31 | > `sys_gettime` 直接返回 `timer::TICKS` ,记得在每次发生时钟中断时将其加一。或者直接返回 `get_cycle() / TIMEBASE` (参考下一章 `crate::timer::now`)。 32 | > 33 | > 由于 rcore 还不是很完善,尤其是 wait 机制,所以弱化了测例 34 | 35 | 测试方法:`python3 test.py lab6` ,注意,请多等待一下再退出 Qemu 。 36 | 37 | 多出来的 `>>` 是由于目前 `rcore` 的 `wait/fork` 不完善导致的(等一位哥哥来修复 38 | 39 | 检察方式(大概):,检察 `thread %d exited, exit code = %d` ,捕获 `exit code` : 40 | 41 | ```rust 42 | sort(code, code + 5); 43 | for i in 0..5 { 44 | assert!((code[i] * 2 / code[0] + 1) / 2 == i + 1); 45 | } 46 | ``` 47 | 48 | 参考输出: 49 | 50 | ```rust 51 | main: fork ok. 52 | thread 0 exited, exit code = 0 53 | thread 5 exited, exit code = 638400 54 | thread 4 exited, exit code = 528400 55 | thread 3 exited, exit code = 396800 56 | thread 2 exited, exit code = 269200 57 | thread 1 exited, exit code = 140000 58 | ``` 59 | -------------------------------------------------------------------------------- /exercise/part8.md: -------------------------------------------------------------------------------- 1 | # 8. 文件系统 2 | 3 | ## 实验要求 4 | 5 | 1. 阅读理解文档第九章,并确保已经在之前的实验中实现了 ``sys_fork`` 系统调用。 6 | 2. 编程实现:基于第九章的内容,支持 pipe ,使得给定的用户态测试程序得到正确的结果。(20 分) 7 | 8 | ## 实验指导 9 | 10 | 新增如下系统调用: 11 | 12 | ```rust 13 | // usr/rust/src/syscall.rs 14 | enum SyscallId { 15 | ... 16 | Pipe = 59, 17 | } 18 | 19 | pub fn sys_pipe(pipefd: &mut[i32; 2]) -> i64; 20 | ``` 21 | 22 | ``sys_pipe`` 的功能是:为当前进程创建一个管道,并返回两个文件描述符分别代表它的读端和写端。 23 | 24 | [测试程序](https://github.com/rcore-os/rCore_tutorial/blob/master/test/usr/pipe_test.rs)的功能如下: 25 | 26 | 1. 父进程调用 ``sys_pipe`` ,创建管道并获得两个文件描述符分别指向管道的读端和写端。 27 | 2. 调用 ``sys_fork`` 产生子进程,子进程拥有同样的文件描述符。 28 | 3. 父进程关闭管道读端,子进程关闭管道写端。 29 | 4. 父进程向管道中写入数据,子进程将管道中的数据读出。 30 | 31 | 从测试程序中可以看出: 32 | 1. 针对于管道的情形,``sys_read/sys_write`` 每次只需读/写一个字符; 33 | 2. 为了能够得到正确的输出,``sys_read`` 在当前管道为空的情况下需要将子进程阻塞等待父进程向管道写入字符。 34 | 35 | 测试方法:``python3 test.py lab8``。 36 | 37 | 其参考输出为: 38 | 39 | ```rust 40 | fd_read = 3, fd_write = 4 41 | forking 42 | message sent to child process pid 1! 43 | thread 0 exited, exit code = 0 44 | message received in child process = Hello world! 45 | thread 1 exited, exit code = 0 46 | ``` 47 | 48 | ## 思考题 49 | 1. 如果父进程还没写数据,子进程就开始读数据会怎么样?应如何解决? 50 | 2. 简要说明你是如何保证读者和写者对于管道 ``Pipe`` 的访问不会触发 race condition 的? 51 | 3. 在实现中是否曾遇到死锁?如果是,你是如何解决它的? 52 | 53 | ## 提示 54 | * 在 ``os/src/fs/file.rs`` 新增 ``FileDescriptorType::FD_PIPE``,并在 ``File`` 内保存 ``Pipe`` 的指针。``Pipe`` 可以用一个环形队列来实现,维护两个指针表示读者和写者当前所在的位置。 55 | * 拓展原来的 ``sys_read, sys_close`` 来支持管道。 56 | * 拓展原来的 ``sys_fork`` 支持文件描述符的复制。 57 | * 灵活利用 ``Arc, spin::Mutex`` 等 wrapper 实现同步互斥。 58 | 59 | -------------------------------------------------------------------------------- /extensions/comment/gitalk.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 27 | -------------------------------------------------------------------------------- /extensions/fill_commit_id.py: -------------------------------------------------------------------------------- 1 | from posix import system 2 | 3 | BASE_URL = 'https://github.com/rcore-os/rCore_tutorial/tree/' 4 | 5 | for line in open('commit_ids.txt').readlines(): 6 | path, commit_id = line[:-1].split(': ') 7 | path = path + '.md' 8 | find = r'^\[CODE\].*' 9 | replace = '[CODE]: {}{}'.format(BASE_URL, commit_id) 10 | system("sed -i '' -E 's#{}#{}#g' {}".format(find, replace, path)) 11 | -------------------------------------------------------------------------------- /extensions/highlight/add_code_style.py: -------------------------------------------------------------------------------- 1 | s = open('docs/gitbook/style.css').read() 2 | code = 'markdown-section code{' 3 | color_red = 'color:#bf616a;' 4 | code_in_pre = 'markdown-section pre>code{' 5 | color_inherit = 'color:#ccc;' 6 | s = s.replace(code, code + color_red) 7 | s = s.replace(code_in_pre, code_in_pre + color_inherit) 8 | with open('docs/gitbook/style.css', 'w') as f: 9 | f.write(s) 10 | 11 | -------------------------------------------------------------------------------- /extensions/highlight/add_riscv_component.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | json_path = 'node_modules/prismjs/components.json' 4 | 5 | data = json.load(open(json_path)) 6 | data['languages']['riscv'] = {'title': 'RISC-V', 'owner': 'shinbokuow2'} 7 | with open(json_path, 'w') as f: 8 | f.write(json.dumps(data, sort_keys = True, indent = 4)) 9 | -------------------------------------------------------------------------------- /extensions/highlight/prism-riscv.js: -------------------------------------------------------------------------------- 1 | Prism.languages.riscv = { 2 | 'comment': /#.*\n/, 3 | 4 | 'general-registers' : { 5 | pattern: /\b(?:x[1-2]?[0-9]|x30|x31|zero|ra|sp|gp|tp|fp|t[0-6]|s[0-9]|s1[0-1]|a[0-7]|pc)\b/, 6 | alias: 'class-name' 7 | }, 8 | 's-mode-csrs' : { 9 | pattern: /\bs(?:status|tvec|ip|ie|counteren|scratch|epc|cause|tval|atp|)\b/, 10 | alias: 'class-name' 11 | }, 12 | 13 | /* timer & monitor csrs are not included yet */ 14 | 'm-mode-csrs' : { 15 | pattern: /\bm(?:isa|vendorid|archid|hardid|status|tvec|ideleg|ip|ie|counteren|scratch|epc|cause|tval)\b/, 16 | alias: 'class-name' 17 | }, 18 | 19 | 20 | 'rv32/64i-instructions': { 21 | pattern: /\b(?:(addi?w?)|(slti?u?)|(?:and|or|xor)i?|(?:sll|srl|sra)i?w?|lui|auipc|subw?|jal|jalr|beq|bne|bltu?|bgeu?|s[bhwd]|(l[bhw]u?)|ld)\b/, 22 | alias: 'keyword' 23 | }, 24 | 'csr-instructions': { 25 | pattern: /\b(?:csrr?[rws]i?)\b/, 26 | alias: 'keyword' 27 | }, 28 | 'privilege-instructions': { 29 | pattern: /\b(?:ecall|ebreak|[msu]ret|wfi|sfence.vma)\b/, 30 | alias: 'keyword' 31 | }, 32 | 'pseudo-instructions': { 33 | pattern: /\b(?:nop|li|la|mv|not|neg|negw|sext.w|seqz|snez|sltz|sgtz|f(?:mv|abs|neg).(?:s|d)|b(?:eq|ne|le|ge|lt)z|bgt|ble|bgtu|bleu|j|jr|ret|call)\b/, 34 | alias: 'important' 35 | }, 36 | 37 | 'relocation-functions': { 38 | pattern: /(?:%hi|%lo|%pcrel_hi|%pcrel_lo|%tprel_(?:hi|lo|add))/, 39 | alias: 'important' 40 | }, 41 | 42 | /* 'function': /function/, */ 43 | 44 | 'operator': /operator/, 45 | 'data-emitting-directives': { 46 | pattern: /(?:.2byte|.4byte|.8byte|.quad|.half|.word|.dword|.byte|.dtpreldword|.dtprelword|.sleb128|.uleb128|.asciz|.string|.incbin|.zero)/, 47 | alias: 'tag' 48 | }, 49 | 'alignment-directives': { 50 | pattern: /(?:.align|.balign|.p2align)/, 51 | alias: 'tag' 52 | }, 53 | 'symbol-directives': { 54 | pattern: /(?:.globl|.local|.equ)/, 55 | alias: 'tag' 56 | }, 57 | 'section-directives': { 58 | pattern: /(?:.text|.data|.rodata|.bss|.comm|.common|.section)/, 59 | alias: 'tag' 60 | }, 61 | 'miscellaneous-directives': { 62 | pattern: /(?:.option|.macro|.endm|.file|.ident|.size|.type)/, 63 | alias: 'tag' 64 | }, 65 | 66 | 'labels': { 67 | pattern: /\S*:/, 68 | alias: 'operator' 69 | }, 70 | 'number': /\b(?:(?:0x|)[\da-f]+|(?:0o|)[0-7]+|\d+)\b/, 71 | 'last-literals': { 72 | pattern: /\b\S*\b/, 73 | alias: 'operator', 74 | }, 75 | 76 | }; 77 | -------------------------------------------------------------------------------- /os2atc2019/.gitignore: -------------------------------------------------------------------------------- 1 | os2atc 2 | *.pdf 3 | *.html -------------------------------------------------------------------------------- /os2atc2019/figures/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/os2atc2019/figures/github.png -------------------------------------------------------------------------------- /os2atc2019/figures/init_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/os2atc2019/figures/init_stack.png -------------------------------------------------------------------------------- /os2atc2019/figures/memory_handler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/os2atc2019/figures/memory_handler.png -------------------------------------------------------------------------------- /os2atc2019/figures/memory_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/os2atc2019/figures/memory_set.png -------------------------------------------------------------------------------- /os2atc2019/figures/sie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/os2atc2019/figures/sie.png -------------------------------------------------------------------------------- /os2atc2019/figures/software-stacks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/os2atc2019/figures/software-stacks.png -------------------------------------------------------------------------------- /os2atc2019/figures/sv39_addr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/os2atc2019/figures/sv39_addr.png -------------------------------------------------------------------------------- /os2atc2019/figures/sv39_pte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/os2atc2019/figures/sv39_pte.png -------------------------------------------------------------------------------- /os2atc2019/figures/sv39_rwx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/os2atc2019/figures/sv39_rwx.png -------------------------------------------------------------------------------- /os2atc2019/figures/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/os2atc2019/figures/switch.png -------------------------------------------------------------------------------- /os2atc2019/figures/thread-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rCore_tutorial_doc/c52cd0059dead4ed090dbaa78dd8519278ccb8c4/os2atc2019/figures/thread-arch.png -------------------------------------------------------------------------------- /plugin_examples.txt: -------------------------------------------------------------------------------- 1 | This text is {% em %}highlighted!{% endem %} 2 | 3 | > **[info] For info** 4 | > 5 | > This is a info! 6 | > 7 | 8 | 23333 9 | 10 | *.json 11 | 12 | $$a+b=c$$ --------------------------------------------------------------------------------