惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

文章列表

游戏玩后感:ReLief:献给亲爱的你 我的周边(谷子)分享 游戏玩后感:Kanon 简谱:致真实的你 《Rust中常见的有关生命周期的误解》学习笔记 简谱:StarMap 简谱:かく咲きたらばいと恋ひめやも 简谱:东风 简谱:无法诉说的思念 简谱:Girlish 游戏玩后感:时钟机关的Layline 简谱:风之琶音 简谱:星空的记忆 简谱:因为遇见了你 简谱:月童 番茄简谱脚本转调器 游戏玩后感:青空下的约定:Refine 游戏玩后感:在这苍穹展翅 书籍读后感:控制论与科学方法论 游戏玩后感:恋爱表达式 游戏玩后感:樱之诗 MLIR-tutorial学习笔记 游戏玩后感:潜伏之赤途 游戏玩后感:纯爱咖啡厅:帕露菲重制版 游戏玩后感:智以泪聚 游戏玩后感:初雪樱 游戏玩后感:告别回忆:从今以后 游戏玩后感:梦灯花 游戏玩后感:金辉恋曲四重奏 游戏玩后感:五彩斑斓的世界 昇腾310P使用记录 游戏玩后感:AIR 游戏玩后感:弹丸论破 游戏玩后感:流景之海的艾佩莉亚 Xilinx_HLS上板过程记录 游戏玩后感:告别回忆2 游戏玩后感:恋爱绮谭 Faiss和Rapidsai_Raft使用记录 游戏玩后感:近月少女的礼仪 游戏玩后感:樱色之云,绯色之恋 游戏玩后感:幸运草的约定 游戏玩后感:星之梦、候鸟和丸子与银河龙 游戏玩后感:白色相簿2 Windows上使用VTune分析PyTorchExtension调用的Cpp程序 SpinalHDL上板过程记录 游戏玩后感:仰望夜空的星辰 最简单的算卦方法之一:梅花易数法 游戏玩后感:苍之彼方的四重奏 krkr引擎解包工具介绍 自定义CUDA实现PyTorch算子的四种简单方法 游戏玩后感:星空的记忆 游戏玩后感:9nine 游戏玩后感:AtriMyDearMoments 游戏玩后感:极限脱出 游戏玩后感:魔女的夜宴 SSH实现多跳代理 动漫观后感:向山进发 flv重封装H264、AAC流 动漫观后感:夏日重现 CSP模板 游戏玩后感:海沙风云 动漫观后感:灵能百分百 游戏玩后感:交响乐之雨 游戏玩后感:爱上火车LastRun 游戏玩后感:LittleBustersEX 游戏玩后感:SummerPockets 游戏玩后感:逆转裁判 Ultra96V2开发板简单使用 SpinalWorkshop实验笔记(三) SpinalWorkshop实验笔记(二) SpinalWorkshop实验笔记(一) PYNQ开发板上使用USB声卡+OSS兼容层播放音频 TestOS移植K210开发板 rCore-Tutorial-Book-v3学习笔记(七) 动漫观后感:凉宫春日的忧郁 rCore-Tutorial-Book-v3学习笔记(♭七) rCore-Tutorial-Book-v3学习笔记(六) rCore-Tutorial-Book-v3学习笔记(五) rCore-Tutorial-Book-v3学习笔记(四) rCore-Tutorial-Book-v3学习笔记(三) rCore-Tutorial-Book-v3学习笔记(二) 游戏玩后感:RewritePlus MIT-6.S081-2020实验(xv6-riscv64)十一:net MIT-6.S081-2020实验(xv6-riscv64)十:mmap MIT-6.S081-2020实验(xv6-riscv64)九:fs MIT-6.S081-2020实验(xv6-riscv64)八:lock MIT-6.S081-2020实验(xv6-riscv64)七:thread MIT-6.S081-2020实验(xv6-riscv64)六:cow MIT-6.S081-2020实验(xv6-riscv64)五:lazy MIT-6.S081-2020实验(xv6-riscv64)四:traps MIT-6.S081-2020实验(xv6-riscv64)三:pgtbl MIT-6.S081-2020实验(xv6-riscv64)二:syscall 动漫观后感:吹响吧上低音号 MIT-6.S081-2020实验(xv6-riscv64)一:util 快速生成网络mp4视频缩略图技术 用plantuml画图示例 QQ缩略图和大图不同实现 Python制作字符图片 动漫观后感:命运石之门 Unity3D+Post_Processing_Stack_V2自定义后处理效果研究
rCore-Tutorial-Book-v3学习笔记(一)
VnYzm · 2021-02-24 · via

概述

最近看到清华的一个操作系统教程rCore-Tutorial-Book,和其他实验不同的是,这个教程介绍的是完全从零开始实现一个Riscv操作系统。教程所用的编程语言是Rust,但是我的Rust水平只到勉强能看懂代码的地步,所以打算用C语言照着实现一遍。虽然说是照着实现,但不同的语言还是会带来不少细节的不同,相比于同个语言照抄代码还是能注意到不少平常没在意的东西。因此开个坑,记录一下遇到的问题,代码放在Github上了。由于是练习,代码写得比较乱。

