0%

2023开源操作系统训练营第三阶段项目一基本任务总结报告-TheSayOL

2023秋冬季开源操作系统训练营第三阶段总结报告

前言

在训练营第三阶段, 我选择了Unikernel项目, 不觉间四周已过, 训练营也步入尾声, 遂做个总结.

Unikernel 项目学习总结

week1

输出有颜色的字符

查阅资料得知, 只要在字符串两侧包裹\u001b[<color>m\u001b[0m就可以了

  • <color>为颜色数字, 比如红色为31
  • \u001b[31mhello world!\u001b[0m即可输出红色的hello world!

支持 HashMap 数据类型

思路

  • 一开始无从下手, 感谢老师在群里提示, 去看了标准库实现
  • 将标准库代码复制过来, 全部注释
  • 一点点放开注释, 缺啥补啥

Rust标准库的哈希表的一些具体内容

  • 底层: 对GoogleC++哈希表的包装
  • 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进行解析
  • 最后选择了名为dtbcrate

解析后获取相应信息

  • 遍历每个节点
  • 每个节点的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_apiax_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_00000x8000_0000, 大小1G
  • 第二个应用映射0x4000_00000x8000_1000, 大小1G
  • 这要求第一个应用大小不能超过1页, 不然会和第二个应用重合

遇到的问题

  • 第二个应用无法映射成功, 每当访问该虚拟地址时均会产生pagefault
  • 感谢老师和同学@王瑞康的帮助, 告诉我这是riscv的规定
    • sv39的页表机制要求, 如果使用一级页表, 那么地址必须1G对齐
    • 如果想实现上述的映射, 必须用到三级页表
  • 将第二个应用的页表改成三级页表, 即可完成映射

感想与收获

对于arceos各个组件说的非常清晰

  • 初看arceos的代码时一团乱麻
  • 看过ppt之后才逐渐梳理清楚, 一图胜千言

第一次分析几千行的标准库代码

  • 起初打算自己实现哈希表, 要是真这么做肯定费力不讨好
  • 学会了如何借鉴和复用他人的代码

第一周的内容给我补了很多急缺的知识, 也加深了很多知识的理解

  • dtb的作用和结构
  • 现代内存分配算法
  • 调度算法
  • 页表机制

总之, 十分感谢能有这次机会参加训练营, 此间经历, 此生难忘.