项目组成和执行流

lab0的项目组成如下:

── Makefile 
├── kern
│   ├── debug
│   │   ├── assert.h
│   │   ├── kdebug.c
│   │   ├── kdebug.h
│   │   ├── kmonitor.c
│   │   ├── kmonitor.h
│   │   ├── panic.c
│   │   └── stab.h
│   ├── driver
│   │   ├── clock.c
│   │   ├── clock.h
│   │   ├── console.c
│   │   ├── console.h
│   │   ├── intr.c
│   │   ├── intr.h
│   │   ├── kbdreg.h
│   │   ├── picirq.c
│   │   └── picirq.h
│   ├── init
│   │   ├── entry.S
│   │   └── init.c
│   ├── libs
│   │   ├── readline.c
│   │   └── stdio.c
│   ├── mm
│   │   ├── memlayout.h
│   │   ├── mmu.h
│   │   ├── pmm.c
│   │   └── pmm.h
│   └── trap
│       ├── trap.c
│       ├── trap.h
│       └── trapentry.S
├── libs
│   ├── defs.h
│   ├── elf.h
│   ├── error.h
│   ├── printfmt.c
│   ├── riscv.h
│   ├── sbi.c
│   ├── sbi.h
│   ├── stdarg.h
│   ├── stdio.h
│   ├── string.c
│   └── string.h
└── tools
    ├── function.mk
    ├── kernel.ld

内核启动

kern/init/entry.S: OpenSBI启动之后将要跳转到的一段汇编代码。在这里进行内核栈的分配,然后转入C语言编写的内核初始化函数。

kern/init/init.c: C语言编写的内核入口点。主要包含kern_init()函数,从kern/entry.S跳转过来完成其他初始化工作。

设备驱动

kern/driver/console.c(h): 在QEMU上模拟的时候,唯一的“设备”是虚拟的控制台,通过OpenSBI接口使用。简单封装了OpenSBI的字符读写接口,向上提供给输入输出库。

库文件

libs/riscv.h: 以宏的方式,定义了riscv指令集的寄存器和指令。如果在C语言里使用riscv指令,需要通过内联汇编和寄存器的编号。这个头文件把寄存器编号和内联汇编都封装成宏,使得我们可以用类似函数的方式在C语言里执行一句riscv指令。

libs/sbi.c(h): 封装OpenSBI接口为函数。如果想在C语言里使用OpenSBI提供的接口,需要使用内联汇编。这个头文件把OpenSBI的内联汇编调用封装为函数。

libs/defs.h: 定义了一些常用的类型和宏。 例如bool 类型(C语言不自带,这里typedef int bool)。

libs/string.c(h): 一些对字符数组进行操作的函数,如memset(),memcpy()等,类似C语言的string.h

kern/libs/stdio.c, libs/readline.c, libs/printfmt.c: 实现了一套标准输入输出,功能类似于C语言的printf()getchar()。需要内核为输入输出函数提供两个桩函数(stub): 输出一个字符的函数,输入一个字符的函数。在这里,是cons_getc()cons_putc()

kern/errors.h: 定义了一些内核错误类型的宏。

编译、链接脚本

tools/kernel.ld: ucore的链接脚本(link script), 告诉链接器如何将目标文件的section组合为可执行文件。

tools/function.mk: 定义Makefile中使用的一些函数

Makefile: GNU make编译脚本

执行流

最小可执行内核的执行流为:

加电 -> OpenSBI启动 -> 跳转到 0x80200000 (kern/init/entry.S)->进入kern_init()函数(kern/init/init.c) ->调用cprintf()输出一行信息->结束

cprintf()函数的执行流为:

接受一个格式化字符串和若干个需要输出的变量作为参数 -> 解析格式化的字符串,把需要输出的各种变量转化为一串字符 -> 调用console.c提供的字符输出接口依次输出所有字符(实际上console.c又封装了sbi.c向上提供的OpenSBI接口)

最后更新于