本文主要记录Linux内核在riscv架构下启动的过程,内核启动早期要设置cpu模式、初始化页表、开启MMU等,这些操作都要和硬件直接交互。所以这部分代码都在arch目录下。本文探讨的基于Linux内核6.11版本
入口地址确认
既然要探究内核启动的过程第一目标就是要找到内核是从哪里开始运行,所以第一要务就是找到链接脚本。Linux链接脚本都在目标架构的kernel目录下。找到vmlinux.ld.S就可以看见关键的两行代码
OUTPUT_ARCH(riscv)
ENTRY(_start)
查看链接脚本的同时也一定要看一下Linux内核的内存布局。这是一个查看Linux内存布局最好的一种方式。
阅读head.S
早期启动的过程主要都写在了head.S中,涉及页表初始化的过程会写在arch/riscv/mm目录内的init.c中。
_start
打开head.S直接就能看见链接脚本里面定义的入口地址_start符号。Linux通过定义宏SYM_CODE_START和SYM_CODE_END让我们更方便的阅读源码。可以把这两个宏中间的部分看成是一个“”函数“”。_start部分最主要的功能是根据一些编译选项完善头信息。然后通过j _start_kernel
跳转到真正的内核启动过程中。
__start_kernel
关闭中断
主要的内核启动过程都在这个里面,SYM_CODE_START(_start_kernel)
后面立即就关闭所有类型的中断。保证启动过程不受中断打扰。
/* Mask all interrupts */
csrw CSR_IE, zero // 关闭所有中断源
csrw CSR_IP, zero // 清空所有挂起的中断
重置寄存器和内存保护设置
这部分代码受编译选项CONFIG_RISCV_M_MODE
的影响,也就是只有在M模式下启动Linux内核才这部分才生效,代码如下
#ifdef CONFIG_RISCV_M_MODE
/* flush the instruction cache */
fence.i
/* Reset all registers except ra, a0, a1 */
call reset_regs
/*
* Setup a PMP to permit access to all of memory. Some machines may
* not implement PMPs, so we set up a quick trap handler to just skip
* touching the PMPs on any trap.
*/
la a0, .L