一篇分析RISC-V Linux汇编启动过程
创始人
2025-06-01 17:11:14

RISC-V Linux的汇编启动部分比较简单,不算复杂。有两个部分比较核心:页表创建和重定向。页表创建是用C语言写的,今天先分析汇编部分,先带大家分析整体汇编启动流程,然后分析重定向。

注意:本文基于linux5.10.111内核

汇编启动流程

先从整体分析汇编做的事情,有个大体框架。

路径:arch/riscv/kernel/head.S,入口是ENTRY(_start_kernel)

从ENTRY(_start_kernel)开始进行启动前的一些初始化,建立页表前的主要工作:

  • 关闭所有中断
/* 关闭所有中断 */csrw CSR_IE, zerocsrw CSR_IP, zero
  • 加载全局指针gp
/* 加载全局指针gp */
.option push
.option norelaxla gp, __global_pointer$
.option pop
  • disable FPU
/* 禁用 FPU 以检测内核空间中浮点的非法使用*/li t0, SR_FScsrc CSR_STATUS, t0
  • 选择一个核启动
/* 选择一个核启动 */la a3, hart_lotteryli a2, 1amoadd.w a3, a2, (a3)bnez a3, .Lsecondary_start
  • 清楚bss段
/* 清除bss */la a3, __bss_startla a4, __bss_stopble a4, a3, clear_bss_done
  • 保存hart id和dtb地址
/* 保存hatr id和dtb地址,hart id保存到a0,dtb地址保存到a1 */mv s0, a0mv s1, a1la a2, boot_cpu_hartid
  • 设置sp指针
la sp, init_thread_union + THREAD_SIZE
  • 上述工作完成,会开始临时页表的创建,跳转到C函数setup_vm建立临时页表
mv a0, s1call setup_vm // 跳转到C函数setup_vm,setup_vm会创建临时页表
  • 重定向
#ifdef CONFIG_MMUla a0, early_pg_dircall relocate	//重定向,实际就是开启MMU
#endif
  • 设置异常向量地址,重载C环境
call setup_trap_vector
/* 重载C环境 */la tp, init_tasksw zero, TASK_TI_CPU(tp)la sp, init_thread_union + THREAD_SIZE
  • 最后跳转到C函数start_kernel,开始C语言部分初始化,汇编部分执行完毕
tail start_kernel

完整_start_kernel汇编代码:

ENTRY(_start_kernel)/* 关闭所有中断 */csrw CSR_IE, zerocsrw CSR_IP, zero/* 在源码中,这里有一个M模式处理的宏,这里没有用到,直接跳过*//* 加载全局指针gp */
.option push
.option norelaxla gp, __global_pointer$
.option pop/* 禁用 FPU 以检测内核空间中浮点的非法使用*/li t0, SR_FScsrc CSR_STATUS, t0#ifdef CONFIG_SMPli t0, CONFIG_NR_CPUSblt a0, t0, .Lgood_corestail .Lsecondary_park
.Lgood_cores:
#endif/* 选择一个核启动 */la a3, hart_lotteryli a2, 1amoadd.w a3, a2, (a3)bnez a3, .Lsecondary_start/* 清除bss */la a3, __bss_startla a4, __bss_stopble a4, a3, clear_bss_done
clear_bss:REG_S zero, (a3)add a3, a3, RISCV_SZPTRblt a3, a4, clear_bss
clear_bss_done:/* 保存hatr id和dtb地址,hart id保存到a0,dtb地址保存到a1 */mv s0, a0mv s1, a1la a2, boot_cpu_hartidREG_S a0, (a2)/* 初始化页表,然后重定向到虚拟地址 */la sp, init_thread_union + THREAD_SIZEmv a0, s1call setup_vm // 跳转到C函数setup_vm,setup_vm会创建临时页表
#ifdef CONFIG_MMUla a0, early_pg_dircall relocate	//重定向,实际就是开启MMU
#endif /* CONFIG_MMU */call setup_trap_vector/* 重载C环境 */la tp, init_tasksw zero, TASK_TI_CPU(tp)la sp, init_thread_union + THREAD_SIZE#ifdef CONFIG_KASANcall kasan_early_init
#endif/* Start the kernel */call soc_early_inittail start_kernel	//跳转到C函数start_kernel,开始C语言部分初始化

