zoukankan      html  css  js  c++  java
  • rCore-Tutorial-Book-v3学习笔记(一)

    概述

    最近看到清华的一个操作系统教程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
    "
                "mv x11, %2
    "
                "mv x12, %3
    "
                "mv x17, %4
    "
                "ecall
    "
                "mv %0, x10
    "
                :"=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以后就可以进行查看代码、加断点、单步执行等操作了。

  • 相关阅读:
    iOS 面试题搜集
    iOS 常用第三方类库、完整APP示例
    iOS 键盘遮挡输入 解决办法
    iOS UIColor RGB HEX
    iOS APP性能优化
    iOS Swift 数组 交换元素的两种方法
    iOS CoreData primitive accessor
    iOS Start developing ios apps (OC) pdf
    iOS 传值方式
    iOS IB_DESIGNABLE IBInspectable @IBDesignable @IBInspectable 加速UI开发
  • 原文地址:https://www.cnblogs.com/YuanZiming/p/14441647.html
Copyright © 2011-2022 走看看