第一部分是实现一个最小化内核,即能让qemu-system-riscv跑起来并输出Hello world!然后退出就算成功。得益于SBI的帮助,我们可以少研究很多东西。这里大致介绍一下SBI,SBI指的是一套辅助操作系统内核编程的工具,它包含两部分:

  • boot loader:即在机器态里初始化裸机上的一些寄存器和硬件设备,把操作系统内核读取到对应的内存区域,然后进入内核态(Supervisor态,直译为监管者态,因为是操作系统内核主要运行的特权级,后面均称内核态),开始执行内核的第一条指令;

  • 处理内核态系统调用的机器态代码:在内核态设置好存储调用号和参数的寄存器,然后执行指令ecall,系统就会进入机器态,由SBI执行一些机器态才能做的操作,然后返回内核态。

没有SBI,机器态相关的代码就得自己写了,xv6就是这样做的,所以xv6除了进程、文件、内存管理这些模块,还有一些充满晦涩代码的模块,这些就是在处理机器态和硬件相关的操作;riscv-pk的系统引导用的是BBL(Berkeley Boot Loader),需要机器态做的任务则转发给spike模拟器的htif模块,由宿主系统执行这些任务。

本项目我用的是RustSBI,和教程用的一样,虽然是用Rust写的,但是已经打包成二进制文件了,可以直接使用。原先我打算使用qemu自带的OpenSBI,但是不知道为什么,在调用OpenSBI的退出程序功能时,qemu会报错,没法正常退出,RustSBI则不会。

内容

首先是SBI的系统调用,由于涉及寄存器操作,需要用到内联汇编:

isize sbi_call(usize id, usize a0, usize a1, usize a2) {
    isize ret;
    asm volatile (
            "mv x10, %1\n"
            "mv x11, %2\n"
            "mv x12, %3\n"
            "mv x17, %4\n"
            "ecall\n"
            "mv %0, x10\n"
            :"=r"(ret)
            :"r"(a0), "r"(a1), "r"(a2), "r"(id)
            :"memory", "x10", "x11", "x12", "x17"
            );
    return ret;
}

本项目中为了简化代码以及与教程保持一致,把unsigned long long定义成usize,long long定义成isize了。这里要注意的是内联汇编的格式,和Rust不同,C语言不能在内联汇编的函数中绑定变量和寄存器(如果写的是x86汇编好像可以,riscv就不行了)(可以在声明变量的同时指定该变量必须用某寄存器存,但语法比较麻烦),所以需要先把变量存到对应寄存器才可以。这样最后一个冒号右侧的限制符也必须添加上这三个寄存器的名字,否则编译器可能会编译出错误的代码,打个比方说,没有限制符,上面的程序编译出来的结果可能会在内联汇编前面用x10存a2,那么进入内联汇编后程序首先将a0赋给x10,a2就被覆盖掉了,程序就出错了。

然后是教程中说的一个bss段清零的操作:

extern char sbss, ebss;
void clear_bss() {
    for (char *i = &sbss; i < &ebss; i++) *i = 0;
}

这里sbss、ebss都是来自linker script的符号。符号可以定义在C程序中,可以定义在汇编代码中,可以定义在linker script中,只要符号的强定义不是在当前C程序,那么对于当前C程序,这个符号可以解释成任何类型,因为它只是一个位置标识。在上面的代码中,我把sbss和ebss解释成linker script中这两个符号指向的第一个字节,那么就只要对这两个字节的地址之间的空间清零就行了。教程里面是把两个符号解释成地址,我觉得C语言应该也一样,即下面的写法和上面应该是等价的:

void sbss();
void ebss();
void clear_bss() {
    for (char *i = (char *)sbss; i < (char *)ebss; i++) *i = 0;
}

然后就是这个函数正确编译需要在编译选项里加-mcmodel=medany,不然会报错,具体原因我没看懂,好像是默认对符号地址有什么限制。

最后是编译选项,我写了个makefile:

default: os.bin
    riscv64-unknown-elf-gcc os.c printf.c entry.S -T linker.ld -ffreestanding -nostdlib -g -o os -mcmodel=medany
    riscv64-unknown-elf-objcopy os --strip-all -O binary os.bin
    qemu-system-riscv64 -machine virt -nographic -bios rustsbi-qemu.bin -device loader,file=os.bin,addr=0x80200000

这里-ffreestanding的意思是允许重新定义标准库里已经有的函数,比如我自己定义了一个printf函数(主要内容是从xv6复制的,这里就显现出Rust的好了,Rust的格式化输出是定义在语言内部的,只需要重写字符串的输出方式,C的整个格式化都得重写),和stdio.h那个同名,不加这个编译选项就会报错。在编译完后,需要用objcopy把程序的elf元信息去掉,因为裸机只能理解代码,不能解析elf格式,经过objcopy后整个文件上来就是二进制代码,裸机可以直接执行。最后是SBI把内核放到的位置,我放在0x8020_0000,和教程的0x8002_0000不太一样,相应的linker script里的内容也要改。

顺便提一下如何使用gdb调试,首先编译的时候必须用-g往二进制文件里添加调试符号表,接着在最后执行qemu的时候添加选项-s -S意思是监听调试端口1234,同时在执行第一句汇编指令前停下来等待gdb连接。然后打开另一个终端,运行riscv-elf-gdb 二进制文件,gdb的程序名不一定是这个,只要是riscv目标版本的都可以,二进制文件指的是没有经过objcopy,gcc直接编译出来的文件。进入gdb后运行命令target remote :1234,连上qemu以后就可以进行查看代码、加断点、单步执行等操作了。