汇编中非常重要的一个部分就是页表的创建,关乎着后面的程序能不能继续往下跑。setup_vm创建页表后就会开始执行relocate重定向,这个重定向主要开启mmu,下面分析relocate的汇编。

资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

relocate

relocate重定向,就是在开启mmu。开启mmu的操作就是将一级页表的地址以及权限写到satp寄存器中,这就算开启mmu了。

#ifdef CONFIG_MMUla a0, early_pg_dir //跳转到relocate前,先把第一级页表early_pg_dir的地址存入a0call relocate		//跳转到relocate,开启MMU
#endif

relocate有两次开启mmu的操作,第一次开启mmu使用的是setup_vm()建立的trampoline_gd_dir页表,这页表保存的是kernel的前2M内存。第二次开启MMU使用的是early_pg_dir页表,这个页表映射了整个kernel内存以及dtb的4M空间。

如果trampoline_pg_dir或者early_pg_dir这两个页表的映射没弄好的话,开启MMU的时候就会失败,所以页表的建立十分关键。页表创建后续再深究,下面分析relocate汇编代码。

  • 计算返回地址返回地址就是ra加上虚拟地址和物理地址之间的偏移量,这个是固定偏移量。PAGE_OFFSET是kernel入口地址对应的虚拟地址,_start就是kernel入口地址的虚拟地址,PAGE_OFFSET - _start就得到它们之间的偏移,然后再和ra相加,就是返回地址。
/* Relocate return address */li a1, PAGE_OFFSETla a2, _startsub a1, a1, a2add ra, ra, a1
  • 将异常入口1f的虚拟地址写入stvec寄存器因为一旦开启MMU,地址都变成了虚拟地址,原来访问的都是物理地址,开启MMU时,地址发生了改变,VA != PA,从而进入异常,所以要先设置异常入口地址,此时的异常入口为1f。
/* Point stvec to virtual address of intruction after satp write */la a2, 1fadd a2, a2, a1csrw CSR_TVEC, a2
  • 提前计算切换到early_pg_dir页表要写入satp的值

再进入relocate之前,就已经把early_pg_dir赋值给a0了,所以a0是early_pg_dir。srl是逻辑右移,mmu使用的是sv39,虚拟地址39位,物理地址56位:

低12位是偏移量,所以PAGE_SHIFT等于12,将early_pg_dir地址右移12位存到a2。根据satp寄存器定义:

MODE等于0x8代表使用sv39 mmu,0x0代表不进行地址翻译,即不开启MMU。这里STAP_MODE为sv39,即0x8。将early_pg_dir地址和SATP_MODE进行或运算后,即可得到写入satp寄存器的值,最后保存到a2。

/* Compute satp for kernel page tables, but don't load it yet */srl a2, a0, PAGE_SHIFTli a1, SATP_MODE	//sv39 mmuor a2, a2, a1
  • 第一次开启MMU,使用trampoline_pg_dir页表

satp值的计算和上述是一样的。开启MMU之前,通过sfence.vma命令先刷新TLB。此时开启MMU,就会进入下面的标号为1的汇编段

la a0, trampoline_pg_dirsrl a0, a0, PAGE_SHIFTor a0, a0, a1sfence.vmacsrw CSR_SATP, a0

进入异常1f段,重新设置异常入口为.Lsecondary_park,然后切换到early_pg_dir页表,相当于第二次开启MMU。此时,如果之前建立的early_pg_dir页表不对,则会就进入.Lsecondary_park。.Lsecondary_park里面是个wfi指令,是个死循环。

完整relocate汇编代码:

relocate:/* Relocate return address */li a1, PAGE_OFFSETla a2, _startsub a1, a1, a2add ra, ra, a1/* Point stvec to virtual address of intruction after satp write */la a2, 1fadd a2, a2, a1csrw CSR_TVEC, a2/* Compute satp for kernel page tables, but don't load it yet */srl a2, a0, PAGE_SHIFTli a1, SATP_MODEor a2, a2, a1/** Load trampoline page directory, which will cause us to trap to* stvec if VA != PA, or simply fall through if VA == PA.  We need a* full fence here because setup_vm() just wrote these PTEs and we need* to ensure the new translations are in use.*/la a0, trampoline_pg_dirsrl a0, a0, PAGE_SHIFTor a0, a0, a1sfence.vmacsrw CSR_SATP, a0
.align 2
1:/* Set trap vector to spin forever to help debug */la a0, .Lsecondary_parkcsrw CSR_TVEC, a0/* Reload the global pointer */
.option push
.option norelaxla gp, __global_pointer$
.option pop/** Switch to kernel page tables.  A full fence is necessary in order to* avoid using the trampoline translations, which are only correct for* the first superpage.  Fetching the fence is guarnteed to work* because that first superpage is translated the same way.*/csrw CSR_SATP, a2sfence.vmaret

