项目组成和执行流
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接口)
最后更新于
这有帮助吗?