From a6743386cfdabdfc521ab4c426e50d88e962a8ac Mon Sep 17 00:00:00 2001 From: thesayol Date: Fri, 1 Dec 2023 23:28:05 +0800 Subject: [PATCH] summary for stage3 --- ...3\223\346\212\245\345\221\212-TheSayOL.md" | 188 ++++++++++++++++++ ...3\223\346\212\245\345\221\212-TheSayOL.md" | 168 ++++++++++++++++ 2 files changed, 356 insertions(+) create mode 100644 "source/_posts/2023\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\351\241\271\347\233\256\344\270\200\345\237\272\346\234\254\344\273\273\345\212\241\346\200\273\347\273\223\346\212\245\345\221\212-TheSayOL.md" create mode 100644 "source/_posts/2023\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\351\241\271\347\233\256\344\270\200\346\234\200\347\273\210\344\273\273\345\212\241\346\200\273\347\273\223\346\212\245\345\221\212-TheSayOL.md" diff --git "a/source/_posts/2023\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\351\241\271\347\233\256\344\270\200\345\237\272\346\234\254\344\273\273\345\212\241\346\200\273\347\273\223\346\212\245\345\221\212-TheSayOL.md" "b/source/_posts/2023\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\351\241\271\347\233\256\344\270\200\345\237\272\346\234\254\344\273\273\345\212\241\346\200\273\347\273\223\346\212\245\345\221\212-TheSayOL.md" new file mode 100644 index 00000000000..b40aeb82208 --- /dev/null +++ "b/source/_posts/2023\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\351\241\271\347\233\256\344\270\200\345\237\272\346\234\254\344\273\273\345\212\241\346\200\273\347\273\223\346\212\245\345\221\212-TheSayOL.md" @@ -0,0 +1,188 @@ +--- +title: 2023开源操作系统训练营第三阶段项目一基本任务总结报告-TheSayOL +date: 2023-12-01 18:52:58 +categories: + - report +tags: + - author:TheSayOL + - repo:[](https://github.com/TheSayOL/arceos) +--- + +# 2023秋冬季开源操作系统训练营第三阶段总结报告 + +## 前言 + +在训练营第三阶段, 我选择了`Unikernel`项目, 不觉间四周已过, 训练营也步入尾声, 遂做个总结. + + +## Unikernel 项目学习总结 + +### week1 + +#### 输出有颜色的字符 + +查阅资料得知, 只要在字符串两侧包裹`\u001b[m`和`\u001b[0m`就可以了 +- ``为颜色数字, 比如红色为`31` +- `\u001b[31mhello world!\u001b[0m`即可输出红色的`hello world!` + + +#### 支持 HashMap 数据类型 + +思路 +- 一开始无从下手, 感谢老师在群里提示, 去看了标准库实现 +- 将标准库代码复制过来, 全部注释 +- 一点点放开注释, 缺啥补啥 + +`Rust`标准库的哈希表的一些具体内容 +- 底层: 对`Google`的`C++`哈希表的包装 +- `new`: 用参数`hashbuilder`生成一个哈希表, 默认为`RandomState` +- `RandomState`: 结构体, 保存两个随机数, 实现了`BuildHasher Trait` +- `BuildHasher`: 可以根据`key`创建`Hasher`, 要求实现方法`build_hasher() -> Hasher` +- `Hasher`: 一个`Trait`, 代表一种哈希算法, 可以根据`key`(字节流)返回哈希值, 要求实现方法`write()`和`finish()` + - `write()`: 往`Hasher`里写`key` + - `finish()`: 结束写, 返回哈希值 +- `build_hasher()`: 生成`Hasher`, 默认的`DefaultHasher`是调用`SipHasher13::new_with_key()`生成的 + - `DefaultHasher`: 结构体, 保存了一个`SipHasher13` + - `SipHasher13::new_with_key()`: 新建一个`SipHasher13` + - `SipHasher13`: 一个`Hasher`, 即一种哈希算法的实现 + + +#### 内存管理 + +实现内存分配算法`Early` +- 参考`TLSF`的代码, 在其基础上修改 +- 初始化页和字节分配器, 共用一块连续空间 +- 字节分配指针从前往后, 页从后往前 +- 指针相遇了就意味着内存耗尽 +- 页分配器不回收释放的内存, 指针一直往前 +- 字节分配器仅当所有分配的内存都释放了才回收 + - 初始化一个计数器为 0 + - 每次分配 +1, 释放 -1 + - 如果计数为 0, 就把指针移回起点 + +#### dtb + +解析`dtb` +- 群友有推荐使用`hermit-dtb`进行解析 +- 最后选择了名为`dtb`的`crate` + +解析后获取相应信息 +- 遍历每个节点 +- 每个节点的`reg`属性都有`4`个值 +- 其中第一个和第三个是起始地址和大小 + - 目前还未找到相关规定, 姑且先当成结论 + + +#### 调度 + +将`fifo`改造成抢占式, 最简单的实现: +- 将`task_tick()`的返回值从`false`改成`true`即可 +- 类似于一个时片极短的`RR` + + +### week2 + +#### 练习 1 & 2 + +> 请为 image 设计一个头结构,包含应用的长度信息,loader 在加载应用时获取它的实际大小。 +> +> 扩展 image 头结构,让 image 可以包含两个应用。打印出每一个应用的二进制代码。 + +实现 +- 给每个`app`的文件头都加上`24`字节的元信息, 如下 + - 魔数 + - `app`起始地址 + - `app`大小 +- 循环: 解析前`24`字节, 如果发现是`app`, 就将其内容打印出来, 否则退出循环 + +反思 +- 或许这样会更好: 学习`inode`, 将每个文件的元信息统一放在`image`开头, 而不是每个文件的开头 + + +#### 练习 3 + +> 批处理方式执行两个单行代码应用,第一个应用的单行代码是 nop ,第二个的是 wfi + +思路 +- 从练习`2`继续, 循环读取`app`的代码 +- 每读取完一个`app`, 就跳转到该`app`指令处执行 +- 执行完毕后返回, 继续循环, 即读取下一个`app`的代码 + +实现 +- 尝试用`jalr`跳到`app`的指令处运行, 但是失败 +- `debug`发现问题和`jalr`无关, 而是`app`执行完毕后, 没有返回 +- 观察实验代码, 发现`app`的函数有`noreturn`的标志, 删去即可 + +#### 练习 4 + +> 请实现 3 号调用 - SYS_TERMINATE 功能调用,作用是让 ArceOS 退出,相当于 OS 关机 + +照猫画虎, 模仿前几号系统调用实现`3`号即可 +- 使用的退出函数为`arceos_api`的`ax_exit()` +- `axstd`也有相关函数, 当时对`arceos`的结构了解尚浅 + + +#### 练习 5 + +> 把三个功能调用的汇编实现封装为函数, 基于 putchar 实现 puts 输出字符串。 + +思路 +- 在实验中已经将`abi_table`基址放在`a7`中传给了应用 +- 应用用一个全局变量保存基址 +- 每个函数根据该基址, 用汇编`jalr`跳转到系统调用函数执行 +- `puts`即循环调用`putchar` + +遇到的问题: 调用`puts`时死循环 +- 观察`gdb`, 发现在`puts`的最后, 执行`ret`无法返回, 而是停留在原地 +- 检查发现, 原因: + - `riscv`使用`ra`保存返回值, 但`ra`不是被调用者保存寄存器 + - 使用内联汇编跳转时, 编译器不会自动帮你加上保存`ra`的指令 + - 在`puts`循环调用`putchar`的过程中, `ra`被修改, 所以无法正常返回 +- 解决: 调用前将`ra`入栈, 调用后再出栈 + + +#### 练习 6 + +> 实现一个应用,唯一功能是打印字符 'D'。 现在有两个应用,让它们分别有自己的地址空间。 让 loader 顺序加载、执行这两个应用。 + +顺序加载和执行 +- 在练习`3`已实现 + +地址空间的分配: 最简单的实现思路 +- 实验过程中, 新建了一张页表, 映射虚拟地址`0x4000_0000`开始的`1G`空间 +- 应用起始地址为`0x4010_0000` +- 既然应用是顺序执行的, 完全可以共用该页表 +- 将第一个应用加载到`0x4010_0000`处, 运行完毕后, 第二个应用直接覆盖上去即可 + +地址空间的分配: 加载全部应用后再执行的思路 +- 额外建立一张页表给第二个应用 +- 第一个应用映射`0x4000_0000`到`0x8000_0000`, 大小`1G` +- 第二个应用映射`0x4000_0000`到`0x8000_1000`, 大小`1G` +- 这要求第一个应用大小不能超过`1`页, 不然会和第二个应用重合 + +遇到的问题 +- 第二个应用无法映射成功, 每当访问该虚拟地址时均会产生`pagefault` +- 感谢老师和同学@王瑞康的帮助, 告诉我这是`riscv`的规定 + - `sv39`的页表机制要求, 如果使用一级页表, 那么地址必须`1G`对齐 + - 如果想实现上述的映射, 必须用到三级页表 +- 将第二个应用的页表改成三级页表, 即可完成映射 + + +## 感想与收获 + +对于`arceos`各个组件说的非常清晰 +- 初看`arceos`的代码时一团乱麻 +- 看过`ppt`之后才逐渐梳理清楚, 一图胜千言 + +第一次分析几千行的标准库代码 +- 起初打算自己实现哈希表, 要是真这么做肯定费力不讨好 +- 学会了如何借鉴和复用他人的代码 + +第一周的内容给我补了很多急缺的知识, 也加深了很多知识的理解 +- `dtb`的作用和结构 +- 现代内存分配算法 +- 调度算法 +- 页表机制 +- ... + +总之, 十分感谢能有这次机会参加训练营, 此间经历, 此生难忘. diff --git "a/source/_posts/2023\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\351\241\271\347\233\256\344\270\200\346\234\200\347\273\210\344\273\273\345\212\241\346\200\273\347\273\223\346\212\245\345\221\212-TheSayOL.md" "b/source/_posts/2023\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\351\241\271\347\233\256\344\270\200\346\234\200\347\273\210\344\273\273\345\212\241\346\200\273\347\273\223\346\212\245\345\221\212-TheSayOL.md" new file mode 100644 index 00000000000..ebae15bb7ca --- /dev/null +++ "b/source/_posts/2023\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\351\241\271\347\233\256\344\270\200\346\234\200\347\273\210\344\273\273\345\212\241\346\200\273\347\273\223\346\212\245\345\221\212-TheSayOL.md" @@ -0,0 +1,168 @@ +--- +title: 2023开源操作系统训练营第三阶段项目一最终任务总结报告-TheSayOL +date: 2023-12-01 18:52:58 +categories: + - report +tags: + - author:TheSayOL + - repo:[](https://github.com/TheSayOL/arceos) +--- + +# 2023秋冬季开源操作系统训练营第三阶段总结报告 + +## 前言 + +此为训练营第三阶段`Unikernel`项目最终任务的报告 +- 描述了我对`arceos`支持`Linux`原始应用以及支持多应用的实现思路以及验证过程 + + +# 思路 + +## 支持 Linux 原始应用 + +> 在思路一的基础上支持思路二 + +思路一 +- 需要获得应用源码 +- 将应用和`arceos`一起静态编译 + +思路二 +- 应用不需要重新编译, 但必须动态编译 +- 实现一个和`libc`接口一致的动态库 +- 运行时, 使用一个动态链接器将应用重定位到该动态库 +- 该动态库必须知道`arceos`系统调用函数的地址 +- 动态库不使用中断, 而是用事先获得的地址, 直接跳转过去进行处理 +- 细节参考第二周的练习`4` + +本思路 +- 应用不需要重新编译, 但必须动态编译 +- 实现一个和`libc`接口一致的库, 和一个动态链接器, 两者和`arceos`一起静态编译 +- 如此一来, 库的内部实现可以直接调用`arceos`函数 +- 应用装入内存时, 用动态链接器重定位到库函数 + +## 支持多应用 + +每个应用都有一个`arceos`的`TCB`, 在其中保存一些关键信息 +- 应用有自己的内存空间, 所以需要保存自己的页表 +- 应用可以创建线程, 所以需要保存子线程的`TCB` +- 多应用情况下, 必须进行内存的管理, 所以还需要保存应用申请的物理页 + + +# 原理和实现 + +## 支持 Linux 原始应用 + +### 实现接口一致的库 + +这部分很简单 +- 对外接口和`libc`一致即可, 目的是让应用感知不到动态库被替换 +- 内部实现随意, 只要达到对应功能即可 +- 因为和`arceos`静态链接, 调用系统调用十分方便 +- 具体实现可以参考或者直接使用`axlibc` +- 也可以"翻译"`glibc`或`musl-libc` + + +### 实现的库函数 + +> 参考 musl-libc 1.2.4 源码 + +对于一个`C`程序, 其启动流程如下 +- 内核将一些信息放入栈中, 随后跳转到程序入口`_start` +- `_start`, 将当前`sp`作为参数, 跳转到`_start_c` +- `_start_c`从`sp`里解析出`argc`和`argv`, 跳转到`__libc_start_main` +- `__libc_start_main`进行初始化工作, 完成后跳转到应用的`main` +- `main`执行完毕, 回到`__libc_start_main` +- `__libc_start_main`完成收尾工作 + +对于动态编译的程序 +- 上述提高的函数里, 仅有`_start, _start_c, main` +- 当然可能还有其他函数比如`_init, _fini`等, 这些往往作为参数交给`__libc_start_main`进行处理 +- 而`__libc_start_main`本身是不包括在程序里的, 也就是说, 需要由我们实现 + +`__libc_start_main`的实现 +- 对于一个`hello world`程序而言, `__libc_start_main`的实现十分简单 +- 不需要进行初始化工作, 直接跳转到`main`即可 +- 也不需要进行收尾工作, 直接`exit`即可 + + +### 动态链接器 + +动态链接器自举 +- 动态链接器装入内存的地址不确定, 需要自举, 即对自己重定位, 这是动态链接器最复杂的部分 +- 自举时无法使用标准库功能, 甚至不能进行函数调用 +- 但因为该动态链接器和`arceos`静态编译, 所以不需要自举 + +### 动态重定位 + +对于一个动态链接的应用 +- 函数调用的地址在编译期无法确定, 需要在运行时查询`GOT`来确定 +- `GOT`可以确定一个函数名到函数地址的映射 +- 装入时重定位: 应用在装入时, 动态链接器修改`GOT`, 将函数地址改成正确对应地址 +- 运行时重定位: 动态链接器将函数地址改成`dl_runtime_resolve()`的地址, 仅当应用调用函数的时候才修改`GOT`为正确地址 + +本实现里, 使用装入时重定位 +- 应用装入内存时, 修改`GOT`, 改成上文的"实现接口一致的库"的对应函数的地址 +- 如此一来, 应用调用库函数便会跳转到期望的地址执行 + + + +## 支持多应用 + + +### 内存管理 + +`arceos`的设计 +- 只支持单应用, 内核以库的形式存在, 即`libos` +- 应用拥有所有内存空间, 随意使用 + +为了支持多应用, 需要对应用进行内存管理 +- `TCB`里使用一个数组保存应用使用的物理内存 +- 当应用退出时, 回收所申请的内存 + + +### 应用独立地址空间 + +`arceos`的设计 +- 只支持单应用, 因此不需要进行页表切换 +- 内核初始化时初始一张页表, 映射内核的数据 +- 将该页表作为根页表写入页表寄存器, 之后不再改动 + +为了支持多应用 +- 每个应用在建立的时候, 都为之新建一张页表, 保存在`TCB`中 +- 页表初始化时, 也需要映射内核的数据 + - 对于`sv39`, 地址空间分为高`256G`和低`256G` + - `arceos`在完成初始化工作之后, 将自己的运行地址设置在了`256G` + - 因此应用运行在低`256G`即可, 高`256G`留给内核运行 +- 设计应用二进制数据的起始地址为`0x10000`, 重定位完毕之后, 将数据写入该地址 + - 申请应用和数据大小一致的物理内存 + - 将应用数据写入该物理内存 + - 在页表上将`0x10000`映射到该内存地址 + +支持页表切换 +- 调度时, 需要将切换页表, 将地址空间从上一个应用切换到下一个 +- 系统初始化创建`main`进程运行, 但是此时没有发生调度, 因此需要手动将页表切换成`main`的页表 + + +### 将来的改进 + +完善库函数 +- 完善`__libc_start_main`, 初始化和收尾工作必须符合应用期望 +- 可以直接用成熟的`__libc_start_main`实现 +- 也可以直接"翻译"一个`libc`中的相关实现 + +支持运行时重定位 +- 目前支持装入时重定位, 在面对复杂应用时, 重定位工作很耗时间 +- 可以进一步改为运行时重定位, 减少应用启动时间 + +支持应用创建子线程 +- 可以在`tcb`中保存一个`pid` +- 创建子线程时, 将`pid`设置为一致, 表示属于同一个进程 + + +# 验证 + +过程 +- 下载`riscv`版本的`Ubuntu`, 使用`qemu`安装并运行 +- 在其中下载`musl-libc`源码, 编译 +- 创建两个`hello world`应用, 使用`musl-gcc`编译 +- 复制到`arceos`项目文件中, 运行`arceos`, 成功