Linux kernel 启动流程分析3

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 (第二阶段)   |
+-----------------------------------+

解释每一步:

  1. 入口:stext

    • 这是内核启动的第一个函数,ARM 架构的内核启动代码通常会在汇编文件中定义 ENTRY(stext) 作为入口点,CPU 会从此处开始执行内核的初始化代码。
  2. 设置为 SVC 模式,关闭所有中断

    • 在引导阶段,ARM 处理器默认是运行在 User 模式,但是为了执行特权操作,必须将处理器模式切换到 SVC 模式。同时,在此阶段关闭中断,避免在内核初始化期间中断干扰。
  3. 获取 CPU ID,提取相应的 proc_info

    • 通过获取 CPU 的标识符(通常是通过某种机制如 CPUID 指令),可以从设备树或者其他硬件资源中获取到与当前 CPU 相关的配置信息(如架构、特性等)。这些信息将用于后续的处理器配置。
  4. 验证 tags 或者 dtb

    • TagsDTB (Device Tree Blob) 是在引导过程中传递给内核的一些配置信息,内核需要验证这些信息的有效性。通过这些信息,内核可以识别硬件配置及设备信息。
  5. 创建页表项

    • 在内核启动时,需要设置虚拟内存管理(MMU)。此时,内核创建并初始化页表,确保各个虚拟地址正确映射到物理内存。
  6. 配置 r13 寄存器,设置跳转函数

    • 寄存器 r13 通常用于保存栈指针等关键信息。在此步骤中,内核会配置好栈指针,并设置跳转地址,以便在开启 MMU 后能够正确跳转到其他函数。
  7. 使能 MMU (Memory Management Unit)

    • 启用 MMU 以开启虚拟内存管理。通过 MMU,可以实现内存地址的映射,从而支持虚拟内存、进程隔离等功能。此时,通过设置相应的控制寄存器,开启 MMU。
  8. 跳转到 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 模式的特点

  1. 特权级:高于 User 模式,允许访问系统资源和执行敏感操作。
  2. 切换机制:用户程序可以通过触发软件中断 (SWI 指令) 来进入 SVC 模式。
  3. 寄存器保护:SVC 模式有自己的寄存器保存机制,以便在从用户模式切换回来时恢复程序的状态。


2.2、为什么要设置成SVC模式?

  1. 执行特权操作

    • 操作系统内核需要执行一系列特权操作,例如控制硬件设备、修改内存映射、调度任务等。这些操作涉及系统的核心资源,不能让普通用户程序直接执行,以防止潜在的安全风险或系统不稳定。
    • SVC 模式 提供了高于 User Mode 的特权级别,允许执行这些敏感操作。
  2. 安全性

    • SVC 模式 使得内核代码能够以受控的方式访问系统资源,而不暴露给普通用户程序。这样,用户程序无法直接操作硬件资源或改变内核态下的关键数据,保证了系统的安全性和稳定性。
    • 通过将普通应用程序限制在 User Mode 中,操作系统可以防止应用程序执行有害的操作,如非法访问内存、修改硬件配置等。
  3. 系统调用 (Syscalls)

    • 操作系统的内核通过 SVC 模式 来处理用户程序发起的系统调用。用户程序通过触发 SWI(Software Interrupt) 指令进入 SVC 模式,然后内核处理相应的请求并返回到用户程序。
    • SVC 模式 是操作系统与应用程序之间进行通信的桥梁,允许用户程序通过定义良好的接口请求系统资源,而不直接访问硬件或内核数据。
  4. 中断和异常处理

    • 操作系统需要能够处理中断和异常,如外部设备中断、内存错误等。中断和异常的处理通常在 SVC 模式 或更高特权模式下进行,以保证系统能够正确响应外部事件并恢复到正常状态。
    • 例如,内存管理的异常(如页面错误)会触发 Abort Mode,而外部中断会进入 IRQ ModeFIQ Mode,这些模式下的处理通常需要 SVC 模式 提供的特权访问。
  5. 上下文切换

    • 操作系统的内核需要管理多任务环境,即调度不同的进程。进行上下文切换时,操作系统需要在 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 ModeSVC Mode 的切换。

SWI 指令的作用:

  • SWI 指令会触发一个软中断(软件中断),它会引发 CPU 切换到 SVC 模式
  • 触发中断后,ARM7 会保存当前的程序计数器(PC)和一些寄存器的值,并跳转到一个预定义的异常向量地址,开始执行异常处理程序(通常由操作系统的内核提供)。

SWI 示例:

SWI 0    ; 触发 SWI 中断

在触发 SWI 后,处理器会:

  • 自动切换到 SVC 模式
  • 保存当前程序计数器(PC)和一些其他的寄存器状态。
  • 跳转到相应的异常向量地址来执行异常处理程序。

2. 在 ARM7 处理器中设置 SVC 模式

ARM7 处理器有 7 种操作模式,其中 SVC 模式 是用来执行操作系统内核的特权代码。进入 SVC 模式后的具体步骤通常是:

  1. 进入 SVC 模式: 在 ARM 架构中,每种操作模式对应一个特定的状态寄存器和堆栈配置。在 ARM7 中,SVC 模式 用于执行特权操作。进入 SVC 模式通常由触发 SWI 指令实现。

  2. 异常向量和 SVC 向量表: ARM7 处理器通过异常向量表来处理不同的异常。每个异常类型(例如 SWI)都有对应的向量表地址。在异常向量表中,SWI 的默认向量地址是 0x08(通常是中断服务程序的入口点)。

  3. 设置异常向量表: 在进入 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

到这里就实现了工作模式的切换和中断的关闭。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值