Linux kernel 启动流程分析3---第一阶段设置SVC、关闭中断
一、概述
1.1、kernel启动流程第一阶段简单说明
kernel 启动流程第一阶段
+-----------------------------------+
| 入口: stext |
+-----------------------------------+
|
v
+-----------------------------------+
| 设置为 SVC 模式, 关闭所有中断 |
+-----------------------------------+
|
v
+-----------------------------------+
| 获取 CPU ID, 提取相应的 proc_info |
+-----------------------------------+
|
v
+-----------------------------------+
| 验证 tags 或者 dtb |
+-----------------------------------+
|
v
+-----------------------------------+
| 创建页表项 |
+-----------------------------------+
|
v
+-----------------------------------+
| 配置 r13 寄存器, 设置跳转函数 |
+-----------------------------------+
|
v
+-----------------------------------+
| 使能 MMU (Memory Management Unit) |
+-----------------------------------+
|
v
+-----------------------------------+
| 跳转到 start_kernel (第二阶段) |
+-----------------------------------+
解释每一步:
-
入口:
stext
- 这是内核启动的第一个函数,ARM 架构的内核启动代码通常会在汇编文件中定义
ENTRY(stext)
作为入口点,CPU 会从此处开始执行内核的初始化代码。
- 这是内核启动的第一个函数,ARM 架构的内核启动代码通常会在汇编文件中定义
-
设置为 SVC 模式,关闭所有中断
- 在引导阶段,ARM 处理器默认是运行在 User 模式,但是为了执行特权操作,必须将处理器模式切换到 SVC 模式。同时,在此阶段关闭中断,避免在内核初始化期间中断干扰。
-
获取 CPU ID,提取相应的
proc_info
- 通过获取 CPU 的标识符(通常是通过某种机制如
CPUID
指令),可以从设备树或者其他硬件资源中获取到与当前 CPU 相关的配置信息(如架构、特性等)。这些信息将用于后续的处理器配置。
- 通过获取 CPU 的标识符(通常是通过某种机制如
-
验证 tags 或者 dtb
- Tags 或 DTB (Device Tree Blob) 是在引导过程中传递给内核的一些配置信息,内核需要验证这些信息的有效性。通过这些信息,内核可以识别硬件配置及设备信息。
-
创建页表项
- 在内核启动时,需要设置虚拟内存管理(MMU)。此时,内核创建并初始化页表,确保各个虚拟地址正确映射到物理内存。
-
配置 r13 寄存器,设置跳转函数
- 寄存器
r13
通常用于保存栈指针等关键信息。在此步骤中,内核会配置好栈指针,并设置跳转地址,以便在开启 MMU 后能够正确跳转到其他函数。
- 寄存器
-
使能 MMU (Memory Management Unit)
- 启用 MMU 以开启虚拟内存管理。通过 MMU,可以实现内存地址的映射,从而支持虚拟内存、进程隔离等功能。此时,通过设置相应的控制寄存器,开启 MMU。
-
跳转到
start_kernel
(第二阶段)- 在完成上述所有硬件初始化后,内核会跳转到
start_kernel
,这是内核的第二阶段,之后的任务会在此函数中完成,例如内核线程的创建、调度初始化等。
- 在完成上述所有硬件初始化后,内核会跳转到
1.2、疑问
主要带着以下几个问题去理解
设置SVC模式
- 什么是SVC模式?
- 为什么要设置成SVC模式?
- 如何设置成SVC模式
关闭所有中断
- 为什么要关闭所有中断?
- 如何关闭所有中断?
- 对应代码
ENTRY(stext)
@ ensure svc mode and all interrupts masked
safe_svcmode_maskall r
safe_svcmode_maskall实现代码如下:
arch/arm/include/asm/assembler.h
.macro safe_svcmode_maskall reg:req
#if __LINUX_ARM_ARCH__ >= 6 && !defined(CONFIG_CPU_V7M)
mrs \reg , cpsr
eor \reg, \reg, #HYP_MODE
tst \reg, #MODE_MASK
bic \reg , \reg , #MODE_MASK
orr \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE
THUMB( orr \reg , \reg , #PSR_T_BIT )
bne 1f
orr \reg, \reg, #PSR_A_BIT
badr lr, 2f
msr spsr_cxsf, \reg
__MSR_ELR_HYP(14)
__ERET
1: msr cpsr_c, \reg
2:
这部分代码也就是后面要讲述的“设置为SVC模式,关闭所有中断”的核心
二、设置为SVC模式
2.1、ARM7体系的几种工作模式(什么是SVC模式?)
ARM7 处理器支持多种工作模式,每种模式对应不同的特权级别和用途。以下是 ARM7 体系结构的几种工作模式,以及每种模式的简要描述:
工作模式 | 模式编号 (CPSR 中的 M 位) | 描述 |
---|---|---|
User 模式 | 0x10 | 用户模式,用于普通应用程序的执行,特权级最低,无法访问硬件资源。 |
FIQ 模式 | 0x11 | 快速中断模式,处理快速中断请求,具有更高的优先级和更多的寄存器,适合处理时效要求高的任务。 |
IRQ 模式 | 0x12 | 普通中断模式,处理标准中断,特权级较高,响应外部硬件中断。 |
Supervisor (SVC) 模式 | 0x13 | 管理模式,内核代码运行的模式,通常在操作系统或引导加载程序中使用,特权级较高,允许访问硬件资源。 |
Abort 模式 | 0x17 | 内存访问异常模式,通常用于处理内存管理错误(如访问非法地址)。 |
Undefined 模式 | 0x1B | 未定义指令模式,用于处理处理器无法识别的指令。 |
System 模式 | 0x1F | 系统模式,用于内核代码的执行,具有更高的特权级,接近于 SVC 模式,但不需要进行用户程序的切换。 |
重点:SVC 模式
**SVC(Supervisor Mode,监控模式)**是 ARM7 处理器中一种重要的特权模式,通常用于操作系统的内核代码执行。内核代码需要执行敏感的操作,如访问硬件设备、进行内存管理、处理中断等。这些操作在 User 模式 中是无法进行的,因此需要切换到 SVC 模式。
- 作用:SVC 模式是操作系统内核运行的标准模式,具有最高的权限,可以访问所有硬件资源和执行特权指令。
- 切换方式:通过
SWI
(软件中断)指令,CPU 会从 User 模式 切换到 SVC 模式。
SVC 模式的特点:
- 特权级:高于 User 模式,允许访问系统资源和执行敏感操作。
- 切换机制:用户程序可以通过触发软件中断 (
SWI
指令) 来进入 SVC 模式。 - 寄存器保护:SVC 模式有自己的寄存器保存机制,以便在从用户模式切换回来时恢复程序的状态。
2.2、为什么要设置成SVC模式?
-
执行特权操作:
- 操作系统内核需要执行一系列特权操作,例如控制硬件设备、修改内存映射、调度任务等。这些操作涉及系统的核心资源,不能让普通用户程序直接执行,以防止潜在的安全风险或系统不稳定。
- SVC 模式 提供了高于 User Mode 的特权级别,允许执行这些敏感操作。
-
安全性:
- SVC 模式 使得内核代码能够以受控的方式访问系统资源,而不暴露给普通用户程序。这样,用户程序无法直接操作硬件资源或改变内核态下的关键数据,保证了系统的安全性和稳定性。
- 通过将普通应用程序限制在 User Mode 中,操作系统可以防止应用程序执行有害的操作,如非法访问内存、修改硬件配置等。
-
系统调用 (Syscalls):
- 操作系统的内核通过 SVC 模式 来处理用户程序发起的系统调用。用户程序通过触发 SWI(Software Interrupt) 指令进入 SVC 模式,然后内核处理相应的请求并返回到用户程序。
- SVC 模式 是操作系统与应用程序之间进行通信的桥梁,允许用户程序通过定义良好的接口请求系统资源,而不直接访问硬件或内核数据。
-
中断和异常处理:
- 操作系统需要能够处理中断和异常,如外部设备中断、内存错误等。中断和异常的处理通常在 SVC 模式 或更高特权模式下进行,以保证系统能够正确响应外部事件并恢复到正常状态。
- 例如,内存管理的异常(如页面错误)会触发 Abort Mode,而外部中断会进入 IRQ Mode 或 FIQ Mode,这些模式下的处理通常需要 SVC 模式 提供的特权访问。
-
上下文切换:
- 操作系统的内核需要管理多任务环境,即调度不同的进程。进行上下文切换时,操作系统需要在 SVC 模式 下保存和恢复不同进程的状态,以便在不同进程之间切换。
- 这样,内核可以保持进程之间的隔离,并确保进程的执行不会相互干扰。
切换到 SVC 模式的方式
SVC 模式 通常通过以下方式切换:
- SWI(Software Interrupt)指令:当用户程序需要进行系统调用时,它会触发
SWI
指令,CPU 会从 User Mode 切换到 SVC Mode。在切换后,操作系统的内核可以处理系统调用请求,并执行必要的操作。
2.3、如何设置成SVC模式?
(1)CPSR & SPSR
ARM工作模式的切换由CPSR寄存器控制。
CPSR表示当前程序状态寄存器,SPSR表示备份的程序状态寄存器。
CPSR:程序状态寄存器(current program status register) (当前程序状态寄存器),在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。如下所示(具体参考文档《ARMV7官方数据手册》“Format of the CPSR and SPSRs”一节,这里只列出一些目前我们可能会用到的):
* 条件标志位(这里可以暂时不关心)
- 对应位 对应标识 功能说明
- 31bit N flag 用两个补码表示的带符号的运算结果标识,为1表示负数,为0表示非负数
- 30bit Z flag 为1表示运算结果为0,为0表示运算结果为非0
- 29bit C flag 加法运算结果进位标志位
- 28bit V flag 加减法运算指令符号位溢出标志位
* 控制位
- 对应位 对应标识 功能说明
- 7bit flag 为1表示禁止IRQ中断。
- 6bit F flag 为1表示禁止FIQ中断。
- 5bit T flag 为1表示程序运行于Thumb状态,否则处于ARM状态。
- [4:0] 模式控制位 用于配置CPU当前的工作模式。
(2)工作模式编码
七种工作模式对应在CPSR中的工作模式编码如下:
- 工作模式 工作模式编码
- USR 10000
- FIQ 10001
- IRQ 10010
- SVC 10011
- ABT 10111
- UND 11011
- SYS 11111
综上,需要将CPSR的[4:0]设置成10011就可以切换到SVC模式。
在 ARM7 处理器中,切换到 SVC(Supervisor Mode) 模式,通常是通过触发 SWI(Software Interrupt) 指令来实现的。下面是一些常见的方式和步骤来设置 ARM7 处理器进入 SVC 模式。
1. 使用 SWI
指令触发 SVC 模式
ARM7 是一个支持多种操作模式的处理器,它在不同的模式下具有不同的权限和寄存器配置。在用户程序(User Mode)运行时,执行 SWI
(Software Interrupt)指令可以触发从 User Mode 到 SVC Mode 的切换。
SWI
指令的作用:
SWI
指令会触发一个软中断(软件中断),它会引发 CPU 切换到 SVC 模式。- 触发中断后,ARM7 会保存当前的程序计数器(PC)和一些寄存器的值,并跳转到一个预定义的异常向量地址,开始执行异常处理程序(通常由操作系统的内核提供)。
SWI
示例:
SWI 0 ; 触发 SWI 中断
在触发 SWI
后,处理器会:
- 自动切换到 SVC 模式。
- 保存当前程序计数器(PC)和一些其他的寄存器状态。
- 跳转到相应的异常向量地址来执行异常处理程序。
2. 在 ARM7 处理器中设置 SVC 模式
ARM7 处理器有 7 种操作模式,其中 SVC 模式 是用来执行操作系统内核的特权代码。进入 SVC 模式后的具体步骤通常是:
-
进入 SVC 模式: 在 ARM 架构中,每种操作模式对应一个特定的状态寄存器和堆栈配置。在 ARM7 中,SVC 模式 用于执行特权操作。进入 SVC 模式通常由触发
SWI
指令实现。 -
异常向量和 SVC 向量表: ARM7 处理器通过异常向量表来处理不同的异常。每个异常类型(例如 SWI)都有对应的向量表地址。在异常向量表中,SWI 的默认向量地址是
0x08
(通常是中断服务程序的入口点)。 -
设置异常向量表: 在进入 SVC 模式 之前,操作系统可能需要设置异常向量表,以便在触发 SWI 时跳转到正确的处理程序。操作系统通常在启动时初始化这个向量表。
3. 设置 SVC 模式相关的寄存器
进入 SVC 模式 后,处理器会自动切换到特定的寄存器设置。通常不需要手动设置,除非你需要在进入 SVC 模式后对寄存器进行一些特殊操作。
- CPSR(Current Program Status Register) 会在进入 SVC 模式时自动修改,切换为 SVC 模式。
- 切换时,处理器将会切换到一个新的 SPSR(Saved Program Status Register) 来保存原来的程序状态。
4. 在 C 语言中触发 SVC
如果你使用 C 语言编程,并且想要通过 SWI
来触发 SVC 模式,通常可以使用内嵌汇编来执行此操作。
示例:
__asm("SWI 0");
此代码会触发一个软件中断,导致处理器从用户模式切换到 SVC 模式。
5. 完整的 SVC 处理过程
一旦触发了 SWI 指令,ARM7 会执行以下步骤:
- 切换到 SVC 模式。
- 保存当前程序计数器(PC)和 CPSR(当前程序状态寄存器)的值,并把它们存储到 SPSR 中。
- 根据异常向量表跳转到相应的 SWI 异常处理程序,通常是操作系统内核提供的代码。
- 在异常处理程序中,操作系统可以处理中断、执行系统调用或调度任务等操作。
6. 离开 SVC 模式
一旦内核处理完毕,系统可以通过返回指令(如 SUBS PC, LR, #4
)来从 SVC 模式 返回到用户模式(User Mode),并恢复之前的状态。
典型返回:
SUBS PC, LR, #4 ; 返回到用户模式
这将恢复用户程序的执行,并切换回 User Mode,使程序继续运行。
三、关闭所有中断
3.1、为什么要关闭所有中断?
1. 防止中断干扰初始化过程
内核启动的第一阶段通常需要对硬件进行配置、初始化操作(例如设置内存管理单元、初始化中断控制器、设置时钟等)。如果在此期间有中断触发,它可能会打断当前的初始化过程,从而导致不可预测的行为或错误。
例如,硬件中断可能会在内核设置重要寄存器、配置中断向量表或启动其他核心组件时,突如其来地中断当前操作,造成:
- 系统进入一个不一致的状态;
- 资源(如内存、设备等)尚未被正确初始化时,中断可能会造成对未初始化资源的访问,导致崩溃或错误。
因此,在启动阶段关闭中断可以确保内核能够在无外部中断的干扰下,完成系统的基本初始化。
2. 保证内核初始化的原子性
内核初始化的过程往往包含多个操作步骤,这些操作必须按顺序完成,且不允许中途被打断。比如:
- 设置内核堆栈;
- 初始化全局数据结构(如内存分配、任务调度器等);
- 配置中断向量表。
如果在初始化期间发生中断,尤其是当中断处理程序没有准备好处理这些中断时,可能会导致竞争条件(race conditions)或者资源冲突。通过关闭中断,可以确保内核初始化过程的原子性和一致性。
3. 避免提前启用中断而产生的异常行为
在内核启动时,许多硬件外设和中断控制器可能尚未初始化。如果中断在这个阶段被启用,系统可能会接收到来自尚未配置的硬件的中断请求。由于硬件和中断控制器的状态可能不一致,处理这些中断可能导致不可预见的行为或系统崩溃。
例如,系统可能没有设置正确的中断向量表,导致中断服务程序(ISR)无法正确处理这些中断。此外,外设也可能尚未准备好,因此任何对它们的访问都可能导致硬件异常或错误。
4. 延迟中断的启用
内核启动的最后阶段,当大部分硬件和子系统都已正确初始化并准备好运行时,中断才会被重新启用。此时,内核已经具备了正确的中断处理机制,能够可靠地处理中断,且内核初始化阶段所需要的资源已经准备好。这时再开启中断可以确保:
- 中断处理程序能够正确处理所有类型的中断;
- 不会对系统的稳定性和一致性产生负面影响。
5. 初始化时避免中断处理程序的错误
在内核启动时,有些系统资源(如中断服务程序、调度器等)尚未初始化。如果系统在这一阶段接收到中断请求,可能会触发尚未正确配置的中断服务程序,导致未定义的行为或系统崩溃。例如:
- 中断向量表可能为空或指向无效地址;
- 中断处理程序可能并未加载,或者存在程序错误。
6. 简化中断管理的实现
在内核启动的早期阶段,通常无法区分哪些中断是“重要的”,哪些是“次要的”,因此关闭所有中断是一种简化管理的方式。这可以避免在调度器、内存管理、设备驱动等初始化过程中需要处理复杂的中断管理问题。
7. 控制中断响应的时机
在一些架构中,中断可以分为不同的优先级。在内核初始化期间,如果不关闭中断,低优先级的中断可能会被高优先级的中断打断,造成资源竞争。通过关闭所有中断,可以完全控制何时开始响应中断,通常是在系统初始化完成后再启用。
3.2、如何关闭所有中断?
中断的关闭同样由CPSR寄存器来进行控制,具体请看上一一节
- 对应位 对应标识 功能说明
- 7bit flag 为1表示禁止IRQ中断。
- 6bit F flag 为1表示禁止FIQ中断。
综上,需要将CPSR的bit6和bit7设置为1。
在操作系统内核启动过程中,为了避免中断干扰初始化过程,通常会在启动的初期阶段关闭所有硬件中断。关闭中断的方式依赖于所使用的硬件架构和处理器的中断管理机制。以下是一般情况下如何关闭所有中断的步骤和方法:
1. 禁用中断的基本原理
大多数现代处理器提供了一个或多个控制寄存器来启用或禁用中断。启动过程中,内核通过设置这些寄存器来控制中断的启用与禁用。
在处理器的不同阶段,禁用中断的方式可能会有所不同。下面介绍了几种常见的禁用中断的方式。
2. 禁用中断的基本步骤(以 x86 和 ARM 为例)
x86 架构
对于 x86 架构,禁用中断通常通过处理器的标志寄存器来实现。具体步骤如下:
-
CLI (Clear Interrupt Flag): 在 x86 架构中,
CLI
指令用于清除中断标志位,从而禁用所有外部硬件中断(IRQ)。这一指令会清除IF
(Interrupt Flag)位,导致处理器忽略所有外部中断请求。- 内核初始化时,可以在启动的早期阶段使用
CLI
禁用中断。 - 禁用中断后,处理器将不会响应任何中断,直到程序显式地通过
STI
(Set Interrupt Flag)指令重新启用中断。
示例:
- 内核初始化时,可以在启动的早期阶段使用
-
cli ; 禁用中断
-
在内核初始化阶段禁用中断: 内核启动时,通常会在非常早的阶段禁用中断,这时操作系统的中断向量表可能尚未设置,因此直接禁用中断是安全的。
ARM 架构
对于 ARM 架构,禁用中断的方式略有不同,通常通过处理器的控制寄存器来管理中断。
-
禁用 IRQ 和 FIQ:
- ARM 处理器提供了两个主要的中断类型:IRQ(普通中断)和 FIQ(快速中断)。
- 通过设置
CPSID
(Change Processor State, Interrupt Disable)指令,可以禁用所有中断,包括 IRQ 和 FIQ。
示例:
-
cpsid i ; 禁用 IRQ(普通中断) cpsid f ; 禁用 FIQ(快速中断)
-
控制寄存器的设置: ARM 处理器的控制寄存器(
CPSR
)用于控制中断的启用与禁用。内核启动时可以通过修改这个寄存器来禁用中断。
3. 中断控制器的禁用
除了处理器的中断控制寄存器外,大多数系统还使用中断控制器(如 x86 的 PIC、APIC,或 ARM 的 GIC)来管理外部中断。在内核启动的初期阶段,内核可能会直接禁用中断控制器,确保不会收到来自硬件的中断请求。
例如:
-
x86 架构: 对于旧的 x86 系统(如使用 8259A PIC),内核可能会在启动时禁用中断控制器。
-
mov al, 0xFF out 0x21, al ; 禁用 PIC1 out 0xA1, al ; 禁用 PIC2
-
ARM 架构: 对于 ARM 系统,内核启动时通常会配置和禁用 GIC(通用中断控制器)。
4. 中断的启用时机
在内核完成基础初始化后(如设置中断向量表、初始化内存管理和其他关键系统资源),会重新启用中断。这通常通过以下步骤进行:
-
x86: 使用
sti
(Set Interrupt Flag)指令重新启用中断。
-
sti ; 启用中断
-
ARM: 使用
cpsie i
来重新启用 IRQ(普通中断)。
-
cpsie i ; 启用 IRQ(普通中断) cpsie f ; 启用 FIQ(快速中断)
5. 其他考虑
在某些架构下,除了简单地禁用中断外,还可能需要禁用特定的中断来源。例如,在内核启动时,禁用定时器中断是常见的做法,以避免调度器和定时器中断在初始化过程中打断当前的操作。
三、代码分析
设置为SVC模式和关闭所有中断都是在safe_svcmode_maskall中完成。
ENTRY(stext)
@ ensure svc mode and all interrupts masked
safe_svcmode_maskall r9
safe_svcmode_maskall实现和分析如下:
arch/arm/include/asm/assembler.h
.macro safe_svcmode_maskall reg:req
#if __LINUX_ARM_ARCH__ >= 6 && !defined(CONFIG_CPU_V7M)
mrs \reg , cpsr
eor \reg, \reg, #HYP_MODE
tst \reg, #MODE_MASK
bic \reg , \reg , #MODE_MASK
orr \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE
THUMB( orr \reg , \reg , #PSR_T_BIT )
bne 1f
orr \reg, \reg, #PSR_A_BIT
badr lr, 2f
msr spsr_cxsf, \reg
__MSR_ELR_HYP(14)
__ERET
1: msr cpsr_c, \reg
2:
其中主要的关闭中断设置SVC的核心代码如下:
mrs \reg , cpsr @获取CPSR寄存器的值到临时寄存器中
bic \reg , \reg , #MODE_MASK @清除模式位[4:0]位
orr \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE @设置BIT6\BIT7,关闭中断,设置[4:0]为SVC_MODE
1: msr cpsr_c, \reg @存储到CPSR的低八位中,CPSR_C表示CRSR寄存器的低8位,也就是控制域屏蔽字节。
其中
arch/arm/include/uapi/asm/ptrace.h中
#define SVC_MODE 0x00000013
#define PSR_F_BIT 0x00000040
#define PSR_I_BIT 0x00000080
到这里就实现了工作模式的切换和中断的关闭。