5.33 Boot设备
5.33 Boot设备
📚 本节导读
学习时长: 约 20 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: C 语言基础、ARM Cortex-M 架构与启动流程基础
本节讲解 OneOS 的 Boot 启动设备。Boot 设备是 OneOS 为 ARM Cortex-M 架构提供的芯片启动配置管理,核心职责是配置中断向量表并处理启动时 CPU 如何找到第一个执行的代码。它位于 drivers/boot/cotex-m/ 目录下。
嵌入式系统上电后,CPU 从向量表读取初始栈指针和复位入口地址,然后开始执行。如果向量表配错或中断处理被绕过,系统根本无法启动或中断会崩溃。本节会带你:
- 理解 Cortex-M 启动时向量表的作用与 OneOS 的软中断统一入口(
cortex_m_vector_entry)设计; - 掌握
cortex_m_set_vector如何配置 VTOR 与reserved_ram保留区; - 了解中断 Hook 机制(
cortex_m_irq_hook)如何让多模块挂接中断处理; - 掌握
drivers/boot/cotex-m/Kconfig的启动配置项。
说明:本节所有源码均取自项目
drivers/boot/cotex-m/vector_table.c与drivers/boot/cotex-m/Kconfig,与项目源码逐字一致(含原始英文注释)。为便于阅读,汇编相关的重复向量表项已按源码结构分段呈现,但不省略任何代码逻辑。
🎯 学习目标
- 理解 Cortex-M 启动时中断向量表的作用与结构
- 掌握
reserved_ram保留区在中断重定向中的核心地位 - 逐个拆解
cortex_m_set_vector/cortex_m_vector_entry/cortex_m_irq_hook的完整源码 - 理解"软中断统一入口"设计的动机(为什么向量表项都指向同一个函数)
- 掌握 Boot 设备的 Kconfig 启动配置
一、概述
1.1 Boot 设备解决什么问题
一句话核心:Boot 设备负责让 Cortex-M 芯片上电后能从正确的位置执行、并能让所有中断都进入 OneOS 的统一处理框架。它让你"配好向量表,芯片就能稳定启动并跑起 OneOS 的中断系统"。
为什么需要 Boot 设备:
- Cortex-M 上电后,CPU 从向量表(起始 4 字节 = 初始 SP,次 4 字节 = 复位入口)取指执行。
- OneOS 需要把所有硬件中断统一导向自己的中断处理(而不是每个驱动各自写中断函数),因此要用一套软中断统一入口 + Hook 机制。
- Boot 设备就是这套"启动 + 中断统一入口"的实现,位于芯片启动阶段(
OS_INIT_LEVEL_PRE_KERNEL_1)。
1.2 Boot 设备的分层与文件结构
Boot 设备位于 drivers/boot/cotex-m/:
| 文件 | 作用 |
|---|---|
vector_table.c | 定义软中断向量表、统一入口、VTOR 配置、Hook 机制 |
Kconfig | 启动配置(是否含向量表、外部中断、Boot 选项等) |
prebuild.py | 编译前预处理脚本(处理链接配置) |
weave.yaml | 条件编译声明 |
关键理解 —— 软中断统一入口(cortex_m_vector_entry):
Cortex-M 的向量表第 15 项之后都是外设中断(如 UART、定时器、GPIO)。在 OneOS 中,这些中断项几乎全部指向同一个函数 cortex_m_vector_entry(见 1.4 节的向量表源码),再由它根据 os_irq_num() 拿到当前中断号去分发。这样一个函数就能处理所有中断,各驱动通过 IRQ Hook 挂接自己的处理逻辑,无需每个中断单独写一个入口。
1.3 全局资源:reserved_ram 保留区
Boot 设备在 vector_table.c 顶部定义了一个关键的全局变量,是中断重定向的"锚点":
来源:drivers/boot/cotex-m/vector_table.c
int OS_USED OS_SECTION("reserved_ram") rsv_ram_start = 0;
#define RESERVED_RAM_VECTOR_ADDR (&rsv_ram_start + 1)
#define RESERVED_RAM_VECTOR_HOOK (&rsv_ram_start + 2)关键理解:
rsv_ram_start是一个放在reserved_ram段的全局变量,用OS_USED防止被链接器优化掉。RESERVED_RAM_VECTOR_ADDR = &rsv_ram_start + 1:指向rsv_ram_start后面 4 字节(+1个int),用来存真实硬件向量表的地址。RESERVED_RAM_VECTOR_HOOK = &rsv_ram_start + 2:指向再后面 4 字节,用来存中断 Hook 函数地址。
这是 OneOS 把"中断处理"从硬件向量表"重定向"到保留 RAM 的关键——CPU 触发中断时,先从
reserved_ram取真实向量表地址,再跳转到对应中断处理(见 2.2 节)。
为什么 +1 / +2(而不是 +4 / +8):rsv_ram_start 是 int(4 字节),指针加 1 等于地址加 4 字节。所以 +1 表示"跳过 4 字节",+2 表示"跳过 8 字节"。这正是为 VECTOR_ADDR 和 VECTOR_HOOK 两块预留的 8 字节。
1.4 中断向量表结构
Boot 设备在 vector_table.c 末尾定义了真正的软中断向量表 vector_table[],放在 vtor_table 段:
来源:drivers/boot/cotex-m/vector_table.c
static const vector_entry OS_USED OS_SECTION("vtor_table") vector_table[] = {
(vector_entry)(&CORTEX_M_STACK_END), /* system stack */
cortex_m_vector_entry, /* reset */
#ifdef BSP_INCLUDE_VECTOR_TABLE
(vector_entry)(&___NMI_Handler),
(vector_entry)(&___HardFault_Handler),
(vector_entry)(&___MemManage_Handler),
(vector_entry)(&___BusFault_Handler),
(vector_entry)(&___UsageFault_Handler),
cortex_m_vector_entry,
0,
0,
0,
cortex_m_vector_entry,
cortex_m_vector_entry,
0,
(vector_entry)(&___PendSV_Handler),
cortex_m_vector_entry,
#ifdef BSP_VECTOR_TABLE_INCLUDE_EXTERNAL_INT
/* 以下为外部中断项(UART/定时器/GPIO 等)。
* 与源码 vector_table.c 第 323~565 行逐字一致:
* - M0 架构:共 60 项,全部为 cortex_m_vector_entry;
* - 非 M0 架构(BSP_VECTOR_TABLE_INCLUDE_EXTERNAL_INT 下的
* #if !defined(ARCH_ARM_CORTEX_M0) 分支):在 60 项基础上再增 180 项,
* 同样全部为 cortex_m_vector_entry。
* 所有外部中断项都指向统一入口 cortex_m_vector_entry,由它统一分发。 */
cortex_m_vector_entry, /* 第 1 项(示例) */
cortex_m_vector_entry, /* 第 2 项(示例) */
/* ... 其余项全部为 cortex_m_vector_entry,详见源码 323~565 行 */
#endif
#endif
};向量表关键项说明:
| 偏移 | 向量表项 | 说明 |
|---|---|---|
| 0 | &CORTEX_M_STACK_END | 初始栈指针(系统栈顶) |
| 4 | cortex_m_vector_entry | 复位入口(上电后第一段执行代码) |
| 8~28 | ___NMI_Handler 等 | 系统异常(NMI、HardFault、MemManage、BusFault、UsageFault) |
| 60 | ___PendSV_Handler | PendSV(上下文切换关键中断) |
| 之后 | cortex_m_vector_entry | 大量外设中断项,全部指向统一入口 |
关键理解:
- 前两项(栈顶 + 复位入口)是 Cortex-M 启动的硬件约定,CPU 一上电就按此执行。
- 系统异常(NMI、Fault 类、PendSV)在
BSP_INCLUDE_VECTOR_TABLE打开时,由___xxx_Handler汇编函数接管(它们通过reserved_ram跳转到真实向量表,见 2.1 节)。 - 外设中断项全部指向
cortex_m_vector_entry,由它统一分发。
1.5 Boot 设备使用场景
1.5.1 一句话核心
Boot 设备适用于需要配置 Cortex-M 芯片启动向量表、并让所有中断进入 OneOS 统一处理框架的芯片启动阶段。它让你"配好 VTOR 与保留区,系统就能从正确位置启动且所有中断都被 OneOS 接管"。
1.5.2 典型应用场景
场景一:芯片上电启动(复位 → 内核初始化)
上电/复位
│
▼(CPU 读向量表)
向量表[0] = 初始栈指针 → 设置 SP
向量表[1] = cortex_m_vector_entry(复位入口)
│
▼
cortex_m_set_vector()(OS_INIT_LEVEL_PRE_KERNEL_1)
│ 配置 SCB->VTOR 指向 vector_table
│ 把真实向量表地址存入 reserved_ram
│ 设置 interrupt_stack_addr / interrupt_stack_size
▼
系统初始化 → 内核初始化 → 启动调度器为什么用 Boot 设备:启动向量表的配置必须在内核启动前完成(OS_INIT_LEVEL_PRE_KERNEL_1),Boot 设备通过 OS_INIT_CALL 精确控制这一时机。
场景二:所有外设中断统一分发
任意外设(UART/定时器/GPIO)产生中断时:
外设中断触发(如 UART 收到数据)
│
▼(硬件跳转到向量表对应项)
向量表项 = cortex_m_vector_entry(统一入口)
│
▼
int irq = os_irq_num() ← 读取当前中断号
│
├─ 若 irq == 0 → 是复位 → 重新调用 cortex_m_set_vector()
└─ 否则 → 从 reserved_ram 取 hook 并调用 hook(irq)
│
▼
hook 内部遍历 os_irq_hook 段,逐个调用各模块注册的 irq 处理为什么用 Boot 设备:这样各驱动只需用 IRQ Hook 机制注册自己的中断处理,就能被统一入口分发给到,无需每个中断单独写向量表入口。
1.5.3 选型边界表(何时不该用它)
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 非 Cortex-M 架构(如 RISC-V、Cortex-A) | 各架构自己的启动实现 | Boot 设备仅针对 ARCH_ARM_CORTEX_M |
| 不使用 OneOS 中断框架的裸机启动 | 裸机启动文件 | Boot 设备强依赖 OneOS 的 os_irq_num/Hook 机制 |
二、核心源码详解
说明:以下源码均取自
drivers/boot/cotex-m/vector_table.c,与项目源码逐字一致(含原始英文注释)。
2.1 核心辅助函数 cortex_m_set_vector —— 配置向量表
一句话定位:在内核启动前(OS_INIT_LEVEL_PRE_KERNEL_1)执行,把软向量表地址写入 VTOR,并把真实向量表地址与 Hook 地址存入 reserved_ram 保留区,同时设置中断栈信息。
完整源码(drivers/boot/cotex-m/vector_table.c,与项目一致):
static os_err_t cortex_m_set_vector(void)
{
#if defined(BSP_INCLUDE_VECTOR_TABLE) && !defined(ARCH_ARM_CORTEX_M0)
SCB->VTOR = (uint32_t)vector_table;
#endif
*(volatile uint32_t *)RESERVED_RAM_VECTOR_ADDR = (uint32_t)&CORTEX_M_VECTORS_BASE;
*(volatile uint32_t *)RESERVED_RAM_VECTOR_HOOK = (uint32_t)&cortex_m_irq_hook;
interrupt_stack_addr = &CORTEX_M_STACK_END;
interrupt_stack_size = CORTEX_M_STACK_SIZE;
#ifndef BSP_USING_COMMON_INT
SCB->VTOR = *(volatile uint32_t *)RESERVED_RAM_VECTOR_ADDR;
#endif
return OS_SUCCESS;
}
OS_INIT_CALL(cortex_m_set_vector, OS_INIT_LEVEL_PRE_KERNEL_1, OS_INIT_SUBLEVEL_HIGH);分步详解:
第 1 步:把软向量表写入 VTOR(条件 BSP_INCLUDE_VECTOR_TABLE 且非 M0)
#if defined(BSP_INCLUDE_VECTOR_TABLE) && !defined(ARCH_ARM_CORTEX_M0)
SCB->VTOR = (uint32_t)vector_table;
#endif详细解释:SCB->VTOR(Vector Table Offset Register)是 Cortex-M 用来指定向量表基址的寄存器。这里把软向量表 vector_table 的地址写入 VTOR,让 CPU 从 vector_table 取中断入口。条件是打开了 BSP_INCLUDE_VECTOR_TABLE 且不是 M0(M0 的 VTOR 行为不同)。
第 2 步:把真实硬件向量表地址存入保留区(无条件执行)
*(volatile uint32_t *)RESERVED_RAM_VECTOR_ADDR = (uint32_t)&CORTEX_M_VECTORS_BASE;详细解释:CORTEX_M_VECTORS_BASE 是链接器提供的真实硬件向量表基址(__Vectors / g_pfnVectors,见 1.3 节宏定义)。把它存进 reserved_ram 的 VECTOR_ADDR 槽位。这样 cortex_m_vector_entry(及汇编异常处理)就能通过它找到真实向量表,跳转到真正的异常处理。
第 3 步:把中断 Hook 函数地址存入保留区(无条件执行)
*(volatile uint32_t *)RESERVED_RAM_VECTOR_HOOK = (uint32_t)&cortex_m_irq_hook;详细解释:把 cortex_m_irq_hook(见 2.3 节)的地址存入保留区的 VECTOR_HOOK 槽位。统一入口收到中断后,会从这里取 Hook 地址并调用它来分发中断。
第 4 步:设置中断栈信息(无条件执行)
interrupt_stack_addr = &CORTEX_M_STACK_END;
interrupt_stack_size = CORTEX_M_STACK_SIZE;详细解释:把中断栈地址设为栈顶 CORTEX_M_STACK_END,中断栈大小设为 CORTEX_M_STACK_SIZE。这两个全局变量供中断系统在中断嵌套时切换栈使用。
第 5 步:未启用通用中断时,回退到真实向量表(条件 未定义 BSP_USING_COMMON_INT)
#ifndef BSP_USING_COMMON_INT
SCB->VTOR = *(volatile uint32_t *)RESERVED_RAM_VECTOR_ADDR;
#endif详细解释:若没有定义 BSP_USING_COMMON_INT(不使用 OneOS 通用中断框架),则把 VTOR 改回真实硬件向量表地址,让 CPU 直接用硬件向量表。这是给"不需要 OneOS 统一中断分发"的场景留的回退路径。
第 6 步:注册自动初始化(无条件执行)
return OS_SUCCESS;
}
OS_INIT_CALL(cortex_m_set_vector, OS_INIT_LEVEL_PRE_KERNEL_1, OS_INIT_SUBLEVEL_HIGH);详细解释:返回 OS_SUCCESS。OS_INIT_CALL(..., OS_INIT_LEVEL_PRE_KERNEL_1, ...) 让它在内核启动前第 1 级自动执行,保证向量表配置先于内核初始化。
cortex_m_set_vector() 条件走向总结:
cortex_m_set_vector() 条件走向总结:
├── #if (BSP_INCLUDE_VECTOR_TABLE 且 非 M0) → SCB->VTOR = vector_table
├── 存入真实向量表地址到 VECTOR_ADDR(reserved_ram)
├── 存入 cortex_m_irq_hook 地址到 VECTOR_HOOK(reserved_ram)
├── 设置 interrupt_stack_addr / interrupt_stack_size
├── #ifndef BSP_USING_COMMON_INT → SCB->VTOR = 真实硬件向量表地址(回退)
└── 返回 OS_SUCCESS(由 OS_INIT_CALL 在 PRE_KERNEL_1 阶段执行)2.2 核心辅助函数 cortex_m_vector_entry —— 软中断统一入口
一句话定位:CPU 中断触发后跳转到的统一入口,读取当前中断号,必要时重新配置向量表,然后通过 Hook 分发。
完整源码(drivers/boot/cotex-m/vector_table.c,与项目一致):
static void cortex_m_vector_entry(void)
{
vector_entry *vtable;
os_irq_hook hook;
#ifdef OS_USING_IRQ_MONITOR
cpu_usage_irq_monitor_start();
#endif
int irq = os_irq_num();
if (irq == 0)
{
irq = 1;
cortex_m_set_vector();
}
else
{
hook = (os_irq_hook) * (uint32_t *)RESERVED_RAM_VECTOR_HOOK;
hook(irq);
}
vtable = (vector_entry *)*(uint32_t *)RESERVED_RAM_VECTOR_ADDR;
vtable[irq]();
os_interrupt_stack_check();
#ifdef OS_USING_IRQ_MONITOR
cpu_usage_irq_monitor_stop();
#endif
}分步详解:
第 1 步:读取当前中断号(无条件执行)
int irq = os_irq_num();详细解释:os_irq_num() 返回当前正在处理的中断号。这是分发中断的依据。
第 2 步:判断是否为复位/异常入口(条件 irq == 0)
if (irq == 0)
{
irq = 1;
cortex_m_set_vector();
}
else
{
hook = (os_irq_hook) * (uint32_t *)RESERVED_RAM_VECTOR_HOOK;
hook(irq);
}详细解释:
irq == 0(复位):os_irq_num()返回 0 说明当前是复位流程(向量表第 1 项指向本函数)。此时把irq设为 1,并重新调用cortex_m_set_vector确保向量表已配置好。irq != 0(正常中断):从reserved_ram的VECTOR_HOOK槽位取出 Hook 函数地址并调用hook(irq),进入统一的中断分发(Hook 内部遍历os_irq_hook段逐个调用各模块处理,见 2.3 节)。
第 3 步:从真实向量表取对应处理函数并调用(无条件执行)
vtable = (vector_entry *)*(uint32_t *)RESERVED_RAM_VECTOR_ADDR;
vtable[irq]();详细解释:从 reserved_ram 的 VECTOR_ADDR 槽位取出真实硬件向量表地址,vtable[irq] 是当前中断号对应的真实处理函数(如 UART 的 ISR),调用它完成实际的中断处理。
第 4 步:栈检查与监控(无条件执行)
os_interrupt_stack_check();
#ifdef OS_USING_IRQ_MONITOR
cpu_usage_irq_monitor_stop();
#endif
}详细解释:os_interrupt_stack_check() 检查中断栈是否溢出。若开启 OS_USING_IRQ_MONITOR,则在中断开始时 cpu_usage_irq_monitor_start()、结束时 cpu_usage_irq_monitor_stop() 统计 CPU 中断占用率。
cortex_m_vector_entry() 条件走向总结:
cortex_m_vector_entry() 条件走向总结:
├── int irq = os_irq_num()
├── irq == 0(复位)
│ ├── irq = 1
│ └── cortex_m_set_vector()(重新配置向量表)
└── irq != 0(正常中断)
└── hook = reserved_ram VECTOR_HOOK;hook(irq)(统一分发)
├── vtable = reserved_ram VECTOR_ADDR;vtable[irq]()(调用真实处理函数)
├── os_interrupt_stack_check()
└── (OS_USING_IRQ_MONITOR)cpu_usage_irq_monitor_stop()2.3 核心辅助函数 cortex_m_irq_hook —— 遍历中断 Hook
一句话定位:被统一入口调用,遍历链接器 os_irq_hook 段中所有已注册的 IRQ Hook,逐个执行,实现"多模块各自挂接中断处理"。
完整源码(drivers/boot/cotex-m/vector_table.c,与项目一致):
static void cortex_m_irq_hook(int irq)
{
#if defined(__CC_ARM) || defined(__CLANG_ARM)
extern const int os_irq_hook$$Base;
extern const int os_irq_hook$$Limit;
const os_irq_hook *hook = (os_irq_hook *)&os_irq_hook$$Base;
int nr = (os_irq_hook *)&os_irq_hook$$Limit - hook;
#elif defined(__ICCARM__) || defined(__ICCRX__) /* for IAR Compiler */
const os_irq_hook *hook = (os_irq_hook *)__section_begin("os_irq_hook");
int nr = (os_irq_hook *)__section_end("os_irq_hook") - hook;
#elif defined(__GNUC__) /* for GCC Compiler */
extern const int __os_irq_hook_start;
extern const int __os_irq_hook_end;
const os_irq_hook *hook = (os_irq_hook *)&__os_irq_hook_start;
int nr = (os_irq_hook *)&__os_irq_hook_end - hook;
#endif
int i;
for (i = 0; i < nr; i++)
{
hook[i](irq);
}
}分步详解:
第 1 步:按编译器获取 Hook 段起止(条件 编译器类型)
#if defined(__CC_ARM) || defined(__CLANG_ARM)
extern const int os_irq_hook$$Base;
extern const int os_irq_hook$$Limit;
const os_irq_hook *hook = (os_irq_hook *)&os_irq_hook$$Base;
int nr = (os_irq_hook *)&os_irq_hook$$Limit - hook;
#elif defined(__ICCARM__) || defined(__ICCRX__) /* for IAR Compiler */
const os_irq_hook *hook = (os_irq_hook *)__section_begin("os_irq_hook");
int nr = (os_irq_hook *)__section_end("os_irq_hook") - hook;
#elif defined(__GNUC__) /* for GCC Compiler */
extern const int __os_irq_hook_start;
extern const int __os_irq_hook_end;
const os_irq_hook *hook = (os_irq_hook *)&__os_irq_hook_start;
int nr = (os_irq_hook *)&__os_irq_hook_end - hook;
#endif详细解释:不同的工具链(Keil __CC_ARM、IAR __ICCARM__、GCC __GNUC__)用不同的方式获取链接器 os_irq_hook 段的起始与结束地址。它们计算出 hook(段起始)和 nr(段内 Hook 数量)。这是把"分散在各源文件通过 OS_IRQ_HOOK 注册的 Hook"聚合起来遍历的基础。
第 2 步:逐个调用 Hook(无条件执行)
int i;
for (i = 0; i < nr; i++)
{
hook[i](irq);
}
}详细解释:遍历 nr 个 Hook,每个都传入当前中断号 irq 并调用。这样,所有通过 IRQ Hook 机制注册的模块(如定时器、串口驱动)都能在当前中断里收到通知。
cortex_m_irq_hook() 条件走向总结:
cortex_m_irq_hook() 条件走向总结:
├── 按编译器分支(__CC_ARM/__CLANG_ARM / __ICCARM__ / __GNUC__)计算 hook 起始与数量 nr
└── for i in [0, nr):hook[i](irq) ← 逐个调用注册的 Hook三、Kconfig 启动配置
Boot 设备的启动选项由 drivers/boot/cotex-m/Kconfig 配置:
来源:drivers/boot/cotex-m/Kconfig
menu "CORTEX-M Boot"
if ARCH_ARM_CORTEX_M
config BSP_INCLUDE_VECTOR_TABLE
bool "include vector table"
default y
config BSP_VECTOR_TABLE_INCLUDE_EXTERNAL_INT
depends on BSP_INCLUDE_VECTOR_TABLE
bool "vector table include external interrupts"
default y
config BSP_USING_COMMON_INT
bool "using common interrupts"
default y
menuconfig BSP_BOOT_OPTION
bool "Board boot configure"
default n
if BSP_BOOT_OPTION
config BSP_TEXT_SECTION_ADDR
hex "text section addr"
default 0x08000000
config BSP_TEXT_SECTION_SIZE
hex "text section size"
default 0x00080000
config BSP_DATA_SECTION_ADDR
hex "data section addr"
default 0x20000000
config BSP_DATA_SECTION_SIZE
hex "data section size"
default 0x00008000
endif
endif
endmenu配置项说明:
| 配置项 | 默认值 | 说明 |
|---|---|---|
BSP_INCLUDE_VECTOR_TABLE | y | 是否包含 OneOS 软中断向量表(关闭则用硬件向量表) |
BSP_VECTOR_TABLE_INCLUDE_EXTERNAL_INT | y | 向量表是否包含外部中断项(依赖前者) |
BSP_USING_COMMON_INT | y | 是否使用 OneOS 通用中断框架 |
BSP_BOOT_OPTION | n | 是否启用板级启动配置(展开后配置地址/大小) |
BSP_TEXT_SECTION_ADDR | 0x08000000 | 代码段(Flash)起始地址 |
BSP_TEXT_SECTION_SIZE | 0x00080000 | 代码段大小(512KB) |
BSP_DATA_SECTION_ADDR | 0x20000000 | 数据段(RAM)起始地址 |
BSP_DATA_SECTION_SIZE | 0x00008000 | 数据段大小(32KB) |
关键理解:
BSP_INCLUDE_VECTOR_TABLE对应源码里的#ifdef BSP_INCLUDE_VECTOR_TABLE(见 2.1 节第 1 步):打开时用 OneOS 软向量表并把 VTOR 指向它;关闭时用硬件向量表。BSP_USING_COMMON_INT对应源码第 5 步的#ifndef BSP_USING_COMMON_INT:打开时用统一入口分发;关闭时 VTOR 回退到真实硬件向量表。BSP_TEXT_SECTION_ADDR等是"板级启动配置"展开的选项,用于告诉链接器代码段/数据段放在内存哪里,配合链接脚本使用。
数值演算(默认内存布局,典型 STM32F4):
| 段 | 地址 | 大小 | 范围 |
|---|---|---|---|
| 代码段(Flash) | 0x08000000 | 0x00080000(512KB) | 0x08000000 ~ 0x0807FFFF |
| 数据段(RAM) | 0x20000000 | 0x00008000(32KB) | 0x20000000 ~ 0x20007FFF |
四、完整启动流程
从芯片上电到系统运行的完整流程:
上电/复位
│
▼(CPU 按硬件约定读向量表前两项)
向量表[0] = 初始栈指针 → SP = 栈顶
向量表[1] = cortex_m_vector_entry → 跳转执行复位入口
│
▼
cortex_m_vector_entry()
│ irq = os_irq_num() == 0(复位)
├─ irq = 1
└─ cortex_m_set_vector() ← OS_INIT_LEVEL_PRE_KERNEL_1
├─ SCB->VTOR = vector_table (软向量表生效)
├─ VECTOR_ADDR = 真实硬件向量表 (存入 reserved_ram)
├─ VECTOR_HOOK = cortex_m_irq_hook (存入 reserved_ram)
└─ 设置中断栈地址/大小
│
▼
系统初始化(Stack、Heap、BSS 清零)
│
▼
内核初始化(os_kernel_init)
│
▼
启动调度器(os_kernel_start)→ 系统运行中断触发时的统一分发流程:
外设中断触发(如 UART 收到数据)
│
▼(硬件从向量表取入口)
vector_table[IRQ] = cortex_m_vector_entry
│
▼
cortex_m_vector_entry()
│ irq = os_irq_num()(非 0)
├─ hook = reserved_ram VECTOR_HOOK;hook(irq) ← 分发到各模块 Hook
│ └─ 遍历 os_irq_hook 段,逐个调用 hook[i](irq)
├─ vtable = reserved_ram VECTOR_ADDR;vtable[irq]() ← 调用真实 ISR
└─ os_interrupt_stack_check()五、Boot 的适配(接入新 Cortex-M 芯片)
5.1 先认识适配层:Boot 设备怎么"长"到新芯片上
drivers/boot/cotex-m/ 的 Boot 设备已适配好整个 Cortex-M 架构(向量表、VTOR、Hook、中断栈),接入新 Cortex-M 芯片几乎不用改代码。真正要做的是板级配置:确认链接脚本提供正确的 CORTEX_M_VECTORS_BASE/CORTEX_M_STACK_END/CORTEX_M_STACK_SIZE(或宏定义),以及 Kconfig 启动选项。
适配全景图:
┌──────────────────────────────────────────────────────────┐
│ 芯片启动(cortex_m_set_vector) │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ Boot 设备(drivers/boot/cotex-m/,Cortex-M 通用不改) │
│ vector_table.c(向量表 + VTOR + Hook + 中断栈) │
└───────────────┬──────────────────────────┬────────────────┘
│ ①提供链接器符号/宏 │ ②Kconfig 选项
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 板级适配(链接脚本 + board.h)★需配置★ │
│ CORTEX_M_VECTORS_BASE/STACK_END/STACK_SIZE 符号或宏 │
│ BSP_INCLUDE_VECTOR_TABLE / COMMON_INT 等 Kconfig │
└───────────────┬──────────────────────────┬────────────────┘
│ 链接脚本(vtor_table/reserved_ram 段) │
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 硬件(新 Cortex-M 芯片 + 内存布局) │
└──────────────────────────────────────────────────────────┘适配的关键洞察:接入新 Cortex-M 芯片只需确认三件事:① 链接脚本提供 __Vectors/g_pfnVectors(真实向量表)、栈顶/栈底符号(供 CORTEX_M_* 宏);② 链接脚本为 vtor_table/reserved_ram 段保留空间;③ Kconfig 正确选择向量表/中断模式。vector_table.c 代码不用改。
5.2 接口契约:接入新芯片需要提供什么
| 层 | 内容 | 职责 | 说明 |
|---|---|---|---|
| 链接符号 | CORTEX_M_VECTORS_BASE | 真实硬件向量表基址 | 编译器用 __Vectors(MDK)/g_pfnVectors(GCC) |
| 链接符号 | CORTEX_M_STACK_END/STACK_SIZE | 系统栈顶/大小 | MDK STACK$$Limit、GCC _estack/_sstack |
| 链接段 | vtor_table | 软向量表存放段 | 链接脚本须保留 |
| 链接段 | reserved_ram | rsv_ram_start 存放段(中断重定向锚点) | 链接脚本须保留 |
| Kconfig | BSP_INCLUDE_VECTOR_TABLE 等 | 启动模式选择 | 板级菜单配置 |
5.3 接入新 Cortex-M 芯片的标准流程
第 1 步:确认启动文件与链接脚本——确认芯片启动汇编(.s)定义了 __Vectors/g_pfnVectors(或 vector_table.c 会 #error/用硬件向量表),链接脚本含 vtor_table/reserved_ram 段。
第 2 步:确认栈符号——确认 STACK$$Limit/STACK$$Base(MDK)或 _estack/_sstack(GCC)在链接脚本定义,CORTEX_M_STACK_* 宏能取到正确值。
第 3 步:配置 Kconfig——在板级菜单开 BSP_INCLUDE_VECTOR_TABLE(用软向量表)、BSP_USING_COMMON_INT(统一中断)、按需 BSP_BOOT_OPTION(代码/数据段布局)。
第 4 步:测试——上电看系统能否启动;触发一个中断(如 SysTick/串口)看 cortex_m_vector_entry 是否正确分发。
5.4 适配检查清单(快速自查)
| 检查项 | 说明 |
|---|---|
链接脚本有 vtor_table/reserved_ram 段? | 无段则 OS_SECTION 不生效、rsv_ram_start 找不到 |
| 栈符号定义对? | CORTEX_M_STACK_END/SIZE 取错则中断栈溢出或栈指针错 |
| 启动汇编符号对? | __Vectors/g_pfnVectors 未定义则 cortex_m_set_vector 取错向量表 |
| M0 的 VTOR 限制? | M0 无 ARCH_ARM_CORTEX_M0 时 #if 分支不同,VTOR 行为需确认 |
| 外设中断都能进统一入口? | BSP_VECTOR_TABLE_INCLUDE_EXTERNAL_INT 关则外设中断不进统一分发 |
📝 本节小结
核心要点回顾
本节围绕 Boot 启动设备展开,核心要点如下:
1. 向量表的作用
- Cortex-M 上电从向量表取初始 SP 和复位入口执行。
- 前两项(栈顶 + 复位入口)是硬件约定,外设中断项在 OneOS 中几乎全部指向统一入口
cortex_m_vector_entry。
2. reserved_ram 保留区
VECTOR_ADDR(&rsv_ram_start + 1)存真实硬件向量表地址。VECTOR_HOOK(&rsv_ram_start + 2)存cortex_m_irq_hook地址。- 统一入口通过这些槽位完成中断重定向。
3. 三个核心函数
| 函数 | 作用 |
|---|---|
cortex_m_set_vector | 配置 VTOR、存真实向量表/Hook 地址、设中断栈,PRE_KERNEL_1 阶段执行 |
cortex_m_vector_entry | 软中断统一入口,读中断号、必要时重配向量表、分发中断 |
cortex_m_irq_hook | 遍历 os_irq_hook 段所有 Hook,逐个调用 |
4. Kconfig 配置
BSP_INCLUDE_VECTOR_TABLE:是否用软向量表。BSP_USING_COMMON_INT:是否用统一中断框架。BSP_BOOT_OPTION:展开代码段/数据段地址大小配置。
5. 硬件适配
- 接入新 Cortex-M 芯片 = 确认链接脚本提供
CORTEX_M_VECTORS_BASE/STACK_END/SIZE+vtor_table/reserved_ram段 + 配 Kconfig,vector_table.c不用改。 - M0 架构 VTOR 行为不同(
#if分支不同),需单独确认。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
| 混淆软向量表与硬件向量表 | 软向量表是 OneOS 的 vector_table(外设中断统一入口);硬件向量表是链接器的 __Vectors/g_pfnVectors,其地址存在 reserved_ram |
忘记 BSP_INCLUDE_VECTOR_TABLE 的影响 | 关闭它则不用软向量表、VTOR 不指向 vector_table,相应 ___xxx_Handler 汇编入口也不会编译 |
忽略 reserved_ram 段 | VECTOR_ADDR/VECTOR_HOOK 依赖 reserved_ram 段,链接脚本必须为该段保留空间 |
混淆 BSP_USING_COMMON_INT | 打开时用统一入口分发;关闭时 VTOR 回退到真实硬件向量表(#ifndef 分支) |
| 把 Boot 配置当运行时常量 | BSP_TEXT_SECTION_ADDR 等是编译期 Kconfig 配置,配合链接脚本决定段布局,改它需重新编译并同步链接脚本 |
链接脚本缺 vtor_table/reserved_ram 段 | 无段则 OS_SECTION 不生效,rsv_ram_start 找不到、中断重定向失效 |
| M0 架构误用 VTOR | M0 的 #if !defined(ARCH_ARM_CORTEX_M0) 分支不同,VTOR 写入行为需按架构确认 |
下一步
掌握了 Boot 启动设备后,接下来学习 Graphic 显示设备:
- 5.34 Graphic设备:了解 OneOS Graphic(LCD 显示)设备的 framebuffer 双缓冲、绘制函数库与刷屏机制。