vector_table符号详解
vector_table符号详解
📚 本节导读
用途: 讲解vector_table.c中与向量表、中断栈相关的关键符号
来源: drivers/boot/cotex-m/vector_table.c
一、概述
1.1 概念定义
这些符号都定义在 drivers/boot/cotex-m/vector_table.c 文件中,它们主要用于:
- 向量表的定位
- 中断栈的配置
二、符号详解
2.1 CORTEX_M_VECTORS_BASE
完整定义
#if defined(__CC_ARM) || defined(__CLANG_ARM)
#define CORTEX_M_VECTORS_BASE __Vectors
#elif defined(__GNUC__)
#define CORTEX_M_VECTORS_BASE g_pfnVectors
#endif说明
| 编译器 | 映射符号 | 说明 |
|---|---|---|
| ARMCC/Clang | __Vectors | ARM编译器链接脚本导出的向量表符号 |
| GCC | g_pfnVectors | GCC编译器链接脚本导出的向量表符号 |
这个符号指向向量表的起始地址,告诉CPU从哪里读取中断向量。
2.2 CORTEX_M_STACK_END
完整定义
#if defined(__CC_ARM) || defined(__CLANG_ARM)
#define CORTEX_M_STACK_END STACK$$Limit
#elif defined(__GNUC__)
#define CORTEX_M_STACK_END _estack
#endif说明
| 编译器 | 映射符号 | 说明 |
|---|---|---|
| ARMCC/Clang | STACK$$Limit | ARM编译器链接脚本导出的栈顶符号 |
| GCC | _estack | GCC编译器链接脚本导出的栈顶符号 |
这个符号指向中断栈的结束地址(栈顶),当中断发生时,CPU 会自动从这个地址开始向下压入寄存器。
2.3 CORTEX_M_STACK_SIZE
完整定义
#if defined(__CC_ARM) || defined(__CLANG_ARM)
#define CORTEX_M_STACK_SIZE ((unsigned long)&STACK$$Limit - (unsigned long)&STACK$$Base)
#elif defined(__GNUC__)
#define CORTEX_M_STACK_SIZE ((unsigned long)&_estack - (unsigned long)&_sstack)
#endif说明
| 编译器 | 计算方式 | 说明 |
|---|---|---|
| ARMCC/Clang | &STACK$$Limit - &STACK$$Base | 栈顶减去栈底 |
| GCC | &_estack - &_sstack | 栈顶减去栈底 |
这个宏计算中断栈的总大小,用于栈溢出检查和栈使用量统计。
三、编译器兼容性
3.1 符号对照表
| 符号 | ARMCC/Clang | GCC |
|---|---|---|
| 向量表 | __Vectors | g_pfnVectors |
| 栈顶 | STACK$$Limit | _estack |
| 栈底 | STACK$$Base | _sstack |
3.2 外部声明
// ARMCC/Clang
extern int __Vectors;
extern int STACK$$Limit;
extern int STACK$$Base;
// GCC
extern int g_pfnVectors;
extern int _estack;
extern int _sstack;这些是链接器导出的符号,在链接脚本中定义,C代码中通过 extern 声明来引用。
四、完整定义位置
4.1 vector_table.c中的完整定义
#if !defined(CORTEX_M_VECTORS_BASE) && !defined(CORTEX_M_STACK_END)
#if defined(__CC_ARM) || defined(__CLANG_ARM)
extern int __Vectors;
extern int STACK$$Limit;
extern int STACK$$Base;
#define CORTEX_M_VECTORS_BASE __Vectors
#define CORTEX_M_STACK_END STACK$$Limit
#define CORTEX_M_STACK_SIZE ((unsigned long)&STACK$$Limit - (unsigned long)&STACK$$Base)
#elif defined(__GNUC__)
extern int g_pfnVectors;
extern int _sstack;
extern int _estack;
#define CORTEX_M_VECTORS_BASE g_pfnVectors
#define CORTEX_M_STACK_END _estack
#define CORTEX_M_STACK_SIZE ((unsigned long)&_estack - (unsigned long)&_sstack)
#endif
#endif五、设计原理
5.1 为什么使用宏来包装?
- 编译器兼容性:同一套代码可以在不同编译器下工作
- 抽象层:隐藏编译器相关的细节
- 可维护性:如果需要添加新的编译器支持,只需修改这个文件
5.2 链接器的作用
这些符号最终来自链接脚本,链接器在链接时会:
- 为向量表分配地址
- 为栈分配空间
- 导出这些符号供C代码引用
六、reserved_ram 相关符号详解
6.1 rsv_ram_start
完整定义
int OS_USED OS_SECTION("reserved_ram") rsv_ram_start = 0;说明
| 属性 | 值 |
|---|---|
| 存储类 | int |
| 段属性 | OS_SECTION("reserved_ram") |
| 防止优化 | OS_USED |
| 初始值 | 0 |
这个变量在 RAM 中预留一个专门的区域,用于保存向量表相关信息。虽然初始值为 0,但实际会被后续代码覆盖。
6.2 RESERVED_RAM_VECTOR_ADDR
完整定义
#define RESERVED_RAM_VECTOR_ADDR (&rsv_ram_start + 1)说明
| 属性 | 值 |
|---|---|
| 类型 | 宏定义 |
| 指向地址 | &rsv_ram_start + 1 |
| 偏移 | 相对于 rsv_ram_start 偏移 4 字节 |
这个宏指向 reserved_ram 段中用于保存向量表基址的位置,在 cortex_m_set_vector() 中用于保存 &CORTEX_M_VECTORS_BASE。
6.3 RESERVED_RAM_VECTOR_HOOK
完整定义
#define RESERVED_RAM_VECTOR_HOOK (&rsv_ram_start + 2)说明
| 属性 | 值 |
|---|---|
| 类型 | 宏定义 |
| 指向地址 | &rsv_ram_start + 2 |
| 偏移 | 相对于 rsv_ram_start 偏移 8 字节 |
这个宏指向 reserved_ram 段中用于保存中断钩子函数地址的位置,在 cortex_m_set_vector() 中用于保存 &cortex_m_irq_hook。
6.4 reserved_ram 段的完整内存布局
SRAM 中 reserved_ram 段的布局:
┌─────────────────────────┐
│ rsv_ram_start (4字节) │ ← 起始标记
├─────────────────────────┤
│ RESERVED_RAM_VECTOR_ADDR│ ← 保存向量表基址 (4字节)
│ (&rsv_ram_start + 1) │
├─────────────────────────┤
│ RESERVED_RAM_VECTOR_HOOK│ ← 保存钩子函数地址 (4字节)
│ (&rsv_ram_start + 2) │
├─────────────────────────┤
│ (可能还有其他保留数据) │
└─────────────────────────┘
总计:至少 12 字节的保留空间6.5 为什么需要 reserved_ram 段?
- 动态修改支持:Flash 中的数据不可修改,但 RAM 可以随时修改
- 中断钩子机制:允许运行时动态更换中断钩子函数
- 引导加载程序兼容性:支持 Bootloader 更换向量表
- 汇编代码访问:汇编异常处理函数(如 NMI、HardFault)需要从固定位置读取原始向量表地址
七、中断栈相关符号详解
7.1 interrupt_stack_addr
完整定义
void *interrupt_stack_addr = OS_NULL;说明
| 属性 | 值 |
|---|---|
| 类型 | void * |
| 初始值 | OS_NULL |
| 作用域 | 全局 |
| 定义位置 | drivers/driver.c |
这个全局变量保存中断栈的结束地址(栈顶),在 cortex_m_set_vector() 中设置为 &CORTEX_M_STACK_END,供 os_interrupt_stack_check() 等函数使用,进行栈溢出检查。
7.2 interrupt_stack_size
完整定义
uint32_t interrupt_stack_size = 0;说明
| 属性 | 值 |
|---|---|
| 类型 | uint32_t |
| 初始值 | 0 |
| 作用域 | 全局 |
| 定义位置 | drivers/driver.c |
这个全局变量保存中断栈的总大小,在 cortex_m_set_vector() 中设置为 CORTEX_M_STACK_SIZE,供 os_interrupt_stack_check() 等函数使用,进行栈使用量统计。
八、vector_table 完整定义详解
8.1 vector_table 完整条目详解
vector_table 是一个完整的 Cortex-M 中断向量表,包含所有系统异常和外部中断的处理函数指针。
1. 系统堆栈指针
(vector_entry)(&CORTEX_M_STACK_END), /* system stack */- 作用:这是向量表的第一个条目,不是函数指针,而是系统堆栈的初始栈顶地址
- Cortex-M 规范:复位后,CPU 会自动从向量表第 0 个位置加载栈指针
- CORTEX_M_STACK_END 来源:
- ARMCC/Clang:来自链接器导出的
STACK$$Limit - GCC:来自链接器导出的
_estack - STM32F103ZET6 项目:值为
0x20010000(64KB SRAM 的末尾)
- ARMCC/Clang:来自链接器导出的
2. 复位向量
cortex_m_vector_entry, /* reset */- 作用:复位后的程序入口点
- 为什么不用直接的 Reset_Handler:OneOS 使用动态向量表机制,通过这种方式支持中断钩子和运行时修改
3. 系统异常向量
(vector_entry)(&___NMI_Handler), // NMI (不可屏蔽中断)
(vector_entry)(&___HardFault_Handler), // HardFault (硬 fault)
(vector_entry)(&___MemManage_Handler), // MemManage (内存管理 fault)
(vector_entry)(&___BusFault_Handler), // BusFault (总线 fault)
(vector_entry)(&___UsageFault_Handler), // UsageFault (用法 fault)
cortex_m_vector_entry, // SVCall (系统调用)
0, 0, 0, // 保留
cortex_m_vector_entry, // DebugMonitor (调试监视器)
cortex_m_vector_entry, // PendSV (可挂起的系统调用)
0, // 保留
(vector_entry)(&___PendSV_Handler), // PendSV (OneOS 特殊处理)
cortex_m_vector_entry, // SysTick (系统定时器)关键异常处理说明:
- NMI、HardFault、MemManage、BusFault、UsageFault:
- 这些是用汇编实现的特殊处理函数
- 它们从 RAM 保留区域读取原始向量表地址
- 然后跳转到真正的处理函数
- 这样设计是为了在 fault 发生时能够可靠地执行
- PendSV:
- 特殊处理,用于任务上下文切换
- 同样通过汇编实现,跳转到原始处理函数
- SVCall、DebugMonitor、SysTick:
- 使用通用的 cortex_m_vector_entry 处理
4. 外部中断向量
#ifdef BSP_VECTOR_TABLE_INCLUDE_EXTERNAL_INT
cortex_m_vector_entry, // IRQ0: WWDG
cortex_m_vector_entry, // IRQ1: PVD
cortex_m_vector_entry, // IRQ2: TAMPER
// ... 更多外部中断
#endif- 数量:包含所有 STM32F103 的 60 个外部中断
- 处理方式:所有外部中断都使用 cortex_m_vector_entry 统一处理
- 扩展支持:对于非 Cortex-M0/M0+ 的芯片,还包含额外的中断条目
8.2 vector_table 完整定义详解
让我们详细分析这一行代码:
static const vector_entry OS_USED OS_SECTION("vtor_table") vector_table[] = {1. static const vector_entry
static:静态存储类,限制变量作用域仅在当前文件const:常量修饰符,表示数组内容不可修改vector_entry:中断处理函数类型
typedef void (*vector_entry)(void);2. OS_USED 宏
宏定义(在 kernel/include/os_stddef.h):
/* ARMCC/Clang 编译器 */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
#define OS_USED __attribute__((used))
/* IAR 编译器 */
#elif defined(__IAR_SYSTEMS_ICC__)
#define OS_USED __root
/* GCC 编译器 */
#elif defined(__GNUC__)
#define OS_USED __attribute__((used))
#endifSTM32F103ZET6 实际配置
- 编译器:ARMCC(Keil uVision)
__CC_ARM或__CLANG_ARM:已定义(编译器自动预定义)__IAR_SYSTEMS_ICC__:未定义__GNUC__:未定义
结论:使用 ARMCC/Clang 编译器分支,OS_USED 展开为 __attribute__((used))。
作用:
- 告诉编译器/链接器该符号是被使用的
- 防止被链接器的垃圾收集机制优化掉
- 确保向量表始终被包含在最终的可执行文件中
3. OS_SECTION("vtor_table") 宏
宏定义(在 kernel/include/os_stddef.h):
/* ARMCC/Clang 编译器 */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
#define OS_SECTION(x) __attribute__((section(x)))
/* IAR 编译器 */
#elif defined(__IAR_SYSTEMS_ICC__)
#define OS_SECTION(x) @x
/* GCC 编译器 */
#elif defined(__GNUC__)
#define OS_SECTION(x) __attribute__((section(x)))
#endifSTM32F103ZET6 实际配置
- 编译器:ARMCC(Keil uVision)
__CC_ARM或__CLANG_ARM:已定义(编译器自动预定义)__IAR_SYSTEMS_ICC__:未定义__GNUC__:未定义
结论:使用 ARMCC/Clang 编译器分支,OS_SECTION("vtor_table") 展开为 __attribute__((section("vtor_table")))。
作用:
- 将变量放置在指定的段中(这里是 "vtor_table")
- 允许链接器脚本精确控制该段的位置
4. 链接器脚本中的 "vtor_table" 段
定义(在 projects/stm32f103zet6-atk-elite/board/linker_scripts/link.lds):
.text :
{
. = ALIGN(4);
_stext = .;
KEEP(*(vtor_table)) /* Startup code */
. = ALIGN(0x400);
KEEP(*(.isr_vector))关键点说明:
KEEP(*(vtor_table)):强制保留 vtor_table 段,防止被优化- 放置在
.text段的最开始位置 _stext = .:标记代码段起始地址ALIGN(4):4 字节对齐ALIGN(0x400):向量表需要 1024 字节对齐(VTOR 寄存器要求)
5. STM32F103ZET6 项目的实际配置
根据项目配置:
#define ARCH_ARM
#define ARCH_ARM_CORTEX_M
#define ARCH_ARM_CORTEX_M3根据项目中的链接器脚本(在 projects/stm32f103zet6-atk-elite/board/linker_scripts/link.sct),STM32F103ZET6 项目使用的是 ARMCC 编译器(Keil uVision),因此实际展开为:
static const vector_entry
__attribute__((used))
__attribute__((section("vtor_table")))
vector_table[] = {链接器脚本配置(在 projects/stm32f103zet6-atk-elite/board/linker_scripts/link.sct):
* (vtor_table, +First)关键点说明:
+First:强制将 vtor_table 段放在执行区域的最开始位置- 确保复位后 CPU 能立即找到向量表
内存布局:
Flash 区域 (0x08000000)
┌─────────────────────────┐ 0x08000000
│ vtor_table (vector_table) │ ← 向量表放在这里
├─────────────────────────┤
│ InRoot$$Sections │ (C库初始化代码)
├─────────────────────────┤
│ .text │ (程序代码)
│ ... │
└─────────────────────────┘九、🔍 STM32F103ZET6 项目实际配置分析
根据 oneos_config.h 和 vector_table.c,STM32F103ZET6 项目的向量表配置如下:
9.1 配置宏定义
#define ARCH_ARM_CORTEX_M3 // 使用 Cortex-M3 内核
#define BSP_INCLUDE_VECTOR_TABLE // 包含完整向量表
#define BSP_VECTOR_TABLE_INCLUDE_EXTERNAL_INT // 包含外部中断
#define BSP_USING_COMMON_INT // 使用通用中断处理9.2 实际向量表条目数量
- 系统异常:16 个(从 Reset 到 SysTick)
- 外部中断:60 个(STM32F103 特有)
- 总计:76 个条目
- 地址对齐:向量表被放置在 Flash 开头,1024 字节对齐(VTOR 要求)
9.3 内存布局
Flash (0x08000000)
├── vector_table (vtor_table段) ← 向量表从这里开始
├── C库初始化代码
└── 程序代码
SRAM (0x20000000)
├── reserved_ram (32字节) ← 保存原始向量表地址和钩子
└── 常规变量和堆栈十、🎯 动态向量表机制
OneOS 的向量表设计支持动态修改,工作原理如下:
10.1 基本机制
- 原始向量表:vector_table 放在 Flash 中,包含 cortex_m_vector_entry 等处理函数
- RAM 备份:在 cortex_m_set_vector 中,将原始向量表地址保存到 RAM 保留区域
- VTOR 配置:设置 SCB->VTOR 指向 Flash 中的 vector_table(因为 BSP_USING_COMMON_INT 已定义)
- 中断处理流程:
- 中断发生 → CPU 从 vector_table 读取 cortex_m_vector_entry
- cortex_m_vector_entry 获取中断号
- 调用中断钩子(如果有)
- 从 RAM 中保存的原始向量表地址读取真正的处理函数
- 调用真正的处理函数
10.2 设计优势
- 支持运行时动态修改中断处理函数
- 支持中断钩子机制
- 兼容不同的 Cortex-M 内核
- 保持向后兼容性
10.3 理解两个向量表
1️⃣ 启动文件中的原始向量表(__Vectors)
- 位置:
projects/stm32f103zet6-atk-elite/board/startup/startup_stm32f103xe_arm.s - 内容:
__Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler ... DCD USART1_IRQHandler ; USART1 中断处理函数 DCD USART2_IRQHandler ; USART2 中断处理函数 ... - 特点:这是真正的中断处理函数表,由 ST 的 HAL 库或用户实现
2️⃣ OneOS 的包装向量表(vector_table)
- 位置:
drivers/boot/cotex-m/vector_table.c - 内容:
static const vector_entry vector_table[] = { (vector_entry)(&CORTEX_M_STACK_END), /* system stack */ cortex_m_vector_entry, /* reset */ (vector_entry)(&___NMI_Handler), /* NMI */ (vector_entry)(&___HardFault_Handler), /* HardFault */ ... cortex_m_vector_entry, /* USART1 - 全部指向同一个包装函数 */ cortex_m_vector_entry, /* USART2 - 全部指向同一个包装函数 */ ... }; - 特点:大部分条目都是
cortex_m_vector_entry,这是一个包装函数
10.4 完整的中断处理流程
中断发生(如 UART1)
↓
CPU 从 VTOR 寄存器读取向量表地址
↓
CPU 跳转到 vector_table[irq] (即 cortex_m_vector_entry)
↓
cortex_m_vector_entry() 开始执行
↓
┌─────────────────────────────────────┐
│ 1. 调用所有注册的钩子 │ ← 这是可选的扩展功能
│ hook(irq); │ (如电源管理、性能统计)
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. 从 RAM 读取原始向量表地址 │
│ vtable = (vector_entry *)*RESERVED_RAM_VECTOR_ADDR;
│ (vtable 指向 __Vectors) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. 调用真正的中断处理函数 │ ← 这才是真正处理硬件的代码
│ vtable[irq](); │ (如 USART1_IRQHandler)
└─────────────────────────────────────┘
↓
中断返回10.5 为什么这样设计?
1. 统一入口管理
- 所有中断都经过
cortex_m_vector_entry(),便于统一管理 - 可以在统一的地方添加钩子、栈检查、性能监控等功能
2. 兼容性
- 保留了原始向量表
__Vectors,可以直接使用 ST 的 HAL 库 - 无需修改现有的中断处理函数
3. 灵活性
CORTEX_M_VECTORS_BASE指向原始向量表(__Vectors)- 如果需要,可以动态更换为其他向量表
- 支持引导加载程序等高级场景
10.6 实际例子(UART1 中断)
原始向量表中的条目:
DCD USART1_IRQHandler ; 真正的 UART1 中断处理函数OneOS 向量表中的条目:
cortex_m_vector_entry, // UART1 位置,指向包装函数执行过程:
- UART1 产生中断
- CPU 调用
cortex_m_vector_entry() cortex_m_vector_entry()调用os_lpmgr_irq_entry(37)(更新电源管理状态)cortex_m_vector_entry()从__Vectors[37]读取USART1_IRQHandler地址- 调用
USART1_IRQHandler()(真正处理 UART 数据收发) - 中断返回