从SBI到stdio

OpenSBI作为运行在M态的软件(或者说固件), 提供了一些接口供我们编写内核的时候使用。

我们可以通过ecall指令(environment call)调用OpenSBI。通过寄存器传递给OpenSBI一个”调用编号“,如果编号在 0-8 之间,则由OpenSBI进行处理,否则交由我们自己的中断处理程序处理(暂未实现)。有时OpenSBI调用需要像函数调用一样传递参数,这里传递参数的方式也和函数调用一样,按照riscv的函数调用约定(calling convention)把参数放到寄存器里。可以阅读SBI的详细文档

C语言并不能直接调用ecall, 需要通过内联汇编来实现。

// libs/sbi.c
#include <sbi.h>
#include <defs.h>

//SBI编号和函数的对应
uint64_t SBI_SET_TIMER = 0;
uint64_t SBI_CONSOLE_PUTCHAR = 1;
uint64_t SBI_CONSOLE_GETCHAR = 2;
uint64_t SBI_CLEAR_IPI = 3;
uint64_t SBI_SEND_IPI = 4;
uint64_t SBI_REMOTE_FENCE_I = 5;
uint64_t SBI_REMOTE_SFENCE_VMA = 6;
uint64_t SBI_REMOTE_SFENCE_VMA_ASID = 7;
uint64_t SBI_SHUTDOWN = 8;
//sbi_call函数是我们关注的核心
uint64_t sbi_call(uint64_t sbi_type, uint64_t arg0, uint64_t arg1, uint64_t arg2) {
    uint64_t ret_val;
    __asm__ volatile (
        "mv x17, %[sbi_type]\n"
        "mv x10, %[arg0]\n"
        "mv x11, %[arg1]\n"
        "mv x12, %[arg2]\n"   //mv操作把参数的数值放到寄存器里
        "ecall\n"    //参数放好之后,通过ecall, 交给OpenSBI来执行
        "mv %[ret_val], x10"  
        //OpenSBI按照riscv的calling convention,把返回值放到x10寄存器里
        //我们还需要自己通过内联汇编把返回值拿到我们的变量里
        : [ret_val] "=r" (ret_val)
        : [sbi_type] "r" (sbi_type), [arg0] "r" (arg0), [arg1] "r" (arg1), [arg2] "r" (arg2)
        : "memory"
    );
    return ret_val;
}

void sbi_console_putchar(unsigned char ch) {
    sbi_call(SBI_CONSOLE_PUTCHAR, ch, 0, 0); //注意这里ch隐式类型转换为int64_t
}

void sbi_set_timer(unsigned long long stime_value) {
    sbi_call(SBI_SET_TIMER, stime_value, 0, 0);
}

现在可以输出一个字符了,有了第一个,就会有第二个第三个……第无数个。

这样我们就可以通过sbi_console_putchar()来输出一个字符。接下来我们要做的事情就像月饼包装,把它封了一层又一层。

console.c只是简单地封装一下

stdio.c里面实现了一些函数,注意我们已经实现了ucore版本的puts函数: cputs()

我们还在libs/printfmt.c实现了一些复杂的格式化输入输出函数。最后得到的cprintf()函数仍在kern/libs/stdio.c定义,功能和C标准库的printf()基本相同。

可能你注意到我们用到一个头文件defs.h, 我们在里面定义了一些有用的宏和类型

printfmt.c还依赖一个头文件riscv.h,这个头文件主要定义了若干和riscv架构相关的宏,尤其是将一些内联汇编的代码封装成宏,使得我们更方便地使用内联汇编来读写寄存器。当然这里我们还没有用到它的强大功能。

到现在,我们已经看过了一个最小化的内核的各个部分,虽然一些部分没有逐行细读,但我们也知道它在做什么。

是不是感觉好麻烦啊!输出一个字符都那么麻烦。那是肯定的噢,可以稍微喘下气,脑子里回忆一下,我们是怎么一层一层剥开,又是如何一层一层包装的。好玩吧!

但一直到现在我们还没进行过编译。下面就把它编译一下跑起来。

最后更新于

这有帮助吗?