[TOC]
arch/arm/kernel/: Linux 32位ARM内核的体系结构特定实现
arch/arm/kernel/
目录是 Linux 内核中专门负责 32 位 ARM 架构体系结构相关代码实现的核心区域。它包含了将通用内核代码与 ARM 处理器及其外围硬件紧密结合的底层逻辑。简单来说,它是 Linux 内核在 ARM 平台上运行的“神经中枢和硬件适配器”。
一、 核心职责
arch/arm/kernel/
目录下的代码负责 Linux 内核在 ARM 架构上运行的所有关键底层功能,包括:
- 早期 CPU 初始化: 在主内核 C 代码开始执行后,进行更详细的 CPU 模式设置、缓存和 MMU (内存管理单元) 的初始化。
- 内存管理设置: 建立虚拟内存与物理内存的映射(页表),这是所有后续内存访问的基础。
- 异常与中断处理: 定义和处理 ARM 处理器产生的各种异常(如数据中止、预取中止、未定义指令、软件中断等)和中断请求。这是内核响应硬件事件和系统调用的核心机制。
- 系统调用分发: 提供用户空间应用程序通过软件中断(SWI/SVC 指令)进入内核并调用系统服务的入口点和分发机制。
- 多处理器支持 (SMP): 实现 ARM 多核系统上各个 CPU 核心的启动、同步和通信机制。
- 计时器和调度: 管理系统定时器,为进程调度提供周期性的时钟中断。
- 电源管理: 处理 ARM 处理器和系统级的电源管理策略和状态转换。
二、 解决的技术问题
- 体系结构异构性: ARM 处理器家族庞大,不同型号(ARMv5、ARMv6、ARMv7)和实现(Cortex-A 系列、Cortex-M 系列,虽然 Cortex-M 通常运行的是裸机或 RTOS)在细节上存在差异。
arch/arm/kernel/
通过宏定义、条件编译和函数指针,适配了这些差异。 - 特权级切换与安全: 用户程序在非特权模式下运行,需要安全地切换到内核特权模式来执行敏感操作。
arch/arm/kernel/
提供了这种模式切换的机制(通过异常向量)。 - 性能优化: 对于中断处理、系统调用等高频操作,使用汇编语言进行精细优化,以减少延迟和提高吞吐量。
- 复杂硬件抽象: 将底层中断控制器、MMU 等硬件的复杂操作抽象化,向上层通用内核提供统一的接口。
三、 关键文件深度解析
1. head.S
这个文件是主内核的汇编入口点。它在 arch/arm/boot/compressed/misc.c
中的解压程序完成任务后,将控制权移交到此处。这是主内核开始执行的第一段汇编代码。
- 核心功能:
- 设置 CPU 模式: 将 CPU 设置到 Supervisor 模式,禁用中断。
- 清空 BSS 段: 清零内核的 BSS 段(未初始化数据),这是 C 语言环境的必要准备。
- 调用
start_kernel()
: 最后,它会跳转到init/main.c
中的start_kernel()
函数。从这里开始,Linux 内核的主体(C 语言代码)正式接管系统。
- 与
arch/arm/boot/compressed/head.S
的区别: 前者是解压程序的入口,任务是解压内核;后者是解压后主内核的入口,任务是建立 C 语言环境并启动内核。
2. entry-armv.S
这是一个核心的汇编文件,包含了 ARM 处理器所有异常(包括中断和系统调用)的统一入口点。
- 核心功能:
- 异常向量表: 定义了 ARM 处理器的异常向量表(Reset, Undefined Instruction, Software Interrupt (SWI/SVC), Prefetch Abort, Data Abort, IRQ, FIQ)。当对应的异常发生时,CPU 硬件会自动跳转到这里定义的地址。
- 保存上下文: 当异常发生时,CPU 会自动保存部分寄存器。
entry-armv.S
的代码会进一步将所有通用寄存器、浮点寄存器、栈指针等保存到当前任务的内核栈上,形成完整的上下文 (Context)。这是实现任务切换和异常返回的基础。 - 模式切换: 从用户模式或其他异常模式安全地切换到内核特权模式。
- 分发: 根据异常类型,将控制权分发给 C 语言实现的异常处理函数(如
do_undefinstr
,do_data_abort
,do_irq
)。 - 系统调用入口: 特别地,它实现了系统调用的汇编入口。当用户进程执行
svc
(或swi
) 指令时,CPU 进入 SVC 模式并跳转到 SWI 向量。这里的代码会解析系统调用号和参数,并最终调用 C 语言的系统调用处理函数sys_call_table
。 - 恢复上下文与返回: 异常处理完成后,将保存的上下文从栈中恢复到寄存器,并执行
rfe
(Return From Exception) 或subs pc, lr, #0
指令返回到异常发生前的代码位置。
3. traps.c
该文件包含了 ARM 处理器各种非中断性异常的 C 语言处理函数。
- 核心功能:
do_undefinstr()
: 处理未定义指令异常。当 CPU 尝试执行一个不认识的指令时发生。do_prefetch_abort()
: 处理预取中止异常。当 CPU 尝试获取一个非法地址的指令时发生。do_data_abort()
: 处理数据中止异常。当 CPU 尝试访问一个非法地址的数据时发生(例如,用户空间尝试访问内核空间数据)。这是实现虚拟内存保护和缺页处理的关键。do_bad_stack()
: 处理栈损坏等异常。- 这些函数通常会检查错误的类型,打印内核错误信息(Oops),并可能终止引发错误的进程。
4. irq.c
该文件实现了 ARM 架构的中断管理通用逻辑。
- 核心功能:
- 中断控制器抽象: 为不同的 ARM 平台中断控制器(如 GIC)提供了一个通用的抽象层,使得上层内核无需关心具体的中断硬件细节。
- 中断注册与注销: 提供了
request_irq()
和free_irq()
等 API,供设备驱动程序注册或注销中断处理函数。 - 中断使能与禁用: 控制特定中断线的使能和禁用。
- 中断服务例程 (ISR) 的分发: 当
entry-armv.S
将中断事件分发到 C 语言层面的handle_irq()
或asm_do_IRQ()
时,irq.c
中的逻辑会查找并执行已注册的设备驱动程序 ISR。 - 软中断 (Softirq) 和 工作队列 (Workqueue) 的上下文管理。
5. smp.c
该文件实现了 ARM 架构下的多处理器(Symmetric Multi-Processing, SMP)支持。
- 核心功能:
- 次级 CPU 启动: 在 ARM 多核系统中,只有一个 CPU(通常是 CPU0)由 Bootloader 启动。
smp.c
包含了唤醒其他次级 CPU 核心并使其进入内核运行的代码和机制。 - IPI (Inter-Processor Interrupt) 管理: 实现 CPU 之间发送和接收软件中断的机制。IPI 用于 CPU 间的同步、调度和缓存一致性维护(如 TLB 刷新)。
- CPU 热插拔: 支持在运行时动态添加或移除 CPU 核心(如果硬件和平台支持)。
- CPU 间同步原语: 可能包含一些底层 CPU 间同步的汇编辅助函数。
- 次级 CPU 启动: 在 ARM 多核系统中,只有一个 CPU(通常是 CPU0)由 Bootloader 启动。
四、 总结
arch/arm/kernel/
目录是 Linux 内核的ARM 体系结构适配层。它将 ARM 处理器特有的低层细节(如异常向量、MMU、缓存控制、CPU 模式)封装起来,向上层提供了通用、抽象的接口,使得 Linux 内核的大部分 C 语言代码可以保持平台无关性。
它是理解 ARM Linux 系统如何从裸机引导到完整运行的关键所在,涉及到 CPU 启动、内存初始化、中断处理和系统调用等所有底层机制。
进入内核流程
- 从解压代码中解压代码后跳转进入
stext
汇编代码开始执行 __lookup_processor_type
循环查找processor_type中匹配的cpu信息- 没有找到匹配的处理器类型,调用
__error_p
函数打印错误信息并进入死循环 - 找到匹配的处理器类型,调用具体的cpu_flush函数,完成处理器的初始化
- V7M 处理器调用
__v7m_cm7_setup
函数
- 异常向量表的地址存储在SCB的VTOR寄存器中,以便处理器能够正确地跳转到异常处理程序。
- 启用UsageFault、BusFault和MemManage异常,以便在发生这些异常时能够进行处理。
- 设置SVC(超级用户调用)和PendSV(挂起的系统服务调用)的优先级,以便在异常发生时能够正确地处理这些异常。
- 通过SVC指令切换到线程模式,并设置堆栈指针(sp)指向init_thread_union + THREAD_START_SP,以便为线程模式准备好堆栈。
- 分配THREAD_SIZE大小的栈空间
- 计算异常返回值,设置控制寄存器(CONTROL)为非特权模式,以便在异常返回时能够正确地恢复处理器状态。
- 配置缓存(如果硬件支持),以提高系统性能。
- 配置系统控制寄存器以确保8字节堆栈对齐,以满足ARM Cortex-M7的对齐要求。
- V7M 处理器调用
- 进入
__after_proc_init
函数,根据配置禁用数据缓存、分支预测和指令缓存。最后,将控制寄存器的值存储到SCB中,并将异常返回值传递给__mmap_switched
函数,以继续执行后续的启动流程。 - 调用
__mmap_switched
函数,完成内核的初始化工作。
arch/arm/kernel/vmlinux.lds
- 通过lds链接脚本可知入口函数为
stext
1 | ENTRY(stext) |
asm-offsets.c: 为汇编代码生成C结构体偏移量
此文件不是一个常规的内核驱动程序, 而是一个在内核编译期间运行的特殊工具。它的核心作用是计算Linux内核中常用C语言结构体 (struct
) 内部各个成员的内存偏移量 (offset), 并将这些偏移量定义为汇编语言可以理解和使用的常量。
在单核无MMU的STM32H750平台上的原理与作用
对于STM32H750这样的平台, 内核的许多底层操作, 特别是上下文切换、异常处理和启动代码, 都是用高度优化的汇编语言编写的。这些汇编代码需要直接访问C语言定义的内核数据结构, 例如 task_struct
(任务描述符) 或 thread_info
(线程信息栈)。
然而, 汇编语言本身并不知道C结构体的布局。例如, 汇编代码无法直接理解 current_task->thread_info->flags
这样的表达式。它需要知道 flags
成员相对于 thread_info
结构体起始地址的精确字节偏移量。这个偏移量可能会因为内核版本的变化、配置选项的不同或编译器的差异而改变。
asm-offsets.c
这个工具就是为了解决这个问题而存在的。在每次编译内核时:
- 它会被编译并执行。
- 它利用C语言的
offsetof()
宏来计算出所有需要的偏移量。 - 它将这些计算结果以汇编语法 (
.equ
或#define
) 的形式输出到一个头文件中 (通常是asm-offsets.h
或offsets.h
)。 - 内核的汇编文件 (
.S
文件) 会包含这个自动生成的头文件, 从而获得所有结构体成员的最新、最准确的偏移量。
对于STM32H750平台, 这尤其重要, 因为:
- MPU配置:
#ifdef CONFIG_ARM_MPU
块中的定义对于配置内存保护单元至关重要。汇编代码需要知道MPU相关结构体的布局, 以便正确地将值写入MPU寄存器。 - 异常处理:
pt_regs
结构体的布局定义了在发生异常时, CPU寄存器在堆栈上的保存顺序。异常处理的汇编代码必须知道每个寄存器(如PC
,SP
,R0
)的准确偏移量才能正确地保存和恢复现场。 - 上下文切换: 汇编代码需要知道
thread_info
中cpu_context
的位置, 以便保存和恢复任务切换时的寄存器上下文。
总之, 这个文件是连接C语言世界和底层汇编世界的桥梁, 它为汇编代码提供了访问内核核心数据结构的精确”地址地图”, 保证了内核在特定硬件平台上的正确运行。
1 |
|
arch/arm/include/asm/glue-proc.h
- 根据不同架构执行不同的代码
1 |
PROC_INFO 架构信息
arch/arm/include/asm/vmlinux.lds.h
1 | //arch/arm/kernel/vmlinux.lds.S |
arch/arm/include/uapi/asm/hwcap.h “hardware capabilities”(硬件能力)
HWCAP flags “hardware capabilities”(硬件能力)
- hwcap 是 “hardware capabilities”(硬件能力)的缩写,用于描述处理器支持的硬件特性或扩展功能。
1 | /* |
arch/arm/include/asm/procinfo.h
1 | /* |
arch/arm/mm/proc-macros.S
initfn
- 这段代码的逻辑是通过 initfn 宏计算两个符号(func 和 base)之间的偏移量,并将结果存储为一个 32 位值。结合 initfn \initfunc, \name 的调用,具体逻辑如下:
1 | .macro initfn, func, base |
arch/arm/mm/proc-v7m.S
ARMv7-M 处理器定义块
此代码片段是Linux内核中的一段ARM汇编代码。其核心作用是定义一系列数据结构和函数指针, 以便内核能够识别和正确操作基于ARMv7-M架构的CPU (如此处特指的Cortex-M7)。它特别为无MMU (内存管理单元) 的单核系统 (如STM32H750) 进行了配置, 通过宏来生成一个处理器信息表 (proc_info_list
) 中的条目。内核在启动时会查询这个表, 以找到与当前CPU匹配的配置, 并据此设置正确的中断/异常处理器、缓存操作函数以及电源管理函数。
1 | /* |
THREAD_SIZE 分配栈大小
__v7m_cm7_setup
函数中分配THREAD_SIZE大小的栈空间
1 | ldr sp, =init_thread_union + THREAD_START_SP @ 设置堆栈指针SP |
- arch/arm/include/asm/thread_info.h 中定义了
THREAD_SIZE
为8192-8字节,预留8字节用于填充magic,用于溢出检测
1 |
|
- arch/arm/kernel/vmlinux.lds 中定义了
init_thread_union
1 | // include/asm-generic/vmlinux.lds.h |
1 | .data : AT(ADDR(.data) - 0) |
arch/arm/include/asm/assembler.h
setmode 用于在引导期间断言处于 SVC 模式
1 | #if defined(CONFIG_CPU_V7M) |
safe_svcmode_maskall 干净地进入 SVC 模式并屏蔽中断
1 | /* |
str_va 将一个 32 位字存储到 \sym 的虚拟地址
1 | /* |
set_current 存储此 CPU 当前任务的任务指针
- 将寄存器中的值存储到符号 __current 所表示的虚拟地址中
1 | /* |
disable_irq_notrace enable_irq_notrace
1 | /* |
ldr_va 从符号\sym的虚拟地址加载一个32位的字
1 | /* |
ldr_this_cpu 从每个 CPU 的变量 ‘sym’ 加载一个 32 位字 插入寄存器 ‘rd’
1 | /* |
get_thread_info 获取当前线程信息
1 | /* |
arch/arm/include/asm/v7m.h
1 | //arch/arm/include/asm/assembler.h |
arch/arm/kernel/entry-header.S
1 | /* |
v7m_exception_entry 进入中断
v7m_exception_entry
宏的主要目的是为ARMv7-M架构的异常处理程序提供一个安全且一致的运行环境。ARMv7-M处理器在发生异常时会自动保存部分寄存器状态,但这并不足以满足Linux内核的需求。Linux内核需要:- 保存更多的寄存器状态:ARMv7-M自动保存的寄存器(如
xPSR
、R0-R3
等)不足以满足内核的上下文切换需求,因此需要额外保存R4-R11
等寄存器。 - 禁用中断:Linux内核假定在进入异常处理程序时中断已被禁用,以避免中断干扰异常处理。
- 对齐堆栈:ARMv7-M可能会自动对堆栈进行8字节对齐,Linux内核需要根据对齐情况调整堆栈指针。
- 为异常处理程序准备上下文:将寄存器状态和其他必要信息存储到堆栈中,供异常处理程序使用。
- 保存更多的寄存器状态:ARMv7-M自动保存的寄存器(如
v7m_exception_entry
宏的作用可以总结为以下几点:
- 确定异常堆栈:根据异常发生时的模式(用户模式或内核模式),选择主堆栈(
SP_main
)或进程堆栈(PSP
)。 - 保存寄存器状态:将所有需要的寄存器(包括
R0-R11
、R12
、LR
、PC
和xPSR
)保存到堆栈中。 - 禁用中断:确保异常处理程序在执行时不会被中断干扰。
- 调整堆栈对齐:根据
xPSR
中的对齐标志调整堆栈指针,确保堆栈对齐符合ARMv7-M架构的要求。 - 为异常处理程序准备上下文:将寄存器状态和其他必要信息存储到堆栈中,供异常处理程序使用。
1 | /* |
v7m_exception_slow_exit 退出中断
- 读取当前内核栈上的pt_regs结构体,从中提取出被中断任务的核心寄存器(SP, LR, PC, xPSR等),然后在目标任务的栈上(这里是PSP)手动重建一个硬件能够识别的、最小化的异常帧。最后,通过BX LR指令,触发硬件自动完成剩余的恢复工作。
1 | /* |
restore_user_regs 恢复寄存器.
- restore_user_regs 的唯一职责是,将在内核栈上保存的pt_regs结构体中的值,准确无误地加载回CPU的物理寄存器中,并执行最后的返回指令,让CPU从被中断的地方继续运行。
1 | .macro restore_user_regs, fast = 0, offset = 0 |
scno (系统调用号) tbl (系统调用表指针) why (Linux syscall) tsk (当前线程信息)
1 | /* |
invoke_syscall (调用系统调用)
- 参数校验:首先,它会检查用户传入的系统调用号(nr)是否在一个合法的范围内,防止恶意或错误地调用未定义的服务,这是一个基本的安全措施。
- 设置返回点:它会预先将系统调用执行完毕后应该返回的地址(由ret参数指定)加载到LR(链接寄存器)中。这样,当内核函数执行BX LR或MOV PC, LR时,就能正确地回到系统调用处理的后续流程中。
- 参数重载(可选):在某些情况下(如系统调用被信号中断后重启),需要从栈上的pt_regs中重新加载参数。reload参数控制是否执行这个操作。
- 查表与跳转:这是最核心的一步。它以系统调用表(table)的基地址为准,以系统调用号(nr)为索引,计算出目标内核函数(如sys_read, sys_write)的地址,然后直接通过LDR PC, […]指令跳转到该函数去执行。
1 | /* |
Spectre漏洞是什么?一个生动的比喻
想象一位非常性急但能干的图书管理员,他叫“预测执行”(Speculative Execution)。
用户请求:你(一个程序)向管理员要一本书,但需要提供一个复杂的索引号
X
。这个X
的计算很慢,而且你可能没有权限访问它。管理员的“预测”:管理员不想干等,他非常聪明,会猜测你最终会要哪本书。比如,他根据你以前常借的书,猜测你这次可能要的是《烹饪大全》。
提前拿书(预测执行):在等你计算出
X
的时候,他就已经跑去书库,把《烹饪大全》拿了出来,甚至翻开了第一页放在桌上。这个过程非常快,但都是在“幕后”悄悄进行的。最终检查:你终于计算出了索引号
X
,并把它交给管理员。- 情况A(猜对了):如果
X
正好对应《烹饪大全》,管理员直接把已经准备好的书给你,大大节省了时间。 - 情况B(猜错了或无权限):如果
X
对应的是《黑客秘籍》,而你没有权限访问,管理员会立刻意识到错误。他会撤销之前的操作,把《烹饪大全》放回书架,擦掉桌上的痕迹,然后正式地告诉你:“抱歉,你无权访问这本书。”
- 情况A(猜对了):如果
漏洞在哪里?
从表面上看,一切都安全无虞。你最终没有拿到不该拿的书。但问题在于,管理员在“预测执行”时,虽然最后撤销了操作,但他的行为留下了一些微小的、可被观察到的“副作用”(Side Effects)。
- 副作用:比如,他去拿《烹饪大全》时,那个书架上的灰尘被多擦掉了一点;或者他回来时,因为拿了本厚书,心跳稍微快了一点。
一个精明的攻击者(另一个恶意程序)虽然看不到书的内容,但可以通过精确测量这些副作用(例如,通过测量访问某个内存地址的速度,即缓存攻击),来反推出管理员曾经预测性地访问过哪些数据。
Spectre漏洞的核心:攻击者可以诱导处理器去预测性地执行一些它本不该执行的代码(比如访问内核或其他进程的敏感数据),即使这些操作最终会被撤销,但其执行过程中在CPU缓存(Cache)中留下的痕迹,会像“幽灵”一样泄露出本应保密的数据。它利用的是现代CPU为了追求性能而广泛使用的“预测执行”机制。
Spectre漏洞的两个主要变种
变种1:分支目标注入 (Bounds Check Bypass)
- 原理:攻击者通过重复训练CPU的分支预测器,让它相信一个if条件判断总是会走向某个分支。然后,攻击者提供一个恶意的、会导致数组越界访问的索引。CPU在正式计算出索引越界之前,会预测性地执行那个分支,用恶意索引去访问内存中的敏感数据,从而将数据加载到缓存中。
- 比喻:诱导图书管理员相信你总是借阅A区1-10号书架的书,然后突然给他一个11号书架的请求,在他反应过来“哦,越界了”之前,他已经预测性地跑去11号书架看了一眼。
变种2:分支历史注入 (Branch Target Injection)
- 原理:这个变种更复杂,它利用了CPU中间接跳转(Indirect Branch)的预测机制。攻击者可以“毒化”分支目标缓冲器(BTB),让CPU在执行一个间接跳转时,错误地预测并跳转到攻击者指定的一小段代码(称为gadget)上,这段代码会访问敏感数据。
- 比喻:在图书馆的索引卡片上(比如“烹饪类 -> 见C区”),攻击者用一种特殊的方式涂改,让管理员在查找“烹饪类”时,错误地以为应该跑去“禁书区”看一眼。
如何修复(或更准确地说,缓解)?
Spectre是CPU微架构的根本性设计问题,无法像普通软件Bug一样被“修复”,只能通过多种手段进行缓解(Mitigation),而这些缓解措施通常会带来性能损失。
软件缓解:
- 增加屏障指令 (Fences):在关键代码路径上插入内存或指令屏障,强制CPU停止预测,等待前序操作完成后再继续。这会显著影响性能。我们之前分析的
CSDB
(条件同步屏障)就是一种轻量级的屏障。 - Retpoline(返回蹦床):这是针对变种2的一种非常有效的软件缓解技术。它将间接跳转替换为一个安全的、不会被恶意预测的无限循环(即“蹦床”),然后通过
ret
指令从这个循环中“弹出”到真正的目标地址。这阻止了CPU进行恶意的分支预测,但也会带来一定的性能开销。 - 清除分支预测器历史:在特权级切换时(如从内核返回用户态),清除分支预测器的历史记录,防止用户态程序“毒化”的预测影响到内核。
- 增加屏障指令 (Fences):在关键代码路径上插入内存或指令屏障,强制CPU停止预测,等待前序操作完成后再继续。这会显著影响性能。我们之前分析的
编译器缓解:
- 编译器可以自动在代码中插入缓解措施。例如,在数组访问前插入掩码操作,确保索引不会越界,从而防止变种1的攻击。
微码(Microcode)更新:
- CPU厂商(Intel, AMD, ARM)可以发布微码更新,为CPU增加新的指令(如IBRS, IBPB)来更好地控制预测行为。操作系统可以通过调用这些新指令来增强安全性。
硬件修复:
- 这是最根本的解决方案。自Spectre被发现以来,新设计的CPU都在硬件层面加入了缓解措施,例如改进分支预测器使其更难被“毒化”,或者提供更细粒度的预测行为控制。但这需要购买新的硬件。
总结:修复Spectre漏洞是一个系统性的工程,需要在操作系统、编译器、CPU微码和硬件设计等多个层面协同工作。这是一个在安全和性能之间不断进行艰难权衡的过程。您在内核代码中看到的#ifdef CONFIG_CPU_SPECTRE
和csdb
指令,正是这个宏大斗争在代码层面的具体体现。
arch/arm/kernel/entry-common.S
ret_to_user ret_to_user_from_irq ret_slow_syscall
- ret_to_user (系统调用的返回路径)
当一个任务执行系统调用(例如 read(), write(), fork())并且该调用执行完毕后,内核需要返回到该任务。它就会跳转到ret_to_user。
1 | /* |
vector_swi 用户空间的程序调用内核时
- 建立内核环境:在手动保存了用户态寄存器(简化代码中省略了这部分)后,vector_swi首先会切换到内核的运行环境,最重要的一步是开启中断,允许更高优先级的中断来抢占正在执行的系统调用。
- 获取调用号:它遵循EABI规范,假定系统调用号已经由用户空间的C库放入了R7寄存器。这避免了旧ABI中需要反汇编SWI指令来解码调用号的复杂过程。
- 查表与分发:它以R7中的调用号为索引,在主系统调用表sys_call_table中找到对应的内核C函数地址。
- 调用与返回:通过invoke_syscall宏,它调用了查找到的C函数来执行具体的内核服务。这个宏还内置了对系统调用被信号中断后自动重启的支持。
集成追踪:在调用C函数前后,它都检查了追踪标志位,如果任务正在被strace等工具追踪,它会跳转到专门的__sys_trace路径,以便记录系统调用的参数和返回值。
1 | ENTRY(vector_swi) |
slow_work_pending 异常和系统调用统一返回路径上的慢速工作处理程序
1 | /* |
ret_fast_syscall __ret_fast_syscall
1 | /* |
ret_from_fork 新创建的任务,在首次被调度器调度后,所执行的第一个函数
- 是所有通过叉()、克隆()或线程创建(kthread_create)系统调用新创建的任务,在首次被调度器调度后,所执行的第一个函数
1 | /* |
arch/arm/kernel/entry-v7m.S
Vector table
- ARMv7-M架构的异常向量表定义了处理器在发生各种异常时应执行的处理程序地址。每个异常都有一个对应的入口点,处理器在发生异常时会跳转到这些入口点执行相应的处理逻辑。
- 在Linux内核中,异常向量表通常位于一个特定的汇编文件中,并且需要确保其自然对齐(即每个入口点的地址都应是4字节对齐的)。以下是一个典型的ARMv7-M异常向量表的定义示例:
vector_table
是异常向量表的入口点,包含了各种异常的处理程序地址。每个异常都有一个对应的处理函数,如果没有定义特定的处理函数,则使用__invalid_entry
作为默认处理函数。__invalid_entry
函数用于处理未定义的异常,它会打印异常信息并进入死循环,以防止系统继续运行。__irq_entry
函数是外部硬件中断的统一入口点,它会保存当前任务的寄存器状态,切换到安全的IRQ栈,并调用通用的中断处理逻辑。处理完中断后,如果有需要推迟的工作,会触发PendSV异常来处理这些工作。exc_ret
是一个全局符号,用于存储异常返回的地址。它通常被用作异常返回时的跳转目标。__pendsv_entry
是PendSV异常的入口点,用于处理需要推迟的工作。它会在中断处理完毕后被调用,以确保系统能够正确地处理这些工作。vector_swi
是SVC(Supervisor Call)异常的入口点,用于处理系统调用。它会在用户空间程序请求内核服务时被调用。
1 | /* |
__invalid_entry 打印异常信息进入死循环
1 | __invalid_entry: |
__irq_entry
- 标准化现场:当中断发生时,首先调用v7m_exception_entry宏,在当前任务的内核栈上创建一个标准的struct pt_regs寄存器帧。
- 切换到安全栈:为了防止中断服务程序耗尽任务内核栈,它会临时将CPU的栈指针切换到一个独立的、专用的IRQ栈。
- 调用通用处理逻辑:在安全的IRQ栈上,调用高层的、用C语言实现的generic_handle_arch_irq函数。这个C函数会根据中断号,分发并执行由设备驱动程序注册的真实中断服务例程(ISR)。
- 检查并推迟工作:在C函数处理完毕后,它会检查是否有因这次中断而产生的、需要稍后处理的慢速工作(如任务调度)。如果有,它并不立即执行,而是通过触发一个低优先级的PendSV异常来“委托”这项工作。
- 快速返回:最后,执行一个简化的硬件返回序列,尽快退出中断,将CPU控制权交还。
1 | /* |
__pendsv_entry pensv中断进入
1 | /* 这段代码是PendSV异常的入口点。*/ |
__switch_to
1 | // arch/arm/include/asm/thread_info.h |
1 | /* |
arch/arm/mm/proc-v7m.S
__v7m_cm7_setup V7M处理器的初始化函数
__v7m_cm7_setup
函数的主要作用是配置ARM Cortex-M7处理器的系统控制块(SCB)和异常处理机制。它设置了异常向量表、异常优先级、堆栈指针等,并根据需要配置缓存。
异常向量表的地址存储在SCB的VTOR寄存器中,以便处理器能够正确地跳转到异常处理程序。
启用UsageFault、BusFault和MemManage异常,以便在发生这些异常时能够进行处理。
设置SVC(超级用户调用)和PendSV(挂起的系统服务调用)的优先级,以便在异常发生时能够正确地处理这些异常。
通过SVC指令切换到线程模式,并设置堆栈指针(sp)指向init_thread_union + THREAD_START_SP,以便为线程模式准备好堆栈。
- 分配THREAD_SIZE大小的栈空间
计算异常返回值,设置控制寄存器(CONTROL)为非特权模式,以便在异常返回时能够正确地恢复处理器状态。
配置缓存(如果硬件支持),以提高系统性能。
配置系统控制寄存器以确保8字节堆栈对齐,以满足ARM Cortex-M7的对齐要求。
1 | __v7m_cm7_setup: |
cpu_v7m_do_idle 进入空闲状态
1 | /* |
arch/arm/kernel/head-nommu.S
stext 内核启动入口点
1 | /* |
__after_proc_init 内核启动后处理函数
__after_proc_init
函数的主要作用是设置控制寄存器(Control Register)和读取处理器ID。它在内核启动后执行,确保处理器处于正确的状态,并为后续的操作做好准备。- 该函数首先加载SCB的基地址,然后根据配置禁用数据缓存、分支预测和指令缓存。最后,将控制寄存器的值存储到SCB中,并将异常返回值传递给
__mmap_switched
函数,以继续执行后续的启动流程。 - 该函数的实现依赖于ARM Cortex-M7架构的特性,使用了特定的寄存器和指令来完成这些操作。
1 | /* |
arch/arm/kernel/head-common.S
__error 死循环
- 死循环
1 | __error: |
__error_p
- 打印R9 CPUID 值,并显示错误信息不支持的CPU架构
- 跳转进入
__error
,死循环
1 | __error_p: |
__lookup_processor_type 循环查找processor_type中匹配的cpu信息
1 | /* |
lookup_processor_type 查找处理器类型
1 | /* |
__mmap_switched_data 内核启动后数据段
- 包含内核启动后需要初始化的数据段和BSS段的起始和结束地址,以及一些其他信息,如处理器ID、机器类型、ATAG指针等。
- 该数据段在内核启动后被
__mmap_switched
函数使用,以便在内核初始化过程中进行必要的设置和清理。
1 | .align 2 |
__mmap_switched 内核启动后处理函数
__mmap_switched
函数的主要作用是完成内核启动后的初始化工作.
- 清除未初始化的BSS段
- 保存处理器ID和机器类型等信息。它在内核启动过程中被调用,以确保内核在正确的状态下运行。
1 | /* |
——————————————–
arch/arm/include/asm/cputype.h
read_cpuid_id 读取处理器ID
1 |
|
cpuid_feature_extract 处理器特征提取
1 | //从SCB读取处理器ID寄存器的值 |
arch/arm/kernel/devtree.c
__arch_info 架构信息
arch/arm/include/asm/mach/arch.h
- 定义了
__arch_info
结构体,表示机器描述符的基本信息,包括机器类型、名称、DTB兼容性等。 - 该结构体用于在内核启动时识别和匹配不同的机器类型,以便进行适当的初始化和配置。
1 | /* |
- arch/arm/kernel/vmlinux.lds
- 其中定义了相关变量存储机器描述符信息的起始和结束地址。
- 使用
.arch.info.init
节来存储机器描述符信息,并使用KEEP
指令确保这些信息在链接时不会被丢弃。
1 | .init.arch.info : { |
- 例如
st,stm32h750
的机器描述符信息如下:
1 | //arch/arm/mach-stm32/board-dt.c |
arch_get_next_mach 获取下一个机器描述符
1 | static const void * __init arch_get_next_mach(const char *const **match) |
setup_machine_fdt 将 dtb 传递到内核时的计算机设置,返回struct machine_desc信息
1 | /** |
arm_dt_init_cpu_maps 初始化CPU映射
1 | /* |
arch/arm/kernel/early_printk.c 早期printk打印
- 调用.s文件进行printk打印
1 | extern void printascii(const char *); |
arch/arm/kernel/setup.c
smp_setup_processor_id 处理器ID设置函数
1 | void __init smp_setup_processor_id(void) |
lookup_processor 查找处理器类型
1 | struct proc_info_list *lookup_processor(u32 midr) |
__get_cpu_architecture 获取CPU架构
1 | //arch/arm/include/asm/system_info.h |
cpuid_init_hwcaps 设置CPU硬件能力
1 | static void __init cpuid_init_hwcaps(void) |
cacheid_init 缓存ID初始化函数
1 | static void __init cacheid_init(void) |
setup_processor 处理器设置函数
1 | static void __init setup_processor(void) |
request_standard_resources 请求标准资源
1 | /* |
setup_arch 设置体系结构
1 | void __init setup_arch(char **cmdline_p) |
customize_machine: 执行特定于开发板的初始化回调
此函数是Linux内核ARM架构移植层中的一个标准化初始化钩子(hook)。它的核心作用是在内核启动的早期阶段, 调用一个由特定开发板或平台提供的、名为init_machine
的C语言回调函数。这个回调函数用于执行那些无法通过设备树(Device Tree)来描述的、非常特殊的板级硬件初始化操作, 例如以编程方式注册平台设备。
在单核无MMU的STM32H750平台上的原理与作用
在像STM32H750这样的现代嵌入式系统上, 硬件的描述和初始化几乎完全由设备树(Device Tree)来驱动。内核会解析设备树, 并自动创建和注册其中描述的所有设备。因此, init_machine
这种传统的、基于C代码的板级初始化方法基本上已被弃用。
对于一个标准的、基于设备树的STM32H750内核配置, machine_desc->init_machine
这个函数指针几乎总是NULL
。machine_desc
结构体本身是ARM架构的遗留产物。
因此, 在STM32H750的启动过程中, customize_machine
这个函数虽然会因为arch_initcall
的注册而被调用, 但其内部的if
条件判断将为假, 函数会直接返回0, 不执行任何实际操作。真正的平台设备初始化将由内核后续的设备树解析代码来完成。
这段代码的存在主要是为了保持对那些非常古老的、没有使用设备树的ARM开发板的向后兼容性。
1 | /* |
arch/arm/include/asm/switch_to.h
switch_to 切换任务
1 |
|