{"code":1,"msg":"成功","time":"1793358332","data":{"title":"rtthread启动链路分析","content":"# 从 Reset_Handler 到 main：RT-Thread 在 STM32F103 上是怎么启动的\r\n\r\n> 环境：STM32F103C8T6 + RT-Thread 4.0.3 + RT-Thread Studio 2.3.1（GCC）\r\n> 文中代码均取自本工程实际构建，不是照抄手册。\r\n\r\n## 目录\r\n\r\n- [Step 1 汇编阶段：Reset_Handler](#step1)\r\n  - [① 把 .data 从 Flash 搬到 SRAM](#step1-1)\r\n  - [② 清零 .bss](#step1-2)\r\n  - [③ SystemInit：复位时钟 + 设中断向量表](#step1-3)\r\n  - [④ 进入 C 世界：跳的是 entry，不是 main](#step1-4)\r\n  - [⑤ 中断函数的弱定义](#step1-5)\r\n- [Step 2 进入 RT-Thread：rtthread_startup()](#step2)\r\n- [Step 3 调度器启动后：谁先跑？](#step3)\r\n- [总结：四个容易记错的点](#summary)\r\n- [怎么自己验证](#verify)\r\n\r\n---\r\n\r\n很多人以为上电后就直接跑 `main` 了。其实在 RT-Thread 里，`main` 只是「main 线程」里的一个普通函数，它前面隔着两层：一层汇编、一层 C 初始化，最后还要等调度器启动。\r\n\r\n把整条链拆成三段：\r\n\r\n1. **Step 1 汇编阶段**：`Reset_Handler` 准备 C 运行环境 → 调 `SystemInit` → 跳 `entry`\r\n2. **Step 2 C 初始化**：`entry → rtthread_startup()`，初始化内核、建线程\r\n3. **Step 3 调度器启动**：`rt_system_scheduler_start()` 挑最高优先级线程先跑；`main` 在 main 线程里最后出场\r\n\r\n一句话版本：\r\n\r\n```\r\nReset_Handler\r\n  → 搬 .data \/ 清 .bss\r\n  → SystemInit（复位时钟、设中断向量表）\r\n  → entry()\r\n  → rtthread_startup()\r\n      → rt_hw_board_init()          ← 72MHz 时钟和堆都在这里配\r\n      → 定时器 \/ 调度器 \/ main 线程 \/ timer 线程 \/ idle 线程\r\n  → rt_system_scheduler_start()\r\n  → 优先级最高的就绪线程（默认是 timer 线程）\r\n  → main_thread_entry() → rt_components_init() → main()\r\n```\r\n\r\n---\r\n\r\n<a id=\"step1\"><\/a>\r\n## Step 1 汇编阶段：Reset_Handler\r\n\r\n上电（或复位）后，CPU 从向量表第二项取出地址，跳到 `Reset_Handler`。\r\n\r\n<a id=\"step1-1\"><\/a>\r\n### ① 把 .data 从 Flash 搬到 SRAM\r\n\r\n`.data` 放的是**有初值的全局\/静态变量**。初值存在 Flash（掉电不丢），但运行时变量得在 SRAM，所以每次上电都要搬一遍。\r\n\r\n```assembly\r\n\/* Copy the data segment initializers from flash to SRAM *\/\r\n  movs r1, #0\r\n  b LoopCopyDataInit\r\n\r\nCopyDataInit:\r\n  ldr r3, =_sidata        \/* 源：Flash 里存的初值 *\/\r\n  ldr r3, [r3, r1]\r\n  str r3, [r0, r1]        \/* 目标：SRAM 里的 .data *\/\r\n  adds r1, r1, #4\r\n\r\nLoopCopyDataInit:\r\n  ldr r0, =_sdata\r\n  ldr r3, =_edata\r\n  adds r2, r0, r1\r\n  cmp r2, r3\r\n  bcc CopyDataInit        \/* 没到末尾就继续搬 *\/\r\n```\r\n\r\n`_sidata` 是初值在 Flash 里的位置，`_sdata` \/ `_edata` 是变量在 SRAM 里的位置——链接脚本用 LMA \/ VMA 分别描述这两套地址。\r\n\r\n<a id=\"step1-2\"><\/a>\r\n### ② 清零 .bss\r\n\r\n`.bss` 放**未初始化的全局\/静态变量**。C 标准规定它们的初值为 0，所以上电清一次。\r\n\r\n```assembly\r\n\/* Zero fill the bss segment. *\/\r\nFillZerobss:\r\n  movs r3, #0\r\n  str r3, [r2], #4\r\n\r\nLoopFillZerobss:\r\n  ldr r3, = _ebss\r\n  cmp r2, r3\r\n  bcc FillZerobss\r\n```\r\n\r\n这一步也解释了为什么「未初始化变量默认是 0」——不是编译器的魔法，是启动代码替你还的债。\r\n\r\n<a id=\"step1-3\"><\/a>\r\n### ③ SystemInit：复位时钟 + 设中断向量表\r\n\r\n```assembly\r\n\/* Call the clock system intitialization function.*\/\r\n    bl  SystemInit\r\n```\r\n\r\n`SystemInit` 做两件事：\r\n\r\n1. 把 RCC 打回**复位默认状态**（时钟源回到内部 HSI 8MHz）；\r\n2. 设置 `SCB->VTOR = FLASH_BASE`（0x08000000），告诉内核中断向量表在哪。\r\n\r\n```c\r\n\/* 节选：SystemInit 的关键动作 *\/\r\nRCC->CR   |= 0x00000001U;   \/* 先把 HSI 打开 *\/\r\nRCC->CFGR &= 0xF8FF0000U;   \/* 清 SW \/ HPRE \/ PPRE \/ ADCPRE \/ MCO *\/\r\n...\r\nSCB->VTOR = FLASH_BASE;     \/* 向量表定位到 Flash 起始 *\/\r\n```\r\n\r\n**这里有个常见误解**：`SystemInit` 并没有把主频配到 72MHz，它只是**复位**。真正的 72MHz 在 Step 2 的 `rt_hw_board_init()` 里配置（见下文）。\r\n\r\n> 完整函数见 STM32F1 标准外设库 `system_stm32f1xx.c`，本文只保留关键两段。\r\n\r\n<a id=\"step1-4\"><\/a>\r\n### ④ 进入 C 世界：跳的是 entry，不是 main\r\n\r\n```assembly\r\n\/* Call the application's entry point.*\/\r\n  bl  entry\r\n```\r\n\r\n`entry` 才是第一个 C 函数，它只有一句话：\r\n\r\n```c\r\nint entry(void)\r\n{\r\n    rtthread_startup();\r\n    return 0;\r\n}\r\n```\r\n\r\n注意：`main` 不在这里出场，`entry` 直接进 `rtthread_startup()`。\r\n\r\n<a id=\"step1-5\"><\/a>\r\n### ⑤ 中断函数的弱定义\r\n\r\n启动文件末尾给每个异常\/中断都写了弱符号：\r\n\r\n```assembly\r\n  .weak NMI_Handler\r\n  .thumb_set NMI_Handler,Default_Handler\r\n\r\n  .weak HardFault_Handler\r\n  .thumb_set HardFault_Handler,Default_Handler\r\n  ......\r\n```\r\n\r\n含义：**链接期**，如果别处（比如你的驱动）有同名强定义，就用你的；没有，就把这个向量绑到默认处理函数 `Default_Handler`：\r\n\r\n```assembly\r\nDefault_Handler:\r\nInfinite_Loop:\r\n  b Infinite_Loop        \/* 原地死循环 *\/\r\n```\r\n\r\n所以「开了中断却没写处理函数」的后果是**落进 `Default_Handler` 死循环**，不是进 HardFault——HardFault 是另一种异常，只有真的出错（非法访问、取指错误等）才触发。\r\n\r\n---\r\n\r\n<a id=\"step2\"><\/a>\r\n## Step 2 进入 RT-Thread：rtthread_startup()\r\n\r\n`entry` 调的就是它：\r\n\r\n```c\r\nint rtthread_startup(void)\r\n{\r\n    rt_hw_interrupt_disable();          \/* 先关全局中断 *\/\r\n\r\n    rt_hw_board_init();                 \/* 板级初始化：堆 + 时钟 *\/\r\n    rt_show_version();                  \/* 打印版本 *\/\r\n\r\n    rt_system_timer_init();             \/* 软件定时器系统 *\/\r\n    rt_system_scheduler_init();         \/* 调度器 *\/\r\n\r\n#ifdef RT_USING_SIGNALS\r\n    rt_system_signal_init();\r\n#endif\r\n\r\n    rt_application_init();              \/* 创建 main 线程 *\/\r\n    rt_system_timer_thread_init();      \/* 创建 timer 线程 *\/\r\n    rt_thread_idle_init();              \/* 创建 idle 线程 *\/\r\n\r\n    rt_system_scheduler_start();        \/* 启动调度器，永不返回 *\/\r\n    return 0;\r\n}\r\n```\r\n\r\n几个值得注意的点：\r\n\r\n- **第一句就关中断**：后面全在初始化内核数据结构，不能被打断。\r\n- **`rt_hw_board_init()` 才是真正配时钟的地方**：本工程实际调用是\r\n  `hw_board_init(BSP_CLOCK_SOURCE, ..., BSP_CLOCK_SYSTEM_FREQ_MHZ)`，\r\n  而 `board.h` 里写的是 `BSP_CLOCK_SOURCE = \"HSI\"`、`BSP_CLOCK_SYSTEM_FREQ_MHZ = 72`。\r\n  堆也是在这里初始化的（`rt_system_heap_init`）——所以这之前不能用 `rt_malloc`。\r\n- **`rt_application_init()` 只是「创建」main 线程，并不运行它**：线程创建后进就绪队列，等调度器开跑才轮到。\r\n- **`rt_system_scheduler_start()` 不返回**，所以函数末尾的 `return 0` 永远走不到。\r\n\r\n---\r\n\r\n<a id=\"step3\"><\/a>\r\n## Step 3 调度器启动后：谁先跑？\r\n\r\n`rt_system_scheduler_start()` 做的事很简单：**从就绪队列里挑优先级最高的线程切过去**。\r\n\r\n本工程默认配置下，timer 线程优先级 4、main 线程 10、idle 线程 31，所以第一个跑的是 **timer 线程**，不是 main。但别把它当成固定顺序——本质是「优先级最高者先跑」，改了优先级或启动瞬间有更高优先级线程就绪，顺序就变。\r\n\r\nmain 线程的入口是 `main_thread_entry`：\r\n\r\n```c\r\nvoid main_thread_entry(void *parameter)\r\n{\r\n    extern int main(void);\r\n    extern int $Super$$main(void);\r\n\r\n#ifdef RT_USING_COMPONENTS_INIT\r\n    rt_components_init();     \/* 组件\/驱动的自动初始化在这里集中执行 *\/\r\n#endif\r\n\r\n#if defined(__CC_ARM) || defined(__CLANG_ARM)\r\n    $Super$$main();\r\n#elif defined(__ICCARM__) || defined(__GNUC__)\r\n    main();                   \/* 直到这里，才真正调用你的 main *\/\r\n#endif\r\n}\r\n```\r\n\r\n所以 `main()` 的地位：它既不是复位入口，也不是启动函数，只是**被 main 线程调用的一个普通函数**。我们熟悉的 `INIT_DEVICE_EXPORT \/ INIT_COMPONENT_EXPORT \/ INIT_ENV_EXPORT \/ INIT_APP_EXPORT`，就是在 `rt_components_init()` 里按等级依次执行的（`INIT_BOARD_EXPORT` 那级更早，在 `rt_hw_board_init()` 里就跑过了）。\r\n\r\n---\r\n\r\n<a id=\"summary\"><\/a>\r\n## 总结：四个容易记错的点\r\n\r\n1. **`main` 不是程序入口**——入口是 `Reset_Handler`，`main` 在 main 线程里。\r\n2. **`SystemInit` 不配 72MHz**——它只复位时钟 + 设 VTOR，真正的主频在 `rt_hw_board_init()`。\r\n3. **没写中断处理函数不会进 HardFault**——会落到 `Default_Handler` 死循环。\r\n4. **调度器启动后第一个跑的不一定是 main**——谁优先级高谁先跑，默认是 timer 线程。\r\n\r\n<a id=\"verify\"><\/a>\r\n## 怎么自己验证\r\n\r\n- 在 `entry` \/ `rtthread_startup` \/ `main_thread_entry` 打断点，看调用栈是不是这条链；\r\n- `arm-none-eabi-nm` 查 `_sidata \/ _sdata \/ _edata \/ _sbss \/ _ebss` 的地址，对照链接脚本；\r\n- 把 main 线程优先级调到比 timer 高，重新跑，看谁先打印——第 4 条就现场验证了。\r\n\r\n---\r\n\r\n<details>\r\n<summary>附：本工程的关键配置（rtconfig.h）<\/summary>\r\n\r\n```c\r\n#define RT_THREAD_PRIORITY_MAX      32\r\n#define RT_TIMER_THREAD_PRIO        4\r\n#define RT_MAIN_THREAD_PRIORITY     10\r\n#define RT_USING_HEAP\r\n#define RT_USING_COMPONENTS_INIT\r\n```\r\n\r\n<\/details>","updatetime":"2026-10-26 08:59:10","author":"CTIE_4063"}}