总结

以上就是RISC-V Linux的汇编启动流程,虽说RISC-V的指令不复杂,但要理解这个汇编启动的部分,还是需要一点基础和时间。另外,大多数人工作中基本用不上汇编,只有真正用上了理解才会比较深。希望本文能够帮助到有需要的人。

 

相关内容

热门资讯

【实验报告】实验一 图像的... 实验目的熟悉Matlab图像运算的基础——矩阵运算;熟悉图像矩阵的显示方法࿰...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
大模型落地比趋势更重要,NLP... 全球很多人都开始相信,以ChatGPT为代表的大模型,将带来一场NLP领...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
kuernetes 资源对象分... 文章目录1. pod 状态1.1 容器启动错误类型1.2 ImagePullBackOff 错误1....
STM32实战项目-数码管 程序实现功能: 1、上电后,数码管间隔50ms计数; 2、...
TM1638和TM1639差异... TM1638和TM1639差异说明 ✨本文不涉及具体的单片机代码驱动内容,值针对芯...
Qt+MySql开发笔记:Qt... 若该文为原创文章,转载请注明原文出处 本文章博客地址:https://h...
Java内存模型中的happe... 第29讲 | Java内存模型中的happen-before是什么? Java 语言...
《扬帆优配》算力概念股大爆发,... 3月22日,9股封单金额超亿元,工业富联、鸿博股份、鹏鼎控股分别为3.0...
CF1763D Valid B... CF1763D Valid Bitonic Permutations 题目大意 拱形排列࿰...
SQL语法 DDL、DML、D... 文章目录1 SQL通用语法2 SQL分类3 DDL 数据定义语言3.1 数据库操作3.2 表操作3....
文心一言 VS ChatGPT... 3月16号,百度正式发布了『文心一言』,这是国内公司第一次发布类Chat...
CentOS8提高篇5:磁盘分...        首先需要在虚拟机中模拟添加一块新的硬盘设备,然后进行分区、格式化、挂载等...
Linux防火墙——SNAT、... 目录 NAT 一、SNAT策略及作用 1、概述 SNAT应用环境 SNAT原理 SNAT转换前提条...
部署+使用集群的算力跑CPU密... 我先在开头做一个总结,表达我最终要做的事情和最终环境是如何的,然后我会一...
Uploadifive 批量文... Uploadifive 批量文件上传_uploadifive 多个上传按钮_asing1elife的...
C++入门语法基础 文章目录:1. 什么是C++2. 命名空间2.1 域的概念2.2 命名...
2023年全国DAMA-CDG... DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义...
php实现助记词转TRX,ET... TRX助记词转地址网上都是Java,js或其他语言开发的示例,一个简单的...
【分割数据集操作集锦】毕设记录 1. 按要求将CSV文件转成json文件 有时候一些网络模型的源码会有data.json这样的文件里...
Postman接口测试之断言 如果你看文字部分还是不太理解的话,可以看看这个视频,详细介绍postma...
前端学习第三阶段-第4章 jQ... 4-1 jQuery介绍及常用API导读 01-jQuery入门导读 02-JavaScri...
4、linux初级——Linu... 目录 一、用CRT连接开发板 1、安装CRT调试工具 2、连接开发板 3、开机后ctrl+c...
Urban Radiance ... Urban Radiance Fields:城市辐射场 摘要:这项工作的目标是根据扫描...
天干地支(Java) 题目描述 古代中国使用天干地支来记录当前的年份。 天干一共有十个,分别为:...
SpringBoot雪花ID长... Long类型精度丢失 最近项目中使用雪花ID作为主键,雪花ID是19位Long类型数...
对JSP文件的理解 JSP是java程序。(JSP本质还是一个Servlet) JSP是&#...
【03173】2021年4月高... 一、单向填空题1、大量应用软件开发工具,开始于A、20世纪70年代B、20世纪 80年...
LeetCode5.最长回文子... 目录题目链接题目分析解题思路暴力中心向两边拓展搜索 题目链接 链接 题目分析 简单来说࿰...