## 日志
这里以天为单位,以周为周期,按时间倒序维护了开发日志。
### 第15周
#### 2018.06.06: FS-Process
- 学习xv6/uCore中文件系统上层接口
- 尝试在Rust中实现RootFS和FileManager
#### 2018.06.04:完成同步互斥
- 完成了信号量、条件变量,哲学家就餐问题
- 简易实现了std::sync::mpsc中的FIFO消息传递通道
#### 2018.06.03:新的物理帧分配器
- 实现了新的物理帧分配器, 支持回收。其本质是一个0-2^k的整数分配器, 内部用bitset维护, 并形成树状级联结构, 每16bit对应上层1bit, 用x86专有指令bsr( 相当于整数log2) 实现快速分配。目前使用容量为64Kb的分配器, 可维护256MB内存空间, 实际占用内存9KB以内。
- 由于Rust语言的限制, 该类无法实现const fn构造函数, 只能使用lazy_static等tradeoff进行运行时初始化。这时必须开启至少O1优化( 使用RVO返回值优化, 防止在栈上构造再复制到全局变量) , 否则会栈溢出。
#### 2018.06.02:重构内存管理模块
- 大幅调整了内存管理模块, 对外统一使用MemorySet管理一个线程的内存, 简化了使用方式。
#### 2018.06.01:同步互斥进阶
- 完成了OS内用锁实现的哲学家就餐问题, 和OS外用条件变量的实现。
- 关于Rust并发模型: Rust的所有权机制使得它可以轻松支持【锁数据】而不是【锁代码】。这使得在实现上述哲学家就餐问题时, 用锁非常自然, 而用条件变量( 管程) 则不太直观。
### 第14周
#### 2018.05.31:同步互斥初步
* 将spin::Mutex代码fork了一份到内核中, 在其中实现了一个接口框架, 可以自由替换底层支持。在此框架下, 已经实现了自旋锁(spin_lock)、禁止中断的自旋锁(spin_lock_irqsave),接下来就可以实现自动调度的锁了。
* 实现了禁止中断自旋锁后, 即可打开syscall的中断了。
* 为了方便内核态的线程操作, 将原有的process模块又封装了一层thread接口, 长得和标准库一模一样( std::thread) 。这样具体的同步互斥问题( 如哲学家就餐) 可以先在外部环境中依赖std库实现并测试, 然后方便地移植到OS环境中。
* 在此过程中又学习了关于内核抢占、同步互斥锁等相关资料, 感觉又是个大坑。并且发现我实现的RustOS一直是内核可抢占的, 主要依赖时钟中断进行线程切换, 不知要不要改掉。
### 第13周
本周完成的主要工作:
- 重新实现中断处理入口
- 实现内核态switch和调度器
- 从disk0中读取用户程序
- 支持运行大部分ucore用户程序
#### 2018.05.24 / 25: 最终报告
#### 2018.05.23: 实现了调度器, IDE驱动
- 参考uCore, 实现了RRScheduler和StrideScheduler, 并可以正确执行priority程序。
- 直接Copy了隔壁15组port的ucore的IDE驱动代码, 略作修改后就可以使用了。
- 为SFS模块增加了一个BlockedDevice接口, 为它实现Device接口( 即支持以字节为单位读写) , 方便对接真实设备。
#### 2018.05.22: 实现了xv6/ucore中的switch(),调整了切换进程的方式
- 为了实现调度器, 我阅读了xv6的文档, 结果发现自己之前对它切换进程方式的理解都是错的! [捂脸] ( 长教训, 以后一定硬着头皮读懂已有代码, 不能闭门造车233)
- 之前: 每次中断后运行的内核态代码和内核栈是不可切换的, 切换进程的方法是在结束中断处理时, 指定新的rsp地址, 这样就可以从别的中断帧恢复环境。这样带来的限制是像sys_wait这种异步操作实现起来很麻烦。
- 有了switch()之后, 就可以自由地在内核线程之间切换。我今天实现了这个, 并用它简化了sys_wait。
- 引入switch()后, 要运行一个新的线程变得更加复杂, 初始的内核栈上要有TrapFrame+Context。此外我发现调用switch进入scheduler线程, 再switch到其它线程, 这一过程好像用中断也可以实现( 类似上述第2条描述的方法) 。
#### 2018.05.21: 可以正常跑uCore大部分的用户程序
- 实现了在用户程序发生异常时kill掉它, 又支持了一批uCore的用户程序。有了昨天统一中断处理的基础, 这个就很容易了。
- 使用mksfs将xv6的64位用户程序也做成了SFS磁盘, 将它和ucore程序的SFS磁盘一起添加到git中。
- 修复了Release模式下SMP启动的Bug, 添加了一些volatile。
- 修复了waitpid, 可以正常跑exit和sleep了。
注: 此时还不支持内核线程的切换( 详见05.22的说明),所以(int*)store要先保存下来, 不太自然。
#### 2018.05.20: 可以正常跑uCore一半的用户程序
写了一个简易事件处理器,支持定期调度+唤醒线程
- 最初实现的是【推模式】, 逻辑比较复杂, 用户提供回调函数, 当事件发生时自动调用。由于Rust的安全约束非常严格, 为了通过编译, 代码非常复杂。
- 最后实现的是【拉模式】,逻辑比较简单,用户只提供事件内容,在每个时刻都主动询问当前是否有事件,并做处理。这种方式下事件处理器很轻巧,可以内嵌在使用者中,调用关系简洁。
重写了中断处理入口
- 原来使用Redox的处理方式:
- 每个中断有不同的入口,保存的中断帧可以有不同形式(性能考虑?)。
- 但现在需要每种中断都能够进行进程切换,这就需要一个统一的中断帧和处理过程。
- 现在改用ucore/xv6的处理方式:
- 建立中断向量表vectors, 补齐错误码, 跳转到统一的处理函数alltraps。
- ucore/xv6中vectors.S是用一个perl脚本生成的, 在Rust中改用build.rs生成。
- 如此修改后, 逻辑更加简洁清晰, 而且修复了一个长期阴魂不散的Bug
精简了IDT的代码
- 最初使用x86_64库的IDT结构; 之后由于类型不匹配的原因, 改用Redox的代码
- 现在随着我姿势水平的提高, 学会了绕过类型约束的unsafe黑魔法, 重新用起了x86_64库, 砍掉了100行的基础代码
- 历史总是螺旋式上升的,马克思诚不我欺也
Travis上编译问题
- 目前在macOS和docker上均可通过编译
- 但在Travis上会出现链接问题, 不知如何解决
#### 2018.05.19: 引入log模块, 支持向终端输出彩色文字
- 可以用五种不同级别输出调试信息: error, warn, info, debug, trace
- 它们在终端以不同颜色显示, 使用了Linux终端控制符
#### 2018.05.18: 可以从sfs.img中加载用户程序
- 将sfs.img做成.o硬链接到kernel, 配合之前写好的SFS模块读取数据
### 第12周
本周完成的主要工作:
- 可以运行用户程序( xv6的64位程序 + ucore的32位程序)
#### 2018.05.18: 可以在CLion中配合gdb调试
- x86_64的QEMU在gdb链接时会报错: Remote 'g' packet reply is too long
- 其实《Writing an OS in Rust》已经给出了[解决方案](https://os.phil-opp.com/set-up-gdb/)。然而我build gdb时编译出错。。
- 最后解决方案: ( macOS下) 使用brew安装 altkatz/gcc_cross_compilers/x64-elf-gcc。这个自带上述bug的补丁, 在CLion中配置使用这个gdb即可。
#### 2018.05.17: 可以运行ucore的32位用户程序
- 不能直接在Long Mode下跑, 因为有些指令是环境相关的, 例如push 0x64会使得esp -= 8
- 因此需要进入Compatibility Mode, 这方面资料好像很少。[OSDev的一个贴子](https://forum.osdev.org/viewtopic.php?f=1& t=24594)指出只需载入32位的CS即可, 即修改L-Bit, 但经测试无效, OS会将其识别为16位CS。之后我把xv6的UCODE和UDATA 32位段描述符直接复制过来, 就可以正常跑了。经测试, DS也需要是32位的。
- 目前已经实现了若干ucore系统调用, 可以跑hello程序了
#### 2018.05.14: Shared-memory & Copy-on-write
- 利用Rust的Trait特性, 将扩展代码都写在一个文件中: [代码](https://github.com/wangrunji0408/RustOS/blob/dev/src/arch/x86_64/paging/cow.rs)。同时包含文档和测试。
#### 2018.05.13:可以运行用户态程序
- 可直接运行xv6 x86_64中的用户程序( 二进制兼容) , 下一步兼容ucore的32位用户程序
- 实现了一个最简单的syscall: write, 可将字符输出到屏幕上
- 试图实现fork时遇到bug…
#### 2018.05.12: 添加MemorySet / Area结构
- 对应ucore中的mm& vma, 用来描述虚拟内存集/段
- 将【内核页表重映射 remap_the_kernel】过程用此结构重构
- 为下一步加载用户态程序做准备
### 第11周
本周完成的主要工作:
- 通过兼容层将Rust SFS对接到ucore_os_lab上
#### 2018.05.07: 完成与ucore_os_lab的对接
- [总结报告 ](https://github.com/wangrunji0408/SimpleFileSystem-Rust/blob/master/docs/rust_port_report.md )(后半部分)
- [整合后的ucore ](https://github.com/wangrunji0408/ucore_os_lab/tree/rust-fs/labcodes_answer/lab8_result )
#### 2018.05.06: 把SFS链接到uCore
将Rust lib链接到ucore遇到的问题:
- 真的痛苦,估计得掉层皮
- 遇到最多的是链接丢失问题。ucore_os_lab的linker script少写了一些section, 包括`*.data.*`, `*.got.*`, `*.bss.*`, 导致Rust lib链接过去后丢失了一些段, 比较坑的是这不会有任何提示。没有成功重定位的地址都是0, 运行时直接Page fault。为了找出出错位置, 各种gdb, objdump全上了, 还得看汇编追踪寄存器, 真是大坑。
- 另一个小问题是Rust lib会引用一些LLVM内置函数( 如udivdi3, 都是除法运算相关) , 链接时会报undefined symbol。但实际上并没有代码用到它们。《Writing an OS in Rust》中提到了这个问题, 它的解决方案是链接时加--gc-sections选项, 将未用到的段删掉, 结果我对ucore如此操作之后所有段都没了, 都boot不起来。。。最后是在C中强行定义这些符号解决的。
关于ucore VFS兼容层的设计:
- ucore VFS中fs和inode头部是具体FS的struct。Rust VFS使用Rc指针相互引用。为了将它们合并起来, 考虑过两种方案: 在头部放Rc指针, 或放Rust SFS的结构本体。前者相当于将Rust VFS作为ucore SFS, 后者则是直接将Rust VFS合并到ucore VFS。
- 放指针: 还需建立Rust INode => ucore INode的反向引用, 要么侵入式地增加Rust INode的字段, 要么在兼容层搞一个全局Map。这种方式耦合较低, 但多一层指针跳转, 性能可能略差。
- 放结构: 需要在Rust new出SFS结构时做文章, 委托ucore分配多一点空间并做VFS的初始化, 得魔改Rust的全局内存分配器。这种方式耦合较高, 需对Rust结构的实际内存布局有深入理解。
- 根据【把方便留给别人, 把困难留给自己】【最大兼容, 最小耦合】的原则, 我选了放指针的方案, 目前还在Debug中`_(:3」∠)_`
### 第10周
本周完成的主要工作:
- 进程模块:只实现了内核线程切换
- 文件系统: 作为独立模块实现完毕, 接下来尝试链接到ucore lab8
#### 2018.05.04 / 05: SFS文件系统
- 基本功能实现完毕,附有单元测试
- 正在尝试和ucore lab8链接
- 阶段性[移植报告](https://github.com/wangrunji0408/SimpleFileSystem-Rust/blob/master/docs/rust_port_report.md)
#### 2018.04.29 / 05.01: SFS文件系统
[GitHub仓库 ](https://github.com/wangrunji0408/SimpleFileSystem-Rust )
基础结构移植完毕, 各项功能正在重写, 计划导出C接口兼容ucore, 预计1k行代码可搞定
#### 2018.04.26 / 27: 内核线程切换
- 实现了简单的内核线程切换
每个线程拥有一个[内核栈],它是当线程运行中发生中断时内核使用的栈,保存着线程的[上下文]信息(中断帧)。对于内核线程而言,其[运行栈]和[内核栈]是统一的。
与ucore不同的是:
- 每个线程的内核栈上只保存自己的[上下文],调度器不能修改它的内容。且[上下文]只需保存这一份,调度器也不需要访问它的内容。
- 恢复[上下文]不使用汇编,而是依靠[中断服务例程]结束时从[内核栈]中pop[中断帧]。
为了实现这点,[中断服务例程]保存完[中断帧]后, 把当前rsp传给调度器, 调度器将其保存, 并用下一个待执行线程的rsp更新之( 在最后一次从此线程切出时保存) 。[中断服务例程]结束时, 重置rsp, 切换到另一个线程的[内核栈]上,恢复[中断帧]。
- 实现了int触发内核态和用户态切换( lab1 challenge)
进入用户态前, 需在TSS中设置返回内核态时的rsp。FIXME: 初始化多核后, 会导致设置失效。
确认以下设置, 否则会出现GPF:
- 段描述符DPL=3
- 页表中设置相应页为[用户可访问]
### 第9周
本周应付期中考试、大作业等事务,进展不大。
#### 2018.04.25:页置换算法
实现“改进的时钟置换算法”
### 第8周
本周完成的主要工作:
- 将Kernel虚地址移到高区
- 完成lab1的移植, 主要是各种驱动
- 完成多核的初始化
接下来可以进行的工作:
- 完善内存管理: 完善allocator, mm& vma结构, 页替换算法
- 其它驱动的移植, 例如IDE( Rucore组)
- 进程管理模块
#### 2018.04.19:内存模块
在外部模块定义了页替换模块接口, 实现了FIFO算法, 并实现了Mock页表用来单元测试。
#### 2018.04.18:多核启动
完成多核AP的启动和初始化: LocalAPIC, GDT, IDT
#### 2018.04.17:设备中断
完成以下设备初始化和中断: 串口, 键盘, PIT时钟
#### 2018.04.15: 高地址
完成高地址修改,合并到主分支
- ( 2天前) 页表重映射后崩溃的原因, 是新页表中权限设置错误( blog_os根据kernel.elf中各段的属性来设置页的权限) , 使用objdump查看elf的段信息后确认有问题, 根本原因是linker script写的不对: 我使用rodata32/bbs32/text32来标记BootLoader中的各段, 以把它们和Kernel中的区别开, 但这导致ld无法正确设置它们的属性, 某些应该可写的段被设置为只读, 最终造成PageFault。解决方案是把名称改成rodata.32/bss.32/text.32。
- [参考教程 ](https://wiki.osdev.org/Higher_Half_x86_Bare_Bones )
- 【新技能】在没有初始化中断时, 出现TripleFault的Debug方法: 利用qemu!
- 加入参数-d int, 即可显示每次中断时的CPU信息
- 发生PageFault时, 检查RPI( 出错位置) 和CR2( 访存目标)
#### 2018.04.14:设备初始化
- 页表建立临时映射, 以满足各设备初始化时访问特殊物理地址的需求。TODO: 撤除or直接映射全部设备空间
- 完成以下初始化: LocalAPIC( 链接C) , IOAPIC, GDT( 导入xv6 x86_64的描述符) , PIC( 复制Redox)
- PIC和APIC都实现了产生时钟中断
- [关于实现中断的问题手册 ](https://wiki.osdev.org/I_Cant_Get_Interrupts_Working )
#### 2018.04.13:高地址
尝试将Kernel虚地址移到高地址区, 遇到很多问题
- 需要修改linker将kernel虚地址置为高地址区, 但实地址还在低地址区( AT指令)
- 需要修改初始页表, 将四级页表项的1st和510th同时映射到低1GB物理空间。由于BootLoader执行时PC使用实地址, 因此在进入Rust之后才能把1st页表项撤销掉。
- 页表重映射( remap_the_kernel) 修改后还没有调试成功, 重置CR3的瞬间会崩掉
- 经过测试, 初始化IDT只能在页表重映射后进行( 放到前面会直接崩掉) , 在开启IDT前不好Debug。
### 第7周
#### 2018.04.12: 中期汇报文档和PPT
- [中期汇报文档 ](https://github.com/wangrunji0408/RustOS/blob/dev/docs/MidReport.md )
- [中期汇报PPT ](https://github.com/wangrunji0408/RustOS/blob/dev/docs/MidPresentation.pdf )
#### 2018.04.11: 学习xv6, C语言绑定
- 阅读xv6代码
- 实现了Rust对C的绑定
- 直接extern符号链接即可, 不需要bindgen
- bindgen输出的代码中, 把std::raw::*替换成原生类型即可
- RISCV Rust Toolchain: 找到了一个日本人写的[采坑系列文章](http://msyksphinz.hatenablog.com/entry/2017/11/29/021030), 写于2017.12, 成功在HiFive上跑起来了。
#### 2018.04.09: TravisCI, C语言绑定
- 从内部退出QEMU的方法:
- 运行qemu时加入 -device isa-debug-exit
- 执行outb(0x501, k), 会退出qemu, 错误码为2k+1
- [在Travis中运行qemu的方法 ](https://github.com/jdub/travis-qemu-example )
- 结合上述方法可以在Travis上做集成测试
- qemu环境下单测比较困难, rust自带的测试框架也无法使用
- 尝试Rust FFI( C语言绑定)
- [Rust-Bindgen ](https://rust-lang-nursery.github.io/rust-bindgen/ )
- 已经对xv6生成了绑定
- 但它依赖std库, 不好集成到只依赖core的RustOS中
- 另一个移植思路是用C绑定Rust, 把原有的模块逐个用Rust重写, 对C提供临时接口, 保持OS始终完整
#### 2018.04.07: ACPI
初步完成ACPI的移植
之后的底层驱动部分,考虑 找Rust库 > 从RustOS里摘 > 从xv6移植。
### 第6周
#### 2018.04.04: RISCV
找到了RISCV Rust Toolchain的Docker, 编译样例项目失败, 看上去除了作者本人, 还没有别人成功过。
#### 2018.04.02: 32位boot
在blog_os x86_64框架下实现x86的boot
#### 2018.04.01: RISCV
构建RISCV32/64 Docker
尝试构建RISCV Rust Toolchain, 失败