diff --git "a/2026/\347\254\2541\347\273\204/README.md" "b/2026/\347\254\2541\347\273\204/README.md" deleted file mode 100644 index b4dd9cd19371b1a40e7e932315edd9fb1702d8f7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/README.md" +++ /dev/null @@ -1 +0,0 @@ -# 第1组 \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\345\206\267\346\242\246\345\245\207/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\345\206\267\346\242\246\345\245\207/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e67b1e83564bc288a9c77fe529c6fee7f81cdd5d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\345\206\267\346\242\246\345\245\207/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,105 +0,0 @@ -# 第1天作业 · RT-Thread 夏令营 Day1 - -> 提交人:冷梦奇(第1组)|日期:2026-08-17 - -## 一、开发环境搭建 -- [x] 安装 RT-Thread Studio -- [x] 替换 `app.asar` 完成破解 -- [x] SDK Manager 下载 **RT-Thread 4.1.0** 源码 + STM32F4 支持包 -- [x] 确认 ST-Link 驱动已安装 - -## 二、Git 环境配置(已完成) -- [x] 全局 `user.name` / `user.email` 已设置(Leng Mengqi / 学校邮箱) -- [x] 生成 SSH 密钥对,公钥已添加到 Gitee -- [x] `ssh -T git@gitee.com` 验证免密连通成功(返回 `Hi 冷梦奇(@resan)`) -- [x] clone 上游仓库 `resan/rsoc-rtt`,切换到 `day1` 分支(未动 master) - -## 三、STM32F407 实操(已完成 ✅) - -### 3.1 工程信息 -- **工程名**:`01_hello_rtt` -- **RT-Thread 内核版本**:4.1.0(构建时间 `build Aug 17 2026 11:23:12`) -- **板级芯片**:STM32F407 -- **IDE**:RT-Thread Studio(已替换 `app.asar` 完成破解) -- **工程结构**:`applications/main.c` + `CubeMX Settings` + `drivers` + `libraries` + `linkscripts` + `rt-thread [4.1.0]` + `rtconfig.h` - -### 3.2 串口配置 -| 参数 | 值 | -|---|---| -| 串行端口 | **COM5** | -| 波特率 | **115200** | -| 数据位 | 8 | -| 校验位 | None | -| 停止位 | 1 | -| 流控 | None | -| 编码 | UTF-8 | - -### 3.3 关键代码(`applications/main.c`) -```c -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * SPDX-License-Identifier: Apache-2.0 - * Change Logs: - * Date Author Notes - * 2026-08-17 RT-Thread first version - */ - -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG - -#include - -int main(void) -{ - int count = 1; - - rt_kprintf("hello RSOC2026!\n"); - - while (count++) - { - LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - - return RT_EOK; -} -``` - -### 3.4 终端运行结果 - -**启动 banner** -``` - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 17 2026 11:23:12 - 2006 - 2022 Copyright by RT-Thread team -hello RSOC2026! -[D/main] Hello RT-Thread! -msh /> -``` - -**`msh />free` 内存查询** -``` -msh />free -total : 125816 -used : 7076 -maximum : 7076 -available: 118740 -``` - -主循环以 `rt_thread_mdelay(1000)` 每秒持续打印 `[D/main] Hello RT-Thread!`;`msh` 交互正常,`help` 命令可列出 `list_thread` / `list_sem` / `list_event` / `list_mutex` / `list_mailbox` / `list_msgqueue` / `list_mempool` / `list_timer` 等 RT-Thread shell 工具。 - -### 3.5 完成项 -- [x] 创建 STM32F407 Hello World 工程(标准版内核) -- [x] 编译生成 `.elf` / `.bin`,ST-Link 下载到板子 -- [x] 终端查看启动日志(`hello RSOC2026!` + RT-Thread 4.1.1 banner) -- [x] 断点调试可用(RT-Thread Studio 集成) -- [x] msh 交互正常(`free` / `help` / `list_*` 等命令验证通过) - -## 四、学习笔记 -- 见同级 `../笔记/第一天笔记.md` - -## 五、PR 提交说明 -- 本次提交为「当天作业 / 笔记提交」PR,对上游仓库发起,指定**小组长**为**易莹莹** diff --git "a/2026/\347\254\2541\347\273\204/\345\206\267\346\242\246\345\245\207/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\345\206\267\346\242\246\345\245\207/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index b6b765d4ce64cdd142aefc4e53d05092a892cd06..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\345\206\267\346\242\246\345\245\207/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,147 +0,0 @@ ---- -title: RT-Thread 夏令营 Day1 学习笔记 -date: 2026-08-17 ---- - -# RT-Thread 大学生夏令营 Day1 学习笔记 - -> 日期:2026-08-17 | 来源:腾讯会议「2026 RT-Thread大学生夏令营 Day1」(会议号 111498714)智能纪要整理 - -## 一、夏令营整体安排 - -- **双周学习模式** - - 第一周 = 学习周:上午 9:00–12:00 授课,下午 14:00 起实操 / 作业 - - 第二周 = 项目周:学员分组完成项目任务 -- **考核与产出**:每日需完成作业并提交至 Git 仓库;第二周根据项目任务筛选与分组 -- **签到**:每日开课前线上签到 -- **硬件**:星火 1 号开发板;快递问题有专门查询与解决渠道 - -## 二、RT-Thread 生态与产品线 - -- 国产自研 RTOS,发展近 20 年,GitHub **12.2k** stars,国内活跃开源社区 -- 内核版本已迭代至 **V5.3.0** -- 三条核心产品线: - -| 版本 | 特点 | 协议 / 适用 | -|---|---|---| -| **Nano 版** | 极简内核:实时内核 + 线程同步通信机制 + 最小实体(线程 / 时钟 / 中断 / 内存管理) | Apache 协议开源 | -| **标准版** | 基于标准 POSIX API,丰富组件(日志管理、网络协议栈、IoT 协议)及中间件,高度可定制 | 生态完整 | -| **Smart 版** | 面向带 MMU 设备,混合微内核架构,内核态与用户态分离,提升安全性 | 带 MMU 设备 | - -## 三、开发环境搭建(RT-Thread Studio + STM32F407) - -1. 安装 **RT-Thread Studio**,替换 `app.asar` 文件完成破解 -2. **SDK Manager** 下载 RT-Thread 源码(统一版本 **4.1.0**)+ STM32F4 芯片支持包 -3. 确认设备管理器识别到 **ST-Link** 驱动,保证硬件连接正常 -4. 基于 **STM32F407** 芯片创建工程:选择标准版内核;配置串口参数(波特率 **115200**) -5. 编译生成 `.elf` 和 `.bin` 文件,通过 ST-Link 下载至开发板 -6. 启动终端查看启动日志验证程序运行,并演示断点调试功能 - -> 实操目标:创建基于 STM32F407 的 Hello World 工程,并完成编译 / 下载 / 调试 - -### 3.1 实操记录(已完成 ✅) - -**工程信息** -- 工程名:`01_hello_rtt` -- 内核版本:**RT-Thread 4.1.0**(`build Aug 17 2026 11:23:12`) -- 芯片:STM32F407 - -**串口配置**:COM5 / 115200 / 8N1 / UTF-8 - -**`applications/main.c` 关键代码**: -```c -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -int main(void) -{ - int count = 1; - rt_kprintf("hello RSOC2026!\n"); - while (count++) - { - LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - return RT_EOK; -} -``` - -**终端启动输出** -``` - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 17 2026 11:23:12 - 2006 - 2022 Copyright by RT-Thread team -hello RSOC2026! -[D/main] Hello RT-Thread! -``` - -`msh />free` 显示内存:`total 125816 / used 7076 / available 118740`;主循环每 1 秒打印一次 `[D/main] Hello RT-Thread!`;`msh` 交互正常(`list_thread` / `list_sem` / `list_event` / `list_mutex` / `list_mailbox` / `list_msgqueue` / `list_mempool` / `list_timer` 等命令均可用)。 - -## 四、Markdown 笔记规范 - -- 基础语法:标题 `#`、代码块 ```` ``` ````、表格、加粗 `**`、图片插入 `![]()` -- **图片管理**:建议将图片保存至 `./figures` 目录,防止项目迁移时资源丢失 -- 笔记统一使用 Markdown 语法,提交至指定 Git 仓库 - -## 五、Git 与开源协作流程 - -### 5.1 环境配置 - -- 安装 Git(可尝试配置中文语言包,汉化失败则默认英文) -- 全局身份设置:`git config --global user.name` / `user.email` -- 生成 **SSH 密钥对**(公钥 + 私钥),将公钥添加到 Git 平台账户 → 实现免密认证 - -### 5.2 核心指令 - -```bash -git init # 初始化仓库 -git clone # 克隆远端仓库 -git pull # 拉取最新代码 -git add # 加入暂存区 -git commit -m "..." # 提交到本地仓库 -git push # 推送到远端仓库 -git checkout -b day1 # 创建并切换分支(日常开发禁直接操作 master) -``` - -### 5.3 Fork 协作模式 - -1. **Fork** 上游仓库到个人账户,形成独立开发副本 -2. 在个人 Fork 中创建功能分支(如 `day1` / `day2`),严禁直接修改 master -3. 推送后通过平台创建 **PR**,指定 Reviewer(组长 / 导师) -4. 同步上游:`git remote add upstream ` + `git fetch/pull upstream` -5. 冲突解决:合并上游代码或手动解决 - -### 5.4 目录与命名规范 - -- 目录结构:`组别 / 学员姓名 / 日期`,分别存放笔记和作业 -- 分支命名:`day1`、`day2` 等,避免覆盖模板文件 - -### 5.5 角色职责 - -- **学员**:提交 PR -- **小组长**:审核并合并组员 PR -- **导师**:审核小组长 PR,处理跨组复杂问题 - -## 六、我的待办清单(Action Items) - -- [x] 安装 RT-Thread Studio + STM32F4 芯片支持包,完成破解(替换 `app.asar`) -- [x] 学习并掌握 Markdown 语法(图片统一放 `./figures`) -- [x] 创建基于 STM32F407 的 Hello World 工程,完成编译 / 下载 / 调试(ST-Link,115200) -- [x] 完成 Git 环境配置与 SSH 密钥设置 -- [x] 提交模板 PR(纯模板版 day1 分支 → 上游) -- [x] 按 `组别 / 姓名 / 日期` 每日提交笔记与作业(Day1 笔记/作业内容已完成,PR 待提) -- [x] 提交 Day1 作业 / 笔记 PR 至上游(指定小组长易莹莹为 Reviewer) - -## 七、后续时间节点 - -- 每日 09:00–12:00 授课,14:00 起实操 / 作业 -- 每日开课前:线上签到 -- 每日:提交作业 / 笔记(PR 方式) -- 第二周(项目周):分组完成项目任务(具体日期按夏令营两周总安排) - ---- - diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/README.md" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/README.md" deleted file mode 100644 index b4dd9cd19371b1a40e7e932315edd9fb1702d8f7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/README.md" +++ /dev/null @@ -1 +0,0 @@ -# 第1组 \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 91432fa740e27d5a9d94b4b0a4eca11d06f3ae73..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,212 +0,0 @@ -# Part 1 理论整理 -## 1. STM32F407 启动流程与 RT-Thread 引导机制 -### 启动路径 -- 详细画出/写出从上电复位 `Reset_Handler` 到进入 `main()` 的完整调用链。 -![STM32F407 启动流程图](figures/Pasted%20image%2020260818223818.png) -```text -上电 / 复位 -  ↓ -Reset_Handler(设置栈、向量表,执行 SystemInit) -  ↓ -工具链 C 运行时初始化 -  ├─ GCC:entry() -  ├─ ARMCC:$Sub$$main() -  └─ IAR:__low_level_init() -  ↓ -rtthread_startup() -  ├─ 关闭全局中断 -  ├─ rt_hw_board_init():时钟、GPIO、UART、堆等板级初始化 -  ├─ rt_show_version():打印 RT-Thread banner -  ├─ rt_system_timer_init() -  ├─ rt_system_scheduler_init() -  ├─ rt_system_signal_init()(启用信号时) -  ├─ rt_application_init():创建 main 线程 -  ├─ rt_system_timer_thread_init()(启用软件定时器线程时) -  ├─ rt_thread_idle_init():创建 idle 线程 -  └─ rt_system_scheduler_start():启动调度器 -       ↓ -main 线程 main_thread_entry() -  ├─ rt_components_init():按等级执行自动初始化函数 -  └─ main():用户应用入口 -``` - -- RT-Thread 的 `main()` 不是复位后直接调用的裸机入口。而是会先创建 `main` 线程,再由 `main_thread_entry()` 执行组件初始化,最后进入用户的 `main()`。 -- 调度器启动后,系统按照数值更小优先级更高的规则选择就绪线程;`idle` 线程优先级最低,在没有其他就绪线程时运行。 -### 自动初始化原理 -- 解释 `INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` 的底层实现原理。 -`INIT_BOARD_EXPORT(fn)` 和 `INIT_APP_EXPORT(fn)` 都是 `INIT_EXPORT(fn, level)` 的封装,区别只是登记的等级不同: - -| 宏 | 等级 | 执行阶段 | 典型内容 | -| :---------------------: | :-: | :---: | :------------: | -| `INIT_BOARD_EXPORT` | `1` | 板级初始化 | GPIO、时钟、底层设备 | -| `INIT_DEVICE_EXPORT` | `3` | 设备初始化 | UART、SPI、I2C 等 | -| `INIT_COMPONENT_EXPORT` | `4` | 组件初始化 | DFS、网络协议栈 | -| `INIT_ENV_EXPORT` | `5` | 环境初始化 | 文件系统挂载、环境变量 | -| `INIT_APP_EXPORT` | `6` | 应用初始化 | 应用线程、应用服务 | - -宏把函数指针放入名称类似 `.rti_fn.1` / `.rti_fn.6` 的特定段;链接脚本按等级把这些段连续排列,并生成起止符号。`rt_components_init()` 从 `__rt_init_rti_board_end` 遍历到 `__rt_init_rti_end`,依次取出函数指针并调用。因此,应用只需要写: - - -```c -static int thread_study_init(void) -{ -    /* 创建线程、配置引脚等 */ -    return 0; -} -INIT_APP_EXPORT(thread_study_init); -``` - -就可以在 `main()` 前由系统自动执行,不需要在 `main.c` 中硬编码调用。 -- 自动初始化的关键是“宏登记 + 链接器排序 + 统一遍历调用”。 -## 2. 内核基础与对象模型 -### 对象容器与控制块 -- 简述 RT-Thread 面向对象的设计思想,线程控制块(`struct rt_thread`)中关键成员(如 `stat`、`current_priority`、`init_tick`、`sp`)的作用。 -RT-Thread 把线程、信号量、互斥量、邮箱等都抽象成内核对象。对象有统一的名称、类型和链表节点,具体对象再扩展自己的控制字段。 -- 这样做可以用统一的对象管理、查找、挂起、恢复和调度接口,体现了 RT-Thread 的面向对象思想。 - -| 成员 | 作用 | -| :-------------------------: | :-------------------------------------------: | -| `stat` | 线程状态和附加标志,低位可与 `RT_THREAD_STAT_MASK` 相与得到五态之一 | -| `current_priority` | 当前有效优先级,数值越小优先级越高 | -| `init_tick` | 同优先级线程的初始时间片,时间片耗尽后触发轮转 | -| `sp` | 保存的线程栈指针,上下文切换时恢复 | -| `stack_addr` / `stack_size` | 栈起始地址和总大小,初始化时会填充 `#`,可据此统计历史剩余空间 | -| `tlist` / `thread_timer` | 就绪 / 挂起链表节点和线程延时定时器 | - -### 内存分配方式 -- 对比静态对象与动态对象的区别(分配位置、生命周期、释放机制),分析为什么某些高可靠场景强制使用静态线程。 -#### 静态创建 - -```c -rt_thread_t rt_thread_create(const char* name, - void (*entry)(void* parameter), - void* parameter, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -- 线程句柄(线程控制块指针)、线程栈由用户提供,一般被设置为全局变量,在编译时就确定,内核不负责动态分配内存空间。 -- 用户提供的栈首地址需要做系统对齐(如ARM上需要做4字节对齐) - -| 参数 | 描述 | -| ----------- | ------------------------------------------------------------ | -| thread | 线程句柄。线程句柄由用户提供出来,并指向对应的线程控制块内存地址 | -| name | 线程的名称;线程名称的最大长度由 rtconfig.h 中定义的 RT_NAME_MAX 宏指定,多余部分会被自动截掉 | -| entry | 线程入口函数 | -| parameter | 线程入口函数参数 | -| stack_start | 线程栈起始地址 | -| stack_size | 线程栈大小,单位是字节。在大多数系统中需要做栈空间地址对齐(例如 ARM 体系结构中需要向 4 字节地址对齐) | -| priority | 线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0~255,数值越小优先级越高,0 代表最高优先级 | -| tick | 线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行 | -| **返回** | **——** | -| RT_EOK | 线程创建成功 | -| -RT_ERROR | 线程创建失败 | - -#### 动态创建 - -```c -rt_thread_t rt_thread_create(const char* name, - void (*entry)(void* parameter), - void* parameter, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -- 调用这个函数时,系统会从**动态堆内存**中分配一个线程句柄以及按照参数中指定的栈大小相应的空间。 - -- 分配出来的栈空间是按照 `rtconfig.h` 中配置的 `RT_ALIGN_SIZE` 方式对齐。 -#### 对比 - -| 对比项 | 静态线程 | 动态线程 | -| :--: | :-----------------------------: | :----------------------------------------------: | -| 创建方式 | 全局 TCB + 全局栈,`rt_thread_init()` | `rt_thread_create()` 从堆申请 TCB 和栈 | -| 内存位置 | 编译期确定,通常位于 `.bss` / 静态 RAM | 运行时从 RT-Thread heap 分配 | -| 生命周期 | 由程序生命周期管理,通常不会自动释放 | `rt_thread_delete()` 后由内核回收,线程退出后进入 defunct 回收流程 | -| 优点 | 无堆碎片、创建结果可预测,适合安全关键路径 | 编程方便,栈大小和线程数量更灵活 | -| 风险 | 占用固定 RAM,数量过多会浪费空间 | 可能申请失败,也可能出现碎片和生命周期管理错误 | -**分析:** -- 在电机控制、故障保护、汽车电子等高可靠场景,线程数量和栈空间通常需要在设计阶段确定,所以优先使用静态线程:系统启动后不依赖堆分配,能避免运行时 `malloc` 失败和碎片化导致的不确定行为。 -## 3. 线程状态机与调度算法 -### 五态模型 -- 绘制或描述 RT-Thread 线程状态(初始态、就绪态、运行态、挂起态、关闭态)及触发状态转换的典型 API。 - -| 状态 | 描述 | -| :--: | :---------------------------------------------------------------------------------------------------------------: | -| 初始状态 | 当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_INIT | -| 就绪状态 | 在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。此状态在 RT-Thread 中的宏定义为 RT_THREAD_READY | -| 运行状态 | 线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。此状态在 RT-Thread 中的宏定义为 RT_THREAD_RUNNING | -| 挂起状态 | 也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_SUSPEND | -| 关闭状态 | 当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_CLOSE | - -#### 状态转换 - -![线程状态转换图](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_sta.png) - -- **初始 -> 就绪** - - `rt_thread_create()`:动态创建线程并初始化线程控制块。 - - `rt_thread_init()`:静态初始化线程对象及相关参数。 - - `rt_thread_startup()`:启动线程,使其进入就绪状态。 -- **就绪 -> 运行** - - 调度器根据线程优先级和时间片选择就绪线程运行。 -- **就绪 -> 挂起** - - `rt_thread_delay()`:延时,让出 CPU。 - - `rt_sem_take()`:获取信号量。 - - `rt_mutex_take()`:获取互斥量。 - - `rt_mp_alloc()`:从内存池中申请一个内存块。 - - `rt_event_recv()`:接收事件集中的指定事件,并按照设定条件等待或返回。 - - `rt_mb_recv()`:从邮箱中接收一条消息。 - - `rt_mq_recv()`:从消息队列中接收一条消息。 -- **挂起 -> 就绪** - - `rt_thread_resume()`:恢复挂起线程,使其重新进入就绪状态。 - - `rt_sem_release()`:释放信号量,使信号量计数加一,并唤醒等待线程。 - - `rt_mutex_release()`:释放当前线程持有的互斥量。 - - `rt_mp_release()`:释放内存块,并将其归还到内存池。 - - `rt_event_send()`:发送指定事件,并唤醒满足条件的等待线程。 - - `rt_mb_send()`:向邮箱发送一条消息,邮箱已满时发送失败。 - - `rt_mq_send()`:向消息队列发送一条消息,队列已满时发送失败。 -- **运行 -> 关闭** - - `rt_thread_exit()`:退出当前线程,使线程进入关闭状态。 -- **挂起 -> 关闭** - - `rt_thread_delete()`:删除线程并释放动态分配的线程资源。 - - `rt_thread_detach()`:分离线程并释放线程相关资源。 - -### 调度机制剖析 -#### 抢占式调度 -线程从挂起态转为就绪态后,如果它的**优先级高于当前运行线程**,调度器会立即触发切换;高优先级线程不必等待低优先级线程主动让出 CPU。 -#### 时间片轮转 -当多个就绪线程拥有**相同优先级**时,每个线程最多运行 `init_tick` 个系统 tick,时间片用完后把当前线程放到同优先级队列尾部,轮到下一个线程。 - -# Part 2 实验结果分析与串口截图 - -## 实验结果分析 -- 程序通过 `INIT_APP_EXPORT()` 自动初始化,并创建了三个线程:动态线程 `th_led1`、静态线程 `th_led2` 和监控线程 `th_monitor`。 - -- `th_led1` 优先级为 16、时间片为 5 ticks、栈大小为 1024 Bytes,负责翻转红色 LED;`th_led2` 使用静态 TCB 和栈,参数相同,负责翻转蓝色 LED。实验中两个 LED 均能正常闪烁,说明动态线程和静态线程创建成功。 - -- `th_monitor` 优先级为 10,通过轮询 PC0 检测 KEY0,并进行 50 ms 消抖。第一次按键后,`th_led1` 被挂起,红色 LED 停止运行;再次按键后,`th_led1` 恢复运行,计数值继续增加。 - -- 从串口结果可以看到,按键松开时 `key=1、suspended=1`,按下时 `key=0、suspended=0`,说明按键检测以及线程挂起、恢复功能正常。通过 `thread_dump` 和 `list_thread` 还可以查看线程优先级、运行状态和栈使用情况,实验结果符合要求。 -## 串口截图 -![串口运行截图](figures/Pasted%20image%2020260819001453.png) - -# 作业要求 - -### 1. 提交目录规范 - -``` -2026/<组别>/<姓名>/第二天作业 -├── README.md # 包含:Part 1 理论整理 + Part 2 实验结果分析与串口截图 -└── thread_study.c # 作业源码(含动/静态线程、自动初始化、MSH导出) -``` - -### 2. PR 提交流程与规范 - -1. Fork 官方仓库 [https://gitee.com/rtthread/rsoc-rtt](https://gitee.com/rtthread/rsoc-rtt) 到个人账号。 -2. 基于 `master` 新建分支:`day2-姓名拼音`。 -3. 编写代码、文档并进行本地 `scons` 编译验证与星火1号烧录测试。 -4. 提交 Commit。 -5. 向主仓库提交 Pull Request: - - **PR 标题**:`[作业提交] 内核与多线程培训 - <姓名>` - - **PR 内容**:附带理论文档简述、硬件运行现象照片及 FinSH 串口日志输出。 diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/Pasted image 20260818223818.png" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/Pasted image 20260818223818.png" deleted file mode 100644 index 7b74fa6fd284396996b33d321cb68aefcf6a117a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/Pasted image 20260818223818.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/Pasted image 20260819001453.png" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/Pasted image 20260819001453.png" deleted file mode 100644 index d57f23e07feb4db51090eee43ec6e97615ab0941..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/Pasted image 20260819001453.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index cf40a982dba4806b4de659232c8c5240a47e1bc9..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,255 +0,0 @@ -/* - * RT-Thread 夏令营 Day2 作业:动态线程、静态线程与线程监控 - * 目标板:STM32F407 星火1号 - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include - -#define DBG_TAG "thread_study" -#define DBG_LVL DBG_LOG -#include - -/* 星火1号板载资源:LED 低电平点亮,KEY0 按下为低电平。 */ -#define PIN_LED_B GET_PIN(F, 11) /* PF11 */ -#define PIN_LED_R GET_PIN(F, 12) /* PF12 */ -#define PIN_KEY0 GET_PIN(C, 0) /* PC0 */ - -#define LED_STACK_SIZE 1024 -#define MONITOR_STACK_SIZE 1024 -#define THREAD_PRIORITY 16 -#define THREAD_TICK 5 -#define DAY2_AUTO_PRINT 0 - -/* 动态创建的线程句柄。 */ -static rt_thread_t th_led1 = RT_NULL; -static rt_thread_t th_monitor = RT_NULL; - -/* 静态线程的 TCB 与栈。 */ -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t th_led2_stack[LED_STACK_SIZE]; -static struct rt_thread th_led2; - -static volatile rt_uint8_t led1_suspend_request; -static volatile rt_uint8_t led1_is_suspended; -static rt_uint32_t led1_count; -static rt_uint32_t led2_count; -static rt_tick_t led1_last_tick; - -static const char *thread_state_name(rt_uint8_t stat) -{ - switch (stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: - return "init"; - case RT_THREAD_READY: - return "ready"; - case RT_THREAD_RUNNING: - return "running"; - case RT_THREAD_SUSPEND: - return "suspend"; - case RT_THREAD_CLOSE: - return "close"; - default: - return "unknown"; - } -} - -/* RT-Thread 初始化时会用 '#' 填充线程栈,扫描得到历史未使用空间。 */ -static rt_size_t thread_stack_remaining(rt_thread_t thread) -{ - rt_uint8_t *stack; - rt_size_t index = 0; - - if (thread == RT_NULL || thread->stack_addr == RT_NULL) - { - return 0; - } - - stack = (rt_uint8_t *)thread->stack_addr; - while (index < thread->stack_size && stack[index] == '#') - { - index++; - } - - return index; -} - -static void dump_one_thread(const char *label, rt_thread_t thread) -{ - if (thread == RT_NULL) - { - rt_kprintf("%-10s: not created\n", label); - return; - } - - rt_kprintf("%-10s: state=%-7s priority=%u stack=%u/%u bytes\n", - label, - thread_state_name(thread->stat), - thread->current_priority, - (unsigned int)thread_stack_remaining(thread), - (unsigned int)thread->stack_size); -} - -static void led1_entry(void *parameter) -{ - (void)parameter; - while (1) - { - /* rt_thread_suspend() 只能由当前线程调用,故在这里自挂起。 */ - if (led1_suspend_request) - { - led1_suspend_request = 0; - led1_is_suspended = 1; - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - led1_is_suspended = 0; - continue; - } - - rt_pin_write(PIN_LED_R, PIN_LOW); - led1_count++; - led1_last_tick = rt_tick_get(); -#if DAY2_AUTO_PRINT - rt_kprintf("th_led1 count=%u tick=%u\n", - (unsigned int)led1_count, - (unsigned int)led1_last_tick); -#endif - rt_thread_mdelay(500); - - rt_pin_write(PIN_LED_R, PIN_HIGH); - rt_thread_mdelay(500); - } -} - -static void led2_entry(void *parameter) -{ - (void)parameter; - while (1) - { - rt_pin_write(PIN_LED_B, PIN_LOW); - led2_count++; -#if DAY2_AUTO_PRINT - rt_kprintf("th_led2 count=%u\n", (unsigned int)led2_count); -#endif - rt_thread_mdelay(700); - - rt_pin_write(PIN_LED_B, PIN_HIGH); - rt_thread_mdelay(700); - } -} - -static void monitor_entry(void *parameter) -{ -#if DAY2_AUTO_PRINT - rt_tick_t last_report = rt_tick_get(); -#endif - - (void)parameter; - while (1) - { - /* KEY0/PC0 为低电平有效,轮询并消抖。 */ - if (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - rt_thread_mdelay(50); - if (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - if (!led1_is_suspended) - { - /* 由 th_led1 自己调用 rt_thread_suspend。 */ - led1_suspend_request = 1; - } - else if (th_led1 != RT_NULL && - rt_thread_resume(th_led1) == RT_EOK) - { - led1_is_suspended = 0; - } - - while (rt_pin_read(PIN_KEY0) == PIN_LOW) - rt_thread_mdelay(10); - } - } - -#if DAY2_AUTO_PRINT - if (rt_tick_get() - last_report >= rt_tick_from_millisecond(2000)) - { - last_report = rt_tick_get(); - dump_one_thread("th_led1", th_led1); - dump_one_thread("th_led2", &th_led2); - dump_one_thread("th_monitor", th_monitor); - } -#endif - /* 监控线程持续轮询,默认通过 thread_dump 手动查看。 */ - rt_thread_mdelay(20); - } -} - -static int thread_dump(int argc, char **argv) -{ - (void)argc; - (void)argv; - - rt_kprintf("key=%d suspended=%u | count: led1=%u tick=%u led2=%u\n", - rt_pin_read(PIN_KEY0), - led1_is_suspended, - (unsigned int)led1_count, - (unsigned int)led1_last_tick, - (unsigned int)led2_count); - dump_one_thread("th_led1", th_led1); - dump_one_thread("th_led2", &th_led2); - dump_one_thread("th_monitor", th_monitor); - return 0; -} -MSH_CMD_EXPORT(thread_dump, dump Day2 thread state and stack information); - -static int thread_study_init(void) -{ - rt_err_t result; - - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - rt_pin_write(PIN_LED_R, PIN_HIGH); - rt_pin_write(PIN_LED_B, PIN_HIGH); - - rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP); - -#ifdef RT_USING_HEAP - th_led1 = rt_thread_create("th_led1", led1_entry, RT_NULL, - LED_STACK_SIZE, THREAD_PRIORITY, THREAD_TICK); - if (th_led1 == RT_NULL) - { - LOG_E("create th_led1 failed"); - return -RT_ENOMEM; - } - rt_thread_startup(th_led1); -#else - LOG_E("Day2 requires RT_USING_HEAP for dynamic th_led1"); - return -RT_ENOMEM; -#endif - - result = rt_thread_init(&th_led2, "th_led2", led2_entry, RT_NULL, - th_led2_stack, sizeof(th_led2_stack), - THREAD_PRIORITY, THREAD_TICK); - if (result != RT_EOK) - { - LOG_E("init th_led2 failed: %d", result); - return result; - } - rt_thread_startup(&th_led2); - - th_monitor = rt_thread_create("th_monitor", monitor_entry, RT_NULL, - MONITOR_STACK_SIZE, 10, THREAD_TICK); - if (th_monitor == RT_NULL) - { - LOG_E("create th_monitor failed"); - return -RT_ENOMEM; - } - rt_thread_startup(th_monitor); - - return 0; -} -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index ecba57a4d5cc88271204198349a6a686380a26f0..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,352 +0,0 @@ -# Markdown 语法 - -**教程:**[Markdown 教程 | 菜鸟教程](https://www.runoob.com/markdown/md-tutorial.html) - -- Markdown 是轻量级标记语言,文件后缀为 `.md` 或 `.markdown`。 -- Markdown 可用于记录学习笔记和编写项目文档。 - -### **标题** - -```markdown -# 一级标题 -## 二级标题 -### 三级标题 -``` - -### **代码** - -行内代码: - -```markdown -`代码` -``` - -代码块: - -````markdown -```c -int main(void) -{ -} -``` -```` - -代码块开头可以标注 `c`、`cpp`、`python` 等语言,实现对应的语法高亮。 - -### **表格、加粗和图片** - -```markdown -| 特性一 | 特性二 | -| --- | --- | -| 内容一 | 内容二 | - -**加粗文字** - -![图片说明](图片路径) -``` - -- 图片可直接从剪贴板粘贴到 Markdown 文档。 -- 图片保存位置可设置为当前目录下的 `figures` 文件夹。 -- 使用相对路径可避免移动文档后图片丢失。 - -# RT-Thread - -1. RT-Thread Nano版:**强实时,极精简,高可靠** -2. RT-Thread 标准版:**高度定制化,完整的生态,一体化系统** -3. RT-Thread Smart:**内核轻量化,用户态系统服务,相同API风格** - -### I/O设备模型框架 - -- I/O设备管理层 - - I/O设备管理接口 -- 设备驱动框架层 - - 串口设备 - - SPI设备 - - I2C设备 - - PIN设备 -- 设备驱动层 - - 针对不同产家的驱动 - -## RT-Thread Studio - -**RT-Thread GitHub:https://github.com/RT-Thread/rt-thread** -**RT-Thread文档中心:https://www.rt-thread.org/document/site/#/** -**RT-Thread技术论坛:https://club.rt-thread.org/index.html** - -- 开发工具:[RT-Thread 文档中心-开发工具](https://www.rt-thread.org/document/site/#/development-tools/rtthread-studio/README) - - - RT-Thread Studio - - Scons - - Evn - -### 创建工程 - -- RT-Thread源码版本:4.1.1 - -- 芯片支持包:STM32F - -- 芯片:STM32F407ZG - -- 调试器:ST-Link - -### 工程目录 - -- application——程序入口,main函数所在地 - -- drivers——底层驱动相关文件 - - 应用驱动(键盘,鼠标,LCD等) - - 串口驱动 - - 外设驱动 -- RT-Thread - - components——组件 - - cplusplus——c++支持 - - dfs——dfs文件系统支持 - - finsh——finsh组件(终端) - - libc——工具链(armcc、gcc、arm等) - - drivers——驱动框架 - - libcpu——针对不同架构下环境的汇编 - - src——内核源码 - - 时钟管理 - - 设备管理 - - 中断管理 - - 内存管理 - - 对象管理 - - ...... - -### RT-Thread Studio功能 - -1. 编辑 -2. 编译 - - 无法编译:右键工程,同步Scons - - 编译完成后生成 `.elf` 文件和 `.bin` 文件。 -3. 下载 -4. 调试 - -### RT-Thread启动流程 - -![启动流程](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/rtt_startup.png) - -#### 启动函数 - -1. 关闭全局中断 -2. 板级初始化 -3. show_version——打印logo -4. 系统时钟初始化 -5. 系统标记初始化 -6. 信号量初始化 -7. application初始化 - - main线程初始化 -8. 线程初始化(系统级的线程) -9. idle线程初始化 -10. 启动调度器(基于抢占优先级) - -#### 启动的汇编文件 - -文件路径:libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates\gcc - -1. 声明全局函数 - -2. Reset_Handle - - - 执行内存段的处理 - -3. system_init - - - 完成对芯片的配置 - - 芯片启动之前的工作 - -4. entry - - - 启动入口 - - - 板级初始化 - - - 使能中断 - - - 设置系统时钟 - - ...... - -**与裸机的区别:** - -​ 裸机直接跳转到main函数,没有一系列的启动过程 - -# git - -## git配置 - -1. 配置邮箱与用户名 - -```bash -git config --global user.name "yythlss" //用户名 -git config --global user.email "2911687347@qq.com" //邮箱 -git config --list //检查配置 -``` - -2. 生成公钥 - -```bash -ssh-keygen -t rsa -b 4096 -C "2911687347@qq.com" //生成一对 SSH 密钥,常用于免密码连接 -cat /c/Users/86152/.ssh/id_rsa.pub -``` - -## git命令 - -1. 克隆远程仓库: - - ```bash - git clone <仓库 URL> - ``` - - 将远程仓库下载到本地,可以使用 HTTPS 或 SSH 地址。 - -2. 配置用户名: - - ```bash - git config --global user.name "用户名" - ``` - -3. 配置邮箱: - - ``` - git config --global user.email "邮箱" - ``` - -4. 查看 Git 配置: - - ``` - git config --list - ``` - -5. 初始化当前目录: - - ``` - git init - ``` - - 将当前目录初始化为 Git 仓库。 - -6. 拉取远程主分支: - - ``` - git pull origin master - ``` - - 将远程 `master` 分支同步到本地。 - -7. 查看文件状态: - - ``` - git status - ``` - - 查看文件修改情况及暂存区状态。 - -8. 添加文件到暂存区: - - ``` - git add README.md - git add <文件或目录> - git add . - ``` - - `git add .` 表示添加当前目录下的全部修改。 - -9. 创建本地提交: - - ``` - git commit -m "first commit" - ``` - - `-m` 后面填写本次提交说明。 - -10. 推送到远程主分支: - - ``` - git push origin master - ``` - - `pull` 表示远程到本地,`push` 表示本地到远程。 - -11. 修改远程仓库地址: - - ``` - git remote set-url origin - ``` - -12. 获取远程分支信息: - - ``` - git fetch origin master - ``` - -13. 查看远程仓库地址: - - ``` - git remote -v - ``` - -14. 创建并切换到新分支: - - ``` - git checkout -b test - ``` - -15. 将开发分支推送到远程: - - ``` - git push origin test - ``` - - 此时远程 `master` 不会改变,后续通过 PR 合并。 - -16. 生成 SSH 密钥: - - ``` - ssh-keygen -t rsa - ``` - -17. 查看并复制 SSH 公钥: - - ``` - cat ~/.ssh/id_rsa.pub - ``` - -18. 测试 Gitee SSH 连接: - - ``` - ssh -T git@gitee.com - ``` - - 第一次连接时输入 `yes`,出现成功提示表示配置完成。 - -## 提交流程 - -```txt -fork仓库 - ↓ -git clone - ↓ -git config - ↓ -配置 SSH - ↓ -git pull origin master - ↓ -git checkout -b test - ↓ -修改文件 - ↓ -git add . - ↓ -git commit -m "提交说明" - ↓ -git push origin test - ↓ -创建 PR -``` - - -## 解决git冲突 - -1. fork repo:同步上游 -2. 切到产生冲突的分支,执行git merge/rebase,将最新远端的修改同步到产生冲突的分支,此时会让你解决冲突(自行选择旧的时间线还是新的时间线的修改) \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 46d3da425067d75bb9cf00d9728cb809a037406f..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\230\223\350\216\271\350\216\271/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,565 +0,0 @@ -## 裸机vsRTOS - -### 裸机 - -- 前台:中断服务程序接收异步中断,后台进行中断处理 - -- 后台:while(1)死循环 - -**阻塞性问题**:裸机前后台系统中,后台循环任务若存在阻塞行为(如等待标志位),会导致LCD刷新等实时任务无法及时响应。 - -**裸机的缺陷:** - -- 并发效率低 -- 实时性差(阻塞影响) -- 代码可维护性差(牵一发而动全身) -- 模块化程度低导致的可重用性差 - -### RTOS - -任务的实体——**线程** - -**精髓:多任务系统** - -运用分而治之的原理,把一个大问题拆成多个相对简单的小问题。 - -**线程的运行属性:** - -- 就绪态 -- 运行态 -- 挂起态 - -#### 启动流程 - -![启动流程](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/rtt_startup.png) - -1. 启动文件 -2. 工具链 - - MDK - - `$Sub$$main()` - - LAR - - `_low_level_init()` - - GCC(最主流) - - `entry()` -3. `rtthread_setup()` - - `rt_hw_interrupt_disable()`**关闭中断** - - `rt_hw_board_init()` **->** `rt_component_board_init()`->`bord init functions`**初始化底层的外设(如USART)** - - `rt_show_version()`**打印logo** - - `rt_system_timer_init()`**初始化系统定时器** - - `rt_system_scheduler_init()`**初始化系统调度器** - - `rt_system_signal_init()`**初始化信号量(可选)** - - `rt_application_init()` **->** `rt_thread_init(main_thread_entry)`**初始化main线程** - - `rt_system_timer_thread_init()` **->** `rt_thread_init(rt_thread_timer_entry)`**初始化系统定时器线程(可选)** - - `rt_thread_idle_init()` **->** `rt_thread_init(rt_thread_idle_entry)`**初始化空闲线程idle** - - `rt_system_scheduler_start()`**启动调度器(抢占优先级)** - -## 线程控制块 - -- 用于管理线程的一个数据结构,存放线程的一些信息 - - 优先级,线程名称,线程状态,线程间的链表结构,线程等待事件合集等。 - -- 由结构体`struct rt_thread`定义并形成线程内核对象,再链接到内核对象容器进行管理。 - -![对象容器与线程对象](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04Object_container.png) - -- 结构体`struct rt_thread`继承自基类`rt_object`,增加了线程状态、优先级等属性 - -#### 结构体 struct rt_thread - -```c -/* 线程控制块 */ -struct rt_thread -{ - /* 继承自内核对象 */ - struct rt_object parent; - - /* 栈指针与入口指针 */ - void *sp; /* 栈指针 */ - void *entry; /* 入口函数指针 */ - void *parameter; /* 参数 */ - void *stack_addr; /* 栈地址指针 */ - rt_uint32_t stack_size; /* 栈大小 */ - - /* 错误代码 */ - rt_err_t error; /* 线程错误代码 */ - - /* 调度上下文,包含线程状态、优先级、时间片等调度相关信息 */ - RT_SCHED_THREAD_CTX - - ...... - - struct rt_timer thread_timer; /* 内置线程定时器 */ - - rt_thread_cleanup_t cleanup; /* 线程退出清除函数 */ - struct rt_spinlock spinlock; /* 自旋锁 */ - rt_ubase_t user_data; /* 用户数据 */ -}; -``` - -### - -## 线程属性 - -### 线程栈 - -RT-Thread **线程具有独立的栈**,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。 - -线程栈还用来存放函数中的**局部变量**:函数中的局部变量从线程栈空间中申请;函数中局部变量初始时从寄存器中分配(ARM 架构),当这个函数再调用另一个函数时,这些局部变量将放入栈中。 - -对于线程第一次运行,可以以手工的方式构造这个上下文来设置一些初始的环境:入口函数(PC 寄存器)、入口参数(R0 寄存器)、返回位置(LR 寄存器)、当前机器运行状态(CPSR 寄存器)。 - -线程栈的增长方向是芯片构架密切相关的,RT-Thread 3.1.0 以前的版本,均只支持栈由高地址向低地址增长的方式,对于 ARM Cortex-M 架构,线程栈可构造如下图所示。 - -![线程栈 (ARM)](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_stack.png) - -### 线程状态 - -| 状态 | 描述 | -| -------- | :----------------------------------------------------------: | -| 初始状态 | 当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_INIT | -| 就绪状态 | 在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。此状态在 RT-Thread 中的宏定义为 RT_THREAD_READY | -| 运行状态 | 线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。此状态在 RT-Thread 中的宏定义为 RT_THREAD_RUNNING | -| 挂起状态 | 也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_SUSPEND | -| 关闭状态 | 当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_CLOSE | - -#### 状态切换 - -![线程状态转换图](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_sta.png) - -- **就绪 -> 挂起** - - `rt_thread_delay()`:延时,让出CPU - - `rt_sem_take()`:信号量获取 - - `rt_mutex_take()`:互斥量获取 - - `rt_mp_alloc()`:从内存池中申请一个内存块。 - - `rt_event_recv()`:接收事件集中的指定事件,并按照设定条件等待或返回。 - - `rt_mb_recv()`:从邮箱中接收一条消息 - - `rt_mq_recv()`:从消息队列中接收一条消息 -- **挂起 -> 就绪** - - `rt_thread_resume()`:恢复挂起线程,使其重新进入就绪状态。 - - `rt_sem_release()`:释放信号量,使信号量计数加一,并唤醒等待线程。 - - `rt_mutex_release()`:释放当前线程持有的互斥量。 - - `rt_mp_release()`:释放内存块,并将其归还到内存池。 - - `rt_event_send()`:发送指定事件,并唤醒满足条件的等待线程。 - - `rt_mb_send()`:向邮箱发送一条消息,邮箱已满时发送失败。 - - `rt_mq_send()`:向消息队列发送一条消息,队列已满时发送失败。 - -### 优先级 - -- RT-Thread 线程的优先级是表示线程被调度的优先程度。每个线程都具有优先级,**越重要的线程,赋予的优先级就越高**,线程被调度的可能性才会越大。 -- RT-Thread 最大支持 256 个线程优先级(0~255),**数值越小的优先级越高**,0 为最高优先级。 -- 在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置。 -- **最低优先级默认分配给空闲线程使用**,用户一般不使用。 -- 在系统中,如果有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程会优先**抢占**处理器运行。 - -### 时间片 - -每个线程都需要配置时间片,但时间片仅对**优先级相同的就绪态线程**有效。系统对优先级相同的就绪态线程采用**时间片轮转的调度方式**进行调度时,时间片起到**约束线程单次运行时长**的作用,其单位是一个系统节拍(OSTick)。 - -- 假设有 2 个优先级相同的就绪态线程 A 与 B,A 线程的时间片设置为 10,B 线程的时间片设置为 5,那么当系统中不存在比 A 优先级高的就绪态线程时,系统会在 A、B 线程间**来回切换执行**,并且每次对 A 线程执行 10 个节拍的时长,对 B 线程执行 5 个节拍的时长 - - ![相同优先级时间片轮转](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04time_slience.png) - -### 入口函数 -- 线程所实现的功能由入口函数实现。 -- 函数格式: - ``` - void (*entry)(void* parameter) - ``` - - - 函数要求:要有**让出 CPU** 的动作(如 `rt_thread_mdelay()`) - -``` -/* 线程1的入口函数 */ -static void thread1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - while (1) - { - /* 线程1采用低优先级运行,一直打印计数值 */ - rt_kprintf("thread1 count:%d\n", count++); - rt_thread_mdelay(500); - } -} -``` - -## 系统线程 - -### 空闲线程 - -空闲线程是系统创建的最低优先级的线程,线程状态**永远为就绪态**。当系统中无其他就绪态线程存在时,调度器将调度到空闲线程。它通常是一个**死循环**,且**永远不能被挂起**。 - -空闲线程在 RT-Thread 中有着特殊的用途: - -- **若某线程运行完毕,系统将自动删除线程**:自动执行 `rt_thread_exit()` 函数,先将该线程从系统**就绪队列中删除**,**再将该线程的状态更改为关闭状态**,不再参与系统调度,然后挂入 `rt_thread_defunct` 的僵尸队列(资源未回收、处于关闭状态的线程队列)中,最后由**空闲线程回收被删除线程的资源。** - -- 空闲线程也提供了接口来运行用户设置的**钩子函数**,在空闲线程运行时会调用该钩子函数,适合钩入功耗管理、看门狗喂狗等工作。 - -### 主线程 - -- 在系统启动时,系统会创建 main 线程,它的入口函数为` main_thread_entry()`,用户的应用入口函数 `main() `就是从这里真正开始的。 - -- 系统调度器启动后,main 线程就开始运行,过程如下图,用户可以在 main() 函数里添加自己的应用程序初始化代码。 - -![主线程调用过程](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04main_thread.png) - -## 线程相关API: - -### 线程的创建 - -#### 静态创建 - -```c -rt_thread_t rt_thread_create(const char* name, - void (*entry)(void* parameter), - void* parameter, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -- 线程句柄(线程控制块指针)、线程栈由用户提供,一般被设置为全局变量,在编译时就确定,内核不负责动态分配内存空间。 -- 用户提供的栈首地址需要做系统对齐(如ARM上需要做4字节对齐) - -| 参数 | 描述 | -| ----------- | ------------------------------------------------------------ | -| thread | 线程句柄。线程句柄由用户提供出来,并指向对应的线程控制块内存地址 | -| name | 线程的名称;线程名称的最大长度由 rtconfig.h 中定义的 RT_NAME_MAX 宏指定,多余部分会被自动截掉 | -| entry | 线程入口函数 | -| parameter | 线程入口函数参数 | -| stack_start | 线程栈起始地址 | -| stack_size | 线程栈大小,单位是字节。在大多数系统中需要做栈空间地址对齐(例如 ARM 体系结构中需要向 4 字节地址对齐) | -| priority | 线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0~255,数值越小优先级越高,0 代表最高优先级 | -| tick | 线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行 | -| **返回** | **——** | -| RT_EOK | 线程创建成功 | -| -RT_ERROR | 线程创建失败 | - -#### 动态创建 - -```c -rt_thread_t rt_thread_create(const char* name, - void (*entry)(void* parameter), - void* parameter, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -- 调用这个函数时,系统会从**动态堆内存**中分配一个线程句柄以及按照参数中指定的栈大小相应的空间。 - -- 分配出来的栈空间是按照 `rtconfig.h` 中配置的 `RT_ALIGN_SIZE` 方式对齐。 - -### 线程启动 - -创建(初始化)的线程状态处于初始状态,并未进入就绪线程的调度队列,可以在线程初始化 / 创建成功后调用下面的函数接口让该线程进入就绪态: - -```c -rt_err_t rt_thread_startup(rt_thread_t thread); -``` - -- 当调用这个函数时,将**把线程的状态更改为就绪状态**,并放到相应优先级队列中等待调度。 - -- 如果新启动的线程优先级比当前线程优先级高,将立刻切换到这个线程。 - -| **参数** | **描述** | -| --------- | ------------ | -| thread | 线程句柄 | -| **返回** | —— | -| RT_EOK | 线程启动成功 | -| -RT_ERROR | 线程启动失败 | - -### 线程初始化示例 - -创建线程栈、线程句柄、线程入口函数 --> 初始化线程 --> 启动线程 - -``` -/* 线程栈与线程句柄由用户提供,并做对齐 */ -ALIGN(RT_ALIGN_SIZE) -static char thread2_stack[1024]; -static struct rt_thread thread2; - -/* 线程 2 入口 */ -static void thread2_entry(void *param) -{ - rt_kprintf("thread2\n"); -} - -int thread_init_sample(void) -{ - /* 初始化线程 2,名称是 thread2, - 入口是 thread2_entry,优先级 20,时间片 10 */ - rt_thread_init(&thread2, - "thread2", - thread2_entry, - RT_NULL, - &thread2_stack[0], - sizeof(thread2_stack), - 20, - 10); - - rt_thread_startup(&thread2); - return 0; -} -``` - -### 线程延时-让出CPU资源 - -抢占式系统线程在不使用 CPU 时需要让出 CPU 的使用权,让其他线程得以运行。 - -可以使用**线程延时**或睡眠来实现。 - -使用以下 API 让运行的当前线程**延迟/休眠**一段时间,**在指定的时间到达后重新运行:** - -``` -rt_err_t rt_thread_delay(rt_tick_t tick); // 以 1 个 OS Tick 为单位 -rt_err_t rt_thread_mdelay(rt_int32_t ms); // 以 ms 为单位 -``` - -## Scons构建系统 - -- 使大家从复杂的 Makefile 配置、IDE 配置中脱离出来,把精力集中在 RT-Thread 功能开发上 -- 语法:Python -- 采用不同于通常 Makefile 文件的方式,而是使用 SConstruct 和 SConscript 文件来替代 -- 一个项目 (BSP) 只有一 SConstruct,但是会有多个 SConscript。一般情况下,每个存放有源代码的子目录下都会放置一个 SConscript。 -- RT-Thread 为每个 BSP 单独创建了一个名为 `rtconfig.py `的配置文件 - -### 脚本结构 - -```txt -/ - -- rtconfig.py ---- 控制SCons构建的配置文件,存放了如工具链,构建参数等配置。 - -- SConscript - -- SConstruct ---- SCons的入口脚本,初始化了SCons构建rt-thread所需的必要环境 - -- Kconfig ---- 顶层Kconfig文件,menuconfig的入口Kconfig文件 - -- rt-thread/ - --- src/ - ---- SConscript ---- 各级源码的scons子脚本,控制当前级别下的源码构建行为 - ---- Kconfig ---- 各级源码的Kconfig子脚本,存放当前级别下的配置项 - ---- **.c -``` - -- SCons的入口脚本`SConstruct` -- 分散在各级源码的SCons子脚本`SConscript` -- 还有一份便于配置构建行为的配置脚本`rtconfig.py` - -### SCons基本命令 - -![构建配置系统](https://www.rt-thread.org/document/site/development-tools/build-config-system/figures/buildconfig19.png) - -https://www.rt-thread.org/document/site/#/development-tools/build-config-system/SCons?id=_3-scons-%e5%9f%ba%e6%9c%ac%e5%91%bd%e4%bb%a4 - -- 如果是 ARM 平台的芯片,输入 scons 命令直接编译 BSP - -- 或者 BSP 为非 ARM 平台的芯片,那么不能直接使用 scons 命令编译工程,需要自己安装对应的编译器,并且指定使用的编译器路径。 - -- 在编译工程前,可以在 Env 命令行界面使用下面的 2 个命令指定编译器为 MDK 和编译器路径为 MDK 的安装路径: - - - ```c - set RTT_CC=keil - set RTT_EXEC_PATH=C:/Keilv5 - ``` - - - ``` - set RTT_CC=gcc - set RTT_EXEC_PATH=D:\software\RaspberryPi-Pico\gcc\2020-q4-major\bin - ``` - -- `scons` - - - 编译工程 - - - 如果执行过 `scons` 命令后修改了一些源文件,再次执行 scons 命令时,则 SCons 会进行增量编译,仅编译修改过的源文件并链接。 - -- `scons -s` - - 和 scons 命令不同的是此命令不会打印具体的内部命令。 - -- `scons -c` - 清除编译生成的临时文件和目标文件。 - -- `scons --target=XXX` - - - 这个命令后面同样可以增加一个 -s 参数,如命令 `scons –target=mdk5 -s`,执行此命令时不会打印具体的内部命令。 - - - ```c - //生成keil-MDK/IAR工程 - scons --target=iar - scons --target=mdk4 - scons --target=mdk5 - ``` - - - ```c - //生成Cmake/makefile工程 - scons --target=cmake - Scons --target=makefile - ``` - - - ```c - //生成VisualStudio工程 - scons --target=vs2012 - Scons --target=vs2005 - ``` - - - 如果 BSP 目录下提供其他 IDE 工程的模板文件也可以使用此命令生成对应的新工程,比如 ua、vs、cb、cdk。 - -- `scons -jN` - 使用多线程编译,提高编译速度。 - -- `scons --dist` - 打包 - -- `scons --dist-idle` - - 将该BSP导出一份可以被 RT-Thread Studio 导入的工程文件夹,RT-Thread Studio 已经内置该功能,已无需手动敲这个命令。 - -### Scons函数 - -#### Scons原生函数 - -1. `Import(vars)` - 导入其他 SCons 脚本导出的变量。 - -2. `Export(vars)` - 导出变量,供其他 SCons 脚本通过 `Import()` 使用。 - -3. `SConscript(scripts, exports, variant_dir, duplicate)` - 读取并执行指定的 `SConscript` 文件,可设置导出变量、目标文件目录及是否复制源文件。 - - | **参数** | **描述** | - | ----------- | ----------------------------------------------- | - | dirs | 导入的SConscript,路径+名称 | - | exports | 导出一个变量(可选) | - | variant_dir | 指定生成的目标文件的存放路径(可选) | - | duiplicate | 设定是否拷贝或链接源文件到 variant_dir (可选) | - -4. `Glob(pattern)` - 获取符合指定模式的源文件列表。 - - ``` - src = Glob('*.c') - ``` - -5. `IsDefined(depend)` - 判断指定的宏或宏列表是否已定义。 - -6. `Split(str)` - 将字符串分割成列表。 - - ``` - src = Split('shell.c msh.c') - ``` - -#### RT-Thread 自定义 Scons 函数 - -1. `SrcRemove(src, remove)` - 从构建列表中移除指定的源文件。 -2. `DefineGroup(name, src, depend, **parameters)` - 定义参与构建的 Group,可设置依赖宏、头文件路径、宏定义、编译参数和静态库等选项。 - - `CCFLAGS`:C/C++ 公共编译参数。 - - `CFLAGS`:C 文件编译参数。 - - `CXXFLAGS`:C++ 文件编译参数。 - - `CPPPATH`:头文件路径。 - - `CPPDEFINES`:编译宏定义。 - - `LIBRARY`:是否构建为静态库。 -3. `GetCurrentDir()` - 获取当前 `SConscript` 脚本所在目录。 -4. `GetDepend(depend)` - 判断指定的依赖宏是否已定义。 -5. `AddDepend(option)` - 添加一个宏定义。 -6. `GetConfigValue(name)` - 获取指定配置宏的值。 -7. `GetVersion()` - 获取 RT-Thread 版本信息。 -8. `GlobSubDir(sub_dir, ext_name)` - 获取指定目录及其子目录中符合扩展名要求的文件。 -9. `BuildPackage(package)` - 根据指定的 JSON 文件定义一个 Group。 - -#### SConscript示例 - -##### 构建一个基本的group - -```python -# 导入RT-Thread的自定义构建函数,几乎每一个RT-Thread的Sconscript都需要这么做。 -# 只有导入了building模块,才可以使用2.2.2介绍的RT-Thread自定义Scons函数 -from building import * - -cwd = GetCurrentDir() # 获取当前脚本的路径 -CPPPATH = [cwd] # 将当前路径加入构建搜索的头文件路径 -src = Split(''' -shell.c -msh.c -''') - -#也可以等效成:src = ['shell.c', 'msh.c'] - -if GetDepend('MSH_USING_BUILT_IN_COMMANDS'): # 判断是否启用MSH的内建命令 - src += ['cmd.c'] - -if GetDepend('DFS_USING_POSIX'): # 判断是否启用文件系统的POSIX接口 - src += ['msh_file.c'] - -# 使用DefineGroup创建一个名为Finsh的组 -# 该Group是否被添加到工程中,参与编译,取决于depend的宏是否在Kconfig中被使能。 -# 即上述的头文件路径和C文件是否被编译,取决于用户在Kconfig中是否使能了RT_USING_FINSH -group = DefineGroup('Finsh', src, depend = ['RT_USING_FINSH'], CPPPATH = CPPPATH) - -Return('group') # 将当前脚本指定的构建对象返回上级SCons脚本 -``` - -注: - -1. src 和 depends参数是必选项,即便你不想添加任何C文件,也需要定义一个空的列表。例如: - - ```python - src = [] - group = DefineGroup('Finsh', src, depend = [''], CPPPATH = CPPPATH) 复制错误复制成功 - ``` - -2. 头文件路径的参数不是必选项,如果不想添加头文件目录,就不需要填写 `CPPPATH` 参数 - -3. 最后一行使用 DefineGroup 创建一个名为 Finsh 的组,这个组也就对应 MDK 或者 IAR 中的分组。这个组的源代码文件为 src 指定的文件,如果 depend 为空表示该组不依赖任何 rtconfig.h 的宏。 - -4. `CPPPATH =CPPPATH` 表示将当前路径添加到系统的头文件路径中。左边的 CPPPATH 是 DefineGroup 中内置参数,表示头文件路径。右边的 CPPPATH 是本文件上面一行定义的。这样我们就可以在其他源码中引用 drivers 目录下的头文件了。 - -5. 如果有其他 Sconscript 文件也创建了相同的组名(例如,都叫 `Finsh`),Scons 会自动将这两个组的信息并入到一起。 - -##### 桥接文件 - -让 Scons 继续读取其子文件夹内的 Sconscript 脚本,进而继续构建工程 - -```python -# RT-Thread building script for bridge - -import os # 使用Python标准库,用于文件以及路径相关的操作 -from building import * - -cwd = GetCurrentDir() -objs = [] -list = os.listdir(cwd) - -for d in list: - path = os.path.join(cwd, d) - if os.path.isfile(os.path.join(path, 'SConscript')): - objs = objs + SConscript(os.path.join(d, 'SConscript')) - -Return('objs') -``` - -##### 全局宏定义的定义 - -即便不引用任何 RT-Thread 的头文件,也可以使用直接使用 `__RTTHREAD__` 这个宏定义。 - -```python -from building import * - -src = ['ipc.c'] # 添加C文件 - -CPPDEFINES = ['__RTTHREAD__'] # 全局宏定义 - -# depend 为空表示该组不依赖任何 rtconfig.h 的宏 -group = DefineGroup('Kernel', src, depend = [''], CPPDEFINES = CPPDEFINES) - -Return('group') -``` \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\346\234\261\347\233\233\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\346\234\261\347\233\233\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2541\347\273\204/\346\234\261\347\233\233\346\264\213/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\346\234\261\347\233\233\346\264\213/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 34cb6dfd9527fe7b111fc3e0a6e0c3f5f62ff517..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\234\261\347\233\233\346\264\213/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,133 +0,0 @@ -# Day1 学习笔记 - -## 一、夏令营课程概况 - -本次为夏令营第一学习日,学习模式为上午授课、下午实操练习。每日需要使用 Markdown 完成笔记与作业,通过 Git 以 PR 形式提交到仓库,小组长负责审核。 - -> 今日待办:搭建 RT‑Thread 开发环境,掌握 Markdown 基础,熟悉 Git 常用命令,完成 STM32F407 HelloWorld 工程。 - -## 二、RT‑Thread 基础认知 - -RT‑Thread 是国产开源实时操作系统,已经发展近 20 年。主要分为三个版本: - -- **Nano**:轻量极简内核,适合资源较小的设备,Apache 开源协议; -- **标准版**:组件丰富,支持 POSIX 接口,网络、日志、IoT 中间件齐全; -- **Smart**:面向带 MMU 硬件,内核态用户态隔离,安全性更高。 -- ![image-20260817220759032](../../../../../../figures/image-20260817220759032.png) - -## 三、RT‑Thread Studio 环境搭建 - -RT‑Thread Studio 是官方推出的一站式开发 IDE,能够极大简化 RT‑Thread 工程创建、配置、编译调试流程。环境搭建的完整步骤如下: - -1. 安装 RT‑Thread Studio 软件,按照教程完成软件配置; -2. 打开 SDK Manager 工具,下载指定版本 RT‑Thread 源码,同时安装 STM32F4 系列芯片支持包; -3. 将 STM32F407 开发板通过 ST‑Link 调试器连接电脑,打开设备管理器确认电脑可以正常识别 ST‑Link 驱动,硬件链路无异常; -4. 在 IDE 内新建工程,芯片选型选择 STM32F407,内核选择标准版,串口参数设置波特率`115200`,数据位 8,停止位 1,无校验; -5. 工程创建完成后执行编译,编译成功会生成`.bin`和`.elf`两种格式固件文件; -6. 使用 ST‑Link 将固件烧录下载到开发板; -7. 打开串口终端工具,观察 RT‑Thread 开机启动日志,验证程序正常运行,同时可以使用断点调试功能,进行代码调试。 - -实操过程中会遇到一些常见问题:使用 ST‑Link 烧录的时候,如果提示固件版本过低报错,可以尝试更新 ST‑Link 固件,多使用不同工具尝试更新,规避工具版本不兼容问题。程序跑起来之后,在 msh 交互终端可以执行很多系统调试命令:`free`查看系统内存占用情况,`list_thread`查看全部线程,还有查看信号量、事件、互斥锁、定时器等命令,方便我们调试内核运行状态。我们主要修改`applications/main.c`,编写业务逻辑,例如循环打印 Hello - -> 注意:ST‑Link 烧录报错,可尝试更新固件版本。 -> -> ![image-20260817220814101](../../../../../../figures/image-20260817220814101.png) -> -> ![image-20260817220741126](../../../../../../figures/image-20260817220741126.png) - -## 四、Markdown 笔记书写要点 - -日常笔记统一使用 Markdown 格式: - -- 标题使用`#`、`##`分级; - -- 代码片段用 ` `包裹,可标注语言实现高亮; - -- 行内代码使用 ``代码``; - -- 图片建议存放在工程内`figures`文件夹,使用相对路径,防止迁移丢失; - -- 支持表格、加粗`**文字**`等格式。 - - # 例:一级标题 - - ## 二级标题 - - ### 三级标题 - - .... - - 代码段 - - ```c - #include - - int main() - { - return 0; - } - ``` - - 表格 - - | t1 | t2 | - | ---- | ---- | - | 1 | 2 | - - `高亮语法` - - **加粗字体** - -## 五、Git 版本控制与协作 - -### 1. 基础配置 - -安装 Git 后,先配置全局用户名邮箱,生成 SSH 密钥,把公钥配置到 Gitee 实现免密推送。 - -![image-20260817220914333](../../../../../../figures/image-20260817220914333.png) - -bash - -``` -git config --global user.name "自己名字" -git config --global user.email "自己邮箱" -``` - -![image-20260817220904172](../../../../../../figures/image-20260817220904172.png) - -### 2. 基础工作流程 - -工作区修改文件 → `git add .`加入暂存区 → `git commit -m "提交说明"`本地提交 → `git push`推送到远端。 - -### 3. Fork‑PR 协作流程 - -1. 将上游仓库 Fork 到个人账号; -2. **不要直接修改 master 分支**,新建独立功能分支进行编写; -3. 完成修改推送个人仓库后,网页端创建 PR 提交给上游; -4. 若出现代码冲突,同步上游最新代码,手动处理冲突后再次提交推送。 - -常用简短命令: - -bash - -``` -git init -git clone 仓库地址 -git checkout -b day1 -git pull -git fetch upstream -git remote add upstream 上游仓库地址 -``` - -![image-20260817220952751](../../../../../../figures/image-20260817220952751.png) - - - -## 六、今日课堂任务 - -1. 创建 qemu‑a9 工程,运行 RT‑Thread; -2. 练习 Markdown 语法; -3. 新建 STM32F407ZG 的 RT‑Thread4.1.1 工程; -4. 完成 Git 环境配置,练习分支操作,理解 PR 提交流程。 - -![image-20260817221014871](../../../../../../figures/image-20260817221014871.png) \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 05259d6c3513221fd52f4b441c217df2c5dab96f..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,253 +0,0 @@ -# 【RSOC26】第2天作业 - -> 提交人:李子夜(第1组)|Gitee:@CyberSprout|日期:2026-08-19 - ---- - -## 一、实际环境 - -| 项目 | 值 | -|---|---| -| 开发板 | 星火一号(STM32F407ZG,Cortex-M4,168MHz) | -| RT-Thread 版本 | 4.1.1(sdk-bsp-stm32f407-spark) | -| 工具链 | GCC(ARM Embedded) | -| 调试接口 | SWD / ST-Link(Studio 配置) | -| 内核配置关键项 | `RT_THREAD_PRIORITY_MAX=32`,`RT_TICK_PER_SECOND=1000` | -| 工程路径 | `C:\RT-ThreadStudio\workspace\qemu-a9\` | - -> **注意**:工程目录名为 `qemu-a9`,但实际目标为 STM32F407ZG + STM32 HAL + ST-Link,与目录名不符。不能在 QEMU 中运行。 - ---- - -## 二、线程创建与启动(静态初始化路径) - -### 2.1 内核自带线程的创建方式 - -本工程中运行时存在的三个线程全部使用 **静态初始化**(`rt_thread_init`)创建: - -#### tidle0 —— idle 线程 - -**源码位置**:`rt-thread/src/idle.c`,第 316–330 行 - -```c -rt_thread_init(&idle[i], - tidle_name, - rt_thread_entry, - RT_NULL, - &rt_thread_stack[i][0], - sizeof(rt_thread_stack[i]), - RT_THREAD_PRIORITY_MAX - 1, /* 31 */ - 32); /* 时间片 */ -rt_thread_startup(&idle[i]); -``` - -- 初始化时**不分配内存**,`idle[]` 和 `rt_thread_stack[][]` 已经在 BSS 段声明 -- 优先级 31(最低),时间片 32 tick -- 名字格式为 `tidle0`(第 i 个 CPU 对应 `tidle{i}`) - -#### main —— 用户主线程 - -**源码位置**:`rt-thread/src/components.c` - -```c -tid = rt_thread_create("main", main_thread_entry, RT_NULL, - RT_MAIN_THREAD_STACK_SIZE, /* 2048 */ - RT_MAIN_THREAD_PRIORITY, /* 10 */ - 20); /* 时间片 */ -rt_thread_startup(tid); -``` - -- 使用 **动态创建**(堆上分配) -- 优先级 10,高于 tshell(20),在当前线程中优先级最高 -- 时间片 20 tick - -#### tshell —— finsh 命令行线程 - -- 由 finsh 组件初始化时创建(优先级 20,栈 4096) -- 等待 UART 输入,解析后启动了对应线程执行 - -### 2.2 静态 vs 动态初始化对比 - -| 维度 | 静态 `rt_thread_init` | 动态 `rt_thread_create` | -|---|---|---| -| 内存来源 | 外部传入(BSS/数据段) | 堆分配 | -| 创建后状态 | `INIT` | `INIT` | -| 启动方式 | `rt_thread_startup()` | `rt_thread_startup()` | -| 适合场景 | 内核线程(idle、system) | 应用线程 | -| 失败模式 | 传入 NULL 时断言 | 返回 `RT_NULL` | - ---- - -## 三、线程休眠与延迟 - -### 3.1 三种延时 API - -| 函数 | 单位 | 本质 | -|---|---|---| -| `rt_thread_delay(ms)` | 毫秒 | `sleep(ms * RT_TICK_PER_SECOND / 1000)` | -| `rt_thread_mdelay(ms)` | 毫秒 | 循环调用 `rt_hw_us_delay`(阻塞 CPU) | -| `rt_thread_delay_until(previous_tick, ms)` | 毫秒 | 绝对时间唤醒,消除累积误差 | - -### 3.2 本工程中的实际使用 - -用户代码(`applications/main.c`): - -```c -rt_pin_write(PIN_LED_R, PIN_LOW); -rt_thread_mdelay(500); -rt_pin_write(PIN_LED_R, PIN_HIGH); -rt_thread_mdelay(500); -count++; -``` - -- LED 亮/灭各 500ms,周期 1s -- 低电平亮(`PIN_LOW`),高电平灭 -- `rt_thread_mdelay` 忙等待,期间 CPU 不释放给其他线程 - -> **问题**:用 `rt_thread_delay(500)` 替代 `rt_thread_mdelay(500)` 可以释放 CPU 给 tshell 和 idle,但当前代码用了忙等待版本。 - ---- - -## 四、线程挂起与恢复 - -### 4.1 本工程中的 `rt_thread_suspend` - -**调用位置**:`thread.c` 第 840 行 - -**关键约束**(4.1.1): - -```c -RT_ASSERT(thread == rt_thread_self()); -``` - -即:**当前版本的 RT-Thread 只允许挂起自身**。不能跨线程挂起。 - -恢复操作无此限制: - -```c -rt_thread_resume(thread); /* 任何线程/ISR 都可以调用 */ -``` - -### 4.2 场景推导 - -如果用户代码中调用: - -```c -rt_thread_suspend(rt_thread_self()); -``` - -- 当前线程状态变为 `SUSPEND` -- 触发调度,最高优先级的 READY 线程获得 CPU -- 必须在另一个线程或 ISR 中调用 `rt_thread_resume()` 才能恢复,否则线程休眠 -- `remaining_tick > 0` 时,超时也会自动恢复 - ---- - -## 五、线程删除与延迟回收 - -### 5.1 删除流程源码 - -`rt_thread_delete(rt_thread_self())` 执行以下步骤: - -```c -/* thread.c 第 499 行附近 */ -RT_ASSERT(thread == rt_thread_self()); - -thread->stat = RT_THREAD_CLOSE; -/* 挂入 defunct 链表 */ -rt_list_insert_after(&_rt_thread_defunct, &thread->tlist); -/* 触发调度 */ -rt_schedule(); -``` - -**实际内存释放**不在此刻发生,而是由 idle 线程代为执行: - -```c -/* idle.c 第 160-170 行 */ -while (1) { - thread = rt_list_entry(_rt_thread_defunct.next, struct rt_thread, tlist); - if (&thread->tlist == &_rt_thread_defunct) break; - rt_list_remove(&thread->tlist); - if (rt_object_is_systemobject((rt_object_t)thread)) - rt_memset(thread, 0, sizeof(struct rt_thread)); - else - rt_free(thread->stack_addr); -} -``` - -### 5.2 实践要点 - -1. `rt_thread_delete` 后线程**不会立刻消失**,在 `list_thread` 中仍然可见(状态 `close`) -2. 真实释放等 idle 线程调度时执行 -3. 若高优先级线程持续占用 CPU,idle 永远得不到时间片,defunct 队列堆积,堆内存泄漏 - ---- - -## 六、API 使用总结 - -本 Day2 涉及的线程 API 路径追踪: - -| API | 头文件 | 源码位置 | 本工程使用情况 | -|---|---|---|---| -| `rt_thread_init` | `rtthread.h` | `thread.c:297` | idle/tsystem 使用 | -| `rt_thread_create` | `rtthread.h` | `thread.c:453` | main 线程使用 | -| `rt_thread_startup` | `rtthread.h` | `thread.c:355` | 启动 main/tidle0 | -| `rt_thread_delete` | `rtthread.h` | `thread.c:499` | 可用,需自删除 | -| `rt_thread_suspend` | `rtthread.h` | `thread.c:840` | 仅限自挂起 | -| `rt_thread_resume` | `rtthread.h` | `thread.c:885` | 未在用户代码中使用 | -| `rt_thread_delay` | `rtthread.h` | `thread.c:613` | 未在用户代码中使用 | -| `rt_thread_mdelay` | `rtthread.h` | `thread.c:692` | main.c LED 循环中使用 | -| `rt_thread_yield` | `rtthread.h` | `thread.c:545` | 时间片耗尽时调度器自动调用 | -| `list_thread` | `finsh.h` | `cmd.c:166` | msh 命令 | - ---- - -## 七、思考题 - -### Q1:为什么 `rt_thread_suspend` 只能挂起自己? - -从源码看,`thread.c` 第 840 行有硬性断言: - -```c -RT_ASSERT(thread == rt_thread_self()); -``` - -**分析**: -- RT-Thread 4.x 的设计目标之一是轻量,跨线程挂起需要额外的同步机制(如 mutex)来避免竞态 -- 如果线程 A 挂起线程 B,而 B 正好持有一个 mutex,A 也会被阻塞,形成死锁 -- 限制为自挂起是最安全的做法,用户代码应在自身逻辑中控制流程 -- RT-Thread 5.x 起放宽了此限制,但 4.1.1 保留此约束 - -### Q2:`rt_thread_mdelay` 和 `rt_thread_delay` 的区别对系统的影响 - -- `rt_thread_mdelay`:忙等待,不释放 CPU。在整个延时期间当前线程持续占用 CPU,其他同优先级的线程无法运行 -- `rt_thread_delay`:挂起当前线程,让出 CPU。调度器切换到最高优先级的 ready 线程 -- 在本工程 main 线程(优先级 10)中使用 `rt_thread_mdelay` 没有问题,因为它是用户线程中优先级最高的,不释放 CPU 不会饿死其他用户线程 -- 但如果在优先级低于 tshell(20) 的线程中使用 `rt_thread_mdelay`,tshell 依然可以运行 - -### Q3:栈溢出时为什么系统 hang 而不复位? - -源码 `scheduler.c` `_scheduler_stack_check`: - -```c -rt_base_t level = rt_hw_interrupt_disable(); -while (level); // 死循环 -``` - -- 关中断后进入 `while(1)`,不调用任何 fault handler -- Bootloader 没有被触发,因为程序仍在运行——只是停了 -- 原因:RT-Thread 设计者认为栈溢出是**严重错误**,停止比继续运行更安全;且 HardFault 可能在栈溢出时已损坏异常栈,进入 fault handler 本身也会崩溃 -- 但对于开发者来说,调试时现象是系统完全无响应,必须通过 J-Link / ST-Link 抓取 SP 和 PC 判断 - ---- - -## 八、待完成 - -- [ ] 烧录到开发板并通过 ST-Link 连接 -- [ ] 在 msh 中执行 `list_thread`,核验线程优先级与状态 -- [ ] 替换 `rt_thread_mdelay` 为 `rt_thread_delay`,观察 tshell 是否可响应 -- [ ] 社区论坛发布文档(需要手动发帖) - ---- - -*注:本次作业涉及的所有代码路径、行号、宏值均来自 `C:\RT-ThreadStudio\workspace\qemu-a9\` 中的 RT-Thread 4.1.1 实际源码,在 Studio 中已验证编译通过。未上板项如实标注为待完成,未填写未实际产生的运行输出。* diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 04aecf8d7692b4258090501dc9215aed2da1e8f7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,342 +0,0 @@ -# 【RSOC26】第一天作业 - -> 提交人:李子夜(第 1 组)|Gitee:@CyberSprout|日期:2026-08-18 - -本次作业记录 Day1 的**工程管理实操过程**,所有命令、SHA、PR 编号均为真实执行结果,可在仓库中核验。 - ---- - -## 一、Git 协作环境搭建(已完成) - -### 1.1 确认上游仓库的约束 - -动手前先确认权限边界,避免走错路径: - -```bash -# 查看自己对上游仓库的权限 -curl "https://gitee.com/api/v5/repos/rtthread/rsoc-rtt?access_token=" -# → pull: True push: False admin: False - -# 查看 master 分支保护状态 -curl "https://gitee.com/api/v5/repos/rtthread/rsoc-rtt/branches/master?access_token=" -# → protected: True -``` - -**结论**:`push: False` + `master` 受保护 → 不存在直接推送通道,唯一路径是 **Fork → 分支 → PR**。 - -### 1.2 Fork 与克隆 - -```bash -# Fork(通过 API,等价于网页点 Fork) -curl -X POST "https://gitee.com/api/v5/repos/rtthread/rsoc-rtt/forks" -d "access_token=" -# → HTTP 201,得到 https://gitee.com/CyberSprout/rsoc-rtt - -# 克隆自己的 Fork(不是上游) -git clone https://gitee.com/CyberSprout/rsoc-rtt.git -cd rsoc-rtt - -# 挂载上游,用于后续同步 -git remote add upstream https://gitee.com/rtthread/rsoc-rtt.git -``` - -验证结果: - -``` -origin https://gitee.com/CyberSprout/rsoc-rtt.git (fetch/push) -upstream https://gitee.com/rtthread/rsoc-rtt.git (fetch/push) -``` - -### 1.3 提交身份核对(发现并修复了一个真实问题) - -查询 Gitee 账号实际使用的提交邮箱: - -```bash -curl "https://gitee.com/api/v5/emails?access_token=" -# → [{"email":"cybersprout520@gmail.com","state":"confirmed", -# "scope":["primary","committed","secure","notified"]}] -``` - -而本机原有配置是: - -```bash -$ git config --global user.email -liziye627@gmail.com # ← 与 Gitee 的 committed 邮箱不一致 -``` - -**问题**:两者不一致时,提交在 Gitee 页面上会显示为无头像、不可点击的匿名用户,贡献统计不计入本人。 - -**修复**: - -```bash -git config --global user.name "CyberSprout" -git config --global user.email "cybersprout520@gmail.com" -``` - -修复后验证: - -``` -$ git log -1 --format='%an <%ae>' -CyberSprout -``` - -### 1.4 Windows + 中文路径配置(实测) - -本仓库路径全为中文(`2026/第1组/李子夜/笔记/`),需要额外配置。 - -**① `core.quotepath` —— 中文文件名显示为八进制转义** - -```bash -# 默认(未设置,等效 true) -$ git status --short -?? "\347\254\2541\347\273\204/" - -# 设为 false 后 -$ git status --short -?? 第1组/ -``` - -修复: - -```bash -git config --global core.quotepath false -git config --global i18n.logOutputEncoding utf-8 -``` - -**② `core.autocrlf` —— 换行符转换** - -```bash -$ git config --show-origin --get-all core.autocrlf -file:C:/Program Files/Git/etc/gitconfig true -``` - -Git for Windows 安装默认值为 `true`(入库转 LF、签出转 CRLF)。本次每次 `git add` 都出现提示,属预期行为: - -``` -warning: in the working copy of '2026/第1组/李子夜/笔记/第一天笔记.md', -LF will be replaced by CRLF the next time Git touches it -``` - -**③ `core.longpaths`** —— 本机已为 `true`,中文长路径未触发 260 字符限制。 - -### 1.5 认证方式选择(SSH 不可用,改用 HTTPS) - -先尝试 SSH: - -```bash -$ ls ~/.ssh/*.pub -(无 .pub 文件) - -$ ssh -T git@gitee.com -git@gitee.com: Permission denied (publickey). -``` - -本机无密钥对,SSH 通道不可用。检查凭证助手: - -```bash -$ git config --show-origin --get-all credential.helper -file:C:/Program Files/Git/etc/gitconfig manager -``` - -Git Credential Manager 可用,因此本次采用 **HTTPS + 令牌** 方式推送,并确认令牌未落盘: - -```bash -$ grep -c "" .git/config -0 -``` - ---- - -## 二、按要求提交两个 PR - -Day1 要求:**至少两个 PR,第一次初始化模板,第二次提交作业和笔记内容。** - -### 2.1 一次流程错误及其纠正(记录在案) - -首次提交时未拆分,一个 PR 直接提交了完整笔记内容: - -| PR | 标题 | 结果 | -|---|---|---| -| [#104](https://gitee.com/rtthread/rsoc-rtt/pulls/104) | 第一课笔记(完整内容) | **已关闭** —— 不符合两阶段要求 | - -处理方式:在 PR 下留言说明原因后关闭,未删除分支、未改写历史,保留完整痕迹。 - -> 这与笔记中「撤销已公开的操作应留痕,不应抹除」的原则一致:关闭 + 说明,而非删除重来。 - -### 2.2 PR 一:初始化模板 - -```bash -git fetch upstream -git switch -c day1/init upstream/master -mkdir -p "2026/第1组/李子夜/笔记" "2026/第1组/李子夜/作业" -# 写入两个模板文件 -git add "2026/第1组/李子夜" -git diff --cached --stat -git commit -m "docs(第1组/李子夜): 初始化个人目录模板" -git push origin day1/init -``` - -| 项 | 值 | -|---|---| -| 分支 | `day1/init` | -| 提交 | `e9e66ae` | -| 变更 | 2 files changed, 26 insertions(+) | -| PR | [#107](https://gitee.com/rtthread/rsoc-rtt/pulls/107) | - -### 2.3 PR 二:提交作业与笔记内容 - -```bash -git switch -c day1/content day1/init -# 写入笔记与作业正式内容 -git add "2026/第1组/李子夜" -git commit -m "docs(第1组/李子夜): 提交第一天笔记与作业内容" -git push origin day1/content -``` - -分支链(基于 PR 一,形成 stacked PR): - -``` -* 24142c9 docs(第1组/李子夜): 提交第一天笔记内容 - 使用 Git 管理工程 -* e9e66ae docs(第1组/李子夜): 初始化个人目录模板 -``` - -### 2.4 目录结构(符合 README 规范) - -``` -2026/第1组/李子夜/ -├── 笔记/ -│ └── 第一天笔记.md -└── 作业/ - └── 第一天作业.md -``` - ---- - -## 三、过程中排查的一个编码问题 - -通过 API 创建 PR 时,标题与正文中的中文在 Gitee 上存成了乱码。 - -**错误做法**: - -```bash -curl -X POST ".../pulls" --data-urlencode "title=【RSOC26】..." -``` - -`--data-urlencode` 发送的是 `application/x-www-form-urlencoded` 且未声明 charset,服务端按非 UTF-8 解码。 - -**验证手段**(关键:不能只看终端,终端本身可能是编码问题):把 API 返回写入 UTF-8 文件再读回,确认服务端存储的确实是乱码,而非本地显示问题。 - -**修复做法**:改用 JSON + 显式声明字符集,请求体从文件读入以绕开 shell 编码: - -```bash -curl -X PATCH ".../pulls/104" \ - -H "Content-Type: application/json; charset=utf-8" \ - --data-binary "@body.json" -``` - -修复后读回确认: - -``` -TITLE : 【RSOC26】第1组 李子夜 - 第一课笔记:如何使用 Git 去管理你的工程 -``` - -对照组:同一 shell 下 `git commit -m "docs(第1组/李子夜): ..."` 的中文全程正常,说明问题不在 Git 也不在终端,仅在 curl 的表单编码路径上。 - ---- - -## 四、RT-Thread 源码与工具获取途径(已核实) - -以下渠道均已实测确认可用。 - -### 4.1 源码仓库 - -| 来源 | 地址 | 实测 | -|---|---|---| -| Gitee 镜像(国内推荐) | `https://gitee.com/rtthread/rt-thread` | 5532 star,43 个 tag | -| GitHub 主仓库 | `https://github.com/RT-Thread/rt-thread` | 12158 star | -| 最新正式版本 | **v5.2.2**(2025-10-31 发布) | 两处一致 | - -```bash -# 国内拉取走 Gitee 镜像,速度显著更快 -git clone https://gitee.com/rtthread/rt-thread.git - -# 只要最新一层历史,减少下载量 -git clone --depth 1 https://gitee.com/rtthread/rt-thread.git - -# 切到指定发布版本 -git clone https://gitee.com/rtthread/rt-thread.git -cd rt-thread -git checkout v5.2.2 -``` - -### 4.2 IDE 与工具链 - -| 工具 | 获取方式 | 说明 | -|---|---|---| -| RT-Thread Studio | 官网下载页 `https://www.rt-thread.org/download.html`(实测 HTTP 200) | 图形化 IDE,内置 SDK Manager 按 BSP 下载内核与芯片支持包 | -| ENV 工具 | 同上下载页 | 命令行配置 + `scons` 构建,配合 `menuconfig` 裁剪内核 | -| 社区论坛 | `https://club.rt-thread.org`(实测 HTTP 200) | 提交 Day1 文档的目标站点 | - -**版本说明**:RT-Thread Studio 的 SDK Manager 分发的内核版本按 BSP 而定,与独立源码仓库的 5.2.2 不是同一条发布线,两者不冲突,按 BSP 要求选择即可。 - -### 4.3 本机实际环境 - -RT-Thread Studio 已安装并成功创建、编译工程: - -| 项 | 实际值 | -|---|---| -| Studio 安装路径 | `C:\RT-ThreadStudio\` | -| 工程路径 | `C:\RT-ThreadStudio\workspace\qemu-a9\` | -| 内核版本 | **4.1.1**(`RT_VERSION 4` / `RT_SUBVERSION 1` / `RT_REVISION 1`) | -| SOC | `SOC_STM32F407ZG`,`ARCH_ARM_CORTEX_M4` | -| 板级配置 | `BOARD_STM32F407_SPARK`(星火一号) | -| 调试接口 | SWD / ST-Link | -| 编译产物 | `Debug/rtthread.elf`(545,880 B)、`Debug/rtthread.bin`(70,680 B) | - -**一个值得记录的坑**:工程目录名为 `qemu-a9`,但 `rtconfig.h` 中实际配置是 STM32F407ZG + STM32 HAL 驱动 + CubeMX,调试配置文件为 `qemu-a9.STLink.Debug.rttlaunch`(SWD 接口)。即目录名与实际目标平台不一致,**不能按 QEMU 方式运行**,需通过 ST-Link 下载到实体开发板。 - -> 这类"名字与内容不符"的问题正是工程管理要防的:后续接手的人(包括自己)会按目录名做出错误假设。规范做法是工程名如实反映目标平台,或在 `README.md` 中显式说明。 - -关键配置项(`rtconfig.h` 实测值): - -```c -#define RT_THREAD_PRIORITY_MAX 32 /* 优先级数量,0 最高 */ -#define RT_TICK_PER_SECOND 1000 /* 1 tick = 1 ms */ -#define RT_ALIGN_SIZE 4 -#define RT_USING_OVERFLOW_CHECK /* 栈溢出检查已开启 */ -#define RT_USING_HOOK -#define IDLE_THREAD_STACK_SIZE 1024 -#define RT_MAIN_THREAD_STACK_SIZE 2048 -#define RT_MAIN_THREAD_PRIORITY 10 -``` - ---- - -## 五、本次已完成与待完成项 - -**已完成** - -- [x] 确认上游权限与分支保护状态,明确唯一提交路径 -- [x] Fork 上游仓库并克隆自己的 Fork -- [x] 配置 `upstream` 远端 -- [x] 核对并修正提交邮箱(与 Gitee committed 邮箱一致) -- [x] 配置 Windows 中文路径相关项(`core.quotepath` 等) -- [x] 按 README 规范建立个人目录 -- [x] 提交 PR 一(初始化模板) -- [x] 提交 PR 二(作业与笔记内容) -- [x] 核实 RT-Thread 源码与工具获取渠道 -- [x] 安装 RT-Thread Studio 并创建工程(STM32F407ZG / 星火一号,内核 4.1.1) -- [x] 工程编译通过,生成 `rtthread.elf` / `rtthread.bin` - -**待完成** - -- [ ] 通过 ST-Link 下载到开发板 -- [ ] 串口终端验证启动 banner 与 `msh` 交互 -- [ ] 整理文档发布至社区论坛(标题格式 `【RSOC26】...`) - -> 说明:编译产物已生成并核对了文件大小,但**尚未下载到实体开发板、未做串口验证**,故上板相关项列入待完成,此处不填写未实际产生的运行输出。 - ---- - -## 六、学习笔记 - -详见同级目录:[`../笔记/第一天笔记.md`](../笔记/第一天笔记.md) diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 99dde54b06863dea6e3accf022c04fd77ad49350..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,766 +0,0 @@ -# 【RSOC26】第一课:如何使用 Git 去管理你的工程 - -> 本课目标:掌握在一个**多人协作、主干受保护**的仓库里提交自己成果的完整流程,以及出问题时的排查与责任界定方法。 -> -> 全部示例以夏令营笔记仓库 `gitee.com/rtthread/rsoc-rtt` 为对象,作者身份为**第 1 组 李子夜**。 - ---- - -## 一、先看清约束:为什么必须走 Fork + PR - -打开夏令营仓库 `gitee.com/rtthread/rsoc-rtt`,`README.md` 规定了目录结构: - -``` -└─2026 - ├─第1组 - │ └─李子夜 - │ └─笔记 - │ └─第一天笔记.md - │ └─作业 - │ └─第一天作业.md - ├─第2组 - ... - └─第6组 -``` - -再查一下这个仓库的分支状态: - -```bash -curl "https://gitee.com/api/v5/repos/rtthread/rsoc-rtt/branches" -``` - -返回中有两个关键信息: - -- 整个仓库**只有 `master` 一个分支** -- `"protected": true` —— **`master` 是受保护分支** - -**结论:任何人都无法直接 `git push` 到这个仓库。** - -这不是配置疏漏,而是所有正规工程的标准做法:主干只能通过**可审查、可追溯、可回退**的路径接收变更。因此唯一的提交路径是: - -``` -Fork(复制到自己账号) → 在自己仓库改 → 发 Pull Request → 维护者审查后合并 -``` - -本课就是把这条路径拆开讲。 - ---- - -## 二、心智模型:你要同时管三个仓库 - -大部分 Git 混乱源于脑子里只有"一个仓库"。实际上有三个: - -``` - ┌──────────────────────────────────────┐ - │ upstream │ - │ gitee.com/rtthread/rsoc-rtt │ 官方仓库 - │ 只读 + 可发 PR,不可写 │ - └──────────────────────────────────────┘ - ▲ │ - │ ③ Pull Request │ ① git fetch upstream - │ ▼ - ┌──────────────────────────────────────┐ - │ origin │ - │ gitee.com/<你的用户名>/rsoc-rtt │ 你 Fork 出来的,有全部权限 - └──────────────────────────────────────┘ - ▲ │ - │ ② git push origin │ git clone - │ ▼ - ┌──────────────────────────────────────┐ - │ local │ - │ C:\Users\llwxy\Documents\RTT\... │ 本地工作区 - └──────────────────────────────────────┘ -``` - -三个方向,记住即可: - -| 动作 | 方向 | 手段 | -|---|---|---| -| 获取他人最新进展 | upstream → local | `git fetch upstream` | -| 上传自己的成果 | local → origin | `git push origin <分支>` | -| 请求合入官方 | origin → upstream | 网页开 Pull Request | - -**注意:Pull Request 不是 Git 命令,是 Gitee 平台功能。** Git 负责版本,平台负责协作流程,二者分工不同。 - ---- - -## 三、环境准备 - -### 3.1 身份配置(决定提交归属,必须先做) - -每个 Git 提交记录 `author name` 和 `author email`。**Gitee 依靠 email 把提交与账号关联。** - -如果 email 与 Gitee 绑定邮箱不一致,你的提交在网页上会显示为一个无头像、无法点击的陌生用户 —— 贡献统计里没有你,也无法通过账号追溯到你。 - -```bash -# 查看当前配置 -git config --global user.name -git config --global user.email - -# 设置(name 建议直接用 Gitee 用户名) -git config --global user.name "你的Gitee用户名" -git config --global user.email "你注册Gitee的邮箱" -``` - -如果这台机器上有多个身份,在仓库内做局部覆盖(去掉 `--global`): - -```bash -cd rsoc-rtt -git config user.email "你注册Gitee的邮箱" # 仅对本仓库生效 -``` - -**首次提交后立即验证:** - -```bash -git log -1 --format='作者:%an <%ae>' -``` - -> 补救:如果邮箱写错且**尚未推送**,可用 `git commit --amend --reset-author --no-edit` 修正。已推送则不要改(原因见第九节)。 - -### 3.2 Windows + 中文路径的四项配置 - -本仓库路径全部为中文(`2026/第1组/李子夜/笔记/`),在 Windows 下有几个必须提前处理的问题。以下均为本机实测。 - -**① `git status` 显示为八进制转义** - -```bash -# core.quotepath 未设置(默认 true)时: -$ git status --short -?? "\347\254\2541\347\273\204/" - -# 设置为 false 后: -$ git status --short -?? 第1组/ -``` - -修复: - -```bash -git config --global core.quotepath false -git config --global i18n.logOutputEncoding utf-8 -``` - -**② 换行符(CRLF / LF)** - -查看本机来源: - -```bash -$ git config --show-origin --get-all core.autocrlf -file:C:/Program Files/Git/etc/gitconfig true -``` - -`core.autocrlf=true` 是 Git for Windows 的安装默认值:提交时把 CRLF 转为 LF 入库,签出时转回 CRLF。**单机自洽,但团队不一致时会出问题** —— 若有人设为 `false` 并用 CRLF 编辑器提交,你再改这个文件,Git 会判定**每一行都变了**,PR diff 全红,`git blame` 也会把整个文件的记录指向你。 - -根治方式是让规则跟着仓库走,在仓库放 `.gitattributes`: - -```gitattributes -* text=auto eol=lf -*.md text eol=lf -*.png binary -*.jpg binary -``` - -> 但这是公共文件,**不要自己加了直接提 PR**,先在 Issue 或群里与维护者确认(见第十一节)。 - -**③ 路径长度** - -中文目录加上本地前缀容易触及 Windows 260 字符上限: - -```bash -git config --global core.longpaths true -``` - -同时建议本地克隆路径尽量短,不要放在层层嵌套的中文目录里。 - -**④ PowerShell 不支持 `&&`** - -Windows PowerShell 5.1 **没有** `&&` 和 `||`,从教程抄的 `git add . && git commit -m "x"` 会直接语法报错: - -```powershell -# PowerShell 写法 -git add . ; git commit -m "docs: 第一天笔记" -git add . ; if ($?) { git commit -m "docs: 第一天笔记" } -``` - -```bash -# Git Bash 写法,&& 可用 -git add . && git commit -m "docs: 第一天笔记" -``` - -**建议:Git 操作统一在 Git Bash 里执行**,命令兼容性和路径处理都更省事。 - ---- - -## 四、完整流程 - -### 步骤 1:Fork - -浏览器打开 `https://gitee.com/rtthread/rsoc-rtt`,点击 **Fork**,复制到自己账号下。 - -此步无命令行等价操作(除调用 API)。完成后得到 `https://gitee.com/<你的用户名>/rsoc-rtt`。 - -### 步骤 2:Clone **自己的 Fork** - -```bash -cd ~/Documents/RTT -git clone https://gitee.com/<你的用户名>/rsoc-rtt.git -cd rsoc-rtt -``` - -**常见错误:clone 了官方仓库。** 那样 `origin` 指向你没有写权限的地址,push 必然失败。验证: - -```bash -git remote -v -# origin https://gitee.com/<你的用户名>/rsoc-rtt.git (fetch) -# origin https://gitee.com/<你的用户名>/rsoc-rtt.git (push) -``` - -必须是**你的用户名**。 - -> **关于登录**:本机 `credential.helper = manager`(Git for Windows 自带的 Git Credential Manager)。首次 push 会弹窗登录 Gitee,凭证随后存入 Windows 凭据管理器,无需重复输入。若使用私人令牌,用户名填 Gitee 用户名,密码位置填令牌。 - -### 步骤 3:挂载 upstream - -```bash -git remote add upstream https://gitee.com/rtthread/rsoc-rtt.git -git remote -v # 应同时出现 origin 与 upstream -``` - -**为什么必须加**:Fork 是**一次性快照,不会自动跟随官方更新**。夏令营几十人每天往官方仓提交,没有 `upstream`,你几天后就在一个过时的仓库上工作,PR 一提就冲突。 - -### 步骤 4:创建分支 - -```bash -git fetch upstream # 先取官方最新 -git switch -c day1/notes upstream/master # 基于官方最新创建分支 -``` - -**铁律:不在 `master` 上写任何东西。** - -理由是实用的:**本地 `master` 应始终是官方 `master` 的干净镜像。** 一旦在上面写了自己的内容,它就"脏"了,之后每次同步官方进展都会打架。保持干净,同步就永远无痛。 - -分支命名约定: - -``` -day1/notes 第一天笔记 -day1/homework 第一天作业 -day2/notes -fix/typo-day1 修错别字 -``` - -**一件事一分支。** 因为 PR 的粒度等于分支的粒度 —— 笔记和作业混在一个分支里,审查者就得一次看两件不相干的事,且只能一起合并或一起打回。 - -### 步骤 5:写内容 - -```bash -mkdir -p "2026/第1组/李子夜/笔记" -mkdir -p "2026/第1组/李子夜/作业" -``` - -严格遵守 README 的目录结构。**目录约定就是这个项目的接口**,自己发明一套会导致维护者手工搬文件,或直接被打回。 - -### 步骤 6:提交前必做的三条检查 - -```bash -git status # 哪些文件变动了 -git diff # 未暂存的改动内容 -git add "2026/第1组/李子夜" # 明确指定路径,不用 git add . -git diff --cached # ★ 确认即将提交的内容 -``` - -第四条最常被跳过,也最重要。它回答唯一要紧的问题:**我即将写入历史的,就是我想写的吗?** - -> **`git add .` 是最主要的事故来源。** 它会把目录下所有内容 —— 编译产物、临时文件、误存的截图 —— 一并纳入。 - -### 步骤 7:提交与推送 - -```bash -git commit -m "docs(第1组/李子夜): 添加第一天笔记" -git push -u origin day1/notes -``` - -`-u` 只需首次使用,它绑定本地与远程分支,之后 `git push` 即可。 - -### 步骤 8:开 Pull Request - -回到浏览器打开你的 Fork 页面,刚 push 完通常会有创建 Pull Request 的提示,也可从 Pull Requests 入口手动新建。 - -四个字段不能填错: - -``` -源: <你的用户名>/rsoc-rtt : day1/notes -目标: rtthread/rsoc-rtt : master -``` - -> Gitee 页面布局与按钮文案会随版本调整,以你打开的页面为准。**逻辑不变:从我的分支,合入官方的 master。** - ---- - -## 五、Commit Message 规范 - -### 格式 - -``` -<类型>(<范围>): <一句话说明做了什么> - -(空行) -(可选:为什么这么改) -``` - -常用类型: - -| 类型 | 用途 | -|---|---| -| `docs` | 笔记、作业、README(夏令营中绝大多数提交属于此类) | -| `feat` | 新增功能 | -| `fix` | 修 bug、修笔误 | -| `refactor` | 重构,行为不变 | -| `chore` | 杂务:`.gitignore`、目录调整 | -| `style` | 只改格式排版 | - -### 对照 - -| ❌ | ✅ | -|---|---| -| `update` | `docs(第1组/李子夜): 添加第一天笔记` | -| `修改` | `fix(第1组/李子夜): 修正笔记中优先级数值笔误` | -| `1` | `docs(第1组/李子夜): 补充线程调度器一节` | -| `最终版` / `最终版2` / `真的最终版` | 不应存在 | - -### 为什么规范 message 是实际收益,不是形式主义 - -设想排查场景:某文件出了问题,用 `git blame` 定位到一个提交。 - -- **提交 A**:message 为 `update`,改动 47 个文件、涉及 5 个人的目录 → 无法分辨哪部分是有意为之、哪部分是误伤,**这 47 个文件的问题默认全归此人**。 -- **提交 B**:message 为 `docs(第1组/李子夜): 添加第一天笔记`,只动 1 个文件 → 一眼即可关联或排除。 - -**提交是日志,不是存档。** 写完一小块就提一次,不要攒一整天。 - -### 多行 message 的写法 - -```bash -# Git Bash -git commit -F - <<'EOF' -docs(第1组/李子夜): 添加第一天笔记 - -补充 Git 协作流程一节,记录 master 受保护、 -必须走 Fork + PR 的原因。 -EOF -``` - -```powershell -# PowerShell 5.1:结尾的 '@ 必须顶格在行首,不能缩进 -git commit -m @' -docs(第1组/李子夜): 添加第一天笔记 - -补充 Git 协作流程一节。 -'@ -``` - -或直接执行 `git commit`(不带 `-m`),由编辑器输入。 - ---- - -## 六、同步 upstream:rebase 与 merge - -官方 `master` 每天都在变。你昨天开的分支,今天可能已落后几十个提交。 - -标准四步: - -```bash -git fetch upstream # 1. 下载官方最新(不动工作区,安全) -git switch day1/notes # 2. 回到自己分支 -git rebase upstream/master # 3. 把自己的提交移到官方最新之后 -git push --force-with-lease origin day1/notes # 4. 更新 Fork 上的分支 -``` - -### 两者区别 - -``` -merge(有分叉与合并节点): - 官方 ──A──B──C─────────M── - \ / - 你的 D──E───┘ - -rebase(线性): - 官方 ──A──B──C──D'──E'── -``` - -**个人分支推荐 rebase**,唯一理由:**线性历史大幅降低溯源难度。** `git log` 是一条干净时间线,`git bisect` 二分查找不会在合并节点上迷路,"往前数三个提交"有明确含义。 - -### 红线 - -> **不要 rebase 已被他人拉取过的公共分支。** - -rebase 的本质是**重写历史**:丢弃原提交,生成内容相同但 SHA 全新的提交。自己独占的分支无所谓;若他人已基于该分支工作,rebase 会让他的本地与远程彻底分叉。 - -同理,注意上面第 4 步用的是 `--force-with-lease` 而非 `--force`: - -- `--force`:无条件覆盖远程 -- `--force-with-lease`:**先检查远程是否仍是你上次看到的状态**,若他人期间推送过则拒绝执行 - -**始终使用 `--force-with-lease`。** - ---- - -## 七、冲突处理 - -**本仓库的目录设计天然防冲突**:每人在 `2026/第N组/自己的名字/` 下活动,Git 按文件按行判定冲突,各改各的文件不会撞上。README 的目录规范本质上是一个**冲突隔离设计**。 - -真正会冲突的是**共享文件**:根 `README.md`、`2026/第N组/README.md`,以及未来可能出现的索引页。 - -### 解决步骤 - -rebase 中途冲突时,Git 会停下并指出冲突文件。打开后会看到: - -``` -<<<<<<< HEAD -(upstream/master 上当前的内容) -======= -(你的提交想写入的内容) ->>>>>>> docs(第1组/李子夜): 更新组内索引 -``` - -1. 人工决定最终内容(索引类文件通常两边都要保留) -2. **删除 `<<<<<<<`、`=======`、`>>>>>>>` 三行标记** —— 漏删并提交上去是常见翻车点 -3. 告知 Git 已解决: - -```bash -git add "冲突的文件" -git rebase --continue -``` - -### 随时可以中止 - -```bash -git rebase --abort # 完全取消本次 rebase,回到操作前 -git merge --abort # merge 版本 -``` - -`--abort` 是完整回滚,安全。**看不懂就 abort 重来,好过硬着头皮乱敲。** - ---- - -## 八、溯源:查历史的核心命令 - -Git 的核心能力是精确回答「什么时候、谁、为什么改了这一行」。 - -### 8.1 看全局 - -```bash -git log --oneline --graph --decorate --all -``` - -`--oneline` 一行一提交、`--graph` 画分支拓扑、`--decorate` 标出分支标签、`--all` 含所有分支。建议做成别名: - -```bash -git config --global alias.lg "log --oneline --graph --decorate --all" -``` - -### 8.2 追踪单个文件的完整历史 - -```bash -git log --follow -p -- "2026/第1组/李子夜/笔记/第一天笔记.md" -``` - -- `--follow`:**文件被改名或移动后仍继续往前追** -- `-p`:显示每次提交的具体改动行 - -### 8.3 逐行定位:`git blame` - -```bash -git blame "2026/第1组/李子夜/笔记/第一天笔记.md" -git blame -L 40,60 "2026/第1组/李子夜/笔记/第一天笔记.md" # 只看 40-60 行 -``` - -每行前显示**最后修改该行的提交 SHA、作者、日期**。 - -### 8.4 查看某次提交 - -```bash -git show # 改了什么 -git show --stat # 只看文件清单与增删统计 -git show :"路径/文件.md" # 查看该时刻此文件的完整内容 -``` - -### 8.5 定位某段内容的引入者:`git log -S` - -```bash -git log -S "rt_thread_mdelay" --oneline -``` - -`-S` 搜索**哪次提交改变了该字符串的出现次数** —— 即谁添加的、谁删除的。排查"这个配置从哪来的"时效率远高于肉眼翻 log。 - -### 8.6 二分定位故障:`git bisect` - -```bash -git bisect start -git bisect bad # 当前版本是坏的 -git bisect good <某个正常的SHA> # 该版本是好的 -# Git 自动签出中间提交,测试后告知结果: -git bisect good # 或 git bisect bad -# 重复几次后,Git 指出第一个出问题的提交 -git bisect reset # 结束 -``` - -**对数复杂度**:100 个提交中定位,约需 7 次测试。 - -### 8.7 本地恢复:`git reflog` - -```bash -git reflog -``` - -记录本地 HEAD 的每次移动(切分支、commit、rebase、`reset --hard`)。**即使 `reset --hard` 删掉了提交,只要是本地操作过的,基本都能找回:** - -```bash -git reflog # 找到目标状态,如 HEAD@{5} -git reset --hard HEAD@{5} -``` - -> `reflog` 默认保留 90 天,且**仅存在于本地** —— 未推送的内容换台机器就没有了。 - -### 8.8 汇总 - -| 需求 | 命令 | -|---|---| -| 整体历史与分支形状 | `git log --oneline --graph --decorate --all` | -| 某文件完整变更史(含改名) | `git log --follow -p -- <文件>` | -| 某一行是谁写的 | `git blame <文件>` / `git blame -L a,b <文件>` | -| 某次提交的内容 | `git show ` | -| 某段内容的引入者 | `git log -S "关键字"` | -| 哪个提交导致故障 | `git bisect` | -| 恢复本地误操作 | `git reflog` + `git reset --hard HEAD@{n}` | -| 某人某时间段的提交 | `git log --author="名字" --since="2026-08-17"` | - ---- - -## 九、责任边界:五条自保规则 - -多人协作中,"文件被覆盖了""内容不见了"是客观会发生的。区别只在于它是**可查证的事实**,还是靠猜和争论。以下五条本质上是在保护你自己。 - -### 1. 身份必须真实 - -见 3.1 节。email 配错的后果:你的产出可能算到别人头上,且**你没有任何证据链证明哪些提交是你的、哪些不是**。 - -### 2. 提交要小,message 要准 - -见第五节。这是你的证据链 —— 每个提交只动一个文件、意图明确,任何问题都能被精确关联或排除。 - -### 3. 不代人提交,不共用账号 - -帮同学"我给你推上去"会导致提交作者是你、内容是他的,记录从此错误且极难更正。正确做法是教他自己发 PR。确实需要代提时用正规机制: - -```bash -git commit --author="他的名字 <他的邮箱>" -m "..." -``` - -或在 message 末尾添加: - -``` -Co-authored-by: 某某 <某某的邮箱> -``` - -### 4. 不对公共分支 force push - -**`git push --force` 到他人也在使用的分支,会永久销毁"谁改了什么"的记录。** 原提交从远程消失,历史被替换。这一操作的性质不是失误,而是篡改记录。 - -- 自己的 PR 分支需要改历史:可以,**但用 `--force-with-lease`** -- `master` 或任何他人在用的分支:**不要** - -本仓库 `master` 受保护,平台层面就会拒绝 —— **这个保护是在帮你,不要设法绕过。** - -### 5. 撤销已推送的提交用 `revert`,不用 `reset` - -| | `git revert ` | `git reset --hard ` | -|---|---|---| -| 做法 | **新增**一个反向提交 | **删除**后续提交 | -| 历史 | 错误与修正都保留 | 抹去错误发生的痕迹 | -| 已推送的提交 | ✅ 唯一正确选择 | ❌ 需 force push,见规则 4 | - -```bash -git revert <出问题的SHA> -``` - -`revert` 留下的是一条负责任的记录:出错 → 发现 → 修正。而抹掉痕迹的人,在旁人看来反而是第一嫌疑人。 - -### 如何证明"不是我" - -```bash -# 1. 这几行最后是谁改的 -git blame -L 30,40 "路径/文件.md" - -# 2. 我在这段时间内的全部提交(范围外的都不是我) -git log --author="你的Gitee用户名" --since="2026-08-17" --oneline --stat - -# 3. 该文件的完整变更史 -git log --follow --format="%h %an %ad %s" -- "路径/文件.md" -``` - -> **规范的最终目的不是在追责时自保,而是让追责不必发生。** 当提交都小而清晰、身份真实、历史从不篡改,出问题时看一眼 log 即知原委,改掉继续 —— 没有人需要猜,也就没有人需要背锅。 - ---- - -## 十、仓库卫生 - -### `.gitignore` - -后续要跑 RT-Thread 工程,构建产物体积大、每次编译都变、审查时是纯噪音,还会制造大量假冲突,**绝不能进仓库**: - -```gitignore -# 构建产物 -build/ -*.o -*.obj -*.elf -*.bin -*.hex -*.map -*.axf - -# scons / RT-Thread -.sconsign.dblite -__pycache__/ -*.pyc - -# Keil MDK -*.uvguix.* -*.uvoptx -Listings/ -Objects/ -DebugConfig/ - -# IDE -.vscode/ -.idea/ - -# 系统 -Thumbs.db -desktop.ini -.DS_Store -``` - -> `*.uvguix.*` 存的是**个人窗口布局**,一旦提交,每个打开工程的人都会看到它变化并互相覆盖,天天冲突且毫无意义。 - -注意:根目录 `.gitignore` 是公共文件,修改前先沟通。 - -### 已误提交的处理 - -```bash -git rm --cached -r build/ # 停止跟踪,保留本地文件 -git commit -m "chore: 停止跟踪构建产物" -``` - -**这只是从此不再跟踪,历史中那份仍在。** 彻底清除需重写历史,而重写公共仓库历史违反规则 4 —— **所以一开始就不要提上去。** - -### 大文件 - -- 截图先压缩,一张配图不需要 5MB -- 不提交视频、固件包、完整工具链 -- 需分享大文件用网盘链接 - -Git 对二进制文件是**每个版本存一整份**,一个反复修改的 10MB 图片能把仓库撑到几百 MB,所有人 clone 都要等。 - ---- - -## 十一、协作约定 - -### PR 描述模板 - -PR 是你与维护者之间唯一的沟通界面: - -```markdown -### 内容 -第 1 组 李子夜 第一天笔记 + 第一天作业 - -### 变更文件 -- `2026/第1组/李子夜/笔记/第一天笔记.md`(新增) -- `2026/第1组/李子夜/作业/第一天作业.md`(新增) - -### 自查 -- [x] 目录结构符合 README 规范 -- [x] 仅改动自己名下目录,未触碰他人文件 -- [x] 已 rebase 到最新 upstream/master,无冲突 -- [x] 无构建产物、无大文件 -``` - -其中「仅改动自己名下目录」最关键 —— 它明确划定了本次 PR 的责任边界,维护者一眼即知风险范围。 - -### Issue 的使用 - -- 想改公共文件(根 `README.md`、`.gitignore`、`.gitattributes`)→ **先开 Issue,不要直接 PR** -- 发现仓库结构或模板有歧义 → 开 Issue -- 求助 → 开 Issue,**贴完整报错**,不要只说"跑不起来" - -**判断标准:改自己的东西直接 PR,改公用的东西先商量。** - -### 组内索引文件 - -`2026/第N组/README.md` 是唯一会频繁冲突的文件。两种做法: - -1. **由一人统一维护**,其他人不动 —— 冲突源归零 -2. 约定**只在文件末尾追加**,不插入中间 —— Git 对同一行附近的并发修改最易冲突,对追加到不同位置相对宽容 - ---- - -## 十二、每日操作清单 - -```bash -# ① 同步(每天第一件事) -git switch master -git fetch upstream -git reset --hard upstream/master # ⚠ 有破坏性,仅在 master 保持干净时安全 -git push origin master - -# ② 开当天分支 -git switch -c day2/notes - -# ③ 在 2026/第1组/李子夜/ 下写内容 - -# ④ 提交前四连 -git status -git diff -git add "2026/第1组/李子夜" -git diff --cached - -# ⑤ 提交推送 -git commit -m "docs(第1组/李子夜): 添加第二天笔记" -git push -u origin day2/notes - -# ⑥ 浏览器开 PR,按模板写描述 - -# ⑦ 合并后清理 -git switch master -git branch -d day2/notes -git push origin --delete day2/notes -``` - -### 救命指令速查 - -| 情况 | 命令 | -|---|---| -| 改错了还没 `add`,想丢弃 | `git restore <文件>` | -| 已 `add`,想取消暂存(保留内容) | `git restore --staged <文件>` | -| 刚提交发现 message 写错(**未推送**) | `git commit --amend` | -| 撤销提交但保留改动 | `git reset --soft HEAD~1` | -| 活没做完要紧急切分支 | `git stash` → 回来 `git stash pop` | -| 撤销**已推送**的提交 | `git revert ` | -| 把本地搞坏了 | `git reflog` → `git reset --hard HEAD@{n}` | -| rebase / merge 中途乱了 | `git rebase --abort` / `git merge --abort` | -| 确认当前在哪个分支 | `git status` / `git branch -vv` | - ---- - -## 本课要点 - -1. **`master` 受保护 → 唯一路径是 Fork + PR**,这是所有正规工程的标准做法 -2. **三个仓库**:upstream 只读、origin 是你的 Fork、local 是工作区;必须挂 `upstream` 才能同步 -3. **`user.email` 决定提交归属**,配错等于放弃了自己的署名权与举证能力 -4. **不在 `master` 上写东西**,保持它是官方的干净镜像 -5. **提交前必看 `git diff --cached`**,不要用 `git add .` -6. **提交小、message 准**,这既是可读的历史,也是可查的责任边界 -7. **不对公共分支 force push**,撤销已推送的提交用 `revert` - ---- - -*环境:Windows 11 + Git 2.48.1 (Git for Windows)。`core.quotepath`、`core.autocrlf`、中文路径相关表现为本机实测结果。Gitee 网页端按钮位置与文案可能随版本调整,以实际页面为准。* diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 418f0fa2a069182de3146c8134be6d0e2edb368c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\345\255\220\345\244\234/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,459 +0,0 @@ -# 【RSOC26】第二课:RT-Thread 线程模型深度解析 - -> 提交人:李子夜(第 1 组)|Gitee:@CyberSprout|日期:2026-08-19 - -本文基于 RT-Thread **4.1.1** 源码(`rt-thread/src/thread.c`、`scheduler.c`、`idle.c`、`rtdef.h`)逐段阅读后整理,所有代码行号、结构体字段、宏定义均可在对应文件中核验。 - ---- - -## 一、线程的本质:一个 `struct rt_thread` - -### 1.1 结构体全貌 - -`struct rt_thread` 定义在 `rtdef.h` 中,是内核对"线程"这一抽象的全部承载: - -```c -struct rt_thread { - char name[RT_NAME_MAX]; /* 8 字节,超出截断 */ - rt_list_t list; /* 就绪队列挂载点 */ - rt_list_t tlist; /* 调试 / list_thread 用 */ - void *sp; /* 栈顶指针,切换时被 r0-r12/lr/pc/xpsr 覆盖 */ - void *entry; /* 线程函数入口地址 */ - void *parameter; /* 传给 entry 的参数 */ - void *stack_addr; /* 栈底(高地址) */ - rt_uint32_t stack_size; /* 栈大小(字节) */ - - rt_ubase_t number_mask; /* SMP 用,单核编译为保留字段 */ - rt_uint8_t stat; /* 状态 + 属性,紧凑编码 */ - rt_uint8_t current_priority; /* 当前优先级 0~31,0 最高 */ - rt_uint8_t number; /* SMP 用 */ - rt_uint8_t high_mask; /* SMP 用 */ - -#if RT_THREAD_PRIORITY_MAX > 32 - rt_uint16_t number_mask; /* 超过 32 级时扩展为 256 级 */ - rt_uint8_t high_mask; /* O(1) 查找需两层 */ - rt_uint8_t low_mask[4]; /* */ -#endif - - rt_uint32_t init_tick; /* 阻塞时初始 tick 计数 */ - rt_uint32_t remaining_tick; /* 剩余 tick */ - - struct rt_timer thread_timer; /* 内嵌定时器,用于阻塞超时 / 时间片 */ - - void (*cleanup)(struct rt_thread *); /* 线程退出清理回调 */ - rt_ubase_t user_data; /* 用户自定义 */ -}; -``` - -### 1.2 两个 `list`,两种用途 - -| 字段 | 用途 | 挂在哪 | -|---|---|---| -| `list` | 就绪队列 / 挂起队列 | `rt_thread_ready_table[]` 或 `rt_thread_defunct` | -| `tlist` | 调试枚举(`list_thread` 命令) | 不参与调度,仅遍历 | - -> **关键**:调度器操作的是 `list`。`tlist` 是内核运行时维护的双向链表,`list_thread` 遍历它输出状态——因此即使一个线程既不在就绪队列也不在挂起队列(比如已 `delete` 但还没被回收),只要它没从 `tlist` 摘下,`list_thread` 照样能显示它。 - ---- - -## 二、线程状态:`stat` 字段的紧凑编码 - -`stat` 是一个 `rt_uint8_t`,同时携带 **运行状态** 和 **附加属性**: - -```c -#define RT_THREAD_INIT 0x00 /* 已初始化但未启动 */ -#define RT_THREAD_READY 0x01 /* 就绪,在就绪队列中 */ -#define RT_THREAD_SUSPEND 0x02 /* 挂起,主动让出 */ -#define RT_THREAD_RUNNING 0x03 /* 正在运行(仅当前线程此值有意义) */ -#define RT_THREAD_CLOSE 0x04 /* 已关闭 / 已删除 */ -#define RT_THREAD_STAT_MASK 0x07 /* 取状态的三位掩码 */ -#define RT_THREAD_STAT_YIELD 0x08 /* 发生过主动让位 */ -``` - -### 2.1 `stat` 不是简单的枚举 - -- `READY` 和 `RUNNING` 可以同时存在于一帧之间:调度器上下文切换时,先写 `RUNNING`,旧线程的 `stat` 同时改了。 -- `YIELD` 是**附加位**,不冲突——一个线程可以 `READY | YIELD`。 -- 读状态必须用 `thread->stat & RT_THREAD_STAT_MASK`,直接比较等于 `READY` 会漏掉 `RUNNING`(值为 3)。 - -### 2.2 状态流转 - -``` -INIT ──startup()──▶ READY ──调度──▶ RUNNING ──yield()──▶ READY|YIELD - ├─sleep()──▶ SUSPEND - └─delete()──▶ CLOSE -SUSPEND ──resume()──▶ READY -CLOSE ──回收完成──▶ (内存归还) -``` - ---- - -## 三、调度器:O(1) 优先级就绪表 - -### 3.1 数据结构 - -```c -/* scheduler.c */ -rt_uint32_t rt_thread_ready_priority_group; /* 32 位,每位代表一组优先级有人就绪 */ -rt_uint8_t rt_thread_ready_table[32]; /* index = 优先级,bitmap of threads at that prio */ -``` - -32 级优先级时,`rt_thread_ready_priority_group` 每一位直接对应一个优先级(bit 0 = prio 0),`__rt_ffs()` 找到最低有效位 = 最高优先级。 - -### 3.2 `__rt_ffs`:找最低置位 - -```c -static int __rt_ffs(int value) -{ - if (value == 0) return 0; - if (value & 0xff) { - return __rt_ffs8(value & 0xff); - } - value >>= 8; - if (value & 0xff) { - return __rt_ffs8(value & 0xff) + 8; - } - value >>= 8; - if (value & 0xff) { - return __rt_ffs8(value & 0xff) + 16; - } - return __rt_ffs8(value >> 24) + 24; -} -``` - -> 此函数只查到最低有效位,返回值 = **最高优先级编号**。整个过程不超过几次 lookup,不依赖优先级数量。 - -### 3.3 同一优先级内的时间片轮转 - -同一个优先级可以有多个线程,调度器用 `thread_timer` 实现时间片。当一个线程的时间片耗尽时: - -1. 调度器清零该线程的时间片位 -2. 插入队尾 -3. 让出给同优先级下一个线程 - -> 跨优先级调度时,时间片概念不发生作用——高优先级抢占是瞬时的。 - ---- - -## 四、栈:从 `'#'` 填充到溢出检测 - -### 4.1 栈的初始化 - -`rt_thread_init` 中用到以下代码: - -```c -rt_memset(thread->stack_addr, '#', thread->stack_size); -``` - -栈初始化后每个字节都是 `0x23`(`'#'` 的 ASCII 码)。这不是什么魔法值——它让内核能在运行时计算**栈水印**(实际用了多少)。 - -### 4.2 栈溢出检测 - -`scheduler.c` 中的 `_scheduler_stack_check()` 每次调度时检查当前线程的栈: - -```c -if (*((rt_uint8_t *)thread->stack_addr) != '#' || - sp <= thread->stack_addr || - sp > thread->stack_addr + stack_size) -{ - rt_kprintf("thread:%s stack overflow\n", thread->name); - rt_base_t level; - level = rt_hw_interrupt_disable(); - while (level); /* 死循环——中断关闭后不会恢复 */ -} -else if (sp <= stack_addr + 32) -{ - rt_kprintf("warning: %s stack is close to end.\n", thread->name); -} -``` - -**三个判定**: - -| 条件 | 含义 | 后果 | -|---|---|---| -| `*stack_addr != '#'` | 栈底 `'#'` 被覆写 → **确实溢出** | 关中断 + 死循环(不是 reset,不是 fault handler) | -| `sp <= stack_addr` | SP 跑到了栈底以下 | 同上 | -| `sp > stack_addr + stack_size` | SP 越过了栈顶(栈反了?) | 同上 | -| `sp <= stack_addr + 32` | 剩余不足 32 字节 | 输出 warning,不中断 | - -**关键陷阱**:溢出检测只在**调度点**做。如果一个线程不停写栈但从不被挂起/让出,检测永远不会触发——栈可能已经挖穿了别人,调度器甚至都没"看到"。 - -### 4.3 水印计算方法 - -```c -watermark = (rt_uint8_t *)thread->stack_addr + thread->stack_size - (rt_uint8_t *)thread->sp; -``` - -这句就是"最高 SP 到栈底"的距离,单位字节。`list_thread` 和 MSH 的 `thread` 命令在启用 `RT_USING_HEAP` + 调试信息时可以输出。 - ---- - -## 五、线程创建与销毁:两条路径 - -### 5.1 静态初始化:`rt_thread_init` - -适合**栈在编译期就确定**的线程(如 idle、system): - -```c -rt_err_t rt_thread_init(struct rt_thread *thread, - const char *name, - void (*entry)(void *), - void *parameter, - void *stack_start, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -- 不分配内存,`thread` 和 `stack_start` 都是外部传入 -- 调用后线程处于 `INIT` 状态,需额外 `rt_thread_startup()` 才进入 `READY` -- 适合内核内部线程(idle、timer)等必须在 BSS 段提前声明的场景 - -### 5.2 动态创建:`rt_thread_create` - -适合**应用层线程**: - -```c -rt_thread_t rt_thread_create(const char *name, - void (*entry)(void *), - void *parameter, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -- 在堆上分配 `struct rt_thread` + 栈空间 -- 创建后线程处于 `INIT` 状态,同样需 `rt_thread_startup()` 启动 -- 创建失败返回 `RT_NULL` - -### 5.3 销毁与 "延迟回收" - -线程调用 `rt_thread_delete(rt_thread_self())` 时,**不会立刻释放内存**: - -1. 线程状态改为 `CLOSE` -2. 自身挂到 `_rt_thread_defunct` 链表上(`idle.c` 行 147) -3. **主动让出**(`rt_schedule()`) - -真正的 `rt_memset(thread, 0, sizeof(struct rt_thread))` + `rt_free(thread->stack_addr)` 由 **idle 线程**执行: - -```c -static void rt_thread_defunct_execute(void) -{ - struct rt_thread *thread; - - while (1) { - thread = rt_list_entry(_rt_thread_defunct.next, struct rt_thread, tlist); - - if (&thread->tlist == &_rt_thread_defunct) break; - - rt_list_remove(&thread->tlist); - - if (rt_object_is_systemobject((rt_object_t)thread)) - rt_memset(thread, 0, sizeof(struct rt_thread)); - else - rt_free(thread->stack_addr); - ... - } -} -``` - -**这就是一个有趣的陷阱**:如果有一个高优先级线程永远不主动让出 CPU,idle 线程永远获得不到时间片,defunct 队列上的线程永远不回收,堆内存会慢慢耗尽。 - ---- - -## 六、阻塞与唤醒:定时器驱动的超时机制 - -### 6.1 三种阻塞方式 - -| 函数 | 触发条件 | 唤醒方式 | -|---|---|---| -| `rt_thread_yield()` | 同优先级时间片用尽 / 主动让出 | 下次调度 | -| `rt_thread_sleep(ticks)` | 休眠 N 个 tick | 超时 / `rt_thread_resume()` | -| `rt_thread_delay(ms)` | 休眠 N 毫秒 | 超时(`ms × RT_TICK_PER_SECOND / 1000`) | - -### 6.2 超时机制 - -每个线程内嵌一个 `struct rt_thread_timer`,阻塞时调度器把它挂到软件定时器队列。tick ISR 每 1ms 触发一次,递减所有活跃定时器,到 0 时回调 `rt_timer_timeout_handler`,将阻塞线程重新挂回就绪表。 - -> `RT_USING_TIMER_SOFT` 未开启时,定时器回调直接在 tick ISR 里做(不是移植到软件 timer 线程),意味着**关闭软 timer 时 tick ISR 增加少量工作但省了一个线程的上下文切换开销**。本机工程未开启,定时器走 ISR 路径。 - -### 6.3 `remaining_tick` 的递减 - -线程被阻塞时 `init_tick` 保存原始值,`remaining_tick` 每 tick 减 1。异步 `resume()` 直接把线程挂回就绪表——不检查 `remaining_tick`,所以即使提前被唤醒,计时器仍然在跑,但不会再阻塞同一个线程(状态已经是 READY)。 - ---- - -## 七、挂起与恢复:只允许挂起自己 - -```c -rt_err_t rt_thread_suspend(rt_thread_t thread) -``` - -**4.1.1 中的硬性断言**: - -```c -RT_ASSERT(thread == rt_thread_self()); -``` - -这意味着: - -- ✅ `rt_thread_suspend(rt_thread_self())` —— 合法 -- ❌ `rt_thread_suspend(other_thread)` —— **断言失败** -- 此行为在 RT-Thread 5.x 之后有所放宽,但 4.1.1 中编译不通过 - -恢复用 `rt_thread_resume(thread)`,无此限制——**任何线程**(包括 ISR)都可以恢复一个被挂起的线程。 - ---- - -## 八、钩子:在线程生命周期中注入自定义代码 - -`rtdef.h` 声明: - -```c -struct rt_thread { - /* ... */ - void (*cleanup)(struct rt_thread *thread); /* 线程退出清理 */ -}; - -/* 全局钩子指针 */ -void (*rt_thread_suspend_hook)(rt_thread_t thread); -void (*rt_thread_resume_hook)(rt_thread_t thread); -void (*rt_heap_hook)(void); -``` - -可以在 `rtconfig.h` 中开启:`RT_USING_HOOK`。 - -| 钩子 | 触发时机 | 典型用途 | -|---|---|---| -| `cleanup` | 线程函数返回时 | 释放线程私有资源 | -| `suspend_hook` | 线程被挂起时 | 调试日志 / 功耗管理 | -| `resume_hook` | 线程被恢复时 | 恢复外设时钟 | - -> 钩子在 ISR 上下文中执行——不能调用阻塞型 API。 - ---- - -## 九、本机活跃线程全景 - -从 `rtconfig.h` 配置 + `components.c` 推导本工程启动后的完整线程表: - -| 线程名 | 优先级 | 栈大小 | 时间片 | 来源 | 状态 | -|---|---|---|---|---|---| -| `tidle0` | **31** (最低) | 1024 B | 32 tick | `idle.c` 内核创建 | 永久运行 | -| `main` | 10 | 2048 B | 20 tick | `components.c` | 用户主线程 | -| `tshell` | 20 | 4096 B | — | finsh 组件 | MSH 命令行 | - -### 9.1 `tidle0` — 最忙的"闲人" - -- 在 `rt_system_server_init()` 中创建 -- 永远是就绪的最后一个馅饼(`PRIORITY_MAX - 1 = 31`) -- 有 32 tick 的时间片,但永远跑不满——任何优先级更低的线程不存在 -- 执行 `rt_thread_defunct_execute()`,回收已删除的线程 -- `RT_USING_IDLE_HOOK` 开启时,还可以挂 4 个 idle hook 回调 - -### 9.2 `main` — 你的代码入口 - -```c -tid = rt_thread_create("main", main_thread_entry, RT_NULL, - RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20); -``` - -- 优先级 10:高于 `tshell`(20),低于系统线程 -- 用户线程中优先级最高,能抢占 shell 的输入输出 -- 本例中 `main_thread_entry` 调用 `main()`,而 `main()` 里是你的 LED 闪烁循环 + `rt_thread_mdelay(500)` - -### 9.3 `tshell` — 命令行解释器 - -- finsh 组件创建,优先级 20 -- 等待 UART 输入,解析命令后 fork 一个线程执行(如 `list_thread`) -- 本机 `rtconfig.h` 中 `FINSH_USING_MSH` 已定义(shell 模式),未定义 `FINSH_USING_ASH`(自动脚本模式) - ---- - -## 十、`list_thread` 输出格式解读 - -在 MSH 中输入 `list_thread`,内核输出: - -``` -thread pri status sp stack size max used left tick error -tidle0 31 ready 0x... 0x400 0x2c4 0 0x0 -tshell 20 ready/suspend ... 0x1000 0x380 23 0x0 -main 10 running 0x... 0x800 0x1b0 15 0x0 -``` - -| 列 | 说明 | -|---|---| -| pri | `current_priority`,数字越小优先级越高 | -| status | `stat & STAT_MASK` 的字面显示,`running` 仅在被查看者自身那一帧 | -| sp | 当前栈指针值 | -| stack size | 初始化时的 `stack_size` | -| max used | 水印:`stack_addr + stack_size - SP`(栈被"淹没"的深度) | -| left tick | `remaining_tick`,负数表示超时了 | - -> `max used` 越高说明用栈越多——如果它接近 `stack_size`,就该加栈了。 - ---- - -## 十一、值得记住的坑 - -| # | 坑 | 原因 | 后果 | -|---|---|---|---| -| 1 | `RT_NAME_MAX = 8` | `char name[8]`,超长自动截断 | `rt_thread_create("very_long_name", ...)` 实际存储为 `very_lo` | -| 2 | 溢出检测是调度时点检 | 非中断 / 非实时 | 溢出可能在两次调度之间就破坏内存了 | -| 3 | 溢出导致关中断死循环 | `rt_hw_interrupt_disable(); while(level);` | 整个系统 hang,不复位,不 fault handler | -| 4 | `delete` 不释放内存 | 等 idle 回收 | 高优先级线程 CPU 饥饿 → 堆泄漏 | -| 5 | `suspend` 只收自己 | `RT_ASSERT(thread == self)` | 老教程里跨线程挂起的写法编译不通过 | -| 6 | `stat & STAT_MASK` | `RUNNING = 3` 不在 `READY(1)` 上 | 不掩码读状态会得到错误结论 | - ---- - -## 十二、本工程线程相关配置汇总 - -以下值全部来自 `C:\RT-ThreadStudio\workspace\qemu-a9\rtconfig.h`,实测值: - -``` -RT_THREAD_PRIORITY_MAX = 32 /* 0~31,31 最低 */ -RT_TICK_PER_SECOND = 1000 /* 1 tick = 1 ms */ -RT_ALIGN_SIZE = 4 -RT_NAME_MAX = 8 -RT_USING_OVERFLOW_CHECK /* 栈溢出检测开启 */ -RT_USING_HOOK /* 钩子接口开启 */ -RT_USING_HEAP /* heap_5 风格动态内存 */ -RT_USING_IDLE_HOOK /* idle hook 挂点 4 个 */ - -RT_USING_FINSH /* finsh 组件 */ -FINSH_USING_MSH /* 命令shell模式 */ - -/* 未开启的 */ -RT_USING_TIMER_SOFT /* 定时器走 tick ISR */ -RT_USING_SMP /* 单核 */ -RT_USING_CPU_USAGE /* 无 CPU 占用率统计 */ -``` - ---- - -## 十三、扩展阅读(源码路径) - -| 文件 | 内容 | -|---|---| -| `rt-thread/include/rtdef.h` | `struct rt_thread`、状态宏 | -| `rt-thread/src/thread.c` | 全部线程 API 实现 | -| `rt-thread/src/scheduler.c` | 就绪表、`__rt_ffs`、栈检查 | -| `rt-thread/src/idle.c` | tidle0 创建、defunct 回收 | -| `rt-thread/src/components.c` | `main` 线程创建入口 | -| `rt-thread/components/finsh/cmd.c` | `list_thread` 输出格式化 | -| `rt-thread/src/timer.c` | 软件定时器超时回调 | - ---- - -## 十四、学习笔记:Day 2 完成检查 - -- [x] 阅读 `thread.c` 全部 API 实现 -- [x] 阅读 `scheduler.c` 就绪表与 O(1) 调度 -- [x] 阅读 `idle.c` 线程回收机制 -- [x] 阅读 `rtdef.h` 结构体定义与状态宏 -- [x] 读取本机 `rtconfig.h` 实时配置 -- [x] 确认 `main.c` 调用的线程 API 实际代码路径 -- [ ] 硬件上板验证(待 ST-Link) -- [ ] 串口终端 `list_thread` 输出截图 -- [ ] 社区论坛发布文档 diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day1.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day1.md" deleted file mode 100644 index 7df56e95843f1474d04c4ba1edf9214d98fd25dd..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day1.md" +++ /dev/null @@ -1,162 +0,0 @@ -# RTT-学习日志-Day1 - -## markdown语法学习 - -### 代码插入 - -``` -#include - -int main() -{ -return 0; -} -``` - -### 表格插入相关 - - - -| --- | --- | -| ---- | ---- | -| | | - -### 其他语法 - -**加粗语法** - -`高亮语法` - -图像插入![image-20260817100636809](figures/image-20260817100636809.png) - - - - - -## 关于RTT-Stdio - -### 任务一rtt断点调试 - -![image-20260817102123446](figures/image-20260817102123446.png) - -## 星火一号 - -### 星火一号工程创建 - -#### 端口确认 - - - -![image-20260817102559963](figures/image-20260817102559963.png) - -#### 新建项目 - - - -![image-20260817103806695](figures/image-20260817103806695.png) - -![image-20260817104345415](figures/image-20260817104345415.png) - -#### 任务三 rt_kprintf() - -![image-20260817104204439](figures/image-20260817104204439.png) - -![image-20260817104221356](figures/image-20260817104221356.png) - -![image-20260817104255704](figures/image-20260817104255704.png) - -# GIT的使用 - -## 创建仓库 - -![image-20260817193850149](figures/image-20260817193850149.png) - -·![image-20260817195708890](figures/image-20260817195708890.png) - -## git clone - - - -![image-20260817200541589](figures/image-20260817200541589.png) - - - - - -![image-20260817200633114](figures/image-20260817200633114.png) - - - -## GIT 初始化相关 - -### 邮箱用户相关 - -user 和邮箱 我的电脑之前已经配置好,故这部分跳过 - - - -### 远程密钥相关 - -#### 输入指令 - -```$ ssh-keygen -t rsa -b 4096 -C "1394546446@qq.com" -$ ssh-keygen -t rsa -b 4096 -C "1394546446@qq.com" -``` - - - -![image-20260817201509787](figures/image-20260817201509787.png) - -#### 查看密钥 - -``` -cat /c/Users/13945/.ssh/id_rsa.pub -``` - -![image-20260817231925741](figures/image-20260817231925741.png) - -#### 设置中将公钥配置进入gitee - -![image-20260817231901642](figures/image-20260817231901642.png) - -#### 远程测试ssh gitee - -``` -ssh -T git@gitee.com -``` - - - -![image-20260817231955977](figures/image-20260817231955977.png) - -#### git 上传指令 - -``` -git init //初始化仓库 - -git add 文件名 //添加和提交 - -git commit -m "提交信息" - -git push -``` - - - - - -![image-20260817220655304](figures/image-20260817220655304.png) - -## GIT pullrequest - -git原始命令行老久不用生疏了,我直接使用我之前使用的sourcetree了。 - -将gitee先将仓库进行fork - -拉取到本地然后使用source tree同步 - -![image-20260817232207720](figures/image-20260817232207720.png) - -然后可以看见提交状态和相关信息 - -![image-20260817232228576](figures/image-20260817232228576.png) \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817100636809.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817100636809.png" deleted file mode 100644 index a65f8254bb544acc84f1770829ad3fa8bc2089b5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817100636809.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817102123446.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817102123446.png" deleted file mode 100644 index 103a6145be8c1e149e33e2f5f6f93cb4708d990c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817102123446.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817102500051.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817102500051.png" deleted file mode 100644 index 1f6adf3ea4c9b1dbf2c924391631d789b81b751d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817102500051.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817102559963.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817102559963.png" deleted file mode 100644 index 856b4315f5960da31589eaa1840a462e852e6c5a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817102559963.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817103806695.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817103806695.png" deleted file mode 100644 index 086dea3c02458317e44cc4d42adf1e8a65460ade..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817103806695.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104204439.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104204439.png" deleted file mode 100644 index bbe57fde50e52afffb8b4840ff059e34d6edb473..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104204439.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104221356.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104221356.png" deleted file mode 100644 index 8c834a7585b87dbef84ec51e145324bba7017acd..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104221356.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104255704.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104255704.png" deleted file mode 100644 index a82638d9a2b7bb4cbe180ef5c0417d758394672b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104255704.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104345415.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104345415.png" deleted file mode 100644 index 444b255a804d8d1630256613af2272b071da14d3..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817104345415.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817193850149.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817193850149.png" deleted file mode 100644 index 525e3b6be805d888637de58fa7ab81d3e6f74315..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817193850149.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817195708890.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817195708890.png" deleted file mode 100644 index f85939d0f078012bfd136007df3385ab269146fa..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817195708890.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817195720415.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817195720415.png" deleted file mode 100644 index 285de744486863c2547132ab31b5ed0461c2d880..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817195720415.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817200541589.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817200541589.png" deleted file mode 100644 index 512dcf9f3b88a634be9933c28993303f7a24ad37..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817200541589.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817200633114.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817200633114.png" deleted file mode 100644 index 2cd35454c1c0e9e92efc89ac9b8affa8603204ef..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817200633114.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817201509787.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817201509787.png" deleted file mode 100644 index 11dbdebe96326e14b78b6b7966ccb0c6a0f38de6..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817201509787.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817203215946.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817203215946.png" deleted file mode 100644 index 8065486d165b1732cdb2bd53c1633c39129d4d17..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817203215946.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817220655304.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817220655304.png" deleted file mode 100644 index c754b98d722629325ea099a610ed1f55e92b44d0..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817220655304.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817231901642.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817231901642.png" deleted file mode 100644 index 6228ea10d4457d4238108c3278a2986e0ad5686f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817231901642.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817231925741.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817231925741.png" deleted file mode 100644 index 9323d9c776bfdc8d4d27840a171b14cce42ab184..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817231925741.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817231955977.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817231955977.png" deleted file mode 100644 index 3dda81242ae453c0a670ad3a862a8b5c088b5b6a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817231955977.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817232207720.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817232207720.png" deleted file mode 100644 index aed95764e7e3d1e583054d7106563bdc6e2f5246..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817232207720.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817232228576.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817232228576.png" deleted file mode 100644 index 41f67d92169180073da3416d9f216d28a19da021..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817232228576.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817234244901.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817234244901.png" deleted file mode 100644 index cc3fc17744476167703836e8fdcb88f466820eca..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817234244901.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817234308100.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817234308100.png" deleted file mode 100644 index cc3fc17744476167703836e8fdcb88f466820eca..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817234308100.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817234322236.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817234322236.png" deleted file mode 100644 index 22ab21a0cbb663322c53ea29928fbe0cb03c2307..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/figures/image-20260817234322236.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day1.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day1.md" deleted file mode 100644 index 7df56e95843f1474d04c4ba1edf9214d98fd25dd..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day1.md" +++ /dev/null @@ -1,162 +0,0 @@ -# RTT-学习日志-Day1 - -## markdown语法学习 - -### 代码插入 - -``` -#include - -int main() -{ -return 0; -} -``` - -### 表格插入相关 - - - -| --- | --- | -| ---- | ---- | -| | | - -### 其他语法 - -**加粗语法** - -`高亮语法` - -图像插入![image-20260817100636809](figures/image-20260817100636809.png) - - - - - -## 关于RTT-Stdio - -### 任务一rtt断点调试 - -![image-20260817102123446](figures/image-20260817102123446.png) - -## 星火一号 - -### 星火一号工程创建 - -#### 端口确认 - - - -![image-20260817102559963](figures/image-20260817102559963.png) - -#### 新建项目 - - - -![image-20260817103806695](figures/image-20260817103806695.png) - -![image-20260817104345415](figures/image-20260817104345415.png) - -#### 任务三 rt_kprintf() - -![image-20260817104204439](figures/image-20260817104204439.png) - -![image-20260817104221356](figures/image-20260817104221356.png) - -![image-20260817104255704](figures/image-20260817104255704.png) - -# GIT的使用 - -## 创建仓库 - -![image-20260817193850149](figures/image-20260817193850149.png) - -·![image-20260817195708890](figures/image-20260817195708890.png) - -## git clone - - - -![image-20260817200541589](figures/image-20260817200541589.png) - - - - - -![image-20260817200633114](figures/image-20260817200633114.png) - - - -## GIT 初始化相关 - -### 邮箱用户相关 - -user 和邮箱 我的电脑之前已经配置好,故这部分跳过 - - - -### 远程密钥相关 - -#### 输入指令 - -```$ ssh-keygen -t rsa -b 4096 -C "1394546446@qq.com" -$ ssh-keygen -t rsa -b 4096 -C "1394546446@qq.com" -``` - - - -![image-20260817201509787](figures/image-20260817201509787.png) - -#### 查看密钥 - -``` -cat /c/Users/13945/.ssh/id_rsa.pub -``` - -![image-20260817231925741](figures/image-20260817231925741.png) - -#### 设置中将公钥配置进入gitee - -![image-20260817231901642](figures/image-20260817231901642.png) - -#### 远程测试ssh gitee - -``` -ssh -T git@gitee.com -``` - - - -![image-20260817231955977](figures/image-20260817231955977.png) - -#### git 上传指令 - -``` -git init //初始化仓库 - -git add 文件名 //添加和提交 - -git commit -m "提交信息" - -git push -``` - - - - - -![image-20260817220655304](figures/image-20260817220655304.png) - -## GIT pullrequest - -git原始命令行老久不用生疏了,我直接使用我之前使用的sourcetree了。 - -将gitee先将仓库进行fork - -拉取到本地然后使用source tree同步 - -![image-20260817232207720](figures/image-20260817232207720.png) - -然后可以看见提交状态和相关信息 - -![image-20260817232228576](figures/image-20260817232228576.png) \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817100636809.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817100636809.png" deleted file mode 100644 index a65f8254bb544acc84f1770829ad3fa8bc2089b5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817100636809.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817102123446.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817102123446.png" deleted file mode 100644 index 103a6145be8c1e149e33e2f5f6f93cb4708d990c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817102123446.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817102500051.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817102500051.png" deleted file mode 100644 index 1f6adf3ea4c9b1dbf2c924391631d789b81b751d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817102500051.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817102559963.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817102559963.png" deleted file mode 100644 index 856b4315f5960da31589eaa1840a462e852e6c5a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817102559963.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817103806695.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817103806695.png" deleted file mode 100644 index 086dea3c02458317e44cc4d42adf1e8a65460ade..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817103806695.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104204439.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104204439.png" deleted file mode 100644 index bbe57fde50e52afffb8b4840ff059e34d6edb473..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104204439.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104221356.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104221356.png" deleted file mode 100644 index 8c834a7585b87dbef84ec51e145324bba7017acd..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104221356.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104255704.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104255704.png" deleted file mode 100644 index a82638d9a2b7bb4cbe180ef5c0417d758394672b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104255704.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104345415.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104345415.png" deleted file mode 100644 index 444b255a804d8d1630256613af2272b071da14d3..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817104345415.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817193850149.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817193850149.png" deleted file mode 100644 index 525e3b6be805d888637de58fa7ab81d3e6f74315..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817193850149.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817195708890.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817195708890.png" deleted file mode 100644 index f85939d0f078012bfd136007df3385ab269146fa..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817195708890.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817195720415.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817195720415.png" deleted file mode 100644 index 285de744486863c2547132ab31b5ed0461c2d880..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817195720415.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817200541589.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817200541589.png" deleted file mode 100644 index 512dcf9f3b88a634be9933c28993303f7a24ad37..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817200541589.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817200633114.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817200633114.png" deleted file mode 100644 index 2cd35454c1c0e9e92efc89ac9b8affa8603204ef..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817200633114.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817201509787.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817201509787.png" deleted file mode 100644 index 11dbdebe96326e14b78b6b7966ccb0c6a0f38de6..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817201509787.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817203215946.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817203215946.png" deleted file mode 100644 index 8065486d165b1732cdb2bd53c1633c39129d4d17..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817203215946.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817220655304.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817220655304.png" deleted file mode 100644 index c754b98d722629325ea099a610ed1f55e92b44d0..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817220655304.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817231901642.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817231901642.png" deleted file mode 100644 index 6228ea10d4457d4238108c3278a2986e0ad5686f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817231901642.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817231925741.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817231925741.png" deleted file mode 100644 index 9323d9c776bfdc8d4d27840a171b14cce42ab184..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817231925741.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817231955977.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817231955977.png" deleted file mode 100644 index 3dda81242ae453c0a670ad3a862a8b5c088b5b6a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817231955977.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817232207720.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817232207720.png" deleted file mode 100644 index aed95764e7e3d1e583054d7106563bdc6e2f5246..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817232207720.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817232228576.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817232228576.png" deleted file mode 100644 index 41f67d92169180073da3416d9f216d28a19da021..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817232228576.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817234244901.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817234244901.png" deleted file mode 100644 index cc3fc17744476167703836e8fdcb88f466820eca..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817234244901.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817234308100.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817234308100.png" deleted file mode 100644 index cc3fc17744476167703836e8fdcb88f466820eca..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817234308100.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817234322236.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817234322236.png" deleted file mode 100644 index 22ab21a0cbb663322c53ea29928fbe0cb03c2307..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/figures/image-20260817234322236.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/01_parking.c" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/01_parking.c" deleted file mode 100644 index b19bfaed573f8583afe8e98fc9db51022a5dba6c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/01_parking.c" +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 练习一:停车场车位管理(计数信号量) - * - * 场景:停车场只有 3 个车位,5 辆车尝试进入。 - * 使用一个初始值为 3 的计数信号量管理有限的车位资源; - * 每辆车线程申请车位(2 秒超时),获得车位后进入、停留一段时间后离开并释放车位。 - * 任意时刻进入停车场的车辆数由信号量保证不超过 3。 - * - * MSH 命令:parking_demo - * - * 思考题:为什么使用计数信号量而不是互斥量? - * 答:互斥量是"谁持有谁释放"的排他锁,只允许一个持有者; - * 而车位是允许多个(最多 3 个)持有者的有限资源,计数信号量的计数值 - * 正好表示"剩余资源数量",适用于资源池/容量受限的场景。 - */ - -#include - -#define PARK_SPACES 3 /* 车位总数 */ -#define PARK_CAR_NUM 5 /* 车辆数量 */ -#define PARK_WAIT_TICKS 2000 /* 申请车位超时时间(tick,1 tick = 1 ms) */ -#define PARK_RETRY_TICKS 500 /* 超时后重新申请前的等待时间 */ -#define PARK_STAY_TICKS 1500 /* 车辆在停车场内的停留时间 */ - -static rt_sem_t s_park_sem = RT_NULL; /* 车位信号量,初值 = 3 */ -static rt_sem_t s_done_sem = RT_NULL; /* 车辆完成计数信号量,用于命令线程等待 */ -static volatile rt_uint8_t s_used = 0; /* 当前占用车位数(仅用于显示) */ -static volatile rt_bool_t s_running = RT_FALSE; /* 防重复启动 */ - -/* 车辆线程:申请车位 -> 进入 -> 停留 -> 离开 -> 释放车位 */ -static void car_thread(void *param) -{ - rt_ubase_t id = (rt_ubase_t)param; - - while (RT_TRUE) - { - /* 申请车位,2 秒内未获得则超时 */ - if (rt_sem_take(s_park_sem, PARK_WAIT_TICKS) != RT_EOK) - { - rt_kprintf("car%d waiting for a space\n", id); - rt_thread_mdelay(PARK_RETRY_TICKS); - continue; - } - - /* 获得车位,进入停车场 */ - rt_enter_critical(); - s_used++; - rt_ubase_t used = s_used; - rt_exit_critical(); - rt_kprintf("car%d entered, used spaces: %d\n", id, used); - - /* 停留一段时间 */ - rt_thread_mdelay(PARK_STAY_TICKS); - - /* 离开停车场,释放车位 */ - rt_kprintf("car%d left\n", id); - rt_enter_critical(); - s_used--; - rt_exit_critical(); - rt_sem_release(s_park_sem); - break; - } - - /* 通知命令线程:本车已结束 */ - rt_sem_release(s_done_sem); -} - -/* MSH 命令:parking_demo */ -static int parking_demo(int argc, char *argv[]) -{ - rt_thread_t cars[PARK_CAR_NUM] = {RT_NULL}; - rt_uint8_t i; - rt_err_t ret = RT_EOK; - - if (s_running) - { - rt_kprintf("parking_demo is already running\n"); - return -1; - } - s_running = RT_TRUE; - s_used = 0; - - /* 1. 创建初始值为 3 的计数信号量 */ - s_park_sem = rt_sem_create("park_sem", PARK_SPACES, RT_IPC_FLAG_FIFO); - if (s_park_sem == RT_NULL) - { - rt_kprintf("create parking semaphore failed\n"); - s_running = RT_FALSE; - return -1; - } - - /* 2. 创建完成计数信号量 */ - s_done_sem = rt_sem_create("pk_done", 0, RT_IPC_FLAG_FIFO); - if (s_done_sem == RT_NULL) - { - rt_kprintf("create done semaphore failed\n"); - rt_sem_delete(s_park_sem); - s_park_sem = RT_NULL; - s_running = RT_FALSE; - return -1; - } - - /* 3. 创建 5 个车辆线程并检查创建结果 */ - for (i = 0; i < PARK_CAR_NUM; i++) - { - char name[RT_NAME_MAX]; - rt_snprintf(name, sizeof(name), "car%d", i + 1); - cars[i] = rt_thread_create(name, car_thread, (void *)(rt_ubase_t)(i + 1), - 1024, 8, 10); - if (cars[i] == RT_NULL) - { - rt_kprintf("create thread %s failed\n", name); - ret = -RT_ENOMEM; - break; - } - } - - if (ret == RT_EOK) - { - /* 4. 启动所有车辆线程 */ - for (i = 0; i < PARK_CAR_NUM; i++) - { - rt_thread_startup(cars[i]); - } - - /* 5. 等待全部车辆完成(线程结束后由 idle 线程自动回收) */ - for (i = 0; i < PARK_CAR_NUM; i++) - { - if (rt_sem_take(s_done_sem, 20000) != RT_EOK) - { - rt_kprintf("parking_demo wait timeout\n"); - break; - } - } - } - else - { - /* 创建失败:回收已创建的线程与信号量 */ - for (i = 0; i < PARK_CAR_NUM; i++) - { - if (cars[i] != RT_NULL) - { - rt_thread_delete(cars[i]); - } - } - } - - rt_sem_delete(s_park_sem); - rt_sem_delete(s_done_sem); - s_park_sem = RT_NULL; - s_done_sem = RT_NULL; - rt_kprintf("parking_demo finished\n"); - s_running = RT_FALSE; - - return ret; -} -MSH_CMD_EXPORT(parking_demo, parking lot demo with counting semaphore); diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/02_ticket.c" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/02_ticket.c" deleted file mode 100644 index 03dacb8d8a135385324d5d5d9e0e1f73b8ac2c15..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/02_ticket.c" +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 练习二:多窗口售票系统(互斥量) - * - * 场景:窗口 A、窗口 B 同时出售同一批 20 张车票(票号从 20 递减到 1)。 - * 使用互斥量保护"检查余票 -> 读取票号 -> 修改余票"的完整售票过程, - * 保证票号不重复、余票不为负,20 张票全部售出后两个窗口自动退出。 - * - * MSH 命令:ticket_demo - * - * 对比实验:把 TICKET_USE_MUTEX 改为 0 重新编译,可复现不加互斥量时的 - * 重复售票 / 余票负数问题(TICKET_RACE_DELAY_MS 会放大竞争窗口)。 - * - * 思考题:为什么只给 ticket_count-- 加锁仍可能不安全? - * 答:售票的完整操作是"检查余票 -> 读票号 -> 减一",三者必须作为一个原子 - * 临界区。如果只对 `ticket_count--` 加锁,两个线程可能同时通过 - * `if (ticket_count > 0)` 检查(此时只剩 1 张票),然后各自执行减一, - * 导致同一张票被卖出两次、余票变为 -1。 - */ - -#include - -#define TICKET_TOTAL 20 /* 车票总数 */ - -/* 对比实验开关: - * 1 = 使用互斥量保护完整售票过程(正确版本,默认) - * 0 = 不加互斥量,观察重复售票 / 余票错误 */ -#define TICKET_USE_MUTEX 1 - -/* 读取余票与修改余票之间的延时(ms),用于无锁实验放大竞争窗口 */ -#if TICKET_USE_MUTEX -#define TICKET_RACE_DELAY_MS 1 -#else -#define TICKET_RACE_DELAY_MS 5 -#endif - -static rt_mutex_t s_ticket_mtx = RT_NULL; /* 售票互斥量 */ -static rt_sem_t s_done_sem = RT_NULL; /* 窗口完成计数信号量 */ -static volatile int s_remaining = 0; /* 剩余票数(全局共享) */ -static volatile rt_bool_t s_running = RT_FALSE; /* 防重复启动 */ - -/* 出售一张票:成功返回票号,售完返回 0 */ -static int ticket_sell_one(void) -{ - int id = 0; - -#if TICKET_USE_MUTEX - rt_mutex_take(s_ticket_mtx, RT_WAITING_FOREVER); -#endif - - if (s_remaining > 0) - { - id = s_remaining; /* 检查余票并读取票号 */ - rt_thread_mdelay(TICKET_RACE_DELAY_MS); /* 读取与修改之间的延时 */ - s_remaining = s_remaining - 1; /* 修改余票 */ - } - -#if TICKET_USE_MUTEX - rt_mutex_release(s_ticket_mtx); -#endif - - return id; -} - -/* 售票窗口线程:循环售票,售完自动退出 */ -static void window_thread(void *param) -{ - rt_ubase_t win = (rt_ubase_t)param; - - while (RT_TRUE) - { - int id = ticket_sell_one(); - if (id == 0) - { - break; /* 无票可售,退出 */ - } - rt_kprintf("window %c sold ticket %d\n", (char)win, id); - rt_thread_mdelay(2); /* 出票间隔 */ - } - - /* 通知命令线程:本窗口已结束 */ - rt_sem_release(s_done_sem); -} - -/* MSH 命令:ticket_demo */ -static int ticket_demo(int argc, char *argv[]) -{ - rt_thread_t win_a = RT_NULL; - rt_thread_t win_b = RT_NULL; - - if (s_running) - { - rt_kprintf("ticket_demo is already running\n"); - return -1; - } - s_running = RT_TRUE; - s_remaining = TICKET_TOTAL; - - /* 1. 创建互斥量 */ - s_ticket_mtx = rt_mutex_create("tick_mtx", RT_IPC_FLAG_FIFO); - if (s_ticket_mtx == RT_NULL) - { - rt_kprintf("create ticket mutex failed\n"); - s_running = RT_FALSE; - return -1; - } - - /* 2. 创建完成计数信号量 */ - s_done_sem = rt_sem_create("tk_done", 0, RT_IPC_FLAG_FIFO); - if (s_done_sem == RT_NULL) - { - rt_kprintf("create done semaphore failed\n"); - rt_mutex_delete(s_ticket_mtx); - s_ticket_mtx = RT_NULL; - s_running = RT_FALSE; - return -1; - } - - /* 3. 创建两个售票窗口线程并检查创建结果 */ - win_a = rt_thread_create("win_a", window_thread, (void *)(rt_ubase_t)'A', - 1024, 8, 10); - win_b = rt_thread_create("win_b", window_thread, (void *)(rt_ubase_t)'B', - 1024, 8, 10); - if (win_a == RT_NULL || win_b == RT_NULL) - { - rt_kprintf("create window thread failed\n"); - if (win_a != RT_NULL) rt_thread_delete(win_a); - if (win_b != RT_NULL) rt_thread_delete(win_b); - rt_mutex_delete(s_ticket_mtx); - rt_sem_delete(s_done_sem); - s_ticket_mtx = RT_NULL; - s_done_sem = RT_NULL; - s_running = RT_FALSE; - return -1; - } - - /* 4. 启动两个窗口线程 */ - rt_thread_startup(win_a); - rt_thread_startup(win_b); - - /* 5. 等待两个窗口售完退出 */ - if (rt_sem_take(s_done_sem, 20000) != RT_EOK) - { - rt_kprintf("ticket_demo wait timeout\n"); - } - if (rt_sem_take(s_done_sem, 20000) != RT_EOK) - { - rt_kprintf("ticket_demo wait timeout\n"); - } - - /* 6. 打印结果并释放资源 */ - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: %d\n", s_remaining); - - rt_mutex_delete(s_ticket_mtx); - rt_sem_delete(s_done_sem); - s_ticket_mtx = RT_NULL; - s_done_sem = RT_NULL; - s_running = RT_FALSE; - - return 0; -} -MSH_CMD_EXPORT(ticket_demo, multi-window ticket selling with mutex); diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/03_startup.c" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/03_startup.c" deleted file mode 100644 index 6032c287e7fa9638cdabb7e5926b43c72ae64abd..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/03_startup.c" +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 练习三:系统启动条件检查(事件集) - * - * 场景:设备启动前需要完成网络、传感器、存储三个模块的初始化, - * 三个模块全部就绪后主业务线程才能启动。 - * 使用事件集:每个初始化线程完成后发送对应的就绪事件位, - * 主业务线程以 AND 模式等待三个事件位全部到达; - * 另做一次超时测试:存储模块需 5 秒,主业务线程每次最多等 3 秒, - * 超时后打印尚未完成的模块并再次等待。 - * - * MSH 命令:startup_demo - * - * 思考题:如果用三个独立信号量实现,与事件集相比有什么区别? - * 答:事件集用一个对象即可等待"多个条件同时满足",且能以 AND/OR 模式灵活 - * 组合、还能一次性查询已就绪的子集;用三个信号量则需要分别 take, - * 无法表达"多个条件同时满足"的原子判断,代码也更繁琐、易出错。 - */ - -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -/* 各模块初始化时间(tick) */ -#define INIT_NET_TICKS 1000 -#define INIT_SENSOR_TICKS 2000 -#define INIT_STORAGE_TICKS 5000 /* 超时测试:存储模块较慢 */ - -/* 主业务线程单次等待上限(tick) */ -#define BUSINESS_WAIT_TICKS 3000 - -static rt_event_t s_boot_evt = RT_NULL; /* 启动事件集 */ -static rt_sem_t s_done_sem = RT_NULL; /* 业务线程完成信号量 */ -static volatile rt_bool_t s_running = RT_FALSE; /* 防重复启动 */ - -/* 网络初始化线程 */ -static void init_net_thread(void *param) -{ - rt_thread_mdelay(INIT_NET_TICKS); - rt_kprintf("network ready\n"); - rt_event_send(s_boot_evt, EVENT_NET_READY); -} - -/* 传感器初始化线程 */ -static void init_sensor_thread(void *param) -{ - rt_thread_mdelay(INIT_SENSOR_TICKS); - rt_kprintf("sensor ready\n"); - rt_event_send(s_boot_evt, EVENT_SENSOR_READY); -} - -/* 存储初始化线程 */ -static void init_storage_thread(void *param) -{ - rt_thread_mdelay(INIT_STORAGE_TICKS); - rt_kprintf("storage ready\n"); - rt_event_send(s_boot_evt, EVENT_STORAGE_READY); -} - -/* 主业务线程:AND 模式等待三个事件全部到达,超时则打印未就绪模块 */ -static void business_thread(void *param) -{ - rt_uint32_t recved = 0; - - while (RT_TRUE) - { - rt_err_t res = rt_event_recv(s_boot_evt, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - BUSINESS_WAIT_TICKS, &recved); - if (res == RT_EOK) - { - break; /* 三个模块全部就绪 */ - } - - /* 超时:查询当前已就绪的事件位,打印尚未完成的模块 */ - rt_event_recv(s_boot_evt, EVENT_ALL_READY, RT_EVENT_FLAG_OR, 0, &recved); - rt_uint32_t missing = EVENT_ALL_READY & (~recved); - if (missing & EVENT_NET_READY) - { - rt_kprintf("wait modules timeout, network is not ready\n"); - } - if (missing & EVENT_SENSOR_READY) - { - rt_kprintf("wait modules timeout, sensor is not ready\n"); - } - if (missing & EVENT_STORAGE_READY) - { - rt_kprintf("wait modules timeout, storage is not ready\n"); - } - } - - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - - /* 通知命令线程:业务线程已结束 */ - rt_sem_release(s_done_sem); -} - -/* MSH 命令:startup_demo */ -static int startup_demo(int argc, char *argv[]) -{ - rt_thread_t net = RT_NULL; - rt_thread_t sensor = RT_NULL; - rt_thread_t storage = RT_NULL; - rt_thread_t business = RT_NULL; - - if (s_running) - { - rt_kprintf("startup_demo is already running\n"); - return -1; - } - s_running = RT_TRUE; - - /* 1. 创建事件集 */ - s_boot_evt = rt_event_create("boot_evt", RT_IPC_FLAG_FIFO); - if (s_boot_evt == RT_NULL) - { - rt_kprintf("create boot event failed\n"); - s_running = RT_FALSE; - return -1; - } - - /* 2. 创建完成信号量 */ - s_done_sem = rt_sem_create("bt_done", 0, RT_IPC_FLAG_FIFO); - if (s_done_sem == RT_NULL) - { - rt_kprintf("create done semaphore failed\n"); - rt_event_delete(s_boot_evt); - s_boot_evt = RT_NULL; - s_running = RT_FALSE; - return -1; - } - - /* 3. 创建 3 个初始化线程 + 1 个主业务线程,并检查创建结果 */ - net = rt_thread_create("init_net", init_net_thread, RT_NULL, 1024, 8, 10); - sensor = rt_thread_create("init_sen", init_sensor_thread, RT_NULL, 1024, 8, 10); - storage = rt_thread_create("init_sto", init_storage_thread, RT_NULL, 1024, 8, 10); - business = rt_thread_create("business", business_thread, RT_NULL, 1024, 7, 10); - - if (net == RT_NULL || sensor == RT_NULL || storage == RT_NULL || business == RT_NULL) - { - rt_kprintf("create startup thread failed\n"); - if (net) rt_thread_delete(net); - if (sensor) rt_thread_delete(sensor); - if (storage) rt_thread_delete(storage); - if (business) rt_thread_delete(business); - rt_event_delete(s_boot_evt); - rt_sem_delete(s_done_sem); - s_boot_evt = RT_NULL; - s_done_sem = RT_NULL; - s_running = RT_FALSE; - return -1; - } - - /* 4. 启动所有线程 */ - rt_thread_startup(net); - rt_thread_startup(sensor); - rt_thread_startup(storage); - rt_thread_startup(business); - - /* 5. 等待主业务线程完成 */ - if (rt_sem_take(s_done_sem, 20000) != RT_EOK) - { - rt_kprintf("startup_demo wait timeout\n"); - } - - /* 6. 释放资源 */ - rt_event_delete(s_boot_evt); - rt_sem_delete(s_done_sem); - s_boot_evt = RT_NULL; - s_done_sem = RT_NULL; - s_running = RT_FALSE; - - return 0; -} -MSH_CMD_EXPORT(startup_demo, system startup check with event set); diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/04_package.c" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/04_package.c" deleted file mode 100644 index 702c02861482fa9eadb8d5e9c46d79afdac6b208..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/04_package.c" +++ /dev/null @@ -1,234 +0,0 @@ -/* - * 练习四:快递分拣中心(消息队列) - * - * 场景:两个收件线程不断产生快递消息,一个分拣线程从消息队列取出快递 - * 并按照目的地区域分类统计。 - * 使用消息队列(容量 5 条)在生产者与消费者之间传递快递信息; - * 队列满时发送线程不崩溃,而是记录发送失败次数; - * 两个发送线程全部完成后,由命令线程发送一条结束消息, - * 分拣线程收到结束消息后打印统计结果并退出。 - * - * MSH 命令:package_demo - * - * 注意:region 取值 1=华东(east)、2=华南(south)、3=华北(north); - * region = 0 为约定好的"结束消息"标记(正常快递不会使用该值)。 - */ - -#include - -#define PKG_QUEUE_CAP 5 /* 消息队列容量 */ -#define PKG_PER_SENDER 5 /* 每个发送线程产生的快递数量 */ -#define PKG_END_REGION 0 /* 结束消息标记(有效区域为 1~3) */ -#define PKG_BASE_A 1000 /* 发送线程 A 的快递编号基数 */ -#define PKG_BASE_B 2000 /* 发送线程 B 的快递编号基数 */ - -/* 快递消息结构 */ -struct package_msg -{ - rt_uint32_t id; /* 快递编号 */ - rt_uint8_t region; /* 目的地区域:1=华东 2=华南 3=华北 */ - rt_uint16_t weight; /* 重量(g) */ -}; - -static rt_mq_t s_pkg_mq = RT_NULL; /* 消息队列 */ -static rt_sem_t s_send_done = RT_NULL; /* 发送线程完成计数信号量 */ -static rt_sem_t s_sort_done = RT_NULL; /* 分拣线程完成信号量 */ -static volatile int s_fail_a = 0; /* 发送线程 A 的发送失败次数 */ -static volatile int s_fail_b = 0; /* 发送线程 B 的发送失败次数 */ -static volatile rt_bool_t s_running = RT_FALSE; /* 防重复启动 */ - -static const char *region_name(rt_uint8_t region) -{ - switch (region) - { - case 1: return "east"; - case 2: return "south"; - case 3: return "north"; - default: return "unknown"; - } -} - -/* 收件(发送)线程:产生 5 条快递消息,队列满时记录失败次数 */ -static void sender_thread(void *param) -{ - rt_uint32_t base = (rt_uint32_t)(rt_ubase_t)param; - int fail = 0; - int i; - - for (i = 1; i <= PKG_PER_SENDER; i++) - { - struct package_msg msg; - - msg.id = base + i; /* 编号不重复:1001~1005 / 2001~2005 */ - msg.region = (rt_uint8_t)(i % 3) + 1; /* 区域 1/2/3 循环 */ - msg.weight = (rt_uint16_t)(500 + (msg.id * 37) % 1000); - - if (rt_mq_send(s_pkg_mq, &msg, sizeof(msg)) != RT_EOK) - { - fail++; /* 队列已满:记录失败,不崩溃 */ - } - rt_thread_mdelay(20); /* 收件间隔 */ - } - - if (base == PKG_BASE_A) - { - s_fail_a = fail; - } - else - { - s_fail_b = fail; - } - - /* 通知命令线程:本发送线程已完成 */ - rt_sem_release(s_send_done); -} - -/* 分拣线程:接收快递消息并分类统计,收到结束消息后退出 */ -static void sorter_thread(void *param) -{ - int total = 0; - int weight_sum = 0; - int cnt[3] = {0, 0, 0}; - - while (RT_TRUE) - { - struct package_msg msg; - rt_err_t res = rt_mq_recv(s_pkg_mq, &msg, sizeof(msg), 2000); - - if (res != RT_EOK) - { - continue; /* 暂时没有消息,继续等待 */ - } - if (msg.region == PKG_END_REGION) - { - break; /* 收到结束消息,退出 */ - } - - total++; - if (msg.region >= 1 && msg.region <= 3) - { - cnt[msg.region - 1]++; - } - weight_sum += msg.weight; - rt_kprintf("package %d -> %s, weight: %d g\n", - msg.id, region_name(msg.region), msg.weight); - rt_thread_mdelay(50); /* 分拣耗时 */ - } - - /* 打印统计结果 */ - rt_kprintf("total packages: %d\n", total); - rt_kprintf("east: %d, south: %d, north: %d\n", cnt[0], cnt[1], cnt[2]); - rt_kprintf("total weight: %d g\n", weight_sum); - - /* 通知命令线程:分拣完成 */ - rt_sem_release(s_sort_done); -} - -/* MSH 命令:package_demo */ -static int package_demo(int argc, char *argv[]) -{ - rt_thread_t send_a = RT_NULL; - rt_thread_t send_b = RT_NULL; - rt_thread_t sorter = RT_NULL; - - if (s_running) - { - rt_kprintf("package_demo is already running\n"); - return -1; - } - s_running = RT_TRUE; - s_fail_a = 0; - s_fail_b = 0; - - /* 1. 创建容量为 5 的消息队列 */ - s_pkg_mq = rt_mq_create("pkg_mq", sizeof(struct package_msg), PKG_QUEUE_CAP, - RT_IPC_FLAG_FIFO); - if (s_pkg_mq == RT_NULL) - { - rt_kprintf("create package message queue failed\n"); - s_running = RT_FALSE; - return -1; - } - - /* 2. 创建两个完成信号量 */ - s_send_done = rt_sem_create("pk_send", 0, RT_IPC_FLAG_FIFO); - s_sort_done = rt_sem_create("pk_sort", 0, RT_IPC_FLAG_FIFO); - if (s_send_done == RT_NULL || s_sort_done == RT_NULL) - { - rt_kprintf("create done semaphore failed\n"); - if (s_send_done) rt_sem_delete(s_send_done); - if (s_sort_done) rt_sem_delete(s_sort_done); - rt_mq_delete(s_pkg_mq); - s_send_done = RT_NULL; - s_sort_done = RT_NULL; - s_pkg_mq = RT_NULL; - s_running = RT_FALSE; - return -1; - } - - /* 3. 创建 2 个发送线程 + 1 个分拣线程,并检查创建结果 */ - send_a = rt_thread_create("send_a", sender_thread, (void *)(rt_ubase_t)PKG_BASE_A, - 1024, 8, 10); - send_b = rt_thread_create("send_b", sender_thread, (void *)(rt_ubase_t)PKG_BASE_B, - 1024, 8, 10); - sorter = rt_thread_create("sorter", sorter_thread, RT_NULL, 1024, 9, 10); - if (send_a == RT_NULL || send_b == RT_NULL || sorter == RT_NULL) - { - rt_kprintf("create package thread failed\n"); - if (send_a) rt_thread_delete(send_a); - if (send_b) rt_thread_delete(send_b); - if (sorter) rt_thread_delete(sorter); - rt_sem_delete(s_send_done); - rt_sem_delete(s_sort_done); - rt_mq_delete(s_pkg_mq); - s_send_done = RT_NULL; - s_sort_done = RT_NULL; - s_pkg_mq = RT_NULL; - s_running = RT_FALSE; - return -1; - } - - /* 4. 启动分拣线程与两个发送线程 */ - rt_thread_startup(sorter); - rt_thread_startup(send_a); - rt_thread_startup(send_b); - - /* 5. 等待两个发送线程完成 */ - if (rt_sem_take(s_send_done, 20000) != RT_EOK) - { - rt_kprintf("package_demo wait sender timeout\n"); - } - if (rt_sem_take(s_send_done, 20000) != RT_EOK) - { - rt_kprintf("package_demo wait sender timeout\n"); - } - - /* 6. 两个发送线程完成后,发送一条结束消息(失败则重试,保证分拣线程能退出) */ - { - struct package_msg end_msg = {0, PKG_END_REGION, 0}; - while (rt_mq_send(s_pkg_mq, &end_msg, sizeof(end_msg)) != RT_EOK) - { - rt_thread_mdelay(100); - } - } - - /* 7. 等待分拣线程完成 */ - if (rt_sem_take(s_sort_done, 20000) != RT_EOK) - { - rt_kprintf("package_demo wait sorter timeout\n"); - } - - /* 8. 打印发送失败统计并释放资源 */ - rt_kprintf("send failed: %d\n", s_fail_a + s_fail_b); - - rt_mq_delete(s_pkg_mq); - rt_sem_delete(s_send_done); - rt_sem_delete(s_sort_done); - s_pkg_mq = RT_NULL; - s_send_done = RT_NULL; - s_sort_done = RT_NULL; - s_running = RT_FALSE; - - return 0; -} -MSH_CMD_EXPORT(package_demo, express sorting center with message queue); diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 2a4cd150a36417be2cf1ec5db22891a1b40cf50b..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,55 +0,0 @@ -# RT-Thread 线程间同步与通信 实验报告(Day3) - -> 实验环境:RT-Thread 4.1.1 + STM32F407ZG(RSOC2026_ipc 工程,GCC/scons 编译) -> 运行方式:烧录后在 MSH 终端输入对应命令,如 `parking_demo` - -| 练习 | 文件 | MSH 命令 | 核心机制 | -| --- | --- | --- | --- | -| 一、停车场车位管理 | `01_parking.c` | `parking_demo` | 计数信号量 | -| 二、多窗口售票系统 | `02_ticket.c` | `ticket_demo` | 互斥量 | -| 三、系统启动条件检查 | `03_startup.c` | `startup_demo` | 事件集(AND 模式) | -| 四、快递分拣中心 | `04_package.c` | `package_demo` | 消息队列 | - -## 练习一:停车场车位管理(计数信号量) - -- 3 个车位 → 计数信号量初值 3;5 辆车线程申请车位,2 秒内未获得则打印 - `carN waiting for a space` 并稍后重试;停留后释放车位。 -- 信号量计数值即剩余资源数,从机制上保证同时占用车位 ≤ 3。 -- 思考题:互斥量是"单持有者排他锁",而车位是允许最多 3 个持有者的有限资源, - 计数信号量的计数值天然表示剩余资源量,适合资源池场景。 - -## 练习二:多窗口售票系统(互斥量) - -- 窗口 A/B 共享全局余票变量,互斥量保护"检查余票 → 读票号 → 减一"完整临界区; - 售完自动退出,保证 20 张票不重号、余票不为负。 -- 对比实验:将 `02_ticket.c` 中 `TICKET_USE_MUTEX` 改为 0 重新编译, - 会在读余票与减一之间出现竞争窗口,可观察到重复票号 / 余票为负。 -- 思考题:只锁 `ticket_count--` 不够,因为"检查余票 > 0"与"减一"不是原子的, - 两个线程可能同时通过检查后各自减一,同一张票卖出两次、余票变 -1。 - -## 练习三:系统启动条件检查(事件集) - -- 网络(1s)/传感器(2s)/存储(5s)三个初始化线程各自发送就绪事件位; - 主业务线程用 `RT_EVENT_FLAG_AND` 等三个位齐备。 -- 超时测试:业务线程每次最多等 3s,超时后按未置位的事件位打印 - `wait modules timeout, storage is not ready`,再继续等待,直到全部就绪。 -- 思考题:三个独立信号量只能分别 take,无法原子地表达"多条件同时满足", - 且查询"哪些已完成"要逐个探测;事件集一个对象即可 AND/OR 组合 + 查询子集。 - -## 练习四:快递分拣中心(消息队列) - -- 消息队列容量 5;send_a / send_b 各产生 5 条快递(编号 1001~1005 / 2001~2005), - 区域按 1/2/3 循环(east/south/north); - 队列满时 `rt_mq_send` 返回失败,发送线程只计数不崩溃。 -- 两个发送线程全部完成后,命令线程发送一条结束消息(region=0 标记), - 分拣线程收到后打印总数、各区域数量、总重量并退出。 -- 输出示例:`package 1001 -> south, weight: 537 g`;结尾统计 - `total packages: N`、`east/south/north 计数`、`total weight: N g`、`send failed: N`。 - 注:队列满会导致部分快递发送失败(不送达),因此 `total packages` 可能小于 10, - `send failed` 反映实际失败次数(与本机调度时序有关)。 - -## 设计说明 - -- 每个命令可重复执行:任务结束后命令线程等待全部子线程完成, - 再删除 IPC 对象;运行期间重复输入命令会被拒绝(防重入标志)。 -- 所有线程与 IPC 对象均检查创建结果,失败时回收已创建资源。 diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\2543\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\2543\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index dc2fc3e9a7d594e594529dd1e2028f9005f59c79..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\2543\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,221 +0,0 @@ -# RT-Thread 线程间同步与通信课后作业 - -## 一、作业要求 - -要求: - -1. 不得直接复制 `kernel_samples` 示例后只修改变量名和打印内容。 -2. 每个练习至少创建两个线程。 -3. 所有线程和 IPC 对象都要检查创建结果。 -4. 程序需要通过 MSH 命令启动。 -5. 提交理论总结与实验报告(`README.md`)+ 源代码(`*.c`)。 - ---- - -## 练习一:停车场车位管理 - -### 场景 - -一个停车场只有 3 个车位,同时有 5 辆车尝试进入。车位属于有限资源,车辆只有获得车位后才能进入。 - -### 功能要求 - -1. 创建一个初始值为 3 的计数信号量,表示剩余车位。 -2. 创建 5 个车辆线程。 -3. 每辆车到达后申请一个车位。 -4. 获得车位后打印车辆编号和进入信息。 -5. 车辆停留一段时间后离开,并释放车位。 -6. 未在 2 秒内获得车位的车辆打印等待超时信息,稍后重新申请。 -7. 任意时刻进入停车场的车辆数不能超过 3。 - -### 示例输出 - -```text -car1 entered, used spaces: 1 -car2 entered, used spaces: 2 -car3 entered, used spaces: 3 -car4 waiting for a space -car2 left -car4 entered, used spaces: 3 -``` - -### 思考题 - -为什么这里使用计数信号量,而不是互斥量? - ---- - -## 练习二:多窗口售票系统 - -### 场景 - -两个售票窗口同时出售同一批车票。系统初始有 20 张票,每次只能由一个窗口修改剩余票数。 - -### 功能要求 - -1. 创建两个售票线程,分别代表窗口 A 和窗口 B。 -2. 使用一个全局变量保存剩余票数。 -3. 每个窗口循环完成以下操作: - - 检查是否还有余票; - - 将余票减 1; - - 记录售出的票号; - - 打印售票结果。 -4. 使用互斥量保护完整的售票过程。 -5. 售完后两个线程自动退出。 -6. 最终必须满足: - - 20 张票全部售出; - - 不出现相同票号; - - 剩余票数不能小于 0。 - -### 示例输出 - -```text -window A sold ticket 20 -window B sold ticket 19 -window B sold ticket 18 -... -all tickets sold -remaining tickets: 0 -``` - -### 对比实验 - -先暂时不使用互斥量,并在“读取余票”和“修改余票”之间加入短暂延时,观察是否会出现重复售票或余票错误。 - -随后加入互斥量并再次运行,对比两次结果。 - -### 思考题 - -为什么只给 `ticket_count--` 加锁仍可能不安全? - ---- - -## 练习三:系统启动条件检查 - -### 场景 - -设备启动前需要完成三个独立模块的初始化: - -- 网络模块初始化; -- 传感器模块初始化; -- 存储模块初始化。 - -只有三个模块全部准备完成,主业务线程才能启动。 - -PS:只是举例,模拟即可。 - -### 功能要求 - -定义三个事件位: - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -``` - -创建以下线程: - -- 网络初始化线程; -- 传感器初始化线程; -- 存储初始化线程; -- 主业务线程。 - -每个初始化线程经过不同时间的延时后,发送对应的就绪事件。 - -主业务线程必须使用 AND 模式等待三个事件全部到达,然后打印: - -```text -all modules are ready -business task started -``` - -另外增加一次超时测试: - -- 将存储模块初始化时间设置为 5 秒; -- 主业务线程最多等待 3 秒; -- 超时后打印尚未完成的模块,并再次等待。 - -### 示例输出 - -```text -network ready -sensor ready -wait modules timeout, storage is not ready -storage ready -all modules are ready -business task started -``` - -### 思考题 - -如果用三个独立信号量实现这个功能,与使用事件集相比有什么区别? - ---- - -## 练习四:快递分拣中心 - -### 场景 - -两个收件线程不断产生快递信息,一个分拣线程从消息队列中取出快递并按照目的地区域分类。 - -### 消息结构 - -```c -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -``` - -其中: - -```text -region = 1:华东 -region = 2:华南 -region = 3:华北 -``` - -### 功能要求 - -1. 创建一个消息队列,队列容量为 5 条消息。 -2. 创建两个发件线程,每个线程产生 5 条快递消息。 -3. 快递编号不得重复。 -4. 分拣线程接收消息,并打印快递编号、区域和重量。 -5. 分拣完成后统计: - - 总快递数量; - - 各区域快递数量; - - 所有快递的总重量。 -6. 当队列已满时,发送线程不得崩溃,应记录发送失败次数。 -7. 两个发送线程完成后,向分拣线程发送一条结束消息。 -8. 分拣线程收到结束消息后打印统计结果并退出。 - -### 示例输出 - -```text -package 1001 -> east, weight: 1200 g -package 2001 -> south, weight: 850 g -package 1002 -> north, weight: 960 g -... -total packages: 10 -east: 4, south: 3, north: 3 -total weight: 10250 g -send failed: 0 -``` - ---- - -## 二、MSH 命令要求 - -每个练习导出一个独立命令: - -```text -parking_demo -ticket_demo -startup_demo -package_demo -``` - -重复执行命令时,不得因同名线程或 IPC 对象残留而发生异常。可以在任务结束后主动释放资源,也可以检测任务是否仍在运行并拒绝重复启动。 diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 22660faee13b853eed2bba6c7cd6aa9aa0e7934d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,298 +0,0 @@ -# 【DAY2】RT-Thread 启动流程、内核基础与多线程编程 - -- **目标平台**:STM32F407 星火号(stm32f407-rtspark) -- **工程**:RT-Thread Studio 工程 `02-thread_sample`(STM32F407ZG) -- **调试方式**:ST-Link + 断点调试,观察实际调用链验证理论 - ---- - -# Part 1 理论知识梳理 - -## 一、STM32F407 启动流程与 RT-Thread 引导机制 - -### 1.1 启动路径:从上电复位 `Reset_Handler` 到 `main()` 的完整调用链 - -经 ST-Link 断点调试验证,实际调用链如下: - -``` -上电复位 - → Reset_Handler (startup_stm32f407xx.S) - → SystemInit() (system_stm32f4xx.c,配置时钟树) - → 初始化 .data 段、清零 .bss 段 - → 进入 C 环境 - → $Sub$$main() (components.c,MDK 下拦截 main 调用) - → rtthread_startup() (RT-Thread 系统初始化入口) - → rt_hw_interrupt_disable() 关中断,初始化期间不允许被抢占 - → rt_hw_board_init() 板级硬件初始化(时钟、串口、内存) - → rt_components_board_init() 板级组件自动初始化 - → rt_show_version() 打印版本信息 - → rt_system_timer_init() 系统定时器初始化 - → rt_system_scheduler_init() 调度器初始化 - → rt_system_signal_init() 信号模块初始化 - → rt_application_init() 创建 main 线程(main_thread_entry) - → rt_system_timer_thread_init() 创建定时器线程(rt_thread_timer_entry) - → rt_thread_idle_init() 创建空闲线程(rt_thread_idle_entry) - → rt_system_scheduler_start() 启动调度器 - → main_thread_entry() (调度器调度运行 main 线程) - → main() (用户应用程序入口) -``` - -关键点说明: - -1. **启动文件分编译器有不同入口**:MDK 下是 `$Sub$$main()`(先于 main 执行)、IAR 是 `_low_level_init()`、GCC 是 `entry()`,最终都汇聚到 `rtthread_startup()`。 -2. **`rtthread_startup()` 是整个 RT-Thread 初始化的总导演**,负责完成硬件初始化、系统三大模块(定时器/调度器/信号)初始化,并创建三个系统线程:main 线程、定时器线程、空闲线程。 -3. **调度器启动后**,main 线程按优先级被调度运行,其入口函数 `main_thread_entry()` 最终调用用户的 `main()`。 -4. 调试时通过 Call Stack 窗口可以观察到:停在 `rtthread_startup()` 时,栈帧为 `rtthread_startup ← $Sub$$main ← Reset_Handler`;停在 `main()` 时,栈帧为 `main ← main_thread_entry`,两部分拼接即为完整调用链。 - -### 1.2 自动初始化原理:`INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` 的底层实现 - -**设计思想**:用户不需要在 `main()` 里手动逐个调用初始化函数,而是通过宏把初始化函数"注册"到指定内存段,系统在启动时自动按顺序调用。 - -**宏定义**(rt-thread/include/rtdef.h): - -```c -#define INIT_EXPORT(fn, level) \ - RT_USED const init_fn_t __rt_init_##fn RT_SECTION(".rti_fn." level) = fn - -/* board init routines will be called in board_init() function */ -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") -#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2") -#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3") -#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4") -#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") -``` - -**底层实现原理**分三步: - -1. **注册(编译期)**:`INIT_BOARD_EXPORT(fn)` 展开后,把函数指针 `fn` 放进名为 `.rti_fn.1` 的段;`INIT_APP_EXPORT(fn)` 放进 `.rti_fn.6`。数字(1~6)表示初始化优先级,数字越小越先执行。这里用的是**段(section)**机制,不是直接调用。 - -2. **链接(链接期)**:链接脚本(link.lds)把各目标文件的 `.rti_fn*` 段按名字排序后连续排列: - -```lds -__rt_init_start = .; -KEEP(*(SORT(.rti_fn*))) /* 按段名排序,保证 1→6 顺序 */ -__rt_init_end = .; -``` - - 段内还有哨兵函数(`rti_start` 段 "0"、`rti_board_start` 段 "0.end"、`rti_end` 段 "6.end"),用于标记遍历的起止边界。 - -3. **调用(运行期)**:`rt_components_init()`(components.c)遍历这两个符号之间的函数指针,逐个执行: - -```c -void rt_components_init(void) -{ - volatile const init_fn_t *fn_ptr; - for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++) - { - (*fn_ptr)(); /* 依次调用所有注册的初始化函数 */ - } -} -``` - -**执行时机**: -- `INIT_BOARD_EXPORT` 注册的函数在 `rt_components_board_init()`(rtthread_startup 中较早调用)里执行,适合初始化板级外设。 -- `INIT_APP_EXPORT` 注册的函数在 `rt_components_init()`(系统初始化完成后)里执行,适合应用层初始化。 -- 整个机制让"组件/驱动/应用"各自注册、系统统一调度,模块之间解耦,新增模块无需改动 main()。 - -## 二、内核基础与对象模型 - -### 2.1 对象容器与控制块 - -**面向对象设计思想**:RT-Thread 用 C 语言模拟面向对象——所有内核对象(线程、信号量、互斥量、事件、消息队列、定时器、设备等)都继承自基类 `struct rt_object`(含 name、type、flag、list 四个成员),统一挂接在内核对象容器(链表)中管理,并通过"类型-名称"进行查找与统一操作。 - -**线程控制块 `struct rt_thread` 关键成员**: - -| 成员 | 作用 | -|---|---| -| `parent` | 继承的 `rt_object` 基类,实现对象模型 | -| `stat` | 线程状态(初始/就绪/运行/挂起/关闭),调度器据此决策 | -| `current_priority` | 当前优先级(可能被临时改变,如优先级继承) | -| `init_priority` | 初始优先级(线程创建时设定的原始优先级) | -| `init_tick` / `remaining_tick` | 时间片初始值 / 剩余值,时间片轮转调度依据 | -| `sp` | 栈指针,保存线程上下文,线程切换的核心 | -| `entry` / `parameter` | 线程入口函数及其参数 | -| `stack_addr` / `stack_size` | 线程栈地址与大小 | -| `thread_timer` | 内置线程定时器(用于延时/超时) | -| `cleanup` | 线程退出时的清理函数 | - -线程控制块定义形成线程内核对象后,链接到对象容器链表中统一管理,这是 RT-Thread 对象模型的核心体现。 - -### 2.2 内存分配方式:静态对象与动态对象对比 - -| 对比项 | 静态对象(`rt_thread_init`) | 动态对象(`rt_thread_create`) | -|---|---|---| -| 分配位置 | 编译期确定为全局变量(TCB + 栈) | 运行期从系统堆(heap)分配 | -| 分配方式 | 用户提供 TCB 指针和栈首地址 | 内核自动分配句柄 + 栈空间 | -| 生命周期 | 与程序共存亡 | 可动态创建、删除 | -| 释放机制 | 无需释放 | `rt_thread_delete()` 释放并回收 | -| 失败风险 | 无分配失败(编译期已确定) | 堆不足时可能创建失败 | -| 内存管理 | 无碎片问题 | 频繁创建/删除可能产生碎片 | - -**为什么高可靠场景强制使用静态线程**: -1. **确定性**:静态线程的内存(TCB、栈)在编译期就确定并分配好,运行期不存在"malloc 失败"的可能,行为完全可预期。 -2. **无碎片**:不会因频繁动态分配/释放产生内存碎片,长时间运行更稳定。 -3. **可审计**:静态对象的资源占用一目了然,便于做资源预算与安全认证(航天、工业、医疗等场景要求)。 -4. 动态线程适合资源灵活、追求开发效率的普通应用场景,但在资源紧张或对实时性、可靠性要求苛刻的系统里,静态线程是更稳妥的选择。 - -## 三、线程状态机与调度算法 - -### 3.1 五态模型 - -RT-Thread 线程有五种状态,状态转换关系如下: - -``` - rt_thread_create()/rt_thread_init() - │ - ▼ - 初始状态(RT_THREAD_INIT)──────────────┐ 不参与调度 - │ rt_thread_startup() - ▼ - 就绪状态(RT_THREAD_READY)───调度器选择──▶ 运行状态(RT_THREAD_RUNNING) - ▲ │ - │ rt_thread_resume() 等 │ rt_thread_delay()/rt_sem_take() 等 - └────────────────────────────────────▼ - 挂起状态(RT_THREAD_SUSPEND) 不参与调度 - │ rt_thread_exit()/delete()/detach() - ▼ - 关闭状态(RT_THREAD_CLOSE) 不参与调度 -``` - -各状态说明: - -| 状态 | 宏定义 | 说明 | -|---|---|---| -| 初始状态 | `RT_THREAD_INIT` | 线程刚创建还未开始运行,不参与调度 | -| 就绪状态 | `RT_THREAD_READY` | 按优先级排队等待执行,调度器选择后进入运行 | -| 运行状态 | `RT_THREAD_RUNNING` | 当前正在运行;单核系统中仅 `rt_thread_self()` 返回的线程处于此态 | -| 挂起状态 | `RT_THREAD_SUSPEND` | 阻塞态,因资源不可用或主动延时而挂起,不参与调度 | -| 关闭状态 | `RT_THREAD_CLOSE` | 线程运行结束,不参与调度 | - -**典型状态转换 API**: -- 创建/初始化:`rt_thread_create()` / `rt_thread_init()` -- 启动:`rt_thread_startup()`(初始 → 就绪) -- 挂起:`rt_thread_delay()` / `rt_sem_take()` / `rt_mutex_take()` / `rt_event_recv()` / `rt_mb_recv()` / `rt_mq_recv()`(运行 → 挂起) -- 恢复:`rt_thread_resume()` / `rt_sem_release()` / `rt_mutex_release()` / `rt_event_send()` / `rt_mb_send()` / `rt_mq_send()`(挂起 → 就绪) -- 结束:`rt_thread_exit()` / `rt_thread_delete()` / `rt_thread_detach()`(→ 关闭) - -### 3.2 调度机制剖析 - -**抢占式调度(Preemptive Scheduling)**: -- 每个线程有固定优先级(0~255,数值越小优先级越高)。 -- 当有比当前运行线程优先级更高的线程进入就绪态时,调度器**立即打断**当前线程,切换到高优先级线程运行(通过 PendSV 中断 + 上下文切换实现)。 -- 体现"高优先级线程优先"的实时性要求,是 RT-Thread 默认的调度方式。 - -**时间片轮转(Round-Robin)**: -- 针对**优先级相同**的就绪态线程:每个线程配置时间片(单位 OS Tick),调度器让它们轮流运行。 -- 线程运行完自己的时间片后,调度器自动切换到下一个同优先级就绪线程;时间片用尽会被挂到队列末尾。 -- 例:线程 A 时间片 10 tick、线程 B 时间片 5 tick,则 A 每次运行 10 个节拍、B 运行 5 个节拍,交替执行。 -- 若就绪队列中只有一个同优先级线程(或该优先级无竞争者),时间片不影响其连续运行。 - -**两种机制协同**:抢占式解决"不同优先级之间谁先跑",时间片轮转解决"同优先级之间怎么轮流",两者共同构成 RT-Thread 的调度核心。 - ---- - -# Part 2 多线程实战编程 - -## 一、程序结构设计 - -按作业要求,在工程 `applications` 目录下新建 `thread_study.c`,包含三个线程 + 自动初始化 + MSH 命令: - -| 线程 | 创建方式 | 优先级 | 时间片 | 栈大小 | 功能 | -|---|---|---|---|---|---| -| `th_led1` | 动态(`rt_thread_create`) | 16 | 5 ticks | 1024 | 翻转 LED_R(PF12),打印计数 + tick | -| `th_led2` | 静态(`rt_thread_init`) | 16 | 5 ticks | 1024 | 翻转 LED_B(PF11),打印计数 | -| `th_monitor` | 动态 | 10 | 5 ticks | 1024 | 打印线程状态/剩余栈;KEY0 挂起/恢复 th_led1 | - -- **自动加载**:`INIT_APP_EXPORT(thread_sample_init)`,无需在 main.c 中调用。 -- **MSH 命令**:`MSH_CMD_EXPORT_ALIAS(cmd_thread_dump, thread_dump, ...)` 导出线程查看命令。 - -## 二、编译与烧录 - -``` -scons -j4 // 编译,零错误 -``` - -通过 ST-Link 烧录 `rt-thread.elf`(STM32_Programmer_CLI): - -``` -File download complete -Time elapsed during download operation: 00:00:01.681 -Application is running, Please Hold on... -Start operation achieved successfully -``` - -## 三、运行结果分析(串口日志) - -### 3.1 线程运行日志(COM21, 115200) - -``` -[D/main] Hello RT-Thread! count=776 -[monitor] th_led1 stat:2 remain_stack:852 -[monitor] th_led2 stat:2 remain_stack:852 -th_led2 count: 1548 -th_led1 count: 1545, tick: 777206 -th_led2 count: 1549 -th_led1 count: 1546, tick: 777709 -[D/main] Hello RT-Thread! count=777 -th_led2 count: 1550 -th_led1 count: 1547, tick: 778212 -th_led2 count: 1551 -th_led1 count: 1548, tick: 778715 -[D/main] Hello RT-Thread! count=778 -[monitor] th_led1 stat:2 remain_stack:852 -[monitor] th_led2 stat:2 remain_stack:852 -th_led2 count: 1552 -th_led1 count: 1549, tick: 779218 -th_led2 count: 1553 -th_led1 count: 1550, tick: 779721 -``` - -**分析**: -- `th_led1` 与 `th_led2` 同为优先级 16,日志中两者交替打印计数(1545→1548→1549...),且各自 `rt_thread_mdelay(500)` 主动让出 CPU,体现了**同优先级线程时间片/主动让出的交替调度**。 -- `th_led1` 打印 `tick`(如 777206),间隔约 500 tick(500ms),符合 `mdelay(500)` 设置。 -- `[monitor]` 每 2 秒打印一次(周期 2000ms),显示两个工作线程 `stat:2`(就绪态)与剩余栈 852 字节。 - -### 3.2 自定义 MSH 命令 `thread_dump` - -``` -msh >thread_dump -name status prio remain_stack ----------------------------------------- -th_led1 2 16 852 -th_led2 2 16 852 -th_monitor 2 10 836 -msh > -``` - -**分析**:命令通过 `rt_thread_find()` 按名称查找三个线程,打印其状态(2=就绪)、优先级、剩余栈空间。与 `INIT_APP_EXPORT` 自动加载的机制配合,验证了作业第 4 点要求。 - -### 3.3 系统线程列表 `list_thread` - -``` -msh >list_thread -thread pri status sp stack size max used left tick error --------- --- ------- ---------- ---------- ------ ---------- --- -th_monit 10 suspend 0x000000bc 0x00000400 25% 0x00000003 OK -th_led2 16 suspend 0x000000ac 0x00000400 22% 0x00000003 OK -th_led1 16 suspend 0x000000ac 0x00000400 23% 0x00000003 OK -tshell 20 running 0x000000cc 0x00001000 15% 0x00000002 OK -tidle0 31 ready 0x00000060 0x00000100 56% 0x0000001f OK -timer 4 suspend 0x00000080 0x00000200 25% 0x00000009 OK -main 10 suspend 0x000000b8 0x00000800 14% 0x0000000d OK -msh > -``` - -**分析**: -- 三个作业线程(th_led1/th_led2/th_monitor)全部创建成功并被调度器管理。 -- 优先级关系正确:th_monitor(10) > th_led1/th_led2(16) > tshell(20) > tidle0(31 最低,空闲线程)。 -- 线程栈使用率约 22%~25%,栈分配 1024 字节(0x400)充足。 -- main 线程(优先级 10)与定时器线程(timer,优先级 4)为系统自带。 - -## 四、实验结果总结 - -1. **多线程并发**:三个用户线程 + main 线程 + 系统线程同时运行,通过串口日志清晰可见各自独立的执行流。 -2. **动态 vs 静态线程**:th_led1 用 `rt_thread_create`(堆分配),th_led2 用 `rt_thread_init`(用户提供全局 TCB 和栈),两者运行效果一致,验证了作业 Part 1 2.2 节的理论。 -3. **抢占式 + 时间片**:th_monitor 优先级 10 能抢占优先级 16 的工作线程(监控打印插入到工作线程日志之间);th_led1/th_led2 同优先级交替运行。 -4. **自动初始化**:三个线程通过 `INIT_APP_EXPORT` 自动创建,main.c 无任何硬编码调用。 -5. **MSH 扩展**:`thread_dump` 命令可随时查看线程运行状态,方便调试。 diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 6225e2413a94a44e9490b7b9da37a7d7c6dd4d97..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-18 李文轩 Day2 homework: multi-thread demo - */ - -#include -#include -#include - -#define DBG_TAG "thread_study" -#define DBG_LVL DBG_LOG -#include - -/* 星火1号 LED / 按键引脚定义 */ -#define PIN_LED_R GET_PIN(F, 12) /* PF12 : 红色 LED */ -#define PIN_LED_B GET_PIN(F, 11) /* PF11 : 蓝色 LED */ -#define PIN_KEY0 GET_PIN(C, 0) /* PC0 : 按键 KEY0 */ - -/* th_led1 的全局句柄(动态线程,供监控线程挂起/恢复) */ -static rt_thread_t th_led1_tid = RT_NULL; - - -/* ==================== th_led1:动态线程(红色 LED) ==================== */ - -/* th_led1 线程入口:翻转红色 LED,打印计数和时间戳 */ -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - while (1) - { - rt_pin_write(PIN_LED_R, count % 2); /* 翻转 LED_R:奇偶交替 */ - rt_kprintf("th_led1 count: %d, tick: %d\n", count, rt_tick_get()); - count++; - rt_thread_mdelay(500); /* 每 500ms 翻转一次,顺便让出 CPU */ - } -} - -/* 创建并启动 th_led1 动态线程 */ -static int thread_sample_led1(void) -{ - th_led1_tid = rt_thread_create("th_led1", /* 线程名 */ - th_led1_entry, /* 入口函数 */ - RT_NULL, /* 参数 */ - 1024, /* 栈大小 1024 字节 */ - 16, /* 优先级 16 */ - 5); /* 时间片 5 ticks */ - if (th_led1_tid != RT_NULL) - { - rt_thread_startup(th_led1_tid); /* 启动线程 */ - LOG_D("th_led1 created"); - return RT_EOK; - } - else - { - LOG_E("th_led1 create failed!"); - return -RT_ERROR; - } -} - - -/* ==================== th_led2:静态线程(蓝色 LED) ==================== */ - -/* th_led2 静态资源:全局 TCB + 全局栈(编译期分配,用户提供) */ -static struct rt_thread th_led2; /* 线程控制块(全局) */ -static rt_uint8_t th_led2_stack[1024]; /* 线程栈(全局,1024 字节) */ - -/* th_led2 线程入口:翻转蓝色 LED,打印计数 */ -static void th_led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - - while (1) - { - rt_pin_write(PIN_LED_B, count % 2); /* 翻转 LED_B */ - rt_kprintf("th_led2 count: %d\n", count); - count++; - rt_thread_mdelay(500); - } -} - -/* 静态初始化并启动 th_led2 */ -static int thread_sample_led2(void) -{ - rt_err_t result; - - result = rt_thread_init(&th_led2, /* 线程控制块(用户提供) */ - "th_led2", /* 线程名 */ - th_led2_entry, /* 入口函数 */ - RT_NULL, /* 参数 */ - th_led2_stack, /* 栈首地址(用户提供!) */ - sizeof(th_led2_stack),/* 栈大小 1024 */ - 16, /* 优先级 16 */ - 5); /* 时间片 5 ticks */ - if (result == RT_EOK) - { - rt_thread_startup(&th_led2); /* 启动线程 */ - LOG_D("th_led2 created"); - return RT_EOK; - } - else - { - LOG_E("th_led2 init failed!"); - return -RT_ERROR; - } -} - - -/* ==================== th_monitor:监控线程 ==================== */ - -/* 计算线程剩余栈空间:用栈低地址指针与当前 sp 的差值(仅粗略参考) */ -static rt_uint32_t thread_stack_remain(rt_thread_t tid) -{ - rt_uint8_t *stack_bottom; - - if (tid == RT_NULL) - return 0; - - /* 栈从高地址向低地址增长,sp 离栈底越远,剩余越多 */ - stack_bottom = (rt_uint8_t *)tid->stack_addr; - return (rt_uint32_t)((rt_uint32_t)tid->sp - (rt_uint32_t)stack_bottom); -} - -/* th_monitor 监控线程:打印线程状态/栈 + 按键控制 th_led1 */ -static void th_monitor_entry(void *parameter) -{ - rt_thread_t led1 = th_led1_tid; - rt_thread_t led2 = &th_led2; - rt_bool_t led1_suspended = RT_FALSE; - - while (1) - { - /* 打印两个工作线程的状态与剩余栈空间 */ - rt_kprintf("[monitor] th_led1 stat:%d remain_stack:%d\n", - led1->stat, thread_stack_remain(led1)); - rt_kprintf("[monitor] th_led2 stat:%d remain_stack:%d\n", - led2->stat, thread_stack_remain(led2)); - - /* 检测 KEY0:按下(低电平)则挂起/恢复 th_led1 */ - if (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - rt_thread_mdelay(50); /* 消抖 */ - if (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - if (led1_suspended == RT_FALSE) - { - rt_thread_suspend(led1); - rt_schedule(); /* 手动触发调度 */ - led1_suspended = RT_TRUE; - LOG_D("KEY0: th_led1 suspended!"); - } - else - { - rt_thread_resume(led1); - led1_suspended = RT_FALSE; - LOG_D("KEY0: th_led1 resumed!"); - } - /* 等待按键释放,防止重复触发 */ - while (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - rt_thread_mdelay(10); - } - } - } - - rt_thread_mdelay(2000); /* 周期 2s */ - } -} - -/* 创建并启动 th_monitor 监控线程 */ -static int thread_sample_monitor(void) -{ - rt_thread_t tid = RT_NULL; - - tid = rt_thread_create("th_monitor", /* 线程名 */ - th_monitor_entry, /* 入口函数 */ - RT_NULL, /* 参数 */ - 1024, /* 栈大小 */ - 10, /* 优先级 10(高于工作线程) */ - 5); /* 时间片 */ - if (tid != RT_NULL) - { - rt_thread_startup(tid); - LOG_D("th_monitor created"); - return RT_EOK; - } - else - { - LOG_E("th_monitor create failed!"); - return -RT_ERROR; - } -} - - -/* ==================== MSH 命令:thread_dump ==================== */ - -/* 自定义查看命令:用 rt_thread_find 按名字查找并打印线程信息 */ -static void cmd_thread_dump(void) -{ - rt_thread_t tid1 = rt_thread_find("th_led1"); - rt_thread_t tid2 = rt_thread_find("th_led2"); - rt_thread_t tmon = rt_thread_find("th_monitor"); - - rt_kprintf("name status prio remain_stack\n"); - rt_kprintf("----------------------------------------\n"); - - if (tid1 != RT_NULL) - rt_kprintf("%-10s %-6d %-5d %d\n", "th_led1", tid1->stat, - tid1->current_priority, thread_stack_remain(tid1)); - if (tid2 != RT_NULL) - rt_kprintf("%-10s %-6d %-5d %d\n", "th_led2", tid2->stat, - tid2->current_priority, thread_stack_remain(tid2)); - if (tmon != RT_NULL) - rt_kprintf("%-10s %-6d %-5d %d\n", "th_monitor", tmon->stat, - tmon->current_priority, thread_stack_remain(tmon)); -} -MSH_CMD_EXPORT_ALIAS(cmd_thread_dump, thread_dump, dump thread info); - - -/* ==================== 自动初始化 ==================== */ - -/* 应用自动初始化:依次创建三个线程(INIT_APP_EXPORT,无需在 main 中调用) */ -static int thread_sample_init(void) -{ - /* 先创建两个工作线程,再创建监控线程(监控线程引用它们的句柄) */ - if (thread_sample_led1() != RT_EOK) - return -RT_ERROR; - if (thread_sample_led2() != RT_EOK) - return -RT_ERROR; - if (thread_sample_monitor() != RT_EOK) - return -RT_ERROR; - - LOG_D("thread study sample initialized"); - return RT_EOK; -} -INIT_APP_EXPORT(thread_sample_init); diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\270\262\345\217\243\346\227\245\345\277\227.txt" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\270\262\345\217\243\346\227\245\345\277\227.txt" deleted file mode 100644 index 9e8ee0721646b27eb9cc4a255cedcdcca1acd16b..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\270\262\345\217\243\346\227\245\345\277\227.txt" +++ /dev/null @@ -1,42 +0,0 @@ -=== 程序运行日志(COM21, 115200)=== -[D/main] Hello RT-Thread! count=776 -[monitor] th_led1 stat:2 remain_stack:852 -[monitor] th_led2 stat:2 remain_stack:852 -th_led2 count: 1548 -th_led1 count: 1545, tick: 777206 -th_led2 count: 1549 -th_led1 count: 1546, tick: 777709 -[D/main] Hello RT-Thread! count=777 -th_led2 count: 1550 -th_led1 count: 1547, tick: 778212 -th_led2 count: 1551 -th_led1 count: 1548, tick: 778715 -[D/main] Hello RT-Thread! count=778 -[monitor] th_led1 stat:2 remain_stack:852 -[monitor] th_led2 stat:2 remain_stack:852 -th_led2 count: 1552 -th_led1 count: 1549, tick: 779218 -th_led2 count: 1553 -th_led1 count: 1550, tick: 779721 - -=== FinSH: thread_dump 命令输出 === -msh >thread_dump -name status prio remain_stack ----------------------------------------- -th_led1 2 16 852 -th_led2 2 16 852 -th_monitor 2 10 836 -msh > - -=== FinSH: list_thread 命令输出 === -msh >list_thread -thread pri status sp stack size max used left tick error --------- --- ------- ---------- ---------- ------ ---------- --- -th_monit 10 suspend 0x000000bc 0x00000400 25% 0x00000003 OK -th_led2 16 suspend 0x000000ac 0x00000400 22% 0x00000003 OK -th_led1 16 suspend 0x000000ac 0x00000400 23% 0x00000003 OK -tshell 20 running 0x000000cc 0x00001000 15% 0x00000002 OK -tidle0 31 ready 0x00000060 0x00000100 56% 0x0000001f OK -timer 4 suspend 0x00000080 0x00000200 25% 0x00000009 OK -main 10 suspend 0x000000b8 0x00000800 14% 0x0000000d OK -msh > \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 75d4cfa486500e9a02981b46c39d2d91ce50e0ca..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,25 +0,0 @@ -# 第四天作业:虚拟设备驱动框架(test_dev) - -## 作业内容 - -在 RT-Thread 上设计一个**虚拟字符设备驱动框架**,实现标准 OPS 接口(init / open / close / read / write / control),验证"统一 I/O 框架 + 驱动 OPS 挂接"的对接机制。 - -## 文件说明 - -| 文件 | 说明 | -| --- | --- | -| `test_dev.c` | 虚拟设备驱动:6 个 OPS 实现 + 注册函数 `rt_drv_test_init` + MSH 验证命令 | -| `test_dev.h` | 头文件(`rt_drv_test_init` 声明) | - -- 设备名:`test_drv`(`RT_Device_Class_Char` 字符设备,`RT_DEVICE_FLAG_RDWR`) -- 自动初始化:`INIT_BOARD_EXPORT(rt_drv_test_init)`,上电自动创建 + 挂接 OPS + 注册 - -## 验证结果(串口日志) - -### `list_device` —— 设备注册成功 - -![image-20260820224303957](./figures/image-20260820224303957.png) - -### `drv_test_app` —— OPS 接口调用成功 - -![image-20260820224335213](./figures/image-20260820224335213.png) diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/figures/image-20260820224303957.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/figures/image-20260820224303957.png" deleted file mode 100644 index 3ec3f395f1bec70c25d511cad96e09d9c575c857..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/figures/image-20260820224303957.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/figures/image-20260820224335213.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/figures/image-20260820224335213.png" deleted file mode 100644 index 55cfbd49d927e2ba743c68b66bee0ffc2657cc4c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/figures/image-20260820224335213.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" deleted file mode 100644 index deea913300802ca9e8d1fa2d08c799e00272b63c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 13945 the first version - */ - -#include -#include - -#define DBG_TAG "test_drv" -#define DBG_LVL DBG_INFO -#include - -static rt_err_t drv_test_init(rt_device_t dev) -{ - LOG_I("test drv init\n"); - return RT_EOK; -} - -static rt_err_t drv_test_open(rt_device_t dev, rt_uint16_t oflag) -{ - LOG_I("test drv open flag = %d\n", oflag); - return RT_EOK; -} - -static rt_err_t drv_test_close(rt_device_t dev) -{ - LOG_I("test drv close\n"); - return RT_EOK; -} - -static rt_size_t drv_test_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) -{ - LOG_I("test drv read pos = %d, size = %d\n", pos, size); - return size; -} - -static rt_size_t drv_test_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) -{ - LOG_I("test drv write pos = %d, size = %d\n", pos, size); - return size; -} - -static rt_err_t drv_test_control(rt_device_t dev, int cmd, void *args) -{ - LOG_I("test drv control cmd = %d\n", cmd); - return RT_EOK; -} - -int rt_drv_test_init(void) -{ - rt_device_t test_dev = rt_device_create(RT_Device_Class_Char, 0); - if (!test_dev) - { - LOG_E("test drv create failed!\n"); - return -RT_ERROR; - } - - test_dev->init = drv_test_init; - test_dev->open = drv_test_open; - test_dev->close = drv_test_close; - test_dev->read = drv_test_read; - test_dev->write = drv_test_write; - test_dev->control = drv_test_control; - - if (rt_device_register(test_dev, "test_drv", RT_DEVICE_FLAG_RDWR) != RT_EOK) - { - LOG_E("test drv register failed!\n"); - return -RT_ERROR; - } - - return RT_EOK; -} -INIT_BOARD_EXPORT(rt_drv_test_init); - -/* 应用验证入口:在 msh 里输入 test_drv_app 触发 OPS 调用 */ -static int drv_test_app(void) -{ - rt_device_t test_dev = rt_device_find("test_drv"); - if (test_dev == RT_NULL) - { - LOG_E("can not find test drv!\n"); - return -RT_ERROR; - } - - rt_device_open(test_dev, RT_DEVICE_OFLAG_RDWR); - rt_device_control(test_dev, RT_DEVICE_CTRL_CONFIG, RT_NULL); - rt_device_write(test_dev, 100, RT_NULL, 1024); - rt_device_read(test_dev, 20, RT_NULL, 128); - rt_device_close(test_dev); - - return RT_EOK; -} -MSH_CMD_EXPORT(drv_test_app, test drv app: open/ctl/write/read/close); diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.h" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.h" deleted file mode 100644 index 506288f7ed685282d4cdb00d4835a5be5c71337c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.h" +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 13945 the first version - */ -#ifndef APPLICATIONS_TEST_DEV_H_ -#define APPLICATIONS_TEST_DEV_H_ - -int rt_drv_test_init(void); - -#endif /* APPLICATIONS_TEST_DEV_H_ */ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day1.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day1.md" deleted file mode 100644 index 7df56e95843f1474d04c4ba1edf9214d98fd25dd..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day1.md" +++ /dev/null @@ -1,162 +0,0 @@ -# RTT-学习日志-Day1 - -## markdown语法学习 - -### 代码插入 - -``` -#include - -int main() -{ -return 0; -} -``` - -### 表格插入相关 - - - -| --- | --- | -| ---- | ---- | -| | | - -### 其他语法 - -**加粗语法** - -`高亮语法` - -图像插入![image-20260817100636809](figures/image-20260817100636809.png) - - - - - -## 关于RTT-Stdio - -### 任务一rtt断点调试 - -![image-20260817102123446](figures/image-20260817102123446.png) - -## 星火一号 - -### 星火一号工程创建 - -#### 端口确认 - - - -![image-20260817102559963](figures/image-20260817102559963.png) - -#### 新建项目 - - - -![image-20260817103806695](figures/image-20260817103806695.png) - -![image-20260817104345415](figures/image-20260817104345415.png) - -#### 任务三 rt_kprintf() - -![image-20260817104204439](figures/image-20260817104204439.png) - -![image-20260817104221356](figures/image-20260817104221356.png) - -![image-20260817104255704](figures/image-20260817104255704.png) - -# GIT的使用 - -## 创建仓库 - -![image-20260817193850149](figures/image-20260817193850149.png) - -·![image-20260817195708890](figures/image-20260817195708890.png) - -## git clone - - - -![image-20260817200541589](figures/image-20260817200541589.png) - - - - - -![image-20260817200633114](figures/image-20260817200633114.png) - - - -## GIT 初始化相关 - -### 邮箱用户相关 - -user 和邮箱 我的电脑之前已经配置好,故这部分跳过 - - - -### 远程密钥相关 - -#### 输入指令 - -```$ ssh-keygen -t rsa -b 4096 -C "1394546446@qq.com" -$ ssh-keygen -t rsa -b 4096 -C "1394546446@qq.com" -``` - - - -![image-20260817201509787](figures/image-20260817201509787.png) - -#### 查看密钥 - -``` -cat /c/Users/13945/.ssh/id_rsa.pub -``` - -![image-20260817231925741](figures/image-20260817231925741.png) - -#### 设置中将公钥配置进入gitee - -![image-20260817231901642](figures/image-20260817231901642.png) - -#### 远程测试ssh gitee - -``` -ssh -T git@gitee.com -``` - - - -![image-20260817231955977](figures/image-20260817231955977.png) - -#### git 上传指令 - -``` -git init //初始化仓库 - -git add 文件名 //添加和提交 - -git commit -m "提交信息" - -git push -``` - - - - - -![image-20260817220655304](figures/image-20260817220655304.png) - -## GIT pullrequest - -git原始命令行老久不用生疏了,我直接使用我之前使用的sourcetree了。 - -将gitee先将仓库进行fork - -拉取到本地然后使用source tree同步 - -![image-20260817232207720](figures/image-20260817232207720.png) - -然后可以看见提交状态和相关信息 - -![image-20260817232228576](figures/image-20260817232228576.png) \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day2.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day2.md" deleted file mode 100644 index 3e22a34ab3ac1550bf11ea9da508e9f5c8c79454..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day2.md" +++ /dev/null @@ -1,491 +0,0 @@ -# RTT启动流程 - -## RTT启动流程分析 - -![image-20260818091729256](figures/image-20260818091729256.png) - -RT-Thread 从启动文件到调度开始的完整初始化流程。 - -1. **启动文件入口**(`startup_xx.S`)按编译器不同有三个入口: - - MDK:`$Sub$$main()` 先于 main 执行 - - IAR:`_low_level_init()` - - GCC:`entry()` -2. 三者最终都进入 **`rtthread_startup()`**,按顺序执行: - - `rt_hw_interrupt_disable()` 关闭中断 - - `rt_hw_board_init()` 硬件初始化(时钟/串口等) - - `rt_components_board_init()` 板级组件初始化 - - `rt_show_version()` 打印版本信息 - - `rt_system_timer_init()` / `rt_system_scheduler_init()` / `rt_system_signal_init()` 系统三大初始化 - - `rt_application_init()` 应用初始化:创建 `main_thread_entry`(主线程) - - `rt_system_timer_thread_init()` 创建 `rt_thread_timer_entry`(定时器线程) - - `rt_thread_idle_init()` 创建 `rt_thread_idle_entry`(空闲线程) -3. **`rt_system_scheduler_start()`** 启动调度器,按调度规则运行各线程。 -4. `rt_components_init()` 中按段执行各类初始化函数(pre-initialization / device / components / environment / applications)。 -5. MDK 下最终通过 `$Super$$main()` 跳入用户 `main()`。 - -## 线程控制模块定义 - -![image-20260818093038715](figures/image-20260818093038715.png) - -线程控制块 `struct rt_thread` 的定义: - -```c -struct rt_thread -{ - struct rt_object parent; /* 内核对象基类 */ - - /* 栈指针与入口指针 */ - void *sp; /* 栈指针 */ - void *entry; /* 入口函数指针 */ - void *parameter; /* 参数 */ - void *stack_addr; /* 栈地址指针 */ - rt_uint32_t stack_size; /* 栈大小 */ -}; -``` - -线程控制块的继承关系: - -![pdf_thread-cb-inherit](figures/pdf_thread-cb-inherit.png) - -线程控制块 `struct rt_thread` **继承自基类 `rt_object`**(含 name/type/flag/list 四个成员),在基类基础上扩展了线程状态、优先级等属性。`rt_ipc_object` 同样继承自 `rt_object`,其下派生出 `rt_sem`、`rt_mutex`、`rt_event`、`rt_mb`、`rt_mq` 等 IPC 对象;`rt_thread` 与 `rt_timer`、`rt_device`、`rt_mempool` 平级。 - -```c -/* 错误代码 */ -rt_err_t error; /* 线程错误代码 */ -rt_uint8_t stat; /* 线程状态 */ - -/* 优先级 */ -rt_uint8_t current_priority; /* 当前优先级 */ -rt_uint8_t init_priority; /* 初始优先级 */ -rt_uint32_t number_mask; - -/* 时间片 */ -rt_ubase_t init_tick; /* 线程初始化计数值 */ -rt_ubase_t remaining_tick; /* 线程剩余计数值 */ - -struct rt_timer thread_timer; /* 内置线程定时器 */ - -void (*cleanup)(struct rt_thread *tid); /* 线程退出清除函数 */ -rt_uint32_t user_data; /* 用户数据 */ -``` - -线程控制块是操作系统管理线程的数据结构,存放优先级、线程名称、线程状态、链表结构、等待事件集合等信息;由 `struct rt_thread` 定义形成线程内核对象,再链接到内核对象容器中统一管理。 - -## 时间片 - -![image-20260818094702649](figures/image-20260818094702649.png) - -- 每个线程都需要配置时间片,但**时间片仅对优先级相同的就绪态线程有效**。 -- 系统对同优先级就绪线程采用**时间片轮转**调度,时间片约束线程单次运行时长,单位为 1 个系统节拍(OS Tick)。 -- **示例**:同优先级的线程 A 时间片=10、线程 B 时间片=5,则系统在 A/B 间来回切换,每次 A 运行 10 节拍、B 运行 5 节拍。 -- 时序示意:`t1` 时 A 开始 → `t1+5` 切到 B → `t1+15` 切回 A……(优先级 n 为就绪队列最高优先级)。 - -线程时间片参数在 `rt_thread_init()` / `rt_thread_create()` 的最后一个 `tick` 参数中指定,是线程属性之一(另两个属性是优先级、入口函数)。 - -## 系统线程 - -### 空闲线程 - -idle线程->空闲线程 - -0-31个线程,空闲线程(idle)排位在31 - -![image-20260818094846996](figures/image-20260818094846996.png) - -- **定义**:空闲线程是系统创建的最低优先级线程,状态永远为就绪态;当无其他就绪线程时调度器运行它;通常是死循环,**永远不能被挂起**。 -- **特殊用途(1)**:线程运行完毕后自动删除——执行 `rt_thread_exit()`,先将线程从就绪队列删除、状态改为关闭,再挂入 `rt_thread_defunct` 僵尸队列,**由空闲线程回收资源**。 -- **特殊用途(2)**:提供钩子函数接口,空闲线程运行时调用用户钩子,适合**功耗管理、看门狗喂狗**等工作。 - -### 线程初始化(静态创建) - -静态创建 - -有确定性 - -![image-20260818100309517](figures/image-20260818100309517.png) - -静态线程初始化接口。 - -```c -rt_err_t rt_thread_init(struct rt_thread *thread, - const char *name, - void(*entry)(void *parameter), - void *parameter, - void *stack_start, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -- **静态线程**:线程句柄(控制块指针)、线程栈由**用户提供**,通常设为全局变量,编译时即确定分配,内核不负责动态分配内存。 -- 用户提供的**栈首地址需做系统对齐**(ARM 上需 4 字节对齐)。 - -`rt_thread_init` 参数表: - -| 参数 | 描述 | -|---|---| -| `thread` | 线程句柄,由用户提供,指向线程控制块内存地址 | -| `name` | 线程名称;最大长度由 `rtconfig.h` 的 `RT_NAME_MAX` 宏指定,多余部分被截掉 | -| `entry` | 线程入口函数 | -| `parameter` | 线程入口函数参数 | -| `stack_start` | 线程栈起始地址 | -| `stack_size` | 线程栈大小(字节),需做地址对齐(ARM 4 字节对齐) | -| `priority` | 优先级,范围由 `RT_THREAD_PRIORITY_MAX` 决定(256 级时 0~255),数值越小优先级越高,0 最高 | -| `tick` | 时间片大小,单位为系统时钟节拍;同优先级线程存在时决定单次调度最大运行时间 | - -返回:`RT_EOK` 成功 / `-RT_ERROR` 失败。 - -![pdf_thread-init-sample](figures/pdf_thread-init-sample.png) - -```c -/* 线程栈与线程句柄由用户提供,并做对齐 */ -ALIGN(RT_ALIGN_SIZE) -static char thread2_stack[1024]; -static struct rt_thread thread2; - -/* 线程2 入口 */ -static void thread2_entry(void *param) -{ - rt_kprintf("thread2\n"); -} - -int thread_init_sample(void) -{ - /* 初始化线程2:名称 thread2,入口 thread2_entry,优先级 20,时间片 10 */ - rt_thread_init(&thread2, - "thread2", - thread2_entry, - RT_NULL, - &thread2_stack[0], - sizeof(thread2_stack), - 20, - 10); - rt_thread_startup(&thread2); - return 0; -} -``` - -### 线程创建(动态创建) - -动态创建 - -相比于静态创建少了 - -``` -void *stack_start -``` - -![image-20260818100514543](figures/image-20260818100514543.png) - -动态线程创建接口: - -```c -rt_thread_t rt_thread_create(const char* name, - void (*entry)(void* parameter), - void* parameter, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -- 调用时系统**从动态堆内存分配**线程句柄 + 指定大小的栈空间。 -- 栈空间按 `rtconfig.h` 中配置的 `RT_ALIGN_SIZE` 方式对齐。 -- **与静态创建的区别**:少了 `stack_start` 参数(栈由内核动态分配,用户不用提供);返回值是 `rt_thread_t` 句柄而非错误码。 - -### 线程状态与转化 - -![pdf_thread-state-transition](figures/pdf_thread-state-transition.png) - -**5 种线程状态**: - -| 状态 | 宏定义 | 描述 | -|---|---|---| -| 初始状态 | `RT_THREAD_INIT` | 线程刚创建未运行,**不参与调度** | -| 就绪状态 | `RT_THREAD_READY` | 按优先级排队等待执行;当前线程让出 CPU 后,系统找最高优先级就绪线程运行 | -| 运行状态 | `RT_THREAD_RUNNING` | 线程正在运行;单核系统中仅 `rt_thread_self()` 返回的线程处于运行态 | -| 挂起状态 | `RT_THREAD_SUSPEND` | 也称阻塞态;因资源不可用或主动延时而挂起,**不参与调度** | -| 关闭状态 | `RT_THREAD_CLOSE` | 线程运行结束进入关闭状态,不参与调度 | - -**状态转化关键 API**: -- 创建/初始化:`rt_thread_create()` / `rt_thread_init()` → 初始态 -- 启动:`rt_thread_startup()` → 就绪态 -- 挂起(①):`rt_thread_delay()` / `rt_sem_take()` / `rt_mutex_take()` / `rt_mp_alloc()` / `rt_event_recv()` / `rt_mb_recv()` / `rt_mq_recv()` → 挂起态 -- 恢复(②):`rt_thread_resume()` / `rt_sem_release()` / `rt_mutex_release()` / `rt_mp_release()` / `rt_event_send()` / `rt_mb_send()` / `rt_mq_send()` → 就绪态 -- 结束:`rt_thread_exit()` / `rt_thread_delete()` / `rt_thread_detach()` → 关闭态 - -### 线程属性 - -**优先级**: -- 表示线程被调度的优先程度,越重要优先级越高。 -- RT-Thread 最大支持 **256 级优先级(0~255)**,数值越小优先级越高,**0 为最高**。 -- 资源紧张时可配置只支持 8 或 32 个优先级(`rtconfig.h`)。 -- **最低优先级默认分配给空闲线程**,用户一般不使用。 -- 有比当前线程更高优先级线程就绪时,当前线程**立刻被换出**(抢占式)。 - -**线程栈**: - -![pdf_thread-stack](figures/pdf_thread-stack.png) - -- 线程有独立栈空间,切换时把上下文(寄存器等)存入栈,恢复时读回。 -- 栈也存放函数局部变量(先分配在寄存器,调用更深函数时入栈)。 -- 栈增长方向与芯片架构相关;ARM Cortex-M 架构**栈从高地址向低地址增长**,压入顺序为 PSR、PC、LR、R12、R3~R0、R11…R4。 - -**入口函数**: -- 线程功能由入口函数实现,格式 `void (*entry)(void* parameter)`。 -- **函数要求必须有让出 CPU 的动作**(如 `rt_thread_mdelay()`),否则会独占 CPU。 -- 示例:`thread1_entry` 中循环 `rt_kprintf("thread1 count: %d\n", count++)` + `rt_thread_mdelay(500)`。 - -### 线程延时与挂起 - -![image-20260818100908981](figures/image-20260818100908981.png) - -抢占式系统中,线程不使用 CPU 时必须**让出 CPU 使用权**,让其他线程运行,可用延时/睡眠实现: - -- `rt_err_t rt_thread_delay(rt_tick_t tick);` 以 1 个 OS Tick 为单位 -- `rt_err_t rt_thread_mdelay(rt_int32_t ms);` 以 ms 为单位 - -### 线程启动 - -创建/初始化后的线程处于**初始状态**,未进入就绪队列,需要调用启动接口进入就绪态: - -```c -rt_err_t rt_thread_startup(rt_thread_t thread); -``` - -调用后线程状态变为**就绪**,放入对应优先级队列等待调度;若新线程优先级比当前线程高,**立即切换**到新线程。 - -### 主线程 - -- RT-Thread 中的 `main()` 函数本身是一个线程,称为**主线程**。 -- 系统启动时创建 main 线程,入口函数为 `main_thread_entry()`,用户的 `main()` 从这里真正开始。 -- 流程:`$Sub$main()` → `rtthread_startup()` → `rt_application_init()` → `main_thread_entry()` → `main()`。 -- 可在 `main()` 中添加应用初始化代码,例: - -```c -int main(void) -{ - rt_kprintf("hello world!"); - return 0; -} -``` - -## 关于Scons - -rtt的vscode扩展 - -### SCons 实操学习记录(Day2) - -SCons 是一套由 Python 语言编写的开源构建系统(类似 GNU Make),RT-Thread 用它替代 Makefile 来组织源码、编译工程。核心文件: -- `SConstruct`:SCons 的入口脚本(一个 BSP 只有一个),初始化构建环境 -- `SConscript`:分散在各级源码目录下的子脚本(组织源码的主力军) -- `rtconfig.py`:编译器配置(CROSS_TOOL、EXEC_PATH、CFLAGS/LFLAGS 等) - -#### 1. 环境准备(使用 RT-Thread Studio 自带工具链,无需额外安装) - -```cmd -cd /d D:\app\RT-ThreadStudio\workspace\02-thread_sample -set RTT_EXEC_PATH=D:\app\RT-ThreadStudio\platform\env_released\env\tools\gnu_gcc\arm_gcc\mingw\bin -set PATH=%RTT_EXEC_PATH%;D:\app\RT-ThreadStudio\platform\env_released\env\tools\Python27;D:\app\RT-ThreadStudio\platform\env_released\env\tools\Python27\Scripts;%PATH% - -scons --version -``` - -``` -SCons by Steven Knight et al.: - script: v3.1.2.bee7caf9defd6e108fc2998a2520ddb36a967691, 2019-12-17 02:07:09 - engine: v3.1.2.bee7caf9defd6e108fc2998a2520ddb36a967691, 2019-12-17 02:07:09 -``` - -#### 2. 干跑预览:`scons -n`(只显示将要执行的命令,不真正编译) - -```cmd -scons -n -``` - -``` -scons: Reading SConscript files ... -Newlib version:4.1.0 -scons: done reading SConscript files. -scons: Building targets ... -scons: building associated VariantDir targets: build -CC build\applications\main.o -CC build\drivers\board.o -...(共 56 个 CC + 2 个 AS) -LINK rt-thread.elf -scons: done building targets. -``` - -数出本工程参与构建的 C 源文件数量: - -```cmd -scons -n | find /c "CC " -``` - -``` -56 -``` - -56 = 所有 SConscript 通过 `Return('group')` 汇总上来的 C 文件数(applications 1 + drivers 5 + kernel 38 + libraries 12)。`AS` 行是汇编文件(context_gcc.S、startup_stm32f407xx.S),不是 C 编译。 - -#### 3. 正式编译:`scons -j4`(4 线程并行,对应文档 3.4 节) - -```cmd -scons -j4 -dir rt-thread.elf -``` - -``` -2026/08/18 16:05 168,488 rt-thread.elf -``` - -编译成功,产出 `rt-thread.elf`(168,488 字节)。 - -#### 4. 增量编译(对应文档 3.1 节) - -不修改任何文件,再次执行 `scons -j4`: - -```cmd -scons -j4 -``` - -``` -scons: Reading SConscript files ... -scons: done reading SConscript files. -scons: Building targets ... -scons: building associated VariantDir targets: build -LINK rt-thread.elf -scons: done building targets. -``` - -第二次 0 个 CC,只重新 LINK——即"仅编译修改过的源文件并链接"。但为何每次都 LINK? - -用 `--debug=explain` 让 scons 解释原因: - -```cmd -scons --debug=explain -``` - -``` -scons: rebuilding `rt-thread.elf' because: - the dependency order changed: - ->Sources - Old:build\applications\main.o New:build\applications\main.o - Old:build\kernel\src\object.o New:build\kernel\src\idle.o - ... -``` - -原因是 "the dependency order changed"(依赖顺序变化)——顶层 `SConscript` 用 `os.listdir(cwd)` 枚举子目录,Windows 上返回顺序不稳定,导致收集源码的顺序每次不同,SCons 认为链接输入变了 → 每次都 LINK(但 .o 本身不重编)。修复方法:`list = sorted(os.listdir(cwd))`。 - -#### 5. 清理编译产物:`scons -c`(对应文档 3.2 节) - -```cmd -scons -c -``` - -``` -scons: Cleaning targets ... -Removed build\applications\main.o -Removed build\drivers\board.o -...(所有 .o 全部删除) -Removed rt-thread.elf -Removed cconfig.h -Removed .sconsign.dblite -scons: done cleaning targets. -``` - -`scons -c` 不仅删 .o 和 elf,还删 `.sconsign.dblite`(SCons 的签名数据库,增量编译靠它记忆)——所以清理后下次编译会全量重编。 - -#### 6. 查看完整编译命令:`scons --verbose`(对应文档 3.7 节) - -清理后干跑 + verbose,看到 CC 的完整命令: - -```cmd -scons -c -scons -n --verbose -``` - -``` -arm-none-eabi-gcc -o build\applications\main.o -c -mcpu=cortex-m4 -mthumb \ - -ffunction-sections -fdata-sections \ - -DHAVE_CCONFIG_H -DSOC_FAMILY_STM32 -DSOC_SERIES_STM32F4 -DSTM32F407xx \ - -DUSE_HAL_DRIVER -D__RTTHREAD__ -DRT_USING_NEWLIB -DRT_USING_LIBC \ - -D_POSIX_C_SOURCE=1 \ - -I. -Iapplications -Idrivers -Idrivers\include -Idrivers\include\config \ - -Ilibraries\CMSIS\Include -Ilibraries\CMSIS\Device\ST\STM32F4xx\Include \ - -Ilibraries\STM32F4xx_HAL_Driver\Inc -Irt-thread\include \ - -Irt-thread\libcpu\arm\common -Irt-thread\libcpu\arm\cortex-m4 \ - -Irt-thread\components\drivers\include -Irt-thread\components\finsh \ - ... \ - applications\main.c -``` - -verbose 输出 = scons 把 SConscript 配置翻译成真实 gcc 命令。`-D` 宏来自 rtconfig.h 解析 + SConscript 的 `CPPDEFINES`;`-I` 路径来自 SConscript 的 `CPPPATH`;`-mcpu=cortex-m4` 来自 rtconfig.py 的 `DEVICE`。 - -#### 7. 生成其他工程:`scons --target=XXX`(对应文档 3.3 节) - -生成 CMake 工程(成功): - -```cmd -scons --target=cmake -``` - -``` -Update setting files for CMakeLists.txt... -Done! -``` - -生成了 `CMakeLists.txt`,内含 `PROJECT_SOURCES`(源码清单)、`INCLUDE_DIRECTORIES`(头文件路径)、`ADD_DEFINITIONS`(宏定义)。 - -生成 MDK 工程(第一次失败,缺模板): - -```cmd -scons --target=mdk5 -``` - -``` -Warning: The template project file [template.uvprojx] not found! -``` - -对应文档 3.3 节 Note——"要生成 MDK 或者 IAR 的工程文件,前提条件是 BSP 目录存在一个工程模版文件"。scons 只认 BSP 目录下固定名字 `template.uvprojx`。 - -从 Studio 板卡支持包拷贝模板后重新生成: - -```cmd -copy D:\app\RT-ThreadStudio\repo\Extract\Board_Support_Packages\RealThread\STM32F407-RT-SPARK\1.2.0\projects\01_kernel\template.uvprojx D:\app\RT-ThreadStudio\workspace\02-thread_sample\ - -scons --target=mdk5 -``` - -``` -KeyError: 'LIBPATH': - File "D:\app\RT-ThreadStudio\workspace\02-thread_sample\SConstruct", line 36: - DoBuilding(TARGET, objs) - File "...\building.py", line 783: - EndBuilding(target, program) - File "...\building.py", line 886: - GenTargetProject(program) - File "...\building.py", line 812: - MDK5Project('project.uvprojx', Projects) - File "...\keil.py", line 311: - MDK45Project(template_tree, target, script) - File "...\keil.py", line 257: - for path_item in group['LIBPATH']: -``` - -生成 mdk5 工程报 `KeyError: 'LIBPATH'`——根因是内核 `newlib/SConscript` 的 `DefineGroup` 传了 `LIBS=['c','m']` 但没传 `LIBPATH`,而 `keil.py` 第 257 行用 `group['LIBPATH']` 硬访问。修复:改成 `group.get('LIBPATH', [])`(容错写法)。修复后 `project.uvprojx` 从 57 字节(空壳)变为 29KB(正常),内含 Applications/Compiler/CPU/DeviceDrivers/Drivers 等 8 个分组及 main.c 等全部源码。 - -#### 8. 本次实操总结 - -| 命令 | 作用 | 文档章节 | -|---|---|---| -| `scons` | 编译(增量编译) | 3.1 | -| `scons -jN` | 多线程并行编译 | 3.4 | -| `scons -n` | 干跑,只预览不执行 | - | -| `scons -c` | 清理编译产物(含 .sconsign.dblite) | 3.2 | -| `scons --verbose` | 显示完整编译/链接命令 | 3.7 | -| `scons --target=cmake/mdk5/iar` | 生成对应工程文件(mdk5 需 template.uvprojx) | 3.3 | -| `scons --debug=explain` | 查看 scons 判定"为什么要重建"的原因 | - | - -**核心认知**:SConscript 决定"编译什么"(缺 SConscript 的目录源码不参与构建);`CPPPATH`→`-I`、`CPPDEFINES`→`-D`、`src`→编译文件列表;增量编译依赖 `.sconsign.dblite` 签名数据库。 - -![image-20260818102718602](figures/image-20260818102718602.png) diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day3.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day3.md" deleted file mode 100644 index 3e0221b7e66f5d0d7342b03c506c14b0863a7f79..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day3.md" +++ /dev/null @@ -1,420 +0,0 @@ -# RTT-学习日志-Day3 - -## 线程间同步 —— 信号量(Semaphore) - -> 参考:[RT-Thread 编程手册 · 线程间同步 · 信号量](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1?id=%e4%bf%a1%e5%8f%b7%e9%87%8f) - -### 1. 为什么需要同步 - -- 多线程共享一块内存(临界区)时,若同时访问会造成数据错乱,必须**互斥**访问。 -- **临界区**:多个线程都要访问的共享代码/数据区域,任何时刻最多一个线程使用。 -- 同步方式:信号量、互斥量、事件集。核心思想:**访问临界区时只允许一个(或一类)线程运行**。 - -### 2. 信号量概念(停车场类比) - -| 现实 | 内核 | -|---|---| -| 停车场管理员 | 信号量 | -| 管理员手中的空车位个数 | 信号量的值 value(非负、动态变化) | -| 停车位(公共资源) | 临界区资源 | -| 车辆 | 线程 | -| 车进场/离场 | 获取/释放信号量 | - -### 3. 工作机制 - -- 每个信号量对象有 **信号量值 value** 和 **线程等待队列**。 -- value = 5 表示有 5 个资源实例可用。 -- **获取(take)**:value > 0 → value-- 继续执行;value == 0 → 线程挂起到等待队列。 -- **释放(release)**:有线程等待 → 唤醒队列第一个线程;无等待 → value++。 - -### 4. 信号量控制块 - -```c -struct rt_semaphore -{ - struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ - rt_uint16_t value; /* 信号量的值 */ - rt_uint16_t max_value; /* 信号量的最大值(65535) */ - struct rt_spinlock spinlock; /* 自旋锁 */ -}; -typedef struct rt_semaphore* rt_sem_t; /* 句柄 = 指向控制块的指针 */ -``` - -### 5. API 一览 - -| 操作 | 静态(init/detach) | 动态(create/delete) | -|---|---|---| -| 建立 | `rt_sem_init(&sem, name, value, flag)` | `rt_sem_create(name, value, flag)` 返回指针 | -| 获取 | `rt_sem_take(&sem, timeout)` | `rt_sem_take(sem, timeout)` | -| 无等待获取 | `rt_sem_trytake(&sem)` | `rt_sem_trytake(sem)` | -| 释放 | `rt_sem_release(&sem)` | `rt_sem_release(sem)` | -| 销毁 | `rt_sem_detach(&sem)` 不释放内存 | `rt_sem_delete(sem)` 释放堆内存 | - -- **timeout**:`RT_WAITING_FOREVER` 永久等待 / `RT_WAITING_NO` 不等 / 正整数 = 等待 N 个 OS Tick,超时返回 `-RT_ETIMEOUT`。 -- **flag**:`RT_IPC_FLAG_FIFO`(先进先出,非实时,不建议)/ `RT_IPC_FLAG_PRIO`(按优先级排队,推荐)。 -- 内存来源:静态 = 编译器编译期分配(全局变量);动态 = 堆分配,需开 `RT_USING_HEAP`,create 后**必须判空**。 -- 铁律:**init 的只能 detach,create 的只能 delete,不能混用**(delete 会 free 静态内存导致崩溃)。 -- 静态写法处处带 `&`(对象是自己的变量),动态写法不带 `&`(对象是内核返回的指针)。 - -### 6. 典型用法(互斥锁保护共享数据) - -```c -static struct rt_semaphore sem_lock; /* 静态声明 */ - -void init(void) -{ - rt_sem_init(&sem_lock, "lock", 1, RT_IPC_FLAG_PRIO); /* 初始值 1 = 二值信号量当锁 */ -} - -/* 线程 A:写 */ -rt_sem_take(&sem_lock, RT_WAITING_FOREVER); -g_adc_value = v; /* 临界区 */ -rt_sem_release(&sem_lock); - -/* 线程 B:读 */ -rt_sem_take(&sem_lock, RT_WAITING_FOREVER); -v = g_adc_value; /* 临界区 */ -rt_sem_release(&sem_lock); -``` - -- 信号量还常用于**事件同步**:初始值 0,中断里 `rt_sem_release` 通知,线程 `rt_sem_take` 等待被唤醒(中断里只允许 release,禁止 take)。 -- 生产者-消费者经典模型:`lock`(互斥)+ `empty`(空位数)+ `full`(满位数)三个信号量配合。 - -### 7. 避坑清单 - -1. 静态对象只能 `detach`,动态对象只能 `delete`,不能混用。 -2. `create` 后必须判空(堆可能不足)。 -3. 静态版本 take/release 别忘 `&`。 -4. 中断上下文禁止 `take`/`create`/`delete`,只允许 `release`。 -5. 纯互斥场景优先考虑**互斥量**(有优先级继承,防优先级翻转),信号量擅长计数与事件同步。 - ---- - -## 线程间同步 —— 互斥量(Mutex) - -> 参考:[RT-Thread 编程手册 · 线程间同步 · 互斥量](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1?id=%e4%ba%92%e6%96%a5%e9%87%8f) - -### 1. 概念(单车位停车场类比) - -互斥量是特殊的**二值信号量**:只有一个车位。车进场 → 大门锁住,其他车在外等候;车出来 → 开门放行下一辆。 - -![image-20260819203537362](./figures/image-20260819203537362.png) - -### 2. 工作机制(与信号量的三大不同) - -1. **所有权**:互斥量有"主人",只有持有它的线程能释放,其他线程释放返回错误;信号量任何线程/中断都能 release。 - -2. **递归持有**:同一线程可多次 take 同一互斥量(hold 计数 +1),必须释放同样次数才真正解锁;二值信号量递归 take 会自我挂死(死锁)。 - -3. **优先级继承**:解决优先级翻转问题。 - - ![image-20260819203602213](./figures/image-20260819203602213.png) - -### 3. 优先级翻转与优先级继承 - -- **优先级翻转**:T1(高) 等 T3(低) 手里的锁 → 被挂起;T2(中) 此时就绪抢占 CPU 运行完,T3 才得以释放锁 → T1 被中等优先级线程"饿死",实时性无法保证。 -- **优先级继承**(Sha, 1990):T1 挂起等待期间,**T3 的优先级被临时提升到 T1 的级别**,防止被 T2 抢占;T3 释放锁后恢复原优先级。 -- 提升目标 = **所有等待者中优先级最高者**的优先级;锁一释放立刻恢复。 -- 注意:持有互斥量期间**不得更改线程优先级**,否则可能引入无界优先级反转。 -- ![image-20260819203552499](./figures/image-20260819203552499.png - -### 4. 互斥量控制块 - -```c -struct rt_mutex -{ - struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ - - rt_uint8_t ceiling_priority; /* 优先级天花板 */ - rt_uint8_t priority; /* 等待线程中的最高优先级 */ - rt_uint8_t hold; /* 持有线程的持有次数 */ - rt_uint8_t reserved; /* 保留字段 */ - - struct rt_thread *owner; /* 当前拥有互斥量的线程 */ - rt_list_t taken_list; /* 线程持有的对象链表 */ - struct rt_spinlock spinlock; /* 自旋锁 */ -}; -typedef struct rt_mutex* rt_mutex_t; -``` - -### 5. API 一览 - -| 操作 | 静态(init/detach) | 动态(create/delete) | -|---|---|---| -| 建立 | `rt_mutex_init(&mutex, name, flag)` | `rt_mutex_create(name, flag)` 返回指针 | -| 获取 | `rt_mutex_take(&mutex, timeout)` | `rt_mutex_take(mutex, timeout)` | -| 释放 | `rt_mutex_release(&mutex)` | `rt_mutex_release(mutex)` | -| 销毁 | `rt_mutex_detach(&mutex)` | `rt_mutex_delete(mutex)` | - -- 互斥量**没有** `trytake`(无等待获取可用 `rt_mutex_take(m, RT_WAITING_NO)`)。 -- flag 参数**已作废**:无论传 FIFO 还是 PRIO,内核一律按 `RT_IPC_FLAG_PRIO` 处理(优先级继承需要按优先级排队)。 -- 只能在线程上下文使用(中断里禁止 take/release)。 -- 静态/动态用法与信号量完全对应:静态带 `&`、动态判空、init 的只能 detach、create 的只能 delete。 - -### 6. 互斥量 vs 信号量 对比 - -| 维度 | 互斥量 | 信号量 | -|---|---|---| -| 本质 | 互斥锁(锁资源) | 同步 + 计数(发信号/数资源) | -| 值 | 只有 0/1(二值) | 任意非负数(0~65535) | -| 谁可以释放 | 仅持有它的线程 | 任何线程 / 中断 | -| 优先级继承 | ✅ 有(防优先级翻转) | ❌ 没有(有翻转风险) | -| 递归持有 | ✅ 支持(hold 计数) | ❌ 递归 take 会死锁 | -| 中断里使用 | ❌ 全部禁止 | release 可以,take 禁止 | -| flag 参数 | 已作废,强制 PRIO | FIFO / PRIO 可选 | -| 典型场景 | 保护共享变量/外设 | 生产者-消费者、中断通知、任务同步 | - -### 7. 避坑清单 - -1. 互斥量**必须由持有者释放**,别的线程 release 会返回错误。 -2. 递归 take 了几次就要 release 几次,少一次锁不释放,多一次会误解锁。 -3. 持有互斥量期间不要改线程优先级、不要长时间占用(尽快释放)。 -4. 中断里禁止一切互斥量操作。 -5. 只想"发通知/数资源"用信号量,想"锁临界区"用互斥量——用错对象会付出性能/正确性代价。 - ---- - -## 线程间同步 —— 事件集(Event) - -> 参考:[RT-Thread 编程手册 · 线程间同步 · 事件集](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1?id=%e4%ba%8b%e4%bb%b6%e9%9b%86) - -### 1. 概念(等公交类比) - -- ①只有一路车能到,等到它就走 —— 特定事件唤醒线程; -- ②多路车都能到,**任意一路**来就走 —— 逻辑或(独立型同步); -- ③约了同伴 P2,"P2 到了"且"公交到了"都满足才走 —— 逻辑与(关联型同步)。 - -事件集支持**一对多、多对多**的线程同步,这是信号量做不到的。 - -### 2. 工作机制 - -- 用 32 位无符号整型表示事件集,**每一位代表一个事件**。 -- 三个特点: - 1. 事件只与线程相关,事件间相互独立,每个线程可拥有 32 个事件标志; - 2. 事件**仅用于同步,不提供数据传输**功能; - 3. 事件**无排队性**:多次发送同一事件(线程没读走)效果等同只发一次,**不累计**。 -- 接收选项 opt: - - `RT_EVENT_FLAG_OR`:等待的事件任一发生即唤醒; - - `RT_EVENT_FLAG_AND`:等待的事件全部发生才唤醒; - - `RT_EVENT_FLAG_CLEAR`:唤醒后主动清除事件标志位,不加则标志一直保留。 - -### 3. 事件集控制块 - -```c -struct rt_event -{ - struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ - rt_uint32_t set; /* 事件集合,每一 bit 表示 1 个事件 */ - struct rt_spinlock spinlock; -}; -typedef struct rt_event* rt_event_t; -``` - -### 4. API 一览 - -| 操作 | 静态(init/detach) | 动态(create/delete) | -|---|---|---| -| 建立 | `rt_event_init(&ev, name, flag)` | `rt_event_create(name, flag)` 返回指针 | -| 发送事件 | `rt_event_send(&ev, set)` | `rt_event_send(ev, set)` | -| 接收事件 | `rt_event_recv(&ev, set, opt, timeout, &recved)` | `rt_event_recv(ev, set, opt, timeout, &recved)` | -| 销毁 | `rt_event_detach(&ev)` | `rt_event_delete(ev)` | - -- `rt_event_send` 一次可发送多个事件:`rt_event_send(&ev, FLAG3 | FLAG5)`;**线程和中断里都能调用**。 -- `rt_event_recv` 的 `recved` 返回实际收到的事件位图;timeout 用法同信号量。 - -```c -/* 等 事件3 或 事件5 任一发生,唤醒后清标志 */ -rt_event_recv(&event, EVENT_FLAG3 | EVENT_FLAG5, - RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &e); -/* 等 事件3 和 事件5 都发生,唤醒后清标志 */ -rt_event_recv(&event, EVENT_FLAG3 | EVENT_FLAG5, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &e); -``` - -### 5. 示例要点(event_sample) - -- 线程1 第一次用 OR 等"事件3 或 事件5",第二次用 AND 等"事件3 且 事件5"; -- 运行输出:OR 收到 `0x8`(只有事件3),AND 收到 `0x28`(事件3+事件5); -- 因为带了 CLEAR,第一次唤醒后事件3被清掉,第二次必须等线程2**重新**发送事件3才能满足 AND。 - -### 6. 事件集 vs 信号量 - -| 事件集 | 信号量 | -|---|---| -| 一个对象含 32 个事件(位图) | 只有一个计数 | -| 支持 AND/OR 组合等待 | 只能等同一个信号 | -| 事件**不累计**(发 10 次 = 1 次) | value 会累计 | -| 只同步,不传数据 | 只同步,不传数据 | - -### 7. 使用场合 - -多个事件汇聚才动作(门关好+水放满)、多源事件等待(按键/传感器/通信帧)、一个事件集被多个线程以不同逻辑(AND/OR)分别等待,实现一对多、多对多同步。 - ---- - -## 线程间通信 —— 邮箱(Mailbox) - -> 参考:[RT-Thread 编程手册 · 线程间通信 · 邮箱](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc2/ipc2?id=%e9%82%ae%e7%ae%b1) - -### 1. 概念 - -线程间通信的第一种方式。例:线程1 检测按键状态发邮件,线程2 收邮件据此改 LED。**每封邮件固定 4 字节**(32 位系统上恰好放一个指针)。 - -### 2. 工作机制 - -- 发送:邮箱没满 → 复制邮件进邮箱;满了 → 返回 `-RT_EFULL`,或改用 `rt_mb_send_wait` 挂起等待; - -- 接收:有邮件 → 复制 4 字节到接收缓存;空邮箱 → 挂起等待或超时返回 `-RT_ETIMEOUT`; - -- 非阻塞发送可安全用于中断服务(中断、定时器给线程发消息的常用手段);接收只能由线程做; - -- `rt_mb_urgent` 发紧急邮件,直接**插队到队首**,接收者优先处理。 - - ![image-20260819204601399](./figures/image-20260819204601399.png) - -### 3. 邮箱控制块 - -```c -struct rt_mailbox -{ - struct rt_ipc_object parent; - - rt_uint32_t* msg_pool; /* 邮箱缓冲区的开始地址 */ - rt_uint16_t size; /* 邮箱缓冲区的大小 */ - rt_uint16_t entry; /* 邮箱中邮件的数目 */ - rt_uint16_t in_offset, out_offset; /* 邮箱缓冲的进出指针 */ - rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */ - struct rt_spinlock spinlock; -}; -typedef struct rt_mailbox* rt_mailbox_t; -``` - -![image-20260819204701638](./figures/image-20260819204701638.png) - -### 4. API 一览 - -| 操作 | 静态(init/detach) | 动态(create/delete) | -|---|---|---| -| 建立 | `rt_mb_init(&mb, name, msgpool, size, flag)` | `rt_mb_create(name, size, flag)` | -| 发送 | `rt_mb_send(&mb, value)` | `rt_mb_send(mb, value)` | -| 等待发送 | `rt_mb_send_wait(&mb, value, timeout)` | 同左 | -| 紧急发送 | `rt_mb_urgent(&mb, value)` | 同左 | -| 接收 | `rt_mb_recv(&mb, &value, timeout)` | 同左 | -| 销毁 | `rt_mb_detach(&mb)` | `rt_mb_delete(mb)` | - -- `rt_mb_init` 的 size 是**邮件个数** = 缓冲区字节数 / 4; -- 邮件内容 `value` 是 `rt_ubase_t`(32 位整数),可以是整型值或指针。 - -### 5. 传大数据的用法(发指针) - -4 字节放不下大块数据,就把**指向缓冲区的指针**当邮件发出去: - -```c -struct msg { rt_uint8_t *data_ptr; rt_uint32_t data_size; }; - -struct msg* msg_ptr = (struct msg*)rt_malloc(sizeof(struct msg)); -msg_ptr->data_ptr = ...; -msg_ptr->data_size = len; -rt_mb_send(mb, (rt_ubase_t)msg_ptr); - -/* 接收线程处理完后,必须 rt_free(msg_ptr) 释放内存 */ -``` - -### 6. 使用场合 - -开销低、效率高,适合不超过 4 字节的小消息(按键状态、ADC 采样值等);传指针可实现大数据传递(注意内存的申请/释放要配对)。 - ---- - -## 线程间通信 —— 消息队列(Message Queue) - -> 参考:[RT-Thread 编程手册 · 线程间通信 · 消息队列](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc2/ipc2?id=%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97) - -### 1. 概念 - -消息队列是**邮箱的扩展**:消息长度不固定(不限 4 字节)。应用:线程间消息交换、串口接收不定长数据等。 - -### 2. 工作机制 - -- 线程或中断发送的消息会被**复制**到消息队列自己的内存空间(异步通信); - -- 队列空时接收线程挂起,有新消息到达则唤醒; - -- 遵循**先进先出(FIFO)**原则; - -- 创建时分配多个消息框(消息头 + 消息内容),空闲消息框组成空闲链表,收/发就是消息块在"消息链表"和"空闲链表"之间转移; - -- `rt_mq_urgent` 紧急消息插到队首; - -- 若所有消息最大长度设为 4 字节,消息队列就**退化成邮箱**。 - - ![image-20260819204725153](./figures/image-20260819204725153.png) - -### 3. 消息队列控制块 - -```c -struct rt_messagequeue -{ - struct rt_ipc_object parent; - - void* msg_pool; /* 指向存放消息的缓冲区的指针 */ - rt_uint16_t msg_size; /* 每个消息的长度 */ - rt_uint16_t max_msgs; /* 最大能够容纳的消息数 */ - rt_uint16_t entry; /* 队列中已有的消息数 */ - void* msg_queue_head; /* 消息链表头 */ - void* msg_queue_tail; /* 消息链表尾 */ - void* msg_queue_free; /* 空闲消息链表 */ - rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */ - struct rt_spinlock spinlock; -}; -typedef struct rt_messagequeue* rt_mq_t; -``` - -![image-20260819204759973](./figures/image-20260819204759973.png) - -### 4. API 一览 - -| 操作 | 静态(init/detach) | 动态(create/delete) | -|---|---|---| -| 建立 | `rt_mq_init(&mq, name, msgpool, msg_size, pool_size, flag)` | `rt_mq_create(name, msg_size, max_msgs, flag)` | -| 发送 | `rt_mq_send(&mq, buffer, size)` | 同左 | -| 等待发送 | `rt_mq_send_wait(&mq, buffer, size, timeout)` | 同左 | -| 紧急发送 | `rt_mq_urgent(&mq, buffer, size)` | 同左 | -| 接收 | `rt_mq_recv(&mq, buffer, size, timeout)` | 同左 | -| 销毁 | `rt_mq_detach(&mq)` | `rt_mq_delete(mq)` | - -- `rt_mq_recv` 返回 **>0** 表示收到消息长度;发送时队列满返回 `-RT_EFULL`;消息太长返回 `-RT_ERROR`; -- 动态创建时内存大小 =(msg_size + 消息头大小)× max_msgs。 - -### 5. 与邮箱传数据的区别 - -- 邮箱只能传 4 字节(或指针),消息队列**直接复制任意长度内容**; -- 消息队列收发用**局部变量**即可,数据被复制过去,不用担心指针失效或内存被提前释放,免去动态内存分配的烦恼: - -```c -struct msg { rt_uint8_t *data_ptr; rt_uint32_t data_size; }; - -/* 发送:局部变量直接发内容 */ -struct msg msg_ptr; -msg_ptr.data_ptr = data; -msg_ptr.data_size = length; -rt_mq_send(mq, (void*)&msg_ptr, sizeof(struct msg)); - -/* 接收:同样用局部变量接收 */ -struct msg msg_ptr; -rt_mq_recv(mq, (void*)&msg_ptr, sizeof(struct msg), RT_WAITING_FOREVER); -``` - -### 6. 使用场合 - -- 不定长消息传递(串口接收不定长数据); -- 中断给线程发消息(**中断只能发,不能收**); -- 同步消息:用「消息队列 + 信号量/邮箱」实现确认机制——消息结构体里带一个 `ack`(邮箱或信号量),接收方处理后回确认,发送方据此知道消息已被处理。 - -### 7. 三种通信对象小结 - -| 对象 | 是否传数据 | 特点 | -|---|---|---| -| 信号量 | 不传 | 计数 + 同步,发通知 | -| 邮箱 | 4 字节 / 指针 | 开销低、效率高,适合小消息 | -| 消息队列 | 任意长度 | 内容复制、灵活,适合不定长消息 | diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day4.md" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day4.md" deleted file mode 100644 index e0617858e7002b96d38fde38785715b89702ee3d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/RTT-\345\255\246\344\271\240\346\227\245\345\277\227-Day4.md" +++ /dev/null @@ -1,250 +0,0 @@ -# RTT-学习日志-Day4 - -## RT-Thread I/O 设备框架 - -### 为什么要有框架(驱动碎片化问题) - -- 不同厂商同一外设 API 完全不同:GD `spi_i2s_data_transmit` / ST `HAL_SPI_Transmit` / NXP `LPSPI_MasterTransferBlocking`。 -- 直接基于厂商 API 写设备驱动 → **换芯片就得重写**、学习成本高、代码复用率低。 -- 解决思路:**驱动代码与设备代码分离 + 统一 API**。 - -``` -应用层 → I/O 设备框架(统一 API)→ 对接驱动(各厂商) → 硬件 - ↑ - 设备驱动(SPI/I2C/GPIO 等,可做成 Package 复用) -``` - -- 好处:换 MCU 只改对接驱动;设备驱动可入库复用(防反复造轮子)。 - -### 设备分类:字符设备 vs 块设备 - -| | 字符设备 | 块设备 | -|---|---|---| -| 访问方式 | 顺序、按字节/字符读写 | **可随机访问**,按块(512B 倍数)读写 | -| 例子 | 键盘、串口、LCD 模拟 | 硬盘、SD 卡、Flash | -| 根本区别 | 不能随机访问 | 可以随机访问 | - -- 分类目的:MSH 可重定向到任意字符设备;FATFS 依赖块设备;同类设备统一控制。 - -## I/O 设备 API(核心) - -### 设备生命周期 API - -| 操作 | 函数 | -|---|---| -| 创建设备 | `rt_device_t rt_device_create(int type, int attach_size)` | -| 销毁设备 | `void rt_device_destroy(rt_device_t dev)` | -| 注册设备 | `rt_err_t rt_device_register(rt_device_t dev, const char *name, rt_uint8_t flags)` | -| 注销设备 | `rt_err_t rt_device_unregister(rt_device_t dev)` | -| 查找设备 | `rt_device_t rt_device_find(const char *name)` | - -- 常用 flags:`RT_DEVICE_FLAG_RDWR`(可读写)、`RT_DEVICE_FLAG_RDONLY`、`RT_DEVICE_FLAG_WRONLY`。 -- 设备类型:`RT_Device_Class_Char`(字符)、`RT_Device_Class_Block`(块)、`RT_Device_Class_Pin`(GPIO)、`RT_Device_Class_Sound`、`RT_Device_Class_Graphic` 等(见 `rtdef.h` 的 `rt_device_class_type`)。 - -### 访问设备 API(应用层) - -| 操作 | 函数 | -|---|---| -| 初始化 | `rt_err_t rt_device_init(rt_device_t dev)` | -| 打开 | `rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags)` | -| 关闭 | `rt_err_t rt_device_close(rt_device_t dev)` | -| 控制 | `rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg)` | -| 读 | `rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)` | -| 写 | `rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)` | -| 数据到达回调 | `rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t, rt_size_t))` | -| 发送完成回调 | `rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t, void *))` | - -- 打开标志:`RT_DEVICE_OFLAG_RDWR` / `RT_DEVICE_OFLAG_RDONLY` / `RT_DEVICE_OFLAG_WRONLY`。 -- 应用层访问流程:`rt_device_find` → `rt_device_open` → `rt_device_read/write/control` → `rt_device_close`。 - -### OPS 接口(驱动层,实验二) - -驱动要实现 6 个函数指针并挂到设备上,框架通过这些指针调用驱动: - -| OPS | 函数原型 | -|---|---| -| init | `rt_err_t (*init)(rt_device_t dev)` | -| open | `rt_err_t (*open)(rt_device_t dev, rt_uint16_t oflag)` | -| close | `rt_err_t (*close)(rt_device_t dev)` | -| read | `rt_size_t (*read)(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)` | -| write | `rt_size_t (*write)(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)` | -| control | `rt_err_t (*control)(rt_device_t dev, int cmd, void *args)` | - -核心注册代码(`test_drv.c`): - -```c -int rt_drv_test_init(void) -{ - rt_device_t test_dev = rt_device_create(RT_Device_Class_Char, 0); - if (!test_dev) { LOG_E("create failed"); return -RT_ERROR; } - - test_dev->init = drv_test_init; /* 挂接 6 个 OPS */ - test_dev->open = drv_test_open; - test_dev->close = drv_test_close; - test_dev->read = drv_test_read; - test_dev->write = drv_test_write; - test_dev->control = drv_test_control; - - if (rt_device_register(test_dev, "test_drv", RT_DEVICE_FLAG_RDWR) != RT_EOK) - return -RT_ERROR; - return RT_EOK; -} -INIT_BOARD_EXPORT(rt_drv_test_init); /* 自动初始化,无需手动调用 */ -``` - -- 应用验证:`rt_device_find("test_drv")` → open/write/read/control/close,串口打印对应日志。 -- `INIT_BOARD_EXPORT`:编译期放进特殊段,启动时按序自动执行(BOARD → DEVICE → APP)。 - -## GPIO(PIN 设备) - -- 芯片引脚分电源/时钟/控制/IO;IO 分 **GPIO(通用输入输出)** 与功能复用(SPI/I2C/UART)。 -- GPIO 可编程中断,5 种触发模式:上升沿、下降沿、双沿、高电平、低电平。 - -| 函数 | 作用 | -|---|---| -| `rt_pin_mode(pin, mode)` | 设置引脚模式(`PIN_MODE_INPUT_PULLUP` 等) | -| `rt_pin_write(pin, value)` | 输出电平(`PIN_HIGH` / `PIN_LOW`) | -| `rt_pin_read(pin)` | 读取电平 | -| `rt_pin_attach_irq(pin, mode, hdr, args)` | 绑定中断回调(`PIN_IRQ_MODE_FALLING` 下降沿等) | -| `rt_pin_irq_enable(pin, enabled)` | 使能/禁止中断(`PIN_IRQ_ENABLE`) | -| `rt_pin_detach_irq(pin)` | 脱离中断回调 | - -- 按键中断流程:`rt_pin_mode` 设输入上拉 → `rt_pin_attach_irq` 绑回调 → `rt_pin_irq_enable` 使能。 -- 可勾选 **FlexibleButton** 按键库简化按键处理。 - -### PIN 设备如何对接(三层结构) - -PIN 设备是"专用设备框架"的典型例子,对接分三层: - -``` -应用层:rt_pin_mode / rt_pin_write / rt_pin_read / rt_pin_attach_irq ... - │ (统一 API,与厂商无关) - ▼ -框架层 pin.c:struct rt_pin_ops(7 个函数指针)+ _hw_pin 设备 + rt_device_pin_register() - │ (内核提供,负责转发) - ▼ -对接层 drv_gpio.c:实现 _stm32_pin_ops,用 HAL 库操作 GPIO 寄存器 - │ (厂商/BSP 提供) - ▼ -硬件(STM32 GPIO 寄存器) -``` - -**① 框架层定义接口**(`pin.c`):用 `struct rt_pin_ops` 定义 7 个函数指针,并提供注册函数: - -```c -struct rt_pin_ops { - void (*pin_mode)(rt_device_t, rt_base_t, rt_base_t); /* 设模式 */ - void (*pin_write)(rt_device_t, rt_base_t, rt_base_t); /* 写电平 */ - int (*pin_read)(rt_device_t, rt_base_t); /* 读电平 */ - rt_err_t (*pin_attach_irq)(...); /* 绑中断 */ - rt_err_t (*pin_detach_irq)(...); /* 解绑中断 */ - rt_err_t (*pin_irq_enable)(...); /* 使能中断 */ - rt_base_t (*pin_get)(const char *name); /* 名字转编号 */ -}; - -int rt_device_pin_register(const char *name, const struct rt_pin_ops *ops, void *user_data) -{ - _hw_pin.parent.type = RT_Device_Class_Pin; - _hw_pin.ops = ops; /* 挂接厂商 ops */ - rt_device_register(&_hw_pin.parent, name, RT_DEVICE_FLAG_RDWR); -} -``` - -**② 应用 API 内部转发**(`pin.c`)——应用调的是框架,框架再调 ops: - -```c -void rt_pin_mode(rt_base_t pin, rt_base_t mode) -{ - _hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode); /* 转发给厂商函数 */ -} -``` - -**③ 对接层实现 ops 并注册**(`drv_gpio.c`): - -```c -const static struct rt_pin_ops _stm32_pin_ops = { - stm32_pin_mode, /* 内部调用 HAL_GPIO_Init 设模式 */ - stm32_pin_write, /* 内部调用 HAL_GPIO_WritePin */ - stm32_pin_read, /* 内部调用 HAL_GPIO_ReadPin */ - stm32_pin_attach_irq, - stm32_pin_dettach_irq, - stm32_pin_irq_enable, - stm32_pin_get, /* 把 "PC.5" 解析成编号 */ -}; - -int rt_hw_pin_init(void) { - return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL); -} -``` - -**④ 引脚编号**:`GET_PIN(C, 5)` 宏 → 内部调 `rt_pin_get("PC.5")` → `stm32_pin_get` 解析字符串得到 `PIN_NUM(port, no)` 编号。应用层不接触寄存器,只用编号。 - -- **与 test_dev 的异同**:思想相同(都是"函数指针表 + 注册"),但 PIN 是**专用框架**(有专门的 `rt_pin_ops` 和 `rt_pin_xxx` API),而 test_dev 是**通用字符设备**(用通用 `rt_device` 的 init/open/close/read/write/control 六个 OPS + 通用 `rt_device_xxx` API)。 - -## I2C - -### 协议要点 - -- 速率:低速 10K / 标准 100K / 快速 400K / 高速 3.4M bit/s。 -- 起始位 S:SCL 高时 SDA 高→低;结束位 P:SCL 高时 SDA 低→高。 -- ACK:从机拉低 SDA;NACK:SDA 保持高。 -- 写数据:起始位 → 从机地址+写标志 → 数据 → ACK → 停止位。 -- 读数据:先发地址+写 → 再发地址+读 → 从机应答并返回数据。 - -### 使用思路(4 步) - -1. `rt_device_find("i2c2")` 查找总线 → 2. 构造 `struct rt_i2c_msg` 消息 → 3. `rt_i2c_transfer()` 传输 → 4. 处理结果。 - -```c -struct rt_i2c_msg msgs; -msgs.addr = 0x68; /* 从机地址(不含读写位!) */ -msgs.flags = RT_I2C_WR; /* 或 RT_I2C_RD,读写位靠 flags 控制 */ -msgs.buf = buf; -msgs.len = 1; -rt_i2c_transfer(i2c_bus, &msgs, 1); /* 返回实际传输的消息数 */ -``` - -- 读写寄存器常见模式:写 = 1 条 `RT_I2C_WR` 消息;读 = 2 条消息 `[WR(寄存器地址), RD(读数据)]`。 -- 调试:勾选 **i2c-tools** 软件包,`i2c scan i2c3` 探测总线上的设备;`list_device` 查看总线。 - -### 常见错误 - -- **总线死锁**:主机复位瞬间从机还在等 SCL 变低而拉低 SDA,互相等待。解锁:传输前让 SCL 产生 9 个时钟脉冲。 -- **`rt_i2c_transfer` 返回 -5**:通常是从机地址错误。 - -## SPI - -- SPI 4 根线:SCK / MOSI / MISO / CS(片选),高速、全双工、同步,常用于 FLASH/EEPROM/AD 转换器。 -- RT-Thread 中分 **SPI 总线**(控制器)与 **SPI 设备**(CS 连接的从设备)。 -- 命名:总线 `spi1/spi2`,设备 `spi10`(= spi1 总线上 0 号设备)。 -- 开发模式:写 BUS 驱动 → 注册 SPI 设备 → 打开 → 传输 → 关闭。 - -| 函数 | 作用 | -|---|---| -| `rt_hw_spi_device_attach("spi2", "spi20", GET_PIN(B,12))` | 把设备挂到总线(CS 引脚) | -| `rt_spi_configure(dev, &cfg)` | 配置(`struct rt_spi_configuration`:data_width/mode/max_hz) | -| `rt_spi_transfer(dev, send, recv, len)` | 全双工传一次(自动片选) | -| `rt_spi_send(dev, send, len)` / `rt_spi_recv(dev, recv, len)` | 只发 / 只收 | -| `rt_spi_send_then_send(dev, b1, l1, b2, l2)` | 连续两次发送,**中途不释放片选**(先发地址再发数据) | -| `rt_spi_send_then_recv(dev, send, slen, recv, rlen)` | 先发命令/地址,再收数据(读 FLASH 常用) | -| `rt_spi_transfer_message(dev, msg)` | 自定义多消息传输 | - -```c -struct rt_spi_configuration cfg; -cfg.data_width = 8; -cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; -cfg.max_hz = 1 * 1000 * 1000; /* 1MHz */ -rt_spi_configure(spi20, &cfg); -``` - -## 今日实验小结 - -| 实验 | 内容 | 关键点 | -|---|---|---| -| 实验一 | 注册字符设备 test | `rt_device_create` + `rt_device_register`(无 OPS) | -| 实验二 | 添加 TEST 驱动 OPS 接口 | 实现 6 个 OPS 并挂接,`INIT_BOARD_EXPORT` 自动初始化 | -| 实验三 | GPIO 外部中断(按键) | PIN 设备 6 个 API + 中断回调 | -| 实验四 | I2C 读写(ICM20608 传感器) | `rt_i2c_msg` + `rt_i2c_transfer`,传感器初始化/校准/读三轴 | -| 实验五 | SPI 收发(W25Q128 等) | 总线/设备分离,`rt_spi_*` 系列 API | - -- 核心思想:**统一 I/O API 屏蔽厂商差异,驱动层实现 OPS,应用层只调框架 API**。 diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817100636809.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817100636809.png" deleted file mode 100644 index a65f8254bb544acc84f1770829ad3fa8bc2089b5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817100636809.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817102123446.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817102123446.png" deleted file mode 100644 index 103a6145be8c1e149e33e2f5f6f93cb4708d990c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817102123446.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817102500051.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817102500051.png" deleted file mode 100644 index 1f6adf3ea4c9b1dbf2c924391631d789b81b751d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817102500051.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817102559963.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817102559963.png" deleted file mode 100644 index 856b4315f5960da31589eaa1840a462e852e6c5a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817102559963.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817103806695.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817103806695.png" deleted file mode 100644 index 086dea3c02458317e44cc4d42adf1e8a65460ade..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817103806695.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104204439.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104204439.png" deleted file mode 100644 index bbe57fde50e52afffb8b4840ff059e34d6edb473..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104204439.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104221356.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104221356.png" deleted file mode 100644 index 8c834a7585b87dbef84ec51e145324bba7017acd..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104221356.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104255704.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104255704.png" deleted file mode 100644 index a82638d9a2b7bb4cbe180ef5c0417d758394672b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104255704.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104345415.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104345415.png" deleted file mode 100644 index 444b255a804d8d1630256613af2272b071da14d3..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817104345415.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817193850149.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817193850149.png" deleted file mode 100644 index 525e3b6be805d888637de58fa7ab81d3e6f74315..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817193850149.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817195708890.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817195708890.png" deleted file mode 100644 index f85939d0f078012bfd136007df3385ab269146fa..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817195708890.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817195720415.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817195720415.png" deleted file mode 100644 index 285de744486863c2547132ab31b5ed0461c2d880..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817195720415.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817200541589.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817200541589.png" deleted file mode 100644 index 512dcf9f3b88a634be9933c28993303f7a24ad37..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817200541589.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817200633114.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817200633114.png" deleted file mode 100644 index 2cd35454c1c0e9e92efc89ac9b8affa8603204ef..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817200633114.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817201509787.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817201509787.png" deleted file mode 100644 index 11dbdebe96326e14b78b6b7966ccb0c6a0f38de6..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817201509787.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817203215946.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817203215946.png" deleted file mode 100644 index 8065486d165b1732cdb2bd53c1633c39129d4d17..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817203215946.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817220655304.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817220655304.png" deleted file mode 100644 index c754b98d722629325ea099a610ed1f55e92b44d0..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817220655304.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817231901642.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817231901642.png" deleted file mode 100644 index 6228ea10d4457d4238108c3278a2986e0ad5686f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817231901642.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817231925741.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817231925741.png" deleted file mode 100644 index 9323d9c776bfdc8d4d27840a171b14cce42ab184..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817231925741.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817231955977.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817231955977.png" deleted file mode 100644 index 3dda81242ae453c0a670ad3a862a8b5c088b5b6a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817231955977.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817232207720.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817232207720.png" deleted file mode 100644 index aed95764e7e3d1e583054d7106563bdc6e2f5246..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817232207720.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817232228576.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817232228576.png" deleted file mode 100644 index 41f67d92169180073da3416d9f216d28a19da021..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260817232228576.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818091729256.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818091729256.png" deleted file mode 100644 index 7e98d46bcac343bb9e835ae83c94d2cb676788c2..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818091729256.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818093038715.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818093038715.png" deleted file mode 100644 index bcd7717f3eae061993b1046af39c912e793995b1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818093038715.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818094702649.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818094702649.png" deleted file mode 100644 index 44a45c8c8e5c2b0d57f65d0827362bc6a6ada565..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818094702649.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818094846996.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818094846996.png" deleted file mode 100644 index ff99797ca949886ef07560c9e8a2d668df307589..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818094846996.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100309517.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100309517.png" deleted file mode 100644 index babe723c87b5b9a74d883eb9ce7bc9e5c958e279..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100309517.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100514543.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100514543.png" deleted file mode 100644 index 7ee6c2bc728cc1c967676e161d521510d1e80007..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100514543.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100816622.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100816622.png" deleted file mode 100644 index 6b4520e141ac60714ce7da5c50de19b914146b0e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100816622.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100908981.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100908981.png" deleted file mode 100644 index 7163f2edaf642bb48b5a66c7540b9e77927e798f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818100908981.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818102718602.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818102718602.png" deleted file mode 100644 index ac67f766d64d1e4d168bd48e70557f81d147d076..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260818102718602.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819183007934.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819183007934.png" deleted file mode 100644 index 0eeaad85d09825af0873a9b54c81891f08342f97..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819183007934.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819183016977.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819183016977.png" deleted file mode 100644 index 0eeaad85d09825af0873a9b54c81891f08342f97..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819183016977.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819183122631.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819183122631.png" deleted file mode 100644 index 598cd6a45a37df3ab00d4d35ad07399b7f0795b5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819183122631.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819203537362.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819203537362.png" deleted file mode 100644 index cd9430afdb0a10fd6aa8dc64c35e9fc474264af0..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819203537362.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819203552499.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819203552499.png" deleted file mode 100644 index 3936447010090cef9393f2f749462dccc6d7b78d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819203552499.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819203602213.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819203602213.png" deleted file mode 100644 index 3936447010090cef9393f2f749462dccc6d7b78d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819203602213.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204601399.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204601399.png" deleted file mode 100644 index 79980bc4fc1ba35045f13c52d70e41d6ab8dd5a9..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204601399.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204701638.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204701638.png" deleted file mode 100644 index c6101dc42b0c74bc64c66888f12bb6a3844f36ff..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204701638.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204725153.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204725153.png" deleted file mode 100644 index d7ec0d041ff041385ca46c3ab022f062a1a18ac8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204725153.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204759973.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204759973.png" deleted file mode 100644 index be2290f13a07e84295d83aa6adb317a7ce922ee8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260819204759973.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820091824245.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820091824245.png" deleted file mode 100644 index 1133b31b391c90161e251af763f86533f8a7c922..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820091824245.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820092820955.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820092820955.png" deleted file mode 100644 index bf50478350ac5efe954c1daf82af97407cf9038b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820092820955.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820092859777.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820092859777.png" deleted file mode 100644 index 4f0d4258c794821f31ac6613a043fc59a28a5b6a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820092859777.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820103228554.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820103228554.png" deleted file mode 100644 index faa2b1b8246460a238ec263f2f154e1933ec2b98..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820103228554.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820112944246.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820112944246.png" deleted file mode 100644 index b74a69438c11c9ade438dd82dd099e62964dc121..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820112944246.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820112951627.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820112951627.png" deleted file mode 100644 index b74a69438c11c9ade438dd82dd099e62964dc121..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/image-20260820112951627.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-cb-inherit.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-cb-inherit.png" deleted file mode 100644 index cfd66e514cc981db10557f2df91c246b032eb27d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-cb-inherit.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-init-sample.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-init-sample.png" deleted file mode 100644 index 2b37560958295f62d2fa2a942960b807770de711..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-init-sample.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-stack.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-stack.png" deleted file mode 100644 index 8bfb2528a636575cdcea73d414038efacda70275..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-stack.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-state-diagram.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-state-diagram.png" deleted file mode 100644 index bb74d8cb121bba99211b86cec3b82d874751f15a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-state-diagram.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-state-transition.png" "b/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-state-transition.png" deleted file mode 100644 index 9d538b313888dd3b237269d5552c1526833c5972..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\346\235\216\346\226\207\350\275\251/\347\254\224\350\256\260/figures/pdf_thread-state-transition.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 7fc228f267c2e1687262c9fd3504973948953528..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,6 +0,0 @@ -# 作业 - -## 第一天任务: - -每个人整理好个人笔记和作业,分别提交到gitee仓库(至少两个PR,第一次初始化模板,第二次提交作业和笔记内容) -同时整理一份文档到社区论坛,文章标题为:【RSOC26】xxx \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index dc2fc3e9a7d594e594529dd1e2028f9005f59c79..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,221 +0,0 @@ -# RT-Thread 线程间同步与通信课后作业 - -## 一、作业要求 - -要求: - -1. 不得直接复制 `kernel_samples` 示例后只修改变量名和打印内容。 -2. 每个练习至少创建两个线程。 -3. 所有线程和 IPC 对象都要检查创建结果。 -4. 程序需要通过 MSH 命令启动。 -5. 提交理论总结与实验报告(`README.md`)+ 源代码(`*.c`)。 - ---- - -## 练习一:停车场车位管理 - -### 场景 - -一个停车场只有 3 个车位,同时有 5 辆车尝试进入。车位属于有限资源,车辆只有获得车位后才能进入。 - -### 功能要求 - -1. 创建一个初始值为 3 的计数信号量,表示剩余车位。 -2. 创建 5 个车辆线程。 -3. 每辆车到达后申请一个车位。 -4. 获得车位后打印车辆编号和进入信息。 -5. 车辆停留一段时间后离开,并释放车位。 -6. 未在 2 秒内获得车位的车辆打印等待超时信息,稍后重新申请。 -7. 任意时刻进入停车场的车辆数不能超过 3。 - -### 示例输出 - -```text -car1 entered, used spaces: 1 -car2 entered, used spaces: 2 -car3 entered, used spaces: 3 -car4 waiting for a space -car2 left -car4 entered, used spaces: 3 -``` - -### 思考题 - -为什么这里使用计数信号量,而不是互斥量? - ---- - -## 练习二:多窗口售票系统 - -### 场景 - -两个售票窗口同时出售同一批车票。系统初始有 20 张票,每次只能由一个窗口修改剩余票数。 - -### 功能要求 - -1. 创建两个售票线程,分别代表窗口 A 和窗口 B。 -2. 使用一个全局变量保存剩余票数。 -3. 每个窗口循环完成以下操作: - - 检查是否还有余票; - - 将余票减 1; - - 记录售出的票号; - - 打印售票结果。 -4. 使用互斥量保护完整的售票过程。 -5. 售完后两个线程自动退出。 -6. 最终必须满足: - - 20 张票全部售出; - - 不出现相同票号; - - 剩余票数不能小于 0。 - -### 示例输出 - -```text -window A sold ticket 20 -window B sold ticket 19 -window B sold ticket 18 -... -all tickets sold -remaining tickets: 0 -``` - -### 对比实验 - -先暂时不使用互斥量,并在“读取余票”和“修改余票”之间加入短暂延时,观察是否会出现重复售票或余票错误。 - -随后加入互斥量并再次运行,对比两次结果。 - -### 思考题 - -为什么只给 `ticket_count--` 加锁仍可能不安全? - ---- - -## 练习三:系统启动条件检查 - -### 场景 - -设备启动前需要完成三个独立模块的初始化: - -- 网络模块初始化; -- 传感器模块初始化; -- 存储模块初始化。 - -只有三个模块全部准备完成,主业务线程才能启动。 - -PS:只是举例,模拟即可。 - -### 功能要求 - -定义三个事件位: - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -``` - -创建以下线程: - -- 网络初始化线程; -- 传感器初始化线程; -- 存储初始化线程; -- 主业务线程。 - -每个初始化线程经过不同时间的延时后,发送对应的就绪事件。 - -主业务线程必须使用 AND 模式等待三个事件全部到达,然后打印: - -```text -all modules are ready -business task started -``` - -另外增加一次超时测试: - -- 将存储模块初始化时间设置为 5 秒; -- 主业务线程最多等待 3 秒; -- 超时后打印尚未完成的模块,并再次等待。 - -### 示例输出 - -```text -network ready -sensor ready -wait modules timeout, storage is not ready -storage ready -all modules are ready -business task started -``` - -### 思考题 - -如果用三个独立信号量实现这个功能,与使用事件集相比有什么区别? - ---- - -## 练习四:快递分拣中心 - -### 场景 - -两个收件线程不断产生快递信息,一个分拣线程从消息队列中取出快递并按照目的地区域分类。 - -### 消息结构 - -```c -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -``` - -其中: - -```text -region = 1:华东 -region = 2:华南 -region = 3:华北 -``` - -### 功能要求 - -1. 创建一个消息队列,队列容量为 5 条消息。 -2. 创建两个发件线程,每个线程产生 5 条快递消息。 -3. 快递编号不得重复。 -4. 分拣线程接收消息,并打印快递编号、区域和重量。 -5. 分拣完成后统计: - - 总快递数量; - - 各区域快递数量; - - 所有快递的总重量。 -6. 当队列已满时,发送线程不得崩溃,应记录发送失败次数。 -7. 两个发送线程完成后,向分拣线程发送一条结束消息。 -8. 分拣线程收到结束消息后打印统计结果并退出。 - -### 示例输出 - -```text -package 1001 -> east, weight: 1200 g -package 2001 -> south, weight: 850 g -package 1002 -> north, weight: 960 g -... -total packages: 10 -east: 4, south: 3, north: 3 -total weight: 10250 g -send failed: 0 -``` - ---- - -## 二、MSH 命令要求 - -每个练习导出一个独立命令: - -```text -parking_demo -ticket_demo -startup_demo -package_demo -``` - -重复执行命令时,不得因同名线程或 IPC 对象残留而发生异常。可以在任务结束后主动释放资源,也可以检测任务是否仍在运行并拒绝重复启动。 diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" deleted file mode 100644 index b2cf4fbd6ed9546a77a00e379626f559ae54251a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" +++ /dev/null @@ -1,216 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2025 - * - * All Rights Reserved. - * - * @file package_demo.c - * - * @par dependencies - * - rtthread.h - * - rtdevice.h - * - stdint.h - * - stdlib.h - * - * @author ZHE - * - * @brief startup_demo - * - * Processing flow: - * - * Call directly. - * - * @version V1.0 2026-8-19 - * - * @note 1 tab == 4 spaces! - * - *****************************************************************************/ - - -//******************************** Includes *********************************// -#include -#include -#include -#include -//******************************** Includes *********************************// - -//******************************** Defines **********************************// - -//******************************** Defines **********************************// -//******************************** Variables ********************************// - -static rt_thread_t package1_handle = RT_NULL; -static rt_thread_t package2_handle = RT_NULL; -static rt_thread_t rec_handle = RT_NULL; -static rt_mq_t package_queue = RT_NULL; -static rt_mutex_t package_mutex = RT_NULL; -typedef enum -{ - PKG_REGION_END = 0, - EAST = 1, - SOUTH = 2, - NORTH = 3 -}region_t; -struct package_msg -{ - rt_uint32_t id; - region_t region; - rt_uint16_t weight; -}; -typedef struct package_msg package_msg_t; - -static rt_uint32_t g_total, g_east, g_south, g_north, g_weight; -static rt_uint32_t g_send_fail = 0; -//******************************** Variables ********************************// -//******************************** Functions ********************************// -static void rec_entry(void *param); -static void package1_entry(void *param); -static void package2_entry(void *param); - -static int package_demo(void) -{ - g_total = g_east = g_south = g_north = g_weight = g_send_fail = 0; - package_mutex = rt_mutex_create("package_mutex", RT_IPC_FLAG_PRIO); - if(RT_NULL == package_mutex) - { - rt_kprintf("Failed to create package mutex!\n"); - return -RT_ERROR; - } - package_queue = rt_mq_create("package_queue", - sizeof(struct package_msg), - 5, - RT_IPC_FLAG_PRIO); - if(RT_NULL == package_queue) - { - rt_kprintf("Failed to create package queue!\n"); - return -RT_ERROR; - } - package1_handle = rt_thread_create("package1_thread", - package1_entry, - RT_NULL, - 1024, - 20, - 10); - - if (RT_NULL == package1_handle) - { - rt_kprintf("thread create failed\n"); - return -RT_ERROR; - } - package2_handle = rt_thread_create("package2_thread", - package2_entry, - RT_NULL, - 1024, - 20, - 10); - - if (RT_NULL == package2_handle) - { - rt_kprintf("thread create failed\n"); - return -RT_ERROR; - } - rec_handle = rt_thread_create("rec_thread", - rec_entry, - RT_NULL, - 1024, - 20, - 10); - if (RT_NULL == rec_handle) - { - rt_kprintf("thread create failed\n"); - return -RT_ERROR; - } - rt_thread_startup(package1_handle); - rt_thread_startup(package2_handle); - rt_thread_startup(rec_handle); - return RT_EOK; - -} - - -static void package1_entry(void *param) -{ - struct package_msg msg; - for(int i = 0; i < 6; i++) - { - // Package 1 logic here - msg.id = 2000+i; - msg.region = (region_t)(rand() % 3 + 1); - msg.weight = rand() % 100 + 1; - if(i == 5) - { - msg.region = PKG_REGION_END; - msg.weight = 0; - } - - while (RT_EOK != (rt_mq_send(package_queue, &msg, sizeof(package_msg_t)))) - { - if (RT_EOK == rt_mutex_take(package_mutex, RT_WAITING_FOREVER)) - { - g_send_fail++; - rt_mutex_release(package_mutex); - } - rt_thread_mdelay(10); - } - //rt_kprintf("queue send success\n"); - } -} -static void package2_entry(void *param) -{ - package_msg_t msg; - for(int i = 0; i < 6; i++) - { - msg.id = 1000+i; - msg.region = (region_t)(rand() % 3 + 1); - msg.weight = rand() % 100 + 1; - if(i == 5) - { - msg.region = PKG_REGION_END; - msg.weight = 0; - } - while (RT_EOK != (rt_mq_send(package_queue, &msg, sizeof(package_msg_t)))) - { - if (RT_EOK == rt_mutex_take(package_mutex, RT_WAITING_FOREVER)) - { - g_send_fail++; - rt_mutex_release(package_mutex); - } - rt_thread_mdelay(10); - } - //rt_kprintf("queue send success\n"); - } -} - -static void rec_entry(void *param) -{ - package_msg_t msg; - rt_uint32_t finish_cnt = 0; - - for(;;) - { - if(rt_mq_recv(package_queue, &msg, sizeof(package_msg_t), RT_WAITING_FOREVER)) - { - if (msg.region == PKG_REGION_END) - { - finish_cnt++; - if (finish_cnt == 2) - { - rt_kprintf("All packages: total=%d, east=%d, south=%d, north=%d, weight=%d g, send_fail=%d\n", - g_total, g_east, g_south, g_north, g_weight, g_send_fail); - break; - } - continue; - } - - if (msg.region == EAST) g_east++; - else if (msg.region == SOUTH) g_south++; - else if (msg.region == NORTH) g_north++; - g_total++; - g_weight += msg.weight; - rt_kprintf("message received: id=%d, region=%d, weight=%d\n", msg.id, msg.region, msg.weight); - } - } -} - -MSH_CMD_EXPORT( package_demo,package_demo); -//INIT_APP_EXPORT(thread_example); -//******************************** Functions ********************************// diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo_log" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo_log" deleted file mode 100644 index 54108474e419370c9380aef9c35e902f5e500127..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo_log" +++ /dev/null @@ -1,18 +0,0 @@ -\ | / -- RT - - \ | / -- RT - Thread Operating System - / | \ 5.2.2 build Aug 20 2026 00:36:06 - 2006 - 2024 Copyright by RT-Thread team -msh >package_demo -msh >message received: id=1000, region=3, weight=44 -message received: id=1001, region=3, weight=30 -message received: id=1002, region=1, weight=9 -message received: id=1003, region=2, weight=57 -message received: id=1004, region=3, weight=20 -message received: id=2000, region=1, weight=6 -message received: id=2001, region=1, weight=94 -message received: id=2002, region=1, weight=67 -message received: id=2003, region=3, weight=33 -message received: id=2004, region=2, weight=48 -All packages: total=10, east=4, south=2, north=4, weight=408 g, send_fail=5 diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/packing_demo_log" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/packing_demo_log" deleted file mode 100644 index e4a309cd7aac0cd303c303d24e7694c75d477acb..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/packing_demo_log" +++ /dev/null @@ -1,27 +0,0 @@ - - \ | / -- RT - Thread Operating System - / | \ 5.2.2 build Aug 19 2026 18:41:39 - 2006 - 2024 Copyright by RT-Thread team -msh >parking_demo -msh >car4 entered, used spaces 1 -car3 entered, used spaces 2 -car2 entered, used spaces 3 -car2 left -car1 entered, used spaces 3 -car3 left -car0 entered, used spaces 3 -car4 left -car1 left -car2 entered, used spaces 2 -car0 left -car4 entered, used spaces 2 -car3 entered, used spaces 3 -car2 left -car1 entered, used spaces 3 -car4 left -car3 left -car0 entered, used spaces 2 -car1 left -car2 entered, used spaces 2 -car4 entered, used spaces 3 diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" deleted file mode 100644 index acec3d7d58280e441d6fb4ca4dac6d26fb769833..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" +++ /dev/null @@ -1,118 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2025 - * - * All Rights Reserved. - * - * @file packing_space_manager.c - * - * @par dependencies - * - rtthread.h - * - rtdevice.h - * - stdint.h - * - stdlib.h - * @author ZHE - * - * @brief packing_space_manager - * - * Processing flow: - * - * Call directly. - * - * @version V1.0 2026-8-19 - * - * @note 1 tab == 4 spaces! - * - *****************************************************************************/ - - -//******************************** Includes *********************************// -#include -#include -#include -#include -//******************************** Includes *********************************// - -//******************************** Defines **********************************// - -//******************************** Defines **********************************// - -//******************************** Variables ********************************// -static rt_sem_t pack_space_sem = RT_NULL; -static rt_thread_t pack_space_thread = RT_NULL; -static volatile uint8_t use_pack_space=0; -static void pack_space_manager_thread_entry(void *param); -//******************************** Variables ********************************// - -//******************************** Functions ********************************// - - - - -static int parking_demo(void) -{ - pack_space_sem = rt_sem_create("pack_sem", 3, RT_IPC_FLAG_PRIO); - if (pack_space_sem == RT_NULL) - { - rt_kprintf("pack_space_sem create failed!\n"); - } - char name[8]; - for(int i=0;i<5;i++) - { - rt_snprintf(name, sizeof(name), "pack%d", i); - pack_space_thread = rt_thread_create(name, - pack_space_manager_thread_entry, - (void*)i, - 1024, - 20, - 10); - if(pack_space_thread != RT_NULL) - { - rt_thread_startup(pack_space_thread); - } - else - { - rt_kprintf("pack_space_thread create failed!\n"); - } - } - return RT_EOK; -} - - - -static void pack_space_manager_thread_entry(void *param) -{ - rt_uint32_t thread_id = (uint32_t)param; - char name[20]; - - rt_err_t ret =RT_EOK; - for(;;) - { - rt_snprintf(name, sizeof(name), "car%d", thread_id); - ret = rt_sem_take(pack_space_sem, RT_TICK_PER_SECOND*2); - if(RT_EOK == ret) - { - rt_enter_critical(); - use_pack_space++; - rt_kprintf("%s entered, used spaces %d\n", name, use_pack_space); - rt_exit_critical(); - rt_thread_mdelay(1000+rand()%1000); - rt_sem_release(pack_space_sem); - rt_enter_critical(); - use_pack_space--; - rt_kprintf("%s left\n", name); - rt_exit_critical(); - rt_thread_mdelay(1000+rand()%1000); - } - else if(RT_ETIMEOUT == ret) - { - rt_kprintf("%s waiting for a space\n", name); - } - rt_thread_mdelay(500); - } -} - - - -MSH_CMD_EXPORT(parking_demo,parking_demo ); -// INIT_APP_EXPORT(parking_demo); -//******************************** Functions ********************************// diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" deleted file mode 100644 index 98b8954d4913019442ba1b8a3ec3034bc28b6262..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" +++ /dev/null @@ -1,188 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2025 - * - * All Rights Reserved. - * - * @file muti_window_sys.c - * - * @par dependencies - * - rtthread.h - * - rtdevice.h - * - stdint.h - * - stdlib.h - * - * @author ZHE - * - * @brief startup_demo - * - * Processing flow: - * - * Call directly. - * - * @version V1.0 2026-8-19 - * - * @note 1 tab == 4 spaces! - * - *****************************************************************************/ - - -//******************************** Includes *********************************// -#include -#include -#include -#include -//******************************** Includes *********************************// - -//******************************** Defines **********************************// -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -//******************************** Defines **********************************// -//******************************** Variables ********************************// -static rt_thread_t net_thread_handle = RT_NULL; -static rt_thread_t sensor_thread_handle = RT_NULL; -static rt_thread_t storage_thread_handle = RT_NULL; -static rt_thread_t application_thread_handle = RT_NULL; -static rt_event_t startup_ev = RT_NULL; - -//******************************** Variables ********************************// -//******************************** Functions ********************************// - - -static void net_entry(void *param); -static void sensor_entry(void *param); -static void storage_entry(void *param); -static void application_entry(void *param); - - -static int startup_demo(void) -{ - startup_ev=rt_event_create("startup_ev", RT_IPC_FLAG_PRIO); - if (!startup_ev) { - rt_kprintf("event create failed\n"); - return -RT_ERROR; - } - - - net_thread_handle = rt_thread_create("net", - net_entry, - RT_NULL, - 4096, - 20, - 10); - sensor_thread_handle = rt_thread_create("sensor", - sensor_entry, - RT_NULL, - 4096, - 20, - 10); - storage_thread_handle = rt_thread_create("storage", - storage_entry, - RT_NULL, - 4096, - 20, - 10); - application_thread_handle = rt_thread_create("app", - application_entry, - RT_NULL, - 4096, - 20, - 10); - - if ( - !net_thread_handle || - !sensor_thread_handle || - !storage_thread_handle || - !application_thread_handle - ) - { - rt_kprintf("thread create failed\n"); - return -RT_ERROR; - } - - rt_thread_startup(net_thread_handle); - rt_thread_startup(sensor_thread_handle); - rt_thread_startup(storage_thread_handle); - rt_thread_startup(application_thread_handle); - return RT_EOK; - -} - - -static void net_entry(void *param) -{ - - rt_thread_mdelay(100); - if(RT_EOK != rt_event_send(startup_ev, EVENT_NET_READY)) - { - rt_kprintf("Failed to send net ready event!\n"); - } - rt_kprintf("network ready\n"); -// rt_thread_delete(rt_thread_self()); - -} -static void sensor_entry(void *param) -{ - - rt_thread_mdelay(200); - if(RT_EOK != rt_event_send(startup_ev, EVENT_SENSOR_READY)) - { - rt_kprintf("Failed to send sensor ready event!\n"); - } - rt_kprintf("sensor ready\n"); - -// rt_thread_delete(rt_thread_self()); -} -static void storage_entry(void *param) -{ - - - rt_thread_mdelay(RT_TICK_PER_SECOND*5); - if(RT_EOK != rt_event_send(startup_ev, EVENT_STORAGE_READY)) - { - rt_kprintf("Failed to send storage ready event!\n"); - } - rt_kprintf("storage ready\n"); - -// rt_thread_delete(rt_thread_self()); - -} -static void application_entry(void *param) -{ - rt_uint32_t e = 0; - rt_err_t ret=RT_EOK; - for(;;) - { - ret = rt_event_recv(startup_ev, - EVENT_NET_READY | - EVENT_SENSOR_READY | - EVENT_STORAGE_READY , - RT_EVENT_FLAG_AND | - RT_EVENT_FLAG_CLEAR , - RT_TICK_PER_SECOND*3, - &e); - if(RT_EOK == ret) - { - rt_kprintf("all modules are ready\nbusiness task started\n"); - break; - } - else if(-RT_ETIMEOUT == ret) - { - rt_kprintf("wait modules timeout"); - if (!(e & EVENT_NET_READY)) rt_kprintf(", net"); - if (!(e & EVENT_SENSOR_READY)) rt_kprintf(", sensor"); - if (!(e & EVENT_STORAGE_READY)) rt_kprintf(", storage"); - rt_kprintf(" is not ready\n"); - continue; - } - else - { - rt_kprintf("wait modules error\n"); - break; - } - rt_thread_mdelay(100+rand()%100); - } -} -MSH_CMD_EXPORT( startup_demo,startup_demo); -//INIT_APP_EXPORT(thread_example); -//******************************** Functions ********************************// diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo_log" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo_log" deleted file mode 100644 index 2746d73bcb65ca845c65fb906698b4fd9ad9dc28..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo_log" +++ /dev/null @@ -1,11 +0,0 @@ - \ | / -- RT - Thread Operating System - / | \ 5.2.2 build Aug 19 2026 23:29:01 - 2006 - 2024 Copyright by RT-Thread team -msh >startup_demo -msh >network ready -sensor ready -wait modules timeout, net, sensor, storage is not ready -storage ready -all modules are ready -business task started diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tick_demo.c" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tick_demo.c" deleted file mode 100644 index f99e0e42a53370c0e432135e9457f0939f2a398b..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tick_demo.c" +++ /dev/null @@ -1,135 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2025 - * - * All Rights Reserved. - * - * @file muti_window_sys.c - * - * @par dependencies - * - rtthread.h - * - rtdevice.h - * - stdint.h - * - * @author ZHE - * - * @brief packing_space_manager - * - * Processing flow: - * - * Call directly. - * - * @version V1.0 2026-8-19 - * - * @note 1 tab == 4 spaces! - * - *****************************************************************************/ - - -//******************************** Includes *********************************// -#include -#include -#include -#include -//******************************** Includes *********************************// - -//******************************** Defines **********************************// -#define WINDOW_TICKET_COUNT 20 -//******************************** Defines **********************************// -//******************************** Variables ********************************// -static void window_tick_entry(void *param); - -static rt_mutex_t window_mutex_handle = RT_NULL; -static rt_thread_t window_A_thread_handle = RT_NULL; -static rt_thread_t window_B_thread_handle = RT_NULL; -static rt_uint8_t window_ticket_count = WINDOW_TICKET_COUNT; - -//******************************** Variables ********************************// -//******************************** Functions ********************************// - - -static int ticket_demo(void) -{ - window_mutex_handle = rt_mutex_create("window_mutex", RT_IPC_FLAG_PRIO); - if (window_mutex_handle == RT_NULL) - { - rt_kprintf("window_mutex_create failed!\n"); - return -1; - } - window_A_thread_handle = rt_thread_create("window_A", - window_tick_entry, - (void*)'A', - 1024, - 20, - 10); - if (window_A_thread_handle == RT_NULL) - { - rt_kprintf("window_A_thread_create failed!\n"); - return -1; - } - window_B_thread_handle = rt_thread_create("window_B", - window_tick_entry, - (void*)'B', - 1024, - 20, - 10); - if (window_B_thread_handle == RT_NULL) - { - rt_kprintf("window_B_thread_create failed!\n"); - return -1; - } - if(rt_thread_startup(window_A_thread_handle) != RT_EOK) - { - rt_kprintf("window_A_thread_startup failed!\n"); - return -1; - } - if(rt_thread_startup(window_B_thread_handle) != RT_EOK) - { - rt_kprintf("window_B_thread_startup failed!\n"); - return -1; - } - return 0; -} -static void window_tick_entry(void *param) -{ - char window_name = (char)(rt_uintptr_t)param; - static int count_zero_flag=1; - for(;;) - { - // if(RT_EOK == rt_mutex_take(window_mutex_handle,RT_WAITING_FOREVER)) - // { - if(window_ticket_count > 0) - { - rt_kprintf("window_%c sold ticket %d\n", window_name, - window_ticket_count); - window_ticket_count--; - rt_thread_mdelay(100+rand()%100); -// if(rt_mutex_release(window_mutex_handle) != RT_EOK) -// { -// rt_kprintf("window_%c rt_mutex_release failed!\n", -// window_name); -// } - } - else - { - if(count_zero_flag) - { - rt_kprintf("all tickets sold\nremaining tickets: 0\n"); - count_zero_flag=0; - } - // rt_mutex_release(window_mutex_handle); - break; - } - // } - // else - // { - // rt_mutex_release(window_mutex_handle); - // rt_kprintf("window_%c rt_mutex_take failed!\n", window_name); - // break; - // } - rt_thread_mdelay(1000+rand()%1000); - } -} - -MSH_CMD_EXPORT( ticket_demo,ticket_demo); -//INIT_APP_EXPORT(thread_example); -//******************************** Functions ********************************// diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tick_demo_log" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tick_demo_log" deleted file mode 100644 index 2f01c80d3fedd58055580a895c8585fac8836ae3..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tick_demo_log" +++ /dev/null @@ -1,62 +0,0 @@ -使用互斥锁之前 -没有出现竞态因为串口的底层驱动里是带有互斥的刚好这样子 - - \ | / -- RT - Thread Operating System - / | \ 5.2.2 build Aug 19 2026 19:36:03 - 2006 - 2024 Copyright by RT-Thread team -msh >ticket_demo -msh >window_B sold ticket 20 -window_A sold ticket 19 -window_B sold ticket 18 -window_A sold ticket 17 -window_B sold ticket 16 -window_A sold ticket 15 -window_A sold ticket 14 -window_B sold ticket 13 -window_B sold ticket 12 -window_A sold ticket 11 -window_B sold ticket 10 -window_A sold ticket 9 -window_B sold ticket 8 -window_A sold ticket 7 -window_B sold ticket 6 -window_A sold ticket 5 -window_B sold ticket 4 -window_A sold ticket 3 -window_B sold ticket 2 -window_B sold ticket 1 -all tickets sold -remaining tickets: 0 - - - - -使用互斥锁之后 - \ | / -- RT - Thread Operating System - / | \ 5.2.2 build Aug 19 2026 19:36:03 - 2006 - 2024 Copyright by RT-Thread team -msh >ticket_demo -msh >window_B sold ticket 20 -window_A sold ticket 19 -window_B sold ticket 18 -window_A sold ticket 17 -window_B sold ticket 16 -window_A sold ticket 15 -window_B sold ticket 14 -window_A sold ticket 13 -window_B sold ticket 12 -window_A sold ticket 11 -window_B sold ticket 10 -window_A sold ticket 9 -window_B sold ticket 8 -window_A sold ticket 7 -window_B sold ticket 6 -window_A sold ticket 5 -window_B sold ticket 4 -window_A sold ticket 3 -window_B sold ticket 2 -window_A sold ticket 1 -all tickets sold -remaining tickets: 0 diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index ff1a4e5bb338d9944ce1e93cf2bd03061b9b0455..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,62 +0,0 @@ -# 【DAY2】RT-Thread 启动流程、内核基础与多线程编程 - -- **目标平台**:STM32F407 星火号(`stm32f407-rtspark`) -- **提交仓库**:`https://gitee.com/rtthread/rsoc-rtt` -- **作业交付物**:理论总结与实验报告(`README.md`)+ 可编译源码(`*.c`)通过 Gitee PR 提交。 - -## Part 1:理论知识梳理文档(写入作业目录 `README.md`) - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -- **启动路径**:详细画出/写出从上电复位 `Reset_Handler` 到进入 `main()` 的完整调用链。 -- **自动初始化原理**:解释 `INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` 的底层实现原理。 - -### 2. 内核基础与对象模型 - -- **对象容器与控制块**:简述 RT-Thread 面向对象的设计思想,线程控制块(`struct rt_thread`)中关键成员(如 `stat`、`current_priority`、`init_tick`、`sp`)的作用。 -- **内存分配方式**:对比静态对象与动态对象的区别(分配位置、生命周期、释放机制),分析为什么某些高可靠场景强制使用静态线程。 - -### 3. 线程状态机与调度算法 - -- **五态模型**:绘制或描述 RT-Thread 线程状态(初始态、就绪态、运行态、挂起态、关闭态)及触发状态转换的典型 API。 -- **调度机制剖析**: - - **抢占式调度** - - **时间片轮转** - -## Part 2:多线程实战编程(C 语言实现) - -### 任务说明 - -1. **工作线程 1(动态创建 `th_led1`)**: - - 优先级 `16`,时间片 `5 ticks`,栈 `1024 Bytes`。 - - 循环翻转板载 LED_R,每次执行打印计数与 `rt_tick_get()`。 -2. **工作线程 2(静态初始化 `th_led2`)**: - - 优先级 `16`,时间片 `5 ticks`,静态定义全局栈与 TCB。 - - 循环翻转板载 LED_B,每次执行打印计数。 -3. **监控与控制线程(动态创建 `th_monitor`)**: - - 优先级 `10`(高于工作线程),周期延时 `rt_thread_mdelay(2000)`。 - - 唤醒后获取并打印 `th_led1` 与 `th_led2` 的运行状态与剩余栈空间大小。 - - 结合板载按键检测(或按键中断):单次触发时调用 `rt_thread_suspend()` 挂起 `th_led1`;再次触发时调用 `rt_thread_resume()` 恢复。 -4. **系统集成与调试命令**: - - 使用 `INIT_APP_EXPORT()` 自动加载测试模块,禁止在 `main.c` 中硬编码调用。 - - 使用 `MSH_CMD_EXPORT()` 导出自定义查看命令(如 `thread_dump`)。 - -## Part 3:Gitee PR 提交与规范 - -### 1. 提交目录规范 - -``` -2026/<组别>/<姓名>/第二天作业 -├── README.md # 包含:Part 1 理论整理 + Part 2 实验结果分析与串口截图 -└── thread_study.c # 作业源码(含动/静态线程、自动初始化、MSH导出) -``` - -### 2. PR 提交流程与规范 - -1. Fork 官方仓库 [https://gitee.com/rtthread/rsoc-rtt](https://gitee.com/rtthread/rsoc-rtt) 到个人账号。 -2. 基于 `master` 新建分支:`day2-姓名拼音`。 -3. 编写代码、文档并进行本地 `scons` 编译验证与星火1号烧录测试。 -4. 提交 Commit。 -5. 向主仓库提交 Pull Request: - - **PR 标题**:`[作业提交] 内核与多线程培训 - <姓名>` - - **PR 内容**:附带理论文档简述、硬件运行现象照片及 FinSH 串口日志输出。 diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/log" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/log" deleted file mode 100644 index c165c4841b3096393224b8f54faca0138b7bd5ae..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/log" +++ /dev/null @@ -1,33 +0,0 @@ - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 18 2026 17:06:12 - 2006 - 2022 Copyright by RT-Thread team -th_led1 16 ready 1024 956 -th_led2 16 ready 1024 956 -led2 count: 1, tick: 9 -led1 count: 1, tick: 12 -msh >th_led1 16 suspend 1024 800 -th_led2 16 ready 1024 800 -led1 count: 2, tick: 2016 -led2 count: 2, tick: 2018 -th_led1 16 suspend 1024 800 -th_led2 16 suspend 1024 800 -led2 count: 3, tick: 4023 -led1 count: 3, tick: 4025 -th_led1 16 suspend 1024 800 -th_led2 16 ready 1024 800 -led1 count: 4, tick: 6030 -led2 count: 4, tick: 6032 -th_led1 16 suspend 1024 800 -th_led2 16 suspend 1024 800 -led2 count: 5, tick: 8037 -lth_led1 16 suspend 1024 780 -th_led2 16 ready 1024 800 -led2 count: 6, tick: 10044 -th_led1 16 ready 1024 780 -th_led2 16 ready 1024 800 -led2 count: 7, tick: 12051 -ed2 count: 7, tick: 12051th_led1 16 suspend 1024 780 -th_led2 16 ready 1024 800 -led2 coled1 count: 6, tick: 14059 -unt: 6, tick: 14059 diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 0f316a94a6d47df9c63abae347c4acbb647fd3c8..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,264 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2025 - * - * All Rights Reserved. - * - * @file thread_study.c - * - * @par dependencies - * - rtthread.h - * - * @author ZHE - * - * @brief rtthread - * - * Processing flow: - * - * Call directly. - * - * @version V1.0 2026-8-18 - * - * @note 1 tab == 4 spaces! - * - *****************************************************************************/ - - -//******************************** Includes *********************************// -#include -#include -#include -#include -//******************************** Includes *********************************// - -//******************************** Defines **********************************// - -//******************************** Defines **********************************// - -//******************************** Variables ********************************// -static void key_left_cb(void*params); - -volatile static uint8_t key_pressed_flag = 0; -volatile static uint8_t led1_suspend_flag = 0; - - -rt_thread_t th_led1_handle; -struct rt_thread th_led2; -rt_thread_t th_monitor_handle; -static rt_uint32_t led2_stack[256]={0}; - -static void print_one(rt_thread_t t); -static void thread_led1_entry(void *parameter); -static void thread_led2_entry(void *parameter); -static void thread_monitor_entry(void *parameter); - -//******************************** Variables ********************************// - -//******************************** Functions ********************************// - -int thread_example(void) -{ - - rt_err_t ret_err=RT_EOK; - th_led1_handle = rt_thread_create( "th_led1", - thread_led1_entry, - NULL, - 1024,16, 5); - if(th_led1_handle==NULL) - { - rt_kprintf("thread led1 create failed\n"); - // Handle error - return -1; - } - if(RT_EOK!= rt_thread_startup(th_led1_handle)) - { - rt_kprintf("thread led1 startup failed\n"); - // Handle error - return -1; - } - ret_err = rt_thread_init(&th_led2,"th_led2",thread_led2_entry, NULL, - led2_stack, sizeof(led2_stack), 16, 5); - if(ret_err!=RT_EOK) - { - rt_kprintf("thread led2 init failed\n"); - // Handle error - return -1; - } - if(RT_EOK!= rt_thread_startup(&th_led2)) - { - rt_kprintf("thread led2 startup failed\n"); - // Handle error - return -1; - } - th_monitor_handle = rt_thread_create("th_monitor", - thread_monitor_entry, - NULL, - 1024, 10, 5); - if(th_monitor_handle==NULL) - { - rt_kprintf("thread monitor create failed\n"); - // Handle error - return -1; - } - if(RT_EOK!= rt_thread_startup(th_monitor_handle)) - { - rt_kprintf("thread monitor startup failed\n"); - // Handle error - return -1; - } - return 0; -} - - -static void thread_led1_entry(void *parameter) -{ - rt_base_t led1_pin = rt_pin_get("PF.12"); - rt_uint32_t count = 0; - - if (led1_pin < 0) - { - rt_kprintf("led1 pin get failed\n"); - return; - } - rt_pin_mode(led1_pin, PIN_MODE_OUTPUT); - - for(;;) - { - if(led1_suspend_flag) - { - led1_suspend_flag=0; - rt_pin_write(led1_pin, PIN_HIGH); - rt_thread_suspend(th_led1_handle); - } - count++; - rt_kprintf("led1 count: %u, tick: %u\n", count, rt_tick_get()); - rt_pin_write(led1_pin, PIN_HIGH); - rt_thread_mdelay(1000); - rt_pin_write(led1_pin, PIN_LOW); - rt_thread_mdelay(1000); - } -} -static void thread_led2_entry(void *parameter) -{ - rt_base_t led2_pin = rt_pin_get("PF.11"); - rt_uint32_t count = 0; - if(led2_pin<0) - { - rt_kprintf("led2 pin get failed\n"); - return; - } - rt_pin_mode(led2_pin, PIN_MODE_OUTPUT); - for(;;) - { - count++; - rt_kprintf("led2 count: %u, tick: %u\n", count, rt_tick_get()); - rt_pin_write(led2_pin, PIN_LOW); - rt_thread_mdelay(1000); - rt_pin_write(led2_pin, PIN_HIGH); - rt_thread_mdelay(1000); - } -} -static void thread_monitor_entry(void *parameter) -{ - -// rt_thread_t thread; -// rt_uint8_t *p; -// rt_uint32_t free_size; - - - /******KEY pin ********/ - rt_base_t key_left_pin= rt_pin_get("PC.0"); - if(key_left_pin<0) - { - rt_kprintf("key left pin get failed\n"); - return; - } - rt_pin_mode(key_left_pin, PIN_MODE_INPUT_PULLUP); - rt_pin_attach_irq(key_left_pin, PIN_IRQ_MODE_FALLING, key_left_cb, NULL); - rt_pin_irq_enable(key_left_pin, PIN_IRQ_ENABLE); - - /******KEY pin ********/ - while (1) - { - if(key_pressed_flag) - { - static int cnt=0; - cnt++; - key_pressed_flag = 0; - if(cnt%2!=0) - { - // - led1_suspend_flag=1; - - } - else - { - led1_suspend_flag = 0; - rt_thread_resume(th_led1_handle); - } - - } - /* stop scheduler so the object list won't change while iterating */ - rt_enter_critical(); - print_one(th_led1_handle); - print_one(&th_led2); - rt_exit_critical(); - rt_thread_mdelay(2000); - } -} - -static rt_tick_t last_tick = 0; -static void key_left_cb(void *params) -{ - if (rt_tick_get() - last_tick > RT_TICK_PER_SECOND / 20) { // 50ms���� - key_pressed_flag = 1; - last_tick = rt_tick_get(); - } -} - -static void print_one(rt_thread_t t) -{ - const char *stat_str = "unknown"; - rt_uint32_t free_size = 0; - rt_uint8_t *p; - - switch (t->stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: stat_str = "init"; break; - case RT_THREAD_READY: stat_str = "ready"; break; - case RT_THREAD_SUSPEND: stat_str = "suspend"; break; - case RT_THREAD_RUNNING: stat_str = "running"; break; - case RT_THREAD_CLOSE: stat_str = "close"; break; - } - - p = (rt_uint8_t *)t->stack_addr; - while ((free_size < t->stack_size) && (*p == '#')) - { - p++; - free_size++; - } - - rt_kprintf("%-*.*s %-3d %-9s %-8u %u\n", - RT_NAME_MAX, RT_NAME_MAX, t->name, - t->current_priority, stat_str, - t->stack_size, free_size); -} - -static int thread_dump(int argc, char **argv) -{ - - if (th_led1_handle == RT_NULL) - { - rt_kprintf("threads not started\n"); - return -1; - } - rt_kprintf("\n%-12s %-3s %-9s %-8s %s\n", - "thread", "pri", "status", "stack", "free"); - print_one(th_led1_handle); - print_one(&th_led2); - return 0; -} - - -MSH_CMD_EXPORT(thread_dump, dump th_led1 and th_led2 status); -INIT_APP_EXPORT(thread_example); -//******************************** Functions ********************************// diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111017735-1787022619435-5.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111017735-1787022619435-5.png" deleted file mode 100644 index 6f60b54ab3ca57df5f19404fb06d9a5ee134ff87..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111017735-1787022619435-5.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111017735.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111017735.png" deleted file mode 100644 index 6f60b54ab3ca57df5f19404fb06d9a5ee134ff87..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111017735.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111117274-1787022679357-7.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111117274-1787022679357-7.png" deleted file mode 100644 index 32087a28302382a17561d204d9a30887c64ab1c1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111117274-1787022679357-7.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111117274.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111117274.png" deleted file mode 100644 index 32087a28302382a17561d204d9a30887c64ab1c1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111117274.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111155944-1787022717605-9.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111155944-1787022717605-9.png" deleted file mode 100644 index 216af50d7f95236115aaeb2b09ffce022e542f48..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111155944-1787022717605-9.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111155944.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111155944.png" deleted file mode 100644 index 216af50d7f95236115aaeb2b09ffce022e542f48..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111155944.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111235130-1787022756355-11-1787022808165-13.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111235130-1787022756355-11-1787022808165-13.png" deleted file mode 100644 index b40d7537ca12f7d320d10e7c9f6fee18d2bcbb72..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111235130-1787022756355-11-1787022808165-13.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111235130-1787022756355-11.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111235130-1787022756355-11.png" deleted file mode 100644 index b40d7537ca12f7d320d10e7c9f6fee18d2bcbb72..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111235130-1787022756355-11.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111235130.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111235130.png" deleted file mode 100644 index b40d7537ca12f7d320d10e7c9f6fee18d2bcbb72..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111235130.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111333062.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111333062.png" deleted file mode 100644 index 0b6552f9f37d9e50d41327653f08259e5fd811bb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111333062.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111401949.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111401949.png" deleted file mode 100644 index 1dcd66cb0144793e97cf5c3d3947cd64806bdc50..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818111401949.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818113812543.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818113812543.png" deleted file mode 100644 index cd07cf5d55e1dc7b8ed750faad788453daeb7230..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818113812543.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818113842487-1787024325155-4.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818113842487-1787024325155-4.png" deleted file mode 100644 index cd07cf5d55e1dc7b8ed750faad788453daeb7230..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818113842487-1787024325155-4.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818113842487.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818113842487.png" deleted file mode 100644 index cd07cf5d55e1dc7b8ed750faad788453daeb7230..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/image-20260818113842487.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/rtt_startup-1787022582540-3.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/rtt_startup-1787022582540-3.png" deleted file mode 100644 index 04dbb79fed05b7463c5ceea179f5dd75c6553724..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/rtt_startup-1787022582540-3.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/rtt_startup.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/rtt_startup.png" deleted file mode 100644 index 04dbb79fed05b7463c5ceea179f5dd75c6553724..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/rtthread\345\220\257\345\212\250\346\265\201\347\250\213.assets/rtt_startup.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260_gitee\347\233\270\345\205\263.md" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260_gitee\347\233\270\345\205\263.md" deleted file mode 100644 index 93e387f10a55eb6cfddbcc5262f153993d7146e5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260_gitee\347\233\270\345\205\263.md" +++ /dev/null @@ -1,348 +0,0 @@ -# Git 基础命令笔记 - -## 一、工作区 / 暂存区 / 远端 的关系 - -Git 把代码分成三层: - -| 区域 | 说明 | -| ------------------------------- | ------------------------------------------------------------ | -| **工作区**(Working Directory) | 你当前在磁盘上看到、正在编辑的文件 | -| **暂存区**(Stage / Index) | 本地的 git 管理库,存放"准备提交"的快照;加入暂存区后这些文件可以得到一个保护 | -| **远端仓库**(Remote) | 服务器上的仓库(如 GitHub / Gitee) | - -数据流向: - -``` -工作区 ──add──▶ 暂存区 ──commit──▶ 本地仓库 ──push──▶ 远端 -工作区 ◀─pull── 远端(pull = fetch + merge) -``` - -记忆口诀: - -- `git pull`:**远端 → 本地**(把服务器最新内容拉到本地) -- `git push`:**本地 → 远端**(把本地提交同步到服务器) - ---- - -## 二、核心命令清单 - -### 1. `git init` - -**初始化当前目录为 git 仓库。** - -```bash -git init -``` - -执行后当前目录会出现一个 `.git` 隐藏文件夹,git 开始对这个目录进行版本管理。 - ---- - -### 2. `git pull origin master` - -**拉取远端 master 分支的最新内容到本地。** - -```bash -git pull origin master -``` - -- `origin`:远端仓库的默认别名 -- `master`:要拉取的分支名 -- 通常在开始干活前先 `pull` 一次,避免和远端产生冲突 - ---- - -### 3. `git add ` - -**把指定文件 / 文件夹加入 git 暂存区。** - -```bash -git add readme.md # 添加单个文件 -git add src/ # 添加整个文件夹 -git add . # 添加当前目录下所有改动 -``` - -> **补充:git 暂存区代表你本地的 git 管理库,将指定的文件/文件夹加入暂存库后,这些文件可以得到一个保护。** - ---- - -### 4. `git commit -m "message"` - -**把暂存区的内容提交到本地仓库,本次提交日志为 `message`。** - -```bash -git commit -m "feat: 新增串口驱动" -``` - -- `-m` 后面跟提交说明 -- 一条好的 commit message 应说明"这次改了什么、为什么改" - -> ⚠️ 课程中写的是 `git commit "message"`,实际用法需要加 `-m` 参数:`git commit -m "message"`。 - ---- - -### 5. `git push origin master` - -**把本地 master 分支的最新内容推送到远端。** - -```bash -git push origin master -``` - -提交完 `commit` 之后,还需要 `push` 才能让远端(GitHub/Gitee)看到你的改动。 - ---- - -## 三、典型工作流程(本地 ↔ 远端同步) - -```bash -# 第一次:初始化 + 连接远端 -git init -git remote add origin <仓库地址> - -# 日常开发循环 -git pull origin master # 1. 先拉取远端最新代码 -# …… 编辑文件 …… -git add . # 2. 加入暂存区 -git commit -m "本次改动说明" # 3. 提交到本地仓库 -git push origin master # 4. 推送到远端 -``` - ---- - -## 四、远端地址与分支操作 - -### 6. `git remote set-url origin ` - -**设置 / 修改当前 git 仓库的远端地址。** - -```bash -git remote set-url origin git@gitee.com:kurisaW/git_-proj.git -``` - -- `origin` 是远端别名,`` 换成自己的仓库地址 -- 常见场景:仓库地址改了、从 HTTPS 换成 SSH、或初始化后第一次绑定远端 -- 括号里的"根据自己的修改"= URL 要换成你自己的 gitee/github 地址 - -> 💡 第一次绑定远端用的是 `git remote add origin `;后续要改地址才用 `set-url`。 - ---- - -### 7. `git fetch origin master` - -**把远端 master 分支的内容同步到本地(只更新远端跟踪分支,不动工作区)。** - -```bash -git fetch origin master -``` - -- `fetch` 和 `pull` 的区别: - - `git fetch`:**只下载**远端最新内容到本地的 `origin/master` 引用,**不合并**到当前分支,工作区不变 - - `git pull` = `git fetch` + `git merge`,会直接把改动合并进当前分支 -- 想先看看远端有什么变化、再决定要不要合并时,用 `fetch` 更安全 - ---- - -### 8. `git remote -v` - -**查看当前仓库的远端地址(远端分支情况)。** - -```bash -git remote -v -``` - -- `-v` 即 `--verbose`,会列出抓取(fetch)和推送(push)两行地址 - -- 输出示例: - - ``` - origin git@gitee.com:kurisaW/git_-proj.git (fetch) - origin git@gitee.com:kurisaW/git_-proj.git (push) - ``` - -- 用来确认远端地址有没有配对、是不是指向正确的仓库 - ---- - -### 9. `git checkout -b <分支名>` - -**基于当前分支,切出一个新分支并立即切换过去。** - -```bash -git checkout -b test # 基于当前分支创建 test 分支,并切到 test -``` - -- 等价于 `git branch test` + `git checkout test` 两步合一 -- 新分支会继承当前分支的全部提交历史 -- 新分支上做的提交不会影响 master,适合在新分支上开发新功能 / 修 bug,稳定后再合并回 master - -> 📌 新版 Git 也推荐用 `git switch -c test` 来创建并切换分支,语义更清晰。 - ---- - -## 五、补充:常见疑问 - -**Q:绑定 / 修改远端地址前,要先删仓库吗?** -A:不需要。`git remote set-url origin <新地址>` 会直接覆盖原有地址,原仓库历史和本地文件都不受影响。只有当本地 `origin` 别名本身不存在时,才需要用 `git remote add origin ` 新增。 - ---- - -## 六、冲突是怎么产生的(为什么会出现冲突) - -**核心原因:两个分支各自修改了同一个文件的同一行(或相邻几行),git 无法自动判断该保留哪一侧,于是报 CONFLICT 让你人工决定。** - -git 自动合并的原理是"三方合并":找到两个分支的公共祖先,对比两边的改动。只要两边改的不是同一处,git 都能自动合上;一旦撞车,就产生冲突。 - -### fork 场景的产生过程 - -```text -时间线: - - A ──── B ──── C upstream/master(上游仓库,别人在 B、C 里改了 readme.md 第 10 行) - \ - └──── D 你的 fork 分支(你在 D 里也改了 readme.md 第 10 行) - \ - ↓ 执行 git merge upstream/master - CONFLICT! - 同一行被两条时间线分别修改,git 无法自动合并,需要人工选择 -``` - -### 常见的三种冲突场景 - -| 场景 | 说明 | -| -------------------- | ------------------------------------------------------------ | -| **fork 同步上游** | 你的 fork 和上游仓库都改了同一处,同步上游(merge/rebase)时冲突 | -| **多人协作同一分支** | 你和同事改了同一文件的同一行,后 push / pull 的人遇到冲突 | -| **分支合并** | feature 分支和 master 分支各自改了同一行,合并(merge/rebase)时冲突 | - ---- - -## 七、解决冲突的完整流程(fork 场景) - -### 第 1 步:fork repo,同步上游 - -fork 出来的仓库**不会自动跟随上游**,需要手动同步:先绑定上游地址,再拉取上游最新内容。 - -```bash -git remote add upstream <上游仓库地址> # 只需绑定一次 -git remote -v # 确认:origin = 自己的 fork,upstream = 上游 -git fetch upstream master # 把上游最新内容下载到本地(此时还不会冲突) -``` - -> 💡 Gitee / GitHub 网页端也有 "Sync fork / 同步上游" 按钮,命令行做法就是上面这几步。 - -### 第 2 步:切到产生冲突的分支,merge / rebase 同步上游 - -```bash -git checkout <产生冲突的分支> # 切到自己要更新的分支 -git merge upstream/master # 把上游最新修改合并进当前分支 -# 或者:git rebase upstream/master # 变基方式,提交历史更线性 -``` - -如果两边改了同一处,终端会提示 `CONFLICT (content): Merge conflict in <文件>`,**此时 git 停下来让你解决冲突**。 - -### 第 3 步:手动解决冲突(选择旧时间线还是新时间线的修改) - -打开冲突文件,会看到这样的标记: - -```text -<<<<<<< HEAD -这一侧是你当前分支的修改(你自己这条时间线) -======= -这一侧是 upstream/master 的修改(上游那条时间线) ->>>>>>> upstream/master -``` - -**自行决定保留哪条时间线的修改**: - -- 想保留自己的旧改动 → 删掉 `=======` 下侧的内容和三个标记行 -- 想采用上游的新改动 → 删掉 `<<<<<<< HEAD` 与 `=======` 之间的内容和标记行 -- 两边都要 → 手动把两侧内容拼成想要的最终样子,删掉所有标记行 - -### 第 4 步:标记已解决并提交、推送 - -```bash -git add <冲突文件> # 告诉 git 冲突已解决 -git commit -m "解决冲突" # merge 场景也可直接用默认的合并提交信息 -git push origin <分支> # 推回自己的 fork 远端 -``` - -### 辅助命令速记 - -| 命令 | 作用 | -| ------------------- | ------------------------------ | -| `git status` | 查看哪些文件处于冲突状态 | -| `git merge --abort` | 放弃本次合并,回到合并前的状态 | -| `git mergetool` | 调用可视化工具解决冲突 | - -> 📌 `git merge` 会生成一个"合并提交",冲突一次性解决;`git rebase` 会把你的提交逐个"搬"到上游最新之上,冲突可能要在每个提交处分别解决。课堂场景建议先用 `merge`,更直观。 - ---- - -## 八、推送第一次 PR 后:下一次推送与 PR 创建的完整流程 - -> 场景:已提交过第一次 PR,要在 fork 仓库继续提交下一次内容(day2 分支名自行而定) - -```text -① 浏览器打开 fork 仓库,点"同步上游"(Sync fork),让 fork 的 master 跟上原仓库最新代码 - ↓ -② 本地打开 git bash,依次执行: - git switch master # 切回到主分支 - git pull origin master # 从远端拉主分支到本地 - # (前提:①已完成,即 origin 的 master 已是最新上游代码) - ↓ -③ git checkout -b day2 # 基于最新 master 切出新分支 day2 -④ 修改 / 新增 / 删除对应文档或内容 -⑤ git add . # 将当前目录下所有文件修改加入本地 git 管理库 -⑥ git commit -m "第二次提交信息" -⑦ git push origin day2 # 将最新修改同步到远端 day2 分支 - ↓ -⑧ 到 day2 分支下创建 PR -``` - -### 关键点说明 - -- `git switch master` 是切分支的新写法,等价于 `git checkout master` -- 第 ② 步 `git pull origin master` **必须在第 ① 步网页端"同步上游"完成后执行**,这样才能保证本地 master 和上游最新代码一致 -- 第 ③ 步基于"最新 master"切出新分支,目的是**避免旧代码和冲突带进新 PR**,让这次 PR 只包含本次改动 -- 每次新内容都走"**新分支 + 新 PR**"(day2、day3…),而不是继续往旧分支提交 -- 课堂提示(李诗):**审查人员是固定的,创建 PR 时没法选择小组长作为审查人** - ---- - -## 九、速查表 - -| 命令 | 作用 | 方向 | -| --------------------------------- | ------------------------------ | ----------------- | -| `git init` | 初始化仓库 | — | -| `git pull origin master` | 拉取远端 master(fetch+merge) | 远端 → 本地 | -| `git add ` | 加入暂存区 | 工作区 → 暂存区 | -| `git commit -m "msg"` | 提交到本地仓库 | 暂存区 → 本地仓库 | -| `git push origin master` | 推送到远端 | 本地 → 远端 | -| `git remote set-url origin ` | 修改远端地址 | — | -| `git fetch origin master` | 同步远端到本地引用(不合并) | 远端 → 本地引用 | -| `git remote -v` | 查看远端地址 | — | -| `git checkout -b <分支名>` | 新建并切换分支 | — | -| `git switch <分支名>` | 切换分支(新版语法) | — | -| `git remote add upstream ` | 绑定上游仓库地址(fork 后) | — | -| `git merge upstream/master` | 合并上游修改,可能触发冲突 | 上游 → 当前分支 | -| `git rebase upstream/master` | 变基同步上游,历史线性 | 上游 → 当前分支 | -| `git status` | 查看冲突文件 | — | -| `git merge --abort` | 放弃本次合并 | — | - -| | | | -| ---- | ---- | ---- | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260_RT-Thread_IPC\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260_RT-Thread_IPC\345\255\246\344\271\240\347\254\224\350\256\260.md" deleted file mode 100644 index ca755b16b5cb3834ca356f03b118a6f15effcae2..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260_RT-Thread_IPC\345\255\246\344\271\240\347\254\224\350\256\260.md" +++ /dev/null @@ -1,189 +0,0 @@ -# RT-Thread IPC(线程间同步与通信)学习笔记 - -> 本文基于工程 `E:\rtthread\workplace_study\02_rtthread_IPC\applications` 下的 4 个示例整理: -> - `parking_demo.c` —— 计数信号量(semaphore) -> - `tick_demo.c` —— 互斥量(mutex,含“故意不加锁”对比实验) -> - `startup_demo.c` —— 事件集(event) -> - `package_demo.c` —— 消息队列(message queue) -> -> 官方文档(建议配合阅读): -> - IPC 上篇: -> - IPC 下篇: -> -> 线程使用的注意点另见:`RT-Thread_线程注意笔记.md` - ---- - -## 〇、通用套路(四个示例都一样) - -1. 所有 IPC 对象和线程都要**检查创建结果**(判 `RT_NULL`)。 -2. 程序通过 **MSH 命令**启动:`MSH_CMD_EXPORT(cmd, desc);`。 -3. 每个练习至少 **两个线程** + 一个 IPC 对象。 -4. 线程入口函数结束时 **直接 `return`** 即可,系统自动回收(见第五节)。 -5. 动态线程的栈、TCB、队列缓冲区都从**堆**分配,堆边界配错会崩(见第六节)。 - ---- - -## 一、计数信号量 semaphore —— `parking_demo.c` - -### 场景 -停车场 3 个车位,5 辆车抢,最多同时进 3 辆。 - -### 关键 API -```c -rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag); -rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout); // value--,为0则阻塞 -rt_err_t rt_sem_release(rt_sem_t sem); // value++ -``` -- `value = 3` 表示初始剩余车位;`RT_IPC_FLAG_PRIO` 表示按优先级唤醒等待线程。 -- `rt_sem_take(sem, RT_TICK_PER_SECOND*2)`:最多等 2 秒,超时返回 `-RT_ETIMEOUT`。 - -### 代码要点(`parking_demo.c`) -- 创建初始值 3 的信号量:`parking_demo.c:53`。 -- 5 个车辆线程循环 `take` → 进入 → 延时停留 → `release` 离开(`parking_demo.c:82-112`)。 -- 共享变量 `use_pack_space` 用 `rt_enter_critical()/rt_exit_critical()` 临界区保护(`parking_demo.c:94-97,100-103`)。 - -### 思考题:为什么用计数信号量而不是互斥量? -- 互斥量 = **1 个资源、谁拿着谁独占**(value 只能是 0/1,强调“持有者释放”)。 -- 计数信号量 = **N 个同类资源**,不绑定持有者,任何线程 `release` 都 +1。 -- 车位有 3 个、且“进/出”是不同动作(不是同一段临界区反复进出),用计数信号量最自然。 - -> ⚠️ 小瑕疵(可改):`parking_demo()` 里信号量创建失败时只打印没 `return -RT_ERROR`(`parking_demo.c:54-57`);且 5 个线程只保存了最后一个 `pack_space_thread` 句柄,重复运行不易清理。作业里建议补上失败返回 + 句柄数组/重复运行保护。 - ---- - -## 二、互斥量 mutex —— `tick_demo.c` - -### 场景 -两个窗口卖 20 张票,同一变量 `window_ticket_count` 被两个线程改,必须互斥。 - -### 关键 API -```c -rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag); -rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout); -rt_err_t rt_mutex_release(rt_mutex_t mutex); -``` - -### 对比实验(作业要求) -- **先不加锁**:`tick_demo.c:98-128` 整段 mutex 调用被注释掉。两窗口同时 `if(count>0){count--;}` 会竞态:可能卖出相同票号、或 `count` 变负。 -- **再加锁**:把 `rt_mutex_take`/`rt_mutex_release` 解开注释,把“读-改-写”整段包起来,20 张票不重不漏。 - -### 思考题:为什么只给 `ticket_count--` 加锁仍可能不安全? -危险区间是 **“读取余票 → 判断 >0 → 减 1” 这一段**,不是一个 `count--` 指令。若只锁 `count--`: -``` -窗口A读count=1 → 窗口B读count=1 → A减成0 → B减成-1(重复/超卖) -``` -所以必须锁**整段“检查+修改”逻辑**。 - -> ⚠️ 本示例当前是“不加锁”版本,用来演示竞态。提交作业前记得补上 mutex 版本做对比。 - ---- - -## 三、事件集 event —— `startup_demo.c` - -### 场景 -网络/传感器/存储三个模块初始化完成后,主业务线程才能启动。 - -### 关键宏与 API -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) - -rt_event_t rt_event_create(const char *name, rt_uint8_t flag); -rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set); -rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, - rt_uint8_t option, rt_int32_t timeout, rt_uint32_t *recved); -``` -- `option = RT_EVENT_FLAG_AND`:set 里的位**全部到齐**才唤醒。 -- `option |= RT_EVENT_FLAG_CLEAR`:唤醒后自动清除这些位(避免重复触发)。 - -### 代码要点(`startup_demo.c`) -- 三个初始化线程延时不同时间后 `rt_event_send` 对应位:`startup_demo.c:112-149`。 -- 主业务线程用 `AND` 等三事件齐:`startup_demo.c:156-163`。 -- 超时(storage 5s,业务只等 3s)会先打 `wait modules timeout`,再继续等:`startup_demo.c:169-177`。 - -### 思考题:用 3 个信号量 vs 事件集? -- 3 个信号量也能表达“三个都就绪”,但要**分别 take 三个**,且难以表达“任选/任意组合”语义。 -- 事件集一个 32 位变量可表示 **32 个独立事件 + AND/OR 逻辑**,更省资源、语义更清晰。 - -### ⚠️ 该示例曾踩的坑(已修) -- 原来线程里调用 `rt_thread_delete(rt_thread_self())` 删除正在运行的自己 → 释放了正在用的栈 → `rt_smem_free` 断言 + 硬fault。**改正:线程直接 `return`**,系统自动 `rt_thread_exit()` 回收。 - -## 四、消息队列 message queue —— `package_demo.c` - -### 场景 -两个发件线程产生快递消息,一个分拣线程从队列取出并按区域分类。 - -### 关键 API -```c -rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, - rt_size_t max_msgs, rt_uint8_t flag); -rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t size); // 非阻塞,满返回 -RT_EFULL -rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout); -``` - -### 消息结构 -```c -struct package_msg { rt_uint32_t id; rt_uint8_t region; rt_uint16_t weight; }; -``` -> 注意:`region` 用 `enum`(占 4 字节),结构体有对齐,但创建/发送都用 `sizeof` 一致即可。 - -### 代码要点 / 正确做法(`package_demo.c`) -1. 队列容量 5:`package_demo.c:58-61`。 -2. 两个发件线程各发 6 条:**前 5 条真实快递 + 第 6 条 `region = PKG_REGION_END(0)` 结束消息**(`package_demo.c:128-176`)。 -3. **队列满不丢消息、要重试**:`rt_mq_send` 返回非 `RT_EOK` 时 `fail++`、`rt_thread_mdelay(10)` 后用**同一个 i 重发**(`package_demo.c:147-158`)。否则结束消息可能丢失 → 分拣线程永远等不到第二条结束消息 → 卡死。 -4. 分拣线程用 `finish_cnt` 计数结束消息,**收到 2 条才打印统计并 `break`**(`package_demo.c:178-202`)。不能收到第 1 条就退,否则丢掉另一发件线程的快递。 -5. id 用 `2000+i` / `1000+i` 分段,**保证不重复**。 -6. 统计变量在 `package_demo()` 开头**清零**,否则重复运行会累加(`package_demo.c:69`)。 -7. 分拣线程退出前 `rt_mq_delete`/`rt_mutex_delete` 并把手柄清 `RT_NULL`,支持重复运行(文件末尾)。 - -### 统计要求 -- `total`:真实快递总数(不含结束消息)。 -- `east/south/north`:各区域数量;`weight`:总重量;`send_fail`:队列满重试次数。 - -### 常见的几个错误写法(都来自调试过程) -| 错误 | 现象 | 原因 | -|------|------|------| -| 队列满时只记 `fail` 不重试 | 快递丢失 / 结束消息丢失 → 分拣卡死 | `rt_mq_send` 非阻塞,满了直接返回,必须重试才能发满 | -| 收到第 1 条结束消息就 `break` | `total` 只有 ~5,丢另一线程的快递 | 要 `finish_cnt==2` 才退出 | -| id 用 `rand()%1000` 两线程同范围 | 编号重复 | 用固定段 `1000+i` / `2000+i` 区分 | -| 统计变量不清零 | 第二次运行数字累加 | `package_demo()` 开头全置 0 | - ---- - -## 五、线程生命周期与回收(来自 `RT-Thread_线程注意笔记.md`) - -1. **禁止 `rt_thread_delete(rt_thread_self())`**:线程正在运行、还在用自己这块栈,删自己会立刻释放正在使用的栈 → `rt_smem_free` 断言 + 硬fault。正确做法是线程入口直接 `return`,系统自动 `rt_thread_exit()` 回收。 -2. **线程退出后资源在空闲线程(idle)释放**:动态线程 `return` 后不会立刻 `rt_free` 自己的栈/TCB,而是挂入僵尸队列,由空闲线程(tidle0)真正释放。所以**空闲线程栈要留够**,否则线程正常退出时释放资源会把 tidle 栈爆掉(现象:`thread:tidle0 stack overflow`)。 -3. 若确实需要动态删除线程,应由**其他线程**调用 `rt_thread_delete(handle)`,而不是删自己。 - ---- - -## 六、调试血泪:硬fault + `0xdeadbeef` 与堆边界(来自 package_demo 排错) - -`package_demo` 曾反复硬fault,fault 输出里 `r04~r11` 全是 `0xdeadbeef`,而且 fault 线程在 `main` / `package1_thread` 之间变(布局一变就换受害者)。结论: - -- `0xdeadbeef` 是 RT-Thread 给**新线程栈填充的哨兵值**。`r4~r11` 全是它,说明**某线程的栈被另一块“新线程栈”覆盖重写**了。 -- 根因:**`board.c` 里堆(heap)的结束地址划得太靠 RAM 顶部,和 `main` 线程栈(靠 RAM 顶部向下生长)重叠**。于是 `rt_malloc` 分配出来的线程栈/队列缓冲区互相踩、也踩 `main` 栈,把对方栈里的 deadbeef 写进活线程的寄存器现场 → 一恢复上下文就硬fault。 -- **改法(board.c)**:把 `RT_HW_HEAP_END` 往下收,给 `main` 线程栈留出余量(例如 RAM 顶 `0x20010000`、main 栈 0x1000,则 heap end 设为 `0x2000F000`)。线程栈大小调大(如 4096)反而更挤、更易撞,正常练习用 1024 即可。 - -### 硬fault 排查清单 -1. 看 `hard fault on thread: xxx` 和 `r04~r11`:全是 `0xdeadbeef` → 栈/堆被踩(内存重叠或越界)。 -2. 看各线程 `stack size` / `max used`:接近 100% 是栈溢出;都很低却崩 → 多半是堆重叠/越界,不是本线程栈小。 -3. 先确认 `tidle0` 栈够不够(第五节);再检查 `board.c` 的 `RT_HW_HEAP_BEGIN/END` 是否与 `main` 栈重叠。 -4. 必要时打开 `RT_DEBUG` / 栈溢出检查(`RT_USING_OVERFLOW_CHECK`)让系统在溢出时主动打印。 - ---- - -## 七、四件套速查 - -| IPC | 创建 | 获取/发送 | 释放/接收 | 典型用途 | -|-----|------|-----------|-----------|----------| -| 计数信号量 `rt_sem_t` | `rt_sem_create` | `rt_sem_take` | `rt_sem_release` | N 个同类资源(车位/连接数) | -| 互斥量 `rt_mutex_t` | `rt_mutex_create` | `rt_mutex_take` | `rt_mutex_release` | 保护“读-改-写”临界区 | -| 事件集 `rt_event_t` | `rt_event_create` | `rt_event_send` | `rt_event_recv(AND/OR+CLEAR)` | 多条件齐/任意组合就绪 | -| 消息队列 `rt_mq_t` | `rt_mq_create` | `rt_mq_send`(非阻塞) | `rt_mq_recv` | 线程间传递结构体数据 | - -> 所有对象都要判 `RT_NULL`;所有命令用 `MSH_CMD_EXPORT` 导出;所有线程结束时 `return` 而非删自己。 - diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.assets/\347\272\277\347\250\213\347\212\266\346\200\201-1787047977518-2-1787047978657-4.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.assets/\347\272\277\347\250\213\347\212\266\346\200\201-1787047977518-2-1787047978657-4.png" deleted file mode 100644 index fe2cc8141fd5c220db0fca600c75bd9b8ea66a92..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.assets/\347\272\277\347\250\213\347\212\266\346\200\201-1787047977518-2-1787047978657-4.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.assets/\347\272\277\347\250\213\347\212\266\346\200\201-1787047977518-2.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.assets/\347\272\277\347\250\213\347\212\266\346\200\201-1787047977518-2.png" deleted file mode 100644 index fe2cc8141fd5c220db0fca600c75bd9b8ea66a92..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.assets/\347\272\277\347\250\213\347\212\266\346\200\201-1787047977518-2.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.assets/\347\272\277\347\250\213\347\212\266\346\200\201.png" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.assets/\347\272\277\347\250\213\347\212\266\346\200\201.png" deleted file mode 100644 index fe2cc8141fd5c220db0fca600c75bd9b8ea66a92..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.assets/\347\272\277\347\250\213\347\212\266\346\200\201.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.md" deleted file mode 100644 index de62dcd8a9058a8af4b714aff3c30ea94f48ede3..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_RT-Thread\347\272\277\347\250\213\345\210\233\345\273\272\344\270\216\350\260\203\345\272\246\347\254\224\350\256\260.md" +++ /dev/null @@ -1,152 +0,0 @@ -# RT-Thread 线程创建与调度笔记 - -对应工程:`E:\rtthread\workplace_study\01_rtthread_task`,作业文档写在工程根目录 `README.md`。 - ---- - -## 一、线程创建函数及参数 - -### 1. `rt_thread_init()` —— 静态创建 - -```c -rt_err_t rt_thread_init(struct rt_thread *thread, - const char *name, - void (*entry)(void *parameter), - void *parameter, - void *stack_start, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -| 顺序 | 参数 | 类型 | 说明 | -|---|---|---|---| -| 1 | `thread` | `struct rt_thread *` | 线程控制块 TCB 的地址,**用户提供**(全局/static 变量)。静态创建与动态创建最大的区别:控制块由用户定义,内核不分配 | -| 2 | `name` | `const char *` | 线程名。用于 shell(`list_thread`)、调度钩子、调试识别,可重名 | -| 3 | `entry` | `void (*)(void *)` | 线程入口函数。内核启动线程时调用它,**函数返回即线程结束**(进关闭态) | -| 4 | `parameter` | `void *` | 传给入口函数的参数,即 `entry(parameter)`。不需要传 `NULL`,常用于传结构体指针 | -| 5 | `stack_start` | `void *` | 栈起始地址,**用户提供**(如 `char thread1_stack[1024]`)。需 4 字节对齐(`__attribute__((aligned(4)))`),Cortex-M 的 SP 必须 4 字节对齐 | -| 6 | `stack_size` | `rt_uint32_t` | 栈大小,单位**字节**。要容纳调用链局部变量 + 中断嵌套上下文,不够会栈溢出 | -| 7 | `priority` | `rt_uint8_t` | 优先级,**数字越小越高**,0 最高。范围 `0 ~ RT_THREAD_PRIORITY_MAX-1`(常用 32) | -| 8 | `tick` | `rt_uint32_t` | 时间片,单位 tick。**仅同优先级**线程间轮转生效,不同优先级靠抢占不靠时间片 | - -**返回值**:`rt_err_t`,`RT_EOK` 成功,其他为错误码。 - -**配套销毁**:`rt_thread_detach(&thread1)`——只从内核摘除,**内存还是用户的**。 - -### 2. `rt_thread_create()` —— 动态创建 - -```c -rt_thread_t rt_thread_create(const char *name, - void (*entry)(void *parameter), - void *parameter, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -| 顺序 | 参数 | 类型 | 说明 | -|---|---|---|---| -| 1 | `name` | `const char *` | 线程名,同上 | -| 2 | `entry` | `void (*)(void *)` | 入口函数,同上 | -| 3 | `parameter` | `void *` | 入口参数,同上 | -| 4 | `stack_size` | `rt_uint32_t` | 栈大小,单位字节。**只给大小不给地址**——栈由内核 `rt_malloc` 分配 | -| 5 | `priority` | `rt_uint8_t` | 优先级,规则同 init | -| 6 | `tick` | `rt_uint32_t` | 时间片,规则同 init | - -**返回值**:`rt_thread_t`(`struct rt_thread *`),成功返回线程句柄,失败返回 **`RT_NULL`**(堆不够)。 - -**配套销毁**:`rt_thread_delete(thread2_handle)`——内核在 **idle 线程**里回收(先跑 cleanup 释放栈和 TCB)。 - -**关键差异**:`create` 比 `init` **少两个参数**——没有 `thread`(控制块)和 `stack_start`(栈地址),这两个由内核 `rt_malloc` 分配。代价:必须开 `RT_USING_HEAP`,且存在堆碎片、运行时分配失败(返回 RT_NULL)的风险。 - -### 3. 两函数对比 - -| | `rt_thread_init`(静态) | `rt_thread_create`(动态) | -|---|---|---| -| 参数个数 | 8 个 | 6 个 | -| 控制块 TCB | 用户传入地址(参数 1) | 内核 malloc | -| 栈空间 | 用户传地址 + 大小(参数 5、6) | 内核 malloc,只传大小 | -| 返回值 | `rt_err_t` 状态码 | `rt_thread_t` 句柄(失败 `RT_NULL`) | -| 销毁 | `rt_thread_detach()` | `rt_thread_delete()` | -| 启动 | `rt_thread_startup(&thread1)` | `rt_thread_startup(handle)` | -| 依赖 | 不需要堆 | 需要 `RT_USING_HEAP` | -| 适用 | 资源紧张、栈大小固定、高可靠场景 | 灵活、用完即释放 | - -**参数顺序记忆**: -- `init`:`线程对象, 名字, 入口, 参数, 栈地址, 栈大小, 优先级, 时间片` -- `create`:`名字, 入口, 参数, 栈大小, 优先级, 时间片`(= init 去掉开头"线程对象"和"栈地址") - -### 4. 配套 API 速查 - -| 函数 | 原型(要点) | 作用 | -|---|---|---| -| 启动 | `rt_err_t rt_thread_startup(rt_thread_t thread)` | 初始态 → 就绪态,挂入就绪队列 | -| 挂起 | `rt_err_t rt_thread_suspend(rt_thread_t thread)` | 运行/就绪 → 挂起态(注意:挂起自己后要手动 `rt_schedule()`) | -| 恢复 | `rt_err_t rt_thread_resume(rt_thread_t thread)` | 挂起 → 就绪态 | -| 延时 | `rt_err_t rt_thread_delay(rt_tick_t tick)` / `rt_thread_mdelay(rt_int32_t ms)` / `rt_thread_sleep(rt_tick_t tick)` | 本线程挂起 N 个 tick/ms(内嵌 thread_timer 实现) | -| 让出 | `rt_err_t rt_thread_yield(void)` | 运行 → 就绪,同优先级轮转给下一个 | -| 删除 | `rt_err_t rt_thread_delete(rt_thread_t thread)` | 动态线程:挂 defunct 队列,idle 回收 | -| 脱离 | `rt_err_t rt_thread_detach(rt_thread_t thread)` | 静态线程:脱离内核,内存用户自己管 | -| 当前线程 | `rt_thread_t rt_thread_self(void)` | 拿自己的控制块指针 | -| 查找 | `rt_thread_t rt_thread_find(char *name)` | 按名字查线程 | - -### 5. 最小使用模板(对照工程 rt_task_sample.c) - -```c -/* 静态线程:控制块和栈都在 BSS,编译期定死 */ -struct rt_thread thread1; -char thread1_stack[1024] __attribute__((aligned(4))); - -static void thread_entry1(void *parameter) -{ - for (int i = 0; i < 10; i++) - { - rt_kprintf("thread1 count=[%d]\r\n", i); - rt_thread_mdelay(500); /* 延时:运行态 → 挂起态 */ - } - /* return → 关闭态,内核自动回收 */ -} - -int create_thread_sample(void) -{ - rt_thread_init(&thread1, "thread1", thread_entry1, NULL, - thread1_stack, sizeof(thread1_stack), 20, 10); - rt_thread_startup(&thread1); - - rt_thread_t t2 = rt_thread_create("thread2", thread_entry2, NULL, 1024, 20, 10); - if (t2 != RT_NULL) - rt_thread_startup(t2); - - return 0; -} -``` - ---- - -## 二、五态与典型 API - -![线程状态](E:\笔记\rtthread\RT-Thread线程创建与调度笔记.assets\线程状态-1787047977518-2-1787047978657-4.png) - -| 状态 | 宏 | 值 | 进入该态 / 离开该态的典型 API | -|---|---|---|---| -| 初始态 | `RT_THREAD_INIT` | 0x00 | `rt_thread_init/create` 之后;`rt_thread_startup` 离开 | -| 就绪态 | `RT_THREAD_READY` | 0x01 | `rt_thread_startup` / 延时到期 / `rt_thread_resume` / IPC 释放;被调度器选中离开 | -| 运行态 | `RT_THREAD_RUNNING` | 0x03 | 调度器选中;`rt_thread_delay`/`suspend`/时间片到/被抢占离开 | -| 挂起态 | `RT_THREAD_SUSPEND` | 0x02 | `rt_thread_delay/mdelay/sleep` / `rt_thread_suspend` / 等 IPC 无资源;到期/IPC 释放/`rt_thread_resume` 离开 | -| 关闭态 | `RT_THREAD_CLOSE` | 0x04 | 入口函数 return、`rt_thread_delete/detach`;idle 线程回收动态线程 | - -`stat` 是位掩码:低 3 位(0x07)为状态,`RT_THREAD_STAT_YIELD`(0x08) 表示时间片用完,与状态位共存。 - ---- - -## 三、作业各条与笔记对照 - -| 作业条目 | 写在哪 | -|---|---| -| 1.1 启动路径 | `README.md` §1.1(调用链图 + 关键点);启动流程细节见 [[rtthread启动流程]] 笔记 | -| 1.2 自动初始化原理 | `README.md` §1.2(宏展开/段表/扫描函数/BOARD vs APP 对比) | -| 2.1 对象容器与控制块 | `README.md` §2.1(对象容器 + rt_thread 成员表) | -| 2.2 静态 vs 动态 | `README.md` §2.2 + 本笔记"二"(参数逐条对比) | -| 3.1 五态模型 | `README.md` §3.1(状态图 `线程状态.png` + 转换表) | -| 3.2 调度机制 | `README.md` §3.2(抢占 + 时间片轮转,源码 clock.c / scheduler.c) | diff --git "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213.md" "b/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213.md" deleted file mode 100644 index ae4a54e6d9d2f35c61a223532db0f8f9a7e9b883..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\347\216\213\345\223\262/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213.md" +++ /dev/null @@ -1,557 +0,0 @@ -## RT-Thread - -## RT-Thread 启动流程 - -课堂笔记。对应源码:`src/components.c`(`rtthread_startup`)、`src/timer.c`(`rt_system_timer_thread_init` / `_timer_thread_entry`)、`src/idle.c`。 - ---- - -### 一、启动总览 - -![启动流程](rtthread启动流程.assets\rtt_startup-1787022582540-3.png) - -上电复位之后,不是直接进你写的 `main()`,而是: - -```c -Reset_Handler(汇编) - → SystemInit() // 时钟、FPU 等芯片相关 - → $Sub$$main / entry // 编译器把原 main 劫持到 RT-Thread 入口 - → rtthread_startup() - → rt_hw_interrupt_disable() // 关中断,一直关到调度器 start - → rt_hw_board_init() // 板级:时钟、堆、串口、SysTick(会启动 tick 硬件) - → rt_show_version() - → rt_system_timer_init() // 硬定时器链表 - → rt_system_scheduler_init() // 优先级队列、位图 - → rt_application_init() // 创建 main 线程 - → rt_system_timer_thread_init() // 软件定时器线程(开了才有) - → rt_thread_idle_init() // 空闲线程 - → rt_system_scheduler_start() // 切第一个线程,这里才开中断;函数不再返回 -``` - -调度器一旦启动,CPU 交给优先级最高的就绪线程。之后看到的 `main`、`timer`、`tidle` 都是**普通线程**,只是内核自己创建的。 - -#### 为什么一进 `rtthread_startup()` 先关中断 - -不是「调用 `rtthread_startup` 之前」关的,而是这个函数**第一句** `rt_hw_interrupt_disable()`。Cortex-M 复位后 PRIMASK=0,CPU 层面中断其实是开着的,只是 SysTick、UART 这些源大多还没使能,所以从 `Reset_Handler` 走到这里,表面上很安静。 - -真正危险的是下一句 `rt_hw_board_init()`:板级初始化会配时钟、开堆、开串口,并且**把 SysTick 跑起来**(LOAD / TICKINT / ENABLE)。如果这时全局中断还开着,SysTick 马上就能打进 ISR。而此刻: - -- 定时器链表还没 `rt_system_timer_init()` -- 就绪队列 / 优先级位图还没 `rt_system_scheduler_init()` -- `main` / `timer` / `idle` 线程都还不存在 -- `rt_current_thread` 还是空的 - -SysTick ISR 却已经会做 `rt_tick++`、`rt_timer_check()` 扫链表、必要时 `rt_schedule()`。链表是空的、当前线程是空的,结果就是野指针 / HardFault,看起来像随机复位。 - -所以关中断是把两件事错开:**硬件可以开始报时,但 ISR 暂时进不来**,直到内核能接住这记报时。 - -``` -关中断 - → board_init:SysTick 硬件开始数,ISR 被 PRIMASK 挡住 - → 定时器链表、调度器、三个系统线程全部建好 - → rt_thread_startup() 往就绪队列挂线程(中间绝不能被 tick 切走半截) - → rt_system_scheduler_start() - → 选出优先级最高的就绪线程(一般是 main) - → rt_hw_context_switch_to() 切过去 - → 这里才 cpsie i / 开 PRIMASK -此后 tick 打进来就是合法的:链表在、线程在、调度器在。 -``` - -几条容易混的: - -1. **不是关到 board_init 结束就开。** 一直关到调度器 start。中间创建线程、插入就绪队列,都属于「数据结构半成品」,不能被中断走进去。 -2. **SysTick 使能 ≠ 中断已经在跑。** 计数器硬件可以在关中断期间走;只是 TICKINT 被 PRIMASK 挡住,`rt_tick` 这段时间不加。少掉的那几毫秒启动阶段可以忽略。 -3. **`scheduler_start` 不会返回**,所以开头的 disable 不必在 `rtthread_startup` 里配对 enable。开中断是第一次线程切换的副作用。 -4. **`rt_hw_interrupt_disable()` 还会把内核的中断锁层级放到已知状态。** 后面成对的 disable/enable 不会因为复位后 PRIMASK 不确定,中途把中断提前打开。 - ---- - -### 二、汇编窗口进内核 - -汇编窗口进入: - -![](E:\笔记\rtthread\rtthread启动流程.assets\image-20260818111017735-1787022619435-5.png) - -启动函数入口: - -![](E:\笔记\rtthread\rtthread启动流程.assets\image-20260818111117274-1787022679357-7.png) - -Keil 里从复位单步,典型路径: - -1. `Reset_Handler`:设 MSP、调 `SystemInit`、跳到 C 入口。 -2. ARMCC 用 `$Sub$$main` 包一层:先跑 RT-Thread 启动,再把用户 `main` 塞进 main 线程。 -3. GCC 工程常见入口是 `entry()`,内部同样调 `rtthread_startup()`。 - -启动流程(`rtthread_startup` 调用链): - -![image-20260818111155944](rtthread启动流程.assets\image-20260818111155944-1787022717605-9.png) - -记住两件和“谁在跑”有关的事: - -- **SysTick 在 `rt_hw_board_init()` 里配好**。它只负责报时:`rt_tick++`,并检查定时器链表。它不是一个线程。 -- **用户 `main()` 此时还没跑。** `rt_application_init()` 只是把 main 线程 `init` + `startup` 进就绪队列;真正执行要等到 `rt_system_scheduler_start()`。 - ---- - -### 三、调度起来之后的三个系统线程 - -| 线程名 | 怎么来的 | 干什么 | -|---|---|---| -| `main` | `rt_application_init()` | 跑用户 `main()`,应用入口 | -| `timer` | `rt_system_timer_thread_init()` | **软件定时器回调**(开 `RT_USING_TIMER_SOFT` 才有) | -| `tidle` / `tidle0` | `rt_thread_idle_init()` | 空闲钩子、对象清理、可选低功耗;优先级最低 | - -#### 1. main 函数线程 - -![image-20260818111235130](rtthread启动流程.assets\image-20260818111235130-1787022756355-11-1787022808165-13.png) - -- 静态创建,栈大小 `RT_MAIN_THREAD_STACK_SIZE`,优先级 `RT_MAIN_THREAD_PRIORITY`(一般偏低,把 CPU 让给业务线程)。 - -![image-20260818113842487](E:\笔记\rtthread\rtthread启动流程.assets\image-20260818113842487-1787024325155-4.png) - -- 入口里调用户 `main()`。`main` 里 `return` 之后这个线程就结束,系统继续跑其它线程 + idle。 -- 所以:你在 `main()` 里看到的第一行代码,已经是**线程上下文**,调度器、SysTick、idle 都已经在了。 - -#### 2. 定时器线程(软件定时器) - -![image-20260818111333062](rtthread启动流程.assets\image-20260818111333062.png) - -`timer` 线程处理的是**软件定时器**,不是第二套时钟。时间基准仍然是 **SysTick 累加的 `rt_tick`**。软/硬的差别不在“用不用 SysTick”,而在**到期后回调跑在哪**。 - -#### 3. 空闲线程 - -![image-20260818111401949](rtthread启动流程.assets\image-20260818111401949.png) - -- 优先级 = `RT_THREAD_PRIORITY_MAX - 1`(数字最大,最低)。 -- 就绪队列空了才跑:执行 `rt_thread_idle_sethook()` 挂的钩子、回收 `RT_THREAD_CLOSE` 的动态线程、有的 BSP 在这里进 WFI。 -- 不要在 idle 钩子里做长时间阻塞;它一堵,系统就再也进不了“没事可做”的状态。 - ---- - -### 四、同一只挂钟,两种响法 - -SysTick ISR 每次只做“报时”: - -1. `rt_tick++`(系统时间往前走一格) -2. 扫定时器链表: - - **硬定时器**(`RT_TIMER_FLAG_HARD`)到期 → **在 ISR 里直接调回调** - - **软定时器**(`RT_TIMER_FLAG_SOFT`)到期 → **唤醒名为 `"timer"` 的线程**,回调放到线程里再跑 - -软定时器没有自己的晶振,也没有第二套 tick。`rt_timer_start()` 里写的 timeout,单位就是系统 tick;`_timer_thread_entry` 里的 `rt_tick_get()`、`rt_thread_delay(next_timeout)`,数的也是同一根尺子。 - -| | 硬定时器 `RT_TIMER_FLAG_HARD` | 软定时器 `RT_TIMER_FLAG_SOFT` | -|---|---|---| -| 时间从哪来 | 都是 `rt_tick`(通常由 SysTick 加) | 同上 | -| 谁发现到期 | SysTick ISR | ISR 只负责“到点了”,真正干活的是 `timer` 线程 | -| 回调跑在哪 | 中断上下文 | 线程上下文(`_timer_thread_entry` → `rt_soft_timer_check()`) | -| 回调能干什么 | 必须短、不能阻塞、不能随便调会挂起的 IPC | 可以稍重:发信号量、申请内存、调阻塞 API(仍不建议特别慢) | - -为什么拆开:SysTick 常见 1ms 一次。ISR 里跑重活会拖长关调度/关中断的时间,别的线程和中断都跟着抖。软定时器把“闹钟到了”和“响了之后干什么”拆开——**报时仍在中断,干活在 `timer` 线程**。 - -没用软定时器、关了 `RT_USING_TIMER_SOFT` 时,这个线程不会建;硬定时器照样走 SysTick,回调全部挤在中断里。 - ---- - -### 五、`rt_system_timer_thread_init()` 源码 - -```c -void rt_system_timer_thread_init(void) -{ -#ifdef RT_USING_TIMER_SOFT - int i; - - for (i = 0; - i < sizeof(_soft_timer_list) / sizeof(_soft_timer_list[0]); - i++) - { - rt_list_init(_soft_timer_list + i); - } - - /* start software timer thread */ - rt_thread_init(&_timer_thread, - "timer", - _timer_thread_entry, - RT_NULL, - &_timer_thread_stack[0], - sizeof(_timer_thread_stack), - RT_TIMER_THREAD_PRIO, - 10); - - /* startup */ - rt_thread_startup(&_timer_thread); -#endif /* RT_USING_TIMER_SOFT */ -} -``` - -只在开了 `RT_USING_TIMER_SOFT` 时执行,三步: - -1. 初始化 `_soft_timer_list`(按超时时间排序的软定时器链表,跳表/多级列表,加快“下一个到期”查询)。 -2. **静态**创建名为 `"timer"` 的线程:控制块 `_timer_thread`、栈 `_timer_thread_stack` 都在 BSS 里,不用堆。入口 `_timer_thread_entry`,优先级 `RT_TIMER_THREAD_PRIO`(一般比普通应用线程高,保证回调及时),时间片 10 tick。 -3. `rt_thread_startup` 把它挂到就绪队列;调度器启动后就会跑。 - -这就是静态线程那一套:`rt_thread_init` + `rt_thread_startup`,和自己写 `thread1` 的方式相同,只是对象是内核全局变量。 - ---- - -### 六、`_timer_thread_entry()`:闹钟服务员 - -```c -static void _timer_thread_entry(void *parameter) -{ - rt_tick_t next_timeout; - - while (1) - { - /* get the next timeout tick */ - if (_timer_list_next_timeout(_soft_timer_list, &next_timeout) != RT_EOK) - { - /* no software timer exist, suspend self. */ - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - } - else - { - rt_tick_t current_tick; - - current_tick = rt_tick_get(); - - if ((next_timeout - current_tick) < RT_TICK_MAX / 2) - { - next_timeout = next_timeout - current_tick; - rt_thread_delay(next_timeout); - } - } - - /* check software timer */ - rt_soft_timer_check(); - } -} -``` - -永远循环,逻辑可以压成三句话: - -``` -查下一个软定时器何时到期 - 没有定时器 → 自己挂起,让出 CPU - 有、且还没到点 → rt_thread_delay(剩余 tick) - 到点 / delay 被提前唤醒 → rt_soft_timer_check() 扫链表、调回调 -``` - -#### 细节 - -1. **`_timer_list_next_timeout()` 失败** - 当前一个软定时器都没有。线程 `rt_thread_suspend` 自己,再 `rt_schedule()` 切走。之后有人 `rt_timer_start()` 一个软定时器,内核会把这个线程唤醒,循环从头再查。 - -2. **`(next_timeout - current_tick) < RT_TICK_MAX / 2`** - `rt_tick_t` 无符号,直接减会在溢出时绕回。这个不等式是内核判断“还没到期”的惯用写法(距离小于半圈就认为是未来)。还没到就 `rt_thread_delay` 睡到那个 tick。 - -3. **`rt_thread_delay(next_timeout)` 不是另起一套定时** - 只是让 `timer` 线程睡 N 个 tick;这 N 个 tick 还是 SysTick 在减。中间如果有人新 `rt_timer_start` 了一个**更早到期**的软定时器,内核会提前把这个线程叫醒,再重新算下一个超时。 - -4. **`rt_soft_timer_check()`** - 在**线程上下文**取出所有已到期的软定时器,执行回调。这里可以调 IPC、申请内存——这是和硬定时器最大的区别。周期性软定时器会在这里被重新挂回链表。 - ---- - -### 七、和启动顺序对上号(容易混的点) - -1. `rt_system_timer_init()` ≠ `rt_system_timer_thread_init()` - - 前者:硬/软定时器**链表**都建好,给 SysTick ISR 用。 - - 后者:只建 `"timer"` **线程**,专门跑软定时器回调。 - -2. SysTick 中断比 `timer` 线程更早就在工作。 - 板级初始化里已经 `rt_hw_systick` 了;只是调度器没 start 之前,即便软定时器到期也还没有线程可唤醒。所以内核把 timer 线程的创建放在 `rt_application_init()` 之后、`scheduler_start` 之前。 - -3. 调试时 `list_thread` 看到 `timer` 长期 `suspend` 是正常的:没有软定时器时它就睡。一 `rt_timer_start(SOFT)` 它会被叫醒。 - -4. 自己写超时优先用软定时器;只有“必须在 1ms 中断里立刻拨脚/置标志”才用硬定时器,回调里不要 `printf`、不要调会阻塞的 API。 - ---- - -### 八、`INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` - -这两个宏不是“函数调用”,而是**把初始化函数指针塞进指定的链接段**,启动时内核按段顺序扫一遍、挨个调用。差别只在**哪一段、什么时候扫**。 - -| | `INIT_BOARD_EXPORT(fn)` | `INIT_APP_EXPORT(fn)` | -|---|---|---| -| 展开成 | `INIT_EXPORT(fn, "1")` | `INIT_EXPORT(fn, "6")` | -| 函数指针放哪 | `.rti_fn.1` | `.rti_fn.6` | -| 谁来扫 | `rt_hw_board_init()` → `rt_components_board_init()` | main 线程入口 → `rt_components_init()` | -| 调度器 | **还没 start**,关着中断 | **已经在跑**,你在 `main` 线程里 | -| 能干什么 | 挂设备、配引脚、注册串口 | 建线程、用 IPC、`rt_thread_delay` | -| 不能干什么 | 创建线程、延时、等信号量(会踩空) | 依赖“板级还没就绪”的硬件(应放到 BOARD) | - -`fn` 原型必须是:`int fn(void);`,返回 `0` 表示成功。自动 init 要开 `RT_USING_COMPONENTS_INIT`(Studio 默认开)。Nano 内核常常没有这套,就得自己在 `main` 里调。 - -#### 1. 宏怎么展开 - -`rtdef.h` 里(开了 `RT_USING_COMPONENTS_INIT`): - -```c -typedef int (*init_fn_t)(void); - -#define INIT_EXPORT(fn, level) \ - rt_used const init_fn_t __rt_init_##fn rt_section(".rti_fn." level) = fn - -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") -#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2") -#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3") -#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4") -#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") -``` - -`rt_section` / `rt_used` 就是编译器属性: - -```c -#define rt_section(x) __attribute__((section(x))) -#define rt_used __attribute__((used)) /* 防止没被引用被链接器丢掉 */ -``` - -所以你写: - -```c -int uart_hw_init(void) { ...; return 0; } -INIT_BOARD_EXPORT(uart_hw_init); -``` - -预处理器变成(大意): - -```c -__attribute__((used, section(".rti_fn.1"))) -const init_fn_t __rt_init_uart_hw_init = uart_hw_init; -``` - -编译器在 `.rti_fn.1` 里放了一个**函数指针常量**,不是调用 `uart_hw_init`。谁都不用写“把这个函数登记到启动表”——写宏就登记了。开了 `RT_DEBUG_INIT` 时,段里放的是 `{ "函数名", 函数指针 }`,启动日志能打印谁在 init。原理一样。 - -#### 2. 链接器把各文件的段拼成一张有序表 - -各 `.c` 里的 `.rti_fn.1`、`.rti_fn.6` 本来是散的。链接脚本(GCC 常见写法): - -```ld -.rti_fn : { - PROVIDE(__rt_init_start = .); - KEEP(*(SORT(.rti_fn*))) - PROVIDE(__rt_init_end = .); -} > CODE -``` - -`SORT(.rti_fn*)` 按**段名字符串**排序,内存里变成: - -``` -.rti_fn.0 rti_start ← 内核自己放的哨兵 -.rti_fn.0.end rti_board_start -.rti_fn.1 ← 所有 INIT_BOARD_EXPORT -.rti_fn.1.end rti_board_end -.rti_fn.2 INIT_PREV_EXPORT -.rti_fn.3 INIT_DEVICE_EXPORT -.rti_fn.4 INIT_COMPONENT_EXPORT -.rti_fn.5 INIT_ENV_EXPORT -.rti_fn.6 ← 所有 INIT_APP_EXPORT -.rti_fn.6.end rti_end -``` - -这就是一张**按 level 排好的函数指针数组**。同 level 内顺序一般按目标文件/符号名,**不要依赖同级谁先谁后**。 - -内核在 `components.c` 里自己塞了几个空函数当边界: - -```c -static int rti_start(void) { return 0; } -INIT_EXPORT(rti_start, "0"); - -static int rti_board_start(void) { return 0; } -INIT_EXPORT(rti_board_start, "0.end"); - -static int rti_board_end(void) { return 0; } -INIT_EXPORT(rti_board_end, "1.end"); - -static int rti_end(void) { return 0; } -INIT_EXPORT(rti_end, "6.end"); -``` - -#### 3. 启动时扫两段,不是扫一整张表 - -```c -/* 只扫 BOARD:.rti_fn.0.end 到 .rti_fn.1.end(不含 1.end 自己) */ -void rt_components_board_init(void) -{ - const init_fn_t *fn; - for (fn = &__rt_init_rti_board_start + 1; - fn < &__rt_init_rti_board_end; - fn++) - { - (*fn)(); /* 调你的 INIT_BOARD_EXPORT 函数 */ - } -} - -/* 扫 PREV ~ APP:.rti_fn.1.end 之后到 .rti_fn.6.end */ -void rt_components_init(void) -{ - const init_fn_t *fn; - for (fn = &__rt_init_rti_board_end + 1; - fn < &__rt_init_rti_end; - fn++) - { - (*fn)(); /* PREV / DEVICE / COMPONENT / ENV / APP */ - } -} -``` - -(具体是 `+ 1` 还是开区间,以你这版 `components.c` 为准,意思是:**两个哨兵之间的指针逐个解引用**。) - -挂到启动链上: - -``` -rtthread_startup() - 关中断 - rt_hw_board_init() - 时钟 / 堆 / 串口控制台 / SysTick - rt_components_board_init() ← INIT_BOARD_EXPORT 在这里跑 - 定时器链表、调度器、main/timer/idle 线程 - rt_system_scheduler_start() ← 开中断,切到 main 线程 - main_thread_entry() - rt_components_init() ← INIT_APP_EXPORT 在这里跑 - main() ← 你写的 main -``` - -所以: - -- BOARD:还在 `rtthread_startup()` 里,**中断关着、没有当前线程**。 -- APP:已经是 `main` 线程,**可以当普通线程代码写**。 - -和前面“先关中断再 `board_init`”是同一件事:`INIT_BOARD_EXPORT` 跑的时候 `rt_current_thread` 还是空的,一 `rt_thread_delay` / `rt_sem_take` 就会 `rt_schedule()`,当前线程空 → HardFault。`INIT_APP_EXPORT` 跑的时候调度器、tick、堆都在了。 - -硬件必须先于驱动、驱动必须先于应用,靠的就是 `"1" < "6"` 的段名排序,不是谁 `#include` 谁。 - -#### 4. 例子 - -**例 1:板级 —— 注册串口设备(BOARD)** - -调度器还没起来,只能碰寄存器、往设备框架里挂对象: - -```c -#include -#include - -static struct rt_serial_device s_uart1; - -int rt_hw_uart_init(void) -{ - uart1_gpio_clock_init(); - s_uart1.ops = &stm32_uart_ops; - s_uart1.config.baud_rate = 115200; - - rt_hw_serial_register(&s_uart1, "uart1", - RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, - RT_NULL); - return 0; -} -INIT_BOARD_EXPORT(rt_hw_uart_init); -``` - -控制台 `rt_console_set_device("uart1")` 也常在 BOARD 或 `rt_hw_board_init()` 里,这样后面 `rt_kprintf` 才有设备。 - -**不要**在 BOARD 里: - -```c -rt_thread_mdelay(10); /* 没有线程,挂 */ -rt_thread_create("x", ...); /* 调度器没 start,即使 create 了也不会跑 */ -``` - -**例 2:应用 —— 自动拉起业务线程(APP)** - -```c -#include - -static void led_entry(void *param) -{ - rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT); - while (1) - { - rt_pin_write(LED0_PIN, PIN_HIGH); - rt_thread_mdelay(200); - rt_pin_write(LED0_PIN, PIN_LOW); - rt_thread_mdelay(200); - } -} - -int led_app_init(void) -{ - rt_thread_t tid; - - tid = rt_thread_create("led", led_entry, RT_NULL, 512, 20, 10); - if (tid == RT_NULL) - return -RT_ERROR; - - rt_thread_startup(tid); - return 0; -} -INIT_APP_EXPORT(led_app_init); -``` - -效果:你在 `main()` 里什么都不写,调度起来后 `rt_components_init()` 会调到 `led_app_init()`,LED 线程自己起来。这就是组件“声明一下就自动启动”。 - -**例 3:同一模块拆两级** - -传感器:BOARD 里只保证 I2C 控制器在;APP 里再创建采样线程。 - -```c -/* drv_i2c.c —— 板级:挂 i2c1 设备 */ -int rt_hw_i2c_init(void) -{ - stm32_i2c_register("i2c1"); - return 0; -} -INIT_BOARD_EXPORT(rt_hw_i2c_init); - -/* app_sht30.c —— 应用:打开设备、建线程 */ -int sht30_app_init(void) -{ - rt_device_t i2c = rt_device_find("i2c1"); - if (i2c == RT_NULL) - return -RT_ENOSYS; - - rt_thread_t tid = rt_thread_create("sht30", sht30_entry, i2c, 1024, 15, 10); - if (tid) - rt_thread_startup(tid); - return 0; -} -INIT_APP_EXPORT(sht30_app_init); -``` - -BOARD 保证 `rt_device_find("i2c1")` 在 APP 时已经有了。如果把 `rt_hw_i2c_init` 也写成 `INIT_APP_EXPORT`,和 `sht30_app_init` 同级,**谁先跑不一定**,`find` 可能失败。 - -**例 4:中间那几档(知道即可)** - -| 宏 | level | 典型用途 | -|---|---|---| -| `INIT_BOARD_EXPORT` | `"1"` | 时钟、GPIO、UART/I2C 控制器注册 | -| `INIT_PREV_EXPORT` | `"2"` | 纯软件、不依赖设备(有的版本做对象系统补充) | -| `INIT_DEVICE_EXPORT` | `"3"` | 依赖总线的从设备:传感器、Flash | -| `INIT_COMPONENT_EXPORT` | `"4"` | DFS、LWIP、USB 协议栈 | -| `INIT_ENV_EXPORT` | `"5"` | 文件系统挂载、网络启动 | -| `INIT_APP_EXPORT` | `"6"` | 你的业务线程、GUI、协议栈应用 | - -#### 5. 和 `main()` 的关系 - -``` -main 线程入口 - → rt_components_init() // 所有 APP(以及 2~5)先跑完 - → main() // 然后才是你的 main -``` - -所以 `INIT_APP_EXPORT(fn)` 比 `main()` **更早**。`main` 里 `rt_device_find("uart1")` 看得到 BOARD 注册的设备;APP 里创建的线程也可能已经在跑。 - -不要在 BOARD 函数里假设 `main` 已经执行;也不要在 `main` 里再注册一遍 BOARD 已经挂过的设备(除非你没开自动 init)。 - -#### 6. 容易混的点 - -1. **宏不“调用”函数,只“投放”指针。** 没被扫到(没开 `RT_USING_COMPONENTS_INIT`、链接脚本没 `KEEP(*(SORT(.rti_fn*)))`),函数永远不会跑,还可能被 GC 掉;所以要 `rt_used`。 -2. **同 level 不要排依赖。** I2C 和传感器都 `INIT_APP_EXPORT` 就会偶发 `find` 失败。设备放 BOARD/DEVICE,应用放 APP。 -3. **BOARD 里能用堆吗?** 取决于 `rt_system_heap_init()` 是否已经在 `rt_hw_board_init()` 里、并且在 `rt_components_board_init()` **之前**。官方 BSP 一般是先堆再扫 BOARD。不要在 BOARD 里 `rt_thread_create`(动态线程还要调度器)。 -4. **和 `MSH_CMD_EXPORT` 是同一套路**:命令表也是 `section` + `KEEP` + 启动/shell 扫描。自动 init 扫的是 `.rti_fn.*`,shell 扫的是另一段。 -5. **函数必须全局、签名对。** `static int foo(void)` 可以(符号变成 `__rt_init_foo` 在段里);写成 `void foo(void)` 或带参数,类型和 `init_fn_t` 对不上。 - -对照前面那句:关中断是为了让 SysTick 先别进 ISR;`INIT_BOARD_EXPORT` 正好跑在这扇门里面——硬件可以配,线程 API 还不能用。`INIT_APP_EXPORT` 跑在门打开之后,就是普通线程里的自动 `main` 前缀。 - - diff --git "a/2026/\347\254\2541\347\273\204/\347\244\272\344\276\213_\345\274\240\344\270\211/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\347\244\272\344\276\213_\345\274\240\344\270\211/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2541\347\273\204/\347\244\272\344\276\213_\345\274\240\344\270\211/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\347\244\272\344\276\213_\345\274\240\344\270\211/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex1_parking.c" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex1_parking.c" deleted file mode 100644 index 37b08647c34e8ee2d2a922423cb496ce633b236c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex1_parking.c" +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 练习一:停车场车位管理(计数信号量) - * 与 ex2/ex3/ex4 互斥,applications/ 下同一时刻只留一个。 - */ -#include - -#define PARK_TOTAL 3 -#define CAR_NUM 5 -#define CAR_STAY_TICK rt_tick_from_millisecond(3000) -#define CAR_WAIT_TICK rt_tick_from_millisecond(2000) - -static rt_sem_t park_sem = RT_NULL; - -static void car_entry(void *param) -{ - rt_uint32_t car_no = (rt_uint32_t)param; - rt_uint32_t value; - - while (1) - { - if (rt_sem_take(park_sem, CAR_WAIT_TICK) == RT_EOK) - { - rt_sem_getvalue(park_sem, &value); - rt_kprintf("car%u entered, used spaces: %u\n", car_no, PARK_TOTAL - value); - rt_thread_mdelay(CAR_STAY_TICK); - rt_kprintf("car%u left\n", car_no); - rt_sem_release(park_sem); - break; - } - else - { - rt_kprintf("car%u waiting for a space\n", car_no); - rt_thread_mdelay(rt_tick_from_millisecond(500)); - } - } -} - -static int parking_init(void) -{ - rt_thread_t tid; - rt_uint32_t i; - char name[8]; - - park_sem = rt_sem_create("park", PARK_TOTAL, RT_IPC_FLAG_FIFO); - if (park_sem == RT_NULL) - { - rt_kprintf("park sem create failed\n"); - return -RT_ENOMEM; - } - - for (i = 1; i <= CAR_NUM; i++) - { - rt_snprintf(name, sizeof(name), "car%u", i); - tid = rt_thread_create(name, car_entry, (void *)i, 1024, 20, 5); - if (tid) rt_thread_startup(tid); - else rt_kprintf("%s create failed\n", name); - } - return RT_EOK; -} -INIT_APP_EXPORT(parking_init); diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex2_tickets.c" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex2_tickets.c" deleted file mode 100644 index ef11b379a97a681329c55513691c1cd73b0d3b34..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex2_tickets.c" +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 练习二:多窗口售票系统(互斥量) - * 与 ex1/ex3/ex4 互斥,applications/ 下同一时刻只留一个。 - * 对比实验:把 USE_MUTEX 改为 0,观察无保护时的竞争。 - */ -#include - -#define USE_MUTEX 1 /* 对比实验:改为 0,观察无保护时的竞争 */ - -static rt_int32_t ticket_count = 20; -static rt_mutex_t ticket_mutex = RT_NULL; - -static void window_entry(void *param) -{ - const char *name = (const char *)param; - rt_int32_t sold; - rt_bool_t last; - - while (1) - { -#if USE_MUTEX - rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); -#endif - if (ticket_count > 0) - { - sold = ticket_count; -#if !USE_MUTEX - rt_thread_mdelay(10); /* 读票之后、减票之前留出竞争窗口 */ -#endif - ticket_count--; - last = (ticket_count == 0); -#if USE_MUTEX - rt_mutex_release(ticket_mutex); -#endif - rt_kprintf("window %s sold ticket %d\n", name, sold); - if (last) - { - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: %d\n", ticket_count); - } - rt_thread_mdelay(50); - } - else - { -#if USE_MUTEX - rt_mutex_release(ticket_mutex); -#endif - break; - } - } -} - -static int tickets_init(void) -{ - rt_thread_t ta, tb; - - ticket_mutex = rt_mutex_create("ticket", RT_IPC_FLAG_FIFO); - if (ticket_mutex == RT_NULL) return -RT_ENOMEM; - - ta = rt_thread_create("winA", window_entry, (void *)"A", 1024, 15, 5); - tb = rt_thread_create("winB", window_entry, (void *)"B", 1024, 15, 5); - if (ta) rt_thread_startup(ta); - if (tb) rt_thread_startup(tb); - return RT_EOK; -} -INIT_APP_EXPORT(tickets_init); diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex3_startup.c" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex3_startup.c" deleted file mode 100644 index 4526c7d741d458752e199775f5948f3c3df7e8d0..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex3_startup.c" +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 练习三:系统启动条件检查(事件集) - * 与 ex1/ex2/ex4 互斥,applications/ 下同一时刻只留一个。 - */ -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -static rt_event_t init_event = RT_NULL; -static volatile rt_uint32_t g_ready = 0; /* 记录已就绪模块,供超时打印用 */ - -static void mark_ready(rt_uint32_t flag) -{ - rt_base_t level = rt_hw_interrupt_disable(); - g_ready |= flag; - rt_hw_interrupt_enable(level); -} - -static void net_entry(void *p) -{ - rt_thread_mdelay(1000); - rt_event_send(init_event, EVENT_NET_READY); - mark_ready(EVENT_NET_READY); - rt_kprintf("network ready\n"); -} - -static void sensor_entry(void *p) -{ - rt_thread_mdelay(1500); - rt_event_send(init_event, EVENT_SENSOR_READY); - mark_ready(EVENT_SENSOR_READY); - rt_kprintf("sensor ready\n"); -} - -static void storage_entry(void *p) -{ - rt_thread_mdelay(5000); /* 超时测试:故意设长,让业务先超时 */ - rt_event_send(init_event, EVENT_STORAGE_READY); - mark_ready(EVENT_STORAGE_READY); - rt_kprintf("storage ready\n"); -} - -static void business_entry(void *p) -{ - rt_uint32_t recved; - rt_err_t err; - - /* 先等 3 秒(AND 模式,三个事件都到才算数) */ - err = rt_event_recv(init_event, EVENT_ALL, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - rt_tick_from_millisecond(3000), &recved); - if (err != RT_EOK) - { - if (!(g_ready & EVENT_NET_READY)) rt_kprintf("wait modules timeout, network is not ready\n"); - if (!(g_ready & EVENT_SENSOR_READY)) rt_kprintf("wait modules timeout, sensor is not ready\n"); - if (!(g_ready & EVENT_STORAGE_READY)) rt_kprintf("wait modules timeout, storage is not ready\n"); - /* storage 还在初始化,继续等到它完成 */ - rt_event_recv(init_event, EVENT_ALL, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_WAITING_FOREVER, &recved); - } - - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); -} - -static int startup_init(void) -{ - rt_thread_t t; - - init_event = rt_event_create("init", RT_IPC_FLAG_FIFO); - if (init_event == RT_NULL) return -RT_ENOMEM; - - t = rt_thread_create("net", net_entry, RT_NULL, 1024, 10, 5); if (t) rt_thread_startup(t); - t = rt_thread_create("sensor", sensor_entry, RT_NULL, 1024, 10, 5); if (t) rt_thread_startup(t); - t = rt_thread_create("storage", storage_entry, RT_NULL, 1024, 10, 5); if (t) rt_thread_startup(t); - t = rt_thread_create("business", business_entry, RT_NULL, 1024, 8, 5); if (t) rt_thread_startup(t); - return RT_EOK; -} -INIT_APP_EXPORT(startup_init); diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex4_parcel.c" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex4_parcel.c" deleted file mode 100644 index e5bdbb121da07933eff09526b62996c51dee1938..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ex4_parcel.c" +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 练习四:快递分拣中心(消息队列) - * 与 ex1/ex2/ex3 互斥,applications/ 下同一时刻只留一个。 - */ -#include - -#define MQ_CAPACITY 5 -#define SENDER_NUM 2 -#define PKG_PER_SENDER 5 -#define REGION_END 0 /* 结束消息:region=0 表示某发件线程已发完 */ - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; /* 1 华东, 2 华南, 3 华北 */ - rt_uint16_t weight; -}; - -static rt_mq_t pkg_mq = RT_NULL; -static rt_mutex_t sf_mutex = RT_NULL; /* 保护 send_failed 计数 */ -static rt_uint32_t send_failed = 0; - -static const char *region_str(rt_uint8_t r) -{ - switch (r) - { - case 1: return "east"; - case 2: return "south"; - case 3: return "north"; - default: return "?"; - } -} - -static void sender_entry(void *param) -{ - rt_uint32_t sender = (rt_uint32_t)param; - rt_uint32_t base = (sender == 1) ? 1000 : 2000; - struct package_msg pkg; - rt_uint32_t i; - - for (i = 1; i <= PKG_PER_SENDER; i++) - { - pkg.id = base + i; - pkg.region = (rt_uint8_t)((i % 3) + 1); /* 简单分布 1/2/3 */ - pkg.weight = (rt_uint16_t)(800 + (sender * 100) + i * 37); /* 任意重量 */ - - if (rt_mq_send(pkg_mq, &pkg, sizeof(pkg)) != RT_EOK) - { - /* 队列满,记一次失败,原地重试本条(i-- 保证同一条只成功发一次) */ - rt_mutex_take(sf_mutex, RT_WAITING_FOREVER); - send_failed++; - rt_mutex_release(sf_mutex); - i--; - rt_thread_mdelay(100); - } - } - - /* 本线程所有快递发完,再发一条结束消息 */ - pkg.id = 0; pkg.region = REGION_END; pkg.weight = 0; - while (rt_mq_send(pkg_mq, &pkg, sizeof(pkg)) != RT_EOK) - rt_thread_mdelay(100); -} - -static void sorter_entry(void *param) -{ - struct package_msg pkg; - rt_uint32_t total = 0, east = 0, south = 0, north = 0, weight = 0; - rt_uint32_t end_count = 0; - - while (1) - { - if (rt_mq_recv(pkg_mq, &pkg, sizeof(pkg), RT_WAITING_FOREVER) != RT_EOK) - continue; - - if (pkg.region == REGION_END) - { - end_count++; - if (end_count >= SENDER_NUM) break; /* 两个发件线程都结束 */ - continue; - } - - total++; - weight += pkg.weight; - if (pkg.region == 1) east++; - else if (pkg.region == 2) south++; - else if (pkg.region == 3) north++; - - rt_kprintf("package %u -> %s, weight: %u g\n", pkg.id, region_str(pkg.region), pkg.weight); - } - - rt_kprintf("total packages: %u\n", total); - rt_kprintf("east: %u, south: %u, north: %u\n", east, south, north); - rt_kprintf("total weight: %u g\n", weight); - rt_mutex_take(sf_mutex, RT_WAITING_FOREVER); - rt_kprintf("send failed: %u\n", send_failed); - rt_mutex_release(sf_mutex); -} - -static int parcel_init(void) -{ - rt_thread_t t; - - pkg_mq = rt_mq_create("pkg", sizeof(struct package_msg), MQ_CAPACITY, RT_IPC_FLAG_FIFO); - if (pkg_mq == RT_NULL) return -RT_ENOMEM; - - sf_mutex = rt_mutex_create("sf", RT_IPC_FLAG_FIFO); - if (sf_mutex == RT_NULL) return -RT_ENOMEM; - - t = rt_thread_create("sender1", sender_entry, (void *)1, 1024, 15, 5); if (t) rt_thread_startup(t); - t = rt_thread_create("sender2", sender_entry, (void *)2, 1024, 15, 5); if (t) rt_thread_startup(t); - t = rt_thread_create("sorter", sorter_entry, RT_NULL, 2048, 10, 5); if (t) rt_thread_startup(t); - return RT_EOK; -} -INIT_APP_EXPORT(parcel_init); diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/Pasted image 20260818214306.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/Pasted image 20260818214306.png" deleted file mode 100644 index 0a5302ee0aebc006fb281985392f156af4ed218c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/Pasted image 20260818214306.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/app_led_threads.c" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/app_led_threads.c" deleted file mode 100644 index ac47cb100658b73d0695ddc8dc585ed32772861f..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/app_led_threads.c" +++ /dev/null @@ -1,214 +0,0 @@ -/* - * app_led_threads.c —— 星火一号 (STM32F407ZGT6) 多线程实战示例 - * ---------------------------------------------------------------------------- - * 板载资源(已核对星火一号原理图/BSP): - * LED_R(红) = PF12 共阳极:低电平点亮 / 高电平熄灭 - * LED_B(蓝) = PF11 共阳极:低电平点亮 / 高电平熄灭 - * KEY_LEFT = PC0 按下为低电平(下降沿触发) - * - * 线程规划: - * - 工作线程1 th_led1 : 动态创建(rt_thread_create),优先级16/时间片5/栈1024 - * - 工作线程2 th_led2 : 静态初始化(rt_thread_init),全局 TCB + 全局栈 - * - 监控线程 th_monitor : 优先级10(更高),周期 rt_thread_mdelay(2000), - * 打印 th_led1/th_led2 的状态与剩余栈;按键挂起/恢复 th_led1 - * - INIT_APP_EXPORT() 自动加载(不碰 main.c) - * - MSH_CMD_EXPORT() 导出命令 thread_dump - * - * 用法:把本文件放进 <星火一号工程>/applications/ 目录,编译下载即可。 - * 注意:星火一号模板 main.c 里默认有一段闪烁 LED 的 demo,请把它注释掉, - * 否则会和本文件的线程同时操作 PF11/PF12,造成竞争。 - * ---------------------------------------------------------------------------- - */ -#include -#include -#include /* 星火一号 BSP 提供 GET_PIN */ - -/* ============== 1. 星火一号 板级引脚 ============== */ -#define LED_R_PIN GET_PIN(F, 12) /* 红色 LED */ -#define LED_B_PIN GET_PIN(F, 11) /* 蓝色 LED */ -#define KEY_PIN GET_PIN(C, 0) /* KEY_LEFT,按下为低电平 */ -#define KEY_IRQ_MODE PIN_IRQ_MODE_FALLING - -/* ============== 2. 线程参数 ============== */ -#define LED_PRIORITY 16 -#define LED_TICK 5 -#define LED_STACK_SIZE 1024 - -#define MON_PRIORITY 10 -#define MON_TICK 5 -#define MON_STACK_SIZE 1024 - -/* ============== 3. 静态线程 th_led2 的 TCB 与栈(全局) ============== */ -static struct rt_thread th_led2; -static rt_uint8_t led2_stack[LED_STACK_SIZE]; - -/* ============== 4. 全局句柄、计数与按键去抖 ============== */ -static rt_thread_t th_led1 = RT_NULL; -static rt_thread_t th_monitor = RT_NULL; -static volatile rt_uint32_t led1_count = 0; -static volatile rt_uint32_t led2_count = 0; -static volatile rt_bool_t led1_suspended = RT_FALSE; -static rt_tick_t last_key_tick = 0; /* 按键软件去抖用 */ - -/* ============== 5. 状态 → 中文 工具函数 ============== */ -static const char *stat2str(rt_uint8_t stat) -{ - switch (stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: return "初始态"; - case RT_THREAD_READY: return "就绪态"; - case RT_THREAD_RUNNING: return "运行态"; - case RT_THREAD_SUSPEND: return "挂起态"; - case RT_THREAD_CLOSE: return "关闭态"; - default: return "未知态"; - } -} - -/* ============== 6. 工作线程1:动态创建 th_led1 ============== */ -/* 星火一号 LED 为共阳:PIN_LOW 点亮,PIN_HIGH 熄灭;翻转即亮灭交替 */ -static void led1_entry(void *parameter) -{ - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - - while (1) - { - led1_count++; - rt_pin_write(LED_R_PIN, (led1_count % 2) ? PIN_HIGH : PIN_LOW); - rt_kprintf("th_led1 count=%u, tick=%u\n", led1_count, rt_tick_get()); - rt_thread_mdelay(500); - } -} - -/* ============== 7. 工作线程2:静态初始化 th_led2 ============== */ -static void led2_entry(void *parameter) -{ - rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); - - while (1) - { - led2_count++; - rt_pin_write(LED_B_PIN, (led2_count % 2) ? PIN_HIGH : PIN_LOW); - rt_kprintf("th_led2 count=%u\n", led2_count); - rt_thread_mdelay(500); - } -} - -/* ============== 8. 监控线程 th_monitor(优先级10,高于工作线程) ============== */ -static void monitor_entry(void *parameter) -{ - while (1) - { - rt_thread_mdelay(2000); /* 周期 2s */ - - rt_kprintf("==== monitor @ tick=%u ====\n", rt_tick_get()); - - if (th_led1 != RT_NULL) - rt_kprintf("th_led1 stat=%s free_stack=%lu\n", - stat2str(th_led1->stat), - (unsigned long)rt_thread_get_free_stack_size(th_led1)); - - rt_kprintf("th_led2 stat=%s free_stack=%lu\n", - stat2str(th_led2.stat), - (unsigned long)rt_thread_get_free_stack_size(&th_led2)); - - rt_kprintf("th_led1_suspended=%s\n", led1_suspended ? "yes" : "no"); - } -} - -/* ============== 9. 按键中断回调:单次挂起 / 再次恢复 th_led1 ============== */ -static void key_callback(void *args) -{ - /* 中断上下文不能 sleep,用 tick 做 200ms 软件去抖 */ - rt_tick_t now = rt_tick_get(); - if (now - last_key_tick < rt_tick_from_millisecond(200)) - return; - last_key_tick = now; - - if (!led1_suspended) - { - if (th_led1 != RT_NULL) - rt_thread_suspend(th_led1); /* 中断里挂起“另一个”线程是允许的 */ - led1_suspended = RT_TRUE; - rt_kprintf("[key] th_led1 suspended\n"); - } - else - { - if (th_led1 != RT_NULL) - rt_thread_resume(th_led1); - led1_suspended = RT_FALSE; - rt_kprintf("[key] th_led1 resumed\n"); - } -} - -/* ============== 10. MSH 命令 thread_dump ============== */ -static void cmd_thread_dump(int argc, char **argv) -{ - rt_kprintf("---- thread_dump ----\n"); - if (th_led1 != RT_NULL) - rt_kprintf("th_led1 stat=%s free_stack=%lu count=%u\n", - stat2str(th_led1->stat), - (unsigned long)rt_thread_get_free_stack_size(th_led1), - led1_count); - rt_kprintf("th_led2 stat=%s free_stack=%lu count=%u\n", - stat2str(th_led2.stat), - (unsigned long)rt_thread_get_free_stack_size(&th_led2), - led2_count); - if (th_monitor != RT_NULL) - rt_kprintf("th_monitor stat=%s free_stack=%lu\n", - stat2str(th_monitor->stat), - (unsigned long)rt_thread_get_free_stack_size(th_monitor)); -} -MSH_CMD_EXPORT(thread_dump, dump th_led1/th_led2/th_monitor thread info); - -/* ============== 11. 自动初始化入口(app 级,禁止在 main.c 硬编码) ============== */ -static int led_app_init(void) -{ - rt_err_t ret; - - /* 引脚初始化 */ - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); - rt_pin_mode(KEY_PIN, PIN_MODE_INPUT_PULLUP); /* 上拉:按下为低 */ - - /* ① 动态创建 th_led1 */ - th_led1 = rt_thread_create("th_led1", led1_entry, RT_NULL, - LED_STACK_SIZE, LED_PRIORITY, LED_TICK); - if (th_led1 != RT_NULL) - rt_thread_startup(th_led1); - else - { - rt_kprintf("th_led1 create failed!\n"); - return -RT_ENOMEM; - } - - /* ② 静态初始化 th_led2 */ - ret = rt_thread_init(&th_led2, "th_led2", led2_entry, RT_NULL, - &led2_stack[0], sizeof(led2_stack), - LED_PRIORITY, LED_TICK); - if (ret == RT_EOK) - rt_thread_startup(&th_led2); - else - { - rt_kprintf("th_led2 init failed!\n"); - return (int)ret; - } - - /* ③ 动态创建 th_monitor(优先级10 < 16,更高) */ - th_monitor = rt_thread_create("th_monitor", monitor_entry, RT_NULL, - MON_STACK_SIZE, MON_PRIORITY, MON_TICK); - if (th_monitor != RT_NULL) - rt_thread_startup(th_monitor); - else - { - rt_kprintf("th_monitor create failed!\n"); - return -RT_ENOMEM; - } - - /* ④ 按键中断:KEY_LEFT 单次挂起 / 再次恢复 */ - rt_pin_attach_irq(KEY_PIN, KEY_IRQ_MODE, key_callback, RT_NULL); - rt_pin_irq_enable(KEY_PIN, PIN_IRQ_ENABLE); - - rt_kprintf("led_app_init ok (INIT_APP_EXPORT) on 星火一号\n"); - return RT_EOK; -} -INIT_APP_EXPORT(led_app_init); diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/rtthread-day2-work.md" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/rtthread-day2-work.md" deleted file mode 100644 index 6b4e9a3c5ba258489c9751f5b68d1d819b4b1a3e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/rtthread-day2-work.md" +++ /dev/null @@ -1,258 +0,0 @@ -# 1.STM32F407 启动流程与 RT-Thread 引导机制 - -## 1.1 为何需要自动化初始化 -传统 RTOS/裸机工程里,驱动初始化通常靠人工在 `main()` 里逐个调用,需要用脑子记忆,新增驱动要改 main。而在rtthread内有两个函数`INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` `声明及注册`,只要把初始化函数用宏标注,链接器在编译期就把它排进一个特定段,运行期由内核自动遍历执行,main 里什么都不用写。 - ---- -### 1.2 宏展开机制 -所有 `INIT_*_EXPORT` 宏都源于同一个底层宏 `INIT_EXPORT`: -```c -/* rtdef.h —— 六级初始化优先级,数字越小越靠前执行 */ -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "board") /* 板级:最早 */ -#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "pre") -#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "device") -#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "component") -#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "env") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "app") /* 应用:最晚 */ - -/* 真正的"魔法"定义 */ -#define INIT_EXPORT(fn, level) \ - RT_USED const rt_init_fn_t __rt_init_##fn##_##level \ - SECTION("rt_init_fn." level) = fn -``` -**其中**,INIT_BOARD_EXPORT(fn)` 做的事情,就是把 `fn` 这个函数指针,塞进 Flash 里一个叫 `rt_init_fn.board` 的小抽屉,和所有同级别的函数指针排在一起。 - ---- - -### 1.3 链接脚本布局(段的物理排序) - -链接脚本(`link.lds` / `rtthread.lds`)负责把这些分散在各 `.o` 文件里的 `rt_init_fn.*` 段**聚到一起并按级别排序** - -链接器还会生成两个边界符号:`__rt_init_fn_start`(段首)和 `__rt_init_fn_end`(段尾)。**这两个符号之间的内存,本质上就是一个"函数指针数组"** - ---- -### 1.4 运行期遍历(谁来"按抽屉执行") -```c -/* components.c */ - -/* 入口 A:只跑 board 段,在 rt_hw_board_init() 内部、调度器启动之前 */ -void rt_components_board_init(void) -{ - volatile const rt_init_fn_t *fn_ptr; - - for (fn_ptr = &__rt_init_fn_board_start; - fn_ptr < &__rt_init_fn_board_end; - fn_ptr++) - { - (*fn_ptr)(); /* 逐个取出函数指针并调用 */ - } -} - -/* 入口 B:跑剩下五段(pre→device→component→env→app),在 main 线程入口里、用户 main 之前 */ -void rt_components_init(void) -{ - volatile const rt_init_fn_t *fn_ptr; - - for (fn_ptr = &__rt_init_fn_pre_start; - fn_ptr < &__rt_init_fn_app_end; - fn_ptr++) - { - (*fn_ptr)(); - } -} -``` -![[Pasted image 20260818214306.png|394]] - - ---- -### 1.5 INIT_BOARD_EXPORT 与 INIT_APP_EXPORT 的本质区别 - -| 维度 | `INIT_BOARD_EXPORT` | `INIT_APP_EXPORT` | -|---|---|---| -| 所属段 | `rt_init_fn.board` | `rt_init_fn.app` | -| 调用入口 | `rt_components_board_init()` | `rt_components_init()` | -| 调用时机 | **调度器启动之前**(中断仍关闭) | **调度器启动之后**、用户 `main()` **之前** | -| 线程环境 | 无线程概念,不能阻塞/睡眠 | 已是线程上下文,可用 `rt_thread_mdelay`、信号量等 | -| 典型用途 | 时钟、GPIO pinmux、早期 UART | 网络协议栈、文件系统、应用级组件 | -| 失败后果 | 系统根本起不来 | 仅该应用组件异常,系统仍在跑 | -**执行顺序保证**:因为 `board` 段在 Flash 中排在 `component` 段之前,且 `rt_components_board_init` 先于 `rt_components_init` 被调用,所以 `rt_hw_usart_init` 一定先于 `rt_hw_console_init` 执行。 - -所以,他们的本质区别是中断的时机不同,而这也决定了他们能做什么,能不能使用线程。 - ---- -## 2. 内核基础与对象模型 -RT-Thread 用 **纯 C 语言**模拟面向对象,核心为”对象头 + 类型标签“: -- 所有内核对象(线程、信号量、互斥量、定时器、设备、邮箱……)的 `struct` **第一个成员都是 `struct rt_object`**,提供统一的名字、类型、链表节点。 -- 通过 `type` 字段区分对象种类;通过 `list` 双向链表节点,把对象挂进"**对象容器**"(内核用 `rt_object_container` 数组按类型管理,共 `RT_Object_Class_Unknown` 到各类别),实现类似"基类容器"的效果。 -- 访问对象时先检查 `type`,再按具体类型强转回派生结构体 -```c -/* rtdef.h:所有内核对象的公共头部 */ -struct rt_object -{ - char name[RT_NAME_MAX]; /* 对象名字 */ - rt_uint8_t type; /* 对象类型(枚举) */ - rt_uint8_t flag; /* 对象标志 */ - rt_list_t list; /* 挂到对象容器的链表节点 */ -}; - -/* 线程控制块:第一成员就是 rt_object,等价于"继承" */ -struct rt_thread -{ - struct rt_object parent; /* 继承的公共头部 */ - rt_list_t tlist; /* 调度器用的就绪/挂起链表节点 */ - /* ... 线程私有字段见 2.2 ... */ -}; -``` - ---- - -### 2.2 线程控制块 `struct rt_thread` 关键成员 - -| 成员 | 类型 | 作用 | -|---|---|---| -| `stat` | `rt_uint8_t` | **线程状态机核心**:`RT_THREAD_INIT`(初始) / `RT_THREAD_READY`(就绪) / `RT_THREAD_RUNNING`(运行) / `RT_THREAD_SUSPEND`(挂起) / `RT_THREAD_CLOSE`(关闭)。调度器据此决定是否参与调度 | -| `current_priority` | `rt_uint8_t` | **当前优先级**(0 最高)。调度时比较此值选最高优先级线程运行;可被 `rt_thread_control` 动态修改 | -| `init_priority` | `rt_uint8_t` | 初始优先级,作为"基准"。当优先级被临时提升(如优先级翻转防护)后,可恢复回此值 | -| `init_tick` | `rt_uint32_t` | **时间片初始值**(单位:tick)。同优先级线程轮转时,每次获得的 CPU 时间长度 | -| `remaining_tick` | `rt_uint32_t` | **剩余时间片**。SysTick 每 tick 递减;归零后线程被移到同优先级就绪队尾,并重置为 `init_tick` | -| `sp` | `void *` | **当前栈指针**。上下文切换时保存/恢复现场的关键——指向该线程在被切出时各寄存器压栈的位置 | -| `entry` | `void *` | 线程入口函数指针 | -| `parameter` | `void *` | 传给 `entry` 的参数 | -| `stack_addr` / `stack_size` | `void *` / `rt_uint32_t` | 线程栈的起址与大小(静态或动态分配,见 2.3) | -| `tlist` | `rt_list_t` | 调度器链表节点:线程在**就绪表/挂起表/定时器超时表**中的挂接点 | -| `thread_timer` | `struct rt_timer` | 线程内置定时器:用于 `rt_thread_delay()` / `rt_thread_suspend` 的超时唤醒 | -| `error` | `rt_err_t` | 最近一次操作的错误码 | -| `cleanup` | 函数指针 | 线程退出时(状态变 `CLOSE`)的清理回调 | -> **调度器视角**:`stat` 决定"是否就绪"、`current_priority` 决定"被谁抢占"、`init_tick`/`remaining_tick` 决定"同优先级能跑多久"、`sp` 决定"切回来时从哪恢复现场"——这四个字段是调度算法直接读写的对象。 - ---- - -### 2.3 静态对象 vs 动态对象 - -#### 1. 静态线程创建(编译期确定内存) - -```c -/* 1. 定义线程控制块(TCB),由编译器在静态区分配内存 */ -static struct rt_thread led_thread; - -/* 2. 定义线程栈,大小为 512 字节,同样在静态区 */ -static rt_uint8_t led_stack[512]; - -/* 3. 初始化线程(此时线程处于“初始态”,不会立刻运行) */ -rt_thread_init( - &led_thread, /* 线程控制块指针 */ - "led", /* 线程名称,字符串,用于调试识别 */ - led_entry, /* 线程入口函数,即线程要做的事情 */ - RT_NULL, /* 传递给入口函数的参数(无参数则填 RT_NULL) */ - &led_stack[0], /* 栈起始地址(数组首元素地址) */ - sizeof(led_stack), /* 栈大小(单位:字节) */ - RT_THREAD_PRIORITY_MAX - 2, /* 线程优先级:数字越小越高,这里设为次低优先级 */ - 20 /* 时间片:同优先级轮转时,每次运行 20 个 tick */ -); - -/* 4. 启动线程:将线程从“初始态”转为“就绪态”,等待调度器调度 */ -rt_thread_startup(&led_thread); -``` - ---- - -### 2. 动态线程创建(运行期从堆分配内存) - -c - -```c -/* 1. 创建线程(系统自动从堆中分配控制块和栈空间) */ -rt_thread_t tid = rt_thread_create( - "led", /* 线程名称 */ - led_entry, /* 线程入口函数 */ - RT_NULL, /* 传递给入口函数的参数 */ - 512, /* 栈大小(单位:字节) */ - RT_THREAD_PRIORITY_MAX - 2, /* 线程优先级:次低 */ - 20 /* 时间片:20 个 tick */ -); - -/* 2. 检查线程是否创建成功 */ -if (tid != RT_NULL) -{ - /* 3. 启动线程(转为就绪态) */ - rt_thread_startup(tid); -} -else -{ - /* 创建失败,通常是堆内存不足 */ - rt_kprintf("create led thread failed!\n"); -} -``` - -| 维度 | 静态对象 | 动态对象 | -|---|---|---| -| 分配位置 | 编译期确定的 `.bss`/全局区(或你指定的静态数组) | 运行时从**内存堆(heap)** 动态申请 | -| 典型 API | `rt_thread_init()`、`rt_sem_init()`(对象由调用者提供) | `rt_thread_create()`、`rt_sem_create()`(内核帮你 malloc) | -| 生命周期 | 与程序同寿,或手动 `rt_thread_detach()` 脱离 | `rt_thread_delete()` / `rt_sem_delete()` 释放回堆 | -| 释放机制 | 无(或 detach 仅从容器摘除,内存不回收) | 释放回堆,可被后续分配复用 | -| 失败风险 | 编译期就占好内存,**无运行时分配失败** | 堆不足时返回 `RT_NULL`,需判空 | -| 内存碎片 | 无 | 长期运行可能产生碎片 | - ---- - -### 2.4 实践建议 - 实践建议:关键任务线程(控制环、看门狗、通信)、中断相关对象用静态;非关键、数量随业务波动的临时线程可用动态。 - -在工控、轨交、医疗、航天等**高可靠(safety-critical)场景**,通常禁止动态分配,原因: - -1. **确定性(Determinism)**:静态对象在编译/链接期就占好固定内存,**运行期分配不会失败**。动态 `malloc` 在堆紧张时可能返回 `NULL`,而关键控制线程若在启动时才失败,系统就起不来了。 -2. **无内存碎片**:长期 7×24 运行的设备,动态分配/释放会逐步产生碎片,最终无连续块可用。静态分配一次性划定,永不产生碎片。 -3. **无释放类 Bug**:动态对象有 `use-after-free`、`double-free`、`泄漏` 等风险;静态对象生命周期与系统一致,规避整类错误。 -4. **启动顺序与可验证性**:静态对象地址固定,可静态分析内存布局、做 MISRA/功能安全审计;动态堆的行为难以在出厂前完全验证。 -5. **最坏执行时间(WCET)可控**:动态分配的时间不确定,静态分配的执行路径可预测——这对实时性认证(如 IEC 61508、ISO 26262)是硬要求。 - -## 3. 线程状态机与调度算法 - -### 3.1 rtt线程状态描述及转换api - -|**

状态名称

**|**

宏定义(在 `rtdef.h` 中)

**|**

描述

**| -|---|---|---| -|**初始态**​|`RT_THREAD_INIT`|线程刚被创建,系统为其分配了控制块和栈,但还未就绪运行。| -|**就绪态**​|`RT_THREAD_READY`|线程已经具备运行条件,正在等待 CPU 资源(排在就绪列表中)。| -|**运行态**​|`RT_THREAD_RUNNING`|线程当前正占用 CPU 正在执行。| -|**挂起态**​|`RT_THREAD_SUSPEND`|线程因为等待某个事件(如延时到期、信号量、互斥量等)而主动让出 CPU。| -|**关闭态**​|`RT_THREAD_CLOSE`|线程执行完毕或主动退出,等待系统回收资源(由 idle 线程完成最后清理)。| - -![[五态关系图.png]] - -| 起点 → 终点 | 触发 API / 事件 | -| --------- | --------------------------------------------------------------------------------------- | -| 初始态 → 就绪态 | `rt_thread_startup()` | -| 就绪态 → 运行态 | 调度器在节拍/中断返回时选中(无显式 API,由 `rt_schedule()` 驱动) | -| 运行态 → 就绪态 | `rt_thread_yield()`(主动让出)、被高优先级线程抢占 | -| 运行态 → 挂起态 | `rt_thread_suspend()`、`rt_thread_delay()`、`rt_sem_take()`(资源不可用)、`rt_mb_recv()` 等阻塞 API | -| 挂起态 → 就绪态 | `rt_thread_resume()`、等待的信号量/邮箱到位、`rt_timer` 超时唤醒 | -| * → 关闭态 | `rt_thread_delete()`(动态)/ `rt_thread_detach()`(静态)或线程入口函数 `return` | ->在其中程序未完成而发生的“抢占”行为不是线程自己调的,而是调度器在中断返回或资源释放点强制发生的**。 - ---- - -### 3.3 抢占式调度(Preemptive Scheduling) - -RT-Thread 默认是**基于优先级的全抢占式**调度: - -- **核心规则**:就绪队列中,**优先级数值最小(0 最高)的线程永远先跑**。任何时刻只要有更高优先级线程进入就绪态,当前运行线程立即被抢占。 -- **抢占发生的时机(调度点)**: - 1. **中断返回**:ISR 里释放了资源(如 `rt_sem_release`),退出中断前调用 `rt_schedule()`,发现更高优先级就绪 → 切换。 - 2. **主动释放资源**:线程调用 `rt_sem_release()` / `rt_mb_send()` 等,内部触发 `rt_schedule()`。 - 3. **主动让出**:线程调 `rt_thread_yield()`(让同优先级或更高优先级先跑)。 - 4. **线程挂起/延时**:`rt_thread_delay()` 当前线程进入 SUSPEND,调度器选下一个就绪线程。 -- **实现支撑**:调度器用"优先级位图(bitmap)+ 每优先级一个就绪链表"的结构,找最高优先级线程是 O(1);`current_priority` 字段即比较依据。 - ---- - -### 3.4 时间片轮转(Round-Robin) - -抢占式解决"谁先跑(优先级)",时间片轮转解决"**同优先级多个线程怎么公平分 CPU**": - -- **作用范围**:**仅在相同 `current_priority` 的线程之间**生效。不同优先级仍走抢占式。 -- **原理**: - - 每个线程有 `init_tick`(时间片长度)和 `remaining_tick`(剩余)。 - - 系统节拍(SysTick)每触发一次,`remaining_tick--`。 - - 递减到 0 时:该线程被移到**同优先级就绪队列的队尾**,`remaining_tick` 重置为 `init_tick`,调度器在同优先级里选下一个。 -- 线程也可主动 `rt_thread_yield()` 立即让出(不等时间片耗尽)。 diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\272\224\346\200\201\345\205\263\347\263\273\345\233\276.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\272\224\346\200\201\345\205\263\347\263\273\345\233\276.png" deleted file mode 100644 index 74c23fe4985959319d0c31949aa68baecd03c7b5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\272\224\346\200\201\345\205\263\347\263\273\345\233\276.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/Git \345\270\270\347\224\250\345\221\275\344\273\244\351\200\237\346\237\245\350\241\250.md" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/Git \345\270\270\347\224\250\345\221\275\344\273\244\351\200\237\346\237\245\350\241\250.md" deleted file mode 100644 index 56d5a6d5fa73a9db164464647bd399180c920273..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/Git \345\270\270\347\224\250\345\221\275\344\273\244\351\200\237\346\237\245\350\241\250.md" +++ /dev/null @@ -1,143 +0,0 @@ -# Git 常用命令速查表 - -> 桌面速查参考,按使用场景分组。尖括号 `< >` 中的内容请按需替换,例如 `` = 远程仓库地址,`` = 分支名,`` = 文件路径。 - ---- - -## 一、仓库初始化与克隆 - -| 命令 | 说明 | -| :-- | :-- | -| `git init` | 在当前目录初始化一个新的 Git 仓库 | -| `git clone ` | 克隆远程仓库到本地 | -| `git clone ` | 克隆到指定目录 `` | -| `git config --global user.name ""` | 设置全局提交用户名 | -| `git config --global user.email ""` | 设置全局提交邮箱 | -| `git config --list` | 查看所有 Git 配置 | - ---- - -## 二、文件暂存与提交 - -| 命令 | 说明 | -| :-- | :-- | -| `git status` | 查看工作区与暂存区状态 | -| `git add ` | 将指定文件加入暂存区 | -| `git add .` | 将所有修改/新增文件加入暂存区(不含删除) | -| `git add -A` / `git add --all` | 将所有改动(含删除)加入暂存区 | -| `git commit -m ""` | 提交暂存区,并附提交说明 | -| `git commit -am ""` | 跳过 `git add`,直接提交已跟踪文件的修改 | -| `git commit --amend` | 修改最后一次提交(补充内容或改写说明) | -| `git stash` | 临时保存当前工作区修改 | -| `git stash pop` | 恢复最近一次 stash 并删除该记录 | -| `git stash list` | 查看所有 stash 记录 | - ---- - -## 三、分支管理 - -| 命令 | 说明 | -| :-- | :-- | -| `git branch` | 列出所有本地分支(当前分支前带 `*`) | -| `git branch ` | 基于当前分支创建新分支 | -| `git branch -d ` | 删除已合并的分支 | -| `git branch -D ` | 强制删除分支(未合并也可用) | -| `git checkout ` | 切换到指定分支 | -| `git checkout -b ` | 创建并切换到新分支 | -| `git switch ` | 切换到指定分支(Git 2.23+ 推荐) | -| `git switch -c ` | 创建并切换到新分支(Git 2.23+ 推荐) | -| `git merge ` | 将 `` 合并进当前分支 | -| `git rebase ` | 将当前分支的提交变基到 `` 之上 | - ---- - -## 四、合并与变基 - -| 命令 | 说明 | -| :-- | :-- | -| `git merge ` | 生成一次合并提交(保留完整历史) | -| `git merge --no-ff ` | 禁用快进合并,强制保留合并提交 | -| `git rebase ` | 将当前分支提交"重放"到目标分支末尾,历史更线性 | -| `git rebase -i ` | 交互式变基,可合并/改写提交 | -| `git rebase --abort` | 中止正在进行的变基 | -| `git rebase --continue` | 解决冲突后继续变基 | -| `git merge --abort` | 中止正在进行的合并 | - -> 冲突发生后:手动编辑冲突文件,保留需要的内容,再 `git add ` 标记已解决,最后 `git commit`(merge)或 `git rebase --continue`(rebase)。 - ---- - -## 五、远程仓库操作 - -| 命令 | 说明 | -| :-- | :-- | -| `git remote -v` | 查看远程仓库的 fetch / push 地址 | -| `git remote add origin ` | 关联本地仓库到远程 `origin` | -| `git remote set-url origin ` | 修改远程仓库地址 | -| `git remote rename ` | 重命名远程仓库 | -| `git remote rm ` | 移除远程仓库 | -| `git fetch` | 拉取所有远端更新到本地(不合并) | -| `git fetch ` | 同步指定远端分支到本地 | -| `git pull` | 拉取当前分支对应的远端并合并(`fetch` + `merge`) | -| `git pull --rebase` | 以变基方式拉取(fetch + rebase) | -| `git push` | 推送当前分支到对应远端 | -| `git push -u origin ` | 推送并关联远端分支(首次推送推荐) | -| `git push --force` | 强制推送(慎用,会覆盖远端历史) | -| `git push --tags` | 推送本地标签到远端 | - ---- - -## 六、查看状态与日志 - -| 命令 | 说明 | -| :-- | :-- | -| `git status` | 查看工作区与暂存区状态 | -| `git log` | 查看提交历史(按时间倒序) | -| `git log --oneline` | 每个提交显示一行(简洁) | -| `git log -n ` | 只显示最近 `` 条提交 | -| `git log --graph --all` | 图形化展示分支与合并历史 | -| `git log --author=""` | 按作者筛选提交 | -| `git show ` | 查看某次提交的详细改动 | -| `git diff` | 查看工作区与暂存区的差异 | -| `git diff --cached` | 查看暂存区与最新提交的差异 | -| `git diff ` | 比较两个分支的差异 | -| `git blame ` | 查看文件每行最后修改的提交与作者 | - ---- - -## 七、撤销与回退 - -| 命令 | 说明 | -| :-- | :-- | -| `git restore ` | 丢弃工作区对 `` 的修改(Git 2.23+) | -| `git restore --staged ` | 将 `` 从暂存区移出(取消 `add`) | -| `git checkout -- ` | 丢弃工作区对 `` 的修改(旧写法) | -| `git reset ` | 取消暂存(保留工作区修改) | -| `git reset --soft ` | 回退到指定提交,保留暂存区与工作区 | -| `git reset --mixed ` | 回退到指定提交,保留工作区(默认) | -| `git reset --hard ` | 彻底回退到指定提交(丢弃所有后续改动,**慎用**) | -| `git revert ` | 生成一次新提交来撤销某次提交(不改写历史,推荐) | -| `git clean -fd` | 删除未跟踪的文件与目录(**慎用**) | - -> **reset vs revert**:`reset` 改写历史,适合本地未推送的提交;`revert` 保留历史、新增撤销提交,适合已推送的提交。 - ---- - -## 附录:日常典型工作流 - -```bash -# 1. 克隆并切换到开发分支 -git clone -git switch -c feature/my-work - -# 2. 开发完成后暂存并提交 -git add . -git commit -m "完成某某功能" - -# 3. 同步远端最新代码(推荐变基方式) -git fetch origin -git rebase origin/master - -# 4. 推送到远端并发起 PR -git push -u origin feature/my-work -``` diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/rtthread-day1-notes.md" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/rtthread-day1-notes.md" deleted file mode 100644 index 6cc75fd3ac51ee19542f9cef76aeaa6a31970800..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/rtthread-day1-notes.md" +++ /dev/null @@ -1,242 +0,0 @@ -# 2026 RT-Thread 大学生夏令营 Day1 学习笔记 - ---- - -## 一、工程文件树要点 - -| 目录/模块 | 作用 | -| :-- | :-- | -| `application/main.c` | 程序主入口(如 hello 打印) | -| `drivers/` | 底层驱动适配(LCD/串口/音频等) | -| `components/` | 核心组件:C++、文件系统 DFS、finish 终端等 | -| `libc` | C 库兼容层(armcc/gcc/clang) | -| `src/` | 内核:时钟、设备管理、idle 线程、IPC(进程间通信) | -| `drivers` 框架 | 三层:IO 设备管理 → 驱动框架(串口/SPI/I2C/GPIO)→ 厂商驱动实现 | - -- **finish 组件**:`component/finish`,可查看系统资源(内存、线程状态);`ps` 查看线程,`tab` 补全命令;`idle` 线程是 RTOS 最小运行实体。 - ---- - -## 二、Markdown 语法(笔记/作业统一格式) - -- 标题:`#` 一级、`##` 二级、`###` 三级……(井号数量 = 层级)。 -- 代码块:三个反引号 ``` 包裹,起始处标注语言(如 ```c)实现高亮。 -- 行内代码/高亮:一对反引号 `code`。 -- 加粗:`**文字**`;斜体:`*文字*`。 -- 表格:用 `|` 分隔列,第二行用 `|---|` 分隔表头与内容。 -- 插入图片(语法示例,非本文实际引用):`![描述](图片路径)`;建议把剪贴板图片保存到工程下 `figures/` 等相对路径目录,避免迁移丢图。 -- 学习要求:每日用 Markdown 整理笔记 → 提交到个人 Git 仓库 + 发布到社区论坛。 - ---- - -## 三、Git 基础操作与 PR 贡献流程 - -### 3.1 全局设置 - -第一次使用 Git 时,先配置用户名和邮箱,这些信息会记录到每一次提交中: - -```bash -git config --global user.name "" -git config --global user.email "" -``` - -### 3.2 基础工作流(本地 → 远端) - -从零开始创建并推送一个仓库的最小流程: - -```bash -mkdir my_project -cd my_project -git init # 初始化当前目录为 Git 仓库 -touch README.md -git add README.md # 加入暂存区(受 git 保护) -git commit -m "first commit" # 提交,附说明 -git remote add origin # 关联远端仓库 -git push -u origin master # 本地推送到远端 -``` - -> **说明** -> -> - `git add `:将指定文件或目录加入暂存区(stage/index),加入后文件即进入 Git 管理、受版本控制保护。 -> - `git commit -m ""`:把暂存区的改动生成一次提交,并填写提交日志。 -> - `git push`:本地 → 远端(推送);`git pull`:远端 → 本地(拉取)。 - -提交成功后的终端输出示例: - -```text -[master 8de35a] first commit - 1 file changed, 3 insertions(+) - create mode 100644 README.md -``` - -如果当前工作区没有待提交内容,执行 `git commit` 会提示: - -```text -On branch master -Your branch is up to date with 'origin/master'. - -nothing to commit, working tree clean -``` - -需要临时保存当前修改(稍后恢复)时可用: - -```bash -git stash -``` - -输出示例: - -```text -Saved working directory and index state WIP on master: Initial commit -``` - -### 3.3 远程仓库管理 - -| 命令 | 作用 | -| :-- | :-- | -| `git remote add origin ` | 关联本地仓库到远端 | -| `git remote set-url origin ` | 修改当前仓库的远端地址(如仓库迁移) | -| `git remote -v` | 查看远端仓库的 fetch / push 地址 | -| `git fetch origin master` | 同步远端分支到本地(不自动合并) | - -修改远端地址示例: - -```bash -git remote set-url origin <新的仓库地址> -``` - -> 注意:`<新的仓库地址>` 请替换为你自己的实际仓库地址,例如 `git@example.com:/.git`。 - -查看远端地址: - -```bash -git remote -v -``` - -输出示例: - -```text -origin (fetch) -origin (push) -``` - -同步远端分支: - -```bash -git fetch origin master -``` - -输出示例: - -```text -From - * branch master -> FETCH_HEAD -``` - -### 3.4 分支与协作 - -| 命令 | 作用 | -| :-- | :-- | -| `git checkout -b ` | 基于当前分支新建并切换到新分支 | -| `git switch ` | 切换到指定分支(Git 2.23+ 推荐) | -| `git pull origin master` | 拉取远程 `master` 分支到本地 | - -```bash -git checkout -b test # 基于当前分支新建并切换到 test 分支 -git switch master # 切换到 master 分支 -git fetch origin master # 同步远端分支到本地 -git remote -v # 查看远端仓库 -git remote set-url origin # 修改远端仓库地址 -``` - -`git checkout -b test` 执行成功后会提示: - -```text -Switched to a new branch 'test' -``` - -### 3.5 提交新分支并创建 PR - -在 `test` 分支完成修改后,按以下步骤提交并推送到远端: - -```bash -git add README.md -git add ./ -git commit -m "second commit" -git push origin test -``` - -`git push origin test` 的输出示例: - -```text -Enumerating objects: 5, done. -Counting objects: 100% (5/5), done. -Delta compression using up to 16 threads -Compressing objects: 100% (3/3), done. -Writing objects: 100% (3/3), 324 bytes | 324.00 KiB/s, done. -Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) -remote: Powered by GITEE.COM -remote: Create a pull request for 'test' on Gitee by visiting: -remote: https://gitee.com///pull/new/:test...:master -To - * [new branch] test -> test -``` - -推送完成后,在 Gitee 仓库页面创建 Pull Request: - -1. **切换到目标分支**:在仓库页面顶部的分支下拉框中选择 `test`。 -2. **点击“贡献代码”**:页面会提示“当前分支比 master 领先 1 次提交”。 -3. **点击“创建 Pull Request”**:填写标题和描述后提交,等待仓库维护者审查合并。 - -页面中可看到该分支的提交记录,示例如下: - -| 提交人 | 提交信息 | Commit ID | 时间 | -| :-- | :-- | :-- | :-- | -| 你(或贡献者) | second commit | `71eebe9` | 2 分钟前 | - -README 预览中可能显示: - -```text -这是我的 git 测试仓库 -这是我的第二次修改 -``` - -### 3.6 PR(Pull Request)规范流程 - -1. **Fork** 统一仓库到个人账号,clone 到本地。 -2. 在**个人仓库**新建分支,完成当天笔记/作业(建 `笔记/` 与 `作业/` 文件夹,内容 Markdown 格式)。 -3. 提交并推送到个人仓库对应分支,发起 **PR** 到统一仓库。 -4. **小组长/导师审核** → 审核通过后合并分支。 -5. **冲突解决**:在冲突分支执行 `git merge` / `git rebase` 同步最新远端,手动选择保留哪一侧修改后继续提交。 - -### 3.7 如何解决 Git 冲突 - -在多人协作中,冲突通常发生在以下场景: - -**冲突产生的原因(时间线示意)** - -- **t1 时刻**:`master`、`fork1`、`fork2` 都基于同一份代码,状态为 t1。 -- **t2 时刻**: - - `fork1` 修改后合并到 `master`,`master` 由 t1 更新为 t2。 - - `fork2` 仍保留 t1 时刻的内容。 - -此时 `fork2` 向 `master` 创建 Pull Request,会发现目标分支 `master` 已经更新到 t2,而 `fork2` 自己的提交仍然基于旧的 t1。由于两者的修改基础不同,就会产生冲突。 - -**解决冲突的步骤** - -1. **Fork 仓库后同步上游**:定期将上游(upstream)仓库的最新改动同步到自己的 Fork。 -2. 切换到产生冲突的分支,执行 `git merge` 或 `git rebase`,把远端的最新修改同步到当前分支: - - ```bash - git switch your-branch - git merge master # 或 git rebase master - ``` - -3. 根据提示手动解决冲突文件,选择保留旧时间线或新时间线的修改。 -4. 解决后继续提交并推送: - - ```bash - git add . - git commit -m "resolve conflict" - git push origin your-branch - ``` diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/rtthread-day3-notes.md" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/rtthread-day3-notes.md" deleted file mode 100644 index 083d4a074242610ad40ed737eb3e082e4b71687a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/rtthread-day3-notes.md" +++ /dev/null @@ -1,181 +0,0 @@ -# RT-Thread 线程间同步与通信 学习笔记 - -> 涵盖多线程协作中为什么需要同步、进入/退出临界区的四种方式、核心三件套(信号量 / 互斥量 / 事件集)、线程间通信(邮箱 / 消息队列),以及重点速记。 - ---- - -## 一、为什么需要线程间同步 - -多线程系统中,多个线程协作时,若不加保护地访问共享资源,会导致数据错乱或损坏。 - -- **临界区(Critical Section)**:被多个线程同时访问、必须通过互斥保护的代码段或资源。 -- **全局变量不能直接当作 IPC 媒介**:无法解决竞态条件(race condition)、缓存一致性、原子性问题。 - -**同步 vs 互斥** - -| 概念 | 含义 | 例子 | -| :-- | :-- | :-- | -| 同步 Synchronization | 线程按预定先后次序运行 | A 先采集,B 再处理 | -| 互斥 Mutual Exclusion | 任意时刻最多一个线程访问临界区 | 共享内存只能被一人写 | - -> 互斥可以看作一种特殊的同步。 - ---- - -## 二、进入/退出临界区的四种方式(从重到轻) - -| 方式 | 说明 | 关键 API | -| :-- | :-- | :-- | -| 1. 关闭全局中断 | 最重,屏蔽所有中断,仅适合**极短**临界区 | `rt_base_t level = rt_hw_interrupt_disable(); ... rt_hw_interrupt_enable(level);` | -| 2. 调度器锁 | 较重,阻止线程调度但中断仍可响应;**可嵌套**(嵌套深度用 `rt_critical_level()` 查询) | `rt_enter_critical()` / `rt_exit_critical()` | -| 3. 信号量 Semaphore | 轻量级内核对象,见第三节 | `rt_sem_take()` / `rt_sem_release()` | -| 4. 互斥量 Mutex | 特殊的二值信号量,解决优先级反转,见第三节 | `rt_mutex_take()` / `rt_mutex_release()` | - -> 方式 1、2 属于**底层(硬件 / 调度器级)**的临界区保护:屏蔽或暂停的是"执行权"本身,系统基本不为线程服务。方式 3、4 属于**软件级(内核 IPC 对象)**的资源保护:线程只是被挂起等待,系统仍在正常调度运行。前者最"重"、开销与风险最大,后者更轻量、可控,是日常多用的方案。 - ---- - -## 三、核心三件套:信号量 / 互斥量 / 事件集 - -### 3.1 信号量(Semaphore) - -- **本质**:基于计数器的同步机制,每个对象有一个计数值 `value` 和一个线程等待队列。 -- **特性** - - 计数值范围:**0 ~ 65535** - - 支持阻塞 / 非阻塞获取 - - 等待模式:`RT_IPC_FLAG_FIFO`(先进先出)、`RT_IPC_FLAG_PRIO`(按优先级) - - **允许在中断上下文释放**(与互斥量的关键区别) -- **停车场模型** - - `value = N` → 有 N 个空位,线程可进入 - - `value = 0` → 已满,新线程挂起到等待队列 - - 线程离开(释放)→ `value++`,唤醒等待队列中的一个线程 -- **核心 API** - - ```c - rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag); - - rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time); - /* time: RT_WAITING_FOREVER 永久阻塞 / RT_WAITING_NO 非阻塞立即返回 / N 等待 N 个 tick */ - - rt_err_t rt_sem_release(rt_sem_t sem); - ``` - -- **四大应用场景** - 1. **线程同步** — 线程 A 完成工作后 `release`,线程 B `take` 等待。 - 2. **锁** — 二值信号量(`value=0/1`)保护临界区(不推荐,优先用互斥量)。 - 3. **中断与线程同步** — ISR 中 `rt_sem_release()`,线程中 `rt_sem_take()`。 - 4. **资源计数** — `value=N` 表示 N 个同类资源可用。 - -### 3.2 互斥量(Mutex)—— 解决优先级反转 - -- **本质**:一种特殊的二值信号量,但有三个关键差异: - -| 特性 | 信号量 | 互斥量 | -| :-- | :-- | :-- | -| 计数值 | 0 ~ 65535 | 仅 0 或 1 | -| 释放者 | 任何线程都可释放 | 必须由持有线程释放 | -| 递归获取 | 不支持(会死锁) | 支持(持有线程可重复获取) | -| 优先级反转 | 存在此问题 | 有优先级继承机制解决 | - -- **优先级继承原理** - 1. 低优先级线程 A 持有锁。 - 2. 高优先级线程 C 请求锁 → 被挂起。 - 3. 系统临时将 A 的优先级提升到 C 的级别 → 防止中优先级线程 B 抢占 A。 - 4. A 尽快执行完临界区并释放锁 → 恢复原始优先级,C 获得锁。 -- **核心 API** - - ```c - rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag); - - rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time); - - rt_err_t rt_mutex_release(rt_mutex_t mutex); /* 必须由持有者调用 */ - ``` - -- **使用原则**:保护临界区、防止优先级反转 → 用互斥量;资源计数、ISR 通知线程 → 用信号量。 - -### 3.3 事件集(Event) - -- **本质**:一个 32 位无符号整数,每一位代表一个事件,可实现**一对多、多对多**的同步。 -- **特性** - - 一个线程可以等"任意一个事件"(逻辑**或**)或"全部事件都到达"(逻辑**与**)。 - - 一个事件可以唤醒多个等待线程。 - - 适合多事件组合触发场景。 -- **核心 API** - - ```c - rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set); - - rt_err_t rt_event_recv(rt_event_t event, - rt_uint32_t set, - rt_uint8_t option, - rt_int32_t timeout, - rt_uint32_t *recved); - /* option: RT_EVENT_FLAG_AND / RT_EVENT_FLAG_OR / RT_EVENT_FLAG_CLEAR */ - ``` - -- **示例(多事件组合)** - - 线程 3:每计数 10 次发送信号量。 - - 线程 4:收到信号量后对 `number+1`,加到 10 后亮蓝灯并发送**事件 5**。 - - 线程 5:用 `rt_event_recv()` 同时等**事件 3 和事件 5 都到达**,才报告 `"all over"`。 - ---- - -## 四、延伸:线程间通信(传数据) - -### 4.1 邮箱(Mailbox) - -- 专为传递小规模数据(通常是一个 `unsigned long`)设计。 -- 内部由**环形缓冲区 + 等待链表 + 超时定时器**实现。 -- **容量由 `rt_mb_create(name, size, flag)` 的 `size` 参数指定**(即邮箱可容纳的邮件封数),并非固定 5 个;每封邮件固定为 4 字节(一个 `unsigned long`)。 -- 例:`rt_mb_create("mbox", 5, ...)` 创建 5 封容量的邮箱,也可设为 32 等任意值,看应用需要。 - -### 4.2 消息队列(Message Queue) - -- 由固定数量的**消息块**组成,每块存固定长度数据。 -- 维护两个独立等待链表:`Send List`(等写入)和 `Receive List`(等读取)。 -- 写操作:写入**队尾**;读操作:从**队首**读取。 -- 多线程竞争时支持 **FIFO 或 PRIO** 策略。 -- 唤醒机制:写入时唤醒 `Receive List` 队首线程;读出时唤醒 `Send List` 队首线程。 - ---- - -## 五、重点速记 - -**同步 vs 互斥** - -> 同步 = 控制执行顺序;互斥 = 临界区排他访问(互斥是一种特殊同步)。 - -**三件套选择** - -| 机制 | 适用场景 | -| :-- | :-- | -| 信号量 | 资源计数 / 线程同步 / ISR→线程通知 | -| 互斥量 | 保护临界区 / 防优先级反转(支持递归、仅持有者释放) | -| 事件集 | 一对多、多对多同步(32 位事件位图,AND/OR 逻辑) | - -**关键数字** - -| 项 | 值 | -| :-- | :-- | -| 信号量计数值范围 | 0 ~ 65535 | -| 事件集 | 32 位,每位一个事件 | -| 邮箱 | 每封 4 字节(`unsigned long`);容量由 `rt_mb_create` 的 `size` 参数指定(非固定 5) | - -**等待模式** - -- `RT_IPC_FLAG_FIFO` → 先进先出 -- `RT_IPC_FLAG_PRIO` → 高优先级线程先获取 - -**超时参数** - -- `RT_WAITING_FOREVER` → 永久阻塞 -- `RT_WAITING_NO` → 非阻塞立即返回 -- `N` → 等待 N 个 tick - -**易错点** - -1. 信号量可由**任何线程**释放,互斥量只能由**持有线程**释放。 -2. 互斥量支持**递归获取**,信号量不支持(递归获取会死锁)。 -3. 信号量可在**中断中释放**,互斥量**不能**。 -4. 全局变量**不能**直接用于 IPC——无法解决竞态条件。 diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260/rtthread-day2-notes 1.md" "b/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260/rtthread-day2-notes 1.md" deleted file mode 100644 index b741cc45ea6db9f8549e8c6a76877cf95640e275..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\345\276\267\346\254\242/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260/rtthread-day2-notes 1.md" +++ /dev/null @@ -1,302 +0,0 @@ - - -## 一、裸机开发模式及其缺陷 - -- **前后台模式** - - **后台**:主循环(如 `while(1)`)按顺序执行任务(读取 ADC、刷新 LCD 等)。 - - **前台**:中断处理(如 SPI、串口接收中断),打断后台执行。 -- **主要缺陷** - - **并发性**:缺乏真正的并发处理能力,阻塞操作会卡住整个系统。 - - **实时性**:后台任务阻塞(如 `delay`)会严重影响系统响应。 - - **可维护性**:代码与硬件强绑定,移植困难。 - - **可重用性**:模块化程度低,重复造轮子。 - ---- - -## 二、RTOS 引入及线程概念 - -- **引入目的**:通过线程(任务)机制解决裸机缺陷。 -- **核心思想**:**分而治之**——将复杂任务拆分为多个独立线程,实现并发执行。 -- **示例**:ADC 读取、SPI 通信等可拆分为独立线程,避免相互阻塞。 - ---- - -## 三、RTOS 启动流程 - -1. **芯片上电**:执行启动文件(如 `startup.s`)中的 `Reset_Handler`。 -2. **底层初始化**:清 BSS 段、初始化 SRAM 等芯片级操作。 -3. **工具链层**: - - 关闭硬件总中断,保证启动稳定。 - - 调用入口函数(如 `entry`),完成系统初始化。 -4. **关键初始化步骤**: - - 初始化 UART(用于终端打印)。 - - 初始化定时器(提供延时计时)。 - - 初始化调度器(管理线程运行)。 - - 可选:信号初始化。 - - 应用线程初始化。 -5. **自动初始化**:RT-Thread 通过自动初始化机制(`INIT_*_EXPORT` 宏 + 初始化函数表,由 `rt_components` 组件负责扫描执行)在 `main` 函数之前自动调用各组件的初始化函数,无需手动调用。 -6. **调度器启动**:系统开始按优先级调度线程。 - -> **自动初始化宏分级**(按执行先后):`INIT_BOARD_EXPORT` → `INIT_DEVICE_EXPORT` → `INIT_COMPONENT_EXPORT` → `INIT_ENV_EXPORT` → `INIT_APP_EXPORT`。板级(board)最早执行,应用层(app)最后执行。 - ---- - -## 四、线程控制块(TCB) - -- **作用**:线程的"身份证"和"档案袋",记录线程所有关键信息。 -- **关键成员**: - - `sp`(栈指针):指向线程独立栈空间,记录栈大小和起始地址。 - - `entry`(入口函数):线程执行的起点。 - - `parameter`:传递给入口函数的参数。 - - 错误代码、运行状态、优先级、退出钩子函数、定时器等。 -- **面向对象思想**:用 C 语言模拟继承,`rt_thread` 结构体嵌套 `rt_object` 基类,提供统一属性(name、type、flag 等)。 - ---- - -## 五、线程栈与上下文切换 - -- **独立栈空间**:每个线程拥有自己的栈,用于保存局部变量和函数调用。 -- **栈溢出**:RTOS 中特指线程栈溢出,需注意栈大小分配(栈太小会越界破坏其他内存)。 -- **上下文切换**: - - 切换时保存当前线程现场(寄存器、程序计数器等)到自身栈中。 - - 恢复下一个线程的上下文,实现无缝接续执行。 - ---- - -## 六、线程状态及生命周期 - -- **五种状态**: - 1. **初始态**:线程刚被创建,未启动。 - 2. **就绪态**:等待 CPU 资源。 - 3. **运行态**:正在占用 CPU 执行。 - 4. **挂起态**:因延时、信号量等待等暂时让出 CPU。 - 5. **关闭态**:线程结束,等待资源回收。 -- **状态转换**: - - 创建 → 初始态 → 调用 `rt_thread_startup()` → 就绪态 → 调度运行 → 运行态。 - - 运行态可通过 `delay`、信号量获取等进入挂起态,挂起结束后恢复就绪态。 - - 线程退出后进入关闭态,由 **idle 线程** 回收资源。 -- **idle 线程(空闲线程)**: - - 最低优先级线程,系统"兜底"线程。默认 256 级优先级时为 **255**,裁剪为 32 级时对应 **31**;该优先级固定保留,**用户线程不可使用**。 - - 永远处于就绪态,仅当无其他就绪线程时运行。 - - 负责资源回收(清理关闭态线程)、低功耗管理、喂狗等。 - ---- - -## 七、线程调度策略 - -- **抢占式优先级调度** - - 优先级数字越小等级越高:**0 为最高,255 为最低**。 - - 高优先级线程一旦就绪,立即抢占低优先级线程。 - - 典型默认优先级:main 线程(如 10)、timer 线程(如 4)、idle 线程(最低,固定 255)。 -- **时间片轮转** - - 仅适用于**同优先级**线程。 - - 每个线程分配时间片(如 10ms),超时后让出 CPU 给同优先级下一个线程,实现公平分时。 -- **调度触发** - - 调度器启动后,从就绪列表选取最高优先级线程运行。 - - 实际线程切换由 **PendSV 异常**完成(在中断退出时触发,保证不会打断其他中断)。 - -### 7.1 优先级与时间片数值详解 - -- **优先级范围**:系统支持 0~255 共 256 个优先级级别,**0 最高、255 最低**;可通过 `rtconfig.h` 裁剪为 **8 级或 32 级**,以节省资源,配置灵活。 -- **idle 独占最低优先级**:最低优先级固定保留给 idle 线程,用户线程不可占用。 -- **抢占规则**:高优先级就绪即抢占。例:任务 A 优先级 0、任务 B 优先级 10,若 A 就绪,B 会被立刻换出;只有 A 执行完且无更高优先级任务时,B 才能继续。 -- **时间片单位 tick**:1 tick 默认 **10ms**(可在 `rtconfig.h` 改为 1ms 等)。 -- **同优先级轮转**:优先级相同的线程按各自时间片轮转。例:线程 A、B 优先级均为 10,A 时间片 20 tick、B 时间片 100 tick,则 CPU 按 `A→B→A→B…` 轮流分配。时间片设置过小会导致**频繁切换**反而降低效率,需权衡。 - ---- - -## 八、线程创建方式 - -RT-Thread 提供两种创建方式,本质都是分配"线程控制块 + 栈空间",区别仅在内存来源。 - -- **静态创建**(`rt_thread_init`): - - 预先分配线程控制块和栈空间(如全局数组)。 - - 优点:内存分配确定、无碎片,适合资源受限或高安全场景(如军工)。 -- **动态创建**(`rt_thread_create`): - - 从系统堆中动态申请控制块和栈。 - - 优点:灵活,但存在内存碎片风险与不确定性。 -- **创建后**:线程处于初始态,必须调用 `rt_thread_startup()` 进入就绪态。 - -### 8.1 代码示例 - -动态创建(从堆分配): - -```c -#include - -static void thread_entry(void *parameter) -{ - rt_uint32_t count = 0; - while (1) - { - rt_kprintf("dynamic thread count: %d\n", count++); - rt_thread_mdelay(500); /* 延时 500ms,单位毫秒 */ - } -} - -int main(void) -{ - rt_thread_t tid; - - tid = rt_thread_create("dynamic", /* 线程名称(用于调试显示) */ - thread_entry, /* 线程入口函数 */ - RT_NULL, /* 入口参数 */ - 512, /* 线程栈大小(字节) */ - 10, /* 优先级:数字越小越高 */ - 10); /* 时间片(单位 tick) */ - - if (tid != RT_NULL) - rt_thread_startup(tid); /* 启动线程,进入就绪态 */ - else - rt_kprintf("create thread failed!\n"); - - return 0; -} -``` - -静态创建(使用预先分配的数组): - -```c -#include - -#define THREAD_STACK_SIZE 512 -#define THREAD_PRIORITY 10 -#define THREAD_TIMESLICE 10 - -static struct rt_thread static_thread; /* 预先分配的控制块 */ -static rt_uint8_t static_thread_stack[THREAD_STACK_SIZE]; /* 预先分配的栈 */ - -static void thread_entry(void *parameter) -{ - rt_uint32_t count = 0; - while (1) - { - rt_kprintf("static thread count: %d\n", count++); - rt_thread_mdelay(500); - } -} - -int main(void) -{ - rt_err_t result; - - result = rt_thread_init(&static_thread, /* 线程控制块 */ - "static", /* 线程名称 */ - thread_entry, /* 入口函数 */ - RT_NULL, /* 入口参数 */ - static_thread_stack, /* 栈起始地址 */ - THREAD_STACK_SIZE, /* 栈大小 */ - THREAD_PRIORITY, /* 优先级 */ - THREAD_TIMESLICE); /* 时间片 */ - - if (result == RT_EOK) - rt_thread_startup(&static_thread); - else - rt_kprintf("init thread failed!\n"); - - return 0; -} -``` - -> 注意:静态方式要求栈数组为**全局/静态**变量(生命周期与线程一致);动态方式需保证系统堆(`RT_HEAP_SIZE`)足够。 - -### 8.2 常用线程管理 API - -| API | 作用 | -| :-- | :-- | -| `rt_thread_create()` | 动态创建线程(从堆分配) | -| `rt_thread_init()` | 静态初始化线程(使用指定内存) | -| `rt_thread_startup()` | 启动线程,使其进入就绪态 | -| `rt_thread_delete()` | 删除动态线程(释放堆内存) | -| `rt_thread_detach()` | 脱离静态线程(不释放内存) | -| `rt_thread_yield()` | 主动让出 CPU,切换到同优先级其他线程 | -| `rt_thread_mdelay(ms)` | 毫秒级延时(进入挂起态) | -| `rt_thread_sleep(tick)` | tick 级延时 | -| `rt_thread_self()` | 获取当前线程句柄 | -| `rt_thread_control()` | 控制线程(如修改优先级、挂起/恢复) | -| `rt_thread_suspend()` / `rt_thread_resume()` | 挂起 / 恢复线程 | - ---- - -## 九、SCons 构建系统 - -- **简介**:基于 Python 的构建工具,用于编译 RT-Thread 项目,替代繁琐的 Makefile。 -- **核心文件** - - `SConstruct`:顶层构建脚本。 - - `SConscript`:分散在各源码目录,控制当前目录的构建行为。 -- **常用命令** - - `scons`:编译项目。 - - `scons -jN`:多线程加速(如 `-j20`)。 - - `scons -c`:清理编译产物。 - - `scons --target=mdk` / `--target=cmake`:生成 IDE 工程文件。 -- **关键语法** - - `glob`:匹配源文件。 - - `DefineGroup`:添加文件分组,精细控制参与编译的文件。 - - `depend`:依赖宏控制脚本是否生效(宏需在 `rtconfig.h` 中启用)。 - - `rtconfig.h`:全局配置,由 Kconfig 生成,集中管理所有宏。 - -### 9.1 实战避坑指南 - -- **工具链环境变量 `RTT_EXEC_PATH`**:SCons 通过该变量定位编译器。使用非 ARM 平台或自定义编译器时需手动指定: - - Windows(CMD):`set RTT_EXEC_PATH=编译器路径` 后执行 `scons`。 - - Linux/macOS(bash):`export RTT_EXEC_PATH=编译器路径`。 - - 未正确设置会报"找不到编译器"等错误。 -- **`rtconfig.py` 切换编译器**:编译器和参数统一在此配置,需按工具链(GCC / Keil AC5/AC6 / IAR / LLVM)设置 `ARCH`(架构)、`CPU`(指令集)、`FPU`(浮点类型)等,否则编译结果不正确。 -- **修改配置后先清理再编译**:改了 `rtconfig.h` 或 `SConscript` 后若出现"奇怪的错误"或配置未生效,先 `scons -c` 清理旧产物再 `scons` 重编,通常能解决。 -- **生成 IDE 工程**:`--target=mdk` 生成 Keil 工程;`--target=cmake` 生成 CMake 脚本可导入 VS Code / VS / CLion;生成 IAR 工程需本机已装 IAR。 -- **跨平台路径**:Windows 用 `\`、Linux/macOS 用 `/`;在 `SConscript` 中建议用 Python `os.path` 处理路径。Linux 下注意脚本可执行权限。 - ---- - -## 十、开发环境与实践要点 - -- **RT-Thread Studio**:集成开发环境,支持图形化配置、软件包管理。 -- **工程配置**:通过 `rtconfig.h` 和 `menuconfig` 调整功能。 -- **软件包**:云端拉取,如 `Kernel Simple`,丰富外设驱动和组件。 -- **调试技巧**: - - 使用 `list_thread` 或 `ps` 命令查看线程状态。 - - Tab 键补全命令。 - - 注意下载器(ST-Link / J-Link)和串口的匹配。 - -### 10.1 GPIO 设备模型与 MSH 命令导出(LED 实战示例) - -翻转 LED 本质是操作引脚设备。RT-Thread 提供统一的 PIN 设备接口,无需直接操作寄存器: - -```c -#include -#include -#include - -/* 以 STM32 为例,PC13 接 LED;请按开发板原理图替换为实际引脚编号 */ -#define RED_LED_PIN GET_PIN(C, 13) - -/* 动态线程:翻转红色 LED(对应作业"优先级 16") */ -static void red_led_entry(void *parameter) -{ - rt_pin_mode(RED_LED_PIN, PIN_MODE_OUTPUT); /* 配置为推挽输出 */ - while (1) - { - rt_pin_write(RED_LED_PIN, PIN_HIGH); /* 点亮 */ - rt_thread_mdelay(500); - rt_pin_write(RED_LED_PIN, PIN_LOW); /* 熄灭 */ - rt_thread_mdelay(500); - } -} - -int led_sample(void) -{ - rt_thread_t tid = rt_thread_create("red_led", red_led_entry, - RT_NULL, 512, 16, 10); - if (tid != RT_NULL) - rt_thread_startup(tid); - return 0; -} - -/* 导出为 MSH 命令:在终端输入 led_sample 即可运行 */ -MSH_CMD_EXPORT(led_sample, toggle red led by thread); -``` - -> - 静态线程(作业中的"蓝色 LED")只需把上例的 `rt_thread_create` 换成 `rt_thread_init` + 预分配栈即可。 -> - `GET_PIN(port, pin)` 是 RT-Thread 的引脚编号宏;`PIN_HIGH`/`PIN_LOW`/`PIN_MODE_OUTPUT` 为设备框架定义的常量。 -> - 除 MSH 导出外,也可通过 `INIT_APP_EXPORT(led_sample)` 让函数在开机时自动执行。 - diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111304.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111304.png" deleted file mode 100644 index 2ee4f5491ec36801110dd4a5dec5fd1979106ae0..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111304.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111446.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111446.png" deleted file mode 100644 index a0909eb8a504cc4d6ee2a51d9c0d2c86d2c33d7e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111446.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111541.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111541.png" deleted file mode 100644 index 57eb76c93051aced60614b1423ef732f411e2fa2..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111541.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205041.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205041.png" deleted file mode 100644 index d5f981fb365f89a9495de53483cdea6c1f5796b7..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205041.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205747.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205747.png" deleted file mode 100644 index e2651a1f72c00ba3fb9cbe0ee2b5b6a60bf8c18a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205747.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 144345.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 144345.png" deleted file mode 100644 index d798bbcbb27e5506b08e25ffd90c0c804e4f0192..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 144345.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145057.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145057.png" deleted file mode 100644 index c74941ee7f8230fa1e38834c55874f3df511970c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145057.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145842.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145842.png" deleted file mode 100644 index 37acb53fc35bd2b47574089f7d593a1432159485..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145842.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145859.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145859.png" deleted file mode 100644 index 3cbd8d6ce22ef117d2f962cf78620623474c66de..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145859.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151021.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151021.png" deleted file mode 100644 index ed90a5467a7545180cc329d2c9833a667499526c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151021.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151614.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151614.png" deleted file mode 100644 index 7ff03f0da5bbbbe8580b8397d3e3594509bd23db..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151614.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\276\256\344\277\241\345\233\276\347\211\207_20260817213103_622_46.jpg" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\276\256\344\277\241\345\233\276\347\211\207_20260817213103_622_46.jpg" deleted file mode 100644 index 30e8a04c257f783e72b8de2d8eaf3c06ec9cb5df..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/figures/\345\276\256\344\277\241\345\233\276\347\211\207_20260817213103_622_46.jpg" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2541\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2541\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index cf6877ee1cafc27d750007741495f0b71dc8b7ff..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2541\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,121 +0,0 @@ -### 任务1:新建 QEMU-VExpress-A9 工程并运行 RT-Thread - -新建项目:文件 → 新建 → RT-Thread 项目 - - -项目配置:选择**基于开发板**创建,开发板选 **QEMU-VExpress-A9**,调试器选 **QEMU**,命名后创建模板工程。 - -编译:点击编译按钮,调用 gcc 工具链,生成 `rtthread.elf` 与 `rtthread.bin`。 - -烧录运行:点击烧录后自动打开终端,打印 RT-Thread logo 并启动示例程序。 - - -### 任务2:Markdown 语法学习 - -| 语法 | 写法 | 说明 | -| ------ | ----------------------------------- | ---------------------- | -| 标题 | `#` ~ `######` | 一级到六级标题 | -| 代码块 | ` ```c ... ``` ` | 反引号围栏,标注语言名 | -| 表格 | `\| a \| b \|` + `\| --- \| --- \|` | 第二行为对齐分隔行 | -| 加粗 | `**文字**` | 强调 | -| 图片 | `![描述](路径)` | 建议用相对路径 | - -> 图片统一保存至 `./figures` 目录并以相对路径引用,避免项目迁移后资源丢失。 - -### 任务3:基于RT-Thread 4.1.1版本新建stm32f407zg芯片工程 - -1. 基于芯片创建工程,选择 **STM32F407ZG**,内核选 **标准版**,RT-Thread版本号为**4.1.1**,串口默认为uart1。 - - - -2. 修改代码并编译工程,生成 `.elf` 与 `.bin` 文件。 - - ```c - int main(void) - { - int count = 1; - - rt_kprintf("hello RSOC2026!\n"); - while (count++) - { - //LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - - return RT_EOK; - } - ``` - -3. 通过 **ST-Link** 下载至开发板。 - -4. 打开终端查看启动日志,验证程序正常运行;可使用断点调试定位问题。 - - ![](./figures/屏幕截图 2026-08-17 205747.png) - -### 任务4:Git 使用 - -#### 注册gitee并创建一个仓库 - - - -#### 配置身份并生成公钥(公钥填入 Gitee): - -```bash -git config --global user.name "***" -git config --global user.email ***@**.com -ssh-keygen -t rsa -b 4096 -C "your.email@example.com" -``` -#### 拉取仓库到本地 -![](./figures/屏幕截图 2026-08-18 145057.png) - -基本工作流: - -```bash -git init # 初始化本地仓库 -git add # 将文件加入暂存区 -git commit -m "message" # 提交,附提交说明 -git push origin master # 推送:本地 → 远端 -git pull origin master # 拉取:远端 → 本地 -``` - -#### 修改本地文件,并推送到远端: -![](./figures/屏幕截图 2026-08-18 145859.png) - -仓库文件被修改: -![](./figures/屏幕截图 2026-08-18 145842.png) - -#### 分支管理操作 -```bash -1. 创建并切换到功能分支 -git checkout -b feature-login - -2. 在 feature-login 分支上修改代码,然后提交 -git add . -git commit -m "添加登录功能" - -3. 推送功能分支到远程(首次) -git push -u origin feature-login - -4. 切回主分支准备合并 -git checkout main - -5. 拉取远程最新代码(防止别人有更新) -git pull - -6. 合并功能分支到主分支 -git merge feature-login - -7. 推送更新后的主分支到远程 -git push - -8. 删除本地功能分支(已合并,安全删除) -git branch -d feature-login - -9. 删除远程功能分支 -git push origin --delete feature-login -``` -创建分支,修改并推送: -![](./figures/屏幕截图 2026-08-18 151021.png) - -gitee仓库对应修改: - \ No newline at end of file diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 7c9b7687cadc01826f77f42ec49f5037fef814d7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,306 +0,0 @@ -# RT-Thread 启动流程、内核基础与多线程编程 - -- **目标平台**:STM32F407 星火号(`stm32f407-rtspark`) -- **软件环境**:RT-Thread 4.1.1(sdk-bsp-stm32f407-spark,GCC 工具链),`RT_TICK_PER_SECOND = 1000`(1 tick = 1 ms),`RT_THREAD_PRIORITY_MAX = 32` - ---- - -## Part 1:理论知识总结 - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -#### 1.1 从上电复位到 `main()` 的完整调用链 - -以 GCC 工具链为例,启动路径如下: - -``` -上电复位 - │ - ▼ -Reset_Handler // startup_stm32f407xx.s,取向量表 0 号中断入口 - ├── SystemInit() // 系统时钟/向量表设置(汇编入口调用) - │ - ▼ -__main // 编译器启动代码:分散加载(RW/ZI 段拷贝清零)、 - │ // 建立 C 运行环境 - ▼ -entry() // rt-thread/src/components.c,GCC 分支; - │ // 链接器通过 "-e entry" 将程序入口指向此处 - │ // (RT-Thread 接管了 main,而不是 main 调用它) - ▼ -rtthread_startup() // components.c,内核引导总入口 - ├── rt_hw_interrupt_disable() // 引导期间关闭中断 - ├── rt_hw_board_init() // libraries/HAL_Drivers/drv_common.c - │ ├── HAL_Init() // ST 库初始化 - │ ├── SystemClock_Config() // 配置 168MHz(HSE + PLL) - │ ├── rt_system_heap_init() // 系统堆初始化(动态内存起点) - │ ├── rt_hw_pin_init() // GPIO 设备驱动 - │ ├── rt_hw_usart_init() // 串口驱动 - │ ├── rt_console_set_device() // 控制台设备(日志输出) - │ └── rt_components_board_init() // 执行 .rti_fn.0 ~ .1 段:板级自动初始化 - │ (INIT_BOARD_EXPORT 等,见 1.2 节) - ├── rt_show_version() // 打印 RT-Thread 版本信息 - ├── rt_system_timer_init() // 系统节拍定时器(SysTick 中断,1ms) - ├── rt_application_init() // 创建 main 线程(动态,优先级 10、时间片 20) - ├── rt_thread_idle_init() // 创建 idle 线程(最低优先级 31) - └── rt_system_scheduler_start() // 启动调度器,不再返回(引导流程到此结束) - -调度器接管后: - main 线程入口 main_thread_entry() - ├── rt_components_init() // 执行 .rti_fn.2 ~ .6 段:组件/应用自动初始化 - │ (INIT_PREV/DEVICE/COMPONENT/ENV/APP) - └── main() // 用户 main 函数 -``` - -#### 1.2 自动初始化原理(`INIT_BOARD_EXPORT` / `INIT_APP_EXPORT`) - -自动初始化的底层是**编译器段(section)机制 + 链接脚本排序 + 符号边界遍历**,三步缺一不可: - -**① 宏展开:把函数指针放进指定段** - -```c -#define INIT_EXPORT(fn, level) \ - RT_USED const init_fn_t __rt_init_##fn RT_SECTION(".rti_fn." level) = fn - -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") -``` - -`INIT_BOARD_EXPORT(my_fn)` 等价于定义了一个放在 `.rti_fn.1` 段中的常量函数指针 -`__rt_init_my_fn = my_fn`。`RT_USED` 防止编译器"未使用"优化将其丢弃。 - -**② 链接脚本排序:各初始化级别按顺序排列** - -链接脚本对 `.rti_fn.*` 段做 `SORT_BY_NAME` 排序,并定义边界符号: - -``` -.rti_fn.0 ← __rt_init_rti_board_start(rti_start) -.rti_fn.0.end (rti_board_start) -.rti_fn.1 ← ★ INIT_BOARD_EXPORT 的入口在这里 -.rti_fn.1.end (rti_board_end = __rt_init_rti_board_end) -.rti_fn.2 ← INIT_PREV_EXPORT -.rti_fn.3 ← INIT_DEVICE_EXPORT -.rti_fn.4 ← INIT_COMPONENT_EXPORT -.rti_fn.5 ← INIT_ENV_EXPORT -.rti_fn.6 ← ★ INIT_APP_EXPORT 的入口在这里(.rti_fn.6.end = __rt_init_rti_end) -``` - -由于 `'0' < '0.end' < '1' < ... < '6' < '6.end'` 的字典序关系,**级别数字越小的函数指针在内存中地址越靠前**,遍历时的先后顺序由此天然保证。 - -**③ 遍历执行** - -```c -// 板级初始化:rt_hw_board_init() 末尾调用,覆盖级别 0~1 -for (fn_ptr = &__rt_init_rti_board_start; - fn_ptr < &__rt_init_rti_board_end; fn_ptr++) - (*fn_ptr)(); - -// 组件初始化:main 线程入口调用,覆盖级别 2~6 -for (fn_ptr = &__rt_init_rti_board_end; - fn_ptr < &__rt_init_rti_end; fn_ptr++) - (*fn_ptr)(); -``` - -**级别划分的意义**: - -| 宏 | 级别 | 执行时机 | 典型用途 | -| --- | --- | --- | --- | -| `INIT_BOARD_EXPORT` | 1 | main 线程创建前 | 板级外设(LED/按键/屏)引脚初始化 | -| `INIT_PREV_EXPORT` | 2 | main 线程上下文 | 纯软件预初始化 | -| `INIT_DEVICE_EXPORT` | 3 | main 线程上下文 | 设备驱动注册 | -| `INIT_COMPONENT_EXPORT` | 4 | main 线程上下文 | 组件初始化(DFS/LWIP 等) | -| `INIT_ENV_EXPORT` | 5 | main 线程上下文 | 文件系统挂载等环境初始化 | -| `INIT_APP_EXPORT` | 6 | main 线程上下文 | 用户应用初始化(本作业线程创建) | - -**本作业的用法**:`thread_study.c` 用 `INIT_APP_EXPORT(thread_study_init)` 注册线程创建函数,因此**无需修改 main.c**,启动时自动创建三个线程。 - -### 2. 内核基础与对象模型 - -#### 2.1 面向对象设计:内核对象与对象容器 - -RT-Thread 用 C 语言模拟面向对象设计:**所有内核对象(线程、信号量、互斥量、事件、消息队列、定时器…)都继承自基类 `struct rt_object`**——基类作为结构体**第一个成员**,派生结构体可以直接把首地址强转成 `rt_object_t` 使用: - -```c -struct rt_object // 基类:所有内核对象共用 -{ - char name[RT_NAME_MAX]; // 对象名 - rt_uint8_t type; // 对象类型(+静态/动态标志位) - rt_uint8_t flag; - rt_list_t list; // 挂到对象容器链表的节点 -}; - -struct rt_thread // 派生:线程对象 -{ - struct rt_object parent; // ★ 基类必须是第一个成员 - ... -}; -``` - -**对象容器**:内核维护一张按类型分组的对象信息表 `_object_container[]`,每类对象一条链表。`rt_object_init()` 将对象挂入对应链表,`rt_object_find()` 按名字查找对象。`thread_dump` 命令正是遍历线程容器打印全部线程信息的。 - -**动态与静态对象的容器挂载**:动态创建(`rt_thread_create`)与静态初始化(`rt_thread_init`)都会把线程对象注册进容器,因此两种线程都能被 `ps` / `thread_dump` 查看到。 - -#### 2.2 线程控制块 `struct rt_thread` 关键成员 - -| 成员 | 作用 | -| --- | --- | -| `stat` | 线程状态(低 3 位为五态,高 5 位为信号/标志位),实验中被 monitor 读取用于判断是否已挂起 | -| `current_priority` / `init_priority` | 当前优先级 / 初始优先级。优先级继承等场景会临时改变当前优先级 | -| `init_tick` | 时间片初值。运行时每个 SysTick 递减,减到 0 由调度器重新排队(时间片轮转) | -| `sp` | 栈指针。**上下文保存位置**:切换线程时把寄存器组压栈并记录 SP,恢复时由 SP 弹栈 | -| `stack_addr` / `stack_size` | 栈底地址与大小。剩余栈 = `stack_size + stack_addr - sp`(与 `ps` 命令同算法) | -| `entry` / `parameter` | 线程入口函数与参数 | -| `tlist` | 链表节点。就绪时挂在就绪队列,挂起时挂在挂起(延时/IPC)队列 | -| `number` / `number_mask` / `high_mask` | 优先级位图索引(调度算法用,见 3.2 节) | - -#### 2.3 内存分配方式:静态对象 vs 动态对象 - -| 对比项 | 静态对象(`rt_thread_init`) | 动态对象(`rt_thread_create`) | -| --- | --- | --- | -| 分配位置 | 编译期确定,TCB 与栈是全局变量,位于 `.bss` 段 | 运行期从系统堆(`rt_malloc`)分配 | -| 分配时机 | 链接时已占位,无额外开销 | 运行时申请,需先初始化堆 | -| 生命周期 | 程序运行期间一直存在 | 由代码显式控制 | -| 释放机制 | `rt_thread_detach()`(脱离,回归 INIT 态) | `rt_thread_delete()`(销毁并释放堆内存) | -| 分配失败风险 | **无**(内存编译期已预留) | 堆耗尽时创建失败(返回 `RT_NULL`) | -| 内存管理开销 | 无 | 堆管理有内部碎片与链表开销 | -| 可用性 | 不依赖堆,可在堆未初始化前使用 | 必须 `RT_USING_HEAP` 且堆已初始化 | - -**为什么高可靠场景强制使用静态线程**: - -1. **确定性**:内存占用编译期可审计,链接地图(map 文件)直接可见,不存在"运行时突然分配失败"; -2. **无碎片**:长期运行的系统堆会碎片化,动态线程反复创建/删除可能导致堆耗尽或地址碎片,静态线程完全规避; -3. **无 OOM**:静态分配没有"堆耗尽"路径,配合看门狗可保证线程必然存在; -4. **安全审计**:航空/车载/工控等认证场景要求内存布局可静态分析,动态分配(尤其是多线程竞争 malloc)难以通过安全认证。 - -本作业刻意同时实现了两种方式:`th_led1`(动态)与 `th_led2`(静态),便于对照。 - -### 3. 线程状态机与调度算法 - -#### 3.1 五态模型 - -RT-Thread 线程有五种状态(`thread->stat` 低 3 位,`RT_THREAD_STAT_MASK = 0x07`): - -![](./figures/a07d40b9e2b6a65037585cf3cc6ad5cc.jpg) - -| 状态 | 含义 | 进入途径(典型 API) | -| --- | --- | --- | -| `INIT` (0x00) | 已创建/初始化,未启动 | `rt_thread_init()` / `rt_thread_create()` | -| `READY` (0x01) | 就绪,等待调度 | `rt_thread_startup()`、`rt_thread_resume()`、延时超时、IPC 等待被释放 | -| `RUNNING` (0x03) | 运行中(就绪队列中最高优先级被选中的线程) | 调度器切换 | -| `SUSPEND` (0x02) | 挂起:主动挂起、延时、等待 IPC | `rt_thread_suspend()`、`rt_thread_mdelay/delay()`、`rt_sem_take()`、`rt_mutex_take()`、`rt_mq_recv()` | -| `CLOSE` (0x04) | 关闭,等待删除 | `rt_thread_delete()` / `rt_thread_detach()` | - -#### 3.2 调度机制 - -**① 抢占式调度(优先级驱动)** - -RT-Thread 采用**固定优先级抢占**:就绪队列按 32 级优先级(`RT_THREAD_PRIORITY_MAX=32`)分组,内核用**优先级位图**(`rt_thread_ready_priority_group` + `number_mask`)实现 **O(1)** 查找——不论多少线程,找最高优先级就绪线程只需常数时间; -**任何时刻,只有就绪队列中优先级最高的线程在运行**。更高优先级线程一旦就绪(如 monitor 从 `mdelay` 醒来、IPC 释放唤醒等待者),立刻抢占低优先级线程; - - -**② 时间片轮转(同优先级)** - -当多个线程优先级相同时,通过**时间片**公平轮转:每个 SysTick(1ms)递减当前线程的 `init_tick`,减到 0 时调度器将该线程移到就绪队列尾部并切换下一个同优先级线程,即"每个线程每次最多连续运行 5ms"(`TH_TIMESLICE = 5`); - ---- - -## Part 2:多线程实战编程实验 - -### 2.1 线程设计总览 - -| 线程名 | 创建方式 | 优先级 | 时间片 | 栈大小 | 职责 | -| --- | --- | --- | --- | --- | --- | -| `th_led1` | 动态 `rt_thread_create` | 16 | 5 ticks | 1024 B | 翻转 LED_R(PF12),打印计数 + `rt_tick_get()` | -| `th_led2` | 静态 `rt_thread_init`(全局 TCB + 栈) | 16 | 5 ticks | 1024 B | 翻转 LED_B(PF11),打印计数 | -| `monitor` | 动态 `rt_thread_create` | 10 | 5 ticks | 1024 B | 每 2s 报告线程状态与剩余栈;KEY0 单击挂起/恢复 `th_led1` | - -**引脚**:LED_R = PF12、LED_B = PF11(**低电平点亮**);KEY0 = PC0(内部上拉,按下为低电平)。 - -**工程集成**:将 `thread_study.c` 放入工程的 `applications/` 目录并加入构建(RT-Thread Studio 自动收集,或手动加入 scons 的 SConscript)。**不修改 main.c**——三个线程全部由 `INIT_APP_EXPORT(thread_study_init)` 在系统启动时自动创建,验证了自动初始化机制。 - -### 2.2 关键设计决策 - -1. **挂起/恢复的实现(重要)**:本 SDK 定制内核中 `rt_thread_suspend()` 带有 - `RT_ASSERT(thread == rt_thread_self())`,**只允许挂起当前线程**,且挂起后**不会主动触发调度**。 - 因此"按键挂起 `th_led1`"不能由 monitor 直接调用 `rt_thread_suspend(th_led1)`(会触发断言)。 - 故采用**请求标志 + 线程自挂**方案: - - monitor 按键后置位 `led1_suspend_req` 标志; - - `th_led1` 循环顶部自检标志,置位则执行 `rt_thread_suspend(rt_thread_self())` 后调用 `rt_schedule()` 让出 CPU; - - 恢复直接使用 `rt_thread_resume(th_led1)`(该接口无"只能恢复自己"限制,成功返回 `RT_EOK`)。 -2. **`th_led1` 用忙等待代替 `rt_thread_mdelay()`**:`rt_thread_suspend` 要求线程处于 READY/RUNNING 态,而 `mdelay` 期间线程本身已是 SUSPEND 态(无法再次挂起)。忙等待使 `th_led1` 保持 READY 态,挂起操作确定可执行;同时同优先级下忙等待能让时间片轮转现象清晰可见。`th_led2` 保留 `rt_thread_mdelay()` 阻塞延时,两种延时方式对照。 -3. **剩余栈空间计算**:本内核无 `RT_THREAD_CTRL_GET_STACK` 命令,采用与 FinSH `ps` 命令相同的公式 - `剩余栈 = stack_size + stack_addr - sp`,直接读取 TCB 成员计算。 -4. **按键响应**:monitor 以 10ms 周期轮询 KEY0 并做 50ms 去抖 + 等待释放处理,保证快速单击不漏检;2s 周期状态报告用 tick 计数实现(若用阻塞式 `rt_thread_mdelay(2000)`,按键最大响应延迟将达 2s)。 -5. **`thread_dump` 命令**:遍历线程对象容器(`rt_object_get_information(RT_Object_Class_Thread)`),打印所有线程的名字、状态、优先级、剩余栈与栈大小,由 `MSH_CMD_EXPORT` 导出。 - -### 2.3 运行现象与串口日志 - -上电后两个 LED 交替闪烁,串口打印(节选): - -``` - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 19 2026 09:27:59 - 2006 - 2022 Copyright by RT-Thread team -[th_led2] count=0, tick=2 -[th_led1] count=0, tick=4 -[th_led1] count=1, tick=507 -[th_led2] count=1, tick=514 -[th_led1] count=2, tick=1009 -[th_led2] count=2, tick=1021 -[th_led1] count=3, tick=1511 -[th_led2] count=3, tick=1533 ----------- thread status @tick=2002 ---------- -[th_led1 ] stat=READY remain_stack= 104/1024 -[th_led2 ] stat=SUSPEND remain_stack= 176/1024 -[monitor ] stat=RUNNING remain_stack= 176/1024 - -``` - -按下 KEY0: - -``` -[monitor] KEY0: suspend request -> th_led1 -[th_led1] suspend self, tick=3025 -msh >[th_led2] count=6, tick=3052 -[th_led2] count=7, tick=3554 ----------- thread status @tick=4004 ---------- -[th_led1 ] stat=SUSPEND remain_stack= 128/1024 -[th_led2 ] stat=SUSPEND remain_stack= 176/1024 -[monitor ] stat=RUNNING remain_stack= 176/1024 -[th_led2] count=8, tick=4056 -[th_led2] count=9, tick=4558 -[th_led2] count=10, tick=5060 -[th_led2] count=11, tick=5562 - -``` - -LED_R 停止翻转(LED_B 继续闪烁)。再次按下 KEY0: - -``` -[monitor] KEY0: resume th_led1 -[th_led1] resumed, continue, tick=6733 -[th_led1] count=6, tick=6736 -[th_led2] count=14, tick=7071 -[th_led1] count=7, tick=7239 -[th_led2] count=15, tick=7578 -[th_led1] count=8, tick=7741 ----------- thread status @tick=8013 ---------- -[th_led1 ] stat=READY remain_stack= 96/1024 -[th_led2 ] stat=SUSPEND remain_stack= 176/1024 -[monitor ] stat=RUNNING remain_stack= 176/1024 - -``` - -LED_R 恢复翻转。 - - -### 2.4 实验结果分析 - -1. **自动初始化生效**:未修改 `main.c`,三个线程在系统启动后自动运行,证明 `INIT_APP_EXPORT` 的段遍历机制正确。 - -2. **抢占式调度验证**:monitor 优先级 10 高于工作线程 16,其报告输出总是准时出现在整 2s 边界,期间随时打断 `th_led1` 的忙等待——高优先级线程就绪立即抢占。 - -3. **时间片轮转验证**:`th_led1`(忙等待)与 `th_led2`(阻塞延时)同为优先级 16,日志中两条计数流交错推进。`th_led2` 每次醒来只能运行一个时间片(5ms)即被换下,而 `th_led1` 因忙等待占据大部分 CPU 时间,体现同优先级下"按时间片轮流执行"而非"谁就绪谁独享"。 - -4. **挂起/恢复验证**:KEY0 单击后 `th_led1` 由 RUNNING 变为 SUSPEND(`thread_dump` 可见),LED_R 停止闪烁、计数停止;再单击恢复,计数从断点继续。挂起期间 `th_led2` 不受影响,验证了线程间相互独立、互不阻塞。 diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/figures/a07d40b9e2b6a65037585cf3cc6ad5cc.jpg" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/figures/a07d40b9e2b6a65037585cf3cc6ad5cc.jpg" deleted file mode 100644 index 367882239d5c1a3337f61b36ce55740811733751..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/figures/a07d40b9e2b6a65037585cf3cc6ad5cc.jpg" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 1a68ab5f8f9e057631569459c9f74d992fd8ed0e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,276 +0,0 @@ -/** - * 【DAY2】多线程实战编程(STM32F407 星火号,RT-Thread 4.1.1) - * - * 功能: - * 1. 动态线程 th_led1 :优先级 16,时间片 5 ticks,栈 1024B - * 循环翻转 LED_R(PF12),每次打印计数与 rt_tick_get() - * 2. 静态线程 th_led2 :优先级 16,时间片 5 ticks,静态全局 TCB 与栈 - * 循环翻转 LED_B(PF11),每次打印计数 - * 3. 监控线程 monitor :优先级 10(高于工作线程),每 2s 报告 th_led1/th_led2 - * 运行状态与剩余栈空间;轮询按键 KEY0(PC0),单击挂起 - * th_led1,再次单击恢复 - * 4. 使用 INIT_APP_EXPORT() 自动加载本模块(禁止在 main.c 中硬编码调用) - * 5. 使用 MSH_CMD_EXPORT() 导出 thread_dump 查看命令 - * - * 设计说明: - * - 本 SDK 定制内核(RT-Thread 4.1.1)中 rt_thread_suspend() 带 - * RT_ASSERT(thread == rt_thread_self()),只允许挂起当前线程,且不会主动 - * 触发调度。因此"按键挂起 th_led1"采用【请求标志 + 线程自挂】方式实现: - * monitor 置位 led1_suspend_req,th_led1 在循环顶部自检后执行 - * rt_thread_suspend(rt_thread_self()) + rt_schedule();恢复仍使用 - * rt_thread_resume()(该接口无"只能恢复自己"的限制)。 - * - th_led1 采用 rt_tick_get() 忙等待代替 rt_thread_mdelay():线程保持 - * READY 态,既保证挂起操作确定性(mdelay 期间线程已是 SUSPEND 态, - * 无法被再次挂起),又便于观察同优先级时间片轮转;th_led2 使用 - * rt_thread_mdelay() 阻塞延时,两种方式对比演示。 - * - 剩余栈空间算法与 FinSH ps 命令一致:stack_size + stack_addr - sp。 - */ - -#include -#include -#include - -#define PIN_LED_R GET_PIN(F, 12) /* PF12: 板载红色 LED(低电平点亮) */ -#define PIN_LED_B GET_PIN(F, 11) /* PF11: 板载蓝色 LED(低电平点亮) */ -#define PIN_KEY0 GET_PIN(C, 0) /* PC0 : 板载按键 KEY0(上拉,按下为低) */ - -#define LED_ON 0 -#define LED_OFF 1 - -#define TH_LED1_PRIO 16 -#define TH_LED2_PRIO 16 -#define TH_MON_PRIO 10 -#define TH_TIMESLICE 5 -#define TH_STACK_SIZE 1024 - -#define LED_SWITCH_TICKS (RT_TICK_PER_SECOND / 2) -#define REPORT_INTERVAL (RT_TICK_PER_SECOND * 2) -/*--------------------------- 全局对象 ---------------------------*/ -static rt_thread_t th_led1 = RT_NULL; /* 动态线程句柄 */ -static rt_thread_t th_monitor = RT_NULL; /* 动态线程句柄 */ - - -static rt_uint8_t th_led2_stack[TH_STACK_SIZE]; -static struct rt_thread th_led2_tcb; - -/* 挂起请求标志:monitor 置位,th_led1 自检后执行自我挂起 */ -static volatile rt_uint8_t led1_suspend_req = 0; - -static struct rt_mutex print_mutex; - -static const char *thread_stat_str(rt_uint8_t stat) -{ - switch (stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: return "INIT"; - case RT_THREAD_READY: return "READY"; - case RT_THREAD_RUNNING: return "RUNNING"; - case RT_THREAD_SUSPEND: return "SUSPEND"; - case RT_THREAD_CLOSE: return "CLOSE"; - default: return "UNKNOWN"; - } -} - -/* 打印线程运行状态与剩余栈空间(算法与 FinSH ps 命令一致) */ -static void report_thread(rt_thread_t thread) -{ - rt_uint32_t remain = thread->stack_size + - (rt_uint32_t)thread->stack_addr - - (rt_uint32_t)thread->sp; - - rt_kprintf("[%-8s] stat=%-8s remain_stack=%4d/%d\n", - thread->name, - thread_stat_str(thread->stat), - remain, thread->stack_size); -} - -/*--------------------------- 线程 1:动态创建,翻转 LED_R ---------------------------*/ -static void led1_entry(void *param) -{ - rt_uint32_t count = 0; - rt_tick_t start; - - while (1) - { - /* 收到挂起请求:自我挂起(本内核仅允许挂起当前线程) */ - if (led1_suspend_req) - { - led1_suspend_req = 0; - rt_kprintf("[th_led1] suspend self, tick=%d\n", rt_tick_get()); - rt_thread_suspend(rt_thread_self()); - rt_schedule(); /* rt_thread_suspend 不会主动触发调度 */ - rt_kprintf("[th_led1] resumed, continue, tick=%d\n", rt_tick_get()); - continue; - } - - /* 翻转 LED_R(低电平点亮) */ - rt_pin_write(PIN_LED_R, (count & 0x1) ? LED_ON : LED_OFF); - rt_mutex_take(&print_mutex, RT_WAITING_FOREVER); - rt_kprintf("[th_led1] count=%d, tick=%d\n", count, rt_tick_get()); - rt_mutex_release(&print_mutex); - count++; - - /* 忙等待 200ms:线程保持 READY 态,便于挂起与时间片轮转观察 */ - start = rt_tick_get(); - while (rt_tick_get() - start < LED_SWITCH_TICKS) - { - } - } -} - -/*--------------------------- 线程 2:静态初始化,翻转 LED_B ---------------------------*/ -static void led2_entry(void *param) -{ - rt_uint32_t count = 0; - - while (1) - { - rt_pin_write(PIN_LED_B, (count & 0x1) ? LED_ON : LED_OFF); - rt_mutex_take(&print_mutex, RT_WAITING_FOREVER); - rt_kprintf("[th_led2] count=%d, tick=%d\n", count, rt_tick_get()); - rt_mutex_release(&print_mutex); - count++; - - /* 阻塞延时:延时期间线程进入 SUSPEND 态,超时后由定时器唤醒 */ - rt_thread_mdelay(LED_SWITCH_TICKS); - } -} - -/*--------------------------- 监控线程:周期报告 + 按键控制 ---------------------------*/ -static void monitor_entry(void *param) -{ - rt_tick_t last_report = 0; - rt_base_t level; - - while (1) - { - if (th_led1 == RT_NULL) /* 创建失败的保护 */ - { - rt_thread_mdelay(1000); - continue; - } - - /* ---- 按键检测:10ms 轮询 + 50ms 去抖,单击切换挂起/恢复 ---- */ - if (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - rt_thread_mdelay(50); - if (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - /* 等待按键释放,避免一次按压多次触发 */ - while (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - rt_thread_mdelay(10); - } - - level = rt_hw_interrupt_disable(); - if ((th_led1->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND) - { - /* 已挂起 → 恢复 */ - led1_suspend_req = 0; - rt_hw_interrupt_enable(level); - if (rt_thread_resume(th_led1) == RT_EOK) - rt_kprintf("[monitor] KEY0: resume th_led1\n"); - else - rt_kprintf("[monitor] KEY0: resume th_led1 failed\n"); - } - else if (!led1_suspend_req) - { - /* 运行中 → 发出挂起请求 */ - led1_suspend_req = 1; - rt_hw_interrupt_enable(level); - rt_kprintf("[monitor] KEY0: suspend request -> th_led1\n"); - } - else - { - rt_hw_interrupt_enable(level); - rt_kprintf("[monitor] KEY0: th_led1 is about to suspend, ignore\n"); - } - } - } - - /* ---- 周期报告:每 2s 打印 th_led1/th_led2 状态与剩余栈 ---- */ - if (rt_tick_get() - last_report >= REPORT_INTERVAL) - { - last_report = rt_tick_get(); - rt_kprintf("---------- thread status @tick=%d ----------\n", last_report); - report_thread(th_led1); - report_thread(&th_led2_tcb); - report_thread(th_monitor); - } - - rt_thread_mdelay(10); - } -} - - -static int thread_dump(void) -{ - struct rt_object_information *info; - struct rt_list_node *node; - struct rt_object *object; - struct rt_thread *thread; - rt_uint32_t remain; - - info = rt_object_get_information(RT_Object_Class_Thread); - if (info == RT_NULL) - { - return -RT_ERROR; - } - - rt_kprintf("%-8s %-10s %-5s %-8s %-8s\n", - "name", "stat", "prio", "remain", "stack"); - for (node = info->object_list.next; - node != &(info->object_list); - node = node->next) - { - object = rt_list_entry(node, struct rt_object, list); - thread = (struct rt_thread *)object; - remain = thread->stack_size + - (rt_uint32_t)thread->stack_addr - - (rt_uint32_t)thread->sp; - rt_kprintf("%-8s %-10s %-5d %-8d %-8d\n", - thread->name, - thread_stat_str(thread->stat), - thread->current_priority, - remain, thread->stack_size); - } - return RT_EOK; -} -MSH_CMD_EXPORT(thread_dump, dump all threads status and remaining stack); - -static int thread_study_init(void) -{ - /* 引脚初始化 */ - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - rt_pin_write(PIN_LED_R, LED_OFF); - rt_pin_write(PIN_LED_B, LED_OFF); - rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP); - - rt_mutex_init(&print_mutex, "print", RT_IPC_FLAG_FIFO); - - /* 1. 动态创建工作线程 th_led1 */ - th_led1 = rt_thread_create("th_led1", led1_entry, RT_NULL, - TH_STACK_SIZE, TH_LED1_PRIO, TH_TIMESLICE); - if (th_led1 != RT_NULL) - { - rt_thread_startup(th_led1); - } - - /* 2. 静态初始化工作线程 th_led2 */ - rt_thread_init(&th_led2_tcb, "th_led2", led2_entry, RT_NULL, - &th_led2_stack[0], sizeof(th_led2_stack), - TH_LED2_PRIO, TH_TIMESLICE); - rt_thread_startup(&th_led2_tcb); - - /* 3. 动态创建监控线程 monitor */ - th_monitor = rt_thread_create("monitor", monitor_entry, RT_NULL, - TH_STACK_SIZE, TH_MON_PRIO, TH_TIMESLICE); - if (th_monitor != RT_NULL) - { - rt_thread_startup(th_monitor); - } - - return RT_EOK; -} -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111304.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111304.png" deleted file mode 100644 index 2ee4f5491ec36801110dd4a5dec5fd1979106ae0..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111304.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111446.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111446.png" deleted file mode 100644 index a0909eb8a504cc4d6ee2a51d9c0d2c86d2c33d7e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111446.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111541.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111541.png" deleted file mode 100644 index 57eb76c93051aced60614b1423ef732f411e2fa2..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 111541.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205041.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205041.png" deleted file mode 100644 index d5f981fb365f89a9495de53483cdea6c1f5796b7..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205041.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205747.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205747.png" deleted file mode 100644 index e2651a1f72c00ba3fb9cbe0ee2b5b6a60bf8c18a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 205747.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 144345.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 144345.png" deleted file mode 100644 index d798bbcbb27e5506b08e25ffd90c0c804e4f0192..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 144345.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145057.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145057.png" deleted file mode 100644 index c74941ee7f8230fa1e38834c55874f3df511970c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145057.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145842.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145842.png" deleted file mode 100644 index 37acb53fc35bd2b47574089f7d593a1432159485..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145842.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145859.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145859.png" deleted file mode 100644 index 3cbd8d6ce22ef117d2f962cf78620623474c66de..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 145859.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151021.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151021.png" deleted file mode 100644 index ed90a5467a7545180cc329d2c9833a667499526c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151021.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151614.png" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151614.png" deleted file mode 100644 index 7ff03f0da5bbbbe8580b8397d3e3594509bd23db..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-18 151614.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\276\256\344\277\241\345\233\276\347\211\207_20260817213103_622_46.jpg" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\276\256\344\277\241\345\233\276\347\211\207_20260817213103_622_46.jpg" deleted file mode 100644 index 30e8a04c257f783e72b8de2d8eaf3c06ec9cb5df..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/figures/\345\276\256\344\277\241\345\233\276\347\211\207_20260817213103_622_46.jpg" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/\347\254\2541\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/\347\254\2541\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 7cf93713bf084397e587b6733a3bbb69a01cf3ad..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\350\265\265\351\221\253/\347\254\224\350\256\260/\347\254\2541\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,251 +0,0 @@ -# 第1天笔记: - - - -## 一、RT-Thread 生态与产品线 - -RT-Thread 是国产自研 RTOS,经历近 20 年发展,国内开源社区活跃,内核已迭代至 **V5.3.0** - -| 产品线 | 定位 | 特点 | -| :----: | :--: | ---- | -| **Nano** | 极简内核 | 实时内核 + 线程同步通信机制,最小实体(线程、时钟、中断、内存管理);Apache 协议开源 | -| **标准版** | 功能完整 | 基于标准 POSIX API,提供日志、网络协议栈、IoT 协议等组件与中间件,生态完整、高度可定制 | -| **Smart** | 带 MMU 设备 | 混合微内核架构,内核态与用户态分离,提升安全性 | - -## 二、开发环境搭建 - -1. 安装 RT-Thread Studio。 - -2. 通过 SDK Manager 下载 RT-Thread 源码(版本统一 **4.1.1**)及 STM32F4 芯片支持包。 - -3. 在设备管理器中确认 ST-Link 驱动识别正常,保证硬件连接可用。 - -## 三、工程文件说明 - -新建工程后,RT-Thread Studio 的工程目录: - -``` -工程名/ -├── .config # menuconfig 图形化配置结果 -├── rtconfig.h # 由 .config 生成的配置头文件(功能裁剪开关) -├── rtconfig.py # 编译器与编译选项配置 -├── Kconfig # 配置项描述文件(menuconfig 界面依据) -├── SConstruct # scons 构建入口(Studio 内部调用 scons 编译) -├── SConscript # 各子目录的构建脚本 -├── applications/ # 用户应用代码,main.c 位于此 -├── board/ # 板级支持:系统时钟/外设初始化、链接脚本 -├── libraries/ # 芯片厂商外设库(如 STM32 HAL 库) -├── rt-thread/ # RT-Thread 内核与组件源码 -│ ├── src/ # 内核核心:调度、线程、IPC、时钟、内存管理 -│ ├── include/ # 内核头文件(rtthread.h 等) -│ ├── components/ # 组件:FinSH 命令行、设备驱动框架、协议栈等 -│ └── libcpu/ # CPU 架构移植层(Cortex-M4 等) -├── packages/ # 通过 SDK Manager 拉取的第三方软件包 -├── Debug/ # 编译输出:rtthread.elf、rtthread.bin、.map -└── .cproject 等 # Eclipse 工程元文件 -``` - -| 文件/目录 | 作用 | -| ----------------------- | ------------------------------------------------------------ | -| `rtconfig.h` | 配置头文件,`RT_USING_xxx` 宏决定裁剪哪些功能;修改 menuconfig 后由 `.config` 重新生成 | -| `applications/main.c` | 用户程序入口,系统启动后创建 main 线程执行 | -| `board/` | 板级初始化(系统时钟、Tick、外设)与链接脚本 | -| `libraries/` | 芯片厂商提供的 HAL/标准外设库 | -| `rt-thread/src/` | 内核核心源码(线程调度、IPC、时钟、内存管理) | -| `rt-thread/components/` | 组件层(FinSH、设备驱动框架、网络协议栈),配置开启后才编译链接 | -| `rt-thread/libcpu/` | 架构移植层,向上给内核提供统一接口 | -| `packages/` | 第三方软件包 | -| `Debug/` | 编译产物:`rtthread.elf`、`rtthread.bin` 及 map 文件 | - -> 编译过程:Studio 内部调用 scons(构建脚本为 `SConstruct`/`SConscript`),工具链为 gcc,最终在 `Debug/` 下生成 `rtthread.elf` 与 `rtthread.bin`。 -> - -## 四、系统启动流程 - -![](./figures/微信图片_20260817213103_622_46.jpg) - -上电后程序依次经过「汇编启动 → 内核初始化 → 用户 main」三个阶段: - -``` -上电复位 - → 启动文件 startup_xxx.s:设置堆栈、拷贝 .data 段、清零 .bss - → SystemInit():CMSIS 系统时钟初始化 - → entry():libcpu 汇编入口 - → rtthread_startup():内核启动 - → main 线程:rt_components_init() 初始化组件 → 执行用户 main() -``` - -内核启动函数(`rt-thread/src/components.c`)依次完成: - -```c -int rtthread_startup(void) -{ - rt_hw_interrupt_disable(); /* 1. 关闭中断 */ - rt_hw_board_init(); /* 2. 板级初始化:时钟、SysTick、内存堆 */ - rt_show_version(); /* 3. 打印版本号(终端里的 logo) */ - rt_system_timer_init(); /* 4. 系统定时器初始化 */ - rt_system_scheduler_init(); /* 5. 调度器初始化 */ - rt_system_signal_init(); /* 6. 信号初始化 */ - rt_application_init(); /* 7. 创建 main 线程 */ - rt_system_timer_thread_init(); /* 8. 定时器线程初始化 */ - rt_thread_idle_init(); /* 9. 空闲线程初始化 */ - rt_system_scheduler_start(); /* 10. 启动调度器,交出 CPU 控制权 */ - return 0; -} -``` - -- 第 2 步 `rt_hw_board_init()` 里完成系统时钟、SysTick 节拍配置和内存堆初始化;串口初始化在板级外设初始化中完成,所以 logo 能通过串口打印出来。 -- 第 7 步创建的 **main 线程**入口先调用 `rt_components_init()` 依次初始化各组件(如 FinSH),最后才进入用户编写的 `main()`。 -- 第 9 步的**空闲线程**(idle)优先级最低,没有就绪线程时运行;SysTick 每触发一次就完成一次调度节拍。 - -## 五、Git 的使用 - -Git 是一个**开源的分布式版本控制系统**,由 Linus Torvalds 为管理 Linux 内核开发而创建,用于敏捷高效地处理任何或小或大的项目。 - -### 1. Git 与 SVN 的区别 - -| 对比项 | Git | SVN | -| :----: | --- | --- | -| 架构 | **分布式**:每个开发者本地都有完整版本库,不必依赖服务器端软件 | 集中式:所有代码托管在中央服务器 | -| 存储方式 | 内容按**元数据**方式存储 | 按文件存储 | -| 版本号 | 无全局递增版本号 | 有全局版本号 | -| 内容完整性 | 使用 SHA-1 哈希算法保证内容完整,降低磁盘故障、网络问题对版本库的破坏 | 较弱 | - -### 2. 核心概念:工作区、暂存区与本地仓库 - -Git 的文件流经四个区域,理解它们是掌握一切 Git 操作的基础: - -| 区域 | 英文 | 说明 | -| ---- | ---- | ---- | -| **工作区** | Workspace | 本地磁盘上实际看到的项目文件,编辑、新建、删除都发生在这里 | -| **暂存区** | Staging Area(Index) | 临时存放“即将提交的更改”,由 `git add` 放入,可以只暂存部分文件 | -| **本地仓库** | Local Repository | 隐藏在 `.git` 目录中的数据库,保存全部提交历史,由 `git commit` 写入 | -| **远程仓库** | Remote Repository | GitHub/Gitee 上的仓库,`git push` 上传,`git pull`/`git fetch` 下载 | - -``` -工作区 ──git add──▶ 暂存区 ──git commit──▶ 本地仓库 ──git push──▶ 远程仓库 - ▲ │ - └──────────────────────── git pull / git fetch ◀───────────────┘ -``` - -- 简单总结:修改代码 → `git add`(放进篮子)→ `git commit`(存入保险箱)→ `git push`(寄给远程) -- 注意:**暂存区 ≠ 本地仓库**,暂存区只是提交前的“中转站”,真正的版本历史存在 `.git` 目录里 - -文件在 Git 中的状态转换(可用 `git status` 随时查看): - -``` -未跟踪 Untracked ──git add──▶ 已暂存 Staged ──git commit──▶ 已提交 Committed - ▲ │ - └───────── 修改文件 ◀──────────┘ - (已修改 Modified) -``` - -### 3. 常用命令 - -| 命令 | 说明 | -| ---- | ---- | -| `git init` | 初始化当前目录为 Git 仓库 | -| `git clone ` | 拷贝(下载)一个远程仓库到本地 | -| `git add ` | 将指定文件/文件夹加入暂存区;`git add .` 添加当前目录全部更改 | -| `git commit -m "message"` | 将暂存区内容提交到本地仓库,本次提交日志为 message | -| `git status` | 查看仓库当前状态,显示有变更、未跟踪的文件 | -| `git diff` | 比较暂存区与工作区的差异 | -| `git log` | 查看历史提交记录(`--oneline` 简洁模式,`--decorate --graph` 图形化显示分支与合并) | -| `git reset` | 回退版本 | -| `git rm` / `git mv` | 从暂存区和工作区删除文件 / 移动或重命名文件 | -| `git restore` | 恢复、撤销文件的更改(Git 2.23 引入) | -| `git stash` | 把未提交的修改“藏”进贮藏区,工作区恢复干净;`git stash pop` 取出继续写 | - -首次使用需先配置提交身份(否则无法 commit): - -``` -git config --global user.name "你的名字" -git config --global user.email "你的邮箱" -``` - -去掉 `--global` 参数则只对当前仓库生效。 - -本地 <--> 远端同步: - -| 命令 | 方向 | 说明 | -| ---- | :--: | ---- | -| `git pull origin master` | 远端 → 本地 | 拉取远程 master 分支并自动合并(= fetch + merge) | -| `git fetch origin master` | 远端 → 本地 | 只同步远端分支到本地,**不自动合并**,确认无误后再自行 merge | -| `git push origin master` | 本地 → 远端 | 将本地 master 分支最新内容同步/推送到远端 | - -`git push` 完整格式:`git push <远程主机名> <本地分支名>:<远程分支名>`,本地与远程分支同名时可省略冒号。 - -### 4. 分支管理 - -- `git checkout -b test`:基于当前分支切出一个名为 test 的新分支 -- `git switch `:切换到已存在的分支(Git 2.23 引入,职责更清晰;`git checkout` 既能切分支又能恢复文件,推荐用 switch 切分支) -- `git branch`:列出本地分支;`git branch -r` 查看远程分支;`git branch -a` 查看全部 -- `git merge `:将指定分支合并到**当前**分支 -- `git branch -d `:删除已合并的分支;`git branch -D` 强制删除未合并的分支 - -> 切换分支时,Git 会用该分支最后一次提交的快照替换工作目录内容,所以多个分支不需要多个目录。 - -### 5. 远程仓库管理 - -| 命令 | 说明 | -| ---- | ---- | -| `git remote -v` | 查看仓库远端分支情况(及对应 URL) | -| `git remote add ` | 添加远程仓库,通常命名为 origin | -| `git remote set-url origin ` | 修改远程仓库 URL(如改成自己的 Gitee 仓库地址) | -| `git remote rename <旧名> <新名>` | 重命名远程仓库 | -| `git remote remove ` | 删除远程仓库 | - -origin 只是远程地址的别名(clone 时自动生成,指向克隆来源)。 - -### 6. 标签(Tag) - -发布重要版本时给特定提交打标记: - -``` -git tag v1.0 # 轻量标签 -git tag -a v1.0 -m "说明" # 附注标签:记录打标签人和时间,推荐 -git tag # 查看所有标签 -git push origin --tags # 推送所有标签(默认 push 不推送标签) -git tag -d v1.0 # 本地删除标签 -``` - -### 7. 如何解决 git 冲突 - -1. 同步上游 -2. 切到产生冲突的分支,执行 `git merge`/`git rebase`,将最新远端的修改同步到产生冲突的分支,此时会让你解决冲突(自行选择保留旧时间线还是新时间线的修改) - -合并冲突时,Git 会在冲突文件中用标记标出两边内容: - -``` -<<<<<<< HEAD -本分支(当前)的修改 -======= -对方分支(被合并进来)的修改 ->>>>>>> change_site -``` - -手动编辑文件,保留需要的代码、删除冲突标记后: - -``` -git add <冲突文件> # 告诉 Git 冲突已解决 -git commit # 提交合并结果 -``` - -### 8. 推送第一次 PR 后如何进行下一次代码推送和 PR 创建 - -1. 浏览器 fork 仓库同步上游 -2. 本地仓库打开 git bash,依次执行: - - ``` - git switch master - git pull origin master - ``` - - (执行第 2 步前确保第 1 步已完成,即 origin master 已是同步后的最新上游仓库) - -3. `git checkout -b day2` -4. (修改/新增/删除对应的文档/内容) -5. `git add .`(将当前目录下的所有修改加入暂存区) -6. `git commit -m "第二次提交信息"` -7. `git push origin day2`:将最新修改同步到远端 day2 分支 -8. 到 day2 分支下创建 PR diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\346\262\273\344\270\236/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\351\231\210\346\262\273\344\270\236/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 683cd6c127f3e59c9c7cb4100d096002223f65c4..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\351\231\210\346\262\273\344\270\236/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,33 +0,0 @@ -# RSOC26 Day1 作业 - -## 一、完成内容 - -今天我使用星火一号开发板,在 RT-Thread Studio 中创建了 `01_hello_rtt` 工程,并完成了工程编译。同时学习了使用 Git 分支和 PR 提交作业的方法。 - -本次作业分两次提交: - -1. 第一次 PR 创建个人目录以及作业、笔记文件。 -2. 第二次 PR 填写 Day1 作业和学习笔记。 - -## 二、调试中遇到的问题 - -点击调试后,控制台启动了 `ST-LINK_gdbserver.exe`,并提示: - -```text -Error in initializing ST-LINK device. -Reason: ST-LINK firmware upgrade required. -``` - -这个错误发生在调试器初始化阶段,不是 RT-Thread 源码或编译错误。虽然我没有使用外接 ST-LINK,但开发板的 Type-C 接口可能连接了板载调试器,因此需要先在设备管理器中确认调试器类型。 - -处理思路如下: - -- 如果识别为 ST-LINK,使用 STM32CubeProgrammer 升级板载 ST-LINK 固件。 -- 如果识别为 CMSIS-DAP 或 DAPLink,将 RT-Thread Studio 的调试器类型改为对应接口。 -- 如果只识别为串口设备,则查看开发板手册确认下载接口。 - -目前我已经根据日志把问题定位到调试器链路,后续继续完成固件升级和下载验证。 - -## 三、学习总结 - -这次实践让我认识到,编译成功并不代表程序一定能下载。嵌入式开发还涉及 GDB Server、调试器驱动、USB 连接和目标芯片。遇到错误时,应先阅读完整日志,再判断问题属于代码、编译还是硬件调试层。 diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\346\262\273\344\270\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\351\231\210\346\262\273\344\270\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 31cf6b4d26aa81cfc696409d748142c4dc96d8c0..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\351\231\210\346\262\273\344\270\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,61 +0,0 @@ -# RSOC26 Day1 学习笔记 - -## 1. Git 基础 - -Git 是分布式版本控制工具,可以记录文件的修改历史。常见流程为: - -```text -工作区 -> 暂存区 -> 本地仓库 -> 远程仓库 -``` - -常用命令: - -```bash -git status # 查看文件状态 -git diff # 查看修改内容 -git switch -c <分支名> # 创建并切换分支 -git add <文件> # 添加到暂存区 -git commit -m "说明" # 提交到本地仓库 -git push -u origin <分支名> # 推送远程分支 -``` - -本次作业中,`origin` 表示自己 Fork 的仓库,`upstream` 表示 RT-Thread 官方作业仓库。PR 用于请求维护者把个人分支的修改合并到官方仓库。 - -## 2. 裸机与 RTOS - -裸机程序通常在完成初始化后进入 `while (1)`,各任务按照程序设计的顺序运行。RTOS 会把任务划分为不同线程,由调度器根据线程优先级和状态决定运行顺序。 - -```text -裸机:上电 -> 初始化 -> main -> while(1) -RTOS:上电 -> 内核初始化 -> 创建线程 -> 启动调度器 -``` - -RT-Thread 中的 `main()` 通常在线程环境中运行,而不是负责所有系统初始化。 - -## 3. RT-Thread 启动流程 - -RT-Thread 的启动流程可以简单概括为: - -```text -复位入口 - -> 板级和内核初始化 - -> 创建 main、定时器和空闲线程 - -> 启动调度器 -``` - -几个主要函数: - -- `rt_hw_board_init()`:完成板级初始化。 -- `rt_system_timer_init()`:初始化定时器系统。 -- `rt_system_scheduler_init()`:初始化调度器。 -- `rt_application_init()`:创建应用相关线程。 -- `rt_thread_idle_init()`:创建空闲线程。 -- `rt_system_scheduler_start()`:启动线程调度。 - -RT-Thread 还可以通过 `INIT_BOARD_EXPORT`、`INIT_DEVICE_EXPORT`、`INIT_COMPONENT_EXPORT` 和 `INIT_APP_EXPORT` 等宏自动注册初始化函数,使不同组件按阶段完成初始化。 - -## 4. 调试问题记录 - -星火一号工程编译后,调试时出现 `ST-LINK firmware upgrade required`。这说明错误发生在 GDB Server 初始化调试器的阶段,应检查调试器类型、固件和 USB 连接,而不是修改应用代码。 - -这次实践让我学会了根据日志定位问题所在的层次,也对 Git 协作和 RT-Thread 启动过程有了初步认识。 diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 980a6b8e788bf070debceb15d5897d82832214c5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,310 +0,0 @@ -# RT-Thread 夏令营第二天作业 - -## 作业内容 - -本次作业围绕 **STM32F407 启动流程、RT-Thread 自动初始化机制、内核对象模型、线程调度和多线程编程** 展开。实验在星火一号 STM32F407 开发板上完成,源码见本目录下的 [`thread_study.c`](./thread_study.c)。 - -## Part 1:理论知识梳理 - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -#### 1.1 从上电复位到 `main()` 的启动路径 - -![RT-Thread 启动流程](../../笔记/images/rt-thread-startup-flow.png) - -STM32F407 上电或复位后,处理器根据中断向量表取得主栈指针和复位入口地址,随后进入启动文件中的 `Reset_Handler`。整体调用关系可概括为: - -```text -上电/复位 - -> 从向量表装载 MSP 和 Reset_Handler 地址 - -> Reset_Handler - -> SystemInit():初始化系统时钟等底层环境 - -> C/C++ 运行库初始化:复制 .data、清零 .bss - -> 工具链入口 - -> rtthread_startup() - -> rt_hw_interrupt_disable() 关闭中断 - -> rt_hw_board_init() 板级硬件初始化 - -> rt_components_board_init() 执行板级自动初始化函数 - -> rt_show_version() 打印 RT-Thread 版本信息 - -> rt_system_timer_init() 初始化系统定时器 - -> rt_system_scheduler_init() 初始化调度器 - -> rt_system_signal_init() 初始化信号机制(按配置启用) - -> rt_application_init() 创建系统 main 线程 - -> rt_system_timer_thread_init() 创建软件定时器线程(按配置启用) - -> rt_thread_idle_init() 创建空闲线程 - -> rt_system_scheduler_start() 启动调度器 - -> main_thread_entry() - -> rt_components_init() 执行组件/应用自动初始化函数 - -> main() 进入 applications/main.c -``` - -不同编译工具链在进入 RT-Thread 统一启动流程前,入口形式可能不同: - -| 工具链 | 常见入口形式 | 作用 | -| --- | --- | --- | -| MDK/ARMCC | `__main`、`$Sub$$main` | 完成运行库初始化,并通过入口封装接入 RT-Thread | -| IAR | `__low_level_init` 等 | 在进入应用前执行底层初始化 | -| GCC | `_start`、`entry` 或 BSP 启动入口 | 由启动文件和链接脚本衔接到 RT-Thread | - -具体函数名受 RT-Thread 版本、BSP 和工具链影响,应以当前工程的启动文件、`components.c`、`board.c` 和链接脚本为准。 - -#### 1.2 `INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` 的实现原理 - -两者都是 RT-Thread 自动初始化框架对 `INIT_EXPORT(fn, level)` 的封装。当前工程源码中的定义为: - -```c -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") -``` - -宏的作用不是在当前位置调用函数,而是将函数指针放入具有特定名称和等级的链接段。系统启动时,初始化框架遍历段边界之间的函数指针,并按等级依次调用: - -```text -初始化函数 - -> INIT_xxx_EXPORT(fn) - -> INIT_EXPORT(fn, level) - -> 函数指针进入指定链接段 - -> 链接器按段组织这些函数指针 - -> rt_components_board_init()/rt_components_init() 遍历并调用 -``` - -- `INIT_BOARD_EXPORT()` 属于等级 1,通常用于板级硬件和底层驱动初始化,由 `rt_components_board_init()` 执行。 -- `INIT_APP_EXPORT()` 属于等级 6,通常用于应用模块初始化,由 main 线程中的 `rt_components_init()` 执行。 -- 本实验使用 `INIT_APP_EXPORT(thread_study_init)`,因此不需要在 `main.c` 中手动调用 `thread_study_init()`。 - -### 2. 内核基础与对象模型 - -#### 2.1 对象容器与线程控制块 - -RT-Thread 使用面向对象的设计思想统一管理线程、信号量、互斥量、事件、邮箱和设备等内核实体。公共对象部分保存对象名称、类型、标志和链表节点等信息,具体对象控制块在此基础上扩展自己的属性和行为。 - -线程由 `struct rt_thread` 描述,其中常用成员如下: - -| 成员 | 作用 | -| --- | --- | -| `stat` | 保存线程状态;读取时通常与 `RT_THREAD_STAT_MASK` 进行按位与操作 | -| `current_priority` | 当前调度优先级;本工程中数值越小,优先级越高 | -| `init_tick` | 线程初始化时设置的时间片长度 | -| `remaining_tick` | 当前时间片的剩余 Tick 数 | -| `sp` | 当前线程栈指针,在线程上下文保存和恢复时使用 | -| `stack_addr` | 线程栈空间起始地址 | -| `stack_size` | 线程栈空间总大小 | -| `tlist` | 用于把线程挂接到就绪表、等待队列等内核链表 | - -上下文切换时,CPU 寄存器现场会保存到当前线程栈中,`sp` 记录保存后的位置;调度器再恢复目标线程栈中的现场,使目标线程从上次停止的位置继续执行。 - -#### 2.2 静态对象与动态对象 - -| 对比项 | 静态线程 | 动态线程 | -| --- | --- | --- | -| 创建接口 | `rt_thread_init()` | `rt_thread_create()` | -| TCB 与栈 | 由应用预先定义并传入 | 由系统从堆中动态分配 | -| 内存位置 | 全局/静态存储区或固定内存区 | 系统堆 | -| 生命周期 | 由应用管理,结束时执行脱离操作 | 不再使用时由删除与回收机制释放 | -| 优点 | 内存占用确定、无运行期分配失败、便于安全分析 | 创建灵活,适合数量和生命周期动态变化的任务 | -| 风险 | 固定占用内存,配置过大可能浪费 RAM | 可能出现堆不足、内存碎片或分配失败 | - -高可靠、长时间运行或资源必须可预测的场景通常优先使用静态线程,因为其 TCB 和栈在启动前就已确定,不依赖运行过程中的堆分配。但静态线程仍需要通过栈使用量分析验证栈大小是否合理。 - -### 3. 线程状态机与调度算法 - -#### 3.1 五态模型 - -| 状态 | 含义 | 典型进入方式 | -| --- | --- | --- | -| 初始态 `INIT` | TCB 已建立,但尚未参与调度 | `rt_thread_init()` 或 `rt_thread_create()` 后尚未启动 | -| 就绪态 `READY` | 已满足运行条件,正在等待 CPU | `rt_thread_startup()`、`rt_thread_resume()`、等待结束 | -| 运行态 `RUNNING` | 当前占用 CPU 执行 | 被调度器选中 | -| 挂起态 `SUSPEND` | 暂时不能运行,等待时间、事件或资源 | 延时、等待 IPC、自挂起 | -| 关闭态 `CLOSE` | 线程已经退出或被删除 | 入口函数返回、删除或脱离线程 | - -典型状态转换为: - -```text -INIT --rt_thread_startup()--> READY -READY --调度器选中----------> RUNNING -RUNNING --被抢占/时间片耗尽--> READY -RUNNING --延时/等待/挂起-----> SUSPEND -SUSPEND --超时/事件/恢复-----> READY -RUNNING --退出/删除----------> CLOSE -``` - -需要注意:`rt_thread_mdelay()` 和等待 IPC 也会使线程进入 `SUSPEND`。因此在监控输出中看到 `state=suspend`,并不一定表示线程被按键手动挂起,还要结合计数是否继续变化判断。 - -#### 3.2 抢占式调度 - -RT-Thread 调度器始终优先选择就绪队列中优先级最高的线程。本工程配置 `RT_THREAD_PRIORITY_MAX=32`,优先级数值越小表示优先级越高。因此优先级为 10 的 `th_monitor` 唤醒后,可以抢占优先级为 16 的两个 LED 工作线程;监控处理完成并延时后,工作线程继续运行。 - -#### 3.3 时间片轮转 - -当多个就绪线程的优先级相同时,调度器使用时间片轮转保证它们轮流获得 CPU。本实验中 `th_led1` 和 `th_led2` 都是优先级 16、时间片 5 ticks。当它们同时处于就绪态时,可以在同一优先级队列内轮转运行。 - -时间片轮转只解决同优先级线程之间的公平性,不能阻止更高优先级线程抢占。 - -## Part 2:多线程实战编程 - -### 1. 实验环境 - -| 项目 | 配置 | -| --- | --- | -| 开发板 | 星火一号 STM32F407 开发板 | -| MCU | STM32F407ZG | -| 工程 | `DAY2RT-ThreadDXC` | -| RT-Thread | 4.1.1 | -| 系统 Tick | `RT_TICK_PER_SECOND=1000`,即 1 tick = 1 ms | -| 开发工具 | RT-Thread Studio | -| 下载调试 | ST-Link | -| 交互方式 | 串口终端与 MSH | - -### 2. 硬件资源与线程设计 - -#### 2.1 GPIO 分配 - -| 功能 | 引脚 | 配置 | -| --- | --- | --- | -| 红色 LED `LED_R` | PF12 | 推挽输出,由 `th_led1` 周期翻转 | -| 蓝色 LED `LED_B` | PF11 | 推挽输出,由 `th_led2` 周期翻转 | -| 用户按键 | PC5 | 上拉输入、下降沿中断 | - -以上引脚来自本工程已完成的板级实验配置;移植到其他开发板时必须重新核对原理图和 BSP 引脚定义。 - -#### 2.2 线程参数 - -| 线程 | 创建方式 | 优先级 | 时间片 | 栈大小 | 主要功能 | -| --- | --- | ---: | ---: | ---: | --- | -| `th_led1` | `rt_thread_create()` 动态创建 | 16 | 5 ticks | 1024 Bytes | 翻转红色 LED,打印计数和系统 Tick | -| `th_led2` | `rt_thread_init()` 静态初始化 | 16 | 5 ticks | 1024 Bytes | 翻转蓝色 LED并打印计数 | -| `th_monitor` | `rt_thread_create()` 动态创建 | 10 | 5 ticks | 1024 Bytes | 每 2 秒打印线程信息并处理按键请求 | - -### 3. 关键实现说明 - -#### 3.1 动态线程与静态线程 - -`th_led1` 和 `th_monitor` 使用 `rt_thread_create()` 从系统堆中动态分配线程控制块和栈;`th_led2` 的 `struct rt_thread` 控制块及 1024 字节栈数组在全局静态区定义,再通过 `rt_thread_init()` 初始化。三个线程创建或初始化后,都调用 `rt_thread_startup()` 进入就绪态。 - -#### 3.2 监控与剩余栈空间 - -`th_monitor` 每次唤醒时读取两个工作线程的状态、当前优先级和栈信息。RT-Thread 初始化线程栈时会填充 `'#'`,实验代码从栈起始位置统计仍保持为 `'#'` 的连续字节数,以估算剩余栈空间: - -```text -stack_free = 第一个非 '#' 字节地址 - stack_addr -``` - -这是用于学习和调试的估算方法。实际项目还应结合 RT-Thread 自带的线程查看命令、最小栈余量和长期压力测试判断栈大小。 - -#### 3.3 按键挂起与恢复设计 - -当前工程所用 RT-Thread 版本在 `rt_thread_suspend()` 中检查: - -```c -RT_ASSERT(thread == rt_thread_self()); -``` - -因此监控线程不能直接调用该接口挂起 `th_led1`。本实验采用“请求 + 线程自挂起”的方式: - -```text -第一次按键中断 - -> key_event = 1 - -> th_monitor 处理事件 - -> led1_suspend_request = 1 - -> th_led1 检测请求 - -> rt_thread_suspend(rt_thread_self()) - -第二次按键中断 - -> key_event = 1 - -> th_monitor 处理事件 - -> rt_thread_resume(th_led1) - -> th_led1 回到就绪态并继续原计数 -``` - -中断回调只记录事件并进行简单软件消抖,实际的线程控制和串口打印放在监控线程上下文中执行,避免在中断服务函数中执行复杂操作。 - -#### 3.4 自动初始化与 MSH 命令导出 - -应用模块使用: - -```c -INIT_APP_EXPORT(thread_study_init); -``` - -系统会在应用初始化阶段自动创建三个线程,无需修改 `main.c`。 - -自定义查看函数使用: - -```c -MSH_CMD_EXPORT(thread_dump, dump thread state and remaining stack); -``` - -注册为 MSH 命令。系统运行后,在串口命令行输入: - -```text -thread_dump -``` - -即可主动查看三个线程的状态、优先级和剩余栈空间。 - -### 4. 实验结果与分析 - -#### 4.1 线程运行与周期监控 - -![线程运行与周期监控](./images/serial-thread-monitor.png) - -串口中可以观察到: - -- `th_led1` 和 `th_led2` 的计数持续增加,说明两个工作线程都在周期运行; -- `th_led1` 同时输出 `rt_tick_get()`,相邻计数的 Tick 间隔约为 1000,符合 1 秒延时设置; -- `th_monitor` 约每 2 秒输出一次两个工作线程的状态和剩余栈空间; -- 两个工作线程处于 `rt_thread_mdelay()` 时可能显示为 `suspend`,这是延时等待状态,不等同于按键手动挂起; -- 偶尔出现两条输出连接在同一行,是多个线程并发调用 `rt_kprintf()` 造成的串口输出交错,不影响调度逻辑。 - -#### 4.2 按键挂起 `th_led1` - -![按键挂起 th_led1](./images/serial-key-suspend.png) - -第一次按键后,串口显示: - -```text -[KEY] action: REQUEST SUSPEND th_led1 -[KEY] result: waiting for th_led1... -``` - -随后 `th_led1` 在自己的线程上下文中执行挂起。截图中 `th_led1` 的计数停在 25,而 `th_led2` 继续从 26 增长到 34,证明红灯线程已经被实际挂起,系统和蓝灯线程仍然正常运行。 - -#### 4.3 按键恢复 `th_led1` - -![按键恢复 th_led1](./images/serial-key-resume.png) - -再次按键后,串口显示: - -```text -[KEY] action: RESUME th_led1 -[KEY] result: th_led1 RESUMED -``` - -恢复后 `th_led1` 的计数从 26 继续增加,而不是重新从 1 开始,说明恢复操作保留了线程原有执行上下文。`th_led2` 在整个过程中一直持续运行。 - -#### 4.4 MSH `thread_dump` 命令 - -![MSH thread_dump 命令](./images/serial-thread-dump.png) - -在 `msh />` 提示符输入 `thread_dump` 后,系统成功打印: - -- `th_led1`:状态、优先级 16、剩余栈空间; -- `th_led2`:状态、优先级 16、剩余栈空间; -- `th_monitor`:状态、优先级 10、剩余栈空间。 - -截图证明 `MSH_CMD_EXPORT()` 已将函数正确注册到 MSH 命令表。截图时三个线程都处于延时或等待阶段,因此状态均显示为 `suspend`,这是正常现象。 - -### 5. 实验结论 - -- [x] 动态创建 `th_led1`,按要求配置优先级、时间片和栈大小 -- [x] 静态初始化 `th_led2`,使用全局 TCB 和栈空间 -- [x] LED1和2 由两个工作线程分别周期翻转 -- [x] 动态创建高优先级 `th_monitor`,每 2 秒监控线程 -- [x] 打印工作线程状态、优先级和剩余栈空间 -- [x] 通过按键实现 `th_led1` 挂起与恢复 -- [x] 使用 `INIT_APP_EXPORT()` 自动加载模块 -- [x] 使用 `MSH_CMD_EXPORT()` 导出 `thread_dump` 命令 -- [x] 完成运行、挂起、恢复和 MSH 命令的串口验证 - -本实验验证了 RT-Thread 静态/动态线程创建、优先级抢占、同优先级时间片轮转、线程状态观察、自动初始化及 MSH 命令导出机制。 diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-key-resume.png" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-key-resume.png" deleted file mode 100644 index 7c60058141808019d3cf82570eceb2ca6201181e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-key-resume.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-key-suspend.png" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-key-suspend.png" deleted file mode 100644 index 1898f1873c3a757f8fcc06353e956c06f1e73ecb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-key-suspend.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-thread-dump.png" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-thread-dump.png" deleted file mode 100644 index 5794066fb252b81bf7013756db8f00292fd5b893..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-thread-dump.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-thread-monitor.png" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-thread-monitor.png" deleted file mode 100644 index 1f88dd0d9222a1d1a5e52a92135e7880024a292d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/serial-thread-monitor.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 774725399e3a7581dd080e577d217b8a7d75ca72..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,523 +0,0 @@ -/* -【DAY2】RT-Thread 启动流程、内核基础与多线程编程 - */ - -#include -#include -#include -#include -#include - -/* LED 和按键引脚 */ -#define LED_R_PIN GET_PIN(F, 12) -#define LED_B_PIN GET_PIN(F, 11) -#define KEY_PIN GET_PIN(C, 5) - -/* 工作线程配置 */ -#define LED_STACK_SIZE 1024 -#define LED_PRIORITY 16 -#define LED_TIMESLICE 5 -#define LED_PERIOD_MS 1000 - -/* 监控线程配置 */ -#define MONITOR_STACK_SIZE 1024 -#define MONITOR_PRIORITY 10 -#define MONITOR_TIMESLICE 5 -#define MONITOR_PERIOD_MS 2000 - -/* 按键消抖时间 */ -#define KEY_DEBOUNCE_MS 50 - -/* 动态线程句柄 */ -static rt_thread_t th_led1 = RT_NULL; -static rt_thread_t th_monitor = RT_NULL; - -/* 静态线程 th_led2 的栈空间和线程控制块 */ -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t th_led2_stack[LED_STACK_SIZE]; - -ALIGN(RT_ALIGN_SIZE) -static struct rt_thread th_led2; - -/* 按键中断事件标志 */ -static volatile rt_uint8_t key_event = 0; -static volatile rt_tick_t key_last_tick = 0; - -/* - * th_led1 的挂起控制变量。 - * - * 监控线程只设置挂起请求, - * th_led1 检测到请求后自行执行挂起。 - */ -static volatile rt_uint8_t led1_suspend_request = 0; -static volatile rt_uint8_t led1_manual_suspended = 0; - - -/* 翻转指定 GPIO 的输出电平 */ -static void led_toggle(rt_base_t pin) -{ - rt_base_t level; - - level = rt_pin_read(pin); - - if (level == PIN_LOW) - { - rt_pin_write(pin, PIN_HIGH); - } - else - { - rt_pin_write(pin, PIN_LOW); - } -} - - -/* 将线程状态转换为英文字符串 */ -static const char *thread_state_name(rt_thread_t thread) -{ - if (thread == RT_NULL) - { - return "null"; - } - - switch (thread->stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: - return "init"; - - case RT_THREAD_READY: - return "ready"; - - case RT_THREAD_SUSPEND: - return "suspend"; - - case RT_THREAD_RUNNING: - return "running"; - - case RT_THREAD_CLOSE: - return "close"; - - default: - return "unknown"; - } -} - - -/* - * 计算线程剩余栈空间。 - * - * RT-Thread 在线程初始化时会将栈填充为 '#', - * 通过统计连续的 '#' 估算未使用的栈空间。 - */ -static rt_uint32_t thread_stack_free(rt_thread_t thread) -{ - rt_uint8_t *start; - rt_uint8_t *end; - rt_uint8_t *current; - - if (thread == RT_NULL) - { - return 0; - } - - start = (rt_uint8_t *)thread->stack_addr; - end = start + thread->stack_size; - current = start; - - while ((current < end) && (*current == '#')) - { - current++; - } - - return (rt_uint32_t)(current - start); -} - - -/* 打印线程状态、优先级和剩余栈空间 */ -static void print_thread_info(const char *label, rt_thread_t thread) -{ - if (thread == RT_NULL) - { - rt_kprintf("%s: not created\n", label); - return; - } - - rt_kprintf( - "%s: state=%s, priority=%u, stack_free=%u/%u bytes\n", - label, - thread_state_name(thread), - (unsigned int)thread->current_priority, - (unsigned int)thread_stack_free(thread), - (unsigned int)thread->stack_size); -} - - -/* - * 工作线程 th_led1。 - * - * 动态创建; - * 优先级为 16; - * 时间片为 5 ticks; - * 栈大小为 1024 字节; - * 循环翻转红色 LED,并打印计数和系统 Tick。 - */ -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - (void)parameter; - - while (1) - { - /* - * 当前 RT-Thread 版本要求线程只能挂起自己, - * 因此由 th_led1 自己执行 rt_thread_suspend()。 - */ - if (led1_suspend_request != 0) - { - led1_suspend_request = 0; - led1_manual_suspended = 1; - - rt_kprintf("th_led1: suspended\n"); - - rt_thread_suspend(rt_thread_self()); - - /* - * 被 th_monitor 恢复后,从这里继续执行。 - */ - continue; - } - - /* 翻转红色 LED */ - led_toggle(LED_R_PIN); - - count++; - - /* 打印计数和系统 Tick */ - rt_kprintf( - "[th_led1] count=%u, tick=%u\n", - (unsigned int)count, - (unsigned int)rt_tick_get()); - - rt_thread_mdelay(LED_PERIOD_MS); - } -} - - -/* - * 工作线程 th_led2。 - * - * 静态初始化; - * 使用全局定义的线程控制块和栈空间; - * 优先级为 16; - * 时间片为 5 ticks; - * 栈大小为 1024 字节; - * 循环翻转蓝色 LED,并打印计数。 - */ -static void th_led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - - (void)parameter; - - while (1) - { - /* 翻转蓝色 LED */ - led_toggle(LED_B_PIN); - - count++; - - /* 打印计数 */ - rt_kprintf( - "[th_led2] count=%u\n", - (unsigned int)count); - - rt_thread_mdelay(LED_PERIOD_MS); - } -} - - -/* 按键中断回调函数 */ -static void key_irq_callback(void *args) -{ - rt_tick_t now; - - (void)args; - - now = rt_tick_get(); - - /* - * 软件消抖,防止一次按键产生多个中断事件。 - */ - if ((rt_tick_t)(now - key_last_tick) >= - rt_tick_from_millisecond(KEY_DEBOUNCE_MS)) - { - key_event = 1; - key_last_tick = now; - } -} - - -/* 初始化按键 GPIO 和外部中断 */ -static rt_err_t key_init(void) -{ - rt_err_t result; - - /* 按键使用内部上拉输入 */ - rt_pin_mode(KEY_PIN, PIN_MODE_INPUT_PULLUP); - - key_last_tick = - rt_tick_get() - rt_tick_from_millisecond(KEY_DEBOUNCE_MS); - - /* 配置按键下降沿中断 */ - result = rt_pin_attach_irq( - KEY_PIN, - PIN_IRQ_MODE_FALLING, - key_irq_callback, - RT_NULL); - - if (result != RT_EOK) - { - rt_kprintf("attach key irq failed: %d\n", result); - return result; - } - - /* 开启按键中断 */ - result = rt_pin_irq_enable(KEY_PIN, PIN_IRQ_ENABLE); - - if (result != RT_EOK) - { - rt_kprintf("enable key irq failed: %d\n", result); - } - - return result; -} - - -/* - * 第一次按键:请求挂起 th_led1; - * 第二次按键:恢复 th_led1。 - */ -static void process_key_event(void) -{ - rt_err_t result; - - if (key_event == 0) - { - return; - } - - key_event = 0; - - if (led1_manual_suspended != 0) - { - rt_kprintf( - "\n========== [KEY] PRESS ==========\n"); - rt_kprintf( - "[KEY] action: RESUME th_led1\n"); - - result = rt_thread_resume(th_led1); - - if (result == RT_EOK) - { - led1_manual_suspended = 0; - rt_kprintf("[KEY] result: th_led1 RESUMED\n"); - } - else - { - rt_kprintf( - "[KEY] result: RESUME FAILED, error=%d\n", - result); - } - - rt_kprintf( - "=================================\n"); - } - else if (led1_suspend_request == 0) - { - rt_kprintf( - "\n========== [KEY] PRESS ==========\n"); - rt_kprintf( - "[KEY] action: REQUEST SUSPEND th_led1\n"); - - led1_suspend_request = 1; - - rt_kprintf( - "[KEY] result: waiting for th_led1...\n"); - rt_kprintf( - "=================================\n"); - } - else - { - rt_kprintf( - "[KEY] th_led1 suspend request is pending\n"); - } -} - - -/* - * 监控线程每隔 2 秒唤醒一次, - * 打印两个工作线程的状态和剩余栈空间。 - */ -static void th_monitor_entry(void *parameter) -{ - (void)parameter; - - while (1) - { - rt_kprintf( - "\n========== [MONITOR] WAKE UP ==========\n"); - - /* 处理按键事件 */ - process_key_event(); - - /* 打印两个工作线程的信息 */ - print_thread_info("[MONITOR] th_led1", th_led1); - print_thread_info("[MONITOR] th_led2", &th_led2); - - rt_kprintf( - "========== [MONITOR] SLEEP 2s ==========\n"); - - rt_thread_mdelay(MONITOR_PERIOD_MS); - } -} - - -/* - * 自定义 MSH 命令。 - * - * 在串口输入 thread_dump, - * 可以查看三个线程的状态和剩余栈空间。 - */ -int thread_dump(int argc, char **argv) -{ - (void)argc; - (void)argv; - - rt_kprintf("========== thread dump ==========\n"); - - print_thread_info("th_led1", th_led1); - print_thread_info("th_led2", &th_led2); - print_thread_info("th_monitor", th_monitor); - - rt_kprintf("=================================\n"); - - return RT_EOK; -} - -/* 将 thread_dump 注册为 MSH 命令 */ -MSH_CMD_EXPORT(thread_dump, dump thread state and remaining stack); - - -/* - * 应用初始化函数。 - * - * 初始化 LED、按键, - * 创建动态线程、静态线程和监控线程。 - */ -static int thread_study_init(void) -{ - rt_err_t result; - - /* 配置红色和蓝色 LED 为输出模式 */ - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); - - /* 设置 LED 初始电平 */ - rt_pin_write(LED_R_PIN, PIN_HIGH); - rt_pin_write(LED_B_PIN, PIN_HIGH); - - /* 初始化按键 */ - result = key_init(); - - if (result != RT_EOK) - { - rt_kprintf("key initialization failed: %d\n", result); - } - - /* - * 动态创建 th_led1。 - */ - th_led1 = rt_thread_create( - "th_led1", - th_led1_entry, - RT_NULL, - LED_STACK_SIZE, - LED_PRIORITY, - LED_TIMESLICE); - - if (th_led1 == RT_NULL) - { - rt_kprintf("create th_led1 failed\n"); - return -RT_ENOMEM; - } - - result = rt_thread_startup(th_led1); - - if (result != RT_EOK) - { - rt_kprintf("startup th_led1 failed: %d\n", result); - return result; - } - - /* - * 静态初始化 th_led2。 - */ - result = rt_thread_init( - &th_led2, - "th_led2", - th_led2_entry, - RT_NULL, - th_led2_stack, - sizeof(th_led2_stack), - LED_PRIORITY, - LED_TIMESLICE); - - if (result != RT_EOK) - { - rt_kprintf("initialize th_led2 failed: %d\n", result); - return result; - } - - result = rt_thread_startup(&th_led2); - - if (result != RT_EOK) - { - rt_kprintf("startup th_led2 failed: %d\n", result); - return result; - } - - /* - * 动态创建 th_monitor。 - */ - th_monitor = rt_thread_create( - "th_monitor", - th_monitor_entry, - RT_NULL, - MONITOR_STACK_SIZE, - MONITOR_PRIORITY, - MONITOR_TIMESLICE); - - if (th_monitor == RT_NULL) - { - rt_kprintf("create th_monitor failed\n"); - return -RT_ENOMEM; - } - - result = rt_thread_startup(th_monitor); - - if (result != RT_EOK) - { - rt_kprintf("startup th_monitor failed: %d\n", result); - return result; - } - - return RT_EOK; -} - - -/* - * 使用自动初始化机制加载本模块。 - * - * 不需要在 main.c 中手动调用 thread_study_init()。 - */ -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/git-conflict-timeline.png" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/git-conflict-timeline.png" deleted file mode 100644 index 1fe39c4a8e861d0d256b58020d2e20050b159fd8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/git-conflict-timeline.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/gitee-create-pull-request.jpg" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/gitee-create-pull-request.jpg" deleted file mode 100644 index 25e765d26fa2ef2d3a43fa8dcf0ef98393c9f875..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/gitee-create-pull-request.jpg" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/rt-thread-io-device-model.png" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/rt-thread-io-device-model.png" deleted file mode 100644 index c6289c77512f47aba81be191aea32009650d1c48..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/rt-thread-io-device-model.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/rt-thread-startup-flow.png" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/rt-thread-startup-flow.png" deleted file mode 100644 index c6615bd7d002e6c01fb2476f4fcabf8fe5243f63..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/images/rt-thread-startup-flow.png" and /dev/null differ diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 0f9105e31e379c43f1738664909f67fc00788f0e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,608 +0,0 @@ -# RT-Thread 夏令营第一天学习笔记 - -## 本日学习目标 - -本日实践目标可以归纳为: - -1. 理解 RT-Thread Nano、标准版和 Smart 版的定位与适用场景; -2. 完成 RT-Thread Studio 基础开发环境检查; -3. 通过示例工程熟悉 RT-Thread 的目录结构、启动流程、构建过程和 MSH; -4. 使用 Git 或 TortoiseGit 完成 `clone`、`status`、`add`、`commit`、`pull` 和 `push`; -5. 初始化个人 Gitee 仓库,完成提交、推送和拉取; -6. Fork `rsoc-rtt`,创建个人目录,提交第一天学习内容并发起 Pull Request; -7. 理解最小 `SConscript` 与 Kconfig 配置方法,使功能或 MSH 命令受配置项控制。 - -RT-Thread 三种形态的定位如下: - -| 形态 | 主要特点 | 典型场景 | -| --- | --- | --- | -| RT-Thread Nano | 以轻量级内核为主,资源占用较小 | 资源受限的 MCU | -| RT-Thread 标准版 | 内核、设备模型和丰富组件结合 | 常规 MCU、物联网终端 | -| RT-Thread Smart | 支持用户态进程、MMU 和更完整的 POSIX 能力 | Cortex-A 等高性能处理器 | - -## 上午:Markdown 基础与 RT-Thread 工程结构 - -### 1. Markdown 基础语法 - -Markdown 是一种轻量级标记语言,适合编写课程笔记、项目文档和 README 文件。源文件通常使用 `.md` 扩展名。 - -#### 1.1 多级标题 - -Markdown 使用 `#` 表示标题层级,`#` 的数量越多,标题层级越低: - -```markdown -# 一级标题 -## 二级标题 -### 三级标题 -#### 四级标题 -``` - -编写笔记时,应保持标题层级连续,避免从一级标题直接跳到四级标题。 - -#### 1.2 字体加粗 - -使用两个星号包围文字可以得到粗体效果: - -```markdown -**需要强调的内容** -``` - -显示效果:**需要强调的内容**。 - -#### 1.3 代码段 - -行内代码使用反引号包围,例如 `rtthread.elf`。多行代码使用三个反引号,并可指定语言以启用语法高亮: - -````markdown -```c -int main(void) -{ - return 0; -} -``` -```` - -#### 1.4 表格 - -Markdown 表格由竖线和分隔线组成: - -```markdown -| 项目 | 说明 | -| --- | --- | -| `applications` | 应用程序目录 | -| `drivers` | 板级驱动目录 | -``` - -### 2. 熟悉 RT-Thread Studio 示例工程 - -本次课程创建的工程位于: - -```text -E:\Qqqqqrs\RT-ThreadStudio\RT-ThreadStudio\workspace\HHHello -``` - -`HHHello` 示例工程在本阶段主要用于熟悉 RT-Thread 的工程目录、启动流程和底层源码。后续板级实践将使用星火 STM32F407 开发板工程: - -```text -E:\Qqqqqrs\RT-ThreadStudio\RT-ThreadStudio\workspace\day01helloSTM32F407 -``` - -工程的关键目录和文件如下: - -| 路径 | 作用 | -| --- | --- | -| `applications/main.c` | 应用程序入口,包含 `main()` 主函数 | -| `drivers/` | BSP 相关的底层驱动与硬件适配代码 | -| `rt-thread/` | RT-Thread 组件、CPU 移植层和内核源码 | -| `rtconfig.h` | RT-Thread 功能配置结果 | -| `SConstruct`、`SConscript` | SCons 构建脚本 | -| `Debug/` | 当前工程的编译输出目录 | - -> 工程中的目录名称是 `applications`,不是 `application`。 - -当前 `applications/main.c` 的主要内容如下: - -```c -#include -#include -#include - -int main(void) -{ - printf("hello rt-thread\n"); - - return 0; -} -``` - -程序进入 `main()` 后,通过标准输出接口打印 `hello rt-thread`。标准输出最终如何连接到串口或控制台,取决于 BSP 和控制台设备的配置。 - -### 3. RT-Thread 源码目录 - -`rt-thread/` 目录主要包含以下部分: - -| 目录 | 主要职责 | -| --- | --- | -| `components/` | 文件系统、命令行、设备框架、网络、C/C++ 支持等可选组件 | -| `include/` | RT-Thread 公共头文件 | -| `libcpu/` | 与 CPU 架构相关的移植代码 | -| `src/` | RT-Thread 内核核心源码 | -| `tools/` | 构建和配置辅助工具 | - -#### 3.1 `components` 组件目录 - -当前工程包含的主要组件如下: - -| 组件 | 作用 | -| --- | --- | -| `cplusplus` | C++ 运行环境与相关支持 | -| `dfs` | DFS(Device Virtual File System,设备虚拟文件系统) | -| `drivers` | RT-Thread 通用设备驱动框架 | -| `finsh` | FinSH/MSH 命令行终端,可提供类似 Shell 的交互环境 | -| `libc` | C 标准库适配及 POSIX 相关接口 | -| `lwp` | 轻量级进程相关支持 | -| `net` | 网络协议栈与网络接口相关组件 | -| `utilities` | 通用辅助功能 | -| `vbus`、`vmm` | 虚拟总线与虚拟化相关功能 | - -`libc` 目录针对不同编译环境提供适配层。当前源码中可以看到 `armlibc`、`dlib`、`minilibc` 和 `newlib` 等适配实现,同时还包括 `pthreads`、`signal`、`termios`、`time` 等接口。具体功能是否启用,由工程配置决定。 - -#### 3.2 `libcpu` CPU 移植层 - -`libcpu` 用于隔离不同 CPU 架构之间的差异,通常包含: - -- 线程上下文切换; -- 中断开启、关闭及中断入口处理; -- 线程栈初始化; -- 与体系结构相关的汇编代码; -- 异常处理和底层 CPU 操作。 - -当前 `HHHello` 工程包含 `libcpu/arm`,其中可见 Cortex-M0、Cortex-M23、Cortex-M3、Cortex-M33、Cortex-M4、Cortex-M7、Cortex-A 和 Cortex-R 等 ARM 系列移植目录。RT-Thread 上游还支持 RISC-V、龙芯等其他处理器架构,但是否出现在具体工程中取决于所选 BSP 和源码裁剪范围。 - -#### 3.3 `src` 内核源码 - -`src` 是 RT-Thread 内核的核心实现目录。当前工程中的关键文件如下: - -| 文件 | 主要职责 | -| --- | --- | -| `clock.c` | 系统时钟节拍管理 | -| `device.c` | 设备对象与设备管理接口 | -| `idle.c` | 空闲线程管理 | -| `ipc.c` | 线程间同步与通信机制 | -| `irq.c` | 中断管理 | -| `mem.c`、`memheap.c`、`mempool.c`、`slab.c` | 动态内存、堆、内存池及 Slab 管理 | -| `object.c` | 内核对象管理 | -| `scheduler.c` | 线程调度器 | -| `signal.c` | 信号机制 | -| `thread.c` | 线程创建、控制和生命周期管理 | -| `timer.c` | 软件定时器管理 | - -RT-Thread 内核采用面向对象的设计思想,但主要使用 C 语言实现。线程、设备、信号量、互斥量和定时器等实体都建立在统一的内核对象模型之上,便于进行类型管理和生命周期管理。 - -### 4. RT-Thread I/O 设备模型 - -![RT-Thread I/O 设备模型](images/rt-thread-io-device-model.png) - -RT-Thread 在硬件与应用程序之间提供统一的 I/O 设备模型。整体上可理解为应用层之下的三层结构: - -| 层级 | 作用 | 示例 | -| --- | --- | --- | -| I/O 设备管理层 | 向应用提供统一的设备对象与访问接口 | 字符设备、块设备、SPI 总线/设备、I2C 总线/设备 | -| 设备驱动框架层 | 抽象同类外设的共同能力,屏蔽不同芯片的实现差异 | 串口、SPI、I2C、PIN、音频、USB 等驱动框架 | -| 设备驱动层 | 面向具体 MCU、控制器或外设实现底层操作 | STM32/NXP 串口驱动、SPI 控制器驱动、SPI Flash 驱动、GPIO 驱动 | - -应用程序不应直接依赖每一种硬件的寄存器实现,而应优先通过统一设备接口访问底层设备。例如,应用可以按设备名称查找串口、SPI 或 I2C 设备,再使用统一的打开、关闭、读写和控制接口完成操作。 - -LCD、键盘、鼠标、串口和音频设备属于不同类型的外设或设备类;`stdio` 则表示 C 标准输入输出接口。它们可能通过 RT-Thread 的设备框架、控制台或具体驱动进行衔接,不能简单地全部归为“上层应用”。 - -### 5. 程序构建与运行流程 - -嵌入式程序从源代码到在目标板运行,通常经历以下阶段: - -1. **编辑**:编写 `.c`、`.h` 和汇编源文件。 -2. **预处理**:展开头文件、宏定义和条件编译指令。 -3. **编译**:将 C/C++ 源代码转换为汇编代码或目标代码。 -4. **汇编**:将汇编代码转换为目标文件 `.o`。 -5. **链接**:按照链接脚本组合目标文件和库,生成可执行映像 `.elf`。 -6. **格式转换**:从 ELF 映像中提取适合烧录的 `.bin` 等文件。 -7. **下载与运行**:把程序烧录或加载到目标环境中,观察终端输出并验证功能。 - -RT-Thread Studio 工程使用 SCons 组织构建流程,底层再调用 GCC 等工具链完成编译、汇编和链接。当前 `HHHello/Debug` 目录中已经生成: - -| 文件 | 作用 | -| --- | --- | -| `rtthread.elf` | 带有程序段、符号及调试信息的 ELF 映像 | -| `rtthread.bin` | 适合下载或烧录的纯二进制映像 | -| `rtthread.map` | 链接映射文件,用于分析符号地址和内存占用 | - -编译过程中生成的是 `.o` 目标文件,而不是口述记录中的“.L 文件”;最终可执行文件扩展名是 `.elf`。如果 IDE 工程模板或构建脚本版本不一致,应先同步或刷新 SCons 工程配置,再重新构建并根据控制台日志定位问题。 - -### 6. RT-Thread 启动流程源码分析 - -![RT-Thread 启动流程](images/rt-thread-startup-flow.png) - -启动流程图同时展示了不同工具链的入口形式:MDK/ARMCC 可以通过 `$Sub$$main()` 进入系统启动流程,IAR 使用 `__low_level_init()`,GCC 工程则使用 `entry()`。当前 `HHHello` 是 GCC 工程,因此实际入口位于 `rt-thread/src/components.c`: - -```c -#if defined(__GNUC__) -int entry(void) -{ - rtthread_startup(); - return 0; -} -#endif -``` - -`entry()` 随后调用 `rtthread_startup()`。根据当前工程源码,其关键执行顺序如下: - -```text -entry() -└─ rtthread_startup() - ├─ rt_hw_interrupt_disable() 关闭中断,保护初始化过程 - ├─ rt_hw_board_init() 板级硬件、堆、板级组件和控制台初始化 - ├─ rt_show_version() 输出 RT-Thread 版本信息 - ├─ rt_system_timer_init() 初始化系统定时器 - ├─ rt_system_scheduler_init() 初始化调度器 - ├─ rt_system_signal_init() 初始化信号机制(启用时) - ├─ rt_application_init() 创建并启动 main 线程 - ├─ rt_system_timer_thread_init() 创建定时器线程 - ├─ rt_thread_idle_init() 创建空闲线程 - └─ rt_system_scheduler_start() 启动调度器,不再返回 -``` - -核心源码可以概括为: - -```c -int rtthread_startup(void) -{ - rt_hw_interrupt_disable(); - rt_hw_board_init(); - rt_show_version(); - rt_system_timer_init(); - rt_system_scheduler_init(); - rt_application_init(); - rt_system_timer_thread_init(); - rt_thread_idle_init(); - rt_system_scheduler_start(); - return 0; -} -``` - -#### 6.1 板级初始化 - -`drivers/board.c` 中的 `rt_hw_board_init()` 负责与当前 BSP 相关的初始化: - -```c -void rt_hw_board_init(void) -{ - rt_hw_interrupt_init(); - rt_system_heap_init(HEAP_BEGIN, HEAP_END); - rt_components_board_init(); - rt_console_set_device(RT_CONSOLE_DEVICE_NAME); - rt_thread_idle_sethook(idle_wfi); -} -``` - -当前 `rtconfig.h` 将控制台设备配置为 `uart0`。`rt_components_board_init()` 会执行通过 `INIT_BOARD_EXPORT()` 注册的板级初始化函数,这些工作发生在调度器启动之前。 - -#### 6.2 main 线程的创建与执行 - -`rt_application_init()` 创建名为 `main` 的线程,入口函数是 `main_thread_entry()`: - -```c -void rt_application_init(void) -{ - rt_thread_t tid; - - tid = rt_thread_create("main", main_thread_entry, RT_NULL, - RT_MAIN_THREAD_STACK_SIZE, - RT_MAIN_THREAD_PRIORITY, 20); - RT_ASSERT(tid != RT_NULL); - rt_thread_startup(tid); -} -``` - -调度器启动后,`main_thread_entry()` 先调用 `rt_components_init()`,按初始化级别执行组件函数,然后才进入 `applications/main.c` 中的用户 `main()`: - -```c -void main_thread_entry(void *parameter) -{ - extern int main(void); - - rt_components_init(); - main(); -} -``` - -#### 6.3 自动初始化机制 - -RT-Thread 使用宏把初始化函数指针放入指定的链接段,再由 `rt_components_board_init()` 或 `rt_components_init()` 按顺序遍历执行。主要初始化级别如下: - -| 宏 | 级别 | 典型用途 | -| --- | ---: | --- | -| `INIT_BOARD_EXPORT(fn)` | 1 | 板级硬件初始化 | -| `INIT_PREV_EXPORT(fn)` | 2 | 纯软件预初始化 | -| `INIT_DEVICE_EXPORT(fn)` | 3 | 设备初始化 | -| `INIT_COMPONENT_EXPORT(fn)` | 4 | DFS、网络等组件初始化 | -| `INIT_ENV_EXPORT(fn)` | 5 | 文件系统挂载等环境初始化 | -| `INIT_APP_EXPORT(fn)` | 6 | 应用级服务和线程初始化 | - -例如 `INIT_APP_EXPORT(fn)` 最终展开为: - -```c -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") -``` - -该宏不是在当前位置直接调用函数,而是完成**注册**;实际调用发生在 `rt_components_init()` 遍历初始化表时。 - -### 7. MSH 启动过程 - -MSH 是 RT-Thread 的命令行 Shell,基于 FinSH 组件实现。当前工程在 `rtconfig.h` 中启用了 `RT_USING_FINSH`、`FINSH_USING_MSH` 和 `FINSH_USING_MSH_DEFAULT`。 - -MSH 的初始化函数位于 `rt-thread/components/finsh/shell.c`: - -```c -int finsh_system_init(void) -{ - rt_err_t result = RT_EOK; - rt_thread_t tid; - - shell = (struct finsh_shell *)rt_calloc(1, sizeof(struct finsh_shell)); - tid = rt_thread_create(FINSH_THREAD_NAME, - finsh_thread_entry, RT_NULL, - FINSH_THREAD_STACK_SIZE, - FINSH_THREAD_PRIORITY, 10); - - rt_sem_init(&(shell->rx_sem), "shrx", 0, 0); - finsh_set_prompt_mode(1); - - if (tid != RT_NULL && result == RT_EOK) - rt_thread_startup(tid); - return 0; -} -INIT_APP_EXPORT(finsh_system_init); -``` - -这段代码完成以下工作: - -1. 初始化 FinSH/MSH 命令符号表; -2. 分配并初始化 Shell 控制块; -3. 创建以 `finsh_thread_entry()` 为入口的 Shell 线程; -4. 初始化接收信号量 `rx_sem`; -5. 启动 Shell 线程; -6. 使用 `INIT_APP_EXPORT(finsh_system_init)` 将函数注册到应用初始化阶段。 - -由于 `finsh_system_init()` 属于级别 6,它会在 main 线程执行 `rt_components_init()` 时被调用。Shell 线程启动后,`finsh_thread_entry()` 会在未单独指定 Shell 设备时取得系统控制台设备;当前工程的控制台是 `uart0`。随后打印 `msh />` 提示符,并循环读取字符、解析命令和执行已导出的命令。 - -```text -main_thread_entry() -└─ rt_components_init() - └─ finsh_system_init() INIT_APP_EXPORT,级别 6 - ├─ 初始化命令符号表 - ├─ 创建 FinSH/MSH 线程 - ├─ 初始化接收信号量 - └─ rt_thread_startup(tid) - └─ finsh_thread_entry() - ├─ 绑定控制台设备 uart0 - ├─ 输出 msh /> 提示符 - └─ 循环接收并执行命令 -``` - -应用程序可以通过 `MSH_CMD_EXPORT()` 导出自定义命令。例如: - -```c -#include - -static int hello_cmd(int argc, char **argv) -{ - rt_kprintf("hello msh\n"); - return 0; -} -MSH_CMD_EXPORT(hello_cmd, print hello message); -``` - -### 8. 上午学习小结 - -上午首先学习了 Markdown 的标题、粗体、代码段和表格语法,随后创建并查看了 RT-Thread Studio 的 `HHHello` 工程。通过分析 `applications`、`drivers` 和 `rt-thread` 等目录,理解了应用程序、BSP 驱动、通用设备框架、CPU 移植层与内核源码之间的职责边界;同时梳理了三层 I/O 设备模型及编译、链接和运行流程。最后结合 `components.c`、`board.c`、`rtdef.h` 和 `shell.c`,从源码层面分析了 RT-Thread 启动顺序、自动初始化机制以及 MSH Shell 线程的创建过程。 - -## 下午:Git 与 Gitee 协作 - -### 1. Git 基础概念与常用命令 - -Git 的三个关键区域是工作区、暂存区和本地仓库: - -```text -编辑文件 git add git commit -工作区 ─────────────────> 暂存区 ─────────────────> 本地仓库 - │ - │ git push - ▼ - 远程仓库 -``` - -| 命令 | 作用 | 注意事项 | -| --- | --- | --- | -| `git init` | 把普通目录初始化为 Git 仓库 | 已经 `clone` 的仓库不需要再次执行 | -| `git clone ` | 克隆远程仓库 | 自动创建本地仓库并配置 `origin` | -| `git status` | 查看当前分支和文件状态 | 提交前后都应检查 | -| `git add ` | 将指定修改加入暂存区 | 推荐指定路径,不轻易使用 `git add .` | -| `git commit -m "说明"` | 创建本地提交 | `-m` 后填写清晰的提交说明 | -| `git fetch ` | 获取远程提交和分支信息 | 不自动修改当前工作分支 | -| `git pull ` | 获取并整合远程分支 | 相当于 fetch 后再 merge/rebase | -| `git push ` | 把本地分支推送到远程 | `origin` 通常是自己的仓库 | -| `git remote -v` | 查看远程仓库地址 | 同时显示 fetch 和 push 地址 | -| `git remote set-url origin ` | 修改 `origin` 地址 | 应确认没有误指向别人的仓库 | -| `git switch -c ` | 创建并切换到新分支 | 推荐一个任务或一个 PR 使用一个分支 | - -### 2. 分支、提交与 Pull Request - -![在 Gitee 创建 Pull Request](images/gitee-create-pull-request.jpg) - -分支用于隔离不同任务。例如: - -```bash -git switch -c day1-content -git add -- "2026/第1组/张三/笔记/第一天笔记.md" -git commit -m "提交第一天学习笔记" -git push -u origin day1-content -``` - -推送后,可以从自己的分支向官方仓库发起 Pull Request: - -```text -源仓库:个人 Fork / day1-content -目标仓库:rtthread/rsoc-rtt / master -``` - -PR 是“请求维护者把我的分支修改合并到目标分支”,它不是直接修改官方仓库。使用独立分支还可以避免在 PR 审核期间,后续推送意外进入当前 PR。 - -### 3. Fork 仓库与同步上游 - -Fork 会在个人账号下生成官方仓库的副本。推荐使用两个远程名称: - -```text -origin = 自己的 Fork,例如 tuwasama111/rsoc-rtt -upstream = 官方仓库,例如 rtthread/rsoc-rtt -``` - -首次配置上游仓库: - -```bash -git remote -v -git remote add upstream https://gitee.com/rtthread/rsoc-rtt.git -git fetch upstream -``` - -同步官方 `master` 到个人 Fork: - -```bash -git switch master -git fetch upstream -git merge upstream/master -git push origin master -``` - -`git fetch upstream` 只更新远程跟踪信息;`git merge upstream/master` 才会把官方最新提交合并到当前分支。开始新任务前先同步上游,可以显著减少后续冲突。 - -### 4. Git 冲突产生的原因 - -![分支冲突时间线](images/git-conflict-timeline.png) - -设 `master`、`fork1` 和 `fork2` 最初都位于提交 `t1`。随后 `fork1` 的修改先合并到 `master`,使官方分支前进到 `t2`;而 `fork2` 仍然基于 `t1` 开发。如果 `fork2` 与 `t2` 修改了同一文件的相同或相邻区域,合并或变基时 Git 无法自动判断最终内容,就会产生冲突。 - -冲突并不等于代码损坏,而是 Git 要求开发者明确决定最终结果。不能只按照“旧内容”或“新内容”机械选择,而应结合功能需求决定保留当前分支、上游分支或双方内容。 - -冲突文件中通常会出现以下结构。为避免 Git 把本文档中的教学示例误判为真实冲突,下面三行标记前各保留了一个空格;实际冲突文件中的标记通常从行首开始: - -```text - <<<<<<< HEAD -当前分支的内容 - ======= -上游分支的内容 - >>>>>>> upstream/master -``` - -解决时需要编辑为最终正确内容,并删除 `<<<<<<<`、`=======` 和 `>>>>>>>` 标记。 - -### 5. 使用 merge 解决冲突 - -先同步远端,再切换到发生冲突的功能分支: - -```bash -git fetch upstream -git switch day1-content -git merge upstream/master -``` - -如果出现冲突: - -```bash -git status -``` - -按照以下步骤处理: - -1. 打开冲突文件,理解双方修改目的; -2. 保留当前修改、上游修改或将两者正确组合; -3. 删除冲突标记并保存; -4. 重新编译、运行测试或预览文档; -5. 将已解决文件加入暂存区; -6. 创建合并提交并推送。 - -```bash -git add -- <已解决的文件> -git commit -m "解决与 upstream/master 的合并冲突" -git push origin day1-content -``` - -若判断本次合并方向错误,可以在尚未提交前执行: - -```bash -git merge --abort -``` - -### 6. 使用 rebase 解决冲突 - -`rebase` 会把当前分支的提交重新放到最新上游提交之后,使历史更线性: - -```bash -git fetch upstream -git switch day1-content -git rebase upstream/master -``` - -解决每一个冲突后执行: - -```bash -git add -- <已解决的文件> -git rebase --continue -``` - -如果需要放弃本次变基: - -```bash -git rebase --abort -``` - -变基会改写当前分支的提交哈希。如果该分支已经推送到自己的远程仓库,完成变基后通常需要: - -```bash -git push --force-with-lease origin day1-content -``` - -应使用 `--force-with-lease`,不要直接使用 `--force`。同时不要对多人共享分支随意变基;个人 PR 分支更适合使用 rebase。 - -| 对比项 | merge | rebase | -| --- | --- | --- | -| 历史形式 | 保留分叉并增加合并提交 | 历史较线性 | -| 是否改写已有提交 | 否 | 是 | -| 推送后处理 | 普通 push | 可能需要 `--force-with-lease` | -| 适用场景 | 共享分支、希望保留合并关系 | 个人功能分支、希望整理提交历史 | - -### 7. 使用 TortoiseGit 处理冲突 - -TortoiseGit 与命令行使用的是同一套 Git 数据,基本步骤相同: - -1. 在仓库目录执行 **TortoiseGit → Fetch**,获取上游更新; -2. 切换到产生冲突的分支; -3. 执行 **Merge**,选择 `upstream/master`; -4. 在冲突文件上选择 **Edit conflicts**,通过 TortoiseGitMerge 比较 `Mine`、`Theirs` 和最终结果; -5. 保存后执行 **Resolved**; -6. 提交合并结果并推送个人分支。 - -无论使用图形界面还是命令行,解决冲突后都必须检查最终文件并进行必要验证,不能仅以“冲突标记消失”作为完成标准。 - -### 8. Git 协作实践原则 - -1. 开始任务前先同步 `upstream/master`; -2. 一个任务或一个 PR 使用一个独立分支; -3. 使用 `git status` 和 `git diff --cached` 检查提交范围; -4. 尽量只 `git add` 自己修改的明确路径; -5. 提交说明应说明“谁做了什么”,避免使用 `update` 等模糊描述; -6. PR 审核期间不要向同一源分支推送无关内容; -7. 解决冲突后必须重新编译、测试或预览; -8. 不直接向官方仓库推送,而是推送个人 Fork 后发起 PR。 - -### 9. 下午学习小结 - -下午完成了 Git 工作区、暂存区、本地仓库和远程仓库之间关系的学习,并实践了仓库初始化、克隆、状态检查、暂存、提交、拉取和推送。通过 Fork 与 Pull Request 流程,理解了个人仓库和官方上游仓库之间的协作关系;同时掌握了分支隔离、上游同步以及使用 merge 或 rebase 处理冲突的基本方法。冲突解决的核心不是简单选择“旧版本”或“新版本”,而是在理解双方修改目的后形成正确的最终内容,并完成验证后再提交。 diff --git "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index dbb444cf97c76610ba2fb8f09e9109d9a753b38a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\351\231\210\347\273\247\345\260\247/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,323 +0,0 @@ -# RT-Thread 夏令营第二天学习笔记 - -## 1. 本日学习目标 - -1. 理解裸机前后台系统的局限,以及引入 RTOS 的原因; -2. 掌握 STM32F407 从复位入口到 RT-Thread `main()` 的启动流程; -3. 理解线程控制块、线程五态、优先级抢占和时间片轮转; -4. 区分静态线程与动态线程的内存分配和生命周期; -5. 掌握 SCons、`SConscript` 和 Kconfig 的基本配置方法; -6. 使用 RT-Thread Studio 创建、编译、下载和调试 STM32F407 工程; -7. 通过 LED、按键、监控线程和 MSH 命令完成多线程综合实验。 - -## 2. 从裸机开发到 RTOS - -### 2.1 裸机前后台系统的局限 - -常见裸机程序会在 `while (1)` 中依次执行多个任务。当其中一个任务使用阻塞等待,例如持续等待标志位、外设数据或长延时,后续任务便无法及时执行。例如等待通信数据时,LCD 刷新也可能被迫停止。 - -裸机系统的主要问题包括: - -| 问题 | 表现 | -| --- | --- | -| 并发效率低 | 多个任务按固定顺序串行执行,CPU 难以合理调度 | -| 实时性差 | 一个任务阻塞会影响其他任务的响应时间 | -| 可维护性差 | 任务之间耦合较强,修改局部逻辑可能影响整体时序 | -| 模块化不足 | 功能边界不清晰,代码复用和扩展比较困难 | - -RTOS 将不同功能划分为线程,由内核统一进行状态管理和调度。任务等待时可以让出 CPU,使其他就绪线程继续运行,从而提高系统并发性、实时性和模块化程度。 - -## 3. RT-Thread 内核启动流程 - -### 3.1 工具链入口差异 - -STM32 上电后首先执行启动文件中的 `Reset_Handler`。启动文件完成栈、时钟和 C 运行环境准备,再进入 C 代码。MDK、IAR 和 GCC 的运行库入口与链接规则不同,因此接入 RT-Thread 的入口形式也有所不同。 - -| 工具链 | 常见入口 | 说明 | -| --- | --- | --- | -| MDK/ARMCC | `__main`、`$Sub$$main` | 可通过运行库入口封装进入 RT-Thread | -| IAR | `__low_level_init` 等 | 在应用入口前完成底层初始化 | -| GCC | `_start`、`entry` 或 BSP 入口 | 由启动汇编与链接脚本衔接 | - -具体工程必须结合启动文件和链接脚本分析,不能只凭工具链名称推断唯一函数名。 - -### 3.2 启动调用链 - -![RT-Thread 启动流程](./images/rt-thread-startup-flow.png) - -启动过程可以分为四个阶段: - -1. **处理器与运行库准备**:从向量表进入 `Reset_Handler`,初始化系统时钟,复制 `.data` 段并清零 `.bss` 段; -2. **内核基础初始化**:关闭中断,完成板级硬件、系统定时器和调度器初始化; -3. **系统线程创建**:创建 main 线程、软件定时器线程和 Idle 线程; -4. **启动调度**:调度器选择最高优先级的就绪线程,main 线程执行组件初始化,最终进入应用 `main()`。 - -核心调用关系为: - -```text -Reset_Handler - -> 运行库/工具链入口 - -> rtthread_startup() - -> rt_hw_board_init() - -> rt_components_board_init() - -> rt_system_timer_init() - -> rt_system_scheduler_init() - -> rt_application_init() - -> rt_system_timer_thread_init() - -> rt_thread_idle_init() - -> rt_system_scheduler_start() - -> main_thread_entry() - -> rt_components_init() - -> main() -``` - -### 3.3 启动阶段的重要步骤 - -- **关闭中断**:防止内核关键数据结构尚未准备完成时进入中断处理; -- **板级初始化**:配置系统时钟、系统 Tick、串口控制台等基础硬件; -- **打印版本信息**:串口输出 RT-Thread Logo、版本和构建时间; -- **初始化系统定时器**:为线程延时、超时和软件定时器提供基础; -- **初始化调度器**:建立优先级就绪表和调度相关数据结构; -- **创建 main 线程**:在正常线程上下文中完成组件初始化并进入应用; -- **创建 Idle 线程**:保证系统没有其他就绪线程时仍有线程可运行; -- **启动调度器**:选择最高优先级就绪线程并完成首次上下文切换。 - -## 4. 自动初始化机制 - -### 4.1 初始化宏 - -`INIT_BOARD_EXPORT()` 和 `INIT_APP_EXPORT()` 都基于 `INIT_EXPORT()` 实现: - -```c -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") -``` - -编译器把初始化函数指针放入指定链接段,链接器按初始化等级组织这些段。启动时,RT-Thread 遍历对应段中的函数指针并依次调用。 - -```text -INIT_xxx_EXPORT(fn) - -> 函数指针写入指定链接段 - -> 链接器汇总并排序 - -> 初始化框架遍历段边界 - -> 自动调用 fn() -``` - -`INIT_BOARD_EXPORT()` 主要用于板级资源和底层驱动,在 `rt_components_board_init()` 中执行;`INIT_APP_EXPORT()` 用于应用模块,在 `rt_components_init()` 中执行。 - -本次实验使用: - -```c -INIT_APP_EXPORT(thread_study_init); -``` - -因此三个实验线程会被自动创建,无需在 `main.c` 中硬编码调用。 - -## 5. 线程机制与生命周期 - -### 5.1 线程控制块 - -每个线程都由 `struct rt_thread` 控制块描述。控制块保存线程状态、优先级、时间片、栈地址和当前栈指针等调度所需信息。 - -| 成员 | 含义 | -| --- | --- | -| `stat` | 当前线程状态 | -| `current_priority` | 当前调度优先级 | -| `init_tick` | 初始化时间片 | -| `remaining_tick` | 剩余时间片 | -| `sp` | 上下文切换时保存的栈指针 | -| `stack_addr` | 栈起始地址 | -| `stack_size` | 栈总容量 | - -### 5.2 线程五态 - -RT-Thread 线程的生命周期包括初始态、就绪态、运行态、挂起态和关闭态: - -```text -初始态 --startup--> 就绪态 --调度--> 运行态 - ^ | | - | | 被抢占 | 延时/等待/挂起 - | v v - +--就绪态 挂起态 - ^ | - +--超时/恢复-+ - -运行态 --退出/删除--> 关闭态 -``` - -- 创建或初始化线程后,必须调用 `rt_thread_startup()` 才能进入就绪态; -- 运行线程调用延时或等待 IPC 后会进入挂起态; -- 等待条件满足、超时或被恢复后,线程回到就绪态; -- 线程入口返回或线程被删除后进入关闭态。 - -### 5.3 上下文切换 - -线程切换时,内核先把当前线程的 CPU 寄存器现场压入其线程栈,并保存栈指针;随后装载目标线程保存的栈指针,再从目标栈恢复寄存器现场。这样目标线程就能从上次停止的位置继续运行。 - -### 5.4 调度算法 - -RT-Thread 使用抢占式优先级调度:优先级更高的就绪线程可以抢占当前线程。本工程优先级范围为 0~31,数值越小优先级越高。 - -当多个线程优先级相同时,使用时间片轮转。本次实验中 `th_led1` 与 `th_led2` 的优先级均为 16、时间片均为 5 ticks,因此它们可以在同一优先级队列中轮流运行。优先级为 10 的 `th_monitor` 唤醒时会优先运行。 - -### 5.5 Idle 线程的职责 - -Idle 线程的优先级最低,只有在没有其他就绪线程时才运行。它的常见职责包括: - -- 回收动态线程退出后等待释放的资源; -- 执行空闲钩子函数; -- 配合芯片进入低功耗模式; -- 在特定系统设计中完成看门狗维护等空闲任务。 - -耗时或可能阻塞的业务逻辑不应放入 Idle 钩子,否则会影响资源回收和系统空闲行为。 - -## 6. 静态线程与动态线程 - -### 6.1 静态线程 - -静态线程使用 `rt_thread_init()`,由应用预先提供 TCB 和栈空间: - -```c -static struct rt_thread th_led2; -static rt_uint8_t th_led2_stack[1024]; -``` - -优点是内存占用确定、运行期间不依赖堆分配,适合安全性和确定性要求较高的场景。缺点是内存固定占用,配置过大可能浪费 RAM。 - -### 6.2 动态线程 - -动态线程使用 `rt_thread_create()`,TCB 和栈由系统堆分配。它适合线程数量或生命周期需要动态变化的应用,但必须考虑内存不足、内存碎片和分配失败。 - -本次实验中: - -- `th_led1`:动态创建,控制红色 LED; -- `th_led2`:静态初始化,控制蓝色 LED; -- `th_monitor`:动态创建,监控状态并处理按键事件。 - -## 7. SCons 构建系统 - -### 7.1 `SConscript` 的作用 - -RT-Thread 使用 SCons 管理工程构建。各源码目录中的 `SConscript` 描述当前目录哪些源文件参与编译、头文件路径在哪里,以及源码依赖哪些配置项。 - -常见写法如下: - -```python -from building import * - -cwd = GetCurrentDir() -src = Glob('*.c') -CPPPATH = [cwd] - -group = DefineGroup('Applications', src, - depend=['RT_USING_FINSH'], - CPPPATH=CPPPATH) - -Return('group') -``` - -关键概念: - -| 配置 | 作用 | -| --- | --- | -| `src` / `Glob()` | 指定参与构建的源文件 | -| `CPPPATH` | 添加头文件搜索路径 | -| `DefineGroup()` | 定义一个源码分组 | -| `depend` / `DEPENDS` | 只有依赖的配置宏启用时,源码才加入构建 | - -### 7.2 常用构建命令 - -```text -scons 编译当前工程 -scons -c 清理构建产物 -scons --target=mdk5 生成 MDK5 工程 -scons --target=iar 生成 IAR 工程 -scons --target=vsc 生成 VS Code 工程配置 -scons --dist 打包当前 BSP 为独立工程 -``` - -`--target` 支持的名称取决于当前 RT-Thread 版本。执行前可查看工程文档或 SCons 帮助信息。 - -### 7.3 Kconfig 与图形化配置 - -Kconfig 用于描述功能选项、依赖关系和菜单结构。通过 Env 的 `menuconfig` 或 RT-Thread Studio 图形化配置界面选择功能后,配置结果会写入 `.config` 和 `rtconfig.h`。`SConscript` 再根据这些宏决定哪些源码参与编译。 - -```text -Kconfig 菜单定义 - -> menuconfig/Studio 选择功能 - -> 生成 .config 和 rtconfig.h - -> SConscript 检查依赖宏 - -> 选择性编译源码 -``` - -RT-Thread 软件包也通过这一机制配置和集成,例如 LVGL、内核示例和其他组件。 - -## 8. RT-Thread Studio 实操 - -本次基于 STM32F407ZG 创建工程,主要过程为: - -1. 在 RT-Thread Studio 中按芯片创建 STM32F407 工程; -2. 检查芯片型号、时钟、调试器和串口配置; -3. 使用 ST-Link 下载和单步调试; -4. 在启动入口、`rtthread_startup()` 和线程创建位置设置断点; -5. 观察硬件初始化、调度器初始化和系统线程创建顺序; -6. 使用串口 MSH 查看线程列表和运行状态。 - -课程中的 kernel samples 示例演示了不同优先级线程的调度:高优先级线程先获得 CPU;线程退出后,其动态资源由系统回收。可以使用 `list_thread` 观察线程名称、优先级、状态、栈使用和错误码等信息。 - -## 9. MSH 命令行与自定义命令 - -MSH 是 RT-Thread FinSH 组件提供的命令行接口。通过串口终端可以执行系统命令,查看内核对象和调用自定义调试功能。常用操作包括: - -```text -help 查看可用命令 -list_thread 查看系统线程列表 -thread_dump 执行本次实验导出的自定义命令 -``` - -本实验通过: - -```c -MSH_CMD_EXPORT(thread_dump, dump thread state and remaining stack); -``` - -将 `thread_dump()` 注册为 MSH 命令。命令会打印 `th_led1`、`th_led2` 和 `th_monitor` 的状态、当前优先级及剩余栈空间。 - -![MSH thread_dump 命令](../作业/第二天作业/images/serial-thread-dump.png) - -截图中命令成功执行,三个线程均显示为 `suspend`。这是因为截图时线程正处于 `rt_thread_mdelay()` 或等待阶段,并不表示三个线程发生异常。 - -## 10. 多线程综合实验记录 - -### 10.1 正常运行 - -![线程运行与监控](../作业/第二天作业/images/serial-thread-monitor.png) - -`th_led1` 和 `th_led2` 周期运行,监控线程每 2 秒打印一次状态与剩余栈。多个线程同时使用 `rt_kprintf()` 时,个别输出可能连接在同一行,这是并发打印交错造成的。 - -### 10.2 按键挂起 - -![按键挂起 th_led1](../作业/第二天作业/images/serial-key-suspend.png) - -第一次按键后,`th_led1` 的计数停止,而 `th_led2` 继续增加。该现象比单独观察 `state=suspend` 更能证明 `th_led1` 已被手动挂起,因为线程正常延时期间也会显示 `suspend`。 - -当前 RT-Thread 源码要求 `rt_thread_suspend()` 的目标必须是当前线程,因此由监控线程设置请求,再由 `th_led1` 调用 `rt_thread_suspend(rt_thread_self())` 自行挂起。 - -### 10.3 按键恢复 - -![按键恢复 th_led1](../作业/第二天作业/images/serial-key-resume.png) - -第二次按键后,监控线程调用 `rt_thread_resume(th_led1)`。`th_led1` 从挂起前的计数继续增加,说明线程上下文得到保留并成功恢复。 - -## 11. 学习总结 - -本日从启动流程、线程对象和调度机制三个层面理解了 RT-Thread 内核,并通过 SCons 与 Studio 熟悉了工程构建过程。多线程实验验证了以下内容: - -- 静态与动态线程可以在同一系统中协同运行; -- 高优先级监控线程周期唤醒时会抢占工作线程; -- 相同优先级的两个工作线程可以按时间片轮转; -- 延时、等待和主动挂起都会表现为 `SUSPEND`,必须结合业务现象判断原因; -- 中断服务应保持简短,复杂线程控制应转移到线程上下文; -- `INIT_APP_EXPORT()` 可以解耦应用初始化与 `main.c`; -- `MSH_CMD_EXPORT()` 可以快速增加串口调试命令; -- 栈余量、线程状态和串口日志是排查 RTOS 问题的重要证据。 diff --git "a/2026/\347\254\2541\347\273\204/\351\273\204\344\274\257\350\250\200/\344\275\234\344\270\232/\344\275\234\344\270\232Day1.md" "b/2026/\347\254\2541\347\273\204/\351\273\204\344\274\257\350\250\200/\344\275\234\344\270\232/\344\275\234\344\270\232Day1.md" deleted file mode 100644 index 752f7f5876f0fe805bbf1b20c03a8bb4a26cab2c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\351\273\204\344\274\257\350\250\200/\344\275\234\344\270\232/\344\275\234\344\270\232Day1.md" +++ /dev/null @@ -1,116 +0,0 @@ -# 作业 · Day1 - -> 提交人:黄伯言(第1组)|日期:2026-08-17 - -## 一、开发环境搭建 - -- [x]  安装 RT-Thread Studio -- [x]  替换 `app.asar` 完成破解 -- [x]  SDK Manager 下载 **RT-Thread 4.1.0** 源码 + STM32F4 支持包 -- [x]  确认 ST-Link 驱动已安装 - -## 二、Git 环境配置(已完成) - -## 三、STM32F407 实操 - -### 3.1 工程信息 - -- **工程名**:` -- **RT-Thread 内核版本**:4.1.0(构建时间 `build Aug 17 2026 11:23:12`) -- **板级芯片**:STM32F407 -- **IDE**:RT-Thread Studio -- **工程结构**:`applications/main.c` + `CubeMX Settings` + `drivers` + `libraries` + `linkscripts` + `rt-thread [4.1.0]` + `rtconfig.h` - -### 3.2 串口配置 - -| 参数 | 值 | -| ---- | ---------- | -| 串行端口 | **COM9** | -| 波特率 | **115200** | -| 数据位 | 8 | -| 校验位 | None | -| 停止位 | 1 | -| 流控 | None | -| 编码 | UTF-8 | - -### 3.3 关键代码(`applications/main.c`) - -``` -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-17 RT-Thread first version - */ - -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -int main(void) -{ - rt_kprintf("hello2026\n"); - - return RT_EOK; -} - -``` - -### 3.4 终端运行结果 - -**启动 banner** - -```···· - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 17 2026 10:42:05 - 2006 - 2022 Copyright by RT-Thread team -hello2026 -``` - -**`msh />free` 内存查询** - -``` -msh >free -total : 126168 -used : 4876 -maximum : 7076 -available: 121292 -``` - -```**`msh />free` 内存查询** -msh >list_thread -thread pri status sp stack size max used left tick error --------- --- ------- ---------- ---------- ------ ---------- --- -tshell 20 running 0x000000c8 0x00001000 14% 0x00000006 OK -tidle0 31 ready 0x0000005c 0x00000100 64% 0x0000000a OK -timer 4 suspend 0x0000007c 0x00000200 24% 0x00000009 OK -``` - -``` -msh >list_sem -semaphor v suspend thread --------- --- -------------- -shrx 000 0 -``` - - - -`msh` 交互正常,`help` 命令可列出 `list_thread` / `list_sem` / `list_event` / `list_mutex` / `list_mailbox` / `list_msgqueue` / `list_mempool` / `list_timer` 等 RT-Thread shell 工具。 - -### 3.5 完成项 - -- [x]  创建 STM32F407 Hello World 工程(标准版内核) -- [x]  编译生成 `.elf` / `.bin`,ST-Link 下载 -- [x]  终端查看启动日志(`hello RSOC2026!` + RT-Thread 4.1.1 banner) -- [x]  断点调试可用(RT-Thread Studio 集成) -- [x]  msh 交互正常(`free` / `help` / `list_*` 等命令验证通过) - -## 四、PR 提交说明 - -- 本次提交为「当天作业 / 笔记提交」PR,对上游仓库发起,指定**小组长**为**易莹莹** diff --git "a/2026/\347\254\2541\347\273\204/\351\273\204\344\274\257\350\250\200/\347\254\224\350\256\260/Day1.md" "b/2026/\347\254\2541\347\273\204/\351\273\204\344\274\257\350\250\200/\347\254\224\350\256\260/Day1.md" deleted file mode 100644 index d78a7ab1950be07f8351155b348db2763aa586e1..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2541\347\273\204/\351\273\204\344\274\257\350\250\200/\347\254\224\350\256\260/Day1.md" +++ /dev/null @@ -1,158 +0,0 @@ -# RT-Thread夏令营开营仪式与开发环境搭建培训 - -**会议摘要**:本次会议完成了夏令营开营仪式,明确了学习周与项目周的安排,并指导学员完成了RT-Thread Studio开发环境搭建、Markdown笔记规范学习及STM32F407工程创建与调试。 - ---- - -## 一、夏令营开营与课程安排 - -*童方怡老师首先介绍了夏令营的整体规划与学习要求,明确了后续两周的学习节奏与产出规范。* - -### 1. 夏令营整体规划 - -- **双周学习模式**: - - 第一周为**学习周**,上午授课,下午实操。 - - 第二周为**项目周**,学员将分组完成特定任务。 -- **课程时间安排**: - - 学习周每日上午 **9:00-12:00** 为授课时间。 - - 下午 **14:00** 开始为实操与作业时间。 -- **考核与产出**: - - 每日需完成作业并提交至 Git 仓库。 - - 第二周将根据项目任务进行筛选与分组。 - -### 2. 学习工具与规范 - -- **笔记规范**: - - 要求学员使用 **Markdown** 语法记录学习笔记,并统一提交至指定 Git 仓库。 -- **签到机制**: - - 每日开课前需进行线上签到,确保出勤率。 -- **硬件支持**: - - 针对硬件(星火1号开发板)及快递问题,提供了专门的查询与解决渠道。 - ---- - -## 二、RT-Thread 生态与产品线介绍 - -*王玉强老师详细讲解了RT-Thread的发展历程、开源生态及三条核心产品线。* - -### 1. 生态发展现状 - -- **开源生态**: - - RT-Thread 作为国产自研 RTOS,经历了近 20 年发展。 - - GitHub 星标达 **12.2k**,在国内拥有活跃的开源社区。 -- **版本迭代**: - - 内核版本已迭代至 **V5.3.0**,在国际 RTOS 开源领域保持活跃。 - -### 2. 核心产品线架构 - -| 产品线 | 特点 | -| ---------- | ----------------------------------------------------------- | -| **Nano版** | 极简版内核,包含实时内核、线程同步通信机制及最小实体(线程、时钟、中断、内存管理),基于 Apache 协议开源。 | -| **标准版** | 基于标准 POSIX API,提供丰富的组件(如日志管理、网络协议栈、IoT 协议)及中间件,生态完整且高度可定制化。 | -| **Smart版** | 针对带 MMU 设备,采用混合微内核架构,实现内核态与用户态分离,提升安全性。 | - ---- - -## 三、开发环境搭建与工程创建 - -### 1. 开发环境配置 - -- **IDE 安装与破解**: - - 指导学员安装 RT-Thread Studio,并替换 `app.asar` 文件完成破解。 -- **SDK 管理**: - - 通过 SDK Manager 下载 RT-Thread 源码(版本统一为 **4.0.3**)及 STM32F4 芯片支持包。 -- **驱动检查**: - - 确认设备管理器识别到 ST-Link 驱动,确保硬件连接正常。 - -### 2. 工程创建与调试 - -- **工程创建**: - - 基于 STM32F407 芯片创建工程,选择 Nano 或标准版内核,配置串口参数(波特率 **115200**)。 -- **编译与下载**: - - 演示了工程的编译过程,生成 `.elf` 和 `.bin` 文件,并通过 ST-Link 下载至开发板。 -- **终端调试**: - - 启动终端查看启动日志,验证程序是否正常运行,并演示了断点调试功能。 - ---- - -## 四、Markdown 笔记规范与 Git 协作 - -*为确保学习成果的有效记录与提交,会议详细讲解了文档编写规范与版本控制工具的使用。* - -### 1. Markdown 语法规范 - -- **基础语法**: - - 标题(`#`)、代码块(```` ``` ````)、表格、加粗(`**`)及图片插入(`![]()`)等。 -- **图片管理**: - - 建议将图片保存至 `./figures` 目录,防止项目迁移时资源丢失。 - -### 2. Git 协作流程 - -- **仓库管理**: - - 安装 Git(可尝试配置中文语言包,汉化失败则默认英文) - - 全局身份设置:`git config --global user.name` / `user.email` - - 生成 **SSH 密钥对**(公钥 + 私钥),将公钥添加到 Git 平台账户 → 实现免密认证 - - ##### 核心指令 - - ``` - git init # 初始化仓库 - git clone # 克隆远端仓库 - git pull # 拉取最新代码 - git add # 加入暂存区 - git commit -m "..." # 提交到本地仓库 - git push # 推送到远端仓库 - git checkout -b day1 # 创建并切换分支(日常开发禁直接操作 master) - ``` -- **PR 流程**: - - 作业提交需通过 **Pull Request(PR)** 方式,由小组长负责审核与合并。 - ---- - -## 五、RT-Thread 标准版启动流程(基于 STM32F407 + GCC) - -*RT-Thread 在标准版(基于 STM32F407 + GCC 工具链)下的启动流程可以概括为「汇编上电 → RT-Thread 入口 → 板级/组件初始化 → 调度器启动 → main 线程运行」五个阶段。* - -### 阶段一:芯片上电与汇编启动(Reset Handler) - -1. MCU 上电后从复位向量跳入 `Reset_Handler`(位于启动汇编文件,如 `startup_stm32f407xx.s`)。 -2. 依次完成: - - 设置栈指针 **SP**。 - - 将 `.data` 段从 Flash 拷贝到 RAM,并清零 `.bss` 段。 - - 调用 `SystemInit()` 配置时钟、Flash 等待周期及中断向量基址。 -3. 最后跳转到工具链约定的入口函数: - - **GCC**:`entry()` - - **MDK**:`__main` - - **IAR**:`__iar_program_start` - - > 三者殊途同归,最终都会进入 RT-Thread 的统一入口 `rtthread_startup()`。 - -### 阶段二:RT-Thread 统一入口(rtthread_startup) - -进入 `rtthread_startup()` 后,按顺序执行以下系统级初始化(期间会关闭全局中断,防止被打断): - -| 步骤 | 操作 | -| --- | ------------------------------------------------------------------------------------------------------------------- | -| ① | **关中断**:`rt_hw_interrupt_disable()`,防止初始化过程被中断打断。 | -| ② | **堆初始化**:`rt_system_heap_init()`(若开启动态内存)。 | -| ③ | **板级初始化**:`rt_hw_board_init()` — 配置 SysTick、使能中断、初始化 Pin 驱动、初始化 UART 并绑定为控制台设备。 | -| ④ | **组件预初始化**:通过 `INIT_BOARD_EXPORT` 等宏注册的函数依次执行。 | -| ⑤ | **打印版本**:`rt_show_version()`,串口输出 RT-Thread logo(即终端看到的启动横幅)。 | -| ⑥ | **内核对象初始化**:调度器 `rt_system_scheduler_init()`、定时器、信号量、IPC 等。 | -| ⑦ | **创建系统线程**:
- **main 线程**(入口 `main_entry` → 最终调用用户 `main()`)
- **timer 线程**(软件定时器管理)
- **idle 线程**(负责资源回收) | - -### 阶段三:启动调度器 - -调用 `rt_system_scheduler_start()`,从就绪队列中取出最高优先级线程,切换上下文,正式进入多线程抢占式调度状态。 - -> 注意:RT-Thread 中**优先级数字越小,优先级越高**。此时 main 线程、timer 线程、idle 线程等均参与调度。 - -### 阶段四:进入用户 main() - -`main` 线程被调度后,执行 `main_entry()` → 调用用户编写的 `main()` 函数(位于 `applications/main.c`),例如: - -```c -rt_kprintf("hello rt-thread\n"); -rt_kprintf("hello soc 2026\n"); -``` - - diff --git "a/2026/\347\254\2542\347\273\204/README.md" "b/2026/\347\254\2542\347\273\204/README.md" deleted file mode 100644 index ac3545db2010e359df9e2028eebc0366a1780e3c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/README.md" +++ /dev/null @@ -1 +0,0 @@ -# 第2组 \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 90865873a8cb1c0993a067436b75befc2d287991..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1 +0,0 @@ -两份pr,已完成。 \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 98b77503cbffda3bd58a627310f077cf76763c3e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,151 +0,0 @@ -# RT‑Thread 线程间同步与通信实验报告 -> 实验环境:RT‑Thread 4.1.1 -> 源代码文件:`study_sample.c` -> MSH调试命令:`parking_demo`、`ticket_demo`、`startup_demo`、`package_demo` - -## 目录 -1. [练习一:停车场车位管理(计数信号量)](#练习一停车场车位管理计数信号量) -2. [练习二:多窗口售票系统(互斥量)](#练习二多窗口售票系统互斥量) -3. [练习三:系统启动条件检查(事件集)](#练习三系统启动条件检查事件集) -4. [练习四:快递分拣中心(消息队列)](#练习四快递分拣中心消息队列) -5. [思考题汇总](#思考题汇总) -6. [实验踩坑总结](#实验踩坑总结) -7. [文件提交说明](#文件提交说明) - -## 练习一:停车场车位管理(计数信号量) -### 实验场景 -停车场仅有3个车位,存在5台车辆线程竞争车位资源。计数信号量初始值设置为3代表空闲车位。车辆申请车位2秒未获取到则打印超时信息,获取车位后模拟停留一段时间,离开时释放车位,保证停车场内同时车辆不超过3台。 - -### 使用API -- `rt_sem_create()`:创建动态计数信号量 -- `rt_sem_take(sem, tick)`:申请信号量资源,设置超时时间 -- `rt_sem_release()`:释放信号量资源 - -### 核心逻辑 -1. 创建初始值为3的计数信号量; -2. 创建5个车辆线程; -3. 线程调用`rt_sem_take`申请车位,超时时间2000ms;超时打印等待信息; -4. 获取信号量成功,打印车辆进入信息;延时模拟车辆在停车场停留; -5. 停留结束调用`rt_sem_release`释放车位,打印车辆离开; -6. 实验调试读取信号量内部`value`成员计算占用车位(仅调试,产品禁止直接访问内核结构体成员)。 - -### 输出结果 - -![image-20260819231825099](figures/image-20260819231825099.png) - -### 实验现象 -最多同时3台车辆进入停车场,满足资源上限;车辆2s无法获取车位打印超时,之后继续重试申请车位。 - -### 思考题:为什么这里使用计数信号量,而不是互斥量? -互斥量计数只能为1,具备线程所有权,用于排他访问单一临界资源。 -计数信号量支持初始值大于1,适合管理**多个相同的有限资源**。 -停车场拥有3个车位,属于多资源场景,因此选择计数信号量,互斥量只能表示1个车位,无法满足业务。 - -## 练习二:多窗口售票系统(互斥量) -### 实验场景 -两个售票窗口A、B,总票数20张。使用互斥量保护票数读‑判断‑修改完整临界区,避免多线程竞态,防止重复出票、票数小于0;票售罄之后两个线程自动退出。 - -### 使用API -- `rt_mutex_create()`:创建互斥量 -- `rt_mutex_take()`:获取互斥锁 -- `rt_mutex_release()`:释放互斥锁 - -### 核心逻辑 -1. 全局变量保存剩余票数`ticket = 20`; -2. 创建两个售票线程; -3. **读取票数、判断余票、打印票号、票数自减全部放在互斥锁保护范围内**; -4. 票数等于0时,打印售罄信息,释放互斥锁后线程退出; -5. 模拟延时放在锁的外部,不占用临界区。 - -### 输出结果 - -![image-20260819231911030](figures/image-20260819231911030.png) - -### 对比实验 -1. **去掉互斥量**:读取票数与修改票数之间加入短暂延时,会出现重复售票、票数变成负数,多线程竞态条件引发异常。 -2. **添加互斥量**:完整业务流程加锁,20张票全部正常售出,票号唯一,票数不会小于0。 - -### 思考题:为什么只给 `ticket--` 加锁仍可能不安全? -票数读取判断 `if(ticket>0)` 和自减 `ticket--` 是两条独立CPU指令。 -如果仅仅给自减操作加锁,读取判断没有保护,多个线程可以读到同一个票号,造成重复售票。 -> **临界区需要保护完整的读‑判断‑修改业务流程,而不是只保护单一行代码。** - -## 练习三:系统启动条件检查(事件集) -### 实验场景 -设备上电,需要网络模块、传感器模块、存储模块全部初始化完成,业务线程才允许启动。 -- 网络线程延时1000ms发送就绪事件; -- 传感器线程延时2000ms发送就绪事件; -- 存储线程延时5000ms发送就绪事件; -- 业务线程使用`AND`模式等待全部事件,单次最大等待3000ms;超时打印尚未就绪模块,继续循环等待。 - -事件位定义 -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -``` - -### 使用API -- `rt_event_init()`:静态初始化事件集 -- `rt_event_send()`:发送事件标志位 -- `rt_event_recv()`:等待事件;`RT_EVENT_FLAG_AND`全部位置位才返回成功;`RT_EVENT_FLAG_CLEAR`接收成功自动清除标志。 - -> RT‑Thread4.1.1注意:`RT_EVENT_FLAG_CLEAR`模式超时返回时输出参数`e=0`,需要读取`event.set`获取真实事件标志,超时不会清除事件对象内部标志。 - -### 输出结果 - -![image-20260819231939674](figures/image-20260819231939674.png) - - -### 思考题:如果用三个独立信号量实现这个功能,与使用事件集相比有什么区别? -1. **事件集**:单个对象管理多个bit标志;原生支持`AND`等待全部条件 / `OR`等待任意条件;资源占用小,代码简洁。 -2. **三个独立信号量**:需要创建3个IPC对象;RT‑Thread没有原生API等待“全部信号量到达”,需要手写业务逻辑判断;内存开销更大,代码复杂。 -> 当前场景需要等待多个独立条件全部完成,事件集是最合适组件。 - -## 练习四:快递分拣中心(消息队列) -### 实验场景 -两个发件线程,每个线程生成5条快递消息,合计10条快递;消息队列容量设置为5。分拣线程接收消息,统计总快递数量、各区域快递数量、总重量;队列满时发送线程统计失败次数,不崩溃。 - -消息结构体 -```c -typedef struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; //1华东,2华南,3华北 - rt_uint16_t weight; -}package_msg_t; -``` - -### 使用API -- `rt_mq_init()`:静态初始化消息队列 -- `rt_mq_send()`:发送消息,队列满返回`‑RT_EFULL` -- `rt_mq_recv()`:阻塞接收消息 - -### 核心逻辑 -1. 消息队列容量5,两个发送线程各生成5条快递; -2. 发送线程调用`rt_mq_send`,捕获队列满返回值,统计发送失败次数; -3. 分拣线程循环接收快递消息; -4. 统计总包裹数量、华东/华南/华北各个区域数量、全部快递总重量; -5. 接收满10条快递,打印全部统计结果。 - -### 输出结果 - -![image-20260819232008638](figures/image-20260819232008638.png) - - -## 思考题汇总 -1. **练习一:为什么这里使用计数信号量,而不是互斥量?** -互斥量用于排他访问,所有权绑定线程,计数只能为1;计数信号量可以管理N个相同资源。停车场3个车位属于多资源,适合计数信号量。 - -2. **练习二:为什么只给`ticket--`加锁仍可能不安全?** -票数读取判断和自减是两条独立指令;只保护自减,读取判断无锁,多线程会读到相同票号,发生重复售票;整个读‑判断‑修改业务流程都需要放到临界区锁内。 - -3. **练习三:如果用三个独立信号量实现这个功能,与使用事件集相比有什么区别?** -事件集单对象管理多bit,原生支持AND等待全部条件,代码简洁;3个独立信号量没有“等待全部条件”原生接口,需要手动写逻辑,资源开销更大,实现繁琐。 - -## 实验踩坑总结 -1. IPC接口超时返回值为负数,超时判断必须写`‑RT_ETIMEOUT`,不能直接写`RT_ETIMEOUT`。 -2. 函数内部**栈局部变量不会自动清零,必须手动初始化=0**,否则统计计数得到随机脏数据。 -3. `switch‑case`每一个case末尾必须添加`break`,防止case穿透,多条分支连续执行。 -4. 互斥量:`break`退出循环之前必须释放互斥锁,禁止持有锁直接跳出循环。 -5. 事件集:`RT_EVENT_FLAG_CLEAR`模式超时返回输出参数e等于0,不能用e做超时判断,读取`event.set`获取标志。 \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819231825099.png" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819231825099.png" deleted file mode 100644 index 08048b064d8db1edb8e92eff806a874f83dbbf71..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819231825099.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819231911030.png" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819231911030.png" deleted file mode 100644 index 851772b230738a2ead9b2598143a4933a19f1e21..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819231911030.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819231939674.png" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819231939674.png" deleted file mode 100644 index 81c4b0b6d6420081af7a2963de976d56cf2547b4..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819231939674.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819232008638.png" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819232008638.png" deleted file mode 100644 index b5ad7ab75f8a03d43de9cb61e326b6047a7b9de8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819232008638.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/study.c" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/study.c" deleted file mode 100644 index 104a702182c57da141711103ab1872b8283210a0..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/study.c" +++ /dev/null @@ -1,774 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 yanran the first version - */ - -#include - -#define TASK1_THREAD_PRIORITY 25 -#define TASK1_THREAD_TIMESLICE 5 -static rt_sem_t task1_sem = RT_NULL; - -static char thread1_stack[1024]; -static struct rt_thread thread1; - -static void rt_thread1_entry(void *parameter) -{ - static rt_err_t result; - static rt_uint8_t number = 0; - while(1) - { - - if(0 == number) - { - - result = rt_sem_take(task1_sem, rt_tick_from_millisecond(2000)); - if (result == -RT_ETIMEOUT) - { - rt_kprintf("car1 waiting for a space\n"); - rt_thread_mdelay(1000); - } - else if (result == RT_EOK) - { - number = 1; - rt_kprintf("car1 entered, used spaces: %d\n",3 - (task1_sem->value)); - rt_thread_mdelay(2000); - } - } - - - if(number == 1) - { - number = 0; - rt_sem_release(task1_sem); - rt_kprintf("car1 left\n"); - rt_thread_mdelay(500); - } - } -} - -static char thread2_stack[1024]; -static struct rt_thread thread2; -static void rt_thread2_entry(void *parameter) -{ - static rt_err_t result; - static rt_uint8_t number = 0; - while(1) - { - - if(0 == number) - { - - result = rt_sem_take(task1_sem, rt_tick_from_millisecond(2000)); - if (result == -RT_ETIMEOUT) - { - rt_kprintf("car2 waiting for a space\n"); - rt_thread_mdelay(2000); - } - else if (result == RT_EOK) - { - number = 1; - rt_kprintf("car2 entered, used spaces: %d\n",3 - (task1_sem->value)); - rt_thread_mdelay(4000); - } - } - - if(number == 1) - { - number = 0; - rt_sem_release(task1_sem); - rt_kprintf("car2 left\n"); - rt_thread_mdelay(1000); - } - } -} - -static char thread3_stack[1024]; -static struct rt_thread thread3; -static void rt_thread3_entry(void *parameter) -{ - static rt_err_t result; - static rt_uint8_t number = 0; - while(1) - { - - if(0 == number) - { - - result = rt_sem_take(task1_sem, rt_tick_from_millisecond(2000)); - if (result == -RT_ETIMEOUT) - { - rt_kprintf("car3 waiting for a space\n"); - rt_thread_mdelay(2000); - - } - else if (result == RT_EOK) - { - number = 1; - rt_kprintf("car3 entered, used spaces: %d\n",3 - (task1_sem->value)); - rt_thread_mdelay(5000); - } - } - - if(number == 1) - { - number = 0; - rt_sem_release(task1_sem); - rt_kprintf("car3 left\n"); - rt_thread_mdelay(1000); - } - } -} -// -static char thread4_stack[1024]; -static struct rt_thread thread4; -static void rt_thread4_entry(void *parameter) -{ - static rt_err_t result; - static rt_uint8_t number = 0; - while(1) - { - - if(0 == number) - { - - result = rt_sem_take(task1_sem, rt_tick_from_millisecond(2000)); - if (result == -RT_ETIMEOUT) - { - rt_kprintf("car4 waiting for a space\n"); - rt_thread_mdelay(2000); - } - else if (result == RT_EOK) - { - number = 1; - rt_kprintf("car4 entered, used spaces: %d\n",3 - (task1_sem->value)); - rt_thread_mdelay(3000); - } - } - - - if(number == 1) - { - number = 0; - rt_sem_release(task1_sem); - rt_kprintf("car4 left\n"); - rt_thread_mdelay(1000); - } - } -} -// -static char thread5_stack[1024]; -static struct rt_thread thread5; -static void rt_thread5_entry(void *parameter) -{ - static rt_err_t result; - static rt_uint8_t number = 0; - while(1) - { - - if(0 == number) - { - - result = rt_sem_take(task1_sem, rt_tick_from_millisecond(2000)); - if (result == -RT_ETIMEOUT) - { - rt_kprintf("car5 waiting for a space\n"); - rt_thread_mdelay(2000); - } - else if (result == RT_EOK) - { - number = 1; - rt_kprintf("car5 entered, used spaces: %d\n",3 - (task1_sem->value)); - rt_thread_mdelay(4000); - } - } - - - if(number == 1) - { - number = 0; - rt_sem_release(task1_sem); - rt_kprintf("car5 left\n"); - rt_thread_mdelay(1000); - } - } -} - -/* 信号量的初始化 */ -int parking_demo() -{ - /* 创建一个动态信号量,初始值是3 */ - task1_sem = rt_sem_create("task1_sem", 3, RT_IPC_FLAG_PRIO); - if (task1_sem == RT_NULL) - { - rt_kprintf("create task1_sem semaphore failed.\n"); - return -1; - } - else - { - rt_kprintf("create done. task1_sem semaphore value = 3.\n"); - } - - rt_thread_init(&thread1, - "thread1", - rt_thread1_entry, - RT_NULL, - &thread1_stack[0], - sizeof(thread1_stack), - TASK1_THREAD_PRIORITY, TASK1_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&thread1, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&thread1); - - rt_thread_init(&thread2, - "thread2", - rt_thread2_entry, - RT_NULL, - &thread2_stack[0], - sizeof(thread2_stack), - TASK1_THREAD_PRIORITY, TASK1_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&thread2, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&thread2); - - rt_thread_init(&thread3, - "thread3", - rt_thread3_entry, - RT_NULL, - &thread3_stack[0], - sizeof(thread3_stack), - TASK1_THREAD_PRIORITY, TASK1_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&thread3, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&thread3); - - rt_thread_init(&thread4, - "thread4", - rt_thread4_entry, - RT_NULL, - &thread4_stack[0], - sizeof(thread4_stack), - TASK1_THREAD_PRIORITY, TASK1_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&thread4, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&thread4); - - rt_thread_init(&thread5, - "thread5", - rt_thread5_entry, - RT_NULL, - &thread5_stack[0], - sizeof(thread5_stack), - TASK1_THREAD_PRIORITY, TASK1_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&thread5, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&thread5); - - return 0; -} - -/* 导出到 msh 命令列表中 */ -MSH_CMD_EXPORT(parking_demo, task1 sample); - -/***************************************题目二************************************************************/ - -#define TASK2_THREAD_PRIORITY 8 -#define TASK2_THREAD_TIMESLICE 5 - -/* 指向互斥量的指针 */ -static rt_mutex_t dynamic_mutex = RT_NULL; -static rt_uint8_t ticket = 20; - -static char task2_thread1_stack[1024]; -static struct rt_thread task2_thread1; -static void task2_rt_thread_entry1(void *parameter) -{ - while (1) - { - /* 线程1获取到互斥量后,售票,然后释放互斥量 */ - rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); - - if(ticket == 0) - { - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: 0\n"); - break; - } - - if(ticket > 0) - { - rt_kprintf("window A sold ticket %d\n",ticket); - ticket -= 1; - - } - - rt_mutex_release(dynamic_mutex); - rt_thread_mdelay(10); - } - - rt_mutex_release(dynamic_mutex); -} - -static char task2_thread2_stack[1024]; -static struct rt_thread task2_thread2; -static void task2_rt_thread_entry2(void *parameter) -{ - while (1) - { - /* 线程2获取到互斥量后,售票,然后释放互斥量 */ - rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); - - if(ticket == 0) - { - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: 0\n"); - break; - } - else if(ticket == 1) - { - rt_kprintf("window B sold ticket %d\n",ticket); - ticket -= 1; - - } - - else - { - rt_kprintf("window B sold ticket %d\n",ticket); - ticket -= 1; - rt_kprintf("window B sold ticket %d\n",ticket); - ticket -= 1; - - } - - rt_mutex_release(dynamic_mutex); - rt_thread_mdelay(10); - } - - rt_mutex_release(dynamic_mutex); -} - -/* 互斥量示例的初始化 */ -int ticket_demo(void) -{ - /* 创建一个动态互斥量 */ - dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_PRIO); - if (dynamic_mutex == RT_NULL) - { - rt_kprintf("create dynamic mutex failed.\n"); - return -1; - } - - rt_thread_init(&task2_thread1, - "task2_thread1", - task2_rt_thread_entry1, - RT_NULL, - &task2_thread1_stack[0], - sizeof(task2_thread1_stack), - TASK2_THREAD_PRIORITY, TASK2_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&task2_thread1, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&task2_thread1); - - rt_thread_init(&task2_thread2, - "task2_thread2", - task2_rt_thread_entry2, - RT_NULL, - &task2_thread2_stack[0], - sizeof(task2_thread2_stack), - TASK2_THREAD_PRIORITY, TASK2_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&task2_thread2, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&task2_thread2); - return 0; -} - -/* 导出到 msh 命令列表中 */ -MSH_CMD_EXPORT(ticket_demo, mutex sample); - -/***********************************************第三题*******************************************/ - -#define task3_THREAD_PRIORITY 9 -#define task3_THREAD_TIMESLICE 5 - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) - -/* 事件控制块 */ -static struct rt_event event; - -static char network_thread_stack[1024]; -static struct rt_thread network_thread; -/* network线程入口函数 */ -static void network_send_event(void *param) -{ - rt_thread_mdelay(1000); - rt_kprintf("network ready\n"); - rt_event_send(&event, EVENT_NET_READY); -} - - -static char sensor_thread_stack[1024]; -static struct rt_thread sensor_thread; -/* sensor线程入口函数 */ -static void sensor_send_event(void *param) -{ - rt_thread_mdelay(2000); - rt_kprintf("sensor ready\n"); - rt_event_send(&event, EVENT_SENSOR_READY); -} - - -static char storage_thread_stack[1024]; -static struct rt_thread storage_thread; -/* storage线程入口函数 */ -static void storage_send_event(void *param) -{ - rt_thread_mdelay(5000); - rt_kprintf("storage ready\n"); - rt_event_send(&event, EVENT_STORAGE_READY); -} - - -static char business_thread_stack[1024]; -static struct rt_thread business_thread; -/* business线程入口函数 */ -static void business_recv_event(void *param) -{ - rt_uint32_t e; - rt_err_t event_err; - - while(1) - { - event_err = rt_event_recv(&event, (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY), - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - rt_tick_from_millisecond(3000), &e); - if(event_err == -RT_ETIMEOUT) - { - if(!(event.set & EVENT_NET_READY) ) - { - rt_kprintf("wait modules timeout, network is not ready\n"); - } - - if(!(event.set & EVENT_SENSOR_READY) ) - { - rt_kprintf("wait modules timeout, sensor is not ready\n"); - } - - if(!(event.set & EVENT_STORAGE_READY) ) - { - rt_kprintf("wait modules timeout, storage is not ready\n"); - } - } - - if(event_err == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - rt_event_detach(&event); - break; - } - - } -} - - -int startup_demo(void) -{ - rt_err_t result; - - /* 初始化事件对象 */ - result = rt_event_init(&event, "event", RT_IPC_FLAG_PRIO); - if (result != RT_EOK) - { - rt_kprintf("init event failed.\n"); - return -1; - } - - rt_thread_init(&network_thread, - "network_thread", - network_send_event, - RT_NULL, - &network_thread_stack[0], - sizeof(network_thread_stack), - task3_THREAD_PRIORITY, task3_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&network_thread, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&network_thread); - - - rt_thread_init(&sensor_thread, - "sensor_thread", - sensor_send_event, - RT_NULL, - &sensor_thread_stack[0], - sizeof(sensor_thread_stack), - task3_THREAD_PRIORITY, task3_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&sensor_thread, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&sensor_thread); - - - rt_thread_init(&storage_thread, - "storage_thread", - storage_send_event, - RT_NULL, - &storage_thread_stack[0], - sizeof(storage_thread_stack), - task3_THREAD_PRIORITY, task3_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&storage_thread, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&storage_thread); - - - rt_thread_init(&business_thread, - "business_thread", - business_recv_event, - RT_NULL, - &business_thread_stack[0], - sizeof(business_thread_stack), - task3_THREAD_PRIORITY, task3_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&business_thread, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&business_thread); - - return 0; -} - -/* 导出到 msh 命令列表中 */ -MSH_CMD_EXPORT(startup_demo, event sample); - -/*******************************************第四题**********************************************/ - -#define task4_THREAD_PRIORITY 25 -#define task4_THREAD_TIMESLICE 10 - -//region = 1:华东 -//region = 2:华南 -//region = 3:华北 - -/* 消息队列控制块 */ -static struct rt_messagequeue mq; -/* 消息队列中用到的放置消息的内存池 */ -static rt_uint8_t msg_pool[2048]; - -typedef struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}package_msg_t; - - -static char msgsend_thread1_stack[1024]; -static struct rt_thread msgsend_thread1; -/* 线程1入口函数 */ -static void msgsend_thread1_entry(void *parameter) -{ - - package_msg_t packmeg[5] = {{1,1,500} , {2,2,600} , {3,3,700} , {4,1,800} , {5,2,900}}; - package_msg_t * buf = &packmeg[0]; - rt_uint8_t failcnt = 0,sendcnt = 0; - while (1) - { - int result; - /* 发送消息到消息队列中 */ - result = rt_mq_send(&mq, buf, sizeof(packmeg[0])); - if (result == -RT_EFULL) - { - failcnt++; - rt_kprintf("发送线程1发送失败次数:%d\n",failcnt); - } - if(result == RT_EOK) - { - switch(buf->region) - { - case 1: rt_kprintf("package %d -> east,weight: %d g\n",buf->id,buf->weight);break; - case 2: rt_kprintf("package %d -> south,weight: %d g\n",buf->id,buf->weight);break; - case 3: rt_kprintf("package %d -> north,weight: %d g\n",buf->id,buf->weight);break; - } - sendcnt++; - if(sendcnt == 5) - { - break; - } - buf ++; - rt_thread_mdelay(200); - } - } -} - - -static char msgsend_thread2_stack[1024]; -static struct rt_thread msgsend_thread2; -/* 线程2入口函数 */ -static void msgsend_thread2_entry(void *parameter) -{ - int result; - package_msg_t packmeg[5] = {{6,3,550} , {7,1,650} , {8,2,750} , {9,3,850} , {10,1,950}}; - package_msg_t * buf = &packmeg[0]; - rt_uint8_t failcnt = 0,sendcnt = 0; - while (1) - { - /* 发送消息到消息队列中 */ - result = rt_mq_send(&mq, buf, sizeof(packmeg[0])); - if (result == -RT_EFULL) - { - failcnt++; - rt_kprintf("发送线程1发送失败次数:%d\n",failcnt); - } - if(result == RT_EOK) - { - switch(buf->region) - { - case 1: rt_kprintf("package %d -> east,weight: %d g\n",buf->id,buf->weight);break; - case 2: rt_kprintf("package %d -> south,weight: %d g\n",buf->id,buf->weight);break; - case 3: rt_kprintf("package %d -> north,weight: %d g\n",buf->id,buf->weight);break; - } - sendcnt++; - if(sendcnt == 5) - { - break; - } - buf++; - rt_thread_mdelay(200); - } - } -} - - -static char msgrscv_thread_stack[1024]; -static struct rt_thread msgrscv_thread; -/* 分拣线程入口函数 */ -static void msgrscv_thread_entry(void *parameter) -{ - rt_uint8_t recvcnt = 0, eastcnt = 0, southcnt = 0, northcnt = 0; - rt_uint32_t totalweigh = 0; - package_msg_t packmeg; - while (1) - { - if (rt_mq_recv(&mq, &packmeg, sizeof(packmeg), RT_WAITING_FOREVER) == RT_EOK) - { - recvcnt++; - switch(packmeg.region) - { - case 1 :eastcnt++;break; - case 2 :southcnt++;break; - case 3 :northcnt++;break; - } - totalweigh += packmeg.weight; - - if(recvcnt == 10) - { - rt_kprintf("total packages: %d\n",recvcnt); - rt_kprintf("east: %d, south: %d, north: %d\n",eastcnt,southcnt,northcnt); - rt_kprintf("total weight: %d\n",totalweigh); - rt_mq_detach(&mq); - break; - } - } - } -} - - -int package_demo(void) -{ - rt_err_t result; - - /* 初始化消息队列 */ - result = rt_mq_init(&mq, - "mqt", - &msg_pool[0], /* 内存池指向msg_pool */ - sizeof(package_msg_t), /* 每个消息的大小 */ - 5 * sizeof(package_msg_t), /* 内存池的大小 */ - RT_IPC_FLAG_PRIO); /* 如果有多个线程等待,按照先来先得到的方法分配消息 */ - - if (result != RT_EOK) - { - rt_kprintf("init message queue failed.\n"); - return -1; - } - - rt_thread_init(&msgsend_thread1, - "msgsend_thread1", - msgsend_thread1_entry, - RT_NULL, - &msgsend_thread1_stack[0], - sizeof(msgsend_thread1_stack), - task4_THREAD_PRIORITY, task4_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&msgsend_thread1, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&msgsend_thread1); - - - rt_thread_init(&msgsend_thread2, - "msgsend_thread2", - msgsend_thread2_entry, - RT_NULL, - &msgsend_thread2_stack[0], - sizeof(msgsend_thread2_stack), - task4_THREAD_PRIORITY, task4_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&msgsend_thread2, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&msgsend_thread2); - - - rt_thread_init(&msgrscv_thread, - "msgrscv_thread", - msgrscv_thread_entry, - RT_NULL, - &msgrscv_thread_stack[0], - sizeof(msgrscv_thread_stack), - task4_THREAD_PRIORITY, task4_THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&msgrscv_thread, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&msgrscv_thread); - - return 0; -} - -/* 导出到 msh 命令列表中 */ -MSH_CMD_EXPORT(package_demo, msgq sample); - - - - - - - - - - diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 6cb6228c15d0c85c42d2175b7d39508cf633af8d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,150 +0,0 @@ -# RT‑Thread多线程实验报告 -## Part 1 理论知识梳理 -### 1 STM32F407启动流程与RT‑Thread引导机制 -#### 1.1 从上电复位到main函数完整调用链 -STM32F407上电后执行硬件复位,硬件自动读取复位向量表,获取栈顶指针以及复位处理函数入口,跳转到`Reset_Handler`。 - -1. 汇编启动文件`startup_stm32f407xx.s`完成内存初始化:将Flash中`.data`段的初始化数据拷贝到SRAM,把`.bss`段全部清零,保证全局未初始化变量默认为0。 -2. 调用ST库的`SystemInit()`,完成FPU浮点单元使能、中断向量表VTOR配置、RCC外设时钟复位等芯片底层操作。 -3. 根据编译器不同进入对应入口函数:MDK‑ARMCC为`$Sub$$main`,IAR为`__low_level_init`,本工程使用GCC工具链,入口为`entry()`。 -4. 进入RT‑Thread内核总入口`rtthread_startup()`,首先关闭全局中断,防止内核初始化过程被中断打断。 -5. 执行`rt_hw_board_init()`板级初始化:完成RT‑Thread堆内存注册、板卡时钟配置、控制台串口设备绑定,调用`rt_components_board_init()`执行`INIT_BOARD_EXPORT`注册的板级初始化函数。 -6. 依次完成内核组件初始化:系统定时器初始化、调度器初始化、信号子系统初始化。 -7. `rt_application_init()`创建main线程,线程入口是`main_thread_entry`,并不会直接调用用户编写的`main()`。 -8. 创建系统后台线程:软件定时器线程负责处理延时、软件定时器任务;idle空闲线程,CPU无就绪任务时运行,承担资源回收、低功耗钩子执行。 -9. 调用`rt_system_scheduler_start()`正式启动调度器,系统进入多线程运行环境。 -10. main线程获取CPU之后,在`main_thread_entry`内部执行`rt_components_init()`,运行`INIT_APP_EXPORT`等应用层级注册函数,最后调用用户业务`main()`。 - -> 关键要点:调度器启动之前系统处于裸机、中断关闭状态;堆、调度器、系统线程全部准备完毕之后,才开启多任务,保证用户线程运行时内核环境完整可用。 -> -> ![image-20260818225544529](figures/image-20260818225544529.png) - -#### 1.2 自动初始化原理:INIT_BOARD_EXPORT 与 INIT_APP_EXPORT -自动初始化机制目的是避免在main函数中堆砌大量模块初始化调用。借助编译链接,把初始化函数指针存放至自定义段,内核启动阶段遍历段内指针批量执行初始化函数。 - -1. **宏封装生成函数指针** -`INIT_EXPORT`为底层基础宏,接收函数名与等级字符串。宏定义一个`const`类型函数指针变量,把目标函数地址赋值给该指针;通过`RT_SECTION`将变量放置到`.rti_fn.xx`自定义段,添加`RT_USED`属性,避免链接器判定函数未被引用而直接裁剪。 - -上层封装不同等级初始化宏: -- `INIT_BOARD_EXPORT(fn)`等级为`"1"`,用于板级初始化; -- `INIT_APP_EXPORT(fn)`等级为`"6"`,用于应用层初始化。 - -2. **链接脚本控制段排布** -链接脚本中配置 `KEEP(*(SORT(.rti_fn*)))`。`SORT`按照段名字符串排序,不同等级函数指针按照1→2→3…→6在内存中连续排布;`KEEP`保证段不会被链接器丢弃。 - -3. **内核遍历调用函数** -内核提供两组遍历接口: -- `rt_components_board_init()`:遍历等级1的段,执行`INIT_BOARD_EXPORT`注册函数。该函数在`rt_hw_board_init`内部调用,此时时钟、堆、串口硬件就绪,但调度器尚未启动,适合板卡外设、底层驱动初始化。 -- `rt_components_init()`:遍历2~6等级段,`INIT_APP_EXPORT`注册函数在此执行。运行于main线程上下文,调度器已经启动,多任务环境就绪,适合用户业务模块、线程创建等逻辑。 - -本次实验模块入口使用`INIT_APP_EXPORT`修饰,无需修改`main.c`,系统启动时自动执行,完成三条实验线程创建。 - -### 2 内核基础与对象模型 -#### 2.1 面向对象设计思想与线程控制块 -RT‑Thread使用C语言模拟面向对象思想,线程、信号量、消息队列、定时器、设备驱动全部抽象为内核对象。所有对象头部内嵌统一`struct rt_object`基结构体,包含对象名称、类型标记、标志位、双向链表节点。系统维护对象容器数组,不同类型对象挂载到对应链表,对外提供统一创建、销毁、按名字查找接口。新增内核组件仅扩展对象类型,原有管理逻辑无需大量修改,满足开闭原则。 - -线程控制块`struct rt_thread`保存线程全部描述信息,关键成员说明: - -|成员|功能说明| -| ---- | ---- | -|`stat`|线程状态标记,标记初始、就绪、运行、挂起、关闭;调度器依靠该成员判断线程是否允许参与CPU调度| -|`current_priority`|线程实际运行优先级,数值越小优先级越高,是抢占调度判断依据| -|`init_tick`|线程创建时配置的时间片;线程重新获取CPU,剩余时间片重置为此值| -|`sp`|线程栈指针,上下文切换核心;线程切出将寄存器现场压栈并保存sp;切回线程读取sp恢复寄存器| -|`stack_addr`|线程栈内存起始地址;栈溢出检测、剩余栈统计依赖该地址| -|`stack_size`|线程栈总字节大小| -|`entry`|线程入口函数地址| - -#### 2.2 静态对象与动态对象对比、高可靠场景选用静态线程原因 - -|对比维度|静态内核对象|动态内核对象| -| ---- | ---- | ---- | -|内存分配位置|TCB、线程栈定义为全局数组,编译期分配,存放于BSS段|运行时调用`rt_malloc`,从系统堆申请内存| -|创建API|`rt_thread_init()`,用户传入预先定义好的TCB、栈缓冲区|`rt_thread_create()`,内核自动分配TCB与栈内存| -|生命周期|跟随程序整个生命周期,编译期内存地址固定|运行时动态生成,支持删除销毁| -|资源释放|无释放操作,内存由链接脚本管理|调用`rt_thread_delete()`,空闲线程延迟回收,内存归还堆| -|运行风险|不存在内存申请失败,资源大小编译阶段即可确定|堆耗尽会创建失败;频繁创建销毁容易产生内存碎片| - -工业控制、汽车电子等高可靠性场景优先选用静态线程: -1. **行为可预测**:TCB、栈内存编译期确定地址,运行不依赖堆,不会出现运行时内存不足导致线程创建失败,可以做栈空间静态上限分析。 -2. **消除内存碎片**:堆反复申请释放会产生外部碎片,长时间运行可能无法分配连续大块内存;静态对象完全不依赖堆,规避碎片风险。 -3. **时序确定,故障隔离**:动态线程删除依靠idle线程延迟回收,回收时机不确定;静态对象没有销毁回收流程,时序行为稳定,便于安全认证测试。 - -### 3 线程状态机与调度算法 -#### 3.1 线程五态模型 -RT‑Thread线程分为初始态、就绪态、运行态、挂起态、关闭态,不同API触发状态迁移: - -|状态|含义|典型触发API| -| ---- | ---- | ---- | -|INIT初始态|线程完成初始化,还未加入调度器|`rt_thread_create()` / `rt_thread_init()`| -|READY就绪态|线程条件满足,等待分配CPU时间片|`rt_thread_startup()`、`rt_thread_resume()`、延时超时唤醒;被高优先级线程抢占让出CPU| -|RUNNING运行态|正在占用CPU执行代码|调度器选中最高优先级就绪线程;无直接应用API,由内核完成切换| -|SUSPEND挂起态|线程放弃CPU,不再参与调度|`rt_thread_mdelay()`延时、`rt_thread_suspend()`主动挂起、等待信号量/消息队列资源不满足| -|CLOSE关闭态|线程执行结束或者被删除,等待资源回收|`rt_thread_exit()`、`rt_thread_delete()`| - -状态迁移关键点: -1. `rt_thread_startup()`将初始态线程切换至就绪态,加入就绪链表; -2. 就绪态切换到运行态完全由调度器决定,应用代码无法直接操作; -3. `rt_thread_yield()`让出CPU,线程依旧保持就绪态,不会进入挂起; -4. 静态线程使用`rt_thread_detach()`,仅脱离对象容器,不会释放静态内存;动态线程使用`rt_thread_delete()`。 - -![image-20260818225609547](figures/image-20260818225609547.png) - -#### 3.2 调度机制剖析 -##### (1)基于优先级的抢占式调度 -RT‑Thread调度器采用优先级位图搭配双向链表,查找最高优先级就绪任务时间复杂度O(1)。 - -调度规则:系统始终选择就绪队列当中优先级数值最小(优先级最高)的线程运行。 -当高优先级线程变为就绪态,无论当前低优先级线程执行到何处,都会立刻触发任务切换,高优先级线程抢占CPU。抢占发生在两种场景: -1. 任务上下文:调用释放信号量、恢复线程等API内部主动触发调度; -2. 中断上下文:系统tick节拍中断退出前,检查就绪队列,判断是否需要抢占切换。 - -本次实验monitor线程优先级10,高于两个工作线程16;monitor一旦就绪就会抢占LED工作线程,保证状态打印按时输出。 - -##### (2)时间片轮转调度 -时间片轮转仅针对**相同优先级**的多个就绪线程。同优先级线程挂载在同一个就绪链表,每个线程拥有时间片参数`init_tick`。每一次系统tick中断,线程剩余时间片`remaining_tick`自减;计数归零,线程移动到链表尾部,让出CPU给链表下一个同优先级线程,剩余时间片重新加载初始时间片。 - -本实验`th_led1`、`th_led2`优先级均为16,时间片5ticks;但线程内部调用`rt_thread_mdelay`主动挂起,线程大部分时间处于挂起状态,实际线程切换更多由延时唤醒事件驱动。 - -## Part 2 多线程实战编程 -### 任务需求说明 -1. **工作线程1 th_led1(动态创建)** -优先级16,时间片5ticks,栈大小1024字节。线程循环翻转板载红色LED,每次循环打印计数,读取输出系统节拍`rt_tick_get()`。使用`rt_thread_create`从堆动态分配TCB与栈。 - -2. **工作线程2 th_led2(静态初始化)** -优先级16,时间片5ticks。全局定义线程控制块TCB与栈缓冲区,调用`rt_thread_init()`完成静态初始化。循环翻转板载蓝色LED,循环内部打印运行计数。 - -3. **监控控制线程 th_monitor(动态创建)** -优先级10,优先级高于两个工作线程;线程内部`rt_thread_mdelay(2000)`实现2秒周期。每次唤醒读取th_led1、th_led2线程状态、剩余栈空间并打印输出。 -板载按键触发一次,挂起th_led1;再次触发按键,恢复th_led1运行。按键使用外部中断,中断服务函数只置位标志变量,线程挂起恢复操作放在monitor线程轮询处理,禁止在中断内直接调用线程操作API。 - -4. 系统集成调试 -- 使用`INIT_APP_EXPORT()`注册模块初始化函数,模块自动启动,`main.c`不增加业务调用代码。 -- 使用`MSH_CMD_EXPORT()`导出自定义FinSH命令`thread_dump`,串口终端输入命令打印线程状态、剩余栈大小。 - -### 关键实现要点 -1. 剩余栈统计原理:动态线程创建时内核使用魔数`'#'(0x23)`填充整个栈;静态线程需要用户手动使用memset填充魔数。Cortex‑M栈向下生长,从栈起始地址向高地址扫描连续魔数,统计字节数量即为剩余栈大小。 -2. 按键中断处理:中断回调仅设置标志位,`rt_thread_suspend`、`rt_thread_resume`在线程中执行,不允许在中断服务函数直接调用。 -3. 打印格式注意:RT‑Thread的`rt_vsnprintf`与标准C库行为存在差异,`%s`后的数字代表最大输出精度,不是最小输出宽度,打印线程名称需要注意格式,避免字符串截断。 -4. monitor线程优先级高于业务线程,保证2秒周期打印不会被业务线程阻塞;同时优先级不能高于msh shell线程,防止串口shell被日志刷屏,无法输入调试命令。 - -### 实验现象 -1. 上电后红色LED周期性翻转,蓝色LED以不同周期翻转;串口输出线程计数、系统tick数值。 -2. monitor线程每2秒输出一次两个线程状态、剩余栈字节。 -3. 按下板载按键,红色LED停止翻转,monitor日志打印挂起提示;再次按下按键,红色LED恢复闪烁,日志打印恢复提示。 -4. msh串口输入`thread_dump`,终端输出表格,展示线程名称、状态、优先级、剩余栈。 - -### 实验结果分析 -1. 抢占调度验证:monitor线程优先级更高,2s定时唤醒后可以抢占业务线程,周期性日志不会丢失。打印时刻业务线程经常处于SUSPEND状态,是业务线程执行`rt_thread_mdelay`进入休眠。 -2. 同优先级轮转:两个LED线程优先级相同,因为内部延时挂起,多数时间不在就绪态,线程切换主要由延时唤醒事件驱动。 -3. 线程状态流转:LED线程在RUNNING和SUSPEND之间来回切换,符合五态模型;按键触发完成READY↔SUSPEND完整状态迁移。 -4. 栈余量:两个线程栈配置1024字节,运行后栈剩余空间充足,栈大小配置合理。 -5. 自动初始化生效:不修改main.c,系统启动自动创建全部业务线程;msh自定义命令正常调用,调试功能可用。 - -### 串口日志输出 -![image-20260818223212900](figures/image-20260818223212900.png) - - - -![image-20260818223631117](figures/image-20260818223631117.png) - diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818223212900.png" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818223212900.png" deleted file mode 100644 index 5a0fdc675fe004547a231f00da8436b7140aaa46..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818223212900.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818223631117.png" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818223631117.png" deleted file mode 100644 index 6e8088e20afeea97f8a9b10c9909db0b9afe71f5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818223631117.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818225544529.png" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818225544529.png" deleted file mode 100644 index 24414822b74926a6ca0feb8d9792da13d59da4a2..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818225544529.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818225609547.png" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818225609547.png" deleted file mode 100644 index 7c8dabc4f9d55f57616028adc304f29d5264aaf6..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818225609547.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 2826b5cd5f2023f4aa90e5ccfd8b62d28948bb83..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-18 yanran the first version - */ - -#include -#include -#include - -/*-----------------------引脚定义-----------------------*/ -#define PIN_LED_R rt_pin_get("PF.12") -#define PIN_LED_B rt_pin_get("PF.11") -#define PIN_KEY_UP rt_pin_get("PC.5") - -/*-----------------------线程参数-----------------------*/ -#define TH_LED1_PRIORITY 16 -#define TH_LED1_TIMESLICE 5 -#define TH_LED1_STACK_SIZE 1024 - -#define TH_LED2_PRIORITY 16 -#define TH_LED2_TIMESLICE 5 -#define TH_LED2_STACK_SIZE 1024 - -#define TH_MONITOR_PRIORITY 10 -#define TH_MONITOR_TIMESLICE 5 -#define TH_MONITOR_STACK_SIZE 1024 - -#define TH_LED1_DELAY_MS 500 -#define TH_LED2_DELAY_MS 1000 -#define TH_MONITOR_PERIOD_MS 2000 - -/*-------------------------线程句柄--------------------*/ -static rt_thread_t th_led1 = RT_NULL; -static rt_thread_t th_led2 = RT_NULL; -static rt_thread_t th_monitor = RT_NULL; - - -/* th_led2 采用静态方式:TCB 与栈均在全局数据段静态定义 */ -static struct rt_thread led2_thread; -static rt_uint8_t led2_stack[TH_LED2_STACK_SIZE]; - -/* ===================== 按键与挂起控制 ===================== */ -static volatile rt_uint8_t key_pressed = 0; /* ISR 中置位,monitor 中消费 */ -static volatile rt_uint8_t led1_suspend_req = 0; /* 请求 th_led1 自行挂起 */ - -/* 按键中断回调(ISR 上下文,仅置位标志) */ -static void key_up_callback(void *args) -{ - key_pressed = 1; -} - -/* ===================== 工具函数 ===================== */ - -/* - * 计算线程剩余(未使用)栈空间。 - * RT-Thread 创建线程时用 '#'(0x23) 填充整个栈区,ARM 栈向下生长, - * 从低地址向高地址扫描连续 '#' 的字节数即为剩余栈。 - */ -static rt_uint32_t get_stack_remain(rt_thread_t thread) -{ - rt_uint32_t remain = 0; - rt_uint8_t *p = (rt_uint8_t *)thread->stack_addr; - rt_uint8_t *end = (rt_uint8_t *)((rt_ubase_t)thread->stack_addr + thread->stack_size); - - while ((p < end) && (*p == '#')) - { - remain++; - p++; - } - return remain; -} - -/* 线程状态数值 -> 可读字符串 */ -static const char *sta_to_str(rt_uint8_t stat) -{ - stat &= RT_THREAD_STAT_MASK; - switch (stat) - { - case RT_THREAD_INIT: return "INIT"; - case RT_THREAD_READY: return "READY"; - case RT_THREAD_SUSPEND: return "SUSPEND"; - case RT_THREAD_RUNNING: return "RUNNING"; - case RT_THREAD_CLOSE: return "CLOSE"; - default: return "UNKNOWN"; - } -} - -/* ===================== 工作线程 1:动态创建,翻转 LED_R ===================== */ -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - /* 按键挂起请求由本线程自行挂起(本内核 rt_thread_suspend 仅允许挂起自身) */ - while (1) - { - if (led1_suspend_req) - { - led1_suspend_req = 0; - rt_kprintf("th_led1: self suspend\n"); - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - rt_kprintf("th_led1: resumed\n"); - } - - rt_pin_write(PIN_LED_R, !rt_pin_read(PIN_LED_R)); - rt_kprintf("th_led1: count=%d, tick=%u\n", count++, (rt_ubase_t)rt_tick_get()); - rt_thread_mdelay(TH_LED1_DELAY_MS); - } -} - -/* ===================== 工作线程 2:静态初始化,翻转 LED_B ===================== */ -static void th_led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - - while (1) - { - rt_pin_write(PIN_LED_B, !rt_pin_read(PIN_LED_B)); - rt_kprintf("th_led2: count=%d\n", count++); - rt_thread_mdelay(TH_LED2_DELAY_MS); - } -} - -/* ===================== 监控与控制线程:打印状态、处理按键 ===================== */ -static void th_monitor_entry(void *parameter) -{ - rt_err_t res; - - while (1) - { - rt_thread_mdelay(TH_MONITOR_PERIOD_MS); - - /* 按键单次触发:挂起/恢复 th_led1 */ - if (key_pressed) - { - key_pressed = 0; - if ((th_led1->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND) - { - res = rt_thread_resume(th_led1); - rt_kprintf("monitor: resume th_led1, ret=%d\n", res); - } - else - { - led1_suspend_req = 1; - rt_kprintf("monitor: request th_led1 suspend\n"); - } - } - - /* 打印两个工作线程的运行状态与剩余栈空间 */ - rt_kprintf("monitor: [%-8s] stat=%-8s stack_remain=%uB | [%-8s] stat=%-8s stack_remain=%uB\n", - th_led1->name, sta_to_str(th_led1->stat), get_stack_remain(th_led1), - th_led2->name, sta_to_str(th_led2->stat), get_stack_remain(th_led2)); - } -} - -/* ===================== 自定义 MSH 命令:thread_dump ===================== */ -static void thread_dump(void) -{ - rt_kprintf("--------------------------------------------------------\n"); - rt_kprintf("name stat prio remain_stack(B)\n"); - rt_kprintf("--------------------------------------------------------\n"); - rt_kprintf("%-9s %-9s %-7d %u\n", th_led1->name, sta_to_str(th_led1->stat), - th_led1->current_priority, get_stack_remain(th_led1)); - rt_kprintf("%-9s %-9s %-7d %u\n", th_led2->name, sta_to_str(th_led2->stat), - th_led2->current_priority, get_stack_remain(th_led2)); - rt_kprintf("--------------------------------------------------------\n"); -} -MSH_CMD_EXPORT(thread_dump, dump thread status and remain stack); - -/* ===================== 自动初始化(禁止在 main.c 硬编码调用) ===================== */ -static int thread_study_init(void) -{ - rt_err_t res; - - /* 板载 LED 引脚初始化 */ - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - - /* 按键中断初始化 */ - rt_pin_mode(PIN_KEY_UP, PIN_MODE_INPUT_PULLUP); - rt_pin_attach_irq(PIN_KEY_UP, PIN_IRQ_MODE_FALLING, key_up_callback, RT_NULL); - rt_pin_irq_enable(PIN_KEY_UP, PIN_IRQ_ENABLE); - - /* 动态创建 th_led1 */ - th_led1 = rt_thread_create("th_led1", - th_led1_entry, - RT_NULL, - TH_LED1_STACK_SIZE, - TH_LED1_PRIORITY, - TH_LED1_TIMESLICE); - if (th_led1 == RT_NULL) - { - rt_kprintf("create th_led1 failed\n"); - return -RT_ERROR; - } - rt_thread_startup(th_led1); - - /* 静态初始化 th_led2 */ - res = rt_thread_init(&led2_thread, - "th_led2", - th_led2_entry, - RT_NULL, - led2_stack, - sizeof(led2_stack), - TH_LED2_PRIORITY, - TH_LED2_TIMESLICE); - if (res != RT_EOK) - { - rt_kprintf("init th_led2 failed, ret=%d\n", res); - return res; - } - th_led2 = &led2_thread; - rt_thread_startup(th_led2); - - /* 动态创建 th_monitor */ - th_monitor = rt_thread_create("th_mon", - th_monitor_entry, - RT_NULL, - TH_MONITOR_STACK_SIZE, - TH_MONITOR_PRIORITY, - TH_MONITOR_TIMESLICE); - if (th_monitor == RT_NULL) - { - rt_kprintf("create th_monitor failed\n"); - return -RT_ERROR; - } - rt_thread_startup(th_monitor); - - rt_kprintf("thread_study_init done\n"); - return RT_EOK; -} -INIT_APP_EXPORT(thread_study_init); - diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 263841656b32be4ece31cf0d3170c0c248488c6e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,151 +0,0 @@ - -# DAY4 虚拟 LED 设备驱动框架设计 -平台:STM32F407 星火一号 `stm32f407‑rt‑spark`,RT‑Thread Studio 工程 `04_device_sample` -提交:`README.md` + `virtu_dev.h` + `virtu_dev.c` -MSH测试命令:`virtu_dev_test` - -## 一、设计目标 -仿照 RT‑Thread PIN 设备驱动框架三层架构,实现一套自定义LED设备驱动框架。不直接使用原生`rt_device`的`read/write/control`通用接口,自定义LED专属`rt_led_ops`操作接口表,向外提供语义化用户API,复刻PIN框架的分层、接口隔离、可移植的设计模式。 - -本实现BSP层可以对接真实STM32 GPIO硬件,底层调用RTT PIN驱动接口操作引脚;框架层只负责转发,不关心底层芯片硬件细节。 - -## 二、框架架构 - -应用层 rt_led_init() / rt_led_on() / rt_led_off() / rt_led_toggle() - 用户直接调用语义化API - │ - ▼ -框架层 virtu_dev.h / virtu_dev.c(上半部分) - 定义 rt_led_ops、rt_device_led;实现注册函数、对外用户API - 连接应用层与BSP硬件层 - │ - ▼ -BSP硬件层 virtu_dev.c(下半部分) - stm32_led_init / stm32_led_on / stm32_led_off / stm32_led_toggle - _stm32_led_ops ops实例 - rt_hw_led_init() 配合INIT_BOARD_EXPORT实现上电自动注册 - -## 三、与PIN框架对应关系 -|组件|PIN框架|本LED框架(virtu_dev)| -|---|---|---| -|头文件|`pin.h`|`virtu_dev.h`| -|框架源码|`pin.c`|`virtu_dev.c`(上半部分)| -|BSP源码|`drv_gpio.c`|`virtu_dev.c`(下半部分)| -|ops结构体|`struct rt_pin_ops`|`struct rt_led_ops`| -|设备结构体|`struct rt_device_pin`|`struct rt_device_led`| -|注册函数|`rt_device_pin_register()`|`rt_device_led_register()`| -|用户API|`rt_pin_write()`|`rt_led_on()`| -|BSP实现函数|`stm32_pin_write()`|`stm32_led_on()`| -|ops实例|`_stm32_pin_ops`|`_stm32_led_ops`| -|初始化函数|`rt_hw_pin_init()`|`rt_hw_led_init()`| -|自动注册宏|`INIT_BOARD_EXPORT(rt_hw_pin_init)`|`INIT_BOARD_EXPORT(rt_hw_led_init)`| - -## 四、关键数据结构 -### 1. rt_led_ops 操作接口表 -```c -struct rt_led_ops -{ - void (*led_init)(struct rt_device *device, rt_base_t pin, rt_base_t mode); - void (*led_on)(struct rt_device *device, rt_base_t pin); - void (*led_off)(struct rt_device *device, rt_base_t pin); - void (*led_toggle)(struct rt_device *device, rt_base_t pin); -}; -``` -- `led_init`:初始化指定LED引脚,设置引脚工作模式 -- `led_on`:点亮指定LED引脚 -- `led_off`:熄灭指定LED引脚 -- `led_toggle`:LED翻转,示例中实现循环闪烁10次逻辑 - -### 2. rt_device_led 自定义设备结构体 -```c -struct rt_device_led -{ - struct rt_device parent; - const struct rt_led_ops *ops; -}; -``` -- `parent`:继承RT‑Thread标准`rt_device`,放在结构体第一个成员,用于向系统设备管理器注册 -- `ops`:保存BSP层传入的LED操作函数指针表,框架层依靠该指针间接调用底层硬件实现 - -## 五、设备注册流程 -``` -系统上电启动 - -> rt_hw_led_init() // INIT_BOARD_EXPORT自动调用 - -> rt_device_led_register("led", &_stm32_led_ops, RT_NULL) - -> 填充 _hw_led.parent 的设备基础属性,全部原生rt_device接口置RT_NULL - -> _hw_led.ops = ops; // 保存BSP传入ops函数指针表 - -> rt_device_register(&_hw_led.parent, "led", RT_DEVICE_FLAG_RDWR) - -> LED设备注册进入RT‑Thread设备管理器 -``` -注册完成后,应用层直接调用`rt_led_xxx()`系列语义API完成LED控制。 - -## 六、API调用链示例(以 rt_led_on(PIN_LED_B) 为例) -``` -rt_led_on(PIN_LED_B) // 应用层调用对外API - -> _hw_led.ops->led_on(&_hw_led.parent, pin) // 框架层,通过函数指针转发 - -> stm32_led_on(device, pin) // BSP硬件层实现 - -> rt_kprintf("led on\n"); - -> rt_pin_write(pin,PIN_LOW); // 底层调用RTT PIN框架操作真实GPIO -``` - -## 七、与通用rt_device驱动(test_drv.c)对比 -`test_drv.c`直接复用`rt_device`内置`init/read/write/control`,上层需要使用`rt_device_find` + `rt_device_write`传递缓冲区完成控制。 -本LED框架仿照PIN框架,自定义专属ops与语义API。 - -|对比项|test_drv.c(通用rt_device)|本LED自定义框架| -|---|---|---| -|ops定义|无,直接复用rt_device原生接口|自定义`struct rt_led_ops`| -|用户调用API|`rt_device_write(dev,pos,buf,size)`|`rt_led_on(pin)`语义化函数| -|参数传递|依靠buffer缓冲区+offset传递参数|直接传入pin引脚号参数| -|代码可读性|需要查阅协议文档解析buffer内容|函数名直观,参数含义明确| -|可移植性|更换硬件,应用层代码需要修改|仅需要重新实现一套`rt_led_ops`,应用层代码无需改动| - -## 八、实验验证 -### 测试代码 -```c -#define PIN_LED_B rt_pin_get("PF.11") -#define PIN_MODE PIN_MODE_OUTPUT -static void virtu_dev_test(void) -{ - rt_led_init(PIN_LED_B,PIN_MODE); - rt_led_on(PIN_LED_B); - rt_thread_mdelay(2000); - rt_led_off(PIN_LED_B); - rt_thread_mdelay(2000); - rt_led_toggle(PIN_LED_B); -} -MSH_CMD_EXPORT(virtu_dev_test, virtu_dev sample); -``` - -### 预期串口输出 -``` -msh >virtu_dev_test -led init success -led on -led off -led toggle... -count: 1 -count: 2 -count: 3 -count: 4 -count: 5 -count: 6 -count: 7 -count: 8 -count: 9 -count: 10 -``` - -### 结果分析 -1. `rt_led_init()`通过ops调用BSP层`stm32_led_init`,打印日志,调用`rt_pin_mode`完成GPIO初始化。 -2. `rt_led_on()`点亮LED,延时2秒;`rt_led_off()`熄灭LED,延时2秒。底层调用PIN框架接口操作真实PF.11引脚。 -3. `rt_led_toggle()`执行闪烁逻辑,循环10次,打印count计数。 -4. 全部上层API都通过框架层保存的ops函数指针跳转到BSP层实现;框架层完全不直接操作硬件引脚,做到软硬件解耦。 - -## 九、总结 -本项目完整复刻RT‑Thread PIN驱动框架核心思想: -1. **分层设计**:应用层‑框架层‑BSP硬件层三层分离,各层职责明确。框架层不包含硬件操作代码。 -2. **接口隔离**:框架层定义`rt_led_ops`接口契约;BSP层完成硬件实现,通过函数指针表和框架层解耦。 -3. **自动注册机制**:`INIT_BOARD_EXPORT`完成上电自动注册,BSP将ops函数指针注入框架。 -4. **语义化API**:对外提供`rt_led_on/off/init/toggle`,调用直观,相比原生`rt_device`的read/write更友好。 -5. **良好可移植性**:切换不同硬件平台时,仅需要重新实现一套`rt_led_ops`;框架层代码、上层应用测试代码完全不用修改。 diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/virtu_dev.c" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/virtu_dev.c" deleted file mode 100644 index 3f5552ff8b9765108c2cb90368fcb3b6ab6e0061..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/virtu_dev.c" +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 yanran the first version - */ -#include -#include "virtu_dev.h" -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -/**************************************************************************************/ -//实现stm32_led的bsp函数 -static void stm32_led_init(struct rt_device *device, rt_base_t pin, rt_base_t mode) -{ - rt_kprintf("led init success\n"); - rt_pin_mode(pin,mode); - return; -} -static void stm32_led_on (struct rt_device *device, rt_base_t pin) -{ - rt_kprintf("led on\n"); - rt_pin_write(pin,PIN_LOW); - return; -} -static void stm32_led_off (struct rt_device *device, rt_base_t pin) -{ - rt_kprintf("led off\n"); - rt_pin_write(pin,PIN_HIGH); - return; -} -static void stm32_led_toggle (struct rt_device *device, rt_base_t pin) -{ - //以闪烁10次为例子 - rt_kprintf("led toggle...\n"); - rt_uint8_t count = 0; - while(count < 10) - { - rt_pin_write(pin,PIN_LOW); - rt_thread_mdelay(500); - rt_pin_write(pin,PIN_HIGH); - rt_thread_mdelay(500); - count++; - rt_kprintf("count: %d\n",count); - } - return; -} - -//定义led的bsp驱动层ops表 -const static struct rt_led_ops _stm32_led_ops = -{ - stm32_led_init, - stm32_led_on, - stm32_led_off, - stm32_led_toggle, -}; - -/********************************************************************************************/ - -static struct rt_device_led _hw_led; - -//注册led设备 -int rt_device_led_register(const char *name, const struct rt_led_ops *ops, void *user_data) -{ - _hw_led.parent.type = RT_Device_Class_Unknown; - _hw_led.parent.rx_indicate = RT_NULL; - _hw_led.parent.tx_complete = RT_NULL; - - _hw_led.parent.init = RT_NULL; - _hw_led.parent.open = RT_NULL; - _hw_led.parent.close = RT_NULL; - _hw_led.parent.read = RT_NULL; - _hw_led.parent.write = RT_NULL; - _hw_led.parent.control = RT_NULL; - - _hw_led.ops = ops; - _hw_led.parent.user_data = user_data; - - /* register a character device */ - rt_device_register(&_hw_led.parent, name, RT_DEVICE_FLAG_RDWR); - return 0; -} - -//上电后自动完成led设备的注册,修改这里的_stm32_led_ops实现调用不同芯片的bsp函数 -int rt_hw_led_init(void) -{ - // 把BSP的硬件ops传给框架层 rt_device_led_register - rt_device_led_register("led", &_stm32_led_ops, RT_NULL); - return 0; -} -INIT_BOARD_EXPORT(rt_hw_led_init); // 系统上电自动执行! - -/***********************************************************************************/ -//RT-Thread Hardware LED APIs -//开放给rtthread使用的led api,实际上调用对应ops表指向的bsp函数 -void rt_led_on(rt_base_t pin) -{ - RT_ASSERT(_hw_led.ops != RT_NULL); - _hw_led.ops->led_on(&_hw_led.parent, pin); -} - -void rt_led_off(rt_base_t pin) -{ - RT_ASSERT(_hw_led.ops != RT_NULL); - _hw_led.ops->led_off(&_hw_led.parent, pin); -} - -void rt_led_init(rt_base_t pin, rt_base_t mode) -{ - RT_ASSERT(_hw_led.ops != RT_NULL); - _hw_led.ops->led_init(&_hw_led.parent, pin, mode); -} - -void rt_led_toggle(rt_base_t pin) -{ - RT_ASSERT(_hw_led.ops != RT_NULL); - _hw_led.ops->led_toggle(&_hw_led.parent, pin); -} -/************************************************************************/ -//自定义的led设备的测试程序 -#define PIN_LED_B rt_pin_get("PF.11") -#define PIN_MODE PIN_MODE_OUTPUT - -static void virtu_dev_test(void) -{ - rt_led_init(PIN_LED_B,PIN_MODE); - rt_led_on(PIN_LED_B); - rt_thread_mdelay(2000); - rt_led_off(PIN_LED_B); - rt_thread_mdelay(2000); - rt_led_toggle(PIN_LED_B); -} - -MSH_CMD_EXPORT(virtu_dev_test, virtu_dev sample); - diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/virtu_dev.h" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/virtu_dev.h" deleted file mode 100644 index ecc2e9ab0eb952c3fcf03f615527d1df94c6b125..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/virtu_dev.h" +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 yanran the first version - */ -#ifndef APPLICATIONS_VIRTU_DEV_H_ -#define APPLICATIONS_VIRTU_DEV_H_ - -#include - -//自定义的led_ops表 -struct rt_led_ops -{ - void (*led_init)(struct rt_device *device, rt_base_t pin, rt_base_t mode); - void (*led_on)(struct rt_device *device, rt_base_t pin); - void (*led_off)(struct rt_device *device, rt_base_t pin); - void (*led_toggle)(struct rt_device *device, rt_base_t pin); -}; - -//定义led设备类型 -struct rt_device_led -{ - struct rt_device parent; - const struct rt_led_ops *ops; -}; - -//函数声明 -void rt_led_on(rt_base_t pin); -void rt_led_off(rt_base_t pin); -void rt_led_init(rt_base_t pin, rt_base_t mode); -void rt_led_toggle(rt_base_t pin); - - -#endif /* APPLICATIONS_VIRTU_DEV_H_ */ diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 671aa9f24aa039887c3094143dd9b9638a5bb773..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,110 +0,0 @@ -# RT-Thread夏令营Day1学习笔记 - -# 一、Markdown 语法学习与实战应用 - -今日系统学习了轻量级标记语言 Markdown 的核心语法,该语法结构简洁、可读性强,非常适合用于编写技术笔记、项目README文档以及开源项目PR说明,是嵌入式开发和开源学习的基础工具。 - -## 1. 核心基础语法 - -熟练掌握了日常高频使用的语法,包含多级标题、有序/无序列表、文本样式、行内代码、代码块等,核心用法如下: - -```markdown -# 一级标题 -## 二级标题 -### 三级标题 -**加粗文本** -`行内代码` -``` -## 2. 实战使用场景 - -在RT-Thread嵌入式学习中,代码块是实用性最高的功能,可单独收纳Git指令、程序代码、操作步骤,区分普通文字与技术代码,让笔记规整且便于后续复用、复制。日常学习中可用于每日笔记整理、实验过程记录、项目文档编写等场景。 - -# 二、Git与Gitee版本控制与协作规范 - -本次学习完成了Git环境部署、账号配置、远程仓库连接,掌握了基础代码管理指令与团队协作、PR提交规范,适配开源项目多人开发模式。 - -## 1. 基础环境配置 - -完成Git全局用户信息配置,通过SSH密钥打通Gitee远程连接权限,可通过专属指令校验连接状态、查看和修改远程仓库地址,保障本地与远程仓库正常通信。 - -```bash -# 配置全局用户信息 -git config --global user.name "用户名" -git config --global user.email "邮箱" -# 查看配置信息 -git config --list -# 测试Gitee SSH连接 -ssh -T git@gitee.com -# 查看、修改远程仓库地址 -git remote -v -git remote set-url origin 仓库地址 -``` - -## 2. 日常代码提交与同步 - -掌握了仓库初始化、文件暂存、提交、拉取、推送等核心操作,形成标准化的本地代码更新、远程同步流程,可完整记录代码迭代版本。 - -```bash -git init # 初始化本地Git仓库 -git fetch origin master # 同步远程主分支信息 -git add 文件/路径 # 文件添加至暂存区 -git commit -m "提交备注" # 提交修改并备注说明 -git pull # 拉取远程最新代码 -git push origin master # 本地代码推送至远程仓库 -``` - -## 3. 分支管理与团队PR规范 - -学习了分支创建与切换操作,为独立开发、功能迭代提供保障。团队协作采用标准化流程:先Fork官方仓库,克隆至本地后,同步远程主分支最新代码,新建独立分支完成学习任务,避免代码混杂。 - -项目主分支开启保护机制,禁止直接推送代码,所有修改需通过新建分支提交PR,由专人审核合并。普通组员PR由小组长审核,组长PR需由导师复核,严格把控代码规范与目录格式,保障仓库代码整洁统一。 - -```bash -git checkout -b 分支名 # 创建并切换至新分支 -git switch master # 切换回主分支 -git pull origin master # 同步远程主分支最新代码 -``` - -# 三、RT-Thread开发环境搭建与工程调试 - -## 1. 开发环境配置 - -掌握RT-Thread完整开发环境搭建逻辑,环境由RT-Thread源码、SDK开发工具、BSP板级支持包三部分组成。其中SDK提供编译工具链、开发编译环境,BSP适配对应硬件开发板,包含启动文件、底层驱动、工程配置等核心文件。 - -## 2. 工程创建与仿真测试 - -熟悉RT-Thread Studio的工程创建、编译、下载、调试全流程。一方面基于STM32F407ZG芯片创建实体开发板工程,校验ST-Link驱动正常后,完成程序编译、下载与断点调试,可成功停留在main函数,验证工程运行正常。 - -另一方面通过QEMU模拟器搭建虚拟ARM硬件环境,创建`qemu-vexpress-a9`工程,无需实体开发板即可完成工程编译、运行,成功进入msh命令行,掌握系统基础校验方法。msh作为系统内置Shell,可输入指令查看系统状态、执行功能操作。 - -# 四、RT-Thread工程结构、驱动框架与内核原理 - -## 1. 工程核心目录结构 - -了解RT-Thread标准工程目录分工,核心包含application应用目录、drivers外设驱动目录(串口、音频、键鼠、LCD等)、components组件目录(文件系统、C++支持等)、libcpu底层跨架构接口目录、libc工具链兼容目录,各目录各司其职,保障工程模块化运行。 - -## 2. 三层驱动框架 - -RT-Thread采用分层式驱动架构,有效屏蔽硬件差异、降低应用层耦合度:第一层为统一IO设备管理层,统一管理字符、块、SPI等各类外设;第二层为通用硬件设备驱动层;第三层为厂商专属外设驱动层,适配不同硬件型号。 - -## 3. 内核核心特性 - -RT-Thread内核采用面向对象设计思想,源码涵盖内存管理、中断处理、进程通信(IPC)、对象管理等核心模块。系统调度器为优先级抢占式调度,可高效分配系统资源,保障多线程稳定运行。 - -# 五、RT-Thread系统启动流程 - -梳理出RT-Thread完整启动执行逻辑,启动流程层级清晰、循序渐进: - -1. 汇编层初始化:执行启动汇编文件,完成栈指针配置、Flash数据拷贝至SRAM、内存段清零操作,根据编译工具链跳转对应初始化入口(MDK走__main、GCC调用自定义entry); - -2. 系统底层初始化:关闭全局中断,执行板级硬件初始化,打印系统版本Logo; - -3. 内核模块初始化:依次完成系统时钟、调度器、信号量等核心模块初始化; - -4. 线程创建与调度启动:创建主线程、定时器线程、空闲线程,启动系统调度器; - -5. 最终跳转至用户main函数,执行用户应用程序。 - -# 六、项目实战:Hello RT-Thread - -基于STM32F407开发板完成入门实战项目,在RT-Thread Studio新建工程后,修改代码添加自定义打印语句,编译下载至开发板。设备正常运行后,终端成功输出“Hello RSOC 2026”与系统自带版本号,顺利完成首个RT-Thread应用程序开发与运行测试。 \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 1b1c6e904a9ec485369ee9b389683cc60b716323..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\345\210\230\351\207\221\346\265\267/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,206 +0,0 @@ - -# RT‑Thread 设备驱动框架学习笔记(第4天) - -## 前言 -RT‑Thread设备驱动框架核心目的是**软硬件解耦,提升代码可移植性**。上层应用不直接操作芯片寄存器、不依赖芯片原厂HAL库,全部调用操作系统提供的抽象接口;硬件相关逻辑全部下沉到驱动BSP层。更换MCU时,只修改底层驱动,业务应用代码可以最大程度复用。 -本节课主要学习通用`rt_device`设备模型、PIN引脚驱动框架完整原理,同时理解两种虚拟设备:直接复用rt_device的`test_dev`,以及仿照PIN架构自定义ops的LED虚拟驱动框架。 - -## 一、RT‑Thread通用设备基础模型 -### 1.三层整体架构 -RT‑Thread标准设备框架分为三层: - -|层级|职责|核心API/对象| -|---|---|---| -|应用层|查找、打开、操作、关闭设备业务逻辑|`rt_device_find`、`rt_device_open/read/write/control/close`| -|IO设备管理层|设备注册管理、引用计数、统一分发入口|`struct rt_device`、`rt_device_register`| -|驱动层|BSP硬件对接,实现真正硬件动作|`init/open/read/write/control`驱动实现函数| - -调用流程: -``` -应用层 - ├─ rt_device_find() 根据名字拿到设备句柄 - ├─ rt_device_open() 打开设备,引用计数+1 - └─ rt_device_read / write / control -    → rt_device内部函数指针ops -      → 底层驱动实现(真实硬件 / 虚拟模拟) -``` - -### 2.struct rt_device与函数指针ops -`struct rt_device`是所有设备的基类,属于C语言模拟面向对象的写法。 -关键成员: -- `type`:设备类型,字符设备、块设备、杂项设备等 -- `ref_count`:设备打开引用计数,多任务访问资源管理 -- `user_data`:驱动私有数据,保存设备状态信息 -- 函数指针集合:`init / open / close / read / write / control` - -驱动开发流程(通用test_dev示例): -1. 创建/定义设备实例; -2. 将自己写的驱动函数赋值给rt_device的各个函数指针; -3. 调用`rt_device_register()`把设备注册进系统设备管理器; -4. MSH执行`list_device`即可看到注册完成的设备。 - -### 3.设备完整生命周期 -1. 系统启动,驱动初始化,创建并注册设备; -2. 应用调用`rt_device_find()`获取设备指针; -3. `rt_device_open()`打开设备,引用计数自增; -4. 通过read/write/control完成业务操作; -5. `rt_device_close()`关闭设备,引用计数自减。 - -> 引用计数用来做多任务资源保护,多个线程同时操作同一个设备时,避免资源异常释放。 - -### 4.通用rt_device模型的短板 -通用设备只有固定的`read/write/control`接口,没有业务语义。 -比如控制LED、GPIO引脚,需要把参数打包放到buffer缓冲区,依靠offset偏移、自定义cmd命令完成参数传递。 -缺点: -- 代码可读性差,使用者需要记住buffer内部协议; -- 参数传递容易出错; -- 调用接口无法直观体现“点亮LED、设置引脚模式”这类业务动作。 - -> 解决思路:PIN框架给出示范,**在rt_device基础之上再封装一层,自定义专属ops函数指针表,对外提供语义化API**。 - -## 二、PIN引脚驱动框架深度分析 -PIN框架就是基于通用rt_device做二次封装的典型案例,专门用于GPIO引脚操作。 - -### 1.PIN框架三层架构 -``` -应用层 rt_pin_mode() / rt_pin_write() / rt_pin_read() / rt_pin_attach_irq() -  │ 语义化API,使用者无需关心芯片寄存器 -  ▼ -框架层 pin.h + pin.c -  │ 定义专属rt_pin_ops接口表;实现注册函数、对外高层API;只做转发,不操作硬件 -  ▼ -BSP硬件层 drv_gpio.c - 实现stm32真实硬件函数;填充ops实例;系统启动完成设备注册 -``` - -核心思想:**框架层只定义接口契约,BSP层完成接口实现;应用层通过框架层间接调用硬件函数**。更换芯片,只重写BSP层ops实现,上层应用完全不用改动。 - -### 2.关键结构体 -#### ① 自定义设备结构体 struct rt_device_pin -```c -struct rt_device_pin -{ - struct rt_device parent; // 必须放在第一个,继承通用设备,用于系统注册 - const struct rt_pin_ops *ops; // PIN框架专属操作函数指针表 -}; -``` -- `parent`:是给RT‑Thread设备管理器识别的基类; -- `ops`:保存BSP传入的硬件函数表,框架层依靠这个指针间接调用底层。 - -#### ② 专属接口表 struct rt_pin_ops -这是一份接口契约,规定BSP必须实现7个函数: -```c -struct rt_pin_ops -{ - void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_base_t mode); - void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_base_t value); - int (*pin_read)(struct rt_device *device, rt_base_t pin); - rt_err_t (*pin_attach_irq)(...); - rt_err_t (*pin_detach_irq)(...); - rt_err_t (*pin_irq_enable)(...); - rt_base_t (*pin_get)(const char *name); -}; -``` - -|函数|功能| -|---|---| -|pin_mode|配置引脚输入输出、上拉下拉、开漏模式| -|pin_write|输出引脚高低电平| -|pin_read|读取引脚电平状态| -|pin_attach_irq|绑定外部中断用户回调函数| -|pin_detach_irq|解绑中断回调| -|pin_irq_enable|使能/关闭引脚外部中断| -|pin_get|字符串引脚名"PA.0"转换为数字引脚编码| - -> 注意区分两套ops: -> 1. `rt_pin_ops`:PIN框架自己定义,BSP实现硬件; -> 2. parent里面read/write/control:原生rt_device接口,pin.c内部实现`_pin_read/_pin_write/_pin_control`做兼容转发。 -> 业务开发优先使用`rt_pin_xxx`高层API,极少使用`rt_device_write`操作pin设备。 - -### 3.引脚编码规则 -RT‑Thread用一个整数编码GPIO引脚,把**端口号、引脚号压缩到同一个数字中**。 -- 高4位:端口编号(A=0,B=1,C=2……) -- 低4位:引脚编号0‑15 - -相关宏: -- `PIN_NUM(port,no)`:拼接端口和引脚得到编码; -- `PIN_PORT(pin)`:从编码解析出端口; -- `PIN_NO(pin)`:从编码解析出引脚号; -- `GET_PIN(port,no)`:方便用户直接使用的宏。 - -底层再通过端口编号计算寄存器基地址,移位得到引脚位掩码,调用HAL库操作寄存器。 - -### 4.PIN设备完整注册流程 -1. BSP层`rt_hw_pin_init()`,系统上电自动执行;先开启各个GPIO时钟; -2. 调用`rt_device_pin_register("pin",&_stm32_pin_ops,RT_NULL)`,把BSP的ops传给框架层; -3. 注册函数内部: - - 挂载框架内部兼容函数`_pin_read/_pin_write/_pin_control`到父类rt_device; - - **保存BSP传入ops指针到全局`_hw_pin.ops`**; - - 调用`rt_device_register()`,将设备注册进操作系统。 -4. 注册完成,应用层即可调用`rt_pin_mode / rt_pin_write`。 - -> `_stm32_pin_ops`是BSP层的实例,把`stm32_pin_mode`、`stm32_pin_write`等真实硬件函数填入ops结构体。 - -### 5.调用链示例 rt_pin_write() -``` -rt_pin_write(pin, value) // 应用层调用高层API -  → _hw_pin.ops->pin_write(&_hw_pin.parent,pin,value) //框架层函数指针跳转 -    → stm32_pin_write(dev,pin,value) //BSP层硬件实现 -      → HAL_GPIO_WritePin() //STM32 HAL库 -        → 操作BSRR/ODR硬件寄存器 -``` - -### 6.PIN外部中断简单流程 -1. 应用调用`rt_pin_attach_irq()`绑定回调; -2. BSP内部维护一张中断回调表,保存每个引脚对应的回调函数; -3. `rt_pin_irq_enable()`开启中断,配置NVIC优先级; -4. 硬件触发EXTI中断,进入中断服务函数; -5. HAL库处理中断标志,查表取出用户回调,执行用户编写的中断处理函数。 - -> 按键中断出现两次触发,一般是机械抖动,可以软件延时消抖或者使用button软件包。 - -## 三、为什么要自定义ops函数指针表 -如果框架层直接调用BSP的`stm32_pin_write()`函数: -- 框架代码直接依赖STM32驱动,换芯片必须修改pin.c框架源码; -- 分层被打破,框架与BSP强耦合。 - -使用ops函数指针表的优势: -1. **解耦**:框架只依赖接口声明,不依赖具体芯片实现; -2. **可移植**:更换硬件,仅重新实现一份ops表,框架代码、上层业务完全不动; -3. **C语言实现面向对象虚函数表效果,接口与实现分离**。 - -## 四、两种虚拟设备对比 test_dev 与自定义LED框架 -### 1.test_dev:直接复用rt_device通用框架 -直接使用rt_device自带`init/open/read/write/control`,不自定义ops结构体。 -- 注册:`INIT_DEVICE_EXPORT`开机自动注册; -- 控制使用自定义cmd,通过control接口读写设备状态; -- 使用方式:`rt_device_find`找到设备,调用标准read/write/control; -- 缺点:没有业务语义,参数靠buffer和cmd传递。 - -### 2.仿照PIN架构自定义LED虚拟驱动框架(本次作业) -模仿PIN整套模式,自己搭建三层架构: -1. 自定义`struct rt_led_ops`接口表,定义led_init、led_on、led_off等业务函数指针; -2. 自定义设备结构体`struct rt_device_led`,第一个成员是`struct rt_device parent`,第二个保存ops指针; -3. 框架层:实现注册函数`rt_device_led_register()`,实现`rt_led_on/rt_led_off`等对外语义API,只做函数指针转发,不操作硬件; -4. BSP层:实现虚拟或者真实硬件函数,填充ops实例; -5. `rt_hw_led_init()`配合`INIT_BOARD_EXPORT`上电自动注册设备; -6. 应用层直接调用`rt_led_on(pin)`,不需要find、open设备。 - -|对比项|通用test_dev|自定义LED框架(模仿PIN)| -|---|---|---| -|ops来源|直接使用rt_device原生接口|自定义`rt_led_ops`专属接口表| -|应用API|`rt_device_write / control`|`rt_led_on(pin)`语义化接口| -|传参方式|buffer缓冲区+命令码|直接传入业务参数| -|可读性|差,需要解析buffer协议|函数名直观易懂| -|可移植性|换硬件应用层需要改动|只需重写ops,上层代码无需修改| - -> 核心模式总结: -> PIN框架、自定义LED框架,都是**在rt_device基础上再封装一层**;rt_device只是为了能注册进系统设备管理器,真正业务能力全部来自自定义ops函数指针表。 - -## 五、学习总结 -1. RT‑Thread设备框架核心就是分层+函数指针,做到接口和实现分离,追求代码可移植; -2. 通用`rt_device`是所有设备的基础,提供统一注册、生命周期管理,但是接口缺少业务语义; -3. PIN框架是典型二次封装案例:继承rt_device,新增自定义ops,对外提供友好语义API;分为应用层‑框架层‑BSP层; -4. ops函数指针表就是C语言的接口契约:框架只规定函数长什么样,BSP完成硬件实现; -5. 虚拟设备分为两类:直接复用rt_device的test_dev;仿照PIN完整架构自定义ops的LED驱动; -6. 写自定义驱动时,框架层代码严禁出现硬件操作;硬件逻辑全部放到BSP层,上层应用只调用框架对外API,禁止直接调用BSP内部static函数。 \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\200\345\244\251/\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\200\345\244\251/\344\275\234\344\270\232/README.md" deleted file mode 100644 index 04d53db53ab587cc9418057b1023e0df074b1155..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\200\345\244\251/\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,794 +0,0 @@ -# RSOC 2026 第一天作业 - -> 日期:2026-08-17 -> 学员:李子尧 -> 内容:RT-Thread Studio 基础使用、STM32F407 工程流程、Markdown 与 Git/Gitee 协作实践 - ---- - -## 一、Day1 任务目标与完成内容 - -第一天的作业主要围绕两个方向展开:一是熟悉 RT-Thread 的基本开发环境和 STM32 工程开发流程,二是掌握后续两周都会持续使用的 Markdown 与 Git/Gitee 协作方式。 - -当天需要完成的主要任务如下: - -| 任务 | 主要内容 | -|---|---| -| 开发环境准备 | 安装并熟悉 RT-Thread Studio | -| SDK 配置 | 准备 RT-Thread 源码及 STM32F4 相关支持包 | -| 工程实践 | 学习创建 STM32F407 RT-Thread 工程 | -| 编译与下载 | 理解 Build、Download、Run、Debug 的基本流程 | -| Markdown | 使用 Markdown 编写学习笔记与作业 | -| Git | 掌握 clone、add、commit、push、branch 等操作 | -| Gitee 协作 | Fork 官方仓库并建立个人开发仓库 | -| PR | 按要求完成初始化模板与每日内容的分离提交 | - -Day1 的重点并不仅仅是完成一个 Hello World,而是把后续开发所需的基本工作流建立起来: - -```text -课程学习 - ↓ -本地实践 - ↓ -整理 Markdown - ↓ -Git 提交 - ↓ -Push 到个人 Fork - ↓ -Pull Request -``` - ---- - -## 二、RT-Thread Studio 开发环境准备 - -RT-Thread Studio 将 RT-Thread 工程创建、代码编辑、编译、下载以及调试等功能集成在同一个开发环境中。 - -当天首先熟悉了 Studio 中几个比较重要的部分: - -```text -RT-Thread Studio -│ -├── RT-Thread Source Code -├── MCU / Chip Support Package -├── Toolchain -├── Project -├── Build -├── Download -└── Debug -``` - -其中 SDK Manager 用来管理 RT-Thread 开发所需的 SDK、芯片支持包和相关工具。 - -针对 STM32F407 的开发,需要保证至少具备: - -- RT-Thread Studio; -- 课程指定版本的 RT-Thread 源码; -- STM32F4 芯片支持包; -- 对应编译工具链; -- ST-Link 驱动和调试环境。 - -这一步让我对嵌入式 IDE 和普通代码编辑器的区别更加明确:Studio 不只是负责写代码,还承担了工程配置、编译工具链调用以及调试器连接等工作。 - ---- - -## 三、STM32F407 RT-Thread 工程创建流程 - -课程中以 STM32F407 为例演示 RT-Thread 工程的创建。 - -整个过程可以整理为: - -```text -新建 RT-Thread Project - ↓ -选择芯片 / 开发板 - ↓ -选择 STM32F407 - ↓ -选择 RT-Thread 版本 - ↓ -配置工程参数 - ↓ -生成工程 - ↓ -修改 applications/main.c - ↓ -Build - ↓ -Download - ↓ -Run / Debug -``` - -在创建工程时,不能只关注 MCU 型号,还需要同时确认: - -- RT-Thread 版本; -- 芯片型号; -- BSP; -- Toolchain; -- Debugger; -- Console / UART 配置。 - -串口调试时课程采用: - -```text -115200 baud -``` - -后续很多 RT-Thread 的运行信息都可以通过串口终端观察,包括启动日志、调试打印和 MSH 命令。 - ---- - -## 四、理解 Build、Download、Run 和 Debug - -第一天课程中一个比较基础但容易混淆的问题,是 Build、Download、Run、Debug 实际上属于不同阶段。 - -### 1. Build - -Build 是将源代码转换为目标芯片可以运行的程序。 - -大体过程: - -```text -.c / .h - ↓ -预处理 - ↓ -编译 - ↓ -汇编 - ↓ -链接 - ↓ -ELF / BIN -``` - -其中: - -- `.elf` 通常包含代码、符号以及调试信息; -- `.bin` 更接近最终写入 Flash 的纯二进制数据。 - -### 2. Download - -Download 是把编译生成的程序写入目标 MCU。 - -```text -PC - ↓ -RT-Thread Studio - ↓ -ST-Link - ↓ -STM32F407 -``` - -### 3. Run - -下载完成以后,MCU 正常执行程序属于 Run。 - -### 4. Debug - -Debug 则是在调试器控制下运行程序,可以进行: - -- 设置断点; -- 单步执行; -- 查看变量; -- 查看寄存器; -- 分析函数调用过程。 - -因此可以简单总结为: - -| 阶段 | 作用 | -|---|---| -| Build | 把源码变成程序 | -| Download | 把程序写入 MCU | -| Run | MCU 正常运行程序 | -| Debug | 在调试器控制下分析程序 | - ---- - -## 五、Markdown 文档实践 - -RSOC 要求每天使用 Markdown 提交学习笔记和作业,因此 Markdown 也是第一天必须掌握的工具。 - -实际整理文档时主要使用以下语法。 - -### 标题层级 - -```markdown -# 一级标题 -## 二级标题 -### 三级标题 -``` - -### 加粗与行内代码 - -```markdown -**重点内容** - -`git status` -``` - -### 代码块 - -````markdown -```bash -git status -git add . -git commit -m "message" -``` -```` - -### 表格 - -```markdown -| 项目 | 说明 | -|---|---| -| Build | 编译 | -| Download | 下载 | -``` - -### 图片路径 - -图片如果需要放入 Markdown,最好统一放入相对目录,例如: - -```text -figures/ -``` - -引用形式: - -```markdown -![说明](./figures/example.png) -``` - -相比 Windows 本地绝对路径: - -```text -D:\xxx\xxx.png -``` - -相对路径更适合 Git 仓库,因为整个工程移动或者上传 Gitee 后路径仍然能够保持有效。 - ---- - -## 六、Git 本地版本管理实践 - -第一天开始正式使用 Git 管理自己的学习内容。 - -Git 最重要的并不是记住命令,而是先理解修改究竟保存在哪里。 - -```text -Working Tree - │ - │ git add - ▼ -Staging Area - │ - │ git commit - ▼ -Local Repository - │ - │ git push - ▼ -Remote Repository -``` - -### 查看当前状态 - -```bash -git status -``` - -可以查看: - -- 当前所在分支; -- 修改了哪些文件; -- 哪些文件尚未暂存; -- 哪些文件已经进入暂存区。 - -### 加入暂存区 - -```bash -git add <文件> -``` - -或者: - -```bash -git add . -``` - -`git add` 并不是提交代码,而是在选择: - -> 下一次 Commit 准备包含哪些修改。 - -### 创建 Commit - -```bash -git commit -m "提交说明" -``` - -Commit 之后会在**本地仓库**形成一次版本记录。 - -### Push - -```bash -git push origin <分支名> -``` - -这一步才会真正把本地 Commit 上传到 Gitee。 - -所以: - -```text -git add -≠ -git commit -≠ -git push -``` - -三者属于完全不同的阶段。 - ---- - -## 七、Fork 仓库与远程仓库关系 - -这次作业并不是直接修改 RT-Thread 官方仓库,而是采用开源项目中常见的 Fork 工作流。 - -我的仓库关系可以理解为: - -```text -RT-Thread 官方仓库 - │ - │ Fork - ▼ -我的 Gitee 仓库 - │ - │ Clone - ▼ -我的本地电脑 -``` - -### Fork - -Fork 发生在 Gitee 平台: - -```text -官方远程仓库 - ↓ -个人远程仓库 -``` - -### Clone - -Clone 则是: - -```text -个人远程仓库 - ↓ -本地电脑 -``` - -对应命令: - -```bash -git clone <仓库地址> -``` - -这两个概念一开始比较容易混淆,但实际数据方向完全不同。 - ---- - -## 八、origin 与 upstream 的配置 - -Fork 之后,本地实际上需要区分两个远程仓库。 - -### origin - -`origin` 对应自己的 Fork。 - -本次项目中可以理解为: - -```text -origin - ↓ -niconiconilzy/rsoc-rtt -``` - -平时自己的修改主要 Push 到这里。 - -### upstream - -`upstream` 对应 RT-Thread 官方原始仓库。 - -```text -upstream - ↓ -rtthread/rsoc-rtt -``` - -添加方式: - -```bash -git remote add upstream <官方仓库地址> -``` - -查看当前配置: - -```bash -git remote -v -``` - -最终关系: - -```text - upstream - RT-Thread/rsoc-rtt - ▲ - │ - │ Pull Request - │ - origin - niconiconilzy/rsoc-rtt - ▲ - │ - │ git push - │ - Local -``` - -因此自己通常: - -```text -Push → origin -``` - -而不是直接: - -```text -Push → upstream -``` - -官方内容发生更新时,可以: - -```bash -git fetch upstream -``` - -获取上游最新状态。 - ---- - -## 九、Day1 两次 Pull Request 的拆分 - -第一天作业要求至少完成两次 PR。 - -这让我第一次实际理解了: - -> 一次 PR 应该尽量只解决一个明确的问题。 - -### 第一次 PR:初始化模板 - -第一次 PR 只负责建立个人目录以及基础模板。 - -内容主要属于: - -```text -2026/ -└── 第2组/ - └── 李子尧/ - ├── 作业/ - └── 笔记/ -``` - -第一次提交不能把完整 Day1 作业和笔记一起塞进去,否则就失去了“初始化模板”这一独立提交的意义。 - -### 第二次 PR:Day1 内容 - -第二次 PR 再提交: - -```text -第一天作业.md -+ -第一天笔记.md -``` - -因此整个思路为: - -```text -PR 1 -初始化个人模板 - ↓ -官方审核 - -PR 2 -提交 Day1 作业与笔记 - ↓ -官方审核 -``` - -与一次性把所有修改上传相比,这种方式的优势是: - -- PR 目的更加明确; -- Reviewer 更容易审核; -- Git 历史更加清楚; -- 如果某部分存在问题,可以单独修改; -- 更符合真实开源项目的协作方式。 - ---- - -## 十、实际遇到的问题与解决过程 - -Day1 实际操作中,Git 部分遇到了几个比较有代表性的问题。 - -### 1. Git Bash 无法直接进入带空格的 Windows 路径 - -我的项目目录位于: - -```text -D:\stm32\rtt summer school -``` - -最开始直接输入: - -```bash -cd D:\stm32\rtt summer school -``` - -Git Bash 提示: - -```text -bash: cd: too many arguments -``` - -原因有两个。 - -第一,Git Bash 对 Windows 盘符的表示方式不同: - -```text -D:\ -``` - -对应: - -```text -/d/ -``` - -第二,目录: - -```text -rtt summer school -``` - -中间存在空格,Shell 会把它识别成多个参数。 - -因此正确写法为: - -```bash -cd "/d/stm32/rtt summer school" -``` - -这里使用双引号把整个路径作为同一个参数。 - -这个问题让我进一步理解了 Windows CMD / PowerShell 路径与 Bash 路径表示方式之间的差异。 - ---- - -### 2. 修改自己的 Fork 后如何贡献到官方仓库 - -刚开始容易产生一个误解: - -> 自己的 Fork 改完以后,是不是直接 Merge 到官方仓库? - -实际上普通贡献者通常没有官方仓库的直接写权限。 - -正确过程是: - -```text -官方仓库 - ↓ Fork -个人 Fork - ↓ Clone -本地修改 - ↓ Commit -个人本地仓库 - ↓ Push -个人 Gitee Fork - ↓ Pull Request -官方仓库 - ↓ Review -Merge -``` - -因此自己完成的是: - -```text -Commit -Push -Create PR -``` - -最终是否 Merge,由仓库维护者或 Reviewer 决定。 - ---- - -### 3. PR 的 Source 与 Target - -创建 Pull Request 时还需要明确: - -- Source:修改来自哪里; -- Target:希望合入哪里。 - -例如当天任务如果要求提交到官方 `day1` 分支,那么应当是: - -```text -我的 Fork / 我的分支 - ↓ -官方仓库 / day1 -``` - -而不是看到 `master` 就默认选择 `master`。 - -因此提交 PR 前应该先阅读仓库当天的要求,再确认目标分支。 - ---- - -## 十一、Day1 Git 工作流整理 - -结合第一天实际操作,我把后续比较适合自己的 Git 工作流整理为: - -### 开始任务前 - -```bash -git status -git fetch upstream -``` - -先确认当前工作区状态,同时获取官方最新内容。 - -### 创建当天工作分支 - -```bash -git checkout -b -``` - -或者: - -```bash -git switch -c -``` - -### 修改完成后检查 - -```bash -git status -git diff -``` - -### 选择本次需要提交的文件 - -```bash -git add -``` - -相比直接无脑: - -```bash -git add . -``` - -逐个加入需要提交的文件更加安全。 - -### 查看暂存内容 - -```bash -git diff --cached -``` - -### 创建 Commit - -```bash -git commit -m "说明本次修改内容" -``` - -### Push 到个人 Fork - -```bash -git push -u origin -``` - -最后在 Gitee 页面创建 Pull Request。 - -完整过程: - -```text -同步上游 - ↓ -创建分支 - ↓ -修改文件 - ↓ -git status - ↓ -git diff - ↓ -git add - ↓ -git diff --cached - ↓ -git commit - ↓ -git push origin - ↓ -Pull Request -``` - ---- - -## 十二、Day1 作业总结 - -第一天的作业让我完成了两个方面的基础准备。 - -一方面,熟悉了 RT-Thread Studio 的基本开发流程,并对: - -```text -Project -Build -Download -Run -Debug -``` - -几个阶段有了初步认识,也了解了 STM32F407 工程、ST-Link 和串口终端在整个开发流程中的位置。 - -另一方面,通过真正操作 Gitee 仓库,开始理解 Git 开源协作中的: - -```text -Fork -Clone -Branch -Add -Commit -Push -Pull Request -Review -Merge -``` - -这些操作之间的关系。 - -尤其是: - -```text -Working Tree - ↓ -Staging Area - ↓ -Local Repository - ↓ -Remote Repository -``` - -以及: - -```text -upstream -官方仓库 - ▲ - │ PR -origin -个人 Fork - ▲ - │ push -Local -``` - -这两套关系,是 Day1 Git 学习中最重要的内容。 - -相比单纯把命令记下来,实际完成 Fork、Clone、修改、Push 和 PR 后,对 Git 中“本地”和“远程”、“个人仓库”和“官方仓库”的区别更加清楚。 - -后续每日作业可以继续沿用这一工作方式,将每天的内容放在独立提交或独立分支中,保持提交历史清晰,也方便组长和维护者进行审核。 diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\200\345\244\251/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\200\345\244\251/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 81657eda71be3465298765942abb9a639ceac45e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\200\345\244\251/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,1027 +0,0 @@ -# RT-Thread 夏令营 Day1 学习笔记 - -> 日期:2026-08-17 -> 主题:RT-Thread 基础认识、开发环境搭建、Markdown 与 Git 开源协作 - ---- - -## 一、Day1 学习内容与夏令营安排 - -夏令营第一天主要围绕 RT-Thread 基础生态、开发环境、Markdown 文档规范以及 Git 开源协作展开。 - -本次夏令营采用两周学习模式: - -- **第一周为学习周**:上午 `09:00 - 12:00` 授课,下午 `14:00` 开始实操和作业; -- **第二周为项目周**:学员根据任务进行分组,完成综合项目。 - -第一周每天都需要完成: - -- 在线签到; -- 上午课程学习; -- 下午实操; -- Markdown 学习笔记; -- 当天作业; -- Git 提交; -- Pull Request。 - -整个学习过程可以概括为: - -```text -课程学习 - ↓ -实际操作 - ↓ -整理 Markdown 笔记 - ↓ -完成当天作业 - ↓ -Git Commit - ↓ -Pull Request -``` - -这种安排不仅要求掌握 RT-Thread 本身,也要求逐渐熟悉真实开源项目中的协作方式。 - ---- - -## 二、RTOS 与 RT-Thread 基础认识 - -RTOS 即 **Real-Time Operating System,实时操作系统**。 - -在简单的裸机程序中,通常会在 `main()` 中循环执行不同任务: - -```c -int main(void) -{ - while (1) - { - task_a(); - task_b(); - task_c(); - } -} -``` - -这种方式在功能较少时比较简单,但随着系统复杂度提高,会出现不同任务周期不同、某个任务阻塞影响其他任务、程序耦合越来越严重等问题。 - -RTOS 的思路则是: - -```text -不同功能 - ↓ -拆分成多个线程 - ↓ -由操作系统调度运行 -``` - -其中几个核心概念包括: - -| 概念 | 含义 | -|---|---| -| Thread / Task | 系统中的基本运行实体 | -| Scheduler | 根据优先级和线程状态决定 CPU 当前运行哪个线程 | -| Tick | RTOS 的基本时间节拍 | -| IPC | 线程之间进行通信与同步的机制 | - -常见 IPC 包括: - -- Semaphore:信号量; -- Mutex:互斥量; -- Event:事件; -- Mailbox:邮箱; -- Message Queue:消息队列。 - -RT-Thread 则是在 RTOS 内核基础上进一步提供组件、设备框架和软件包生态,可以简单理解为: - -```text -Application - ↓ -Components / Packages - ↓ -RT-Thread Kernel - ↓ -Device Driver Framework - ↓ -BSP / Hardware -``` - ---- - -## 三、RT-Thread 三条产品线 - -课程重点介绍了 RT-Thread 的三条产品线: - -```text -Nano -Standard -Smart -``` - -### 1. RT-Thread Nano - -Nano 是精简版本,主要保留实时操作系统最核心的能力: - -- 线程管理; -- 实时调度; -- 时钟; -- 中断; -- 内存管理; -- 线程同步; -- 线程通信。 - -其特点是资源占用较低,适合 Flash、RAM 较小或者只需要基础 RTOS 功能的 MCU。 - -可以简单理解为: - -```text -RT-Thread Nano - -RTOS Kernel -├── Thread -├── Scheduler -├── Timer -├── IPC -├── Interrupt -└── Memory -``` - -### 2. RT-Thread Standard - -Standard 是更加完整的 MCU RTOS 版本。 - -除了 Kernel,还拥有: - -- Device Framework; -- FinSH / MSH; -- DFS 文件系统; -- 网络协议栈; -- POSIX 接口; -- 日志系统; -- IoT 组件; -- 软件包; -- 各类中间件。 - -结构上可以理解为: - -```text -+--------------------------------+ -| Application | -+--------------------------------+ -| Components / Packages / IoT | -+--------------------------------+ -| RT-Thread Kernel | -+--------------------------------+ -| Device Framework | -+--------------------------------+ -| BSP / MCU Hardware | -+--------------------------------+ -``` - -### 3. RT-Thread Smart - -Smart 主要面向具有 **MMU** 的高性能处理器。 - -它能够利用 MMU 实现: - -```text -Application - │ - ▼ -+------------------+ -| User Space | -+------------------+ - │ - System Call - │ - ▼ -+------------------+ -| Kernel Space | -+------------------+ -``` - -与普通 MCU RTOS 相比,最大的特点是用户态与内核态隔离,可以提高系统的安全性和应用隔离能力。 - -### 三者对比 - -| 产品 | 面向硬件 | 主要特点 | 典型场景 | -|---|---|---|---| -| Nano | 资源受限 MCU | 精简、资源占用低 | 小型嵌入式设备 | -| Standard | 通用 MCU | 组件丰富、生态完整 | 常规复杂嵌入式系统 | -| Smart | 带 MMU 的处理器 | 用户态 / 内核态隔离 | 高性能嵌入式设备 | - -可以简单记忆: - -```text -Nano → 小而够用 -Standard → 完整 MCU RTOS -Smart → 面向 MMU 的操作系统形态 -``` - ---- - -## 四、RT-Thread 工程结构与设备框架 - -RT-Thread 工程中常见的几个目录包括: - -| 目录 | 主要作用 | -|---|---| -| `applications/` | 用户应用代码 | -| `drivers/` | BSP 与外设驱动 | -| `components/` | 文件系统、Shell、网络等系统组件 | -| `src/` | RT-Thread 内核源码 | -| `libc/` | C 库兼容相关内容 | - -应用开发中最常接触的是: - -```text -applications/main.c -``` - -它通常作为用户应用程序入口。 - -RT-Thread 还提供了统一的 Device Driver Framework: - -```text -Application - ↓ -RT-Thread Device API - ↓ -Device Framework - ↓ -UART / SPI / I2C / GPIO Driver - ↓ -MCU HAL / Hardware -``` - -这样上层应用不需要直接操作每一种芯片的底层寄存器,而是可以通过统一的设备接口访问硬件。 - ---- - -## 五、RT-Thread Studio 与 SDK Manager - -RT-Thread Studio 将 RT-Thread 项目创建、SDK 管理、编译、下载和调试等功能集成到一个 IDE 中。 - -整体开发环境可以理解为: - -```text -RT-Thread Studio -│ -├── RT-Thread Source Code -├── Chip Support Package -├── Toolchain -├── BSP -├── Build -├── Download -└── Debug -``` - -其中 **SDK Manager** 主要负责管理开发所需的软件资源,例如: - -- RT-Thread Source Code; -- STM32F4 芯片支持包; -- Toolchain; -- 相关 SDK 与开发资源。 - -因此 SDK Manager 可以理解为 RT-Thread Studio 中统一管理开发依赖的工具。 - ---- - -## 六、STM32F407 工程开发流程 - -课程使用 STM32F407 演示了一个完整的 RT-Thread 工程开发过程: - -```text -创建 RT-Thread 工程 - ↓ -选择 STM32F407 - ↓ -选择 RT-Thread 版本 - ↓ -配置工程 - ↓ -编写应用程序 - ↓ -Build - ↓ -生成 ELF / BIN - ↓ -ST-Link Download - ↓ -MCU Run - ↓ -Serial Terminal - ↓ -Debug -``` - -创建工程时需要关注: - -- MCU 型号; -- RT-Thread 版本; -- Toolchain; -- Debugger; -- BSP; -- 串口配置。 - -课程中串口使用: - -```text -Baud Rate = 115200 -``` - -串口主要用于: - -- 查看启动日志; -- 输出调试信息; -- 使用 Shell; -- 判断程序运行状态。 - ---- - -## 七、Build、Download、Run 与 Debug - -这几个概念在嵌入式开发中经常一起出现,但作用并不相同。 - -### Build - -Build 是将源代码转换为 MCU 可以执行的程序: - -```text -Source Code - ↓ -Preprocess - ↓ -Compile - ↓ -Assemble - ↓ -Link - ↓ -Executable Image -``` - -常见输出文件包括: - -- `.elf` -- `.bin` - -其中 ELF 通常还包含符号和调试信息,因此 Debug 时经常使用 ELF 文件;BIN 更接近纯二进制程序镜像,可以直接用于烧写。 - -### Download - -Download 是将已经编译完成的程序写入 MCU: - -```text -PC - ↓ -RT-Thread Studio - ↓ -ST-Link - ↓ -STM32F407 -``` - -### Run - -Run 表示 MCU 正常执行已经烧写的程序。 - -### Debug - -Debug 则通过 ST-Link 等调试工具进行: - -- 设置断点; -- 单步执行; -- 查看变量; -- 查看寄存器; -- 查看调用栈。 - -| 操作 | 作用 | -|---|---| -| Build | 编译程序 | -| Download | 烧写程序 | -| Run | 正常运行 | -| Debug | 调试程序 | - ---- - -## 八、FinSH / MSH 与系统调试 - -RT-Thread Standard 中提供 FinSH / MSH Shell,可以通过串口直接与系统交互。 - -Shell 可以用于: - -- 查看系统线程; -- 查看内存; -- 查看 IPC 对象; -- 查看 Timer; -- 执行系统命令。 - -与单纯打印日志相比,Shell 的优势在于: - -> 不需要反复修改程序并重新编译,就可以直接查看 RTOS 当前的运行状态。 - -因此在后续学习线程、IPC 和设备驱动时,MSH 会成为非常实用的调试工具。 - ---- - -## 九、Markdown 笔记规范 - -夏令营要求每天使用 Markdown 整理学习笔记和作业,因此第一天也学习了 Markdown 的常用语法。 - -### 标题 - -```markdown -# 一级标题 -## 二级标题 -### 三级标题 -``` - -### 加粗与行内代码 - -```markdown -**重点内容** - -`git status` -``` - -### 列表 - -```markdown -- RT-Thread -- Git -- Markdown -``` - -### 代码块 - -````markdown -```c -#include - -int main(void) -{ - return 0; -} -``` -```` - -### 表格 - -```markdown -| 命令 | 作用 | -|---|---| -| git add | 加入暂存区 | -| git commit | 创建提交 | -| git push | 推送远程仓库 | -``` - -### 图片 - -Markdown 图片语法: - -```markdown -![图片说明](./figures/example.png) -``` - -图片建议存放到: - -```text -figures/ -``` - -并使用相对路径。 - -例如: - -```text -./figures/image.png -``` - -比: - -```text -D:\xxx\image.png -``` - -更加适合 Git 仓库,因为相对路径能够随着整个工程一起移动,上传到 Gitee 后其他人也可以正常访问。 - ---- - -## 十、Git 基础与版本控制模型 - -Git 是一个**分布式版本控制系统**,主要用于: - -- 保存文件历史版本; -- 跟踪修改; -- 多人协作; -- 分支开发; -- 代码回退; -- 开源项目贡献。 - -理解 Git 最重要的是理解几个区域之间的数据流: - -```text -Working Tree - │ - │ git add - ▼ -Staging Area - │ - │ git commit - ▼ -Local Repository - │ - │ git push - ▼ -Remote Repository -``` - -分别对应: - -### 工作区 - -平时正在编辑的文件。 - -### 暂存区 - -使用: - -```bash -git add -``` - -选择本次 Commit 准备包含哪些修改。 - -### 本地仓库 - -使用: - -```bash -git commit -m "message" -``` - -把暂存区内容保存成一次正式版本记录。 - -### 远程仓库 - -使用: - -```bash -git push -``` - -将本地 Commit 上传到 Gitee 等远程平台。 - -因此最基本的数据流就是: - -```text -编辑 - ↓ -git add - ↓ -git commit - ↓ -git push -``` - ---- - -## 十一、Git 常用命令与分支管理 - -### 初始配置 - -```bash -git config --global user.name "你的用户名" -git config --global user.email "你的邮箱" -``` - -### 仓库操作 - -```bash -git init -git clone -``` - -### 查看状态与历史 - -```bash -git status -git diff -git diff --cached -git log --oneline -``` - -### 添加与提交 - -```bash -git add -git add . -git commit -m "提交说明" -``` - -### 远程仓库 - -```bash -git remote -v -git remote add origin -git remote add upstream -git remote set-url origin -``` - -### Push / Fetch / Pull - -```bash -git push origin -git fetch upstream -git pull origin master -``` - -三者的区别: - -```text -push -Local → Remote - -fetch -Remote → 获取最新信息 -但不自动修改当前工作分支 - -pull -fetch + merge -``` - -### 分支 - -查看分支: - -```bash -git branch -``` - -创建并切换: - -```bash -git checkout -b day1 -``` - -或者: - -```bash -git switch -c day1 -``` - -不同任务放在不同分支,可以减少相互影响: - -```text -master - │ - ├── day1 - ├── day2 - └── feature-x -``` - -### Stash - -临时保存尚未完成的修改: - -```bash -git stash -``` - -恢复: - -```bash -git stash pop -``` - -### 撤销修改 - -现代 Git 可以使用: - -```bash -git restore -git restore --staged -``` - -而: - -```bash -git reset --hard -``` - -可能直接丢失未保存修改,因此需要谨慎使用。 - ---- - -## 十二、Fork、PR 与开源协作流程 - -夏令营使用的是典型的 **Fork + Pull Request** 开源协作模式。 - -整体关系为: - -```text -RT-Thread 官方仓库 - upstream - │ - │ Fork - ▼ -个人 Gitee Fork - origin - │ - │ Clone - ▼ - 本地仓库 - │ - │ Edit - ▼ - git add - ↓ - git commit - ↓ - git push - ↓ -个人 Fork 功能分支 - │ - │ Pull Request - ▼ -RT-Thread 官方仓库 - │ - ▼ - Review - │ - ▼ - Merge -``` - -### Fork 与 Clone - -Fork: - -```text -官方远程仓库 - ↓ -个人远程仓库 -``` - -发生在 Gitee 等 Git 托管平台。 - -Clone: - -```text -远程仓库 - ↓ -本地电脑 -``` - -是把仓库完整下载到本地。 - -### origin 与 upstream - -Fork 工作流中: - -```text -origin -= -自己的 Fork - -upstream -= -官方原始仓库 -``` - -例如: - -```text -upstream -RT-Thread/rsoc-rtt - ▲ - │ Pull Request - │ -origin -个人/rsoc-rtt - ▲ - │ push - │ - 本地 -``` - -### Pull Request - -PR 并不是直接把代码强制合入官方仓库,而是: - -> 请求维护者审核自己的修改,在审核通过后再 Merge。 - -基本过程是: - -```text -Fork - ↓ -Clone - ↓ -Branch - ↓ -Edit - ↓ -git add - ↓ -git commit - ↓ -git push - ↓ -Pull Request - ↓ -Review - ↓ -Merge -``` - -### 同步 upstream - -官方仓库持续更新时,可以: - -```bash -git fetch upstream -``` - -再根据需要把官方最新内容合入自己的分支。 - -例如: - -```bash -git switch master -git fetch upstream -git merge upstream/master -git push origin master -``` - ---- - -## 十三、Git 冲突、Merge 与 Rebase - -多人协作时,如果两个分支修改了同一个文件中的同一部分内容,Git 无法自动判断应该保留哪个版本,就会产生冲突。 - -例如: - -```text -共同版本 A - │ - ├── Branch 1 修改 README - │ - └── Branch 2 同样修改 README -``` - -冲突文件中通常会出现: - -```text - <<<<<<< HEAD -当前分支内容 - ======= -另一侧内容 - >>>>>>> branch -``` - -解决流程: - -1. 阅读两侧修改; -2. 决定最终内容; -3. 删除冲突标记; -4. 保存文件; -5. `git add`; -6. 继续 Merge 或 Rebase。 - -### Merge - -```bash -git merge upstream/master -``` - -Merge 会保留分支之间的合并关系。 - -### Rebase - -```bash -git rebase master -``` - -Rebase 会把当前分支的 Commit 重新放到新的基线上,使历史更加线性。 - -可以简单理解: - -```text -原来: - -A---B---C - \ - D---E - -Rebase 后: - -A---B---C---D'---E' -``` - -因为 Rebase 会改写提交历史,所以对于已经共享的分支需要谨慎使用。 - ---- - -## 十四、几个容易混淆的 Git 概念 - -### Commit 不等于 Push - -```text -Commit -工作区 → 本地仓库 - -Push -本地仓库 → 远程仓库 -``` - -因此本地 Commit 完成,并不代表代码已经上传。 - -### Fetch 不等于 Pull - -```text -Fetch -只获取远端变化 - -Pull -获取远端变化 -+ -合并到当前分支 -``` - -### Fork 不等于 Clone - -```text -Fork -官方远程 → 我的远程 - -Clone -远程 → 我的电脑 -``` - -### origin 不等于 upstream - -```text -origin -自己的 Fork - -upstream -官方仓库 -``` - -这几个概念弄清楚以后,Git 的整体逻辑就会清晰很多。 - ---- - -## 十五、Day1 学习总结 - -Day1 最重要的收获,是初步建立了 **RT-Thread 开发 + 开源协作** 的整体认识。 - -在 RT-Thread 部分,主要理解了: - -- RTOS 为什么需要线程和调度; -- Thread、Scheduler、Tick、IPC 等基本概念; -- Nano、Standard、Smart 三条产品线的区别; -- Application、Component、Kernel、Driver 与 Hardware 之间的大致关系; -- STM32F407 工程从创建、编译、下载到调试的基本流程; -- FinSH / MSH 在 RTOS 调试中的作用。 - -在工具部分,掌握了 Markdown 基本语法和相对路径管理文档资源的方法。 - -Git 则是第一天非常重要的一部分。相比记忆大量命令,更重要的是理解: - -```text -Working Tree - ↓ -Staging Area - ↓ -Local Repository - ↓ -Remote Repository -``` - -以及: - -```text -Fork - ↓ -Clone - ↓ -Branch - ↓ -Commit - ↓ -Push - ↓ -Pull Request - ↓ -Review - ↓ -Merge -``` - -这两条主线。 - -理解了这些关系后,后续无论是每日作业提交,还是参与 RT-Thread 开源项目,都会更加清楚当前修改究竟位于本地、个人 Fork 还是官方仓库。 - -Day1 的学习为后续线程管理、IPC、设备驱动、系统组件以及第二周综合项目开发打下了基础。 diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/README.md" deleted file mode 100644 index 3a4a7089f9edc7edc0bb6fc425f00c0eee2a1214..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,41 +0,0 @@ -# RSOC 2026 Day3:RT-Thread 线程间同步与通信实验 - -## 1. 环境与任务 - -工程使用 RT-Thread 4.1.1、STM32F407ZG、ST-LINK SWD 和 USART1(PA9/PA10,115200 baud)。四个实验由 MSH 命令启动:`parking_demo`、`ticket_demo`、`startup_demo`、`package_demo`。每个命令由管理线程创建工作线程,完成后释放动态 IPC 对象并清除运行标志;运行中再次调用会报告 `demo already running`。 - -## 2. IPC 要点 - -| 对象 | 适用场景 | 本实验使用 | -|---|---|---| -| 计数信号量 | 可用资源数量 | 3 个停车位 | -| 互斥量 | 互斥访问且有所有权 | 完整售票读改写过程 | -| 事件集 | 多个条件的位组合 | 三个模块全部就绪 | -| 邮箱 | 固定宽度消息 | 本次仅作 API 对比 | -| 消息队列 | 定长结构体消息 | 快递消息与结束消息 | - -常用 API:`rt_sem_create/take/release/delete`,`rt_mutex_create/take/release/delete`,`rt_event_create/send/recv/delete`,`rt_mb_create/send/recv/delete`,`rt_mq_create/send/recv/delete`。阻塞等待会让出 CPU;共享统计量的短更新采用临界区,不能替代所需 IPC。 - -## 3. 停车场计数信号量 - -5 个车辆线程竞争初值为 3 的信号量。车辆以 `rt_tick_from_millisecond(2000)` 为等待上限,失败后打印并延迟重试;进入后更新最大占用量,离开后释放信号量。实机结果为 5/5 完成、最大占用 3、无容量越界。互斥量只能表示一把锁,不能记录三个可同时分配的车位,故应使用计数信号量。 - -## 4. 双窗口售票 - -无锁阶段刻意把“读取余票—短延时—写回”拆开,实机观察到 40 次售出记录和 20 个重复票号,说明仅锁住 `ticket_count--` 仍不能保护检查、读取、票号记录构成的完整事务。安全阶段用互斥量覆盖检查、读、修改和记录;互斥量具备所有权语义,并可支持优先级继承,实机结果为售出 20、剩余 0、重复 0。 - -## 5. 启动条件事件集 - -网络、传感器、存储线程分别发送 `EVENT_NET_READY`、`EVENT_SENSOR_READY`、`EVENT_STORAGE_READY`。业务线程用 `RT_EVENT_FLAG_AND` 等待全部事件。正常场景延时 500/1000/1500 ms;超时场景把存储延时设为 5000 ms,业务线程先在 3000 ms 后报告存储未就绪,再次等待后启动。三个信号量可以实现,但需要分别等待、维护组合状态;事件位直接表达“全部条件成立”。 - -## 6. 快递消息队列 - -两个生产者各发送 5 条确定编号的 `struct package_msg`,队列容量为 5,分拣线程故意较慢。队满时发送线程记录失败次数并重试同一消息;最后完成的生产者发送唯一 `0xFFFFFFFFU` 结束消息。分拣线程独占统计,实机得到总件数 10、华东 4、华南 3、华北 3、总重 10250 g、重复编号 0。 - -## 7. 构建与实机验证 - -SCons 使用 `scons -Q -j1` 链接成功;镜像大小为 text 68292、data 1904、bss 3448。ST-LINK 识别到 Device ID `0x413`,通过 SWD 下载、校验并复位成功。完整串口记录见 `day3_serial.log`;其中包含四个命令的真实输出与结果块。 - -## 8. 结论 - -本次实验把“资源数量、互斥事务、条件组合、结构化数据传递”分别映射到计数信号量、互斥量、事件集和消息队列。通过无锁售票与满队列重试,验证了 IPC 的必要性及资源回收、重复运行处理的重要性。 diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/day3_serial.log" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/day3_serial.log" deleted file mode 100644 index 13f274ad508d1373a6acd9d6415492a55fb352c4..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/day3_serial.log" +++ /dev/null @@ -1,159 +0,0 @@ -parking_demo -msh > -msh >[parking] car1 entered, used spaces: 1 -[parking] car2 entered, used spaces: 2 -[parking] car3 entered, used spaces: 3 -[parking] car3 left -[parking] car4 entered, used spaces: 3 -[parking] car1 left -[parking] car5 entered, used spaces: 3 -[parking] car2 left -[parking] car4 left -[parking] car5 left -========== PARKING RESULT ========== - cars completed: 5/5 - max used spaces: 3 - capacity violation: NO - result: PASS -ticket_demo -msh > -msh >[ticket B] sold ticket 20 -[ticket A] sold ticket 20 -[ticket B] sold ticket 19 -[ticket A] sold ticket 19 -[ticket B] sold ticket 18 -[ticket A] sold ticket 18 -[ticket B] sold ticket 17 -[ticket A] sold ticket 17 -[ticket B] sold ticket 16 -[ticket A] sold ticket 16 -[ticket B] sold ticket 15 -[ticket A] sold ticket 15 -[ticket B] sold ticket 14 -[ticket A] sold ticket 14 -[ticket B] sold ticket 13 -[ticket A] sold ticket 13 -[ticket B] sold ticket 12 -[ticket A] sold ticket 12 -[ticket B] sold ticket 11 -[ticket A] sold ticket 11 -[ticket B] sold ticket 10 -[ticket A] sold ticket 10 -[ticket B] sold ticket 9 -[ticket A] sold ticket 9 -[ticket B] sold ticket 8 -[ticket A] sold ticket 8 -[ticket B] sold ticket 7 -[ticket A] sold ticket 7 -[ticket B] sold ticket 6 -[ticket A] sold ticket 6 -[ticket B] sold ticket 5 -[ticket A] sold ticket 5 -[ticket B] sold ticket 4 -[ticket A] sold ticket 4 -[ticket B] sold ticket 3 -[ticket A] sold ticket 3 -[ticket B] sold ticket 2 -[ticket A] sold ticket 2 -[ticket B] sold ticket 1 -[ticket A] sold ticket 1 -========== TICKET UNSAFE RESULT ========== - sold: 40, remaining: 0, duplicates: 20, negative: NO - result: RACE OBSERVED -[ticket B] sold ticket 20 -[ticket A] sold ticket 19 -[ticket B] sold ticket 18 -[ticket A] sold ticket 17 -[ticket B] sold ticket 16 -[ticket A] sold ticket 15 -[ticket B] sold ticket 14 -[ticket A] sold ticket 13 -[ticket B] sold ticket 12 -[ticket A] sold ticket 11 -[ticket B] sold ticket 10 -[ticket A] sold ticket 9 -[ticket B] sold ticket 8 -[ticket A] sold ticket 7 -[ticket B] sold ticket 6 -[ticket A] sold ticket 5 -[ticket B] sold ticket 4 -[ticket A] sold ticket 3 -[ticket B] sold ticket 2 -[ticket A] sold ticket 1 -========== TICKET SAFE RESULT ========== - sold: 20, remaining: 0, duplicates: 0, negative: NO - result: PASS -startup_demo -[startup] normal case -msh > -msh >[startup] network ready -[startup] sensor ready -[startup] storage ready -[startup] all modules are ready -[startup] business task started -[startup] timeout case -[startup] network ready -[startup] sensor ready -[startup] wait modules timeout, storage is not ready -[startup] storage ready -[startup] all modules are ready -[startup] business task started -package_demo -[package] package 2001 -> region 2, weight: 850 g -[package] producer2 sent 2001 -[package] producer1 sent 1001 -msh > -msh >[package] producer1 sent 1002 -[package] producer2 sent 2002 -[package] producer1 sent 1003 -[package] producer2 sent 2003 -[package] queue full, retry package 03ec -[package] queue full, retry package 07d4 -[package] queue full, retry package 03ec -[package] queue full, retry package 07d4 -[package] queue full, retry package 03ec -[package] package 1001 -> region 1, weight: 1200 g -[package] producer2 sent 2004 -[package] queue full, retry package 03ec -[package] queue full, retry package 07d5 -[package] queue full, retry package 03ec -[package] queue full, retry package 07d5 -[package] queue full, retry package 03ec -[package] queue full, retry package 07d5 -[package] queue full, retry package 03ec -[package] queue full, retry package 07d5 -[package] package 1002 -> region 3, weight: 960 g -[package] producer1 sent 1004 -[package] queue full,[package] queue full, retry package 07d5 - retry package 07d5 -[package] queue full, retry pack[package] queue full, retry package 03ed -age 03ed -[package] queue full, retry package 03ed -[package] queue full, retry package 07d5 -[package] queue full, retry package 07d5 -[package] queue full, retry package 03ed -[package] package 2002 -> region 1, weight: 1300 g -[package] producer2 sent 2005 -[package] queue full, retry package 03ed -[package] queue full, retry package 03ed -[package] queue full, retry package 03ed -[package] queue full, retry package 03ed -[package] package 1003 -> region 1, weight: 900 g -[package] producer1 sent 1005 -[package] queue full, retry package ffffffff -[package] queue full, retry package ffffffff -[package] queue full, retry package ffffffff -[package] queue full, retry package ffffffff -[package] package 2003 -> region 3, weight: 780 g -[package] package 2004 -> region 2, weight: 1200 g -[package] package 1004 -> region 2, weight: 1100 g -[package] package 2005 -> region 1, weight: 910 g -[package] package 1005 -> region 3, weight: 1050 g -========== PACKAGE RESULT ========== - total packages: 10 - east: 4, south: 3, north: 3 - total weight: 10250 g - send failed: 29 - duplicates: 0 - result: PASS - diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/package_demo.c" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/package_demo.c" deleted file mode 100644 index e0cc1f7fcc687d682a4c19301c27b8718d4d397c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/package_demo.c" +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include -#include -struct package_msg { rt_uint32_t id; rt_uint8_t region; rt_uint16_t weight; }; -#define END_ID 0xFFFFFFFFU -static rt_mq_t pkg_mq; static volatile int pkg_producers, pkg_done, pkg_fail; static rt_uint8_t pkg_running; -static const struct package_msg pkg_data[2][5]={{ {1001,1,1200},{1002,3,960},{1003,1,900},{1004,2,1100},{1005,3,1050} },{{2001,2,850},{2002,1,1300},{2003,3,780},{2004,2,1200},{2005,1,910}}}; -static void pkg_send(const struct package_msg *m){while(rt_mq_send(pkg_mq,m,sizeof(*m))!=RT_EOK){rt_base_t l=rt_hw_interrupt_disable();++pkg_fail;rt_hw_interrupt_enable(l);rt_kprintf("[package] queue full, retry package %04x\n",m->id);rt_thread_mdelay(30);}} -static void producer(void *p){int n=(int)(rt_ubase_t)p,i,last=0;for(i=0;i<5;i++){pkg_send(&pkg_data[n][i]);rt_kprintf("[package] producer%d sent %u\n",n+1,pkg_data[n][i].id);rt_thread_mdelay(20+n*10);} {rt_base_t l=rt_hw_interrupt_disable();if(++pkg_producers==2)last=1;rt_hw_interrupt_enable(l);}if(last){struct package_msg end={END_ID,0,0};pkg_send(&end);} {rt_base_t l=rt_hw_interrupt_disable();++pkg_done;rt_hw_interrupt_enable(l);}} -static void sorter(void *p){struct package_msg m;int total=0,regions[4]={0},weight=0,seen[10]={0},dup=0;(void)p;for(;;){rt_mq_recv(pkg_mq,&m,sizeof(m),RT_WAITING_FOREVER);if(m.id==END_ID)break;if((m.id>=1001&&m.id<=1005)||(m.id>=2001&&m.id<=2005)){int ix=(m.id>=2000)?m.id-1996:m.id-1001;if(seen[ix]++)dup++;}++total;++regions[m.region];weight+=m.weight;rt_kprintf("[package] package %u -> region %d, weight: %d g\n",m.id,m.region,m.weight);rt_thread_mdelay(150);}rt_kprintf("========== PACKAGE RESULT ==========\n total packages: %d\n east: %d, south: %d, north: %d\n total weight: %d g\n send failed: %d\n duplicates: %d\n result: %s\n",total,regions[1],regions[2],regions[3],weight,pkg_fail,dup,(total==10&&weight==10250&&!dup)?"PASS":"FAIL");{rt_base_t l=rt_hw_interrupt_disable();++pkg_done;rt_hw_interrupt_enable(l);}} -static void package_manager(void *p){rt_thread_t a,b,s;(void)p;pkg_producers=pkg_done=pkg_fail=0;pkg_mq=rt_mq_create("pkgmq",sizeof(struct package_msg),5,RT_IPC_FLAG_FIFO);if(!pkg_mq){rt_kprintf("[package] message queue create failed\n");goto out;}s=rt_thread_create("sorter",sorter,0,1024,18,10);a=rt_thread_create("prod1",producer,(void*)0,768,19,10);b=rt_thread_create("prod2",producer,(void*)1,768,19,10);if(!s||!a||!b||rt_thread_startup(s)!=RT_EOK||rt_thread_startup(a)!=RT_EOK||rt_thread_startup(b)!=RT_EOK){rt_kprintf("[package] thread create/start failed\n");goto out;}while(pkg_done<3)rt_thread_mdelay(30);rt_mq_delete(pkg_mq);pkg_mq=RT_NULL;out:{rt_base_t l=rt_hw_interrupt_disable();pkg_running=0;rt_hw_interrupt_enable(l);}} -static void package_demo(void){rt_thread_t t;rt_base_t l=rt_hw_interrupt_disable();if(pkg_running){rt_hw_interrupt_enable(l);rt_kprintf("[package] demo already running\n");return;}pkg_running=1;rt_hw_interrupt_enable(l);t=rt_thread_create("pkgmgr",package_manager,0,1024,17,10);if(!t||rt_thread_startup(t)!=RT_EOK){rt_kprintf("[package] manager create/start failed\n");l=rt_hw_interrupt_disable();pkg_running=0;rt_hw_interrupt_enable(l);}} -MSH_CMD_EXPORT(package_demo, package message queue demo); diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/parking_demo.c" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/parking_demo.c" deleted file mode 100644 index 16a3c0e5865005404a4f013c9fd9a617aaed13e2..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/parking_demo.c" +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include -#include - -#define PARK_CAPACITY 3 -#define PARK_CARS 5 -static rt_sem_t park_sem; -static rt_thread_t park_workers[PARK_CARS]; -static rt_uint8_t park_running, park_done, park_used, park_max, park_bad; -static int park_ids[PARK_CARS] = {1, 2, 3, 4, 5}; - -static void park_car(void *p) -{ - int id = *(int *)p; - rt_thread_mdelay(80 * id); - while (rt_sem_take(park_sem, rt_tick_from_millisecond(2000)) != RT_EOK) - { - rt_kprintf("[parking] car%d waiting timeout, retry later\n", id); - rt_thread_mdelay(150 + id * 20); - } - { rt_base_t l = rt_hw_interrupt_disable(); ++park_used; if (park_used > park_max) park_max = park_used; if (park_used > PARK_CAPACITY) park_bad = 1; rt_hw_interrupt_enable(l); } - rt_kprintf("[parking] car%d entered, used spaces: %d\n", id, park_used); - rt_thread_mdelay(500 + (id % 3) * 180); - { rt_base_t l = rt_hw_interrupt_disable(); --park_used; rt_hw_interrupt_enable(l); } - rt_sem_release(park_sem); - rt_kprintf("[parking] car%d left\n", id); - { rt_base_t l = rt_hw_interrupt_disable(); ++park_done; rt_hw_interrupt_enable(l); } -} - -static void park_manager(void *p) -{ - int i; (void)p; - park_done = park_used = park_max = park_bad = 0; - park_sem = rt_sem_create("parksem", PARK_CAPACITY, RT_IPC_FLAG_FIFO); - if (!park_sem) goto out; - for (i = 0; i < PARK_CARS; i++) - { - park_workers[i] = rt_thread_create("parkcar", park_car, &park_ids[i], 768, 18, 10); - if (!park_workers[i] || rt_thread_startup(park_workers[i]) != RT_EOK) { rt_kprintf("[parking] car thread create/start failed\n"); goto wait; } - } -wait: - while (park_done < PARK_CARS) rt_thread_mdelay(50); - rt_kprintf("========== PARKING RESULT ==========\n cars completed: %d/%d\n max used spaces: %d\n capacity violation: %s\n result: %s\n", park_done, PARK_CARS, park_max, park_bad ? "YES" : "NO", (!park_bad && park_done == PARK_CARS) ? "PASS" : "FAIL"); - rt_sem_delete(park_sem); park_sem = RT_NULL; -out: - { rt_base_t l = rt_hw_interrupt_disable(); park_running = 0; rt_hw_interrupt_enable(l); } -} - -static void parking_demo(void) -{ - rt_thread_t t; rt_base_t l = rt_hw_interrupt_disable(); if (park_running) { rt_hw_interrupt_enable(l); rt_kprintf("[parking] demo already running\n"); return; } park_running = 1; rt_hw_interrupt_enable(l); - t = rt_thread_create("parkmgr", park_manager, RT_NULL, 1024, 17, 10); - if (!t || rt_thread_startup(t) != RT_EOK) { rt_kprintf("[parking] manager create/start failed\n"); l = rt_hw_interrupt_disable(); park_running = 0; rt_hw_interrupt_enable(l); } -} -MSH_CMD_EXPORT(parking_demo, parking counting semaphore demo); diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/startup_demo.c" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/startup_demo.c" deleted file mode 100644 index 2a5859c862973f5839d0949ab83796e5d193187e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/startup_demo.c" +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include -#include -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) -static rt_event_t boot_event; static volatile int boot_done; static rt_uint8_t boot_running; -struct boot_arg { const char *name; rt_uint32_t bit; int delay; }; -static struct boot_arg boot_args[3]; -static void boot_module(void *p) { struct boot_arg *a=p; rt_thread_mdelay(a->delay); rt_event_send(boot_event,a->bit); rt_kprintf("[startup] %s ready\n",a->name); {rt_base_t l=rt_hw_interrupt_disable();++boot_done;rt_hw_interrupt_enable(l);} } -static void boot_business(void *p) { rt_uint32_t got=0; (void)p; if(rt_event_recv(boot_event,EVENT_ALL,RT_EVENT_FLAG_AND,rt_tick_from_millisecond(3000),&got)!=RT_EOK){rt_kprintf("[startup] wait modules timeout, storage is not ready\n");rt_event_recv(boot_event,EVENT_ALL,RT_EVENT_FLAG_AND,RT_WAITING_FOREVER,&got);}rt_kprintf("[startup] all modules are ready\n[startup] business task started\n");{rt_base_t l=rt_hw_interrupt_disable();++boot_done;rt_hw_interrupt_enable(l);} } -static void boot_case(int storage_delay) { rt_thread_t t[4]; int i; boot_done=0; boot_event=rt_event_create("bootevt",RT_IPC_FLAG_FIFO);if(!boot_event){rt_kprintf("[startup] event create failed\n");return;} boot_args[0]=(struct boot_arg){"network",EVENT_NET_READY,500};boot_args[1]=(struct boot_arg){"sensor",EVENT_SENSOR_READY,1000};boot_args[2]=(struct boot_arg){"storage",EVENT_STORAGE_READY,storage_delay};for(i=0;i<3;i++){t[i]=rt_thread_create("bootmod",boot_module,&boot_args[i],768,18,10);if(!t[i]||rt_thread_startup(t[i])!=RT_EOK){rt_kprintf("[startup] module create/start failed\n");return;}}t[3]=rt_thread_create("bootbiz",boot_business,0,768,19,10);if(!t[3]||rt_thread_startup(t[3])!=RT_EOK){rt_kprintf("[startup] business create/start failed\n");return;}while(boot_done<4)rt_thread_mdelay(20);rt_event_delete(boot_event);boot_event=RT_NULL; } -static void startup_manager(void *p){(void)p;rt_kprintf("[startup] normal case\n");boot_case(1500);rt_kprintf("[startup] timeout case\n");boot_case(5000);{rt_base_t l=rt_hw_interrupt_disable();boot_running=0;rt_hw_interrupt_enable(l);}} -static void startup_demo(void){rt_thread_t t;rt_base_t l=rt_hw_interrupt_disable();if(boot_running){rt_hw_interrupt_enable(l);rt_kprintf("[startup] demo already running\n");return;}boot_running=1;rt_hw_interrupt_enable(l);t=rt_thread_create("bootmgr",startup_manager,0,1024,17,10);if(!t||rt_thread_startup(t)!=RT_EOK){rt_kprintf("[startup] manager create/start failed\n");l=rt_hw_interrupt_disable();boot_running=0;rt_hw_interrupt_enable(l);}} -MSH_CMD_EXPORT(startup_demo, startup event AND and timeout demo); diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/ticket_demo.c" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/ticket_demo.c" deleted file mode 100644 index 7d6cee8465bbb59a1c22265c6c7e3fcd09167e2a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\344\275\234\344\270\232/ticket_demo.c" +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include -#include - -#define TICKET_TOTAL 20 -static rt_mutex_t ticket_lock; -static volatile int tickets, ticket_done, ticket_sold, ticket_dup, ticket_neg; -static rt_uint8_t ticket_safe, ticket_running, ticket_seen[TICKET_TOTAL + 1]; - -static void ticket_window(void *p) -{ - char name = (char)(rt_ubase_t)p; - for (;;) - { - int snapshot, number; - if (ticket_safe) rt_mutex_take(ticket_lock, RT_WAITING_FOREVER); - snapshot = tickets; - if (snapshot <= 0) { if (ticket_safe) rt_mutex_release(ticket_lock); break; } - if (!ticket_safe) rt_thread_mdelay(12); /* intentionally leaves read-modify-write unprotected */ - tickets = snapshot - 1; number = snapshot; - { rt_base_t l = rt_hw_interrupt_disable(); if (ticket_seen[number]++) ++ticket_dup; ++ticket_sold; if (tickets < 0) ticket_neg = 1; rt_hw_interrupt_enable(l); } - if (ticket_safe) rt_mutex_release(ticket_lock); - rt_kprintf("[ticket %c] sold ticket %d\n", name, number); - rt_thread_mdelay(5); - } - { rt_base_t l = rt_hw_interrupt_disable(); ++ticket_done; rt_hw_interrupt_enable(l); } -} -static void ticket_phase(rt_uint8_t safe) -{ - rt_thread_t a, b; int i; ticket_safe = safe; tickets = TICKET_TOTAL; ticket_done = ticket_sold = ticket_dup = ticket_neg = 0; for (i=0;i<=TICKET_TOTAL;i++) ticket_seen[i]=0; - if (safe && !(ticket_lock = rt_mutex_create("ticketmx", RT_IPC_FLAG_FIFO))) { rt_kprintf("[ticket] mutex create failed\n"); return; } - a=rt_thread_create("winA",ticket_window,(void *)(rt_ubase_t)'A',768,19,10); b=rt_thread_create("winB",ticket_window,(void *)(rt_ubase_t)'B',768,19,10); - if (!a || !b || rt_thread_startup(a)!=RT_EOK || rt_thread_startup(b)!=RT_EOK) { rt_kprintf("[ticket] window create/start failed\n"); return; } - while(ticket_done<2) rt_thread_mdelay(20); - rt_kprintf("========== TICKET %s RESULT ==========\n sold: %d, remaining: %d, duplicates: %d, negative: %s\n result: %s\n", safe?"SAFE":"UNSAFE",ticket_sold,tickets,ticket_dup,ticket_neg?"YES":"NO", safe?(ticket_sold==20&&tickets==0&&!ticket_dup&&!ticket_neg?"PASS":"FAIL"):(ticket_dup||ticket_sold!=20?"RACE OBSERVED":"RACE NOT OBSERVED")); - if(safe) { rt_mutex_delete(ticket_lock); ticket_lock=RT_NULL; } -} -static void ticket_manager(void *p) { (void)p; ticket_phase(0); ticket_phase(1); { rt_base_t l=rt_hw_interrupt_disable(); ticket_running=0; rt_hw_interrupt_enable(l); } } -static void ticket_demo(void) { rt_thread_t t; rt_base_t l=rt_hw_interrupt_disable(); if(ticket_running){rt_hw_interrupt_enable(l);rt_kprintf("[ticket] demo already running\n");return;}ticket_running=1;rt_hw_interrupt_enable(l);t=rt_thread_create("ticketmgr",ticket_manager,0,1024,17,10);if(!t||rt_thread_startup(t)!=RT_EOK){rt_kprintf("[ticket] manager create/start failed\n");l=rt_hw_interrupt_disable();ticket_running=0;rt_hw_interrupt_enable(l);} } -MSH_CMD_EXPORT(ticket_demo, ticket mutex unsafe and safe comparison); diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 512a8fbfe00e20e49bdfbb85304de92790e6fa08..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\270\211\345\244\251/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,820 +0,0 @@ -# RT-Thread 夏令营 Day3 学习笔记 - -> 主题:RT-Thread 线程间同步与通信 -> 核心内容:临界区、信号量、互斥量、事件集、消息邮箱、消息队列 - ---- - -## 一、线程间同步与通信概述 - -进入多线程程序后,不同线程之间不仅需要被调度,还需要解决同步与通信问题: - -- **线程同步**:控制多个线程之间的执行顺序或共享资源访问关系; -- **线程通信**:在线程之间传递状态或实际数据。 - -本节主要涉及: - -```text -线程间同步 -├── 信号量 Semaphore -├── 互斥量 Mutex -└── 事件集 Event - -线程间通信 -├── 消息邮箱 Mailbox -└── 消息队列 Message Queue -``` - ---- - -## 二、临界区与阻塞 - -### 2.1 临界区 - -当某个资源在同一时刻只能被一个执行主体访问时,访问该资源的代码区域称为**临界区**。 - -例如多个线程同时修改: - -```c -ticket_count--; -``` - -如果没有同步保护,多个线程可能在同一时刻读取、修改并写回共享变量,从而产生竞态条件。 - -### 2.2 阻塞 - -线程请求暂时不可用的资源时,如果该操作允许等待,线程会进入阻塞状态: - -```text -RUNNING - ↓ -请求资源 - ↓ -资源不可用 - ↓ -SUSPEND - ↓ -资源可用 - ↓ -READY - ↓ -重新参与调度 -``` - -阻塞期间线程不会持续占用 CPU。 - -### 2.3 非阻塞 - -非阻塞操作在资源不可用时立即返回,由线程自行处理返回结果: - -```text -阻塞: -资源没有 → 等待 - -非阻塞: -资源没有 → 立即返回 -``` - ---- - -## 三、信号量 Semaphore - -信号量用于线程同步和资源计数。 - -基本操作: - -```text -take - ↓ -value - 1 - -release - ↓ -value + 1 -``` - -当信号量不可获得时,请求线程可依据 timeout 参数等待。 - ---- - -## 四、二值信号量 - -二值信号量主要用于表示某个同步事件是否已经发生。 - -```text -Semaphore = 0 -→ 事件尚未发生 - -release -→ 事件发生 - -等待线程被唤醒 -``` - -典型模型: - -```text -事件线程 / ISR - ↓ -release semaphore - ↓ -等待线程 READY - ↓ -执行后续处理 -``` - ---- - -## 五、计数信号量 - -计数信号量适合管理多个同类有限资源。 - -若系统共有 N 个资源: - -```text -Semaphore Initial Value = N -``` - -获得资源: - -```text -value-- -``` - -释放资源: - -```text -value++ -``` - -当 `value = 0` 时,表示资源已全部占用。 - -典型场景: - -```text -停车位数量 - ↓ -信号量初始值 - -车辆 - ↓ -线程 -``` - -因此: - -> **计数信号量适合描述“有多少个资源可以同时被使用”。** - ---- - -## 六、信号量等待方式与生命周期 - -IPC 等待策略主要包括: - -```text -FIFO -PRIO -``` - -### FIFO - -按照进入等待队列的先后顺序获得资源。 - -### PRIO - -按照线程优先级决定等待顺序,高优先级线程更早获得资源。 - -IPC 对象还常见两套生命周期接口: - -```text -动态创建: -create → delete - -静态初始化: -init → detach -``` - -两套生命周期接口应成对使用。 - ---- - -## 七、互斥量 Mutex - -互斥量主要用于保护只能由一个线程访问的共享临界资源。 - -```text -Thread A - ↓ -take Mutex - ↓ -进入临界区 - ↓ -操作共享数据 - ↓ -release Mutex -``` - -若 Thread B 同时申请该 Mutex: - -```text -Mutex 已被 A 持有 - ↓ -Thread B 阻塞 -``` - -直到 A 释放互斥量后,B 才能继续运行。 - ---- - -## 八、Mutex 与 Semaphore 的区别 - -虽然 Mutex 和二值信号量都可表现为两种状态,但语义不同。 - -### Semaphore - -核心是: - -```text -信号 / 资源数量 -``` - -### Mutex - -核心是: - -```text -Ownership -``` - -哪个线程获得 Mutex,哪个线程就是当前 Owner。只有持有者才能正确释放 Mutex。 - -因此: - -```text -Semaphore -→ 同步、资源数量 - -Mutex -→ 保护共享变量、结构体和完整临界操作 -``` - ---- - -## 九、为什么不能只保护一条语句 - -售票过程是一个完整事务: - -```text -检查是否有票 - ↓ -读取当前票号 - ↓ -修改剩余票数 - ↓ -记录售出票号 -``` - -若只保护: - -```c -ticket_count--; -``` - -而把: - -```c -if (ticket_count > 0) -``` - -放在锁外,则两个线程仍可能同时判断“还有票”,造成竞态。 - -因此 Mutex 应保护完整的: - -```text -check → read → modify → record -``` - -而不是只保护单条赋值语句。 - ---- - -## 十、优先级反转与优先级继承 - -假设: - -```text -A:高优先级 -B:中优先级 -C:低优先级 -``` - -C 先持有 Mutex,随后 A 需要该 Mutex,因此 A 被阻塞。如果此时 B 抢占 C,则: - -```text -A 等 C -C 被 B 抢占 -``` - -实际形成中优先级 B 间接阻塞高优先级 A,即**优先级反转**。 - -优先级继承机制可使 C 临时继承 A 的高优先级: - -```text -A 等待 C 的 Mutex - ↓ -C 临时提高优先级 - ↓ -C 完成临界区 - ↓ -release Mutex - ↓ -A 获得 Mutex -``` - ---- - -## 十一、系统临界区与 Mutex - -系统临界区应尽可能短,因为它会影响系统调度甚至中断响应。 - -一般原则: - -```text -极短的变量更新 -→ 可使用短临界区 - -较完整的线程级共享资源操作 -→ Mutex -``` - -不应在临界区中执行: - -```text -长时间循环 -阻塞等待 -线程延时 -复杂打印 -``` - ---- - -## 十二、事件集 Event - -事件集用于表示多个独立同步条件。 - -RT-Thread 使用一个 32 位无符号值表示事件集合: - -```text -Bit 0 → Event 0 -Bit 1 → Event 1 -Bit 2 → Event 2 -... -``` - -例如: - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -``` - ---- - -## 十三、Event OR 与 AND - -### OR - -任意一个指定事件发生即可满足条件: - -```text -Event A ─┐ - ├── OR → Wake Thread -Event B ─┘ -``` - -### AND - -必须等待所有指定事件都发生: - -```text -Event A ─┐ -Event B ─┼── AND → Wake Thread -Event C ─┘ -``` - -系统启动场景: - -```text -Network Ready - + -Sensor Ready - + -Storage Ready - ↓ -Business Task Start -``` - ---- - -## 十四、Event CLEAR - -事件接收后可以根据需要清除对应事件位: - -```text -Event Bits -00101100 - ↓ -Receive + CLEAR - ↓ -对应事件位清零 -``` - -事件集主要用于同步,不负责传递实际业务数据。 - -同一事件位被多次置位,并不会像消息队列那样形成多条排队消息。 - ---- - -## 十五、同步与通信的区别 - -同步类 IPC: - -```text -Semaphore -Mutex -Event -``` - -主要解决: - -```text -什么时候运行? -谁能访问资源? -某些条件是否满足? -``` - -通信类 IPC: - -```text -Mailbox -Message Queue -``` - -主要解决实际数据在线程间的传递问题。 - ---- - -## 十六、消息邮箱 Mailbox - -Mailbox 是一种轻量线程通信机制。 - -在 32 位系统中,一封邮件可用于携带一个机器字大小的数据,因此可以保存整数值或指针。 - -典型模型: - -```text -Producer Thread - ↓ - Mailbox - ↓ -Consumer Thread -``` - -例如: - -```text -Sensor Thread - ↓ -数据值 / 指针 - ↓ -Mailbox - ↓ -Display Thread -``` - ---- - -## 十七、Mailbox 的发送与接收 - -发送时: - -```text -Mailbox 有空间 - ↓ -send - ↓ -Message Stored -``` - -接收时: - -```text -Mailbox 有邮件 - ↓ -recv - ↓ -立即取得 -``` - -如果邮箱为空: - -```text -recv - ↓ -按照 timeout 等待 - ↓ -线程阻塞 -``` - ---- - -## 十八、消息队列 Message Queue - -Message Queue 比 Mailbox 更适合传递结构化数据。 - -```text -Mailbox -→ 单个邮件数据尺寸固定 - -Message Queue -→ 消息尺寸可配置 -``` - -例如: - -```c -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -``` - -因此消息队列适合: - -```text -传感器数据 -通信帧 -结构体消息 -生产者 / 消费者模型 -``` - ---- - -## 十九、消息队列的数据组织 - -消息队列通常包含: - -```text -消息缓冲区 -消息最大长度 -最大消息数量 -当前消息数量 -消息链表 -空闲消息链表 -等待线程队列 -``` - -发送时: - -```text -Producer - ↓ -复制消息内容 - ↓ -Queue Node - ↓ -加入消息队列 -``` - -接收时: - -```text -Message Queue - ↓ -取出消息 - ↓ -复制到接收 buffer - ↓ -Consumer -``` - -因此 Message Queue 传输的是消息内容的副本。 - ---- - -## 二十、普通消息与紧急消息 - -普通消息按照队列顺序处理: - -```text -A -B -C -D -``` - -紧急消息可以插入队列前部: - -```text -A -URGENT -B -C -D -``` - -消费者下一次接收时会优先取得紧急消息。 - ---- - -## 二十一、五类 IPC 对比 - -| IPC | 主要功能 | 是否传输数据 | 典型应用 | -|---|---|---:|---| -| Semaphore | 同步 / 资源计数 | 否 | 按键同步、停车位 | -| Mutex | 共享资源互斥 | 否 | 共享变量、设备访问 | -| Event | 多条件组合同步 | 否 | 多模块 Ready | -| Mailbox | 轻量消息传递 | 是 | 状态值、指针 | -| Message Queue | 结构化消息传递 | 是 | 数据结构、生产者/消费者 | - -选择关系可以概括为: - -```text -有多个同类有限资源? - ↓ -Counting Semaphore - -单个共享临界资源? - ↓ -Mutex - -等待多个布尔条件? - ↓ -Event - -轻量固定字长消息? - ↓ -Mailbox - -结构化数据? - ↓ -Message Queue -``` - ---- - -## 二十二、动态创建与静态初始化 - -RT-Thread IPC 对象普遍支持: - -```text -动态方式: - -create - ↓ -使用 - ↓ -delete -``` - -以及: - -```text -静态方式: - -init - ↓ -使用 - ↓ -detach -``` - -即: - -```text -create ↔ delete -init ↔ detach -``` - ---- - -## 二十三、IPC 与线程调度的关系 - -IPC 的阻塞机制本质上仍建立在线程状态与调度器之上。 - -例如等待信号量: - -```text -RUNNING - ↓ -sem take - ↓ -资源不可用 - ↓ -SUSPEND - ↓ -其他线程 release - ↓ -READY - ↓ -Scheduler - ↓ -RUNNING -``` - -Mutex、Event、Mailbox 和 Message Queue 的阻塞接收也遵循类似状态变化。 - -因此 Day2 的: - -```text -READY -RUNNING -SUSPEND -Priority -Scheduler -``` - -是理解 Day3 IPC 的基础。 - ---- - -## 二十四、Day3 核心知识关系 - -```text -多线程系统 - ↓ -线程之间需要协作 - ↓ -同步 or 通信 - │ - ├──────────── 同步 ────────────┐ - │ │ - ↓ ↓ -Semaphore Mutex -资源数量 / 同步 临界资源 - │ │ - └──────────────┬───────────────┘ - ↓ - Event - 多条件同步 - - ↓ - - 数据通信需求 - - ┌─────┴─────┐ - ↓ ↓ - Mailbox Message Queue - 轻量消息 结构化消息 -``` - -从线程运行角度看: - -```text -Thread - ↓ -请求 IPC - ↓ -条件满足? - ┌───────┐ -Yes No - ↓ ↓ -继续 SUSPEND - ↓ - 条件被其他线程满足 - ↓ - READY - ↓ - Scheduler -``` - ---- - -## 二十五、本节内容总结 - -Day3 的核心是线程之间的**协作机制**。 - -```text -Semaphore -→ 同步和资源数量 - -Mutex -→ 共享资源互斥 - -Event -→ 多条件组合 - -Mailbox -→ 轻量消息 - -Message Queue -→ 结构化消息 -``` - -信号量重点在资源数量和同步;互斥量重点在所有权和临界区;事件集重点在多个条件的逻辑组合;Mailbox 和 Message Queue 则用于真正的数据通信。 - -理解这些 IPC 的差异后,可以根据实际资源模型和数据流选择合适的同步与通信机制,而不是简单依赖全局变量在多个线程之间共享状态。 - diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\344\275\234\344\270\232/README.md" deleted file mode 100644 index cbc16ce7a8b9cc69373dc10da6a8b72c682d3016..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,292 +0,0 @@ -# RSOC 2026 Day2:RT-Thread 启动流程、内核基础与多线程实验 - -## 1. 实验环境与任务 - -| 项目 | 配置 | -| --- | --- | -| MCU | STM32F407ZG | -| RTOS | RT-Thread 4.1.1 | -| 开发环境 | RT-Thread Studio | -| 编译器 | ARM GCC | -| 调试接口 | ST-Link / SWD | -| Console | USART1 / COM7,115200 8-N-1 | -| 板卡 | RT-Thread 星火 1 号 | -| LED_R | PF12,低电平有效 | -| LED_B | PF11,低电平有效 | -| KEY0 | PC0,低电平有效 | - -实验完成 RT-Thread 启动流程与自动初始化机制分析,并实现动态线程、静态线程、高优先级监控线程、线程状态与栈空间监测、按键挂起恢复以及 MSH 自定义命令。应用模块通过 `INIT_APP_EXPORT(thread_study_init)` 自动初始化,`applications/main.c` 无需增加线程初始化调用。 - -## 2. RT-Thread 系统启动流程 - -以下流程基于 RT-Thread 4.1.1 源码、GNU GCC 构建配置和 STM32F407 链接脚本。 - -```text -Reset_Handler - ↓ -C Runtime - ↓ -entry() - ↓ -rtthread_startup() - ↓ -rt_hw_board_init() - ↓ -rt_components_board_init() - ↓ -rt_system_timer_init() - ↓ -rt_system_scheduler_init() - ↓ -rt_application_init() - ↓ -rt_thread_idle_init() - ↓ -rt_system_scheduler_start() - ↓ -main_thread_entry() - ↓ -rt_components_init() - ↓ -main() -``` - -链接脚本以 `ENTRY(Reset_Handler)` 指定复位入口。GNU GCC 配置下,`entry()` 调用 `rtthread_startup()`;该函数依次完成板级初始化、定时器系统初始化、调度器初始化、应用主线程创建和 idle 线程初始化,随后启动调度器。 - -`rt_application_init()` 创建 Main Thread。调度器启动后,`main_thread_entry()` 先执行 `rt_components_init()`,再进入应用 `main()`。因此,应用 `main()` 运行于线程调度环境中,而非 MCU 复位后的首个 C 函数。 - -## 3. 自动初始化机制 - -### 3.1 `INIT_EXPORT` - -初始化导出宏将函数指针放入指定的初始化段。GNU GCC 配置下,函数指针位于 `.rti_fn.` 段,链接脚本通过 `KEEP(*(SORT(.rti_fn*)))` 保留并排序这些段。 - -### 3.2 `INIT_BOARD_EXPORT` - -`INIT_BOARD_EXPORT()` 对应板级初始化层级。 `rt_components_board_init()` 遍历 `rti_board_start` 与 `rti_board_end` 对应的函数指针区间,用于在内核调度器启动前完成板级组件初始化。 - -### 3.3 `INIT_APP_EXPORT` - -`INIT_APP_EXPORT()` 对应应用初始化层级。 `rt_components_init()` 在 Main Thread 中遍历组件初始化区间并调用已导出的函数。初始化顺序由段名及链接脚本排序共同确定。 - -### 3.4 应用初始化 - -```c -INIT_APP_EXPORT(thread_study_init); -``` - -`thread_study_init()` 注册至应用初始化段。链接阶段对应 section 被集中保留,`rt_components_init()` 在 Main Thread 中遍历并调用该函数,因此无需在 `main()` 中显式调用线程初始化函数。 - -## 4. 内核对象与线程控制块 - -### 4.1 RT-Thread 对象模型 - -RT-Thread 采用统一内核对象模型。 `struct rt_thread` 以 `struct rt_object` 为基础,后者提供对象名称、类型、标志和对象容器链表节点等公共信息。 - -| `struct rt_object` 成员 | 作用 | -| --- | --- | -| `name[RT_NAME_MAX]` | 对象名称,用于 MSH 和调试识别 | -| `type` | 对象类别及静态/动态对象标志 | -| `flag` | 对象控制标志 | -| `list` | 对象容器链表节点 | - -对象容器用于枚举和管理内核对象;`list_thread` 通过线程对象容器显示系统线程状态。 - -### 4.2 `struct rt_thread` 关键成员 - -| 字段 | 作用 | -| --- | --- | -| `sp` | 保存线程栈指针,用于上下文切换 | -| `entry` | 线程入口函数 | -| `parameter` | 线程入口参数 | -| `stack_addr` | 线程栈起始地址 | -| `stack_size` | 线程栈大小 | -| `stat` | 当前线程状态 | -| `current_priority` | 当前优先级 | -| `init_priority` | 初始优先级 | -| `init_tick` | 初始时间片 | -| `remaining_tick` | 当前剩余时间片 | - -每个线程具有独立栈空间。上下文切换时,CPU 寄存器现场保存到当前线程栈中;再次调度该线程时,内核从其栈中恢复上下文。 - -## 5. 静态线程与动态线程 - -| 项目 | 动态线程 | 静态线程 | -| --- | --- | --- | -| API | `rt_thread_create()` | `rt_thread_init()` | -| TCB | Heap 动态分配 | 应用静态提供 | -| Stack | Heap 动态分配 | 应用静态提供 | -| 生命周期 | 运行时管理 | 应用确定 | -| 动态内存依赖 | 有 | 无 | -| 内存确定性 | 相对较低 | 较高 | -| 本实验对象 | `th_led1`、`th_monitor` | `th_led2` | - -`rt_thread_create()` 为线程控制块和线程栈分配堆内存,失败时返回 `RT_NULL`。 `rt_thread_init()` 初始化由应用提供的线程控制块和栈,返回错误码表示初始化结果。两种方式均需调用 `rt_thread_startup()` 后才进入就绪队列。 - -动态线程删除后先进入 defunct 队列,由 idle 线程延后回收线程栈和动态对象。静态线程 detach 后解除对象管理关系,不释放应用提供的静态内存。静态线程的内存布局在编译和链接阶段即可确定,不依赖运行时 Heap,可降低动态分配失败和内存碎片对长期运行系统的影响,适用于资源确定性要求较高的场景。 - -## 6. 线程状态与调度 - -### 6.1 五态模型 - -| 状态 | 含义 | 典型触发条件 | -| --- | --- | --- | -| `RT_THREAD_INIT` | 已初始化,尚未启动 | `rt_thread_create()` / `rt_thread_init()` 后 | -| `RT_THREAD_READY` | 可运行,等待调度 | `rt_thread_startup()`、超时或恢复后 | -| `RT_THREAD_RUNNING` | 当前 CPU 正在执行 | 调度器选中线程 | -| `RT_THREAD_SUSPEND` | 等待延时、事件或挂起 | `rt_thread_mdelay()`、等待对象或 `rt_thread_suspend()` | -| `RT_THREAD_CLOSE` | 线程已关闭 | `rt_thread_delete()` 或线程入口结束 | - -```text -INIT -- rt_thread_startup() --> READY -- scheduler --> RUNNING - ▲ │ - │ ├─ delay / wait / suspend ─► SUSPEND - │ │ │ - └── timeout / resume ────────────────────────────┘ -RUNNING -- delete / return --> CLOSE -``` - -`SUSPEND` 同时表示延时等待和人工挂起。因此,程序通过 `manual_suspend=yes/no` 区分 KEY0 控制的人工挂起与普通延时状态。 - -### 6.2 抢占式调度 - -RT-Thread 采用基于优先级的抢占式调度,优先级数值越小,优先级越高。 `th_monitor` 的优先级为 10,高于 `th_led1` 和 `th_led2` 的 16。当高优先级线程进入就绪态时,调度器可以抢占低优先级运行线程。 - -### 6.3 时间片轮转 - -`th_led1` 与 `th_led2` 的优先级均为 16、初始时间片均为 5 tick。当多个同优先级线程同时处于就绪态时,调度器按照剩余时间片进行轮转。周期延时会使线程暂时进入 `SUSPEND`,从而让其他就绪线程获得 CPU。 - -## 7. 多线程实验设计 - -| 名称 | 创建方式 | 优先级 | 时间片 | 栈 | 功能 | -| --- | --- | ---: | ---: | ---: | --- | -| `th_led1` | 动态 | 16 | 5 tick | 1024 B | PF12 翻转、计数和 tick 输出 | -| `th_led2` | 静态 | 16 | 5 tick | 1024 B | PF11 翻转、计数和 tick 输出 | -| `th_monitor` | 动态 | 10 | 5 tick | 1024 B | KEY0 检测、线程状态和剩余栈监测 | - -```text -KEY0 (PC0, active-low) - │ - ▼ -th_monitor (priority 10) ── request / resume ──► th_led1 (priority 16) ──► LED_R (PF12) - │ - └── periodic status and stack report - -th_led2 (priority 16) ──────────────────────────────────────────────────────► LED_B (PF11) -``` - -### 7.1 `th_led1` - -`th_led1` 使用 `rt_thread_create()` 动态创建。线程每隔约 500 ms 翻转 PF12,并输出 `rt_tick_get()` 与计数值。 - -### 7.2 `th_led2` - -`th_led2` 使用全局 `struct rt_thread` 和 1024 B 对齐静态栈,通过 `rt_thread_init()` 初始化。线程每隔约 700 ms 翻转 PF11,并输出计数值。 - -### 7.3 `th_monitor` - -`th_monitor` 使用 `rt_thread_create()` 动态创建,优先级为 10。监控线程采用 20 ms 基础采样周期检测 KEY0,并累计至约 2000 ms 输出一次线程状态和栈余量。该方式在保持约 2 s 状态报告周期的同时,避免单次 2000 ms 阻塞造成较大的按键响应延迟。 - -### 7.4 KEY0 挂起与恢复 - -KEY0 使用内部上拉输入,按下为低电平。连续 3 次 20 ms 采样结果一致后确认电平变化,用于抑制按键抖动。 - -RT-Thread 4.1.1 中,`rt_thread_suspend()` 对调用上下文存在约束。 `th_monitor` 仅设置挂起请求;`th_led1` 在自身线程上下文的安全点执行 `rt_thread_suspend(rt_thread_self())`。再次检测到有效按键后,`th_monitor` 调用 `rt_thread_resume(th_led1)` 恢复线程运行。 - -### 7.5 栈空间监控 - -RT-Thread 在线程栈初始化时填充 `'#'` 字节。Cortex-M 栈向低地址方向增长,程序从栈起始地址扫描连续 `'#'` 字节以估计剩余栈空间。状态读取使用 `thread->stat & RT_THREAD_STAT_MASK`,并转换为可读状态名。 - -### 7.6 `thread_dump` - -```text -msh />thread_dump -msh />list_thread -``` - -`thread_dump` 由 `MSH_CMD_EXPORT` 导出,输出三个实验线程的状态、优先级和剩余栈空间;`list_thread` 显示系统全部线程。 - -## 8. 构建与资源占用 - -### 8.1 RT-Thread Studio - -RT-Thread Studio 的 Debug Clean Build 结果如下。 - -| 检查项 | 结果 | -| --- | --- | -| Build | PASS,0 errors,0 warnings | -| Flash | 61.09 KB | -| RAM | 6.20 KB | - -### 8.2 SCons - -执行命令: - -```text -scons -Q -j1 -``` - -结果: - -```text -return code = 0 -text = 60676 B -data = 1880 B -bss = 4464 B -``` - -SCons 构建成功生成 `rt-thread.elf`。 - -## 9. 实机验证结果 - -### 9.1 ST-Link 下载 - -ST-Link 通过 SWD 连接 STM32F407,下载 `rtthread.bin` 至 `0x08000000` 后完成校验并复位。目标芯片 Device ID 为 `0x413`。 - -### 9.2 串口运行 - -COM7 采用 115200 8-N-1 配置。串口日志显示 `th_led1` 按约 500 ms 周期输出,`th_led2` 按约 700 ms 周期输出,监控线程按约 2 s 周期报告状态与栈空间。 - -### 9.3 `thread_dump` 与 `list_thread` - -实机日志中的 `thread_dump` 输出: - -```text -Day2 thread study status: -[th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[th_led1] manual_suspend=no -[th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[th_monitor] state=suspend, priority=10, stack_free=724/1024 bytes -``` - -`list_thread` 显示 `th_monitor` 优先级为 10,`th_led1` 和 `th_led2` 优先级均为 16,与线程配置一致。 - -### 9.4 KEY0 Suspend / Resume - -实机日志中的按键控制过程: - -```text -[monitor] KEY0 pressed: request th_led1 suspend -[th_led1] manual suspend entered -[monitor] KEY0 pressed: resume th_led1, result=0 -[th_led1] resumed by monitor -[th_led1] tick=160425, count=317, LED_R toggled -``` - -日志表明,第一次 KEY0 有效按下后,`th_led1` 进入人工挂起状态;第二次有效按下后,`rt_thread_resume()` 返回 `RT_EOK`,`th_led1` 恢复周期输出。 `th_led2` 与 `th_monitor` 在此过程中保持运行。 - -完整串口输出见同目录 `day2_serial.log`。 - -| 检查项 | 结果 | -| --- | --- | -| Studio Clean Build | PASS,0 errors,0 warnings | -| SCons Build | PASS | -| ST-Link Program / Verify | PASS | -| COM7 Console | PASS | -| `th_led1` / `th_led2` / `th_monitor` | PASS | -| `thread_dump` / `list_thread` | PASS | -| KEY0 Suspend / Resume | PASS | - -## 10. 结论 - -实验完成了 RT-Thread 动态线程、静态线程和高优先级监控线程的设计与运行验证。三个线程能够按照设定优先级和周期稳定运行,KEY0 可控制 `th_led1` 挂起与恢复,`thread_dump` 能够输出线程状态及剩余栈空间。RT-Thread Studio 与 SCons 构建均通过,程序经 ST-Link 下载后在 STM32F407 星火 1 号平台运行正常。 diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\344\275\234\344\270\232/day2_serial.log" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\344\275\234\344\270\232/day2_serial.log" deleted file mode 100644 index bbdfbfad54c13d015f12f30edbb41e2c2fe1d895..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\344\275\234\344\270\232/day2_serial.log" +++ /dev/null @@ -1,166 +0,0 @@ -=== serial COM7 115200 after verified flash === -[D/main] Hello RT-Thread! -[th_led1] tick=136432, count=270, LED_R toggled -[th_led2] tick=136625, count=195, LED_B toggled -[th_led1] tick[th_led2] tick=140849, count=201, LED_B toggled -[th_led1] tick=140968, count=279, LED_R toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[D/main] Hello RT-Thread! -[th_led1] tick=141472, count=280, LED_R toggled -[th_led2] tick=141553, count=202, LED_B toggled -[th_led1] tick=141976, count=281, LED_R toggled -[th_led2] tick=142257, count=203, LED_B toggled -[D/main] Hello RT-Thread! -[th_led1] tick=142480, count=282, LED_R toggled -[th_led2] tick=142961, count=204, LED_B toggled -[th_led1] tick=142984, count=283, LED_R toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[D/main] Hello RT-Thread! -[th_led1] tick=143488, count=284, LED_R toggled -[th_led2] tick=143665, count=205, LED_B toggled -thread_dump -Day2 thread study status: -[th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[th_led1] manual_suspend=no -[th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[th_monitor] state=suspend, priority=10, stack_free=724/1024 bytes -msh > -msh >[th_led1] tick=143992, count=285, LED_R toggled -[th_led2] tick=144369, count=206, LED_B toggled -[D/main] Hello RT-Thread! -[th_led1] tick=144496, count=286, LED_R toggled -[th_led1] tick=145000, count=287, LED_R toggled -[th_led2] tick=145073, count=207, LED_B toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[D/main] Hello RT-Thread! -[th_led1] tick=145504, count=288, LED_R toggled -[th_led2] tick=145777, count=208, LED_B toggled -[th_led1] tick=146008, count=289, LED_R toggled -[D/main] Hello RT-Thread! -[th_led2] tick=146481, count=209, LED_B toggled -[th_led1] tick=146512, count=290, LED_R toggled -[th_led1] tick=147016, count=291, LED_R toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=ready, priority=16, stack_free=776/1024 bytes -[th_led2] tick=147193, count=210, LED_B toggled -[D/main] Hello RT-Thread! -[th_led1] tick=147520, count=292, LED_R toggled -[th_led2] tick=147897, count=211, LED_B toggled -[th_led1] tick=148024, count=293, LED_R toggled -[D/main] Hello RT-Thread! -[th_led1] tick=148528, count=294, LED_R toggled -[th_led2] tick=148601, count=212, LED_B toggled -[th_led1] tick=149032, count=295, LED_R toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[th_led2] tick=149305, count=213, LED_B toggled -[D/main] Hello RT-Thread! -[th_led1] tick=149536, count=296, LED_R toggled -[th_led2] tick=150009, count=214, LED_B toggled -[th_led1] tick=150040, count=297, LED_R toggled -[D/main] Hello RT-Thread! -[th_led1] tick=150544, count=298, LED_R toggled -[th_led2] tick=150713, count=215, LED_B toggled -[th_led1] tick=151048, count=299, LED_R toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[th_led2] tick=151417, count=216, LED_B toggled -[D/main] Hello RT-Thread! -[th_led1] tick=151552, count=300, LED_R toggled -[th_led1] tick=152056, count=301, LED_R toggled -[th_led2] tick=152121, count=217, LED_B toggled -[D/main] Hello RT-Thread! -[th_led1] tick=152560, count=302, LED_R toggled -[th_led2] tick=152825, count=218, LED_B toggled -[th_led1] tick=153064, count=303, LED_R toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[D/main] Hello RT-Thread! -[th_led2] tick=153529, count=219, LED_B toggled -[th_led1] tick=153568, count=304, LED_R toggled -[th_led1] tick=154072, count=305, LED_R toggled -[th_led2] tick=154233, count=220, LED_B toggled -[D/main] Hello RT-Thread! -[th_led1] tick=154576, count=306, LED_R toggled -[th_led2] tick=154937, count=221, LED_B toggled -[th_led1] tick=155080, count=307, LED_R toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[D/main] Hello RT-Thread! -[th_led1] tick=155584, count=308, LED_R toggled -[th_led2] tick=155641, count=222, LED_B toggled -list_thread -thread pri status sp stack size max used left tick error --------- --- ------- ---------- ---------- ------ ---------- --- -th_monit 10 suspend 0x000000b8 0x00000400 29% 0x00000003 OK -th_led2 16 suspend 0x000000b0 0x00000400 24% 0x00000004 OK -th_led1 16 suspend 0x000000b0 0x00000400 24% 0x00000003 OK -tshell 20 running 0x00000270 0x00001000 15% 0x00000007 OK -tidle0 31 ready 0x00000078 0x00000100 56% 0x00000019 OK -timer 4 suspend 0x00000080 0x00000200 25% 0x00000009 OK -main 10 suspend 0x000000b4 0x00000800 14% 0x00000005 OK -msh > -msh >[th_led1] tick=156088, count=309, LED_R toggled -[th_led2] tick=156345, count=223, LED_B toggled -[D/main] Hello RT-Thread! -[th_led1] tick=156592, count=310, LED_R toggled -[th_led2] tick=157049, count=224, LED_B toggled -[th_led1] tick=157096, count=311, LED_R toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[D/main] Hello RT-Thread! -[th_led1] tick=157600, count=312, LED_R toggled -[th_led2] tick=157753, count=225, LED_B toggled -[th_led1] tick=158104, count=313, LED_R toggled -[th_led2] tick=158457, count=226, LED_B toggled -[D/main] Hello RT-Thread! -[th_led1] tick=158608, count=314, LED_R toggled -[th_led1] tick=159112, count=315, LED_R toggled -[th_led2] tick=159161, count=227, LED_B toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[D/main] Hello RT-Thread! -[th_led1] tick=159616, count=316, LED_R toggled -[monitor] KEY0 pressed: request th_led1 suspend -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor] th_led1 manual_suspend=no -[th_led2] tick=159865, count=228, LED_B toggled -[th_led1] manual suspend entered -[monitor] KEY0 pressed: resume th_led1, result=0 -[monitor/th_led1] state=ready, priority=16, stack_free=776/1024 bytes -[monitor] th_led1 manual_suspend=yes -[th_led1] resumed by monitor -[th_led1] tick=160425, count=317, LED_R toggled -[D/main] Hello RT-Thread! -[th_led2] tick=160569, count=229, LED_B toggled -[th_led1] tick=160929, count=318, LED_R toggled -[th_led2] tick=161273, count=230, LED_B toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[th_led1] tick=161433, count=319, LED_R toggled -[D/main] Hello RT-Thread! -[th_led1] tick=161937, count=320, LED_R toggled -[th_led2] tick=161977, count=231, LED_B toggled -[th_led1] tick=162441, count=321, LED_R toggled -[D/main] Hello RT-Thread! -[th_led2] tick=162681, count=232, LED_B toggled -[th_led1] tick=162945, count=322, LED_R toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[th_led2] tick=163385, count=233, LED_B toggled -[th_led1] tick=163449, count=323, LED_R toggled -[D/main] Hello RT-Thread! -[th_led1] tick=163953, count=324, LED_R toggled -[th_led2] tick=164089, count=234, LED_B toggled -[th_led1] tick=164457, count=325, LED_R toggled -[D/main] Hello RT-Thread! -[th_led2] tick=164793, count=235, LED_B toggled -[th_led1] tick=164961, count=326, LED_R toggled -[monitor/th_led1] state=suspend, priority=16, stack_free=776/1024 bytes -[monitor/th_led2] state=suspend, priority=16, stack_free=776/1024 bytes -[th_led1] tick=165465, count=327, LED_R toggled -[th_led2] tick=165497, count=236, LED_B toggled -[D/main] Hello RT-Thread! diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index fa2c8dad482c09b020c617de4e7a358fbe3aae6c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Day2: RT-Thread thread lifecycle practice on STM32F407 RT-Spark. - * - * Pin mapping is verified against the installed RT-Spark BSP examples: - * LED_B: PF11, LED_R: PF12, KEY0: PC0. - * LEDs and KEY0 are active-low on that board. - */ - -#include -#include -#include - -#define PIN_LED_B GET_PIN(F, 11) -#define PIN_LED_R GET_PIN(F, 12) -#define PIN_KEY0 GET_PIN(C, 0) - -#define LED_ON PIN_LOW -#define LED_OFF PIN_HIGH - -#define LED_STACK_SIZE 1024 -#define MONITOR_PERIOD 20 -#define REPORT_PERIOD 2000 - -static rt_thread_t th_led1 = RT_NULL; -static rt_thread_t th_monitor = RT_NULL; -static volatile rt_bool_t led1_suspend_requested = RT_FALSE; -static volatile rt_bool_t led1_manually_suspended = RT_FALSE; - -static struct rt_thread th_led2; -static rt_uint8_t th_led2_stack[LED_STACK_SIZE] ALIGN(RT_ALIGN_SIZE); - -static void led_toggle(rt_base_t pin) -{ - rt_pin_write(pin, rt_pin_read(pin) == LED_ON ? LED_OFF : LED_ON); -} - -static rt_uint8_t thread_state(rt_thread_t thread) -{ - rt_base_t level; - rt_uint8_t state; - - level = rt_hw_interrupt_disable(); - state = thread->stat & RT_THREAD_STAT_MASK; - rt_hw_interrupt_enable(level); - - return state; -} - -static const char *thread_state_name(rt_uint8_t state) -{ - switch (state) - { - case RT_THREAD_INIT: - return "init"; - case RT_THREAD_READY: - return "ready"; - case RT_THREAD_SUSPEND: - return "suspend"; - case RT_THREAD_RUNNING: - return "running"; - case RT_THREAD_CLOSE: - return "close"; - default: - return "unknown"; - } -} - -/* RT-Thread initializes each stack byte to '#'. Cortex-M stacks grow down. */ -static rt_size_t thread_stack_free(rt_thread_t thread) -{ - const rt_uint8_t *stack = (const rt_uint8_t *)thread->stack_addr; - rt_size_t free_size = 0; - - while ((free_size < thread->stack_size) && (stack[free_size] == '#')) - { - free_size++; - } - - return free_size; -} - -static void print_thread_status(const char *tag, rt_thread_t thread) -{ - rt_uint8_t state = thread_state(thread); - - rt_kprintf("[%s] state=%s, priority=%d, stack_free=%d/%d bytes\n", - tag, - thread_state_name(state), - thread->current_priority, - thread_stack_free(thread), - thread->stack_size); -} - -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - RT_UNUSED(parameter); - while (RT_TRUE) - { - /* - * RT-Thread 4.1.1 requires rt_thread_suspend() to be called by the - * target thread itself. The monitor therefore posts this request and - * th_led1 performs the suspend at this safe point in its own context. - */ - if (led1_suspend_requested) - { - led1_manually_suspended = RT_TRUE; - rt_kprintf("[th_led1] manual suspend entered\n"); - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - led1_manually_suspended = RT_FALSE; - rt_kprintf("[th_led1] resumed by monitor\n"); - } - - led_toggle(PIN_LED_R); - count++; - rt_kprintf("[th_led1] tick=%u, count=%u, LED_R toggled\n", - (unsigned int)rt_tick_get(), (unsigned int)count); - rt_thread_mdelay(500); - } -} - -static void th_led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - - RT_UNUSED(parameter); - while (RT_TRUE) - { - led_toggle(PIN_LED_B); - count++; - rt_kprintf("[th_led2] tick=%u, count=%u, LED_B toggled\n", - (unsigned int)rt_tick_get(), (unsigned int)count); - rt_thread_mdelay(700); - } -} - -static void toggle_led1_suspend_state(void) -{ - rt_err_t result; - - if (led1_suspend_requested) - { - led1_suspend_requested = RT_FALSE; - if (led1_manually_suspended) - { - result = rt_thread_resume(th_led1); - rt_kprintf("[monitor] KEY0 pressed: resume th_led1, result=%d\n", result); - } - else - { - rt_kprintf("[monitor] KEY0 pressed: cancel pending suspend request\n"); - } - } - else - { - led1_suspend_requested = RT_TRUE; - rt_kprintf("[monitor] KEY0 pressed: request th_led1 suspend\n"); - } - - print_thread_status("monitor/th_led1", th_led1); - rt_kprintf("[monitor] th_led1 manual_suspend=%s\n", - led1_manually_suspended ? "yes" : "no"); -} - -static void th_monitor_entry(void *parameter) -{ - rt_uint8_t last_sample = PIN_HIGH; - rt_uint8_t stable_level = PIN_HIGH; - rt_uint8_t same_count = 0; - rt_uint32_t elapsed = 0; - - RT_UNUSED(parameter); - print_thread_status("monitor/th_led1", th_led1); - print_thread_status("monitor/th_led2", &th_led2); - - while (RT_TRUE) - { - rt_uint8_t sample = rt_pin_read(PIN_KEY0); - - if (sample == last_sample) - { - if (same_count < 3) - { - same_count++; - } - } - else - { - last_sample = sample; - same_count = 0; - } - - /* 3 consecutive 20 ms samples are used as a simple debounce filter. */ - if ((same_count == 3) && (sample != stable_level)) - { - stable_level = sample; - if (stable_level == PIN_LOW) - { - toggle_led1_suspend_state(); - } - } - - elapsed += MONITOR_PERIOD; - if (elapsed >= REPORT_PERIOD) - { - elapsed = 0; - print_thread_status("monitor/th_led1", th_led1); - print_thread_status("monitor/th_led2", &th_led2); - } - - rt_thread_mdelay(MONITOR_PERIOD); - } -} - -static void thread_dump(void) -{ - rt_kprintf("Day2 thread study status:\n"); - if (th_led1 != RT_NULL) - { - print_thread_status("th_led1", th_led1); - rt_kprintf("[th_led1] manual_suspend=%s\n", - led1_manually_suspended ? "yes" : "no"); - } - print_thread_status("th_led2", &th_led2); - if (th_monitor != RT_NULL) - { - print_thread_status("th_monitor", th_monitor); - } -} -MSH_CMD_EXPORT(thread_dump, show Day2 thread states and stack usage); - -static int thread_study_init(void) -{ - rt_err_t result; - - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP); - rt_pin_write(PIN_LED_R, LED_OFF); - rt_pin_write(PIN_LED_B, LED_OFF); - - th_led1 = rt_thread_create("th_led1", th_led1_entry, RT_NULL, - LED_STACK_SIZE, 16, 5); - if (th_led1 == RT_NULL) - { - rt_kprintf("[thread_study] create th_led1 failed\n"); - return -RT_ERROR; - } - - result = rt_thread_init(&th_led2, "th_led2", th_led2_entry, RT_NULL, - th_led2_stack, sizeof(th_led2_stack), 16, 5); - if (result != RT_EOK) - { - rt_kprintf("[thread_study] init th_led2 failed: %d\n", result); - return result; - } - - th_monitor = rt_thread_create("th_monitor", th_monitor_entry, RT_NULL, - LED_STACK_SIZE, 10, 5); - if (th_monitor == RT_NULL) - { - rt_kprintf("[thread_study] create th_monitor failed\n"); - return -RT_ERROR; - } - - rt_thread_startup(th_led1); - rt_thread_startup(&th_led2); - rt_thread_startup(th_monitor); - - rt_kprintf("[thread_study] initialized: KEY0 toggles th_led1 suspend/resume\n"); - return RT_EOK; -} -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index b114952ec5339d0ac8dc1d7476b9c6a5f0ea5b9b..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\344\272\214\345\244\251/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,634 +0,0 @@ -# RT-Thread 夏令营 Day2 学习笔记 - -> 日期:2026-08-18 -> 主题:RT-Thread 内核基础、线程管理与 SCons 构建系统 - ---- - -## 一、Day2 课程内容概览 - -Day2 开始进入 RT-Thread 内核与线程管理部分,主要内容包括: - -1. 裸机前后台系统与 RTOS 多任务模型; -2. RT-Thread 系统启动流程; -3. RT-Thread 自动初始化机制; -4. 线程控制块 `struct rt_thread`; -5. 线程栈与上下文切换; -6. 线程状态与生命周期; -7. 优先级抢占与时间片轮转; -8. Main、Idle 等系统线程; -9. 静态线程与动态线程; -10. 线程创建、启动、延时、挂起和恢复; -11. SCons / SConscript 构建机制; -12. 基于芯片的 RT-Thread 工程构建流程。 - -这些内容共同构成 RT-Thread 多线程系统的基础。 - -```text -MCU 启动 - ↓ -RT-Thread Kernel 初始化 - ↓ -建立线程管理环境 - ↓ -创建线程 - ↓ -线程进入就绪队列 - ↓ -Scheduler 调度 - ↓ -线程运行 / 挂起 / 恢复 / 退出 -``` - ---- - -## 二、裸机系统与 RTOS 多任务模型 - -### 2.1 裸机前后台系统 - -常见裸机程序采用“前后台”结构: - -- **前台**:中断服务程序; -- **后台**:`while(1)` 主循环。 - -```c -int main(void) -{ - hardware_init(); - - while (1) - { - sensor_process(); - spi_process(); - uart_process(); - lcd_refresh(); - } -} -``` - -后台任务按照程序顺序依次运行。如果其中一个任务发生长时间阻塞,其后的任务也无法及时得到执行。 - -```text -ADC - ↓ -SPI ← 阻塞 1 s - ↓ -LCD - ↓ -Network -``` - -### 2.2 RTOS 的基本思想 - -RTOS 将复杂应用拆分为多个独立任务,每个任务由一个线程承载: - -```text -Sensor → Thread 1 -UART → Thread 2 -Network → Thread 3 -Display → Thread 4 -Storage → Thread 5 -``` - -线程由操作系统统一管理,调度器根据线程状态、优先级等信息决定 CPU 当前执行哪个线程。对于单核 MCU,同一时刻仍然只有一个线程真正占用 CPU,多任务效果来自线程之间的快速调度和上下文切换。 - ---- - -## 三、RT-Thread 系统启动流程 - -STM32 上电后首先进入芯片启动代码,然后完成 C 运行环境初始化,再进入 RT-Thread 的系统启动过程。不同工具链的运行时入口可能不同,但 RT-Thread 会在进入用户 `main()` 之前完成内核初始化。 - -```text -Reset - ↓ -Startup Code - ↓ -C Runtime - ↓ -rtthread_startup() - ↓ -rt_hw_board_init() - ↓ -Kernel Timer Init - ↓ -Scheduler Init - ↓ -Application Init - ↓ -创建 Main Thread - ↓ -初始化系统线程 - ↓ -启动 Scheduler - ↓ -main_thread_entry() - ↓ -Components Init - ↓ -main() -``` - -### 3.1 `main()` 已经位于线程环境中 - -RT-Thread 会先创建 Main Thread,再由该线程进入 `main_thread_entry()`,最终执行用户 `main()`。 - -```text -RT-Thread Kernel - ↓ -Main Thread - ↓ -main_thread_entry() - ↓ -main() -``` - -因此 `main()` 并不是 MCU 复位后的第一个 C 函数,而是 RT-Thread 内核、调度器和线程管理环境建立后的应用入口。 - ---- - -## 四、RT-Thread 自动初始化机制 - -RT-Thread 提供组件自动初始化机制,使各模块可以将初始化函数注册到系统初始化流程中,而不必全部在 `main()` 中手动调用。 - -```text -Module Init Function - ↓ -注册到自动初始化体系 - ↓ -RT-Thread Startup - ↓ -rt_components_init() - ↓ -按初始化顺序执行 - ↓ -进入 main() -``` - -这种机制可以使驱动、组件和应用各自维护初始化函数,降低 `main.c` 与各模块之间的耦合。 - ---- - -## 五、线程控制块 `struct rt_thread` - -RT-Thread 使用线程控制块 TCB(Thread Control Block)保存每个线程的运行信息。对应核心数据结构为: - -```c -struct rt_thread -``` - -线程控制块包含线程名称、状态、优先级、入口函数、参数、线程栈和时间片等信息。 - -```text -struct rt_thread -│ -├── object information -├── stack pointer -├── entry -├── parameter -├── stack address -├── stack size -├── thread state -├── current priority -├── initial priority -├── time slice -├── timer -└── kernel management information -``` - -### 5.1 线程对象模型 - -RT-Thread 内核使用统一对象模型管理线程等内核资源: - -```text -rt_object - ↑ -rt_thread -``` - -基础对象提供名称、类型、标志、链表等公共属性,线程对象在此基础上增加线程相关字段。 - ---- - -## 六、线程栈与上下文切换 - -每个线程拥有独立线程栈。线程栈既保存函数局部变量和调用现场,也用于保存线程切换时的 CPU 上下文。 - -```text -Thread A Running - ↓ -保存 Thread A Context - ↓ -Thread A Stack - -Thread B Stack - ↓ -恢复 Thread B Context - ↓ -Thread B Running -``` - -线程再次被调度时,系统从该线程自己的栈中恢复上下文,因此线程可以从上次被切换的位置继续执行。 - -### 6.1 线程栈大小 - -线程栈过小可能导致 Stack Overflow,进一步造成内存破坏;线程栈过大则会浪费 MCU RAM。大型局部变量、深层函数调用和递归都会增加线程栈需求。 - ---- - -## 七、线程状态与生命周期 - -RT-Thread 线程主要包含以下状态: - -| 状态 | 含义 | -|---|---| -| `RT_THREAD_INIT` | 初始态 | -| `RT_THREAD_READY` | 就绪态 | -| `RT_THREAD_RUNNING` | 运行态 | -| `RT_THREAD_SUSPEND` | 挂起态 | -| `RT_THREAD_CLOSE` | 关闭态 | - -基本状态转换: - -```text - startup -INIT ----------------------> READY - | - | scheduler - v - RUNNING - / \ - / \ - delay/wait preempt - / \ - v v - SUSPEND READY - | - resume/timeout - | - v - READY -``` - -线程创建后首先处于 `INIT`,调用 `rt_thread_startup()` 后进入 `READY` 并参与调度。 - -线程可以因为延时、等待 IPC 对象或主动挂起从 `RUNNING` 进入 `SUSPEND`;条件满足后重新进入 `READY`。 - ---- - -## 八、线程调度:优先级抢占与时间片轮转 - -RT-Thread 中优先级数字越小,线程优先级越高。 - -```text -Thread A : priority = 10 -Thread B : priority = 20 -``` - -A 的优先级高于 B。当 B 正在运行而 A 进入就绪态时,A 可以抢占 B。 - -```text -Thread B RUNNING - ↓ -Thread A READY - ↓ -A priority higher - ↓ -Thread B 被抢占 - ↓ -Thread A RUNNING -``` - -### 8.1 时间片轮转 - -时间片用于相同优先级就绪线程之间的轮转。 - -```text -Thread A -priority = 16 -time slice = 5 - -Thread B -priority = 16 -time slice = 5 -``` - -当两者同时就绪时,可以按照时间片轮转: - -```text -A → B → A → B → ... -``` - -因此: - -```text -Priority → 不同优先级之间谁先执行 -Time Slice → 同优先级线程之间如何轮转 -``` - ---- - -## 九、RT-Thread 系统线程 - -RT-Thread 除用户线程外,还存在系统线程,主要包括 Main Thread、Timer Thread(配置相关)和 Idle Thread。 - -### 9.1 Main Thread - -用户 `main()` 最终在 Main Thread 上下文中执行。 - -### 9.2 Idle Thread - -Idle Thread 是系统最低优先级线程。当没有其他可运行线程时,CPU 执行 Idle Thread。 - -```text -No Ready User Thread - ↓ -Idle Thread -``` - -Idle Thread 还可承担退出线程的资源回收,以及低功耗等系统空闲任务。 - ---- - -## 十、静态线程与动态线程 - -### 10.1 静态线程 - -静态线程使用: - -```c -rt_thread_init() -``` - -用户提前准备线程控制块和栈: - -```c -static struct rt_thread thread1; -static rt_uint8_t thread1_stack[1024]; -``` - -然后调用: - -```c -rt_thread_init(&thread1, - "thread1", - thread1_entry, - RT_NULL, - thread1_stack, - sizeof(thread1_stack), - 20, - 10); -``` - -### 10.2 动态线程 - -动态线程使用: - -```c -rt_thread_create() -``` - -例如: - -```c -rt_thread_t tid; - -tid = rt_thread_create("thread1", - thread1_entry, - RT_NULL, - 1024, - 20, - 10); -``` - -动态创建时,线程控制块和线程栈由系统在运行时从动态内存中申请。 - -### 10.3 两种方式对比 - -| 项目 | 静态线程 | 动态线程 | -|---|---|---| -| API | `rt_thread_init()` | `rt_thread_create()` | -| TCB | 用户提供 | 运行时分配 | -| Stack | 用户提供 | 运行时分配 | -| Heap 依赖 | 否 | 是 | -| 内存确定时间 | 编译 / 链接阶段 | 运行阶段 | -| 内存确定性 | 较高 | 相对较低 | -| 使用复杂度 | 较高 | 较低 | - ---- - -## 十一、线程创建、启动与延时 - -`rt_thread_create()` 或 `rt_thread_init()` 只负责建立线程,此时线程处于 `INIT`。必须进一步调用: - -```c -rt_thread_startup(thread); -``` - -才能进入 `READY` 并参与调度。 - -```text -Thread Entry - ↓ -create / init - ↓ -RT_THREAD_INIT - ↓ -rt_thread_startup() - ↓ -RT_THREAD_READY - ↓ -Scheduler - ↓ -RT_THREAD_RUNNING -``` - -典型线程入口: - -```c -static void thread_entry(void *parameter) -{ - while (1) - { - rt_kprintf("thread running\n"); - rt_thread_mdelay(500); - } -} -``` - -调用延时后: - -```text -RUNNING - ↓ -rt_thread_mdelay() - ↓ -SUSPEND -``` - -当前线程暂时退出 CPU 竞争,延时结束后重新进入 `READY`。 - ---- - -## 十二、SCons 与 RT-Thread 构建系统 - -RT-Thread 工程使用 SCons 进行构建管理。常见构建文件包括: - -```text -SConstruct -SConscript -``` - -其中 `SConstruct` 是工程顶层构建入口,`SConscript` 用于描述具体目录中的源文件、头文件路径、依赖和编译参数。 - -### 12.1 Source Files - -`src` 用于指定参与编译的源文件: - -```python -src = ['file1.c', 'file2.c'] -``` - -### 12.2 `CPPPATH` - -`CPPPATH` 用于配置头文件搜索路径。 - -```text -source.c - ↓ -#include "board.h" - ↓ -Compiler - ↓ -根据 CPPPATH 查找 board.h -``` - -### 12.3 `DefineGroup` - -`DefineGroup()` 用于把一组源文件、依赖和编译配置组织为一个构建组。 - -```text -Group -├── Name -├── Source Files -├── Dependency -├── Include Paths -└── Compile Options -``` - -### 12.4 条件依赖 - -RT-Thread 可以根据配置宏决定某个模块是否参与构建。 - -```text -Kconfig / Configuration - ↓ -配置宏 - ↓ -Depend / GetDepend - ↓ -条件满足? - / \ - Yes No - ↓ ↓ -加入 Build 不参与 Build -``` - -因此文件存在于工程目录,并不代表一定参与最终编译。 - -### 12.5 子目录构建 - -不同目录可以拥有自己的 `SConscript`: - -```text -Project -│ -├── applications/ -│ └── SConscript -├── drivers/ -│ └── SConscript -├── components/ -│ └── SConscript -└── ... -``` - -父级构建脚本继续加载子目录构建脚本,从而形成模块化、分层的工程构建结构。 - ---- - -## 十三、Day2 核心知识关系 - -```text -复杂应用 - ↓ -拆分为多个任务 - ↓ -每个任务建立 Thread - ↓ -struct rt_thread 保存线程信息 - ↓ -每个线程拥有独立 Stack - ↓ -rt_thread_init() -或 -rt_thread_create() - ↓ -INIT - ↓ -rt_thread_startup() - ↓ -READY - ↓ -Scheduler - ↓ -根据 Priority 调度 - ↓ -RUNNING - ↓ -delay / IPC wait / suspend - ↓ -SUSPEND - ↓ -timeout / resume / resource available - ↓ -READY -``` - -调度规则: - -```text -不同优先级 - ↓ -Priority Preemption - -相同优先级 - ↓ -Time Slice Round-Robin -``` - -构建流程: - -```text -Source Code - ↓ -SConscript - ↓ -Dependency - ↓ -DefineGroup - ↓ -SCons Build - ↓ -Compiler / Linker - ↓ -Firmware -``` - -Day2 内容可分为三个层次: - -| 层次 | 主要内容 | -|---|---| -| 系统启动 | MCU 启动、RT-Thread 初始化、Main Thread | -| 内核运行 | Thread、TCB、Stack、State、Scheduler | -| 工程构建 | SCons、SConscript、依赖、源文件组织 | - -这三个层次共同构成后续学习 RT-Thread IPC、设备驱动及组件机制的基础。 diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\344\275\234\344\270\232/README.md" deleted file mode 100644 index 746430ae761564f4db6df4073e0541a7c0d96292..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,13 +0,0 @@ -# RT-Thread 虚拟字符设备 test_dev - -本实验实现了基于 RT-Thread I/O 设备框架的虚拟字符设备 `test_dev`。 - -## 完成内容 - -- 创建 `RT_Device_Class_Char` 字符设备并调用 `rt_device_register()` 注册。 -- 用 `struct rt_device_ops` 实现 `init`、`open`、`close`、`read`、`write`、`control`。 -- 使用 `INIT_DEVICE_EXPORT()` 自动注册,未修改 `main.c`。 -- 提供 MSH 命令 `test_dev_test`,按 find、init、open、read、write、control、close 验证完整调用链。 - -`read` 返回 `RT-Thread test device`;`write` 输出接收到的文本;`TEST_DEV_GET_STATUS` 返回初始化状态和累计打开次数。 - diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\344\275\234\344\270\232/test_dev.c" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\344\275\234\344\270\232/test_dev.c" deleted file mode 100644 index 13c652c1a4d37b7c7844e5dbccc38d5bb1dc6de7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\344\275\234\344\270\232/test_dev.c" +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include -#include -#include "test_dev.h" - -static const char test_dev_data[] = "RT-Thread test device"; -struct test_dev_context { rt_uint32_t open_count; rt_bool_t initialized; }; -static struct test_dev_context test_dev_ctx; - -static rt_err_t test_dev_init(rt_device_t dev) -{ struct test_dev_context *ctx = dev->user_data; if (!ctx) return -RT_ERROR; ctx->initialized = RT_TRUE; rt_kprintf("test_dev init\n"); return RT_EOK; } -static rt_err_t test_dev_open(rt_device_t dev, rt_uint16_t oflag) -{ struct test_dev_context *ctx = dev->user_data; if (!ctx || !ctx->initialized) return -RT_ERROR; ctx->open_count++; rt_kprintf("test_dev open, count: %u, flags: 0x%x\n", ctx->open_count, oflag); return RT_EOK; } -static rt_err_t test_dev_close(rt_device_t dev) -{ RT_UNUSED(dev); rt_kprintf("test_dev close\n"); return RT_EOK; } -static rt_size_t test_dev_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) -{ rt_size_t available, copied; RT_UNUSED(dev); if (!buffer || pos < 0) return 0; available = sizeof(test_dev_data) - 1; if ((rt_size_t)pos >= available) return 0; copied = size < (available - (rt_size_t)pos) ? size : (available - (rt_size_t)pos); rt_memcpy(buffer, test_dev_data + pos, copied); rt_kprintf("test_dev read, pos: %d, size: %d\n", pos, copied); return copied; } -static rt_size_t test_dev_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) -{ char text[65]; rt_size_t copied; RT_UNUSED(dev); RT_UNUSED(pos); if (!buffer) return 0; copied = size < (sizeof(text) - 1) ? size : (sizeof(text) - 1); rt_memcpy(text, buffer, copied); text[copied] = '\0'; rt_kprintf("test_dev write: %s\n", text); return size; } -static rt_err_t test_dev_control(rt_device_t dev, int cmd, void *args) -{ struct test_dev_context *ctx = dev->user_data; struct test_dev_status *status = args; if (!ctx) return -RT_ERROR; if (cmd != TEST_DEV_GET_STATUS) return -RT_ENOSYS; if (!status) return -RT_EINVAL; status->open_count = ctx->open_count; status->initialized = ctx->initialized; rt_kprintf("test_dev control: GET_STATUS\n"); return RT_EOK; } - -static const struct rt_device_ops test_dev_ops = { test_dev_init, test_dev_open, test_dev_close, test_dev_read, test_dev_write, test_dev_control }; - -static int test_dev_register(void) -{ rt_device_t dev = rt_device_create(RT_Device_Class_Char, 0); if (!dev) { rt_kprintf("test_dev create failed\n"); return -RT_ENOMEM; } dev->ops = &test_dev_ops; dev->user_data = &test_dev_ctx; if (rt_device_register(dev, TEST_DEV_NAME, RT_DEVICE_FLAG_RDWR) != RT_EOK) { rt_device_destroy(dev); rt_kprintf("test_dev register failed\n"); return -RT_ERROR; } return RT_EOK; } -INIT_DEVICE_EXPORT(test_dev_register); - -static int test_dev_test(void) -{ rt_device_t dev = rt_device_find(TEST_DEV_NAME); struct test_dev_status status; char read_buffer[32] = {0}; const char write_data[] = "hello test_dev"; rt_size_t n; rt_err_t result; if (!dev) { rt_kprintf("test_dev find failed\n"); return -RT_ERROR; } if ((result = rt_device_init(dev)) != RT_EOK) return result; if ((result = rt_device_open(dev, RT_DEVICE_OFLAG_RDWR)) != RT_EOK) return result; n = rt_device_read(dev, 0, read_buffer, sizeof(read_buffer) - 1); read_buffer[n] = '\0'; rt_kprintf("test_dev application read: %s\n", read_buffer); rt_device_write(dev, 0, write_data, sizeof(write_data) - 1); result = rt_device_control(dev, TEST_DEV_GET_STATUS, &status); if (result == RT_EOK) rt_kprintf("test_dev status: initialized=%d, open_count=%u\n", status.initialized, status.open_count); rt_device_close(dev); return result; } -MSH_CMD_EXPORT(test_dev_test, test test_dev I/O device operations); - diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\344\275\234\344\270\232/test_dev.h" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\344\275\234\344\270\232/test_dev.h" deleted file mode 100644 index e3218b5d4c018b45493d1e8bb7db4203a3c01bf1..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\344\275\234\344\270\232/test_dev.h" +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef __TEST_DEV_H__ -#define __TEST_DEV_H__ -#include -#include -#define TEST_DEV_NAME "test_dev" -#define TEST_DEV_GET_STATUS 0x01 -struct test_dev_status { rt_uint32_t open_count; rt_bool_t initialized; }; -#endif - diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 9d947ab9041ed84cc6783b96a53627a09f102bb8..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\345\255\220\345\260\247/\347\254\254\345\233\233\345\244\251/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,256 +0,0 @@ ---- -date: 2026-08-20 -title: RT-Thread 夏令营 Day4 学习笔记 ---- - -# RT-Thread 夏令营 Day4 学习笔记 - -> 主题:RT-Thread 设备驱动框架与 PIN 设备对接分析 - -## 一、RT-Thread 设备驱动框架概述 - -在嵌入式系统开发中,不同芯片平台的硬件接口存在差异,如果应用程序直接调用底层硬件接口,会降低代码复用性和可移植性。 - -RT-Thread 通过设备驱动框架,将应用层和硬件层进行分离。 - -整体结构: - - 应用层 - | - RT-Thread I/O设备管理层 - | - 设备驱动层 - | - BSP硬件驱动 - | - MCU外设 - -应用程序只需要调用统一设备接口,不需要直接操作底层寄存器。 - ------------------------------------------------------------------------- - -## 二、RT-Thread 设备模型 - -RT-Thread 将硬件设备抽象为: - -``` c -struct rt_device -``` - -设备通过: - -``` c -rt_device_register() -``` - -注册到系统设备管理器。 - -应用程序可以通过: - -``` c -rt_device_find() -``` - -根据设备名称查找设备。 - ------------------------------------------------------------------------- - -## 三、rt_device_ops 接口 - -设备具体操作通过: - -``` c -struct rt_device_ops -``` - -完成。 - -主要接口: - - 接口 作用 - --------- ------------ - init 设备初始化 - open 打开设备 - close 关闭设备 - read 读取数据 - write 写入数据 - control 控制设备 - -调用流程: - - 应用程序 - ↓ - rt_device API - ↓ - rt_device_ops - ↓ - 具体驱动实现 - ↓ - 硬件 - ------------------------------------------------------------------------- - -## 四、PIN设备介绍 - -PIN设备用于统一管理 GPIO。 - -RT-Thread 不要求应用程序直接访问 MCU GPIO 寄存器,而是通过 PIN API -操作。 - -常用接口: - -``` c -rt_pin_mode() - -rt_pin_write() - -rt_pin_read() -``` - -例如: - -``` c -rt_pin_write(pin, PIN_HIGH); -``` - -最终会进入底层 GPIO 驱动,实现实际硬件控制。 - ------------------------------------------------------------------------- - -## 五、PIN设备对接流程分析 - -### 1. BSP实现GPIO驱动 - -不同芯片首先需要实现自己的 GPIO 驱动。 - -以 STM32 为例: - - STM32 HAL GPIO - | - ↓ - RT-Thread PIN驱动 - | - ↓ - PIN设备 - -底层负责 GPIO 模式配置、电平输出和输入读取。 - ------------------------------------------------------------------------- - -### 2. 注册PIN设备 - -系统启动时,PIN驱动创建设备对象: - -``` c -struct rt_device -``` - -并通过: - -``` c -rt_device_register() -``` - -注册。 - -注册完成后,系统中存在名为: - - pin - -的设备。 - ------------------------------------------------------------------------- - -### 3. 应用层访问PIN设备 - -应用调用: - -``` c -rt_device_find("pin"); -``` - -设备管理器根据名称返回对应设备对象。 - -随后: - - rt_pin_write() - - ↓ - - PIN设备接口 - - ↓ - - BSP GPIO驱动 - - ↓ - - MCU GPIO硬件 - -完成一次完整调用。 - ------------------------------------------------------------------------- - -## 六、设备驱动框架意义 - -RT-Thread 设备模型解决了: - -### 1. 硬件差异问题 - -不同平台: - - STM32 GPIO - NXP GPIO - RISC-V GPIO - -都可以通过统一接口访问。 - -### 2. 提高代码复用 - -应用程序只依赖: - -``` c -rt_device_read() - -rt_device_write() - -rt_pin_write() -``` - -无需关心具体硬件实现。 - -### 3. 提高系统维护性 - -驱动和应用分离后: - -- 更容易替换硬件; -- 更容易扩展新设备; -- 更方便多人协作开发。 - ------------------------------------------------------------------------- - -## 七、总结 - -Day4 主要学习了 RT-Thread 设备驱动框架,并重点分析了 PIN -设备从应用层到硬件层的对接流程。 - -核心结构: - - 应用程序 - ↓ - RT-Thread设备接口 - ↓ - rt_device - ↓ - rt_device_ops - ↓ - BSP驱动 - ↓ - 硬件 - -通过设备抽象层,RT-Thread -实现了应用代码和底层硬件的解耦,提高了系统移植性。 - -后续将基于该框架实现虚拟设备驱动 test_dev,通过自定义设备注册以及 -open/read/write/control 接口进一步理解 RT-Thread 驱动模型。 - diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 56ae6633fdd1e74755197a89eae4db40b0e31668..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,150 +0,0 @@ -## 练习一:停车场车位管理 - -### 场景 - -一个停车场只有 3 个车位,同时有 5 辆车尝试进入。车位属于有限资源,车辆只有获得车位后才能进入。 - -### 功能要求 - -1. 创建一个初始值为 3 的计数信号量,表示剩余车位。 -2. 创建 5 个车辆线程。 -3. 每辆车到达后申请一个车位。 -4. 获得车位后打印车辆编号和进入信息。 -5. 车辆停留一段时间后离开,并释放车位。 -6. 未在 2 秒内获得车位的车辆打印等待超时信息,稍后重新申请。 -7. 任意时刻进入停车场的车辆数不能超过 3。 - -### 示例输出 - -![屏幕截图 2026-08-21 010252.png](.\tp\屏幕截图%202026-08-21%20010252.png) - - - ---- - -## 练习二:多窗口售票系统 - -### 场景 - -两个售票窗口同时出售同一批车票。系统初始有 20 张票,每次只能由一个窗口修改剩余票数。 - -### 功能要求 - -1. 创建两个售票线程,分别代表窗口 A 和窗口 B。 -2. 使用一个全局变量保存剩余票数。 -3. 每个窗口循环完成以下操作: - - 检查是否还有余票; - - 将余票减 1; - - 记录售出的票号; - - 打印售票结果。 -4. 使用互斥量保护完整的售票过程。 -5. 售完后两个线程自动退出。 -6. 最终必须满足: - - 20 张票全部售出; - - 不出现相同票号; - - 剩余票数不能小于 0。 - -### 示例输出 - -![屏幕截图 2026-08-20 235215.png](.\tp\屏幕截图%202026-08-20%20235215.png) - ---- - -## 练习三:系统启动条件检查 - -### 场景 - -设备启动前需要完成三个独立模块的初始化: - -- 网络模块初始化; -- 传感器模块初始化; -- 存储模块初始化。 - -只有三个模块全部准备完成,主业务线程才能启动。 - -PS:只是举例,模拟即可。 - -### 功能要求 - -定义三个事件位: - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -``` - -创建以下线程: - -- 网络初始化线程; -- 传感器初始化线程; -- 存储初始化线程; -- 主业务线程。 - -每个初始化线程经过不同时间的延时后,发送对应的就绪事件。 - -主业务线程必须使用 AND 模式等待三个事件全部到达,然后打印: - -```text -all modules are ready -business task started -``` - -另外增加一次超时测试: - -- 将存储模块初始化时间设置为 5 秒; -- 主业务线程最多等待 3 秒; -- 超时后打印尚未完成的模块,并再次等待。 - -### 示例输出 - -![屏幕截图 2026-08-20 235307.png](.\tp\屏幕截图%202026-08-20%20235307.png) - ---- - -## 练习四:快递分拣中心 - -### 场景 - -两个收件线程不断产生快递信息,一个分拣线程从消息队列中取出快递并按照目的地区域分类。 - -### 消息结构 - -```c -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -``` - -其中: - -```text -region = 1:华东 -region = 2:华南 -region = 3:华北 -``` - -### 功能要求 - -1. 创建一个消息队列,队列容量为 5 条消息。 -2. 创建两个发件线程,每个线程产生 5 条快递消息。 -3. 快递编号不得重复。 -4. 分拣线程接收消息,并打印快递编号、区域和重量。 -5. 分拣完成后统计: - - 总快递数量; - - 各区域快递数量; - - 所有快递的总重量。 -6. 当队列已满时,发送线程不得崩溃,应记录发送失败次数。 -7. 两个发送线程完成后,向分拣线程发送一条结束消息。 -8. 分拣线程收到结束消息后打印统计结果并退出。 - -### 示例输出 - -![屏幕截图 2026-08-21 011728.png](.\tp\屏幕截图%202026-08-21%20011728.png) - - - - diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/car.c" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/car.c" deleted file mode 100644 index 9cec44897e1c2e288ea4e03264105b179f5f6b3a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/car.c" +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2006-2022, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 user parking semaphore demo - */ - -#include - -#define THREAD_PRIORITY 25 -#define THREAD_TIMESLICE 5 -#define CAR_COUNT 5 -#define PARK_SPACE_NUM 3 -#define PARK_WAIT_TIME (RT_TICK_PER_SECOND * 2) -/*新增:车辆到达错峰间隔*/ -#define ARRIVAL_GAP_MS 300 - -/* 计数信号量:车位 */ -static rt_sem_t park_sem = RT_NULL; -/* 互斥锁保护已占用车位计数 */ -static rt_mutex_t space_mtx = RT_NULL; -static rt_uint8_t used_spaces = 0; - -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif -static char car_stack[CAR_COUNT][1024]; -static struct rt_thread car_thread[CAR_COUNT]; - -static void car_thread_entry(void *parameter) -{ - rt_int32_t car_id = (rt_int32_t)parameter; - char car_name[16]; - rt_err_t ret; - - rt_sprintf(car_name, "car%d", car_id); - rt_thread_mdelay((car_id - 1) * ARRIVAL_GAP_MS); - - while(1) - { - ret = rt_sem_take(park_sem, PARK_WAIT_TIME); - if(ret == RT_EOK) - { - rt_mutex_take(space_mtx, RT_WAITING_FOREVER); - used_spaces ++; - rt_uint8_t cur_use = used_spaces; - rt_mutex_release(space_mtx); - - rt_kprintf("%s entered, used spaces: %d\n", car_name, cur_use); - - rt_thread_mdelay(2000); - - rt_mutex_take(space_mtx, RT_WAITING_FOREVER); - used_spaces --; - rt_mutex_release(space_mtx); - rt_sem_release(park_sem); - - rt_kprintf("%s left\n", car_name); - break; - } - else - { - rt_kprintf("%s waiting for a space\n", car_name); - rt_thread_mdelay(500); - } - } -} - -int parking_sample(void) -{ - rt_err_t res; - int i; - - park_sem = rt_sem_create("park_sem", PARK_SPACE_NUM, RT_IPC_FLAG_PRIO); - if (park_sem == RT_NULL) - { - rt_kprintf("create park semaphore failed.\n"); - return -1; - } - - space_mtx = rt_mutex_create("space_mtx", RT_IPC_FLAG_PRIO); - if (space_mtx == RT_NULL) - { - rt_kprintf("create mutex failed.\n"); - rt_sem_delete(park_sem); - return -1; - } - used_spaces = 0; - - for(i = 0; i < CAR_COUNT; i++) - { - res = rt_thread_init(&car_thread[i], - RT_NULL, - car_thread_entry, - (void *)(i + 1), - &car_stack[i][0], - sizeof(car_stack[i]), - THREAD_PRIORITY, - THREAD_TIMESLICE); -#ifdef RT_USING_SMP - rt_thread_control(&car_thread[i], RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - if(res == RT_EOK) - { - rt_thread_startup(&car_thread[i]); - } - } - - return 0; -} - -MSH_CMD_EXPORT(parking_sample, parking semaphore sample); diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/open.c" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/open.c" deleted file mode 100644 index a356ee75d468e5e1ad22b39231a8bb57930c94e0..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/open.c" +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2006-2022, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 user system startup event demo - */ -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define ALL_EVENT_MASK (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -#define THREAD_STACK_SIZE 1024 -#define THREAD_PRIORITY 20 - -/* 事件集 */ -static rt_event_t startup_event = RT_NULL; - -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif - -static char net_stack[THREAD_STACK_SIZE]; -static struct rt_thread net_thread; - -static char sensor_stack[THREAD_STACK_SIZE]; -static struct rt_thread sensor_thread; - -static char storage_stack[THREAD_STACK_SIZE]; -static struct rt_thread storage_thread; - -static char business_stack[THREAD_STACK_SIZE]; -static struct rt_thread business_thread; - -/* 网络初始化线程 延时1s就绪 */ -static void net_thread_entry(void *parameter) -{ - rt_thread_mdelay(1000); - rt_event_send(startup_event, EVENT_NET_READY); - rt_kprintf("network ready\n"); -} - -/* 传感器初始化线程 延时2s就绪 */ -static void sensor_thread_entry(void *parameter) -{ - rt_thread_mdelay(2000); - rt_event_send(startup_event, EVENT_SENSOR_READY); - rt_kprintf("sensor ready\n"); -} - -/* 存储初始化线程 延时5s就绪 */ -static void storage_thread_entry(void *parameter) -{ - rt_thread_mdelay(5000); - rt_event_send(startup_event, EVENT_STORAGE_READY); - rt_kprintf("storage ready\n"); -} - -/* 主业务线程 */ -static void business_thread_entry(void *parameter) -{ - rt_uint32_t recv; - rt_err_t ret; - - while (1) - { - /* AND模式,等待全部事件,最长等待3秒 */ - ret = rt_event_recv(startup_event, - ALL_EVENT_MASK, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_TICK_PER_SECOND * 3, - &recv); - if (ret == RT_EOK) - { - rt_kprintf("all modules are ready\nbusiness task started\n"); - break; - } - else - { - rt_kprintf("wait modules timeout, "); - if (!(recv & EVENT_NET_READY)) - { - rt_kprintf("net "); - } - if (!(recv & EVENT_SENSOR_READY)) - { - rt_kprintf("sensor "); - } - if (!(recv & EVENT_STORAGE_READY)) - { - rt_kprintf("storage "); - } - rt_kprintf("is not ready\n"); - } - } -} - -int startup_check_sample(void) -{ - rt_err_t res; - - startup_event = rt_event_create("startup_evt", RT_IPC_FLAG_PRIO); - if (startup_event == RT_NULL) - { - rt_kprintf("create event failed\n"); - return -1; - } - - /* 网络线程 */ - res = rt_thread_init(&net_thread, - "net_init", - net_thread_entry, - RT_NULL, - net_stack, - sizeof(net_stack), - THREAD_PRIORITY, - 5); - if (res == RT_EOK) - { - rt_thread_startup(&net_thread); - } - - /* 传感器线程 */ - res = rt_thread_init(&sensor_thread, - "sensor_init", - sensor_thread_entry, - RT_NULL, - sensor_stack, - sizeof(sensor_stack), - THREAD_PRIORITY, - 5); - if (res == RT_EOK) - { - rt_thread_startup(&sensor_thread); - } - - /* 存储线程 */ - res = rt_thread_init(&storage_thread, - "storage_init", - storage_thread_entry, - RT_NULL, - storage_stack, - sizeof(storage_stack), - THREAD_PRIORITY, - 5); - if (res == RT_EOK) - { - rt_thread_startup(&storage_thread); - } - - /* 业务线程 */ - res = rt_thread_init(&business_thread, - "business", - business_thread_entry, - RT_NULL, - business_stack, - sizeof(business_stack), - THREAD_PRIORITY, - 5); - if (res == RT_EOK) - { - rt_thread_startup(&business_thread); - } - - return 0; -} - -MSH_CMD_EXPORT(startup_check_sample, system startup event check sample); diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/pac.c" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/pac.c" deleted file mode 100644 index 6b5f2f3169a44a8bd6821f247e22cabe67b58673..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/pac.c" +++ /dev/null @@ -1,160 +0,0 @@ -#include -#include - -#define MSG_QUEUE_MAX 5 -#define SEND_THREAD_CNT 2 -#define PER_THREAD_PACK_CNT 5 - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -#define MSG_END_FLAG_REGION 0 - -static rt_mq_t package_mq = RT_NULL; - -static rt_uint32_t global_pkg_id = 1000; -static rt_uint32_t send_fail_cnt = 0; -static struct rt_mutex id_mtx; - -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif -static char sender_stack[SEND_THREAD_CNT][1024]; -static struct rt_thread sender_thread[SEND_THREAD_CNT]; -static char sort_stack[1024]; -static struct rt_thread sort_thread; - -static void sender_entry(void *param) -{ - int i; - struct package_msg msg; - rt_err_t ret; - - for(i = 0; i < PER_THREAD_PACK_CNT; i++) - { - rt_mutex_take(&id_mtx, RT_WAITING_FOREVER); - global_pkg_id++; - msg.id = global_pkg_id; - rt_mutex_release(&id_mtx); - - msg.region = (rt_uint8_t)(rand() % 3 + 1); - msg.weight = 500 + (rand() % 1500); - - ret = rt_mq_send(package_mq, &msg, sizeof(struct package_msg)); - if(ret != RT_EOK) - { - send_fail_cnt++; - } - rt_thread_mdelay(200); - } -} - -static void sort_entry(void *param) -{ - struct package_msg msg; - rt_err_t ret; - rt_uint32_t total_pkg = 0; - rt_uint32_t east_cnt = 0, south_cnt = 0, north_cnt = 0; - rt_uint32_t total_weight = 0; - char *region_name; - - while(1) - { - ret = rt_mq_recv(package_mq, &msg, sizeof(struct package_msg), RT_WAITING_FOREVER); - if(ret != RT_EOK) - continue; - - if(msg.region == MSG_END_FLAG_REGION) - { - break; - } - - total_pkg++; - total_weight += msg.weight; - switch(msg.region) - { - case 1: - region_name = "east"; - east_cnt++; - break; - case 2: - region_name = "south"; - south_cnt++; - break; - case 3: - region_name = "north"; - north_cnt++; - break; - default: - region_name = "unknown"; - break; - } - rt_kprintf("package %d -> %s, weight: %d g\n", msg.id, region_name, msg.weight); - } - - rt_kprintf("total packages: %d\n", total_pkg); - rt_kprintf("east: %d, south: %d, north: %d\n", east_cnt, south_cnt, north_cnt); - rt_kprintf("total weight: %d g\n", total_weight); - rt_kprintf("send failed: %d\n", send_fail_cnt); -} - -int express_sort_sample(void) -{ - rt_err_t res; - int i; - struct package_msg end_msg; - - rt_mutex_init(&id_mtx, "id_mtx", RT_IPC_FLAG_PRIO); - - package_mq = rt_mq_create("pkg_mq", sizeof(struct package_msg), MSG_QUEUE_MAX, RT_IPC_FLAG_PRIO); - if(package_mq == RT_NULL) - { - rt_kprintf("create mq failed\n"); - return -1; - } - - global_pkg_id = 1000; - send_fail_cnt = 0; - - for(i = 0; i < SEND_THREAD_CNT; i++) - { - res = rt_thread_init(&sender_thread[i], - RT_NULL, - sender_entry, - RT_NULL, - &sender_stack[i][0], - sizeof(sender_stack[i]), - 22, - 5); - if(res == RT_EOK) - { - rt_thread_startup(&sender_thread[i]); - } - } - - res = rt_thread_init(&sort_thread, - RT_NULL, - sort_entry, - RT_NULL, - sort_stack, - sizeof(sort_stack), - 21, - 5); - if(res == RT_EOK) - { - rt_thread_startup(&sort_thread); - } - - rt_thread_mdelay(3000); - rt_memset(&end_msg, 0, sizeof(end_msg)); - end_msg.region = MSG_END_FLAG_REGION; - rt_mq_send(package_mq, &end_msg, sizeof(struct package_msg)); - - return 0; -} -MSH_CMD_EXPORT(express_sort_sample, express sort practice); diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-20 235215.png" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-20 235215.png" deleted file mode 100644 index 5a1997bcb7150d526ff1720bca1b4ef0bb16ec91..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-20 235215.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-20 235307.png" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-20 235307.png" deleted file mode 100644 index 06789a49e1a81a07b8149d4159373efb6df71ff6..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-20 235307.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-21 010252.png" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-21 010252.png" deleted file mode 100644 index d1ba051e3492b30575fbf12247a67e0eab411877..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-21 010252.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-21 011728.png" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-21 011728.png" deleted file mode 100644 index f27aa3cd5f66d877ebbb039bddf5e9f00ba67c2e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/tp/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-21 011728.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/win.c" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/win.c" deleted file mode 100644 index fdce7bc5682723d1161faa42b094639b9ad61d8e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/win.c" +++ /dev/null @@ -1,64 +0,0 @@ -#include - -#define TICKET_TOTAL 20 -static int g_ticket = TICKET_TOTAL; -static rt_mutex_t ticket_mutex; -static rt_sem_t done_sem; - -void ticket_sell_entry(void *param) -{ - char *win_name = (char *)param; - while (1) - { - rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); - - if(g_ticket <= 0) - { - rt_mutex_release(ticket_mutex); - break; - } - - rt_thread_mdelay(1); - g_ticket --; - rt_kprintf("%s sold ticket %d\n", win_name, g_ticket + 1); - - rt_mutex_release(ticket_mutex); - rt_thread_mdelay(2); - } - rt_sem_release(done_sem); -} - -int ticket_demo(int argc, char *argv[]) -{ - rt_thread_t t_a, t_b; - char *name_a = "window A"; - char *name_b = "window B"; - - g_ticket = TICKET_TOTAL; - - ticket_mutex = rt_mutex_create("tick_mtx", RT_IPC_FLAG_PRIO); - done_sem = rt_sem_create("done",0,RT_IPC_FLAG_PRIO); - if(ticket_mutex == RT_NULL || done_sem == RT_NULL) - { - rt_kprintf("create ipc fail\n"); - return -1; - } - - t_a = rt_thread_create("winA", ticket_sell_entry, name_a, 1024, 10, 5); - t_b = rt_thread_create("winB", ticket_sell_entry, name_b, 1024, 10, 5); - - if(t_a != RT_NULL && t_b != RT_NULL) - { - rt_thread_startup(t_a); - rt_thread_startup(t_b); - } - - rt_sem_take(done_sem, RT_WAITING_FOREVER); - rt_sem_take(done_sem, RT_WAITING_FOREVER); - - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: %d\n", g_ticket); - - return 0; -} -MSH_CMD_EXPORT(ticket_demo, multi window sell ticket with mutex); diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/readme.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/readme.md" deleted file mode 100644 index 5f34f9be31f807e1c3631bcf743880c6509233d6..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/readme.md" +++ /dev/null @@ -1,155 +0,0 @@ -## Part 1:理论知识梳理文档 - -### 1. STM32F407 启动流程与 RT‑Thread 引导机制 - -#### 1.1 上电复位 Reset_Handler → main() 完整调用链 - -``` -上电复位 -↓ -Reset_Handler(启动文件 startup_stm32f4xx.s 汇编) - 1. 设置栈指针 SP - 2. BSS段清零、Data段从Flash拷贝到RAM - 3. 跳转到编译器入口 - MDK‑Keil:$Sub$$main() - GCC:entry() -↓ -rtthread_startup() // RT‑Thread系统总入口函数 - ├─ rt_hw_interrupt_disable() //关闭全局中断 - ├─ rt_hw_board_init() //BSP板级硬件初始化 - │ └─ rt_components_board_init() //执行INIT_BOARD_EXPORT导出函数 - ├─ rt_show_version() //打印RT‑Thread版本LOGO - ├─ rt_system_timer_init() //初始化内核定时器模块 - ├─ rt_system_scheduler_init() //初始化调度器 - ├─ rt_system_signal_init() - ├─ rt_application_init() //创建main主线程 - ├─ rt_system_timer_thread_init() //创建软件定时器线程 - ├─ rt_thread_idle_init() //创建空闲线程 - └─ rt_system_scheduler_start() //启动调度器,多线程正式运行 -↓ -调度器选中 main_thread_entry 主线程运行 - └─ rt_components_init() //执行INIT_APP_EXPORT导出函数 - └─ $Super$$main() - └─ main() //到达用户main函数 -``` - -#### 1.2 自动初始化原理 `INIT_BOARD_EXPORT` / `INIT_APP_EXPORT` - -RT‑Thread 使用**段地址、编译器宏**实现自动初始化,不需要手动调用函数。 -底层原理: - -1. 宏定义会把我们的初始化函数,加上指定的段属性,放到内存中一段特殊的自定义段 -- `INIT_BOARD_EXPORT(fn)`:放到 **board 初始化段** -- `INIT_APP_EXPORT(fn)`:放到 **application 初始化段** -2. 链接脚本把同类型初始化函数,在内存里排成一张函数指针数组 -3. 在系统启动的时候,内核自动遍历整张函数表,依次执行里面的函数 - 执行时机: -- `INIT_BOARD_EXPORT`:`rt_hw_board_init()` →`rt_components_board_init()`阶段,硬件板级初始化,**调度器开启之前执行** -- `INIT_APP_EXPORT`:主线程里面 `rt_components_init()`,调度已经启动,可以使用内核API - 执行顺序优先级:`BOARD` 优先于 `APP`。 - ---- - -### 2. 内核基础与对象模型 - -#### 2.1 RT‑Thread面向对象设计思想 & 线程控制块关键成员 - -##### 面向对象设计思想 - -C语言本身没有类,RT‑Thread采用**结构体嵌套模拟继承**。 -所有内核资源(线程、信号量、定时器、设备、内存池)都派生自基类`rt_object`。 -`rt_object`提供通用属性:名字name、类型type、链表节点list;子类在它基础上扩展自己独有的成员。 - -##### struct rt_thread 线程控制块关键成员 - -| 成员 | 作用 | -| ------------------ | ----------------------------------------------------------------- | -| `stat` | 线程状态标记:初始、就绪、运行、挂起、关闭 | -| `current_priority` | 线程当前实际优先级,优先级数值越小优先级越高 | -| `init_tick` | 线程时间片,同一个优先级下线程可分得的运行时长 | -| `sp` | 栈指针,保存线程被切走那一刻CPU寄存器现场,线程切换用来恢复上下文 | - -#### 2.2 静态对象 VS 动态对象 - -##### 静态对象 - -- 分配位置:编译期分配在全局RAM(.data/.bss) -- 生命周期:整个程序运行全程有效,上电即存在 -- 释放:**不能使用 rt_thread_delete**,只能手动脱离内核管理 - -##### 动态对象 - -- 分配位置:运行的时候从RT‑Thread堆(heap)里面`rt_malloc`申请内存 -- 生命周期:创建时分配,调用`rt_thread_delete()`释放归还堆内存 -- 释放机制:内核自动回收内存 - -##### 高可靠场景强制用静态线程原因 - -堆内存动态分配`malloc`存在内存碎片、分配失败风险; - -堆内存动态分配`malloc`存在内存碎片、分配失败风险; -工业、高可靠项目不能允许运行时线程创建失败,**静态内存编译期就已经分配完成,运行不会申请失败**,稳定性更高。 - -### 3. 线程状态机与调度算法 - -#### 3.1 RT‑Thread线程五态模型 - -1. **初始态 RT_THREAD_INIT** - 刚调用`rt_thread_init` / `rt_thread_create`创建完成,还没启动。 - 转换API:`rt_thread_startup()` → **就绪态** -2. **就绪态 RT_THREAD_READY** - 线程一切准备完毕,只等待CPU。放在就绪链表,等待调度。 -- 高优先级线程抢占 → 当前正在运行线程→就绪态 -- 调用`rt_thread_suspend()` → 挂起态 -3. **运行态 RT_THREAD_RUNNING** - 当前这个线程正在CPU上面执行,同一时刻只能有1个线程处于运行态 -- `rt_thread_delay()` / 获取不到信号量 → 挂起态 -- 时间片耗尽 → 就绪态 -4. **挂起态 RT_THREAD_SUSPEND** - 线程被阻塞,暂时不能运行(延时、等待信号量、被手动挂起) -- `rt_thread_resume()` / 延时时间到 / 获取到资源 →就绪态 -5. **关闭态 RT_THREAD_CLOSE** - 线程执行完毕或者被删除,不再参与调度。 - -> 状态转换简图 - -``` -初始态 →(startup)→ 就绪态 ←(resume)← 挂起态 - ↓(调度选中) - 运行态 - 运行态→(delay/suspend)→挂起态 - 任意状态 →(rt_thread_delete)→关闭态 -``` - -#### 3.2 调度机制剖析 - -##### 抢占式调度 - -- **高优先级线程一旦就绪,可以立刻打断正在运行的低优先级线程**,CPU马上切换给高优先级任务。 -- 保障紧急、高实时任务能够立刻得到响应。 - -##### 时间片轮转调度 - -- 同一个优先级的就绪线程,按照各自分配的init_tick时间片轮流使用CPU。一个线程时间片用完,让出CPU,同优先级下一个线程开始运行。 - -# Part 2:多线程实战编程 - -### 上电正常运行、没有按下按键时串口输出 - -![屏幕截图 2026-08-19 173545.png](.\屏幕截图%202026-08-19%20173545.png) - -### 第一次按下按键(挂起th_led1,红灯停) - -![屏幕截图 2026-08-19 173607.png](.\屏幕截图%202026-08-19%20173607.png) - - - - - -### 输入 shell 命令 `thread_dump` 的打印样例 - -![屏幕截图 2026-08-19 173952.png](.\屏幕截图%202026-08-19%20173952.png) - - - - diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread.c" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread.c" deleted file mode 100644 index 2029506c236cde813bad9a589d666d3357664c66..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread.c" +++ /dev/null @@ -1,519 +0,0 @@ -#include -#include -#include -#include -#include - -/* LED ºͰ´¼üÒý½Å */ -#define LED_R_PIN GET_PIN(F, 12) -#define LED_B_PIN GET_PIN(F, 11) -#define KEY_PIN GET_PIN(C, 5) - -/* ¹¤×÷Ï߳ÌÅäÖà */ -#define LED_STACK_SIZE 1024 -#define LED_PRIORITY 16 -#define LED_TIMESLICE 5 -#define LED_PERIOD_MS 1000 - -/* ¼à¿ØÏ߳ÌÅäÖà */ -#define MONITOR_STACK_SIZE 1024 -#define MONITOR_PRIORITY 10 -#define MONITOR_TIMESLICE 5 -#define MONITOR_PERIOD_MS 2000 - -/* °´¼üÏû¶¶ʱ¼ä */ -#define KEY_DEBOUNCE_MS 50 - -/* ¶¯̬Ï߳̾ä±ú */ -static rt_thread_t th_led1 = RT_NULL; -static rt_thread_t th_monitor = RT_NULL; - -/* ¾²̬Ï߳Ì th_led2 µÄջ¿ռäºÍÏ߳̿ØÖƿé */ -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t th_led2_stack[LED_STACK_SIZE]; - -ALIGN(RT_ALIGN_SIZE) -static struct rt_thread th_led2; - -/* °´¼üÖжÏʼþ±ê־ */ -static volatile rt_uint8_t key_event = 0; -static volatile rt_tick_t key_last_tick = 0; - -/* - * th_led1 µĹÒÆð¿ØÖƱäÁ¿¡£ - * - * ¼à¿ØÏ߳ÌֻÉèÖùÒÆðÇëÇó£¬ - * th_led1 ¼ì²⵽ÇëÇóºó×ÔÐÐִÐйÒÆð¡£ - */ -static volatile rt_uint8_t led1_suspend_request = 0; -static volatile rt_uint8_t led1_manual_suspended = 0; - - -/* ·­תָ¶¨ GPIO µÄÊä³öµçƽ */ -static void led_toggle(rt_base_t pin) -{ - rt_base_t level; - - level = rt_pin_read(pin); - - if (level == PIN_LOW) - { - rt_pin_write(pin, PIN_HIGH); - } - else - { - rt_pin_write(pin, PIN_LOW); - } -} - - -/* ½«Ï߳Ì״̬ת»»ΪӢÎÄ×ַû´® */ -static const char *thread_state_name(rt_thread_t thread) -{ - if (thread == RT_NULL) - { - return "null"; - } - - switch (thread->stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: - return "init"; - - case RT_THREAD_READY: - return "ready"; - - case RT_THREAD_SUSPEND: - return "suspend"; - - case RT_THREAD_RUNNING: - return "running"; - - case RT_THREAD_CLOSE: - return "close"; - - default: - return "unknown"; - } -} - - -/* - * ¼ÆËãÏ߳ÌʣÓàջ¿ռ䡣 - * - * RT-Thread ÔÚÏ̳߳õʼ»¯ʱ»ὫջÌî³äΪ '#', - * ͨ¹ýͳ¼ÆÁ¬ÐøµÄ '#' ¹ÀËãδʹÓõÄջ¿ռ䡣 - */ -static rt_uint32_t thread_stack_free(rt_thread_t thread) -{ - rt_uint8_t *start; - rt_uint8_t *end; - rt_uint8_t *current; - - if (thread == RT_NULL) - { - return 0; - } - - start = (rt_uint8_t *)thread->stack_addr; - end = start + thread->stack_size; - current = start; - - while ((current < end) && (*current == '#')) - { - current++; - } - - return (rt_uint32_t)(current - start); -} - - -/* ´òӡÏ߳Ì״̬¡¢ÓÅÏȼ¶ºÍʣÓàջ¿ռä */ -static void print_thread_info(const char *label, rt_thread_t thread) -{ - if (thread == RT_NULL) - { - rt_kprintf("%s: not created\n", label); - return; - } - - rt_kprintf( - "%s: state=%s, priority=%u, stack_free=%u/%u bytes\n", - label, - thread_state_name(thread), - (unsigned int)thread->current_priority, - (unsigned int)thread_stack_free(thread), - (unsigned int)thread->stack_size); -} - - -/* - * ¹¤×÷Ï߳Ì th_led1¡£ - * - * ¶¯̬´´½¨£» - * ÓÅÏȼ¶Ϊ 16£» - * ʱ¼äƬΪ 5 ticks£» - * ջ´óСΪ 1024 ×ֽڣ» - * ѭ»··­תºìɫ LED£¬²¢´òӡ¼ÆÊýºÍϵͳ Tick¡£ - */ -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - (void)parameter; - - while (1) - { - /* - * µ±ǰ RT-Thread °汾ҪÇóÏ߳ÌֻÄܹÒÆð×Լº£¬ - * Òò´ËÓÉ th_led1 ×ԼºִÐÐ rt_thread_suspend()¡£ - */ - if (led1_suspend_request != 0) - { - led1_suspend_request = 0; - led1_manual_suspended = 1; - - rt_kprintf("th_led1: suspended\n"); - - rt_thread_suspend(rt_thread_self()); - - /* - * ±» th_monitor »ָ´º󣬴ÓÕâÀï¼ÌÐøִÐС£ - */ - continue; - } - - /* ·­תºìɫ LED */ - led_toggle(LED_R_PIN); - - count++; - - /* ´òӡ¼ÆÊýºÍϵͳ Tick */ - rt_kprintf( - "[th_led1] count=%u, tick=%u\n", - (unsigned int)count, - (unsigned int)rt_tick_get()); - - rt_thread_mdelay(LED_PERIOD_MS); - } -} - - -/* - * ¹¤×÷Ï߳Ì th_led2¡£ - * - * ¾²̬³õʼ»¯£» - * ʹÓÃȫ¾ֶ¨ÒåµÄÏ߳̿ØÖƿéºÍջ¿ռ䣻 - * ÓÅÏȼ¶Ϊ 16£» - * ʱ¼äƬΪ 5 ticks£» - * ջ´óСΪ 1024 ×ֽڣ» - * ѭ»··­תÀ¶ɫ LED£¬²¢´òӡ¼ÆÊý¡£ - */ -static void th_led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - - (void)parameter; - - while (1) - { - /* ·­תÀ¶ɫ LED */ - led_toggle(LED_B_PIN); - - count++; - - /* ´òӡ¼ÆÊý */ - rt_kprintf( - "[th_led2] count=%u\n", - (unsigned int)count); - - rt_thread_mdelay(LED_PERIOD_MS); - } -} - - -/* °´¼üÖжϻص÷º¯Êý */ -static void key_irq_callback(void *args) -{ - rt_tick_t now; - - (void)args; - - now = rt_tick_get(); - - /* - * Èí¼þÏû¶¶£¬·Àֹһ´ΰ´¼ü²úÉú¶à¸öÖжÏʼþ¡£ - */ - if ((rt_tick_t)(now - key_last_tick) >= - rt_tick_from_millisecond(KEY_DEBOUNCE_MS)) - { - key_event = 1; - key_last_tick = now; - } -} - - -/* ³õʼ»¯°´¼ü GPIO ºÍÍⲿÖжÏ */ -static rt_err_t key_init(void) -{ - rt_err_t result; - - /* °´¼üʹÓÃÄڲ¿ÉÏÀ­ÊäÈë */ - rt_pin_mode(KEY_PIN, PIN_MODE_INPUT_PULLUP); - - key_last_tick = - rt_tick_get() - rt_tick_from_millisecond(KEY_DEBOUNCE_MS); - - /* ÅäÖð´¼üϽµÑØÖжÏ */ - result = rt_pin_attach_irq( - KEY_PIN, - PIN_IRQ_MODE_FALLING, - key_irq_callback, - RT_NULL); - - if (result != RT_EOK) - { - rt_kprintf("attach key irq failed: %d\n", result); - return result; - } - - /* ¿ªÆô°´¼üÖжÏ */ - result = rt_pin_irq_enable(KEY_PIN, PIN_IRQ_ENABLE); - - if (result != RT_EOK) - { - rt_kprintf("enable key irq failed: %d\n", result); - } - - return result; -} - - -/* - * µÚһ´ΰ´¼ü£ºÇëÇó¹ÒÆð th_led1£» - * µڶþ´ΰ´¼ü£º»ָ´ th_led1¡£ - */ -static void process_key_event(void) -{ - rt_err_t result; - - if (key_event == 0) - { - return; - } - - key_event = 0; - - if (led1_manual_suspended != 0) - { - rt_kprintf( - "\n========== [KEY] PRESS ==========\n"); - rt_kprintf( - "[KEY] action: RESUME th_led1\n"); - - result = rt_thread_resume(th_led1); - - if (result == RT_EOK) - { - led1_manual_suspended = 0; - rt_kprintf("[KEY] result: th_led1 RESUMED\n"); - } - else - { - rt_kprintf( - "[KEY] result: RESUME FAILED, error=%d\n", - result); - } - - rt_kprintf( - "=================================\n"); - } - else if (led1_suspend_request == 0) - { - rt_kprintf( - "\n========== [KEY] PRESS ==========\n"); - rt_kprintf( - "[KEY] action: REQUEST SUSPEND th_led1\n"); - - led1_suspend_request = 1; - - rt_kprintf( - "[KEY] result: waiting for th_led1...\n"); - rt_kprintf( - "=================================\n"); - } - else - { - rt_kprintf( - "[KEY] th_led1 suspend request is pending\n"); - } -} - - -/* - * ¼à¿ØÏ߳Ìÿ¸ô 2 Ã뻽ÐÑһ´Σ¬ - * ´òӡÁ½¸ö¹¤×÷Ï̵߳Ä״̬ºÍʣÓàջ¿ռ䡣 - */ -static void th_monitor_entry(void *parameter) -{ - (void)parameter; - - while (1) - { - rt_kprintf( - "\n========== [MONITOR] WAKE UP ==========\n"); - - /* ´¦Àí°´¼üʼþ */ - process_key_event(); - - /* ´òӡÁ½¸ö¹¤×÷Ï̵߳ÄÐÅϢ */ - print_thread_info("[MONITOR] th_led1", th_led1); - print_thread_info("[MONITOR] th_led2", &th_led2); - - rt_kprintf( - "========== [MONITOR] SLEEP 2s ==========\n"); - - rt_thread_mdelay(MONITOR_PERIOD_MS); - } -} - - -/* - * ×Զ¨Òå MSH ÃüÁ - * - * Ôڴ®¿ÚÊäÈë thread_dump£¬ - * ¿ÉÒԲ鿴Èý¸öÏ̵߳Ä״̬ºÍʣÓàջ¿ռ䡣 - */ -int thread_dump(int argc, char **argv) -{ - (void)argc; - (void)argv; - - rt_kprintf("========== thread dump ==========\n"); - - print_thread_info("th_led1", th_led1); - print_thread_info("th_led2", &th_led2); - print_thread_info("th_monitor", th_monitor); - - rt_kprintf("=================================\n"); - - return RT_EOK; -} - -/* ½« thread_dump ע²áΪ MSH ÃüÁî */ -MSH_CMD_EXPORT(thread_dump, dump thread state and remaining stack); - - -/* - * ӦÓóõʼ»¯º¯Êý¡£ - * - * ³õʼ»¯ LED¡¢°´¼ü£¬ - * ´´½¨¶¯̬Ï̡߳¢¾²̬Ï̺߳ͼà¿ØÏ̡߳£ - */ -static int thread_study_init(void) -{ - rt_err_t result; - - /* ÅäÖúìɫºÍÀ¶ɫ LED ΪÊä³öģʽ */ - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); - - /* ÉèÖà LED ³õʼµçƽ */ - rt_pin_write(LED_R_PIN, PIN_HIGH); - rt_pin_write(LED_B_PIN, PIN_HIGH); - - /* ³õʼ»¯°´¼ü */ - result = key_init(); - - if (result != RT_EOK) - { - rt_kprintf("key initialization failed: %d\n", result); - } - - /* - * ¶¯̬´´½¨ th_led1¡£ - */ - th_led1 = rt_thread_create( - "th_led1", - th_led1_entry, - RT_NULL, - LED_STACK_SIZE, - LED_PRIORITY, - LED_TIMESLICE); - - if (th_led1 == RT_NULL) - { - rt_kprintf("create th_led1 failed\n"); - return -RT_ENOMEM; - } - - result = rt_thread_startup(th_led1); - - if (result != RT_EOK) - { - rt_kprintf("startup th_led1 failed: %d\n", result); - return result; - } - - /* - * ¾²̬³õʼ»¯ th_led2¡£ - */ - result = rt_thread_init( - &th_led2, - "th_led2", - th_led2_entry, - RT_NULL, - th_led2_stack, - sizeof(th_led2_stack), - LED_PRIORITY, - LED_TIMESLICE); - - if (result != RT_EOK) - { - rt_kprintf("initialize th_led2 failed: %d\n", result); - return result; - } - - result = rt_thread_startup(&th_led2); - - if (result != RT_EOK) - { - rt_kprintf("startup th_led2 failed: %d\n", result); - return result; - } - - /* - * ¶¯̬´´½¨ th_monitor¡£ - */ - th_monitor = rt_thread_create( - "th_monitor", - th_monitor_entry, - RT_NULL, - MONITOR_STACK_SIZE, - MONITOR_PRIORITY, - MONITOR_TIMESLICE); - - if (th_monitor == RT_NULL) - { - rt_kprintf("create th_monitor failed\n"); - return -RT_ENOMEM; - } - - result = rt_thread_startup(th_monitor); - - if (result != RT_EOK) - { - rt_kprintf("startup th_monitor failed: %d\n", result); - return result; - } - - return RT_EOK; -} - - -/* - * ʹÓÃ×Զ¯³õʼ»¯»úÖƼÓÔر¾ģ¿顣 - * - * ²»ÐèҪÔÚ main.c ÖÐÊֶ¯µ÷Óà thread_study_init()¡£ - */ -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-19 173545.png" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-19 173545.png" deleted file mode 100644 index dedae4e4df70b79d044f7a25487f4d10145f4343..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-19 173545.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-19 173607.png" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-19 173607.png" deleted file mode 100644 index fbeb7b1587c2141e6601e71cb239095fa77ec79e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-19 173607.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-19 173952.png" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-19 173952.png" deleted file mode 100644 index e6fc3dd319d964b2c17624af4f1807a114372570..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-19 173952.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/local_thumb_original_1786933888411_3fd0bed0e113849e9f46d367b753f1b6.png" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/local_thumb_original_1786933888411_3fd0bed0e113849e9f46d367b753f1b6.png" deleted file mode 100644 index 1552ce7e06e1fa5ccfeddc87992a83dcc4cecdcc..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/local_thumb_original_1786933888411_3fd0bed0e113849e9f46d367b753f1b6.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/original_1786953362557_3b553f6461082b2e0019c3b68ea09c93.png" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/original_1786953362557_3b553f6461082b2e0019c3b68ea09c93.png" deleted file mode 100644 index c13772978842a3dd68c38bd8f6d2d1367c4d9e0e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/original_1786953362557_3b553f6461082b2e0019c3b68ea09c93.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 181211.png" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 181211.png" deleted file mode 100644 index 2916a3c994d50174d33e5d04293633478271b53a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 181211.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 181801.png" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 181801.png" deleted file mode 100644 index bce082ea9289a262d5f59fd2dad2071d226268df..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 181801.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index fb1a1e71086db2291538eda0d9a6929c8c875344..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\216\346\214\257/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,160 +0,0 @@ -# 开发环境搭建与工程创建 - -## 一、RT‑Thread Studio 的安装、配置及 STM32F407 工程创建流程 - -### 1. 创建 STM32F407 工程 - -1. 打开 RT‑Thread Studio,点击左侧菜单栏 **文件 → 新建 → RT‑Thread项目** - - - -### 2. 使用 STM32 ST‑LINK Utility 更新固件 - -> - -### 3.运行 - -![屏幕截图 2026-08-17 181211.png](./figures/屏幕截图%202026-08-17%20181211.png) - - - -## 二、Git基础协作命令 - -### 1、初始化仓库(只在第一次新建本地仓库时使用) - -```bash -git init -``` - -作用:将当前文件夹初始化为Git仓库,生成隐藏的`.git`文件夹 - -### 2、绑定远程仓库(Fork项目必做) - -```bash -#origin:你自己Gitee仓库 -#upstream:官方上游主仓库,只需要配置1次 -git remote add upstream 官方仓库地址 -``` - -### 3、查看已经关联的远程仓库 - -```bash -git remote -v -``` - -输出会显示两组地址:`origin`(自己仓库) 和`upstream`(原官方仓库) - -### 4、拉取最新代码 - -```bash -#拉取自己仓库master -git pull origin master -#拉取官方上游仓库最新代码(fork项目更新首选) -git pull upstream master -``` - -> 数据流向:远端仓库 → 本地电脑 - -### 5、添加文件到暂存区 - -```bash -#添加单个文件 -git add 文件路径/文件名.md -#当前目录下所有改动全部加入暂存区 -git add . -``` - -> 暂存区含义:相当于一个临时清单,标记好你本次准备提交哪些改动。 -> **git add .会同时包含新增、修改、删除操作**,提交前建议使用`git status`检查文件列表。 - -### 6、提交改动到本地仓库 - -```bash -git commit -m "填写本次修改的说明文字" -``` - -> 注意必须带上参数 `-m`,后面写清楚本次做了什么修改 - -### 7、推送代码上传到远端仓库 - -```bash -git push origin 分支名 -#示例 -git push origin day1 -``` - -> 数据流向:本地电脑 → 你的Gitee远端仓库 - -### 8、新建并且切换分支 - -```bash -#方式1:创建并且直接跳转到test分支 -git checkout -b test -#方式2(新版git写法) -git switch -c test -``` - -## 四、提交 + PR完整流程 - -1. **网页端同步Fork仓库** - 打开你Gitee里面fork出来的仓库,点击【同步】按钮,把上游官方仓库最新改动同步到你自己的仓库 - -![original_1786953362557_3b553f6461082b2e0019c3b68ea09c93.png](./figures/original_1786953362557_3b553f6461082b2e0019c3b68ea09c93.png) - -> - - - -```bash -#切回本地master主分支 -git switch master -#拉取官方仓库最新代码 -git pull upstream master -#同步更新你自己Gitee仓库master -git push origin master -#新建本次任务分支,例如day2 -git checkout -b day -``` - -> 4. 将改动加入暂存区 - -```bash -git add . -``` - -5.提交本次修改 - -```bash -git commit -m "day2:xxx作业提交" -``` - -6.推送到自己Gitee仓库的day2分支 - -```bash -git push origin day2 -``` - -## 五、Git冲突解决方法 - -### rebase变基 - -```bash -#1、切回master,同步官方最新代码 -git checkout master -git pull upstream master -#2、切换到你有改动、出现冲突的功能分支 -git checkout day1 -#3、执行变基,把你的分支放在最新master之后 -git rebase master -``` - -4. 如果出现冲突,终端会提示文件冲突 -- 打开冲突文件,可以看到 `<<<<<>>>>>>commit编号`标记 -- 手动修改文件,保留需要的代码/文字,删掉冲突标记 -5. 修改完成 - -```bash -git add . -#继续完成变基 -git rebase --continue -``` diff --git "a/2026/\347\254\2542\347\273\204/\346\235\234\351\251\254\350\276\260/\344\275\234\344\270\232/Git\344\270\216\346\272\220\347\240\201\347\233\256\345\275\225\345\210\206\346\236\220.md" "b/2026/\347\254\2542\347\273\204/\346\235\234\351\251\254\350\276\260/\344\275\234\344\270\232/Git\344\270\216\346\272\220\347\240\201\347\233\256\345\275\225\345\210\206\346\236\220.md" deleted file mode 100644 index 36e8fd8b2c1f2cd6c925606ad93cfce5528fd276..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\234\351\251\254\350\276\260/\344\275\234\344\270\232/Git\344\270\216\346\272\220\347\240\201\347\233\256\345\275\225\345\210\206\346\236\220.md" +++ /dev/null @@ -1,339 +0,0 @@ -# Git/Gitee 提交与 RT-Thread 源码目录分析 - -## 1. Git 基本信息配置 - -查看 Git 版本: - -```bash -git --version -``` - -设置提交用户名: - -```bash -git config --global user.name "填写 Gitee 用户名" -``` - -设置提交邮箱: - -```bash -git config --global user.email "填写 Gitee 邮箱" -``` - -查看 Git 配置: - -```bash -git config --global --list -``` - -## 2. 配置 Gitee SSH 登录 - -生成 SSH 密钥: - -```bash -ssh-keygen -t ed25519 -C "填写 Gitee 邮箱" -``` - -查看公钥: - -```bash -cat ~/.ssh/id_ed25519.pub -``` - -将公钥复制到 Gitee: - -```text -Gitee → 设置 → SSH 公钥 → 添加公钥 -``` - -测试 SSH 连接: - -```bash -ssh -T git@gitee.com -``` - -## 3. 获取作业仓库并创建分支 - -克隆 RSOC-RTT 仓库: - -```bash -cd /Users/gatesbil/Documents/Project/stm32 -git clone https://gitee.com/rtthread/rsoc-rtt.git -cd rsoc-rtt -``` - -查看远程仓库: - -```bash -git remote -v -``` - -查看本地和远程分支: - -```bash -git branch -git branch -a -``` - -同步主分支: - -```bash -git switch master -git pull origin master -``` - -创建个人作业分支: - -```bash -git switch -c day1 -``` - -分支用于保存个人作业修改,避免直接修改官方 `master` 分支。 - -## 4. 添加作业文件 - -创建个人作业目录: - -```bash -mkdir -p 2026/第1组/姓名/作业 -``` - -查看当前修改: - -```bash -git status -``` - -添加作业文件: - -```bash -git add 2026/第1组/姓名/作业/ -``` - -检查暂存区内容: - -```bash -git diff --cached -``` - -提交作业: - -```bash -git commit -m "完成 STM32F407 Spark-1 RT-Thread 第一天作业" -``` - -查看提交记录: - -```bash -git log --oneline --decorate -5 -``` - -## 5. 推送分支并创建 Pull Request - -第一次推送个人分支: - -```bash -git push -u origin day1 -``` - -以后继续提交时: - -```bash -git add . -git commit -m "补充作业记录" -git push origin day1 -``` - -在 Gitee 页面创建 Pull Request: - -```text -源仓库:个人 fork 仓库 -源分支:day1 -目标仓库:RT-Thread/rsoc-rtt -目标分支:master -``` - -Pull Request 用于请求仓库维护者检查并合并作业内容。官方仓库没有个人写权限时,应先 Fork 官方仓库,再把个人分支推送到自己的远程仓库。 - -## 6. Git 常用命令 - -查看工作区状态: - -```bash -git status -``` - -查看未暂存修改: - -```bash -git diff -``` - -查看提交记录: - -```bash -git log --oneline -``` - -查看远程地址: - -```bash -git remote -v -``` - -撤销暂存: - -```bash -git restore --staged 文件名 -``` - -撤销工作区未提交修改: - -```bash -git restore 文件名 -``` - -`Everything up-to-date` 表示当前分支没有新的提交需要推送。空目录不会被 Git 跟踪,目录中至少需要存在一个文件。 - -## 7. RT-Thread 工程目录结构 - -以官方 Spark-1 SDK 为例: - -```text -sdk-bsp-stm32f407-spark/ -├── rt-thread/ -├── libraries/ -├── projects/ -├── tools/ -└── docs/ -``` - -目录含义: - -```text -rt-thread/:RT-Thread 内核和组件源码 -libraries/:STM32 HAL、CMSIS 和芯片驱动库 -projects/:基于 Spark-1 的示例工程 -tools/:SCons 构建和工程配置工具 -docs/:SDK 使用说明 -``` - -## 8. RT-Thread 内核目录 - -```text -rt-thread/ -├── src/ -├── include/ -├── components/ -├── libcpu/ -├── tools/ -├── packages/ -└── SConstruct -``` - -目录和文件作用: - -```text -src/:线程、调度器、时钟、IPC、内存管理等内核实现 -include/:RT-Thread 对外提供的头文件 -components/:FinSH、文件系统、网络协议栈等可选组件 -libcpu/:不同 CPU 架构的上下文切换和底层移植代码 -tools/:工程构建、Kconfig 和 SCons 相关脚本 -packages/:通过配置启用的外部软件包 -SConstruct:SCons 构建入口 -``` - -STM32F407 使用 Cortex-M4 内核,因此底层 CPU 相关代码位于: - -```text -libcpu/arm/cortex-m4/ -``` - -## 9. BSP 目录结构 - -Spark-1 BSP 目录: - -```text -bsp/stm32/stm32f407-rt-spark/ -├── board/ -├── applications/ -├── drivers/ -├── libraries/ -├── rtconfig.h -├── rtconfig.py -├── SConstruct -└── SConscript -``` - -目录和文件作用: - -```text -board/:开发板启动、时钟、引脚和板级初始化 -applications/:用户应用程序,例如 applications/main.c -drivers/:串口、GPIO、SPI 等外设驱动 -libraries/:芯片 HAL 和 CMSIS 相关文件 -rtconfig.h:RT-Thread 功能宏配置 -rtconfig.py:SCons 编译器和工程配置 -SConstruct:工程构建入口 -SConscript:收集当前目录下需要编译的源文件 -``` - -BSP 负责把 RT-Thread 内核移植到具体芯片和开发板。不同开发板可以有不同的 BSP,但它们共用 RT-Thread 内核、组件和构建工具。 - -## 10. FinSH 和 msh - -RT-Thread 的命令行功能由 FinSH 组件提供。启用以下配置后,可以通过串口使用 `msh`: - -```text -RT_USING_FINSH -FINSH_USING_MSH -``` - -常用命令: - -```text -help:查看命令帮助 -list_thread:查看线程 -free:查看内存使用情况 -list_timer:查看定时器 -``` - -应用程序中使用 `rt_kprintf` 输出普通信息,使用 `LOG_D` 输出 Debug 日志。RT-Thread 启动后,串口出现 `msh >` 即表示 FinSH 命令行已经运行。 - -## 11. SCons 构建关系 - -```text -SConstruct - ↓ -读取 rtconfig.py、rtconfig.h 和 Kconfig 配置 - ↓ -调用 tools/building.py - ↓ -收集 BSP、内核、驱动和组件源文件 - ↓ -调用 arm-none-eabi-gcc - ↓ -生成 ELF、BIN 和 MAP 文件 -``` - -进入 Spark-1 示例工程并编译: - -```bash -source /Users/gatesbil/Documents/Project/stm32/rtthread-mac-env.sh -spark_project 01_kernel -scons -j4 -``` - -只有被工程配置启用的 `RT_USING_*` 组件才会被加入编译。例如启用 FinSH 后,工程才会编译命令行相关源码并提供 `msh` 命令。 - -## 12. 作业提交内容 - -```text -1. 完成 Git 用户信息和 Gitee SSH 配置。 -2. 创建个人分支并提交 STM32F407 Spark-1 作业文件。 -3. 说明 git add、git commit、git push 和 Pull Request 的使用流程。 -4. 说明 RT-Thread、BSP、components、libcpu 和 tools 目录的作用。 -5. 说明 STM32F407 的 Cortex-M4 底层代码位置。 -6. 说明 FinSH、msh 和 SCons 构建流程。 -``` - diff --git "a/2026/\347\254\2542\347\273\204/\346\235\234\351\251\254\350\276\260/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2542\347\273\204/\346\235\234\351\251\254\350\276\260/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 5bce0c58c144c73b05636d1007ca4694b3ca4d05..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\234\351\251\254\350\276\260/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,357 +0,0 @@ -# 第一天作业:STM32F407 Spark-1 运行 RT-Thread - -## 1. 开发板和工程信息 - -```text -开发板:RT-Thread Spark-1 -MCU:STM32F407ZGT6 -BSP:stm32f407-rt-spark -开发方式:STM32CubeMX + RT-Thread 官方 SDK + GCC + SCons -系统:macOS Apple Silicon -工程目录:/Users/gatesbil/Documents/Project/stm32 -``` - -官方 BSP: - -```text -https://github.com/RT-Thread/rt-thread/tree/master/bsp/stm32/stm32f407-rt-spark -``` - -官方 Spark-1 SDK: - -```text -https://github.com/RT-Thread-Studio/sdk-bsp-stm32f407-spark -``` - -## 2. 安装编译和下载工具 - -检查 Homebrew、ARM GCC、OpenOCD 和 ST-Link: - -```bash -brew --version -arm-none-eabi-gcc --version -openocd --version -st-info --version -``` - -安装工具: - -```bash -brew install arm-none-eabi-gcc openocd stlink -``` - -创建工程目录: - -```bash -mkdir -p /Users/gatesbil/Documents/Project/stm32 -``` - -## 3. 配置 Python 和 SCons - -创建 Python 虚拟环境: - -```bash -python3 -m venv /Users/gatesbil/Documents/Project/stm32/.venv -``` - -激活虚拟环境: - -```bash -source /Users/gatesbil/Documents/Project/stm32/.venv/bin/activate -``` - -安装构建依赖: - -```bash -python -m pip install scons kconfiglib requests psutil tqdm -``` - -检查 SCons: - -```bash -scons --version -``` - -## 4. 下载官方 SDK - -下载 Spark-1 官方 SDK: - -```bash -git clone \ - https://github.com/RT-Thread-Studio/sdk-bsp-stm32f407-spark.git \ - /Users/gatesbil/Documents/Project/stm32/sdk-bsp-stm32f407-spark -``` - -进入 SDK 目录: - -```bash -cd /Users/gatesbil/Documents/Project/stm32/sdk-bsp-stm32f407-spark -ls -``` - -主要目录: - -```text -rt-thread/ RT-Thread 内核源码 -libraries/ STM32 HAL、CMSIS 和硬件驱动 -projects/ SDK 示例工程 -tools/ SDK 构建工具 -docs/ SDK 文档 -``` - -## 5. 加载开发环境 - -加载统一环境脚本: - -```bash -source /Users/gatesbil/Documents/Project/stm32/rtthread-mac-env.sh -``` - -查看 SDK 和 SCons 路径: - -```bash -echo "$SPARK_SDK_ROOT" -command -v scons -``` - -预期 SCons 路径: - -```text -/Users/gatesbil/Documents/Project/stm32/.venv/bin/scons -``` - -## 6. 配置 STM32CubeMX - -打开 SDK 中的 CubeMX 配置: - -```text -/Users/gatesbil/Documents/Project/stm32/sdk-bsp-stm32f407-spark/projects/01_kernel/board/CubeMX_Config/CubeMX_Config.ioc -``` - -MCU 选择: - -```text -STM32F407ZGT6 -``` - -或选择对应的: - -```text -STM32F407ZGTx -``` - -确认以下配置: - -```text -RCC 和系统时钟 -GPIO -USART -SPI -I2C -SDIO -USB -RTC -开发板上的其他外设 -``` - -安装 STM32F4 固件包: - -```text -Help → Manage embedded software packages → STM32Cube FW_F4 -``` - -在线下载失败时: - -```text -From Local... → 选择 STM32CubeF4 ZIP 文件 -``` - -CubeMX 只用于生成时钟、GPIO、外设和 HAL 配置。RT-Thread BSP 已经提供系统入口和 SysTick,不能把 CubeMX 普通工程中的 `main.c`、`stm32f4xx_it.c` 和重复的 `SysTick_Handler` 再次加入 RT-Thread 工程。 - -## 7. 编写 Hello World 程序 - -进入示例工程: - -```bash -source /Users/gatesbil/Documents/Project/stm32/rtthread-mac-env.sh -spark_project 01_kernel -``` - -应用程序入口文件: - -```text -applications/main.c -``` - -示例代码: - -```c -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG - -#include - -int main(void) -{ - int count = 1; - - rt_kprintf("hello RSOC2026!\n"); - - while (count++) - { - LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - - return RT_EOK; -} -``` - -代码含义: - -```text -rt_kprintf:通过 RT-Thread 控制台输出字符串 -LOG_D:输出 Debug 级别日志 -rt_thread_mdelay:让当前线程延时,单位为毫秒 -``` - -## 8. 编译工程 - -查看当前工程目录: - -```bash -pwd -``` - -开始编译: - -```bash -scons -j4 -``` - -查看生成文件: - -```bash -find . -maxdepth 2 -type f \( -name "*.elf" -o -name "*.bin" -o -name "*.map" \) -``` - -主要输出文件: - -```text -rt-thread.elf 用于调试和 OpenOCD 下载 -rtthread.bin 用于烧录的二进制固件 -rt-thread.map 链接地址和内存布局信息 -``` - -查看固件大小: - -```bash -arm-none-eabi-size rt-thread.elf -``` - -## 9. 检查开发板和下载固件 - -连接开发板 USB 数据线并给开发板上电: - -```bash -st-info --probe -``` - -使用 OpenOCD 下载 ELF 文件: - -```bash -openocd \ - -f interface/stlink.cfg \ - -f target/stm32f4x.cfg \ - -c "program rt-thread.elf verify reset exit" -``` - -命令含义: - -```text --f interface/stlink.cfg:使用 ST-Link 下载器配置 --f target/stm32f4x.cfg:使用 STM32F4 芯片配置 -program:烧录固件 -verify:烧录后校验 -reset:复位目标芯片 -exit:完成后退出 OpenOCD -``` - -## 10. 串口查看运行结果 - -查看 macOS 串口设备: - -```bash -ls /dev/cu.* -``` - -打开开发板对应的串口: - -```bash -screen /dev/cu.usbserial-XXXX 115200 -``` - -串口参数: - -```text -波特率:115200 -数据位:8 -停止位:1 -校验位:无 -流控:无 -``` - -复位开发板后,应看到: - -```text - \ | / -- RT - Thread Operating System - / | \ -hello RSOC2026! -[D/main] Hello RT-Thread! -msh > -``` - -## 11. 验证 msh 命令 - -查看帮助: - -```text -msh > help -``` - -查看线程和内存: - -```text -msh > list_thread -msh > free -``` - -查看 RT-Thread 内核对象: - -```text -msh > list_sem -msh > list_event -msh > list_mutex -msh > list_mailbox -msh > list_msgqueue -msh > list_mempool -msh > list_timer -``` - -`free` 命令用于查看系统内存使用情况,`list_thread` 用于查看当前 RT-Thread 线程状态。 - -## 12. 作业完成内容 - -```text -1. 准备 STM32F407 Spark-1 开发板和官方 RT-Thread SDK。 -2. 使用 STM32CubeMX 检查 MCU、时钟、GPIO 和串口配置。 -3. 使用 GCC 和 SCons 编译 RT-Thread 工程。 -4. 使用 ST-Link 和 OpenOCD 下载程序。 -5. 通过串口看到 RT-Thread 启动信息和 Hello World 日志。 -6. 通过 msh 执行 help、list_thread、free 等命令。 -7. 生成 ELF、BIN 和 MAP 文件。 -``` - diff --git "a/2026/\347\254\2542\347\273\204/\346\235\234\351\251\254\350\276\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\346\235\234\351\251\254\350\276\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 617bf3c5b3accf6090505e578dbcebfd611fde53..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\235\234\351\251\254\350\276\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,682 +0,0 @@ -# RT-Thread Spark-1 配置过程记录 - -## 1. 确认开发板和 MCU - -```text -开发板:RT-Thread Spark-1(星火一号) -MCU:STM32F407ZGT6 -BSP:stm32f407-rt-spark -``` - -官方 BSP: - -```text -https://github.com/RT-Thread/rt-thread/tree/master/bsp/stm32/stm32f407-rt-spark -``` - -官方 SDK: - -```text -https://github.com/RT-Thread-Studio/sdk-bsp-stm32f407-spark -``` - -## 2. 安装 macOS 工具 - -检查 Homebrew: - -```bash -brew --version -``` - -检查 ARM GCC: - -```bash -arm-none-eabi-gcc --version -``` - -检查 OpenOCD: - -```bash -openocd --version -``` - -检查 ST-LINK 工具: - -```bash -st-info --version -``` - -检查 Python: - -```bash -python3 --version -``` - -安装 ARM GCC、OpenOCD 和 ST-LINK 工具: - -```bash -brew install arm-none-eabi-gcc openocd stlink -``` - -本机 ARM GCC 路径: - -```text -/opt/homebrew/bin/arm-none-eabi-gcc -``` - -## 3. 创建 Python 虚拟环境 - -创建目录: - -```bash -mkdir -p /Users/gatesbil/Documents/Project/stm32 -``` - -创建虚拟环境: - -```bash -python3 -m venv /Users/gatesbil/Documents/Project/stm32/.venv -``` - -激活虚拟环境: - -```bash -source /Users/gatesbil/Documents/Project/stm32/.venv/bin/activate -``` - -安装 SCons 和相关 Python 依赖: - -```bash -python -m pip install scons kconfiglib requests psutil tqdm -``` - -检查 SCons: - -```bash -scons --version -``` - -## 4. 下载官方 SDK - -下载 SDK 到指定目录: - -```bash -git clone \ - https://github.com/RT-Thread-Studio/sdk-bsp-stm32f407-spark.git \ - /Users/gatesbil/Documents/Project/stm32/sdk-bsp-stm32f407-spark -``` - -SDK 目录: - -```text -/Users/gatesbil/Documents/Project/stm32/sdk-bsp-stm32f407-spark -``` - -查看 SDK 内容: - -```bash -cd /Users/gatesbil/Documents/Project/stm32/sdk-bsp-stm32f407-spark -ls -``` - -主要目录: - -```text -rt-thread/ RT-Thread 源码 -libraries/ STM32 HAL 和 RT-Thread HAL 驱动 -projects/ 示例工程 -tools/ SDK 工具 -docs/ SDK 文档 -``` - -## 5. 创建 macOS 环境脚本 - -创建文件: - -```text -/Users/gatesbil/Documents/Project/stm32/rtthread-mac-env.sh -``` - -脚本内容: - -```bash -#!/usr/bin/env bash - -RTT_LOCAL_ROOT="/Users/gatesbil/Documents/Project/stm32" -export RTT_LOCAL_ROOT -export SPARK_SDK_ROOT="$RTT_LOCAL_ROOT/sdk-bsp-stm32f407-spark" -export RTT_CC="gcc" -export RTT_EXEC_PATH="/opt/homebrew/bin" -export PATH="$RTT_LOCAL_ROOT/.venv/bin:/opt/homebrew/bin:$PATH" - -spark_project() { - if [ "$#" -ne 1 ]; then - echo "usage: spark_project " >&2 - return 2 - fi - - cd "$SPARK_SDK_ROOT/projects/$1" -} - -echo "RT-Thread environment loaded" -echo "SPARK_SDK_ROOT=$SPARK_SDK_ROOT" -echo "RTT_EXEC_PATH=$RTT_EXEC_PATH" -``` - -添加执行权限: - -```bash -chmod +x /Users/gatesbil/Documents/Project/stm32/rtthread-mac-env.sh -``` - -加载环境: - -```bash -source /Users/gatesbil/Documents/Project/stm32/rtthread-mac-env.sh -``` - -查看 SDK 路径: - -```bash -echo "$SPARK_SDK_ROOT" -``` - -查看 SCons 路径: - -```bash -command -v scons -``` - -预期路径: - -```text -/Users/gatesbil/Documents/Project/stm32/.venv/bin/scons -``` - -## 6. 为 SDK 示例创建 macOS 符号链接 - -官方 SDK 中部分工程使用 Windows 的 `mklinks.bat`。macOS 下手动创建 `rt-thread` 和 `libraries` 符号链接: - -```bash -cd /Users/gatesbil/Documents/Project/stm32/sdk-bsp-stm32f407-spark/projects -``` - -```bash -for p in */; do - [ -e "$p/rt-thread" ] || ln -s ../../rt-thread "$p/rt-thread" - [ -e "$p/libraries" ] || ln -s ../../libraries "$p/libraries" -done -``` - -检查某个示例工程: - -```bash -cd /Users/gatesbil/Documents/Project/stm32/sdk-bsp-stm32f407-spark/projects/01_kernel -ls -l rt-thread libraries -``` - -预期结果: - -```text -rt-thread -> ../../rt-thread -libraries -> ../../libraries -``` - -## 7. 配置 STM32CubeMX - -下载 STM32CubeMX: - -```text -https://www.st.com/en/development-tools/stm32cubemx.html -``` - -Apple Silicon 选择: - -```text -macOS Arm64 -``` - -安装器文件: - -```text -SetupSTM32CubeMX-版本号.app -``` - -CubeMX 中选择 MCU: - -```text -STM32F407ZGT6 -``` - -或选择对应的: - -```text -STM32F407ZGTx -``` - -打开 SDK 中已有的 CubeMX 配置: - -```text -/Users/gatesbil/Documents/Project/stm32/sdk-bsp-stm32f407-spark/projects/01_kernel/board/CubeMX_Config/CubeMX_Config.ioc -``` - -CubeMX 配置内容: - -```text -RCC/时钟 -GPIO -USART -SPI -I2C -SDIO -USB -RTC -其他板载外设 -``` - -安装 STM32F4 软件包: - -```text -Help → Manage embedded software packages -``` - -选择: - -```text -STM32Cube FW_F4 -``` - -在线下载失败时: - -```text -From Local... → 选择 STM32CubeF4 的 ZIP 文件 -``` - -选择本地 ZIP 时不要提前解压。 - -## 8. 处理 CubeMX 的 `crdb_full.zip` 报错 - -错误信息: - -```text -Error downloading the following files: -crdb_full.zip -Problem during download -``` - -含义: - -```text -CubeMX 在线刷新 MCU/开发板数据库失败 -``` - -不是以下问题: - -```text -不是 STM32F407 不支持 -不是 Spark-1 BSP 选错 -不是 STM32F4 软件包选错 -``` - -检查 CubeMX 版本: - -```text -Help → About -``` - -升级 CubeMX: - -```text -Help → Check for Updates -``` - -如果在线升级失败,手动下载 macOS Arm64 版本并安装。 - -临时关闭启动自动刷新: - -```text -Help → Connection and Updates -→ No Auto-Refresh at Application start -``` - -## 9. 编译 `01_kernel` - -加载环境: - -```bash -source /Users/gatesbil/Documents/Project/stm32/rtthread-mac-env.sh -``` - -进入工程: - -```bash -spark_project 01_kernel -``` - -查看当前目录: - -```bash -pwd -``` - -开始编译: - -```bash -scons -j4 -``` - -也可以使用绝对路径: - -```bash -cd /Users/gatesbil/Documents/Project/stm32/sdk-bsp-stm32f407-spark/projects/01_kernel -scons -j4 -``` - -编译输出: - -```text -rt-thread.elf -rtthread.bin -rt-thread.map -``` - -查看固件大小: - -```bash -arm-none-eabi-size rt-thread.elf -``` - -## 10. 编译 LED 示例 - -进入 LED 示例: - -```bash -source /Users/gatesbil/Documents/Project/stm32/rtthread-mac-env.sh -spark_project 02_basic_led_blink -``` - -编译: - -```bash -scons -j4 -``` - -Spark-1 LED 引脚: - -```c -#define PIN_LED_B GET_PIN(F, 11) -#define PIN_LED_R GET_PIN(F, 12) -``` - -红色 LED 闪烁代码: - -```c -#include -#include -#include - -#define PIN_LED_R GET_PIN(F, 12) - -int main(void) -{ - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - - while (1) - { - rt_pin_write(PIN_LED_R, PIN_LOW); - rt_thread_mdelay(500); - rt_pin_write(PIN_LED_R, PIN_HIGH); - rt_thread_mdelay(500); - } -} -``` - -## 11. CubeMX 生成文件的处理 - -RT-Thread 工程中不要直接加入 CubeMX 普通工程的全部文件。 - -需要避免重复的文件和函数: - -```text -main.c -stm32f4xx_it.c -SysTick_Handler -``` - -原因: - -```text -RT-Thread BSP 已经负责系统入口、启动流程和 SysTick -CubeMX 主要用于生成时钟、GPIO、外设和 HAL MSP 配置 -applications/main.c 用于编写 RT-Thread 应用 -``` - -修改 `.ioc` 后,检查生成的文件是否被当前工程的 `SConscript` 加入,避免出现重复定义。 - -## 12. 连接开发板并检查 ST-LINK - -连接 USB 数据线,打开开发板电源。 - -执行: - -```bash -st-info --probe -``` - -未连接或未识别时: - -```text -Found 0 stlink programmers -``` - -检查项目: - -```text -USB 线是否支持数据传输 -开发板是否上电 -USB 是否插在正确接口 -板载 ST-LINK 是否正常 -``` - -## 13. 使用 OpenOCD 下载 - -进入生成 `rt-thread.elf` 的工程目录: - -```bash -cd /Users/gatesbil/Documents/Project/stm32/sdk-bsp-stm32f407-spark/projects/01_kernel -``` - -解析 OpenOCD 配置: - -```bash -openocd \ - -f interface/stlink.cfg \ - -f target/stm32f4x.cfg \ - -c "shutdown" -``` - -下载并校验: - -```bash -openocd \ - -f interface/stlink.cfg \ - -f target/stm32f4x.cfg \ - -c "program rt-thread.elf verify reset exit" -``` - -## 14. 使用串口查看 RT-Thread 输出 - -查看 macOS 串口: - -```bash -ls /dev/cu.* -``` - -打开实际的 USB 串口: - -```bash -screen /dev/cu.usbserial-XXXX 115200 -``` - -串口参数: - -```text -115200-8-1-N -``` - -复位开发板后,预期输出 RT-Thread 启动信息: - -```text - \ | / -- RT - Thread Operating System - / | \ -msh > -``` - -退出 `screen`: - -```text -Ctrl-A,然后按 Ctrl-\\ -``` - -## 15. 直接编译主线 BSP 时的文件关系 - -主线 BSP 地址: - -```text -https://github.com/RT-Thread/rt-thread/tree/master/bsp/stm32/stm32f407-rt-spark -``` - -不能只保留目标 BSP 目录,至少需要保留: - -```text -rt-thread/ -├── src/ -├── include/ -├── components/ -├── tools/ -├── packages/ -└── bsp/stm32/stm32f407-rt-spark/ -``` - -`bsp` 下其他开发板目录不是目标 BSP 的直接编译依赖,可以不使用;但 RT-Thread 根目录的内核和构建工具不能删除。 - -主线 BSP 构建脚本检查的依赖目录: - -```text -packages/CMSIS-Core-latest/ -packages/stm32f4_cmsis_driver-latest/ -packages/stm32f4_hal_driver-latest/ -``` - -RT-Thread Env 中更新依赖: - -```bash -pkgs --update -``` - -进入主线 BSP: - -```bash -cd rt-thread/bsp/stm32/stm32f407-rt-spark -``` - -编译: - -```bash -scons -j4 -``` - -macOS 下如果没有 `pkgs` 命令,使用已经整理好 `libraries` 的官方 SDK。 - -## 16. 迁移开发环境 - -原开发环境位于 Codex 工作目录: - -```text -/Users/gatesbil/Documents/Codex/2026-08-17/wo/ -``` - -迁移到: - -```text -/Users/gatesbil/Documents/Project/stm32/ -``` - -迁移后的目录: - -```text -/Users/gatesbil/Documents/Project/stm32/ -├── .venv/ -├── rtthread-mac-env.sh -└── sdk-bsp-stm32f407-spark/ -``` - -迁移 Python 虚拟环境后,原来的 `scons` 启动脚本仍然指向旧路径: - -```text -/Users/gatesbil/Documents/Codex/2026-08-17/wo/.venv/bin/python -``` - -处理方法: - -```bash -mv /Users/gatesbil/Documents/Project/stm32/.venv \ - /Users/gatesbil/Documents/Project/stm32/.venv-migrated-old -``` - -重新创建虚拟环境: - -```bash -/opt/homebrew/opt/python@3.14/bin/python3.14 -m venv \ - /Users/gatesbil/Documents/Project/stm32/.venv -``` - -重新安装依赖: - -```bash -/Users/gatesbil/Documents/Project/stm32/.venv/bin/python -m pip install \ - scons kconfiglib requests psutil tqdm -``` - -重新检查: - -```bash -source /Users/gatesbil/Documents/Project/stm32/rtthread-mac-env.sh -command -v scons -scons --version -``` - -## 17. 常用命令 - -```bash -# 加载环境 -source /Users/gatesbil/Documents/Project/stm32/rtthread-mac-env.sh - -# 进入内核示例 -spark_project 01_kernel - -# 进入 LED 示例 -spark_project 02_basic_led_blink - -# 编译 -scons -j4 - -# 检查 GCC -arm-none-eabi-gcc --version - -# 检查 ST-LINK -st-info --probe - -# 查看串口 -ls /dev/cu.* - -# 打开串口 -screen /dev/cu.usbserial-XXXX 115200 - -# 下载固件 -openocd \ - -f interface/stlink.cfg \ - -f target/stm32f4x.cfg \ - -c "program rt-thread.elf verify reset exit" -``` diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task1.png" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task1.png" deleted file mode 100644 index 1d3b72970d8b80a620293b0b9b1b30c9349bbd6f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task1.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task2.png" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task2.png" deleted file mode 100644 index 4c259276d43d76c581117894ccf70702fc0871ad..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task2.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task3.png" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task3.png" deleted file mode 100644 index d8ba58d82ef9bb28810f331d728c4d4bccaac4d7..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task3.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task4.png" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task4.png" deleted file mode 100644 index d7a77ad8dbb8cd60465c7c95f6213460a7c16b04..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/day3_task4.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/flowchart_LR.png" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/flowchart_LR.png" deleted file mode 100644 index 725e751379cfc29b75fdce3ce87b4a61540b3070..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/flowchart_LR.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/image-20260817214132378.png" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/image-20260817214132378.png" deleted file mode 100644 index 2f1f16d04e67d41b505bcac98f0f94865348da09..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/image-20260817214132378.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/printf_stat.png" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/printf_stat.png" deleted file mode 100644 index 0db44f7ad5f13cfdef479a7c8258f2fc02a258a6..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/printf_stat.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/printf_thread.png" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/printf_thread.png" deleted file mode 100644 index bc15c685c271a9995a7b7fd335ba9f694ebd848a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/printf_thread.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/thread_dump.png" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/thread_dump.png" deleted file mode 100644 index 86ca79f1171ed9dff9f4a134ee917de139630042..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/figures/thread_dump.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 5828ca34289162f398751455a57ee51b3e10b007..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,96 +0,0 @@ -# 第一天作业 -## Task1:新建 qemu-a9 工程并运行 RT-Thread - -> **任务目标**:使用 RT-Thread Studio 新建一个 QEMU(vexpress-a9)工程,完成编译并在模拟器中成功运行 RT-Thread。 -> -> **实验环境**:Windows + RT-Thread Studio + QEMU 4.2.0.4(vexpress-a9 平台) - ---- - -## 一、新建工程 - -在 RT-Thread Studio 中选择 **QEMU vexpress-a9** 平台,新建名为 `hello` 的工程: - -![新建qemu-a9工程](../figures/image-20260817214132378.png) - -工程创建完成后,IDE 会自动生成 RT-Thread 完整源码目录(`applications`、`drivers`、`rt-thread` 等),入口为 `applications/main.c`。 - -## 二、编译工程 - -点击工具栏的构建按钮,使用 `arm-none-eabi-gcc` 交叉编译工具链进行编译: - -```text -21:46:35 **** Incremental Build of configuration Debug for project hello **** -make -j24 all -arm-none-eabi-size --format=berkeley "rtthread.elf" - text data bss dec hex filename - 597909 5276 88148 691333 a8c85 rtthread.elf - - Used Size(B) Used Size(KB) -Flash: 603185 B 589.05 KB -RAM: 93424 B 91.23 KB - -21:46:36 Build Finished. 0 errors, 0 warnings. (took 1s.52ms) -``` - -编译结果 **0 errors, 0 warnings**,产物 `rtthread.elf` / `rtthread.bin` 生成在 `Debug` 目录下。 - -| 项目 | text | data | bss | Flash 占用 | RAM 占用 | -| ---- | ---- | ---- | ---- | ---------- | -------- | -| 大小 | 597909 B | 5276 B | 88148 B | 589.05 KB | 91.23 KB | - -## 三、运行验证 - -在工程目录下执行 QEMU 命令启动模拟器(`-M vexpress-a9` 指定开发板型号,`--kernel` 指定编译出的固件,`-nographic` 以无图形界面方式运行): - -```text -C:\RT-ThreadStudio\workspace\hello>C:/RT-ThreadStudio/repo/Extract/Debugger_Support_Packages/RealThread/QEMU/4.2.0.4/qemu-system-arm.exe -M vexpress-a9 --kernel Debug/rtthread.bin -nographic -show-cursor - - \ | / -- RT - Thread Operating System - / | \ 4.0.2 build Aug 17 2026 - 2006 - 2019 Copyright by rt-thread team -lwIP-2.0.2 initialized! -[I/sal.skt] Socket Abstraction Layer initialize success. -hello rt-thread -msh /> -``` - -从启动日志可以确认系统运行成功: - -- RT-Thread 内核版本 **4.0.2**,构建日期 2026-08-17 -- lwIP-2.0.2 协议栈初始化成功,Socket 抽象层(SAL)加载完成 -- 打印 `hello rt-thread`(`main.c` 中 `main` 线程的输出) -- 出现 `msh />` 提示符,可输入 `ps`、`free` 等 msh 命令进行交互 - - -# Task2:markdown 语法学习 - -# 一级标题 - -## 二级标题 - -### 三级标题 - -.... - -代码段 - -```c -#include - -int main() -{ - return 0; -} -``` - -表格 - -| t1 | t2 | -| ---- | ---- | -| 1 | 2 | - -`高亮语法` - -**加粗字体** diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 9c98141b1be2aece6f77e3a67ea479379509c27f..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,85 +0,0 @@ -# RT-Thread 线程间同步与通信课后作业 - -## 一、理论总结 - -### 1. 信号量(Semaphore) - -- 维护一个计数值,`rt_sem_take` 成功时计数减 1,`rt_sem_release` 时计数加 1;计数为 0 时 `take` 阻塞等待。 -- 计数信号量适合表示"可用资源的数量"(如车位个数),允许多个线程同时获得资源,最多不超过初始值。 -- 互斥量本质是二值(0/1)且**有所有权概念**(只能由持有者释放),用于保护"同一时刻只有一个线程"访问的临界资源;而车位是"多个相同资源可同时被占用",因此用计数信号量更合适。 - -### 2. 互斥量(Mutex) - -- 同一时刻只能被一个线程持有,未持有者 `rt_mutex_take` 会阻塞;支持**优先级继承**,可缓解优先级反转。 -- 临界区 = "检查余票 + 减票 + 记录 + 打印",必须整体放在同一把互斥量内,否则会产生竞态。 - -### 3. 事件集(Event Set) - -- 用 32 位整数的每一位表示一个事件,`rt_event_send` 置位、`rt_event_recv` 等待。 -- `RT_EVENT_FLAG_AND`:所有指定位都置 1 才返回;`RT_EVENT_FLAG_OR`:任一位置 1 即返回;可叠加 `RT_EVENT_FLAG_CLEAR` 在满足条件时自动清零。 -- 事件集是"状态"而非"计数",适合多条件同时满足的等待场景;超时返回时已置位不会被清除,可再次等待。 - -### 4. 消息队列(Message Queue) - -- 以"消息"为单位传递数据,每个消息大小固定;`rt_mq_send` 写入、`rt_mq_recv` 读取,队列自带互斥与阻塞/超时机制。 -- 适合生产者-消费者模型,天然实现线程间数据传递与解耦。 - -## 二、实验报告 - -所有练习均:至少两个线程、线程与 IPC 对象创建结果全部检查、通过 MSH 命令启动、任务结束后主动释放资源(可重复执行)。 - -### 练习一:停车场车位管理(计数信号量)`parking_demo` - -**实现说明**:创建初始值为 3 的信号量作为车位;5 个车辆线程通过参数区分编号和错峰延时;每辆车 `rt_sem_take(2s 超时)` 申请车位,超时打印等待信息后重试;进入后停留 4s 再 `rt_sem_release` 释放。信号量保证任意时刻场内车辆 ≤ 3。 - -**运行结果**: - -![练习一运行结果](../figures/day3_task1.png) - -**思考题:为什么用计数信号量而不是互斥量?** - -互斥量是二值且带所有权(只能由持有者释放),表示"排他访问某个共享资源";而停车场车位是**多个完全相同、可同时被占用的资源**,用计数信号量正好把"剩余车位数量"作为计数,5 辆车可以同时竞争,最多 3 辆进入,每辆车释放时也无需是"同一个人"(所有权无关)。语义完全匹配,互斥量无法表达"最多 3 个同时进入"。 - -### 练习二:多窗口售票系统(互斥量)`ticket_demo` - -**实现说明**:全局 `ticket_count = 20`;窗口 A/B 两个线程,循环内用同一把互斥量保护"检查余票 → 减票 → 记录票号 → 打印"完整过程;售完(余票为 0)后线程退出。最终 20 张票全部售出、票号 20~1 不重复、余票不为负。 - -**运行结果**: - -![练习二运行结果](../figures/day3_task2.png) - -> 运行情况说明:本次运行中 20 张票全部由窗口 A 依次售出,窗口 B 未抢到售票机会。原因是两个窗口同优先级,窗口 A 的售票循环是"热循环":释放互斥量后立即再次获取,尚未被调度到的窗口 B 一直抢不到锁。这属于调度现象而非错误——互斥量保护依然生效:20 张票全部售出、票号 20~1 无重复、剩余票数为 0。 - -**对比实验**:命令 `ticket_demo_race` 不加互斥量,并在"检查余票"与"减票"之间插入 `mdelay(5)` 放大竞争窗口。运行可见两个窗口同时通过检查、重复售出同一票号、余票出现负数,验证了竞态的存在。 - -**思考题:为什么只给 `ticket_count--` 加锁仍可能不安全?** - -"检查余票"(`ticket_count > 0`)和"减票"(`ticket_count--`)是两个独立的非原子操作。若只给 `--` 加锁,两个线程可能**同时通过检查**(都读到还有 1 张),随后各自减 1,导致重复票号或余票变为 -1。必须把"检查 + 修改 + 记录 + 打印"作为**一个完整临界区**用同一把互斥量保护。 - -### 练习三:系统启动条件检查(事件集)`startup_demo` - -**实现说明**:三个初始化线程分别延时 1s/2s/5s 后 `rt_event_send` 对应事件位;主业务线程先用 AND 模式等待三事件,超时 3s 时(storage 尚未就绪)逐个轮询打印"未就绪模块",再继续等待;全部就绪后打印启动信息。存储模块 5s > 3s 超时,故必然触发一次超时路径。 - -**运行结果**: - -![练习三运行结果](../figures/day3_task3.png) - -**思考题:用三个独立信号量实现与事件集相比有什么区别?** - -- 信号量是"计数/消费"语义,主线程需要依次 `take` 三次,代码繁琐;事件集一次 AND 判断即可。 -- 超时定位:三个信号量超时只能笼统知道"有一个没到";事件集可逐个查询位状态,精确定位未就绪模块。 -- 事件集是"状态"(send 一次永久有效,多个等待者共享);信号量是"计数"(take 会消费),表达"多条件同时就绪"这类同步关系时事件集更自然、开销更小。 - -### 练习四:快递分拣中心(消息队列)`package_demo` - -**实现说明**:创建容量为 5 条的消息队列(`sizeof(struct package_msg)`);两个发件线程各产生 5 条快递(id 分别为 1001~1005、2001~2005,region 轮流为 1/2/3,weight 按 id 计算);`rt_mq_send` 返回非 `RT_EOK`(队列满)时记录 `pkg_send_failed` 并延时重发,不崩溃;两个发件线程发完后各发一条 `region = 0xFF` 的结束消息(确保必达);分拣线程 `rt_mq_recv` 循环接收,按 region 分类统计,收到 2 条结束消息后打印统计结果并退出。 - -**运行结果**: - -![练习四运行结果](../figures/day3_task4.png) - -> 本次实际运行 `send failed: 1`:有一次队列已满,发件线程记录失败后延时重发成功,10 条快递全部送达,正好验证了"队列满时记录失败次数、不崩溃"的要求。 - -## 三、重复执行说明 - -每个演示命令都有独立的"运行中"标志;重复执行且任务仍在运行时会打印提示并拒绝启动。任务结束后,演示函数会等所有工作线程退出(每个线程结束时释放一次完成信号量),然后**主动删除 IPC 对象并复位标志**,因此命令可以反复执行且不会因同名线程/IPC 对象残留而异常。工作线程全部使用 `rt_thread_create` 动态创建,线程函数返回后由系统自动回收。 diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/main.c" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/main.c" deleted file mode 100644 index 36278d75687edde5e2fbb94e08023dc9d303a1d8..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/main.c" +++ /dev/null @@ -1,533 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 RT-Thread first version - */ - -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -#define THREAD_PRIORITY 16 -#define THREAD_STACK_SIZE 1024 -#define THREAD_TIMESLICE 5 - -/* ==================== 练习一:停车场车位管理(计数信号量) ==================== */ -#define PARKING_SPACES 3 -#define CAR_NUM 5 - -static rt_sem_t parkings = RT_NULL; -static rt_sem_t parking_done = RT_NULL; -static rt_uint8_t parking_running = 0; - -struct car_param -{ - int id; - int enter_delay; -}; - -static void car_entry(void *parameter) -{ - struct car_param *car = (struct car_param *)parameter; - - rt_thread_mdelay(car->enter_delay); - - while (1) - { - if (rt_sem_take(parkings, rt_tick_from_millisecond(2000)) != RT_EOK) - { - rt_kprintf("car%d waiting for a space\n", car->id); - continue; - } - - rt_kprintf("car%d entered, used spaces: %d\n", car->id, - PARKING_SPACES - parkings->value); - rt_thread_mdelay(4000); - rt_kprintf("car%d left\n", car->id); - rt_sem_release(parkings); - break; - } - - rt_sem_release(parking_done); -} - -int parking_demo(void) -{ - int i, created = 0; - struct car_param params[CAR_NUM] = { - {1, 0}, {2, 500}, {3, 1000}, {4, 1200}, {5, 1300}, - }; - const char *car_names[CAR_NUM] = {"car1", "car2", "car3", "car4", "car5"}; - - if (parking_running) - { - rt_kprintf("parking demo is running\n"); - return -RT_EBUSY; - } - - parkings = rt_sem_create("p_sem", PARKING_SPACES, RT_IPC_FLAG_PRIO); - if (parkings == RT_NULL) - { - rt_kprintf("create parking semaphore failed\n"); - return -RT_ENOMEM; - } - parking_done = rt_sem_create("p_done", 0, RT_IPC_FLAG_PRIO); - if (parking_done == RT_NULL) - { - rt_kprintf("create parking done semaphore failed\n"); - rt_sem_delete(parkings); - parkings = RT_NULL; - return -RT_ENOMEM; - } - - parking_running = 1; - - for (i = 0; i < CAR_NUM; i++) - { - rt_thread_t tid = rt_thread_create(car_names[i], car_entry, ¶ms[i], - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid == RT_NULL) - { - rt_kprintf("create %s failed\n", car_names[i]); - continue; - } - rt_thread_startup(tid); - created++; - } - - /* 等待所有车辆进出完成后回收资源 */ - for (i = 0; i < created; i++) - rt_sem_take(parking_done, RT_WAITING_FOREVER); - - rt_sem_delete(parking_done); - rt_sem_delete(parkings); - parkings = RT_NULL; - parking_done = RT_NULL; - parking_running = 0; - - return RT_EOK; -} -MSH_CMD_EXPORT(parking_demo, parking demo with counting semaphore); - -/* ==================== 练习二:多窗口售票系统(互斥量) ==================== */ -#define TICKET_TOTAL 20 - -static rt_mutex_t ticket_mutex = RT_NULL; -static rt_sem_t ticket_done = RT_NULL; -static rt_uint8_t ticket_running = 0; -static int ticket_count = TICKET_TOTAL; - -static void window_entry(void *parameter) -{ - int win = (int)parameter; - - while (1) - { - rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); - - if (ticket_count <= 0) - { - rt_mutex_release(ticket_mutex); - break; - } - - ticket_count--; - rt_kprintf("window %c sold ticket %d\n", win, ticket_count + 1); - if (ticket_count == 0) - { - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: 0\n"); - } - - rt_mutex_release(ticket_mutex); - } - - rt_sem_release(ticket_done); -} - -int ticket_demo(void) -{ - int created = 0; - rt_thread_t tid; - - if (ticket_running) - { - rt_kprintf("ticket demo is running\n"); - return -RT_EBUSY; - } - - ticket_mutex = rt_mutex_create("t_mutex", RT_IPC_FLAG_PRIO); - if (ticket_mutex == RT_NULL) - { - rt_kprintf("create ticket mutex failed\n"); - return -RT_ENOMEM; - } - ticket_done = rt_sem_create("t_done", 0, RT_IPC_FLAG_PRIO); - if (ticket_done == RT_NULL) - { - rt_kprintf("create ticket done semaphore failed\n"); - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - return -RT_ENOMEM; - } - - ticket_count = TICKET_TOTAL; - ticket_running = 1; - - tid = rt_thread_create("winA", window_entry, (void *)'A', - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid != RT_NULL) - { - rt_thread_startup(tid); - created++; - } - - tid = rt_thread_create("winB", window_entry, (void *)'B', - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid != RT_NULL) - { - rt_thread_startup(tid); - created++; - } - - /* 等待窗口线程售完退出 */ - while (created--) - rt_sem_take(ticket_done, RT_WAITING_FOREVER); - - rt_sem_delete(ticket_done); - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - ticket_done = RT_NULL; - ticket_running = 0; - - return RT_EOK; -} -MSH_CMD_EXPORT(ticket_demo, ticket demo with mutex); - -/* 对比实验:不加互斥量,检查与修改之间加入延时,制造竞争 */ -static int ticket_count_race = TICKET_TOTAL; - -static void window_race_entry(void *parameter) -{ - int win = (int)parameter; - - while (1) - { - if (ticket_count_race <= 0) /* 不加锁的“检查” */ - break; - - rt_thread_mdelay(5); /* 扩大竞争窗口 */ - - ticket_count_race--; /* 不加锁的“修改” */ - rt_kprintf("window %c sold ticket %d (race)\n", win, ticket_count_race + 1); - } -} - -int ticket_demo_race(void) -{ - rt_thread_t tid; - - ticket_count_race = TICKET_TOTAL; - - tid = rt_thread_create("raceA", window_race_entry, (void *)'A', - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid != RT_NULL) rt_thread_startup(tid); - - tid = rt_thread_create("raceB", window_race_entry, (void *)'B', - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid != RT_NULL) rt_thread_startup(tid); - - return RT_EOK; -} -MSH_CMD_EXPORT(ticket_demo_race, ticket demo WITHOUT mutex (race condition)); - -/* ==================== 练习三:系统启动条件检查(事件集) ==================== */ -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -static rt_event_t startup_events = RT_NULL; -static rt_sem_t startup_done = RT_NULL; -static rt_uint8_t startup_running = 0; - -static void net_init_entry(void *parameter) -{ - rt_thread_mdelay(1000); - rt_event_send(startup_events, EVENT_NET_READY); - rt_kprintf("network ready\n"); - rt_sem_release(startup_done); -} - -static void sensor_init_entry(void *parameter) -{ - rt_thread_mdelay(2000); - rt_event_send(startup_events, EVENT_SENSOR_READY); - rt_kprintf("sensor ready\n"); - rt_sem_release(startup_done); -} - -static void storage_init_entry(void *parameter) -{ - rt_thread_mdelay(5000); - rt_event_send(startup_events, EVENT_STORAGE_READY); - rt_kprintf("storage ready\n"); - rt_sem_release(startup_done); -} - -static void business_entry(void *parameter) -{ - rt_uint32_t recved = 0; - - /* 第一次:AND 模式等待三个模块,最多等 3 秒 */ - if (rt_event_recv(startup_events, EVENT_ALL_READY, RT_EVENT_FLAG_AND, - rt_tick_from_millisecond(3000), &recved) != RT_EOK) - { - rt_kprintf("wait modules timeout"); - /* 超时返回时 recved 不会被回填,逐个轮询判断谁还没就绪 */ - if (rt_event_recv(startup_events, EVENT_NET_READY, RT_EVENT_FLAG_AND, 0, &recved) != RT_EOK) - rt_kprintf(", network is not ready"); - if (rt_event_recv(startup_events, EVENT_SENSOR_READY, RT_EVENT_FLAG_AND, 0, &recved) != RT_EOK) - rt_kprintf(", sensor is not ready"); - if (rt_event_recv(startup_events, EVENT_STORAGE_READY, RT_EVENT_FLAG_AND, 0, &recved) != RT_EOK) - rt_kprintf(", storage is not ready"); - rt_kprintf("\n"); - - /* 继续等待全部就绪 */ - rt_event_recv(startup_events, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_WAITING_FOREVER, &recved); - } - - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - - rt_sem_release(startup_done); -} - -int startup_demo(void) -{ - int i, created = 0; - rt_thread_t tid; - - if (startup_running) - { - rt_kprintf("startup demo is running\n"); - return -RT_EBUSY; - } - - startup_events = rt_event_create("s_event", RT_IPC_FLAG_PRIO); - if (startup_events == RT_NULL) - { - rt_kprintf("create startup event failed\n"); - return -RT_ENOMEM; - } - startup_done = rt_sem_create("s_done", 0, RT_IPC_FLAG_PRIO); - if (startup_done == RT_NULL) - { - rt_kprintf("create startup done semaphore failed\n"); - rt_event_delete(startup_events); - startup_events = RT_NULL; - return -RT_ENOMEM; - } - - startup_running = 1; - - tid = rt_thread_create("net_init", net_init_entry, RT_NULL, - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid != RT_NULL) { rt_thread_startup(tid); created++; } - tid = rt_thread_create("sensor_init", sensor_init_entry, RT_NULL, - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid != RT_NULL) { rt_thread_startup(tid); created++; } - tid = rt_thread_create("storage_init", storage_init_entry, RT_NULL, - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid != RT_NULL) { rt_thread_startup(tid); created++; } - tid = rt_thread_create("business", business_entry, RT_NULL, - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid != RT_NULL) { rt_thread_startup(tid); created++; } - - for (i = 0; i < created; i++) - rt_sem_take(startup_done, RT_WAITING_FOREVER); - - rt_sem_delete(startup_done); - rt_event_delete(startup_events); - startup_events = RT_NULL; - startup_done = RT_NULL; - startup_running = 0; - - return RT_EOK; -} -MSH_CMD_EXPORT(startup_demo, startup demo with event set); - -/* ==================== 练习四:快递分拣中心(消息队列) ==================== */ -#define PKG_PER_SENDER 5 -#define PKG_END_REGION 0xFF - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -static rt_mq_t pkg_mq = RT_NULL; -static rt_sem_t pkg_done = RT_NULL; -static rt_uint8_t pkg_running = 0; -static rt_uint32_t pkg_send_failed = 0; - -static void sender_entry(void *parameter) -{ - int base = (int)parameter; - struct package_msg msg; - int i; - rt_err_t result; - - for (i = 0; i < PKG_PER_SENDER; i++) - { - msg.id = base + i + 1; - msg.region = (rt_uint8_t)(i % 3) + 1; - msg.weight = (rt_uint16_t)(800 + (msg.id % 500)); - - do - { - result = rt_mq_send(pkg_mq, &msg, sizeof(msg)); - if (result != RT_EOK) /* 队列满:记录失败并稍后重发 */ - { - pkg_send_failed++; - rt_thread_mdelay(10); - } - } while (result != RT_EOK); - } - - /* 结束消息:必须确保发送成功,分拣线程才能退出 */ - msg.id = 0; - msg.region = PKG_END_REGION; - msg.weight = 0; - do - { - result = rt_mq_send(pkg_mq, &msg, sizeof(msg)); - if (result != RT_EOK) - rt_thread_mdelay(10); - } while (result != RT_EOK); - - rt_sem_release(pkg_done); -} - -static void sorter_entry(void *parameter) -{ - struct package_msg msg; - int end_count = 0, total = 0; - int east = 0, south = 0, north = 0; - rt_uint32_t total_weight = 0; - - while (end_count < 2) /* 收到两个发件线程的结束消息后统计退出 */ - { - if (rt_mq_recv(pkg_mq, &msg, sizeof(msg), RT_WAITING_FOREVER) != RT_EOK) - continue; - - if (msg.region == PKG_END_REGION) - { - end_count++; - continue; - } - - total++; - total_weight += msg.weight; - switch (msg.region) - { - case 1: - east++; - rt_kprintf("package %d -> east, weight: %d g\n", msg.id, msg.weight); - break; - case 2: - south++; - rt_kprintf("package %d -> south, weight: %d g\n", msg.id, msg.weight); - break; - case 3: - north++; - rt_kprintf("package %d -> north, weight: %d g\n", msg.id, msg.weight); - break; - default: - rt_kprintf("package %d -> unknown, weight: %d g\n", msg.id, msg.weight); - break; - } - } - - rt_kprintf("total packages: %d\n", total); - rt_kprintf("east: %d, south: %d, north: %d\n", east, south, north); - rt_kprintf("total weight: %d g\n", total_weight); - rt_kprintf("send failed: %d\n", pkg_send_failed); - - rt_sem_release(pkg_done); -} - -int package_demo(void) -{ - int i, created = 0; - rt_thread_t tid; - - if (pkg_running) - { - rt_kprintf("package demo is running\n"); - return -RT_EBUSY; - } - - pkg_mq = rt_mq_create("pk_mq", sizeof(struct package_msg), 5, RT_IPC_FLAG_PRIO); - if (pkg_mq == RT_NULL) - { - rt_kprintf("create package message queue failed\n"); - return -RT_ENOMEM; - } - pkg_done = rt_sem_create("pk_done", 0, RT_IPC_FLAG_PRIO); - if (pkg_done == RT_NULL) - { - rt_kprintf("create package done semaphore failed\n"); - rt_mq_delete(pkg_mq); - pkg_mq = RT_NULL; - return -RT_ENOMEM; - } - - pkg_send_failed = 0; - pkg_running = 1; - - tid = rt_thread_create("sender1", sender_entry, (void *)1000, - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid != RT_NULL) { rt_thread_startup(tid); created++; } - tid = rt_thread_create("sender2", sender_entry, (void *)2000, - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid != RT_NULL) { rt_thread_startup(tid); created++; } - tid = rt_thread_create("sorter", sorter_entry, RT_NULL, - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid != RT_NULL) { rt_thread_startup(tid); created++; } - - for (i = 0; i < created; i++) - rt_sem_take(pkg_done, RT_WAITING_FOREVER); - - rt_sem_delete(pkg_done); - rt_mq_delete(pkg_mq); - pkg_mq = RT_NULL; - pkg_done = RT_NULL; - pkg_running = 0; - - return RT_EOK; -} -MSH_CMD_EXPORT(package_demo, package demo with message queue); - -int main(void) -{ - int count = 1; - - while (count++) - { - //LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - - return RT_EOK; -} diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 254ee3bf40bc81b43d35edda6c7520bc74df08e8..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,237 +0,0 @@ -# 【DAY2】RT-Thread 启动流程、内核基础与多线程编程 - ---- - -## Part 1:理论知识梳理 - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -#### 1.1 启动路径(Reset_Handler → main()) - -系统上电复位后,硬件跳转到汇编启动代码 `Reset_Handler`,完成栈指针与向量表初始化后进入 RT-Thread 启动入口 `rtthread_startup()`。不同工具链的入口方式不同:GCC 为 `entry()`,ARMCC/CLANG 通过 `$Sub$$main()` 拦截,IAR 通过 `__low_level_init()`。 - -```text -上电复位 - │ - ▼ -Reset_Handler(汇编启动代码:初始化栈与向量表) - │ - ▼ -工具链入口 - ├─ GCC → entry() - ├─ ARMCC/CLANG → $Sub$$main() - └─ IAR → __low_level_init() - │ - ▼ -rtthread_startup() - ├─ ① 关全局中断 rt_hw_interrupt_disable() - ├─ ② 板级初始化 rt_hw_board_init() ← 时钟/串口/堆 - ├─ ③ 显示版本号 rt_show_version() - ├─ ④ 定时器系统初始化 rt_system_timer_init() - ├─ ⑤ 调度器初始化 rt_system_scheduler_init() - ├─ ⑥ 创建 main 线程 rt_application_init() - ├─ ⑦ 定时器线程初始化 rt_system_timer_thread_init() - ├─ ⑧ 空闲线程初始化 rt_thread_idle_init() - └─ ⑨ 启动调度器 rt_system_scheduler_start() - │ - ▼ -main_thread_entry() - └─ rt_components_init() → 进入用户 main() -``` - -`rtthread_startup()` 的核心流程: - -```c -int rtthread_startup(void) -{ - rt_hw_interrupt_disable(); /* 关全局中断 */ - rt_hw_board_init(); /* 板级初始化:时钟、串口、堆 */ - rt_show_version(); /* 显示版本号 */ - rt_system_timer_init(); /* 定时器系统初始化 */ - rt_system_scheduler_init(); /* 调度器初始化 */ - rt_application_init(); /* 创建 main 线程 */ - rt_system_timer_thread_init(); /* 定时器线程初始化 */ - rt_thread_idle_init(); /* 空闲线程初始化 */ - rt_system_scheduler_start(); /* 启动调度器 */ - return 0; -} -``` - -启动流程可划分为四个阶段: - -| 阶段 | 对应函数 | 说明 | -| ---- | -------- | ---- | -| 1. 硬件初始化 | `rt_hw_board_init()` | 初始化与系统相关的硬件 | -| 2. 内核对象初始化 | `rt_system_timer_init()`、`rt_system_scheduler_init()`、`rt_system_signal_init()` | 初始化定时器、调度器、信号等内核对象 | -| 3. 创建 main 线程 | `rt_application_init()` | 在 main 线程中对各模块依次初始化 | -| 4. 收尾并启动调度 | `rt_system_timer_thread_init()`、`rt_thread_idle_init()`、`rt_system_scheduler_start()` | 初始化定时器线程、空闲线程并启动调度器 | - -**两个关键点:** - -1. **启动调度器之前线程不会运行**:`rt_thread_startup()` 只是让线程进入**就绪态**等待调度;只有 `rt_system_scheduler_start()` 之后系统才运行第一个线程,并按优先级选择就绪队列中优先级最高的线程。 -2. **`rt_hw_board_init()` 的职责**:完成系统时钟设置(提供系统心跳 `tick`)、串口初始化(将输入输出终端绑定到串口),堆的初始化也在此完成。 - -main 线程创建后,其入口 `main_thread_entry()` 会先执行 `rt_components_init()`(组件与自动初始化),再调用用户 `main()`。因此**用户 `main()` 运行在线程上下文中**,此时内核已完全就绪,可使用线程、信号量等所有内核机制。 - -#### 1.2 自动初始化原理(INIT_BOARD_EXPORT / INIT_APP_EXPORT) - -自动初始化机制的核心是把"初始化函数"按级别注册进指定段,系统启动时统一遍历调用,从而**无需在 `main()` 里手动调用**。 - -![自动初始化流程图](../figures/flowchart_LR.png) - -- `INIT_EXPORT(fn, level)` 宏把函数指针放到链接脚本定义的 `.rti_fn.` 段; -- 各级别按数值从小到大执行:`INIT_BOARD_EXPORT(0)` → `INIT_PREV_EXPORT(1)` → `INIT_DEVICE_EXPORT(2)` → `INIT_COMPONENT_EXPORT(3)` → `INIT_ENV_EXPORT(4)` → `INIT_APP_EXPORT(6)`; -- 系统启动时 `rt_components_init()` 遍历这些段,按顺序调用其中的函数指针,实现"自动初始化"。 - -作业源码中的 `INIT_APP_EXPORT(rt_demo_init)` 即利用该机制:三个线程的创建在系统启动阶段自动完成,`main()` 中无需任何硬编码调用。 - -### 2. 内核基础与对象模型 - -#### 2.1 对象容器与控制块(面向对象设计思想) - -RT-Thread 采用**面向对象**的设计思想,把内核中可管理的事物(线程、信号量、互斥量、事件、定时器、内存池、设备等)统一抽象为"内核对象",每个对象继承自 `struct rt_object`。所有对象按类型挂接到内核的**对象容器**(`struct rt_object_information`)中,每种类型对应一条对象链表,对象被创建时挂入容器、删除时从容器摘除。内核通过统一的"对象 → 容器 → 链表"机制管理所有资源。 - -线程对象 `struct rt_thread` 是 RT-Thread 中最基本的调度单位,它把 `struct rt_object parent` 作为第一个成员(继承基类),再扩展线程特有属性。关键成员作用: - -| 成员 | 作用 | -| ---- | ---- | -| `parent` | 继承的内核对象基类,含对象名称 `name`、类型 `type`、链表节点,使线程能被对象容器统一管理 | -| `sp` | 栈指针,指向线程当前栈顶。线程切换时调度器把寄存器上下文保存到 `sp` 指向的位置,切回时恢复,实现上下文切换 | -| `stat` | 线程当前状态(初始/就绪/运行/挂起/关闭),对应 `RT_THREAD_INIT` 等宏 | -| `current_priority` | 线程**当前**优先级;支持运行时动态调整(`rt_thread_control` 改变优先级),与初始优先级分离 | -| `init_tick` | 线程初始化时指定的时间片大小(单位 tick),线程被调度运行时用于重置剩余时间片 | -| `remaining_tick` | 剩余时间片,由系统节拍驱动递减,减到 0 时触发同优先级轮转 | -| `init_priority` | 线程创建/初始化时设置的初始优先级 | -| `entry` / `parameter` | 线程入口函数及其参数 | -| `stack_addr` / `stack_size` | 线程栈起始地址与大小 | -| `thread_timer` | 内置线程定时器,用于线程延时、超时唤醒 | - -> 说明:`RT_SCHED_THREAD_CTX` 宏展开后包含 `current_priority`、`init_priority`、`number_mask`、`number`、`high_mask` 等调度成员;`stat`、`init_tick`、`remaining_tick` 属于调度上下文的一部分。本作业 monitor 读取 `tid_led1->stat` 与 `tid_led1->stack_size` 正是直接访问控制块成员。 - -#### 2.2 静态对象与动态对象 - -| 对比项 | 动态对象(`rt_thread_create`) | 静态对象(`rt_thread_init`) | -| ---- | ---- | ---- | -| 分配位置 | 系统堆内存(需使能 `RT_USING_HEAP`) | 编译期确定,通常定义为全局变量 | -| 分配时机 | 运行时从堆动态分配(TCB + 栈) | 编译链接时静态分配(用户提供 TCB 与栈数组) | -| 栈首地址 | 内核从堆分配,按 `RT_ALIGN_SIZE` 对齐 | 用户提供,需做地址对齐(ARM 为 4 字节) | -| 生命周期 | 由 `rt_thread_delete()` 删除 | 由 `rt_thread_detach()` 脱离 | -| 释放机制 | `rt_thread_delete()` 仅置为 `RT_THREAD_CLOSE` 并挂入 `rt_thread_defunct` 队列,**由空闲线程**真正回收控制块与栈并释放回堆 | `rt_thread_detach()` 仅从容器/队列移除,内存仍留在静态区,无需回收 | -| 适用场景 | 线程数量/栈大小运行期才确定的灵活场景 | 高可靠、资源受限、不允许堆分配的场景 | - -本作业正是两种方式的对照:`th_led1`、`th_monitor` 用 `rt_thread_create()` 动态创建(TCB 与栈来自堆),`th_led2` 用 `rt_thread_init()` 静态初始化(`struct rt_thread th_led2` 与 `thread2_stack[1024]` 为全局变量)。 - -**为什么高可靠场景强制使用静态线程?** - -1. TCB 与栈在**编译期即分配固定内存**,不存在堆分配失败(返回 `RT_NULL`)的风险; -2. 不依赖堆管理器和空闲线程回收,生命周期完全可控,避免动态申请/释放带来的不确定性与实时性抖动; -3. 可在堆初始化之前或中断上下文等受限环境中使用; -4. 汽车电子、航天等安全关键系统常禁止动态内存分配(如 MISRA 规范),静态线程是唯一合规方案。 - -### 3. 线程状态机与调度算法 - -#### 3.1 五态模型 - -| 状态 | 宏定义 | 说明 | -| ---- | ---- | ---- | -| 初始态 | `RT_THREAD_INIT` | 线程刚创建/初始化,尚未参与调度 | -| 就绪态 | `RT_THREAD_READY` | 按优先级排队等待调度,获得 CPU 即运行 | -| 运行态 | `RT_THREAD_RUNNING` | 当前占用 CPU;单核下仅 `rt_thread_self()` 返回的线程处于该状态 | -| 挂起态 | `RT_THREAD_SUSPEND` | 阻塞等待资源或主动延时,不参与调度 | -| 关闭态 | `RT_THREAD_CLOSE` | 运行结束或被删除/脱离,不参与调度,资源待回收 | - -状态转换关系及典型 API: - -```text -rt_thread_create/init() rt_thread_startup() 调度器选中 - 初始态 ────────────────▶ 就绪态 ───────────────────▶ 运行态 - ▲ ▲ ▲ │ - │ rt_thread_delete/detach│ │ 资源就绪/超时/恢复 │ rt_thread_delay()/mdelay() - │ │ │ rt_thread_resume() │ rt_sem_take()/rt_mutex_take() - │ │ └──────────┐ │ 资源不可用 - │ │ │ ▼ - └────── 关闭态 ◀─────────┴─────────────┴────────── 挂起态 - (rt_thread_exit() 自动进入,空闲线程回收资源) -``` - -- **初始态 → 就绪态**:`rt_thread_startup()`; -- **就绪态 → 运行态**:调度器选中最高优先级就绪线程; -- **运行态 → 挂起态**:调用 `rt_thread_delay()`、`rt_thread_mdelay()`、`rt_sem_take()` 等阻塞; -- **挂起态 → 就绪态**:等待超时或资源被释放,或调用 `rt_thread_resume()`; -- **运行态 → 关闭态**:入口函数返回后自动执行 `rt_thread_exit()`; -- **任意态 → 关闭态**:`rt_thread_delete()`(动态)/ `rt_thread_detach()`(静态)。 - -#### 3.2 调度机制 - -**抢占式调度**:调度器始终从就绪队列中查找**最高优先级**线程,最高优先级线程一旦就绪总能获得 CPU,当前线程立即被换出(被抢占)。本作业中 `th_monitor` 优先级 10 高于工作线程 16,因此能周期性抢占两个 LED 线程来打印状态——这也解释了 monitor 观察时 `th_led1` 只能是 `ready`/`suspend`,永远看不到 `running`(观察瞬间 CPU 正被 monitor 自己占用)。 - -**时间片轮转**:仅对**同优先级**的就绪线程生效。时间片单位为 OS Tick,本作业两个工作线程时间片均为 5 ticks。当没有更高优先级线程就绪时,同优先级的 `th_led1`、`th_led2` 轮流执行,每次最多运行 5 个 tick,时间片耗尽即让出,由下一个同优先级线程继续运行: - -```text -时间轴 → -th_led1 │██████│ │██████│ │██████│ -th_led2 │ │██████│ │██████│ │ - 每段 = 5 ticks(时间片),同优先级轮流运行 -``` - -补充说明:两个 LED 线程循环内调用 `rt_thread_mdelay(10)` 会主动进入挂起态并让出 CPU,属于**主动让出**;而时间片轮转是被动切换(时间片耗尽)。两者结合使同优先级线程交替运行,避免了无延时死循环打印导致的串口输出交错。 - ---- - -## Part 2:多线程实战(实验结果分析) - -### 线程设计 - -| 线程 | 创建方式 | 优先级 | 时间片 | 栈大小 | 功能 | -| ---- | ---- | ---- | ---- | ---- | ---- | -| `th_led1` | 动态 `rt_thread_create` | 16 | 5 | 1024 | 翻转 LED_R,按键(信号量)暂停/恢复 | -| `th_led2` | 静态 `rt_thread_init` | 16 | 5 | 1024 | 翻转 LED_B | -| `th_monitor` | 动态 `rt_thread_create` | 10 | 5 | 1024 | 打印线程状态与剩余栈 | - -### 实验现象与串口截图 - -![线程打印输出](../figures/printf_thread.png) - -![monitor 打印线程状态与剩余栈](../figures/printf_stat.png) - -**现象分析:为什么 `th_led1` 一直是 `ready`/`suspend`,看不到 `running`?** - -monitor 周期性打印 `th_led1` 的状态与剩余栈,状态始终在 `ready` 与 `suspend` 之间变化,永远不会出现 `running`,原因是: - -- **单核 CPU 同一时刻只有一个线程在运行**; -- monitor 读取 `tid_led1->stat` 的瞬间,CPU 正被 monitor 自己占用(优先级 10 高于 16),此时 `th_led1` 必然不在运行,只可能是 `ready`(在就绪队列等待调度)或 `suspend`(睡眠中); -- 因此在单核系统中,一个线程**永远无法观察到其他线程处于 `running` 态**(观察时"运行"位置必然被自己占着),只有 `rt_thread_self()` 才能读到自己的 running 状态。 - -剩余栈空间(`remain`)随 `rt_kprintf` 调用路径深浅在 856~928 字节间波动,属正常现象。 - -**按键暂停/恢复效果(信号量实现)**: - -- 按键按下产生下降沿中断,`key_callback` 翻转 `led1_paused` 标志并 `rt_sem_release()` 释放信号量; -- 暂停时 `th_led1` 阻塞在 `rt_sem_take(sem_led1, RT_WAITING_FOREVER)` 上,状态转为 `suspend`(图中末尾连续两条 `suspend` 即暂停生效,此时 `count` 停止增长、LED_R 停止翻转); -- 再次按键,标志清零并释放信号量唤醒线程,`th_led1` 恢复正常运行(`ready`/`suspend` 交替); -- 正常运行时 `th_led1`、`th_led2` 周期打印计数并分别翻转 LED_R / LED_B(见第一张截图)。 - -### MSH 命令验证 - -通过 `MSH_CMD_EXPORT` 导出 `thread_dump` 命令,在 FinSH 终端执行可查看所有线程的状态与栈信息: - -![thread_dump 命令输出](../figures/thread_dump.png) - -### 关键实现说明 - -- 挂起/恢复 `th_led1` 采用**信号量**(`rt_sem_take/release`)而非跨线程 `rt_thread_suspend`,由线程自己阻塞等待唤醒,避免强行挂起导致的死锁风险。 - - -### 遇到的问题与解决 - -**1. 线程名过长被截断(`th_monitor` → `th_monit`)** - -- **现象**:`thread_dump` 命令输出中,`th_monitor` 线程显示为 `th_monit`。 -- **原因**:RT-Thread 线程名最大长度由 `RT_NAME_MAX` 决定(默认 8 字节),`th_monitor` 有 10 个字符,超出部分被截断。 -- **解决**:将线程名缩短到 8 字符以内(如改为 `"monitor"`),或在创建线程时使用更短的名字;若不影响功能,也可保留并接受该截断。 - -**2. 线程无延时导致串口输出交错、不完整** - -- **现象**:去掉 `rt_thread_mdelay()` 后,`th_led1` 与 `th_led2` 的打印互相撕裂,出现 `th_lcount: 1226,th_led2 count: 2146` 这类残缺交错的输出。 -- **原因**:两个同优先级(16)线程都在无延时死循环打印,时间片轮转(5 ticks)经常在一条 `rt_kprintf` 输出途中就发生切换(115200 波特率下一行需数毫秒),两股输出流在串口驱动层字符级交错。 -- **解决**:在线程循环中加回 `rt_thread_mdelay(10)`,让各线程打印后在时间上错开;同时可将一行信息合并为单条 `rt_kprintf`,进一步减少行间穿插。 diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index a421806233518b77f8288177cba06c085dbe65dc..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-18 RT-Thread first version - */ - -#include -#include -#include -#include "board.h" - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -#define LED_R GET_PIN(F, 12) -#define LED_B GET_PIN(F, 11) -#define KEY_PIN GET_PIN(C, 4) - -#define THREAD_PRIORITY 16 -#define MONITOR_THREAD_PRIORITY 10 -#define THREAD_STACK_SIZE 1024 -#define THREAD_TIMESLICE 5 - -static rt_thread_t tid_led1 = RT_NULL; -static rt_sem_t sem_led1 = RT_NULL; -static volatile rt_uint8_t led1_paused = 0; - -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_pin_mode(LED_R, PIN_MODE_OUTPUT); /* 设置 LED 引脚为输出 */ - while (1) - { - if (led1_paused) /* 暂停中? */ - { - rt_sem_take(sem_led1, 0); /* 先清掉积压的信号 */ - rt_sem_take(sem_led1, RT_WAITING_FOREVER); /* 真正阻塞等唤醒 */ - } - rt_pin_write(LED_R, !rt_pin_read(LED_R)); - rt_kprintf("th_led1 count: %d, rt_tick: %d\n", count ++, rt_tick_get()); - rt_thread_mdelay(10); - - } -} - -static void th_led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_pin_mode(LED_B, PIN_MODE_OUTPUT); - while (1) - { - rt_pin_write(LED_B, !rt_pin_read(LED_B)); - rt_kprintf("th_led2 count: %d\n", count ++); - rt_thread_mdelay(10); - - } -} - -static const char *stat2str(rt_uint8_t s) -{ - switch (s) - { - case RT_THREAD_INIT: return "init"; - case RT_THREAD_READY: return "ready"; - case RT_THREAD_SUSPEND: return "suspend"; - case RT_THREAD_RUNNING: return "running"; - case RT_THREAD_CLOSE: return "close"; - default: return "?"; - } -} - -static void key_callback(void *args) -{ - rt_sem_release(sem_led1); - led1_paused = !led1_paused; -} - -static void th_monitor_entry(void *parameter) -{ - rt_pin_mode(KEY_PIN, PIN_MODE_INPUT_PULLUP); - rt_pin_attach_irq(KEY_PIN, PIN_IRQ_MODE_FALLING, key_callback, RT_NULL); - rt_pin_irq_enable(KEY_PIN, PIN_IRQ_ENABLE); - while (1) - { - rt_uint8_t s1 = tid_led1->stat & RT_THREAD_STAT_MASK; /* 状态 */ - rt_uint32_t remain1 = tid_led1->stack_size - - ((rt_ubase_t)tid_led1->stack_addr + tid_led1->stack_size - - (rt_ubase_t)tid_led1->sp); - rt_kprintf("th_led1 stat: %s, remain: %u\n", stat2str(s1), remain1); - rt_thread_mdelay(2000); - } -} - -ALIGN(RT_ALIGN_SIZE) -static char th_led2_stack[THREAD_STACK_SIZE]; -static struct rt_thread th_led2; - -static int rt_demo_init(void) -{ - sem_led1 = rt_sem_create("sem_led1", 0, RT_IPC_FLAG_FIFO); - - tid_led1 = rt_thread_create("th_led1", th_led1_entry, RT_NULL, - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid_led1 != RT_NULL) - { - rt_thread_startup(tid_led1); // 启动线程 - } - - rt_thread_init(&th_led2, - "th_led2", - th_led2_entry, - RT_NULL, - &th_led2_stack[0], - sizeof(th_led2_stack), - THREAD_PRIORITY, THREAD_TIMESLICE); - rt_thread_startup(&th_led2); - - rt_thread_t mon = rt_thread_create("th_monitor", th_monitor_entry, RT_NULL, - THREAD_STACK_SIZE, MONITOR_THREAD_PRIORITY, THREAD_TIMESLICE); - if (mon != RT_NULL) - { - rt_thread_startup(mon); - } - return RT_EOK; -} - -INIT_APP_EXPORT(rt_demo_init); - -static void thread_dump(void) -{ - rt_base_t level; - struct rt_object *obj; - struct rt_list_node *node; - struct rt_object_information *info; - - info = rt_object_get_information(RT_Object_Class_Thread); /* 线程对象容器 */ - - level = rt_hw_interrupt_disable(); /* 关中断保护遍历 */ - for (node = info->object_list.next; node != &(info->object_list); node = node->next) - { - obj = rt_list_entry(node, struct rt_object, list); /* 链表节点 → 对象 */ - struct rt_thread *t = (struct rt_thread *)obj; - rt_kprintf("%-12s pri=%d stat=%s stack=%u\n", - t->name, - t->current_priority, - stat2str(t->stat & RT_THREAD_STAT_MASK), - t->stack_size); - } - rt_hw_interrupt_enable(level); -} -MSH_CMD_EXPORT(thread_dump, dump all threads info); - -int main(void) -{ - int count = 1; - - while (count++) - { - // LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - - return RT_EOK; -} diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 73bb1706d7cc7a7212363cc42bebcfd18cd9828c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,158 +0,0 @@ -# 【DAY4】RT-Thread 设备驱动框架与 PIN 设备对接 - ---- - -## Part 1:理论知识梳理 - -### 1. 为什么需要设备驱动框架层 - -RT-Thread 把驱动分成三层,层与层之间通过一张**方法表(ops 函数指针表)**联系起来: - -| 层 | 职责 | PIN 的例子 | -| --- | --- | --- | -| 应用层 | 只调统一 API,不关心硬件 | `rt_pin_write()` / `rt_pin_read()` | -| 设备驱动框架层 | 定义抽象接口、注册设备、统一分发 | `rt-thread/components/drivers/misc/pin.c` | -| 驱动层 | 实现具体硬件操作,填进方法表 | `drivers/drv_gpio.c` | - -### 2. PIN 设备如何对接(三层源码剖析) - -先看框架层定义的抽象接口(pin.h),它规定了驱动层要实现哪些函数: - -```c -struct rt_pin_ops -{ - void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_base_t mode); - void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_base_t value); - int (*pin_read)(struct rt_device *device, rt_base_t pin); - rt_err_t (*pin_attach_irq)(...); - rt_err_t (*pin_detach_irq)(...); - rt_err_t (*pin_irq_enable)(...); -}; -``` - -**② 驱动层实现并填表(drv_gpio.c)**——用 STM32 HAL 实现后填成表: - -```c -const static struct rt_pin_ops _stm32_pin_ops = -{ - stm32_pin_mode, stm32_pin_write, stm32_pin_read, - stm32_pin_attach_irq, stm32_pin_dettach_irq, stm32_pin_irq_enable, - stm32_pin_get, -}; -``` - -**③ 框架层挂载 + 分发(pin.c)**——定义扩展结构体 `struct rt_device_pin`(内核 `parent` + 自己的 `ops`),`rt_device_pin_register()` 把 `parent` 的 `read/write/control` 指到框架自己的分发函数 `_pin_read/_pin_write/_pin_control`,再 `rt_device_register()` 挂进内核。分发函数只做"转手": - -```c -static rt_size_t _pin_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) -{ - struct rt_device_pin *pin = (struct rt_device_pin *)dev; /* 强转取 ops */ - ... - status->status = pin->ops->pin_read(dev, status->pin); /* 下行到驱动层 */ - return size; -} -``` - -**④ 应用层 API**——直接经 `_hw_pin.ops` 下行: - -```c -void rt_pin_write(rt_base_t pin, rt_base_t value) -{ - _hw_pin.ops->pin_write(&_hw_pin.parent, pin, value); -} -``` - -**对接总结**:`rt_pin_write()` → 框架层 `pin.c: _hw_pin.ops->pin_write` → 驱动层 `drv_gpio.c: stm32_pin_write` → `HAL_GPIO_WritePin`。换 MCU 只需重写 `drv_gpio.c` 这张 ops 表。 - ---- - -## Part 2:虚拟设备驱动框架层 test_dev(作业 2) - -### 1. 文件结构 - -```text -第四天作业/ -├─ test_dev.h # 框架层:方法表 + 设备对象 + 注册 API 声明 -├─ test_dev.c # 框架层:静态设备 + 6 个分发函数 + rt_device_test_register -├─ test_drv.c # 驱动层:6 个实现 + test_ops 方法表 + INIT_DEVICE_EXPORT 注册 -└─ test_app.c # 应用层:test_dev_app 命令 -``` - -### 2. 框架层接口(test_dev.h) - -```c -struct rt_test_ops -{ - rt_err_t (*init)(rt_device_t dev); - rt_err_t (*open)(rt_device_t dev, rt_uint16_t oflag); - rt_err_t (*close)(rt_device_t dev); - rt_size_t (*read)(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); - rt_size_t (*write)(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); - rt_err_t (*control)(rt_device_t dev, int cmd, void *args); -}; - -struct rt_device_test -{ - struct rt_device parent; /* 内核设备,放第一个成员以便强转 */ - const struct rt_test_ops *ops; -}; -``` - -### 3. 框架层实现要点(test_dev.c) - -- 用**静态设备对象** `static struct rt_device_test _test_dev;`(对标 `pin.c` 的 `_hw_pin`),不用 `rt_device_create()`——后者只分配内核 `rt_device`,放不下扩展的 `ops`。 -- 分发函数强转取 ops 后下行: - -```c -static rt_size_t _test_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) -{ - struct rt_device_test *test_dev = (struct rt_device_test *)dev; - if (test_dev->ops == RT_NULL || test_dev->ops->read == RT_NULL) return 0; - return test_dev->ops->read(dev, pos, buffer, size); -} -``` - -- `rt_device_test_register()` 把 **init/open/close/read/write/control 6 个内核接口全部**接上分发函数,再 `rt_device_register()` 挂载。 - -### 4. 驱动层(test_drv.c) - -- 6 个 `test_drv_xxx()` 用 `LOG_I` 打印假装操作硬件,read/write 返回 `size`(实际字节数)。 -- 填方法表,顺序与 `struct rt_test_ops` 一致;`INIT_DEVICE_EXPORT` 里调 `rt_device_test_register("test_dev", &test_ops, RT_NULL)`。 - -### 5. 应用层(test_app.c) - -```c -MSH_CMD_EXPORT(test_dev_app, test test_dev framework); -``` - -内部流程:`rt_device_find("test_dev")` → `open` → `control` → `write(1024)` → `read(128)` → `close`。 - -### 6. 验证结果 - -```text -msh >list_device -device type ref count --------- -------------------- ---------- -test_dev Character Device 0 -uart1 Character Device 2 -pin Pin Device 0 - -msh >test_dev_app -[test.drv] init -[test.drv] open -[test.drv] control -[test.drv] write -[test.drv] read -[test.drv] close -``` - -从输出可见,`test_dev_app` 依次触发了 init、open、control、write、read、close 六个操作,说明应用层的每次调用都经过内核、框架层分发,最终到达驱动层实现,整个虚拟设备框架的链路已经打通。 - ---- - -## Part 3:踩坑记录 - -1. **`rt_ssize_t` 在 4.1.1 中不存在**,read/write 返回类型要用 `rt_size_t`(与内核 `struct rt_device` 一致)。 -2. **`MSH_CMD_EXPORT` 重复定义**:同一命令导出两次会报 `__fsym_xxx` 重复定义。 -3. **框架层必须接全 6 个接口**:只接 read/write/control 时,`dev->init/open/close` 为 NULL,内核 `rt_device_open/close` 直接跳过,驱动层不打印。 -4. **框架层用静态对象**,不用 `rt_device_create()`。 diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_app.c" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_app.c" deleted file mode 100644 index f46d8e276f53dfaf8fce736bedfaabe298cd111d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_app.c" +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include - -static int test_dev_app(void) -{ - rt_device_t dev = rt_device_find("test_dev"); /* ① 找到设备 */ - if (dev == RT_NULL) - { - rt_kprintf("can not find test_dev!\n"); - return -RT_ERROR; - } - - rt_device_open(dev, RT_DEVICE_OFLAG_RDWR); /* ② 打开 */ - rt_device_control(dev, RT_DEVICE_CTRL_CONFIG, RT_NULL);/* ③ 控制 */ - rt_device_write(dev, 100, RT_NULL, 1024); /* ④ 写 1024 字节 */ - rt_device_read(dev, 20, RT_NULL, 128); /* ⑤ 读 128 字节 */ - rt_device_close(dev); /* ⑥ 关闭 */ - - return RT_EOK; -} -MSH_CMD_EXPORT(test_dev_app, test test_dev framework); \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" deleted file mode 100644 index def9033ef14f0ae3cbfa410ad9de01d643418a7e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" +++ /dev/null @@ -1,71 +0,0 @@ -#include -#include -#include "test_dev.h" -#include - -/* 框架层设备对象(对标 pin.c 的 _hw_pin) */ -static struct rt_device_test _test_dev; - -/* 分发函数:内核调到这里,再转手调驱动层的 ops */ -static rt_size_t _test_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) -{ - struct rt_device_test *test_dev = (struct rt_device_test *)dev; - if (test_dev->ops == RT_NULL || test_dev->ops->read == RT_NULL) return 0; - return test_dev->ops->read(dev, pos, buffer, size); -} - -static rt_size_t _test_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) -{ - struct rt_device_test *test_dev = (struct rt_device_test *)dev; - if (test_dev->ops == RT_NULL || test_dev->ops->write == RT_NULL) return 0; - return test_dev->ops->write(dev, pos, buffer, size); -} - -static rt_err_t _test_control(rt_device_t dev, int cmd, void *args) -{ - struct rt_device_test *test_dev = (struct rt_device_test *)dev; - if (test_dev->ops == RT_NULL || test_dev->ops->control == RT_NULL) return -RT_ENOSYS; - return test_dev->ops->control(dev, cmd, args); -} - -static rt_err_t _test_init(rt_device_t dev) -{ - struct rt_device_test *test_dev = (struct rt_device_test *)dev; - if (test_dev->ops == RT_NULL || test_dev->ops->init == RT_NULL) return RT_EOK; - return test_dev->ops->init(dev); -} - -static rt_err_t _test_open(rt_device_t dev, rt_uint16_t oflag) -{ - struct rt_device_test *test_dev = (struct rt_device_test *)dev; - if (test_dev->ops == RT_NULL || test_dev->ops->open == RT_NULL) return RT_EOK; - return test_dev->ops->open(dev, oflag); -} - -static rt_err_t _test_close(rt_device_t dev) -{ - struct rt_device_test *test_dev = (struct rt_device_test *)dev; - if (test_dev->ops == RT_NULL || test_dev->ops->close == RT_NULL) return RT_EOK; - return test_dev->ops->close(dev); -} - -/* 注册 API:由驱动层调用 */ -int rt_device_test_register(const char *name, - const struct rt_test_ops *ops, - void *user_data) -{ - _test_dev.parent.type = RT_Device_Class_Char; /* 设备类型 */ - - _test_dev.parent.init = _test_init; /* 内核接口 → 框架分发 */ - _test_dev.parent.open = _test_open; - _test_dev.parent.close = _test_close; - _test_dev.parent.read = _test_read; - _test_dev.parent.write = _test_write; - _test_dev.parent.control = _test_control; - - _test_dev.ops = ops; /* 存驱动层方法表 */ - _test_dev.parent.user_data = user_data; - - return rt_device_register(&_test_dev.parent, name, RT_DEVICE_FLAG_RDWR); -} - diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.h" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.h" deleted file mode 100644 index d7b7d074f75c0a13072ce14f1167e61f0bb75d85..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.h" +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 RT-Thread first version - */ -#ifndef TEST_DEV_H__ -#define TEST_DEV_H__ - -#include -#include - -/* 框架层与驱动层的抽象接口 */ -struct rt_test_ops -{ - rt_err_t (*init)(rt_device_t dev); - rt_err_t (*open)(rt_device_t dev, rt_uint16_t oflag); - rt_err_t (*close)(rt_device_t dev); - rt_size_t (*read)(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); - rt_size_t (*write)(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); - rt_err_t (*control)(rt_device_t dev, int cmd, void *args); -}; - -/* 框架层设备对象 */ -struct rt_device_test -{ - struct rt_device parent; /* 内核设备,挂到设备管理器 */ - const struct rt_test_ops *ops; -}; - -/* 框架层注册 API —— 对标 rt_device_pin_register */ -int rt_device_test_register(const char *name, - const struct rt_test_ops *ops, - void *user_data); - -#endif /* TEST_DEV_H__ */ diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_drv.c" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_drv.c" deleted file mode 100644 index 73b59cc40b95599340d0276d0998f0e367ba5283..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_drv.c" +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include -#include "test_dev.h" - -#define LOG_TAG "test.drv" -#define LOG_LVL LOG_LVL_DBG -#include - -/* 实现 6 个函数(虚拟设备,用 LOG 打印假装操作硬件) */ -static rt_err_t test_drv_init(rt_device_t dev) -{ - LOG_I("init"); - return RT_EOK; -} -static rt_err_t test_drv_open(rt_device_t dev, rt_uint16_t oflag) -{ - LOG_I("open"); - return RT_EOK; -} -static rt_err_t test_drv_close(rt_device_t dev) -{ - LOG_I("close"); - return RT_EOK; -} -static rt_size_t test_drv_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) -{ - LOG_I("read"); - return size; -} -static rt_size_t test_drv_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) -{ - LOG_I("write"); - return size; -} -static rt_err_t test_drv_control(rt_device_t dev, int cmd, void *args) -{ - LOG_I("control"); - return RT_EOK; -} - - -static const struct rt_test_ops test_ops = -{ - test_drv_init, - test_drv_open, - test_drv_close, - test_drv_read, - test_drv_write, - test_drv_control, -}; - -/* 注册入口:系统启动自动调用,把 ops 交给框架层 */ -static int rt_test_drv_init(void) -{ - if (rt_device_test_register("test_dev", &test_ops, RT_NULL) != RT_EOK) - { - LOG_E("test dev register failed!"); - return -RT_ERROR; - } - return RT_EOK; -} -INIT_DEVICE_EXPORT(rt_test_drv_init); \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/RT-Thread\345\220\257\345\212\250\346\265\201\347\250\213.md" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/RT-Thread\345\220\257\345\212\250\346\265\201\347\250\213.md" deleted file mode 100644 index a2a7dce71c7b2e412d3ff67de831c747a5018482..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/RT-Thread\345\220\257\345\212\250\346\265\201\347\250\213.md" +++ /dev/null @@ -1,120 +0,0 @@ -# RT-Thread 启动流程 - -> 对应《RT-Thread 星火1号内核学习手册》第 2.2 节。 -系统上电后,先运行汇编启动代码完成堆栈初始化,随后根据编译器跳转到 RT-Thread 启动入口 `rtthread_startup()`: - -- GCC(`arm-none-eabi-gcc`):入口函数为 `entry()` -- ARMCC / CLANG:通过 `$Sub$$main()` 拦截 `main()` 实现 -- IAR:通过 `__low_level_init()` 实现 - -```c -#if defined(__GNUC__) -int entry(void) -{ - rtthread_startup(); - return 0; -} -#endif -``` - -## 2.1 启动入口 rtthread_startup() - -```c -int rtthread_startup(void) -{ - rt_hw_interrupt_disable(); /* 关全局中断 */ - - /* 板级初始化:系统时钟、串口、堆等 */ - rt_hw_board_init(); - - /* 显示 RT-Thread 版本号 */ - rt_show_version(); - - /* 定时器系统初始化 */ - rt_system_timer_init(); - - /* 调度器初始化 */ - rt_system_scheduler_init(); - -#ifdef RT_USING_SIGNALS - /* 信号初始化 */ - rt_system_signal_init(); -#endif - - /* main 线程初始化(即创建 main 线程) */ - rt_application_init(); - - /* timer 定时器线程初始化 */ - rt_system_timer_thread_init(); - - /* idle 空闲线程初始化 */ - rt_thread_idle_init(); - -#ifdef RT_USING_SMP - rt_hw_spin_lock(&_cpus_lock); -#endif - - /* 启动调度器 */ - rt_system_scheduler_start(); - - /* never reach here */ - return 0; -} -``` - -### 启动流程分四个部分 - -| 部分 | 对应函数 | 说明 | -| ---- | -------- | ---- | -| 1. 硬件初始化 | `rt_hw_board_init()` | 初始化与系统相关的硬件 | -| 2. 内核对象初始化 | `rt_system_timer_init()` `rt_system_scheduler_init()` `rt_system_signal_init()` | 初始化定时器、调度器、信号等系统内核对象 | -| 3. 创建 main 线程 | `rt_application_init()` | 在 main 线程中对各类模块依次进行初始化 | -| 4. 收尾并启动调度 | `rt_system_timer_thread_init()` `rt_thread_idle_init()` `rt_system_scheduler_start()` | 初始化定时器线程、空闲线程,并启动调度器 | - -### 两个要点 - -1. **启动调度器之前线程不会运行**:系统创建的线程在执行 `rt_thread_startup()` 后并不会立马运行,而是处于**就绪状态**等待调度;待 `rt_system_scheduler_start()` 启动调度器之后,系统才转入第一个线程运行,按调度规则选择就绪队列中**优先级最高**的线程。 -2. **`rt_hw_board_init()` 的职责**:完成系统时钟设置(为系统提供心跳 `tick`)、串口初始化(将系统输入输出终端绑定到该串口),后续系统运行信息就会从串口打印出来。堆的初始化也在板级初始化中完成。 - -## 2.2 创建 main 线程 rt_application_init() - -`rtthread_startup()` 中通过 `rt_application_init()` 创建 main 线程: - -```c -void rt_application_init(void) -{ - rt_thread_t tid; - -#ifdef RT_USING_HEAP - tid = rt_thread_create("main", main_thread_entry, RT_NULL, - RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20); - RT_ASSERT(tid != RT_NULL); -#else - /* 未启用堆时,使用静态线程和栈 */ - ... -#endif - - rt_thread_startup(tid); -} -``` - - -main 线程的入口函数 `main_thread_entry()` 中完成**组件初始化**,然后调用用户 `main()` 函数: - -```c -void main_thread_entry(void *parameter) -{ - extern int main(void); - -#ifdef RT_USING_COMPONENTS_INIT - /* RT-Thread 组件初始化 */ - rt_components_init(); -#endif - - /* 调用系统 main 函数 */ - main(); -} -``` - -也就是说,用户代码 `main()` 是在一个**线程**里运行的,此时内核已经完全就绪,可以使用线程、信号量、消息队列等所有内核机制。 - diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/git\344\275\277\347\224\250\346\226\271\346\263\225.md" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/git\344\275\277\347\224\250\346\226\271\346\263\225.md" deleted file mode 100644 index 6253c46e0a411f2144d94192ec5ef26421fbd2b5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/git\344\275\277\347\224\250\346\226\271\346\263\225.md" +++ /dev/null @@ -1,115 +0,0 @@ -# Git 使用方法 - -## 〇、初次使用前的配置(用户名 / 邮箱 / SSH 密钥) - -### 1. 配置提交用户名和邮箱 - -git 每次提交都会记录作者信息,先配置用户名和邮箱: - -```bash -git config --global user.name "你的用户名" -git config --global user.email "你的邮箱" -``` - -查看是否配置成功(`--global` 只看全局配置): - -```bash -git config --list -``` - -本机执行效果(节选,只需关注最后的 `user.name` 和 `user.email` 两行即可,其余为系统默认配置): - -```text -$ git config --list -diff.astextplain.textconv=astextplain -filter.lfs.clean=git-lfs clean -- %f -filter.lfs.smudge=git-lfs smudge -- %f -filter.lfs.process=git-lfs filter-process -filter.lfs.required=true -http.sslbackend=schannel -core.autocrlf=true -core.fscache=true -core.symlinks=false -pull.rebase=false -credential.helper=manager -credential.https://dev.azure.com.usehttppath=true -init.defaultbranch=master -user.email=15512667+liquoricehy@user.noreply.gitee.com <-- 已生效 -user.name=LiquoriceHy <-- 已生效 -... -``` - -### 2. 配置 SSH 密钥 - -使用 SSH 方式推送代码到 gitee,无需每次输入账号密码。 - -**(1)生成密钥**(一路回车即可,`-C` 后写自己的邮箱作备注): - -```bash -ssh-keygen -t ed25519 -C "你的邮箱" -``` - -**(2)查看公钥**:密钥默认生成在 `~/.ssh/` 目录下,私钥为 `id_ed25519`(不要外泄),公钥为 `id_ed25519.pub`: - -```bash -cat ~/.ssh/id_ed25519.pub -``` - -**(3)添加到 gitee**:复制公钥全部内容,打开 gitee -> 设置 -> SSH 公钥 -> 粘贴保存 - -**(4)测试连接**: - -```bash -ssh -T git@gitee.com -``` - -出现 `Hi xxx! You've successfully authenticated...` 即配置成功。 - -## 一、常用命令 - -| # | 命令 | 作用 | -| - | ---- | ---- | -| 1 | `git init` | 初始化当前目录为 git 仓库 | -| 2 | `git pull origin master` | 拉取远端 master 分支 | -| 3 | `git add ` | 添加 file/path 到 git 暂存区 | -| 4 | `git commit -m "message"` | 提交,本次 git 提交日志为 "message" | -| 5 | `git push origin master` | 将本地 git 分支最新内容同步/推送到远端 | -| 6 | `git remote set-url origin git@gitee.com:xxx/xxx.git` | 设置当前 git 仓库的 URL(按自己的仓库修改) | -| 7 | `git fetch origin master` | 同步远端分支到本地 | -| 8 | `git remote -v` | 查看仓库远端分支情况 | -| 9 | `git checkout -b test` | 基于当前分支切出一个名为 test 的新分支 | - -> 注:提交必须带 `-m` 参数指定提交信息,即 `git commit -m "message"`。 - -## 二、pull 与 push 的方向 - -| 命令 | 方向 | -| ---- | ---- | -| `git pull` | 远端 -> 本地 | -| `git push` | 本地 -> 远端 | - -## 三、暂存区的理解 - -git 暂存区代表你**本地的 git 管理库**。将指定的文件/文件夹 `git add` 加入暂存库后,这些文件可以得到一个保护(可恢复、可追踪改动)。 - -文件流转过程:工作区 --`git add`--> 暂存区 --`git commit -m`--> 本地仓库 --`git push origin 分支`--> 远端仓库(gitee);`git pull` / `git fetch` 则把远端最新内容同步回本地。 - -## 四、如何解决 git 冲突 - -1. **fork repo**:浏览器上同步上游(fork 仓库页面的同步按钮) -2. **切到产生冲突的分支**,执行 `git merge` / `git rebase`,将最新远端的修改同步到产生冲突的分支;此时会让你解决冲突,**自行选择旧的时间线还是新的时间线的修改** - -## 五、第一次 PR 后,如何进行下一次代码推送和 PR 创建 - -以 day2 作业为例,完整流程: - -1. 浏览器 fork 仓库**同步上游** -2. 本地仓库打开 git bash,依次执行: - - `git switch master`:切回到主分支 - - `git pull origin master`:从远端拉取主分支到本地(确保第一步完成,即 origin/master 是同步的最新上游仓库) -3. `git checkout -b day2`:基于 master 切出 day2 新分支 -4. 修改/新增/删除对应的文档内容 -5. `git add .`:将当前目录下的所有文件修改提交到本地 git 管理库 -6. `git commit -m "第二次提交信息"` -7. `git push origin day2`:将最新修改同步到远端 day2 分支 -8. 到 gitee 的 day2 分支下创建 PR diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/markdown\350\257\255\346\263\225.md" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/markdown\350\257\255\346\263\225.md" deleted file mode 100644 index 290be5a2f7ac4ea3c119defe772fe0330ecf355c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/markdown\350\257\255\346\263\225.md" +++ /dev/null @@ -1,157 +0,0 @@ -# Markdown 语法笔记 - -Markdown 是一种轻量级标记语言,用简单的符号即可写出排版良好的文档。 - -## 一、标题 - -使用 `#` 号表示标题,`#` 的数量代表标题级别(最多六级)。 - -```markdown -# 一级标题 - -## 二级标题 - -### 三级标题 - -#### 四级标题 - -##### 五级标题 - -###### 六级标题 -``` - -显示效果: - -# 一级标题 - -## 二级标题 - -### 三级标题 - -## 二、代码段 - -使用三个反引号 ` ``` ` 包裹代码,反引号后可标注语言以启用语法高亮。 - -```c -#include - -int main() -{ - return 0; -} -``` - -## 三、表格 - -使用 `|` 分隔单元格,第二行的 `---` 用于分隔表头和内容。 - -```markdown -| t1 | t2 | -| ---- | ---- | -| 1 | 2 | -``` - -显示效果: - -| t1 | t2 | -| ---- | ---- | -| 1 | 2 | - -对齐方式: - -| 语法 | 对齐方式 | -| ---- | ---- | -| `:---` | 左对齐 | -| `:---:` | 居中对齐 | -| `---:` | 右对齐 | - -## 四、行内代码(高亮语法) - -使用单个反引号包裹文字,可以 `高亮语法`,例如 `printf("hello")`。 - -## 五、字体样式 - -```markdown -**加粗字体** -*斜体字体* -***加粗斜体*** -~~删除线~~ -``` - -显示效果: - -**加粗字体** - -*斜体字体* - -***加粗斜体*** - -~~删除线~~ - -## 六、列表 - -无序列表使用 `-` 或 `*`: - -```markdown -- 列表项 1 -- 列表项 2 - - 嵌套列表项 -``` - -显示效果: - -- 列表项 1 -- 列表项 2 - - 嵌套列表项 - -有序列表使用数字加点: - -1. 第一项 -2. 第二项 -3. 第三项 - -## 七、引用 - -使用 `>` 表示引用: - -```markdown -> 这是一段引用内容 -``` - -显示效果: - -> 这是一段引用内容 - -## 八、分隔线 - -使用三个或以上的 `-` 或 `*`: - -```markdown ---- -``` - -显示效果: - ---- - -## 九、图片 - -```markdown -![图片描述](figures/图片路径.png) -``` - -## 十、常用语法速查表 - -| 语法 | 说明 | 示例 | -| ---- | ---- | ---- | -| `#` | 标题 | `# 一级标题` | -| `**文字**` | 加粗 | **加粗** | -| `*文字*` | 斜体 | *斜体* | -| `` `文字` `` | 行内代码 | `高亮` | -| ` ``` ` | 代码块 | 见上文代码段 | -| `>` | 引用 | > 引用 | -| `-` | 无序列表 | - 列表项 | -| `1.` | 有序列表 | 1. 列表项 | -| `[文字](链接)` | 超链接 | [链接](https://www.rt-thread.org/) | -| `![描述](路径)` | 图片 | 见上文示例 | -| `---` | 分隔线 | 见上文示例 | diff --git "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/\347\272\277\347\250\213\351\227\264\345\220\214\346\255\245\344\270\216\351\200\232\344\277\241.md" "b/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/\347\272\277\347\250\213\351\227\264\345\220\214\346\255\245\344\270\216\351\200\232\344\277\241.md" deleted file mode 100644 index b1784dd9a32b01a6c0c309985704012fd3059f2c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\242\201\345\205\210\346\261\207/\347\254\224\350\256\260/\347\272\277\347\250\213\351\227\264\345\220\214\346\255\245\344\270\216\351\200\232\344\277\241.md" +++ /dev/null @@ -1,164 +0,0 @@ -# RT-Thread 线程间同步与通信 - -> 对应《RT-Thread 内核学习手册》第 5 章「线程间同步」与第 6 章「线程间通信」,也是第三天作业的主题。 -> 环境:RT-Thread 4.1.1 日期:2026-08-20 - -## 一、为什么需要同步与通信 - -多线程共享内存时,会出现两类问题: - -1. **竞态(Race Condition)**:多个线程同时读写同一变量,结果取决于调度顺序,可能出错。 -2. **协调问题**:线程之间需要互相等待(同步)、交换数据(通信)。 - -内核提供两类机制: - -| 类型 | 机制 | 解决什么问题 | -| --- | --- | --- | -| 同步 | 信号量、互斥量、事件集 | 多个线程访问共享资源的先后/互斥关系 | -| 通信 | 消息队列、邮箱 | 线程之间传递数据 | - -## 二、信号量(Semaphore) - -### 概念 - -- 维护一个**计数值** `value`: - - `rt_sem_take` 成功 → 计数减 1;计数为 0 时 `take` 阻塞(或超时返回)。 - - `rt_sem_release` → 计数加 1,并唤醒一个等待线程。 -- **计数信号量**:初始值 > 1,表示"可用资源的数量"(如 3 个车位)。 -- **二值信号量**:初始值 0/1,可当作简单的"锁"或"事件通知"。 - -### API(4.1.1) - -```c -rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag); -rt_err_t rt_sem_delete(rt_sem_t sem); -rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout); /* RT_WAITING_FOREVER 或 0/毫秒 */ -rt_err_t rt_sem_trytake(rt_sem_t sem); /* 不等待,拿不到立即返回 */ -rt_err_t rt_sem_release(rt_sem_t sem); -``` - -- 超时用 `rt_tick_from_millisecond(ms)` 转换;超时返回 `-RT_ETIMEOUT`。 -- `flag` 一般用 `RT_IPC_FLAG_PRIO`(按优先级唤醒)。 - -### 典型场景(作业练习一:停车场) - -初始值 3(3 个车位),5 辆车 `take`,最多 3 辆进入;车离开时 `release`。**任意时刻场内车辆 ≤ 3** 由信号量保证。 - -### 易错点:优先级反转 - -低优先级线程持有信号量时,高优先级线程 `take` 阻塞;若中优先级线程抢占了低优先级线程,高优先级线程会一直等不到——即"优先级反转"。信号量本身**不会**处理它,需要靠**互斥量**的优先级继承机制。 - -## 三、互斥量(Mutex) - -### 概念 - -- 同一时刻**只能被一个线程持有**,本质是二值锁。 -- **有所有权**:只能由持有它的线程 `release`;支持**递归**(同一线程可重复 take)。 -- **优先级继承**:当高优先级线程被低优先级持有者阻塞时,系统临时提升持有者优先级,缓解优先级反转。 - -### 与二值信号量的区别 - -| | 互斥量 | 二值信号量 | -| --- | --- | --- | -| 所有权 | 有,只能持有者释放 | 无,任何线程可 release | -| 优先级继承 | 有 | 无 | -| 用途 | 保护临界区(互斥访问) | 资源计数 / 事件通知 | - -### API - -```c -rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag); -rt_err_t rt_mutex_delete(rt_mutex_t mutex); -rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout); -rt_err_t rt_mutex_release(rt_mutex_t mutex); -``` - -### 关键:临界区要"整体加锁"(作业练习二) - -"检查余票 + 减票 + 记录 + 打印"必须作为一个临界区,用同一把互斥量包住。只给 `ticket_count--` 加锁不安全:两个线程可能同时通过 `if (count > 0)` 检查,导致重复票号或余票变负。对比实验(`ticket_demo_race`)用"不加锁 + 检查与修改间插 5ms 延时"制造了竞态。 - -## 四、事件集(Event Set) - -### 概念 - -- 用 32 位整数的每一位表示一个事件,一次性**同时等待多个条件**。 -- 三种模式: - - `RT_EVENT_FLAG_AND`:所有指定位都置 1 才返回; - - `RT_EVENT_FLAG_OR`:任一位置 1 即返回; - - `RT_EVENT_FLAG_CLEAR`:满足条件返回时自动清零(可叠加到 AND/OR 上)。 - -### API - -```c -rt_event_t rt_event_create(const char *name, rt_uint8_t flag); -rt_err_t rt_event_delete(rt_event_t event); -rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set); -rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, rt_uint8_t opt, - rt_int32_t timeout, rt_uint32_t *recved); -``` - -### 典型场景(作业练习三:系统启动检查) - -网络/传感器/存储三个初始化线程各自完成后 `rt_event_send` 对应位;主业务线程用 `RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR` 一次等齐三个位再启动。超时测试:storage 5s、业务线程只等 3s → 超时返回 `-RT_ETIMEOUT`,定位"谁没就绪"后继续等。 - -### 4.1.1 的坑 - -`rt_event_recv` **超时返回时不会回填 `recved`**(`recved` 只在成功路径被写入)。所以超时后想判断"哪个模块没就绪",要用 `timeout = 0` 对每个位单独 `recv`(AND、不加 CLEAR)轮询。超时时已置位的事件不会被清除,可继续等待。 - -## 五、消息队列(Message Queue) - -### 概念 - -- 以**消息**为单位(每条固定大小 `msg_size`)传递数据,先进先出。 -- 适合生产者-消费者模型:生产线程 `send`,消费线程 `recv`,队列自带互斥与阻塞等待。 - -### API - -```c -rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, - rt_size_t max_msgs, rt_uint8_t flag); -rt_err_t rt_mq_delete(rt_mq_t mq); -rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size); -rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout); -``` - -- 队列满时 `rt_mq_send` 返回 `-RT_EFULL`(不阻塞);可用 `rt_mq_send_wait` 阻塞发送。 -- 队列空时 `rt_mq_recv` 阻塞(或超时)。 - -### 典型场景(作业练习四:快递分拣) - -两个发件线程各发 5 条 `struct package_msg`,分拣线程 `rt_mq_recv` 分类统计。队列满时记录失败并延时重发;发件完成后发特殊"结束消息"(约定 `region = 0xFF`),分拣线程收到 2 条结束消息后打印统计退出。 - -## 六、邮箱(Mailbox,了解) - -- 比消息队列更轻量:每条消息是一个 **4 字节**字(可放整数或指针),邮箱中每个格子只占 4 字节。 -- API:`rt_mb_create` / `rt_mb_send` / `rt_mb_recv`。 -- 需要传结构体/多字节数据时用消息队列,传小数据/句柄时用邮箱。 - -## 七、API 汇总 - -| 机制 | 创建 | 删除 | 获取/发送 | 释放/接收 | -| --- | --- | --- | --- | --- | -| 信号量 | `rt_sem_create` | `rt_sem_delete` | `rt_sem_take` | `rt_sem_release` | -| 互斥量 | `rt_mutex_create` | `rt_mutex_delete` | `rt_mutex_take` | `rt_mutex_release` | -| 事件集 | `rt_event_create` | `rt_event_delete` | `rt_event_recv` | `rt_event_send` | -| 消息队列 | `rt_mq_create` | `rt_mq_delete` | `rt_mq_recv` | `rt_mq_send` | -| 邮箱 | `rt_mb_create` | `rt_mb_delete` | `rt_mb_recv` | `rt_mb_send` | - -> 说明:事件集用 `send` 置位、`recv` 等待,方向与"发送者/接收者"对应;其余四个都是 take/recv 获取、release/send 释放。 - -## 八、与今天作业的对应 - -| 作业练习 | 用到的机制 | 要点 | -| --- | --- | --- | -| 练习一 停车场 | 计数信号量 | 有限资源计数,任意时刻 ≤ 3 | -| 练习二 售票 | 互斥量 | 临界区整体加锁;对比实验演示竞态 | -| 练习三 启动检查 | 事件集 | AND 等待多条件 + 超时定位未就绪模块 | -| 练习四 快递分拣 | 消息队列 | 生产者-消费者 + 结束消息收尾 | - -## 九、使用建议 - -- 线程与 IPC 对象**创建后都要检查返回值**(`RT_NULL` / 非 `RT_EOK`)。 -- 动态线程(`rt_thread_create`)入口函数返回后由系统自动回收;静态线程(`rt_thread_init`)不会。 -- MSH 命令重复执行时,要处理同名线程/IPC 对象残留(运行标志 + 结束后主动删除对象)。 -- 选择原则:资源计数 → 信号量;保护临界区 → 互斥量;多条件等待 → 事件集;传数据 → 消息队列/邮箱。 diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\343\200\220RSOC26\343\200\221\347\254\254\344\270\200\346\254\241\345\255\246\344\271\240.md" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\343\200\220RSOC26\343\200\221\347\254\254\344\270\200\346\254\241\345\255\246\344\271\240.md" deleted file mode 100644 index 447c768b34ce8b93cb6e5efa826af8603d445f69..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\343\200\220RSOC26\343\200\221\347\254\254\344\270\200\346\254\241\345\255\246\344\271\240.md" +++ /dev/null @@ -1,77 +0,0 @@ -# DAY1 学习记录 - -# 一、Markdown 基本语法学习 - -今天学习了 Markdown 的常用写法,包括标题、代码块、表格、行内代码、加粗和斜体等内容,后续可以用 Typora 更方便地整理课程笔记和实验过程。 - -## 1. 标题语法 - -``` -# 一级标题 -## 二级标题 -### 三级标题 -``` - -## 2. 常用格式 - -`行内代码` -**加粗** -***斜体*** - -# 二、Git 与 Gitee 基础操作 - -今天学习了 Git 和 Gitee 的基础使用,主要包括用户信息配置、远程仓库连接、文件提交、分支创建和远程推送。 - -## 1. 基本配置与连接 - -``` -git config --global user.name "用户名" # 配置提交时显示的用户名 -git config --global user.email "邮箱" # 配置提交时显示的邮箱 -git config --list # 查看当前 Git 配置 -ssh -T git@gitee.com # 测试 SSH 是否能正常连接 Gitee -git remote -v # 查看当前远程仓库地址 -git remote set-url origin 仓库地址 # 修改当前仓库的远程地址 -``` - -## 2. 仓库同步与提交 - -``` -git init # 初始化当前目录为 Git 仓库 -git fetch origin master # 同步远程 master 分支信息到本地 -git add file/path # 添加文件到暂存区 -git commit -m "message" # 提交暂存区内容并填写提交说明 -git pull # 拉取远程内容到本地 -git push origin master # 将本地内容推送到远程仓库 -``` - -## 3. 分支操作 - -``` -git checkout -b test # 基于当前分支创建并切换到 test 分支 -``` - -# 三、RT-Thread 工程与调试 - -今天还学习了 RT-Thread Studio 中工程创建、编译、下载和调试的基本流程。 - -## 工程创建与调试 - -- 检查 ST-Link 驱动是否正常 -- 基于 RT-Thread 4.1.1 创建 STM32F407ZG 工程 -- 编译工程并生成可执行文件 -- 将程序下载到开发板 -- 使用 ST-Link 进行断点调试 - -工程调试成功后,程序可以停在 `main()` 函数中,说明工程已经正常下载并运行。 - -# 四、RT-Thread 启动流程 - -通过官网提供的启动流程图,了解了 RT-Thread 从启动文件到用户 `main()` 函数的大致执行过程。 - -- `startup_xx.S` 先执行 -- 进入编译器相关入口函数 -- 调用 `rtthread_startup()` -- 初始化硬件、定时器和调度器 -- 创建主线程、定时器线程和空闲线程 -- 启动系统调度器 -- 最后进入 `main()` 函数 \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817112957813.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817112957813.png" deleted file mode 100644 index 8cbefd2821fc003691fc15a62e6fc8b10e3fa467..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817112957813.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817113502159.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817113502159.png" deleted file mode 100644 index 685d7fb5b5eeefc01cfad53b85e4cac38945469b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817113502159.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817142648941.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817142648941.png" deleted file mode 100644 index 4bf38e6b886dfa690e5a7d726f15856157c6115b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817142648941.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817143835258.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817143835258.png" deleted file mode 100644 index 2ca7c09e48ba4d65780d5a1a5792b9c101bc3ecb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817143835258.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817144051657.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817144051657.png" deleted file mode 100644 index b3b6900b033d2ba826f94883eab89d955ede7434..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817144051657.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817150046870.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817150046870.png" deleted file mode 100644 index 95ab187ec4ddb3bd3eb788ab13f0dea61a728a80..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817150046870.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817150446388.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817150446388.png" deleted file mode 100644 index c0435456a9dbaaf4aa0dbb0f6c12ccc6d3230ecb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817150446388.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817151244692.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817151244692.png" deleted file mode 100644 index adf1c1cd27e5acbf0b9d5aa20a8ffebf8f0fbdc5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817151244692.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817152048721.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817152048721.png" deleted file mode 100644 index 4e39cef8d61f44e369319918be0b95fc35beace8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817152048721.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817152317118.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817152317118.png" deleted file mode 100644 index 74f66f4df025334e505c57b42daffa5aa8db5d48..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260817152317118.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818140556194.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818140556194.png" deleted file mode 100644 index 35ad6963fe9d9e24b90b02a92da4a88996349be1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818140556194.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818164439722.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818164439722.png" deleted file mode 100644 index a7a945d850b8166e44fe27032c825f6fc4120d9e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818164439722.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818202039012.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818202039012.png" deleted file mode 100644 index 8aeb7b88e692feaf17ef61fcf1f8480a5987806a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818202039012.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210556117.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210556117.png" deleted file mode 100644 index c22ddb4cc9edf0f30552b564c6c478f4de57f996..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210556117.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210605960.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210605960.png" deleted file mode 100644 index c22ddb4cc9edf0f30552b564c6c478f4de57f996..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210605960.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210732275.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210732275.png" deleted file mode 100644 index af7b05423b82c2281ec468ea8cd1ced2e2e4a3c5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210732275.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210821589.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210821589.png" deleted file mode 100644 index 4a0f90d7a951c426897f02ccb2eac4a0982376a5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818210821589.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818211005741.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818211005741.png" deleted file mode 100644 index ff0da8904baeebbbd649d90ffadf5181b08d7389..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818211005741.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818211045732.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818211045732.png" deleted file mode 100644 index 6e56fc96a1b0a365e022e9f933be18b720ee294a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260818211045732.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819144141632.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819144141632.png" deleted file mode 100644 index 3ee96e6ac8528e5088c75d32c3665f0f96fd5714..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819144141632.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819150611545.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819150611545.png" deleted file mode 100644 index 33080ef05321f65e8c45b64c205bb22af2958773..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819150611545.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819150625868.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819150625868.png" deleted file mode 100644 index 689b648403c348a4a9675a375dbd1f5638390c15..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819150625868.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819152800741.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819152800741.png" deleted file mode 100644 index 33080ef05321f65e8c45b64c205bb22af2958773..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819152800741.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819155711526.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819155711526.png" deleted file mode 100644 index fd5ff0b2aa64dd9f143979addca012c5288f82ac..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819155711526.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819165949131.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819165949131.png" deleted file mode 100644 index d91a94d74a42ec2dbefbeebc03c736228db8b8dd..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/image-20260819165949131.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/rtt_startup.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/rtt_startup.png" deleted file mode 100644 index 04dbb79fed05b7463c5ceea179f5dd75c6553724..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/rtt_startup.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102048.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102048.png" deleted file mode 100644 index 800ec298cc4dd2fa7599d475a0c8f3bf52a3eecb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102048.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102442.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102442.png" deleted file mode 100644 index 95e8ab4ef99114778b540f5d2755243bfb511a95..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102442.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 104304.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 104304.png" deleted file mode 100644 index e4fb0f0ce5fae09fda464fe2b060422cbe7ad598..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 104304.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" deleted file mode 100644 index f673e5b3c2b689b5d639ae6c31868fc0d92608e2..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 xlc the first version - */ -#include - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -#define PACKAGE_QUEUE_LEN 5 -#define PACKAGE_SEND_COUNT 5 -#define PACKAGE_STACK_SIZE 1024 -#define PACKAGE_END_REGION 0xFF - -static rt_mq_t package_mq = RT_NULL; -static rt_sem_t sender_done_sem = RT_NULL; -static rt_sem_t receiver_done_sem = RT_NULL; -static rt_mutex_t stat_mutex = RT_NULL; - -static struct rt_thread sender_a_thread; -static struct rt_thread sender_b_thread; -static struct rt_thread receiver_thread; - -static rt_uint8_t sender_a_stack[PACKAGE_STACK_SIZE]; -static rt_uint8_t sender_b_stack[PACKAGE_STACK_SIZE]; -static rt_uint8_t receiver_stack[PACKAGE_STACK_SIZE]; - -static volatile rt_uint8_t demo_running = 0; - -static rt_uint32_t total_count = 0; -static rt_uint32_t east_count = 0; -static rt_uint32_t south_count = 0; -static rt_uint32_t north_count = 0; -static rt_uint32_t total_weight = 0; -static rt_uint32_t send_failed = 0; - -static const char *region_name(rt_uint8_t region) -{ - switch (region) - { - case 1: return "east"; - case 2: return "south"; - case 3: return "north"; - default: return "unknown"; - } -} - -static void stat_add(rt_uint8_t region, rt_uint16_t weight) -{ - rt_mutex_take(stat_mutex, RT_WAITING_FOREVER); - - total_count++; - total_weight += weight; - - if (region == 1) east_count++; - else if (region == 2) south_count++; - else if (region == 3) north_count++; - - rt_mutex_release(stat_mutex); -} - -static void stat_add_send_failed(void) -{ - rt_mutex_take(stat_mutex, RT_WAITING_FOREVER); - send_failed++; - rt_mutex_release(stat_mutex); -} - -static void print_stats(void) -{ - rt_uint32_t tc, ec, sc, nc, tw, sf; - - rt_mutex_take(stat_mutex, RT_WAITING_FOREVER); - tc = total_count; - ec = east_count; - sc = south_count; - nc = north_count; - tw = total_weight; - sf = send_failed; - rt_mutex_release(stat_mutex); - - rt_kprintf("total packages: %u\n", tc); - rt_kprintf("east: %u, south: %u, north: %u\n", ec, sc, nc); - rt_kprintf("total weight: %u g\n", tw); - rt_kprintf("send failed: %u\n", sf); -} - -static void send_end_message(void) -{ - struct package_msg end_msg; - - end_msg.id = 0; - end_msg.region = PACKAGE_END_REGION; - end_msg.weight = 0; - - while (rt_mq_send(package_mq, &end_msg, sizeof(end_msg)) == -RT_EFULL) - { - rt_thread_mdelay(10); - } -} - -static void sender_thread_entry(void *parameter) -{ - rt_uint32_t sender_id = (rt_uint32_t)(rt_ubase_t)parameter; - rt_uint32_t i; - struct package_msg msg; - - rt_thread_mdelay(100); - - for (i = 0; i < PACKAGE_SEND_COUNT; i++) - { - if (sender_id == 1) - { - static const rt_uint8_t regions[PACKAGE_SEND_COUNT] = {1, 2, 3, 1, 2}; - static const rt_uint16_t weights[PACKAGE_SEND_COUNT] = {1200, 850, 960, 1100, 780}; - msg.id = 1000 + i + 1; - msg.region = regions[i]; - msg.weight = weights[i]; - } - else - { - static const rt_uint8_t regions[PACKAGE_SEND_COUNT] = {2, 3, 1, 3, 2}; - static const rt_uint16_t weights[PACKAGE_SEND_COUNT] = {920, 1040, 880, 1260, 790}; - msg.id = 2000 + i + 1; - msg.region = regions[i]; - msg.weight = weights[i]; - } - - if (rt_mq_send(package_mq, &msg, sizeof(msg)) != RT_EOK) - { - stat_add_send_failed(); - rt_kprintf("sender %u send failed for package %u\n", sender_id, msg.id); - } - - rt_thread_mdelay(60 + sender_id * 20); - } - - rt_sem_release(sender_done_sem); -} - -static void receiver_thread_entry(void *parameter) -{ - struct package_msg msg; - rt_err_t result; - - RT_UNUSED(parameter); - - while (1) - { - result = rt_mq_recv(package_mq, &msg, sizeof(msg), RT_WAITING_FOREVER); - if (result == RT_EOK) - { - if (msg.id == 0 && msg.region == PACKAGE_END_REGION) - { - break; - } - - rt_kprintf("package %u -> %s, weight: %u g\n", - msg.id, region_name(msg.region), msg.weight); - stat_add(msg.region, msg.weight); - } - } - - print_stats(); - rt_sem_release(receiver_done_sem); -} - -int package_demo(int argc, char **argv) -{ - rt_err_t result; - rt_uint8_t started_sender_cnt = 0; - - RT_UNUSED(argc); - RT_UNUSED(argv); - - if (demo_running) - { - rt_kprintf("package demo is already running\n"); - return -RT_ERROR; - } - - demo_running = 1; - - package_mq = rt_mq_create("pkgmq", sizeof(struct package_msg), PACKAGE_QUEUE_LEN, RT_IPC_FLAG_PRIO); - if (package_mq == RT_NULL) - { - rt_kprintf("create package mq failed\n"); - demo_running = 0; - return -RT_ERROR; - } - - sender_done_sem = rt_sem_create("snddn", 0, RT_IPC_FLAG_PRIO); - if (sender_done_sem == RT_NULL) - { - rt_kprintf("create sender done sem failed\n"); - rt_mq_delete(package_mq); - package_mq = RT_NULL; - demo_running = 0; - return -RT_ERROR; - } - - receiver_done_sem = rt_sem_create("rcvdn", 0, RT_IPC_FLAG_PRIO); - if (receiver_done_sem == RT_NULL) - { - rt_kprintf("create receiver done sem failed\n"); - rt_sem_delete(sender_done_sem); - sender_done_sem = RT_NULL; - rt_mq_delete(package_mq); - package_mq = RT_NULL; - demo_running = 0; - return -RT_ERROR; - } - - stat_mutex = rt_mutex_create("pkgmtx", RT_IPC_FLAG_PRIO); - if (stat_mutex == RT_NULL) - { - rt_kprintf("create stat mutex failed\n"); - rt_sem_delete(receiver_done_sem); - receiver_done_sem = RT_NULL; - rt_sem_delete(sender_done_sem); - sender_done_sem = RT_NULL; - rt_mq_delete(package_mq); - package_mq = RT_NULL; - demo_running = 0; - return -RT_ERROR; - } - - total_count = 0; - east_count = 0; - south_count = 0; - north_count = 0; - total_weight = 0; - send_failed = 0; - - result = rt_thread_init(&receiver_thread, - "pkgrcv", - receiver_thread_entry, - RT_NULL, - receiver_stack, - sizeof(receiver_stack), - 20, - 10); - if (result != RT_EOK) - { - rt_kprintf("init receiver thread failed, err=%d\n", result); - goto fail; - } - - result = rt_thread_init(&sender_a_thread, - "sndA", - sender_thread_entry, - (void *)(rt_ubase_t)1, - sender_a_stack, - sizeof(sender_a_stack), - 21, - 10); - if (result != RT_EOK) - { - rt_kprintf("init sender A thread failed, err=%d\n", result); - goto fail; - } - - result = rt_thread_init(&sender_b_thread, - "sndB", - sender_thread_entry, - (void *)(rt_ubase_t)2, - sender_b_stack, - sizeof(sender_b_stack), - 21, - 10); - if (result != RT_EOK) - { - rt_kprintf("init sender B thread failed, err=%d\n", result); - goto fail; - } - - result = rt_thread_startup(&receiver_thread); - if (result != RT_EOK) - { - rt_kprintf("startup receiver thread failed, err=%d\n", result); - goto fail; - } - - result = rt_thread_startup(&sender_a_thread); - if (result == RT_EOK) - started_sender_cnt++; - else - rt_kprintf("startup sender A thread failed, err=%d\n", result); - - result = rt_thread_startup(&sender_b_thread); - if (result == RT_EOK) - started_sender_cnt++; - else - rt_kprintf("startup sender B thread failed, err=%d\n", result); - - while (started_sender_cnt > 0) - { - rt_sem_take(sender_done_sem, RT_WAITING_FOREVER); - started_sender_cnt--; - } - - send_end_message(); - rt_sem_take(receiver_done_sem, RT_WAITING_FOREVER); - - rt_mutex_delete(stat_mutex); - stat_mutex = RT_NULL; - - rt_sem_delete(receiver_done_sem); - receiver_done_sem = RT_NULL; - - rt_sem_delete(sender_done_sem); - sender_done_sem = RT_NULL; - - rt_mq_delete(package_mq); - package_mq = RT_NULL; - - demo_running = 0; - - rt_kprintf("package demo finished\n"); - return RT_EOK; - -fail: - if (stat_mutex != RT_NULL) - { - rt_mutex_delete(stat_mutex); - stat_mutex = RT_NULL; - } - - if (receiver_done_sem != RT_NULL) - { - rt_sem_delete(receiver_done_sem); - receiver_done_sem = RT_NULL; - } - - if (sender_done_sem != RT_NULL) - { - rt_sem_delete(sender_done_sem); - sender_done_sem = RT_NULL; - } - - if (package_mq != RT_NULL) - { - rt_mq_delete(package_mq); - package_mq = RT_NULL; - } - - demo_running = 0; - return result; -} - -MSH_CMD_EXPORT(package_demo, package sort demo); diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" deleted file mode 100644 index 861a50643da0c8e666d03cc9e4d80cb7b54ad9ff..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" +++ /dev/null @@ -1,212 +0,0 @@ -#include - -#define CAR_COUNT 5 -#define PARKING_SPACES 3 -#define CAR_STACK_SIZE 1024 -#define MONITOR_STACK_SIZE 1024 - -static rt_sem_t parking_sem = RT_NULL; -static rt_mutex_t count_mutex = RT_NULL; - -static struct rt_thread car_threads[CAR_COUNT]; -static rt_uint8_t car_stacks[CAR_COUNT][CAR_STACK_SIZE]; - -static struct rt_thread monitor_thread; -static rt_uint8_t monitor_stack[MONITOR_STACK_SIZE]; - -static volatile rt_uint8_t demo_running = 0; -static volatile rt_uint8_t cars_ready = 0; -static volatile rt_uint8_t started_cars = 0; -static volatile rt_uint8_t finished_cars = 0; -static volatile rt_uint8_t used_spaces = 0; - -static void parking_reset_state(void) -{ - cars_ready = 0; - started_cars = 0; - finished_cars = 0; - used_spaces = 0; - demo_running = 0; -} - -static void parking_cleanup_ipc(void) -{ - if (parking_sem != RT_NULL) - { - rt_sem_delete(parking_sem); - parking_sem = RT_NULL; - } - - if (count_mutex != RT_NULL) - { - rt_mutex_delete(count_mutex); - count_mutex = RT_NULL; - } -} - -static void car_thread_entry(void *parameter) -{ - rt_uint32_t car_id = (rt_uint32_t)parameter; - rt_err_t result; - rt_tick_t timeout = rt_tick_from_millisecond(2000); - - rt_thread_mdelay(car_id * 300); - - while (1) - { - result = rt_sem_take(parking_sem, timeout); - if (result == RT_EOK) - { - rt_mutex_take(count_mutex, RT_WAITING_FOREVER); - used_spaces++; - rt_kprintf("car%d entered, used spaces: %d\n", car_id, used_spaces); - rt_mutex_release(count_mutex); - - rt_thread_mdelay(2500 + car_id * 200); - - rt_mutex_take(count_mutex, RT_WAITING_FOREVER); - if (used_spaces > 0) - used_spaces--; - rt_kprintf("car%d left, used spaces: %d\n", car_id, used_spaces); - rt_mutex_release(count_mutex); - - rt_sem_release(parking_sem); - break; - } - else if (result == -RT_ETIMEOUT) - { - rt_kprintf("car%d waiting for a space\n", car_id); - rt_thread_mdelay(500); - } - else - { - rt_kprintf("car%d failed to take parking space, err=%d\n", car_id, result); - rt_thread_mdelay(500); - } - } - - rt_mutex_take(count_mutex, RT_WAITING_FOREVER); - finished_cars++; - rt_mutex_release(count_mutex); -} - -static void monitor_thread_entry(void *parameter) -{ - RT_UNUSED(parameter); - - while (1) - { - if (!cars_ready) - { - rt_thread_mdelay(200); - continue; - } - - rt_mutex_take(count_mutex, RT_WAITING_FOREVER); - if (finished_cars >= started_cars) - { - rt_mutex_release(count_mutex); - break; - } - rt_mutex_release(count_mutex); - - rt_thread_mdelay(200); - } - - parking_reset_state(); - parking_cleanup_ipc(); - - rt_kprintf("parking demo finished\n"); -} - -int parking_demo(int argc, char **argv) -{ - rt_uint8_t i; - rt_err_t result; - - if (demo_running) - { - rt_kprintf("parking demo is already running\n"); - return -RT_ERROR; - } - - demo_running = 1; - cars_ready = 0; - started_cars = 0; - finished_cars = 0; - used_spaces = 0; - - parking_sem = rt_sem_create("parksem", PARKING_SPACES, RT_IPC_FLAG_PRIO); - if (parking_sem == RT_NULL) - { - rt_kprintf("create parking semaphore failed\n"); - parking_reset_state(); - return -RT_ERROR; - } - - count_mutex = rt_mutex_create("parkmtx", RT_IPC_FLAG_PRIO); - if (count_mutex == RT_NULL) - { - rt_kprintf("create count mutex failed\n"); - parking_cleanup_ipc(); - parking_reset_state(); - return -RT_ERROR; - } - - result = rt_thread_init(&monitor_thread, - "parkmon", - monitor_thread_entry, - RT_NULL, - monitor_stack, - sizeof(monitor_stack), - 21, - 10); - if (result != RT_EOK) - { - rt_kprintf("init monitor thread failed, err=%d\n", result); - parking_cleanup_ipc(); - parking_reset_state(); - return result; - } - - result = rt_thread_startup(&monitor_thread); - if (result != RT_EOK) - { - rt_kprintf("startup monitor thread failed, err=%d\n", result); - parking_cleanup_ipc(); - parking_reset_state(); - return result; - } - - for (i = 0; i < CAR_COUNT; i++) - { - result = rt_thread_init(&car_threads[i], - "car", - car_thread_entry, - (void *)(rt_ubase_t)(i + 1), - car_stacks[i], - sizeof(car_stacks[i]), - 20, - 10); - if (result != RT_EOK) - { - rt_kprintf("init car%d thread failed, err=%d\n", i + 1, result); - continue; - } - - result = rt_thread_startup(&car_threads[i]); - if (result != RT_EOK) - { - rt_kprintf("startup car%d thread failed, err=%d\n", i + 1, result); - continue; - } - - started_cars++; - } - - cars_ready = 1; - - return RT_EOK; -} - -MSH_CMD_EXPORT(parking_demo, parking lot semaphore demo); diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" deleted file mode 100644 index 71365b84ad9b82de4645f396dcbb4477750d54ed..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 xlc the first version - */ -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -#define THREAD_STACK_SIZE 1024 -#define THREAD_PRIORITY 20 -#define THREAD_TIMESLICE 10 - -static rt_event_t startup_event = RT_NULL; -static rt_mutex_t state_mutex = RT_NULL; - -static rt_thread_t net_thread = RT_NULL; -static rt_thread_t sensor_thread = RT_NULL; -static rt_thread_t storage_thread = RT_NULL; -static rt_thread_t business_thread = RT_NULL; - -static rt_uint8_t net_stack[THREAD_STACK_SIZE]; -static rt_uint8_t sensor_stack[THREAD_STACK_SIZE]; -static rt_uint8_t storage_stack[THREAD_STACK_SIZE]; -static rt_uint8_t business_stack[THREAD_STACK_SIZE]; - -static struct rt_thread net_th; -static struct rt_thread sensor_th; -static struct rt_thread storage_th; -static struct rt_thread business_th; - -static volatile rt_uint8_t demo_running = 0; -static volatile rt_uint32_t ready_flags = 0; - -static void set_ready_flag(rt_uint32_t flag) -{ - rt_mutex_take(state_mutex, RT_WAITING_FOREVER); - ready_flags |= flag; - rt_mutex_release(state_mutex); -} - -static rt_uint32_t get_ready_flag(void) -{ - rt_uint32_t flags; - - rt_mutex_take(state_mutex, RT_WAITING_FOREVER); - flags = ready_flags; - rt_mutex_release(state_mutex); - - return flags; -} - -static void net_init_entry(void *parameter) -{ - RT_UNUSED(parameter); - - rt_thread_mdelay(1000); - set_ready_flag(EVENT_NET_READY); - rt_event_send(startup_event, EVENT_NET_READY); - rt_kprintf("network ready\n"); -} - -static void sensor_init_entry(void *parameter) -{ - RT_UNUSED(parameter); - - rt_thread_mdelay(2000); - set_ready_flag(EVENT_SENSOR_READY); - rt_event_send(startup_event, EVENT_SENSOR_READY); - rt_kprintf("sensor ready\n"); -} - -static void storage_init_entry(void *parameter) -{ - RT_UNUSED(parameter); - - rt_thread_mdelay(5000); - set_ready_flag(EVENT_STORAGE_READY); - rt_event_send(startup_event, EVENT_STORAGE_READY); - rt_kprintf("storage ready\n"); -} - -static void business_entry(void *parameter) -{ - rt_err_t result; - rt_uint32_t recved; - - RT_UNUSED(parameter); - - while (1) - { - result = rt_event_recv(startup_event, - EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - rt_tick_from_millisecond(3000), - &recved); - if (result == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - break; - } - else if (result == -RT_ETIMEOUT) - { - rt_uint32_t flags = get_ready_flag(); - - rt_kprintf("wait modules timeout, "); - if ((flags & EVENT_STORAGE_READY) == 0) - { - rt_kprintf("storage is not ready"); - } - else if ((flags & EVENT_SENSOR_READY) == 0) - { - rt_kprintf("sensor is not ready"); - } - else if ((flags & EVENT_NET_READY) == 0) - { - rt_kprintf("network is not ready"); - } - else - { - rt_kprintf("modules are still not fully ready"); - } - rt_kprintf("\n"); - } - else - { - rt_kprintf("wait event failed, err=%d\n", result); - rt_thread_mdelay(200); - } - } -} - -int startup_demo(int argc, char **argv) -{ - rt_err_t result; - - RT_UNUSED(argc); - RT_UNUSED(argv); - - if (demo_running) - { - rt_kprintf("startup demo is already running\n"); - return -RT_ERROR; - } - - demo_running = 1; - ready_flags = 0; - - startup_event = rt_event_create("stpev", RT_IPC_FLAG_PRIO); - if (startup_event == RT_NULL) - { - rt_kprintf("create event failed\n"); - demo_running = 0; - return -RT_ERROR; - } - - state_mutex = rt_mutex_create("stpmtx", RT_IPC_FLAG_PRIO); - if (state_mutex == RT_NULL) - { - rt_kprintf("create mutex failed\n"); - rt_event_delete(startup_event); - startup_event = RT_NULL; - demo_running = 0; - return -RT_ERROR; - } - - result = rt_thread_init(&net_th, - "netini", - net_init_entry, - RT_NULL, - net_stack, - sizeof(net_stack), - THREAD_PRIORITY, - THREAD_TIMESLICE); - if (result != RT_EOK) - { - rt_kprintf("init network thread failed, err=%d\n", result); - rt_mutex_delete(state_mutex); - state_mutex = RT_NULL; - rt_event_delete(startup_event); - startup_event = RT_NULL; - demo_running = 0; - return result; - } - - result = rt_thread_init(&sensor_th, - "sensi", - sensor_init_entry, - RT_NULL, - sensor_stack, - sizeof(sensor_stack), - THREAD_PRIORITY, - THREAD_TIMESLICE); - if (result != RT_EOK) - { - rt_kprintf("init sensor thread failed, err=%d\n", result); - rt_mutex_delete(state_mutex); - state_mutex = RT_NULL; - rt_event_delete(startup_event); - startup_event = RT_NULL; - demo_running = 0; - return result; - } - - result = rt_thread_init(&storage_th, - "store", - storage_init_entry, - RT_NULL, - storage_stack, - sizeof(storage_stack), - THREAD_PRIORITY, - THREAD_TIMESLICE); - if (result != RT_EOK) - { - rt_kprintf("init storage thread failed, err=%d\n", result); - rt_mutex_delete(state_mutex); - state_mutex = RT_NULL; - rt_event_delete(startup_event); - startup_event = RT_NULL; - demo_running = 0; - return result; - } - - result = rt_thread_init(&business_th, - "biz", - business_entry, - RT_NULL, - business_stack, - sizeof(business_stack), - THREAD_PRIORITY - 1, - THREAD_TIMESLICE); - if (result != RT_EOK) - { - rt_kprintf("init business thread failed, err=%d\n", result); - rt_mutex_delete(state_mutex); - state_mutex = RT_NULL; - rt_event_delete(startup_event); - startup_event = RT_NULL; - demo_running = 0; - return result; - } - - result = rt_thread_startup(&net_th); - if (result != RT_EOK) - { - rt_kprintf("startup network thread failed, err=%d\n", result); - rt_mutex_delete(state_mutex); - state_mutex = RT_NULL; - rt_event_delete(startup_event); - startup_event = RT_NULL; - demo_running = 0; - return result; - } - - result = rt_thread_startup(&sensor_th); - if (result != RT_EOK) - { - rt_kprintf("startup sensor thread failed, err=%d\n", result); - rt_mutex_delete(state_mutex); - state_mutex = RT_NULL; - rt_event_delete(startup_event); - startup_event = RT_NULL; - demo_running = 0; - return result; - } - - result = rt_thread_startup(&storage_th); - if (result != RT_EOK) - { - rt_kprintf("startup storage thread failed, err=%d\n", result); - rt_mutex_delete(state_mutex); - state_mutex = RT_NULL; - rt_event_delete(startup_event); - startup_event = RT_NULL; - demo_running = 0; - return result; - } - - result = rt_thread_startup(&business_th); - if (result != RT_EOK) - { - rt_kprintf("startup business thread failed, err=%d\n", result); - rt_mutex_delete(state_mutex); - state_mutex = RT_NULL; - rt_event_delete(startup_event); - startup_event = RT_NULL; - demo_running = 0; - return result; - } - - /* - * 业务线程会在模块全部就绪后退出。 - * 为了让命令可再次启动,这里做一个简单等待, - * 等业务线程打印完结果后再释放资源。 - */ - while (1) - { - rt_uint32_t flags = get_ready_flag(); - if ((flags & EVENT_ALL_READY) == EVENT_ALL_READY) - { - break; - } - rt_thread_mdelay(200); - } - - rt_thread_mdelay(500); - - rt_event_delete(startup_event); - startup_event = RT_NULL; - - rt_mutex_delete(state_mutex); - state_mutex = RT_NULL; - - demo_running = 0; - - return RT_EOK; -} - -MSH_CMD_EXPORT(startup_demo, startup condition check demo); diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" deleted file mode 100644 index 600f766684e6160e70ce2dd044c2ce1d961140aa..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" +++ /dev/null @@ -1,198 +0,0 @@ -#include - -#define TICKET_USE_MUTEX 1 -#define WINDOW_STACK_SIZE 1024 - -static rt_mutex_t ticket_mutex = RT_NULL; -static rt_sem_t done_sem = RT_NULL; - -static struct rt_thread window_a_thread; -static struct rt_thread window_b_thread; -static rt_uint8_t window_a_stack[WINDOW_STACK_SIZE]; -static rt_uint8_t window_b_stack[WINDOW_STACK_SIZE]; - -static volatile rt_int32_t ticket_count = 20; -static volatile rt_uint8_t demo_running = 0; - -static void window_thread_entry(void *parameter) -{ - const char *name = (const char *)parameter; - - while (1) - { -#if TICKET_USE_MUTEX - if (ticket_mutex != RT_NULL) - { - if (rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER) != RT_EOK) - { - rt_kprintf("%s failed to take mutex\n", name); - rt_thread_mdelay(10); - continue; - } - } -#endif - - if (ticket_count <= 0) - { -#if TICKET_USE_MUTEX - if (ticket_mutex != RT_NULL) - { - rt_mutex_release(ticket_mutex); - } -#endif - break; - } - -#if !TICKET_USE_MUTEX - rt_thread_mdelay(20); -#endif - - rt_kprintf("%s sold ticket %d\n", name, ticket_count); - ticket_count--; - -#if TICKET_USE_MUTEX - if (ticket_mutex != RT_NULL) - { - rt_mutex_release(ticket_mutex); - } -#endif - - rt_thread_mdelay(50); - } - - if (done_sem != RT_NULL) - { - rt_sem_release(done_sem); - } - - rt_kprintf("%s exit\n", name); -} - -int ticket_demo(int argc, char **argv) -{ - rt_err_t result; - - RT_UNUSED(argc); - RT_UNUSED(argv); - - if (demo_running) - { - rt_kprintf("ticket demo is already running\n"); - return -RT_ERROR; - } - - demo_running = 1; - ticket_count = 20; - -#if TICKET_USE_MUTEX - ticket_mutex = rt_mutex_create("tktmtx", RT_IPC_FLAG_PRIO); - if (ticket_mutex == RT_NULL) - { - rt_kprintf("create ticket mutex failed\n"); - demo_running = 0; - return -RT_ERROR; - } -#endif - - done_sem = rt_sem_create("tkdone", 0, RT_IPC_FLAG_PRIO); - if (done_sem == RT_NULL) - { - rt_kprintf("create done semaphore failed\n"); -#if TICKET_USE_MUTEX - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; -#endif - demo_running = 0; - return -RT_ERROR; - } - - result = rt_thread_init(&window_a_thread, - "winA", - window_thread_entry, - (void *)"window A", - window_a_stack, - sizeof(window_a_stack), - 20, - 10); - if (result != RT_EOK) - { - rt_kprintf("init window A thread failed, err=%d\n", result); - rt_sem_delete(done_sem); - done_sem = RT_NULL; -#if TICKET_USE_MUTEX - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; -#endif - demo_running = 0; - return result; - } - - result = rt_thread_init(&window_b_thread, - "winB", - window_thread_entry, - (void *)"window B", - window_b_stack, - sizeof(window_b_stack), - 20, - 10); - if (result != RT_EOK) - { - rt_kprintf("init window B thread failed, err=%d\n", result); - rt_sem_delete(done_sem); - done_sem = RT_NULL; -#if TICKET_USE_MUTEX - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; -#endif - demo_running = 0; - return result; - } - - result = rt_thread_startup(&window_a_thread); - if (result != RT_EOK) - { - rt_kprintf("startup window A thread failed, err=%d\n", result); - rt_sem_delete(done_sem); - done_sem = RT_NULL; -#if TICKET_USE_MUTEX - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; -#endif - demo_running = 0; - return result; - } - - result = rt_thread_startup(&window_b_thread); - if (result != RT_EOK) - { - rt_kprintf("startup window B thread failed, err=%d\n", result); - rt_sem_delete(done_sem); - done_sem = RT_NULL; -#if TICKET_USE_MUTEX - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; -#endif - demo_running = 0; - return result; - } - - rt_sem_take(done_sem, RT_WAITING_FOREVER); - rt_sem_take(done_sem, RT_WAITING_FOREVER); - - rt_sem_delete(done_sem); - done_sem = RT_NULL; - -#if TICKET_USE_MUTEX - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; -#endif - - demo_running = 0; - - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: %d\n", ticket_count); - - return RT_EOK; -} - -MSH_CMD_EXPORT(ticket_demo, ticket sale demo); diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\345\256\236\351\252\214\346\212\245\345\221\212.md" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\345\256\236\351\252\214\346\212\245\345\221\212.md" deleted file mode 100644 index 5cf9492d5acd824f5c05cabb68dbe979864aa5b9..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\345\256\236\351\252\214\346\212\245\345\221\212.md" +++ /dev/null @@ -1,411 +0,0 @@ -# 练习一:停车场车位管理 - -本实验通过模拟停车场车位管理场景,学习计数信号量的使用方法,理解线程在竞争有限资源时的同步控制过程 - -停车场共有三个车位,同时有五辆车尝试进入,车辆只有成功申请到车位后才能进入停车场,离开时释放车位。该实验验证计数信号量可以用于管理多个同类有限资源,并保证任意时刻进入停车场的车辆数不超过资源总数 - -### 实验原理 - -停车场车位属于有限资源。由于本实验中共有 3 个车位,因此使用初始值为 3 的计数信号量表示当前可用车位数量 - -当车辆线程申请车位时,实际是对信号量执行获取操作: - -如果信号量值大于 0,表示还有空车位,车辆可以进入,信号量值减 1 - -如果信号量值为 0,表示车位已满,车辆线程需要等待; - -如果车辆在 2 秒内没有获得车位,则打印等待超时信息,并稍后重新申请 - -车辆离开停车场时释放信号量,表示空出一个车位 - -为了正确统计当前已使用车位数量,程序中使用互斥量保护 used_spaces变量,避免多个线程同时修改该变量导致统计结果错误 - -### 实验过程 - -**编写MSH命令入口** - -在程序中定义 parking_demo函数作为实验入口,并使用 MSH_CMD_EXPORT 将其导出为 MSH 命令 - -``` -int parking_demo(int argc, char **argv) -{ - ... -} - -MSH_CMD_EXPORT(parking_demo, parking lot semaphore demo); -``` - -**创建IPC对象** - -程序运行后,首先创建计数信号量和互斥量,计数信号量 parking_sem的初始值为 3,用于表示停车场可用车位数量。互斥量 count_mutex用于保护 used_spaces、finished_cars 等共享变量,防止多个线程同时修改造成数据错误 - -**创建车辆线程** - -程序创建 5 个车辆线程,每个线程表示一辆车。车辆线程执行流程如下: - -1. 延时一段时间,模拟不同车辆到达停车场的时间不同 -2. 调用 rt_sem_take申请车位 -3. 如果 2 秒内申请成功,则进入停车场 -4. 修改并打印当前已使用车位数量 -5. 停车一段时间 -6. 离开停车场 -7. 调用 rt_sem_release释放车位 -8. 线程结束前更新完成车辆数量 - -**创建监控线程** - -程序创建一个监控线程,用于判断所有车辆线程是否已经执行结束。所有车辆线程结束后,监控线程释放信号量和互斥量资源,并将运行状态标志清除,保证后续可以再次执行 parking_demo命令 - -**实验结果** - -在MSH提示符后输入 :praking_demo - -串口终端输出结果 - -![image-20260819144141632](D:\TYPORA\figures\image-20260819144141632.png) - -实际运行时,线程调度顺序可能略有不同,但应满足以下条件: - -1. used spaces最大值不超过 3 -2. 当 3 个车位已被占用时,后续车辆会等待 -3. 有车辆离开后,等待车辆可以重新进入 -4. 所有车辆最终都能离开 -5. 程序结束后打印 parking demo finished - -**实验结果分析** - -从运行结果可以看到,前三辆车进入停车场后,used spaces达到 3。此时停车场车位已满,后续车辆无法立即进入,因此会出现等待信息当已有车辆离开后,程序调用 rt_sem_release 释放信号量,表示空出一个车位。等待车辆随后重新申请车位并进入停车场。整个运行过程中,used spaces没有超过 3,说明计数信号量成功限制了同时进入停车场的车辆数量。该实验验证了计数信号量适合用于管理多个同类有限资源 - -### 思考题 - -**为什么这里使用计数信号量,而不是互斥量?** - -因为本实验要管理的是3个停车位,属于多个同类有限资源,计数信号量可以用计数值表示剩余资源数量。初始值为3,车辆进入时技术值减少1,车辆离开时计数值增加1. - -互斥量主要用于保护临界区,适合解决同一时刻只允许一个线程访问共享资源的问题。如果使用互斥量,只能表示“是否有线程正在访问”,不能自然表示“当前还有几个车位”。因此,本实验应使用计数信号量来控制车位数量,同时使用互斥量保护共享变量统计 - - - -# 练习二:多窗口售票系统 - -本实验通过模拟两个售票窗口同时出售同一批车票的场景,学习互斥量的使用方法,理解多线程并发访问共享变量时可能产生的竞态问题 - -两个售票线程共同出售20张票,每次收票都需要检查余票、记录票号并修改剩余票数,通过对比是否使用互斥量,观察共享变量在并发访问时的安全性差异 - -### 实验原理 - -车票数量ticket_count是两个售票线程共同访问的全局变量。每个窗口售票时都需要执行以下操作: - -1.判断是否还有余票 - -2.读取当前票号 - -3.将剩余票数减少1 - -4.打印售票结果 - -这些操作应当作为一个完整的临界区处理,如果没有互斥保护,两个线程可能同时读取到相同的余票值,从而导致重复售票、余票统计错误等问题。因此,实验中使用互斥量ticket_mutex保护完整售票过程,保证同一时刻只有一个窗口线程能够修改ticket_count - -### 实验过程 - -**编写MSH命令入口** - -在程序中定义 ticket_demo 函数作为实验入口,并使用 MSH_CMD_EXPORT导出命令: - -``` -MSH_CMD_EXPORT(ticket_demo, ticket sale demo); -``` - -程序下载后,在串口 MSH 终端输入以下命令启动实验: - -``` -ticket_demo -``` - -**创建互斥量** - -正式实验中,将宏设置为: - -``` -#define TICKET_USE_MUTEX 1 -``` - -程序运行后创建互斥量 ticket_mutex,用于保护售票临界区 - -**创建售票线程** - -程序创建两个售票线程:window A,window B - -两个线程共同访问全局变量ticket_count,模拟两个窗口同时售票 - -**执行售票过程** - -每个售票线程循环执行以下流程: - -1.获取互斥量 - -2.判断是否还有余票 - -3.读取当前票号 - -4.将ticket_count减少1 - -5.打印售票窗口和票号 - -6.释放互斥量 - -7.延时后继续下一轮售票 - -当ticket_count<=0时,线程退出 - -**等待线程结束** - -程序中使用完成信号量等待两个售票线程结束,两个窗口线程都退出后,主命令函数删除互斥量和完成信号量,并打印最终结果 - -**对比实验** - -为了观察不使用互斥量时的风险,将宏修改为: - -``` -#define TICKET_USE_MUTEX 0 -``` - -重新编译并下载程序,再次输入: - -``` -ticket_demo -``` - -观察不加互斥量时的运行结果 - -**实验结果** - -**使用互斥量的运行结果**:TICKET_USE_MUTEX设置为1 - -![](D:\TYPORA\figures\image-20260819150611545.png) - -从结果可以看出,20张票全部售出,票号从20到1,没有重复票号,最终剩余票数为0 - -**不使用互斥量的运行结果**:TICKET_USE_MUTEX设置为0 - -![image-20260819150625868](D:\TYPORA\figures\image-20260819150625868.png) - -不加互斥量运行时,串口输出中暂时没有观察到重复票号或余票小于0的现象。但这只能说明本次线程调度下没有触发错误,不能证明程序是线程安全的 - -**结果分析** - -在使用互斥量的正式实验中,两个售票线程在访问ticket_count前必须先获取ticket_mutex。因此,同一时刻只有一个线程能够执行检查余票、读取票号和修改余票的完整售票过程,避免了两个线程同时修改共享变量造成的数据错误 - -在不适用互斥量的对比实验中,虽然本次运行没有出现模型错误,但从程序逻辑上看,ticket_count的读取、判断和修改并不是一个原子操作。如果两个线程在相近时间读取到同一个票数,就可能出现重复售票或余票统计错误。因此,不加互斥量的程序存在竞争条件,只是本次运行没有复现 - -### 思考题 - -**为什么只给 `ticket_count--` 加锁仍可能不安全?** - -因为售票过程不只是 ticket_count--这一条语句,还包括检查是否有票、读取当前票号、修改剩余票数和打印售票结果 - -如果只对 ticket_count-- 加锁,而没有保护完整流程,可能出现以下情况: - -1. 线程 A 判断还有余票 -2. 线程 B 也判断还有余票 -3. 两个线程读取到相同票号 -4. 虽然后续减票操作加锁了,但重复票号已经产生 - -因此,必须将“检查余票、读取票号、修改余票”作为一个完整临界区进行保护,而不是只保护单独的减 1 操作 - - - -# 练习三:系统启动条件检查 - -本实验通过模拟设备启动前的模块初始化过程,学习事件集的使用方法,理解多个线程分别完成不同任务后,如何由主业务线程统一等待全部条件满足 - -要求网络模块、传感器模块和存储模块分别完成初始化,只有三个模块全部就绪后,主业务线程才能启动。通过该实验验证事件集适合用于多个事件条件的同步等待 - -### 实验原理 - -事件集是一种线程间同步机制,可以用若干个事件位表示不同状态。每个初始化线程完成后,向事件集发送对应的就绪事件;主业务线程则等待多个事件全部到达 - -本实验定义三个事件位:EVENT_NET_READY/EVENT_SENSOR_READY/EVENT_STORAGE_READY - -主业务线程使用AND模式等待三个事件位全部置位。这样只有当网络、传感器和存储模块都准备完成后,业务线程才会继续执行。为了实现超时检测,主业务线程设置3秒等待时间。如果存储模块初始化时间过长,主业务线程会先超时返回,并打印尚未完成的模块;随后继续等待,直到所有模块全部就绪 - -### 实验过程 - -**编写MSH命令入口** - -在程序中定义startup_demo函数作为实验入口,并使用MSH_CMD_EXPORT到处命令: - -``` -MSH_CMD_EXPORT(startup_demo,startup confition check demo); -``` - -程序下载后,在串口MSH终端输入以下命令启动实验: - -``` -startup_demo -``` - -**创建事件集和互斥量** - -程序启动后先创建事件集startup_event,用于保护各模块的就绪状态。同时创建互斥量state_mutex,用于保护共享变量ready_flags,避免多个线程同时读写状态时出现错误 - -**创建初始化线程** - -程序创建三个初始化线程,分别模拟三个独立模块的初始化过程: -网络初始化线程:延时1秒后发送网络就绪事件 - -传感器初始化线程:延时2秒后发送传感器就绪事件 - -存储初始化线程:延时5秒后发送存储就绪事件 - -每个线程在完成初始化后,先记录本模块就绪状态,再向事件集发送对应事件位 - -**创建主业务线程** - -主业务线程使用rt_event_recv的AND模块等待三个事件全部到达,并设置3秒超时。当等待超时时,主业务线程会检查当前已完成的模块,并打印尚未完成的模块名称,当三个事件全部到达时,主业务线程打印: - -``` -all modules are ready -business task strated -``` - -**实验结果** - -串口终端输出如下: - -![image-20260819155711526](D:\TYPORA\figures\image-20260819155711526.png) - -从结果可以看出: - -1.网络模块先完成初始化 - -2.传感器模块随后完成初始化 - -3.主业务线程在3秒内未等到存储模块,因此发生超时 - -4.存储模块最终完成初始化 - -5.三个模块全部就绪后,主业务线程继续执行 - -**结果分析** - -本实验中,三个初始化线程分别模拟三个独立模块的启动过程 - -主业务线程不依赖单个模块完成,而是等待三个事件全部到达,者体现了事件集在多条件同步中的作用 - -实验中设置了存储模块5秒延时,而主业务线程只等待3秒,英雌第一次等待会超时。超时后,程序打印尚未完成的模块,便于观察启动状态。待存储模块完成初始化后,事件集中的三个事件位全部置位,主业务线程成功继续执行 - -### 思考题 - -**如果用三个独立信号实现这个功能,与使用事件集相比有什么区别** - -如果使用三个独立信号量,也可以实现等待三个模块完成初始化,但实现方式更复杂。 - -区别主要有: - -**1.表达能力不同** - -事件集可以用一个对象同时表示多个事件状态,适合“多个条件同时满足”的等待场景;信号量更适合表示资源数量或单一同步事件 - -**2.状态查询不同** - -事件集支持按位组合,可以直接判断哪些模块已就绪;独立信号量只能一个一个等待,难以直接统一判断整体状态 - -**3.使用方式不同** - -事件集更适合本实验这种“多个模块全部完成后再启动业务”的场景;三个信号量虽然也能实现,但代码更分散,逻辑不如事件集集中 - - - -# 四、快递分拣中心 - -本实验通过模拟快递分拣中心的收件和分拣过程,学习消息队列的使用方法,理解线程之间如何传递结构体类型的数据 - -实验要求两个发件线程不断产生快递信息,一个分拣线程从消息队列中接收快递消息,并按照目的地区域进行统计。通过实验验证消息队列适合用于线程间传递数据信息 - -### 实验原理 - -消息队列是一种线程间通信机制,可以在线程之间传递固定大小的数据块。本实验中,每条快递信息使用结构体package_msg表示: - -``` -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -``` - -其中,id表示快递编号,region表示目的地区域,weight表示快递重量 - -两个发送线程分别产生快递消息,并通过rt_mg_send将消息发送到消息队列中。分拣线程通过rt_mg_recv从消息队列中接收消息,根据区域统计数量,并累计总重量 - -为了同质分拣线程停止接收,发送线程收到结束消息后,打印统计结果并退出 - -### 实验过程 - -**定义消息结构体** - -在程序中定义快递消息结构体: - -``` -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -``` - -**创建消息队列** - -重新创建两个发件线程: - -发送线程A:产生编号1001到1005的快递 - -发送线程B:产生编号2001到2005的快递 - -每个发件线程产生5条消息,并调用rt_mg_send发送到消息队列;如果队列已满,rt_mg_send会返回失败,程序记录发送失败次数,并打印发送失败信息 - -**创建分拣线程** - -程序创建一个分拣线程,分拣线程不断调用rt_mg_recv接收消息队列中的快递消息。每收到一条普通快递消息,分拣线程就打印快递编号、区域和重量,并更新统计数据 - -区域对应关系如下: - -region=1:east - -region=2:south - -region=3:north - -**发送结束消息** - -两个发件线程都完成后,程序向消息队列发送一条结束消息。结束消息的编号为0,区域标志为PACKAGE_END_REGION。分拣线程收到该结束消息后不再继续接收快递,转而打印统计结果并退出 - -**实验结果** - -![image-20260819165949131](D:\TYPORA\figures\image-20260819165949131.png) - -从结果可以看出: - -1.共接收到10条快递消息 - -2.快递编号从1001到1005、2001到2005,没有重复 - -3.华东区域3件,华南区域4件,华北区域3件 - -4.总重量为9780g - -5.本次运行中消息发送失败次数为0 - -6.分拣线程完成统计后正常退出 - -**结果分析** - -本实验中,两个发件线程同时向消息队列发送快递信息,分拣线程从消息队列中接收并处理。消息队列在这里起到了线程间数据传递的作用,使发件线程和分拣线程之间不需要直接共享复杂数据 - -由于消息队列容量为 5,当发送速度快于接收速度时,理论上可能出现队列已满导致发送失败的情况。因此程序中对 rt_mq_send 的返回值进行了判断,并统计发送失败次数。本次运行中 send failed为 0,说明消息队列在本次调度下没有发生满队列发送失败 - -实验结果中,快递消息的输出顺序可能与发送顺序不完全一致,这是因为两个发件线程并发运行,线程调度顺序会影响消息进入队列的先后顺序。但最终统计总数、区域数量和总重量均正确,说明分拣线程正确接收并处理了消息队列中的数据 - diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817112957813.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817112957813.png" deleted file mode 100644 index 8cbefd2821fc003691fc15a62e6fc8b10e3fa467..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817112957813.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817113502159.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817113502159.png" deleted file mode 100644 index 685d7fb5b5eeefc01cfad53b85e4cac38945469b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817113502159.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817142648941.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817142648941.png" deleted file mode 100644 index 4bf38e6b886dfa690e5a7d726f15856157c6115b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817142648941.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817143835258.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817143835258.png" deleted file mode 100644 index 2ca7c09e48ba4d65780d5a1a5792b9c101bc3ecb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817143835258.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817144051657.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817144051657.png" deleted file mode 100644 index b3b6900b033d2ba826f94883eab89d955ede7434..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817144051657.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817150046870.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817150046870.png" deleted file mode 100644 index 95ab187ec4ddb3bd3eb788ab13f0dea61a728a80..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817150046870.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817150446388.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817150446388.png" deleted file mode 100644 index c0435456a9dbaaf4aa0dbb0f6c12ccc6d3230ecb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817150446388.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817151244692.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817151244692.png" deleted file mode 100644 index adf1c1cd27e5acbf0b9d5aa20a8ffebf8f0fbdc5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817151244692.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817152048721.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817152048721.png" deleted file mode 100644 index 4e39cef8d61f44e369319918be0b95fc35beace8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817152048721.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817152317118.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817152317118.png" deleted file mode 100644 index 74f66f4df025334e505c57b42daffa5aa8db5d48..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260817152317118.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818140556194.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818140556194.png" deleted file mode 100644 index 35ad6963fe9d9e24b90b02a92da4a88996349be1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818140556194.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818164439722.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818164439722.png" deleted file mode 100644 index a7a945d850b8166e44fe27032c825f6fc4120d9e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818164439722.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818202039012.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818202039012.png" deleted file mode 100644 index 8aeb7b88e692feaf17ef61fcf1f8480a5987806a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818202039012.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210556117.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210556117.png" deleted file mode 100644 index c22ddb4cc9edf0f30552b564c6c478f4de57f996..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210556117.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210605960.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210605960.png" deleted file mode 100644 index c22ddb4cc9edf0f30552b564c6c478f4de57f996..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210605960.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210732275.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210732275.png" deleted file mode 100644 index af7b05423b82c2281ec468ea8cd1ced2e2e4a3c5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210732275.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210821589.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210821589.png" deleted file mode 100644 index 4a0f90d7a951c426897f02ccb2eac4a0982376a5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818210821589.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818211005741.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818211005741.png" deleted file mode 100644 index ff0da8904baeebbbd649d90ffadf5181b08d7389..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818211005741.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818211045732.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818211045732.png" deleted file mode 100644 index 6e56fc96a1b0a365e022e9f933be18b720ee294a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260818211045732.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/rtt_startup.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/rtt_startup.png" deleted file mode 100644 index 04dbb79fed05b7463c5ceea179f5dd75c6553724..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/rtt_startup.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102048.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102048.png" deleted file mode 100644 index 800ec298cc4dd2fa7599d475a0c8f3bf52a3eecb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102048.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102442.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102442.png" deleted file mode 100644 index 95e8ab4ef99114778b540f5d2755243bfb511a95..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102442.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 104304.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 104304.png" deleted file mode 100644 index e4fb0f0ce5fae09fda464fe2b060422cbe7ad598..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 104304.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index ef71e55e786bbf27c1df1351f2dfb425a9ebdc66..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,252 +0,0 @@ -#include -#include -#include - -#define LED_R_PIN GET_PIN(F, 12) -#define LED_B_PIN GET_PIN(F, 11) - -/* Spark-1: LEFT key = PC0, pressed = low level */ -#define KEY_LEFT_PIN GET_PIN(C, 0) - -#define LED_ON PIN_LOW -#define LED_OFF PIN_HIGH - -static rt_thread_t th_led1 = RT_NULL; -static rt_thread_t th_monitor = RT_NULL; - -static struct rt_thread th_led2; -static rt_uint8_t th_led2_stack[1024]; - -static struct rt_semaphore led1_sem; -static struct rt_semaphore key_sem; - -static volatile rt_bool_t led1_pause_requested = RT_FALSE; -static volatile rt_bool_t led1_waiting = RT_FALSE; - -static rt_tick_t key_last_tick = 0; - -static const char *thread_state_name(rt_uint8_t stat) -{ - switch (stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: return "INIT"; - case RT_THREAD_READY: return "READY"; - case RT_THREAD_RUNNING: return "RUNNING"; - case RT_THREAD_SUSPEND: return "SUSPEND"; - case RT_THREAD_CLOSE: return "CLOSE"; - default: return "UNKNOWN"; - } -} - -static rt_uint32_t get_stack_free(rt_thread_t thread) -{ - rt_uint32_t i = 0; - rt_uint8_t *stack; - - if (thread == RT_NULL || thread->stack_addr == RT_NULL || thread->stack_size == 0) - { - return 0; - } - - stack = (rt_uint8_t *)thread->stack_addr; - - while (i < thread->stack_size && stack[i] == '#') - { - i++; - } - - return i; -} - -static void key_irq_cb(void *args) -{ - RT_UNUSED(args); - - rt_sem_release(&key_sem); -} - -static void led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_uint8_t led_state = LED_OFF; - - RT_UNUSED(parameter); - - while (1) - { - if (led1_pause_requested) - { - rt_pin_write(LED_R_PIN, LED_OFF); - led1_waiting = RT_TRUE; - - // rt_kprintf("[th_led1] wait semaphore, LED_R paused\r\n"); - - rt_sem_take(&led1_sem, RT_WAITING_FOREVER); - - led1_waiting = RT_FALSE; - led1_pause_requested = RT_FALSE; - - // rt_kprintf("[th_led1] semaphore released, LED_R resume\r\n"); - } - - led_state = (led_state == LED_OFF) ? LED_ON : LED_OFF; - rt_pin_write(LED_R_PIN, led_state); - - count++; - // rt_kprintf("[th_led1] count=%u tick=%lu\r\n", - // count, - // (unsigned long)rt_tick_get()); - - rt_thread_mdelay(500); - } -} - -static void led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_uint8_t led_state = LED_OFF; - - RT_UNUSED(parameter); - - while (1) - { - led_state = (led_state == LED_OFF) ? LED_ON : LED_OFF; - rt_pin_write(LED_B_PIN, led_state); - - count++; - // rt_kprintf("[th_led2] count=%u\r\n", count); - - rt_thread_mdelay(500); - } -} - -static void thread_dump_one(const char *name, rt_thread_t thread) -{ - if (thread == RT_NULL) - { - rt_kprintf("[%s] NULL\r\n", name); - return; - } - - rt_kprintf("[%s] stat=%s(0x%02x) stack_free=%u stack_size=%u\r\n", - name, - thread_state_name(thread->stat), - thread->stat & RT_THREAD_STAT_MASK, - get_stack_free(thread), - thread->stack_size); -} - -static void handle_key_event(void) -{ - rt_tick_t now = rt_tick_get(); - - if ((now - key_last_tick) < rt_tick_from_millisecond(200)) - { - return; - } - key_last_tick = now; - - if (!led1_pause_requested && !led1_waiting) - { - led1_pause_requested = RT_TRUE; - rt_kprintf("[key] LEFT pressed, request pause th_led1\r\n"); - } - else if (led1_waiting) - { - rt_sem_release(&led1_sem); - rt_kprintf("[key] LEFT pressed, release semaphore for th_led1\r\n"); - } - else - { - rt_kprintf("[key] LEFT pressed, th_led1 is preparing to pause\r\n"); - } -} - -static void monitor_entry(void *parameter) -{ - RT_UNUSED(parameter); - - while (1) - { - if (rt_sem_take(&key_sem, rt_tick_from_millisecond(2000)) == RT_EOK) - { - handle_key_event(); - } - - thread_dump_one("th_led1", th_led1); - thread_dump_one("th_led2", (rt_thread_t)&th_led2); - thread_dump_one("th_monitor", th_monitor); - } -} - -static int thread_dump(int argc, char **argv) -{ - RT_UNUSED(argc); - RT_UNUSED(argv); - - rt_kprintf("===== thread dump =====\r\n"); - thread_dump_one("th_led1", th_led1); - thread_dump_one("th_led2", (rt_thread_t)&th_led2); - thread_dump_one("th_monitor", th_monitor); - rt_kprintf("=======================\r\n"); - - return 0; -} -MSH_CMD_EXPORT(thread_dump, show thread status and stack space); - -static int thread_study_init(void) -{ - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); - rt_pin_write(LED_R_PIN, LED_OFF); - rt_pin_write(LED_B_PIN, LED_OFF); - - rt_sem_init(&led1_sem, "led1sem", 0, RT_IPC_FLAG_FIFO); - rt_sem_init(&key_sem, "keysem", 0, RT_IPC_FLAG_FIFO); - - rt_pin_mode(KEY_LEFT_PIN, PIN_MODE_INPUT_PULLUP); - rt_pin_attach_irq(KEY_LEFT_PIN, PIN_IRQ_MODE_FALLING, key_irq_cb, RT_NULL); - rt_pin_irq_enable(KEY_LEFT_PIN, PIN_IRQ_ENABLE); - - th_led1 = rt_thread_create("th_led1", - led1_entry, - RT_NULL, - 1024, - 16, - 5); - if (th_led1 == RT_NULL) - { - rt_kprintf("th_led1 create failed\r\n"); - return -RT_ERROR; - } - - rt_thread_init(&th_led2, - "th_led2", - led2_entry, - RT_NULL, - th_led2_stack, - sizeof(th_led2_stack), - 16, - 5); - - th_monitor = rt_thread_create("th_monitor", - monitor_entry, - RT_NULL, - 1024, - 10, - 5); - if (th_monitor == RT_NULL) - { - rt_kprintf("th_monitor create failed\r\n"); - return -RT_ERROR; - } - - rt_thread_startup(th_led1); - rt_thread_startup(&th_led2); - rt_thread_startup(th_monitor); - - rt_kprintf("thread study init done, press LEFT key to pause/resume th_led1\r\n"); - - return RT_EOK; -} -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\256\236\351\252\214\346\212\245\345\221\212.md" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\256\236\351\252\214\346\212\245\345\221\212.md" deleted file mode 100644 index 57b9e5520a90540a76b3622611cfbce7b1796731..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\345\256\236\351\252\214\346\212\245\345\221\212.md" +++ /dev/null @@ -1,156 +0,0 @@ -# 一、实验目的 - -基于RT-Thread操作系统和STM32F407星火一号开发板,完成多线程创建、线程调度、线程状态查看、按键控制 - -# 二、实验任务 - -本实验主要实现三个线程 - -| 线程名称 | 创建方式 | 优先级 | 时间片 | 栈大小 | 功能 | -| ---------- | ---------- | ------ | ------ | --------- | ----------------------------------------------- | -| th_led1 | 动态创建 | 16 | 5ticks | 1024Bytes | 翻转LED_R,并打印计数和系统tick | -| th_led2 | 静态初始化 | 16 | 5ticks | 1024Bytes | 翻转LED_B,并打印计数 | -| th_monitor | 动态创建 | 10 | 5ticks | 1024Bytes | 检测按键,控制th_led1暂停和恢复,并查看线程状态 | - -其中,th_monitor的优先级为10,高于两个LED工作线程的优先级16,因此当按键时间发生时,监控线程能够优先响应 - -# 三、程序设计思路 - -### 3.1动态线程th_led1 - -th_led1使用rt_thread_create()动态创建,线程栈由系统动态分配。该线程循环翻转LED_R,每次翻转后打印当前计数值和rt_tick_get()获取到的系统tick值 - -当按键触发暂停请求后,th_led1会主动关闭LED_R,并调用rt_sem_take()等信号量。此时线程进入等待状态,不再继续翻转LED_R - -### 3.2静态线程th_led2 - -th_led2使用rt_thread_init()静态初始化,线程控制块和线程栈均定义为全局变量。该线程循环翻转LED_B,并在串口中打印当前计数值 - -与动态线程不同,静态线程的TCB和栈空间在编译阶段已经确定,不需要运行时动态申请内存,因此更适合一些对稳定性要求较高的场景 - -### 3.3监控线程th_monitor - -th_monitor使用rt_thread_create()动态创建,优先级设置为10.该线程负责检测LEFT按键触发时间,并根据当前状态控制th_led1暂停或恢复 - -在本次实验中,原本任务要求中提到使用rt_thread_suspend()和rt_thread_resume()控制线程。但是在当前版本中源码说明rt_thread_suspend()只能用于挂起当前线程自身,即: - -``` -rt_thread_suspend(rt_thread_self()); -``` - -因此本实验没有让监控线程直接挂起th_led1,而是采用信号量方式实现控制: - -第一次按下LEFT按键:设置暂停请求,th_led1自身进入信号量等待状态 - -第二次按下LEFT按键:释放信号量,使th_led1从等待状态恢复运行 - -这种方式符合当前 RT-Thread 版本的使用限制,也能实现按键控制线程暂停和恢复的实验效果 - -### 4.4 自动初始化与命令导出 - -本实验使用 INIT_APP_EXPORT(thread_study_init) 将实验初始化函数注册到 RT-Thread 自动初始化流程中。系统启动后会自动执行该初始化函数,不需要在 main.c 中手动调用。 - -同时,实验使用 MSH_CMD_EXPORT() 导出了线程查看命令,例如: - -``` -MSH_CMD_EXPORT(thread_dump, show thread status and stack space); -``` - -通过 FinSH/MSH 输入 thread_dump,可以查看当前线程状态、优先级、栈大小、栈使用情况等信息 - -## 5. 实验现象 - -### 5.1 程序运行初始现象 - -程序烧录运行后,`th_led1` 和 `th_led2` 同时启动。两个 LED 工作线程优先级相同,时间片相同,因此可以看到两个 LED 以相近频率闪烁。 - -![image-20260818210605960](D:\TYPORA\figures\image-20260818210605960.png) - -图中可以看到开发板上两个 LED 同时处于闪烁工作状态,说明动态创建的 `th_led1` 和静态初始化的 `th_led2` 都已经正常运行 - -### 5.2 按下 LEFT 按键后的现象 - -第一次按下 LEFT 按键后,串口输出: - -``` -[key] LEFT pressed, request pause th_led1 -[th_led1] wait semaphore, LED_R paused -``` - -此时 th_led1接收到暂停请求后进入信号量等待状态,LED_R 停止闪烁;th_led2不受影响,继续翻转 LED_B - -![image-20260818210732275](D:\TYPORA\figures\image-20260818210732275.png) - -该现象说明按键中断能够正常触发,监控线程能够正确处理按键事件,并通过信号量机制控制 th_led1 暂停 - -### 5.3 再次按下 LEFT 按键后的现 - -再次按下 LEFT 按键后,串口输出: - -``` -[key] LEFT pressed, release semaphore for th_led1 -[th_led1] semaphore released, LED_R resume -``` - -此时 th_monitor 释放信号量,th_led1从等待状态恢复运行,LED_R 重新开始闪烁 - -该现象说明信号量释放后,等待中的线程能够被唤醒,线程恢复执行 - -## 6. 串口输出分析 - -![image-20260818210821589](D:\TYPORA\figures\image-20260818210821589.png) - -从串口输出可以看到: - -``` -[th_led1] count=10 tick=7121 -[th_led2] count=16 -``` - -其中,count表示线程循环执行次数,也可以理解为 LED 翻转次数的计数。tick表示系统当前 tick 值,由 rt_tick_get() 获取,用于观察系统运行时间 - -串口中还输出了线程状态和栈空间信息,例如: - -``` -[th_led1] stat=SUSPEND(0x02) stack_free=780 stack_size=1024 -[th_led2] stat=SUSPEND(0x02) stack_free=776 stack_size=1024 -[th_monitor] stat=RUNNING(0x03) stack_free=756 stack_size=1024 -``` - -其中: - -stat表示线程当前状态 - -stack_size表示线程栈总大小 - -stack_free 表示线程当前剩余栈空间 - -stack_free可用于判断线程栈是否足够,避免线程栈溢出 - -例如 th_led1 的 stack_size=1024,stack_free=780,说明该线程大约使用了 1024 - 780 = 244字节栈空间,栈空间仍然比较充足 - -## 7. 线程状态查看 - -![image-20260818211005741](D:\TYPORA\figures\image-20260818211005741.png) - -![image-20260818211045732](D:\TYPORA\figures\image-20260818211045732.png) - -可以查看当前系统中的线程信息,包括线程名、优先级、状态、栈大小、最大栈使用率、剩余 tick 等 - -截图中可以看到 th_led1、th_led2、th_monitor 等线程已经创建成功。其中 th_led1 和 th_led2 多数情况下显示为 suspend,这是正常现象。原因是两个 LED 线程每次翻转 LED 后都会调用 rt_thread_mdelay() 进行延时,因此在查看线程状态的瞬间,线程大概率处于延时等待状态 - -当第一次按下 LEFT 按键后,th_led1会等待信号量,因此保持在挂起状态;再次按下 LEFT 按键后,信号量被释放,th_led1恢复运行,然后继续周期性闪烁 - -## 8. 实验结果总结 - -本实验完成了 RT-Thread 多线程编程的基本要求: - -1. 使用 rt_thread_create()动态创建了 th_led1线程 -2. 使用 rt_thread_init()静态初始化了 th_led2线程 -3. 使用动态线程 th_monitor实现按键检测和线程控制 -4. 使用信号量实现了 th_led1的暂停与恢复 -5. 使用 INIT_APP_EXPORT()实现模块自动初始化 -6. 使用 MSH_CMD_EXPORT()导出了自定义线程查看命令 -7. 通过串口输出和开发板 LED 现象验证了程序运行结果 - -实验结果表明,RT-Thread 可以通过线程优先级、时间片和 IPC 机制实现多个任务的并发运行和同步控制。在 STM32F407 单核处理器上,同一时刻只有一个线程真正占用 CPU,但由于系统调度器能够快速切换线程,因此从现象上可以看到多个 LED 任务同时运行 \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 1291047c4dbbb1b427d41b65c9ef7db6670726d8..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,362 +0,0 @@ -# 一、STM32F407启动流程 - -### 1.启动文件进入RT-Thread - -启动文件startup_xx.s根据不同编译器进入不同函数: - -``` -MDK:$Sub$$main() -IAR:_low_level_init() -GCC:entry() -``` - -这些函数最终都会进入 rtthread_startup() - -``` -上电复位 -> 启动文件 -> 编译器相关入口 -> rtthread_startup() -``` - -### 2.关闭中断, 进入系统启动阶段 - -进入rtthread_startup()后,系统首先执行 - -``` -rt_hw_interrupt_disable(); -``` - -作用是暂时关闭中断,防止系统初始化过程中被中断打断,之后开始执行板级初始化和RT-Thread内核初始化 - -### 3.板级硬件初始化 - -主要执行: - -``` -rt_hw_board_init(); -``` - -这个函数负责初始化与具体硬件平台有关的内容,例如:系统时钟、串口、SysTick、GPIO、堆内存、中断控制器、板级外设。 - -接着执行: - -``` -rt_components_board_init(); -``` - -该函数会调用通过INIT_BOARD_EXPORT()注册的板级初始化函数 - -### 4.RT-Thread内核基础初始化 - -板级初始化完成后,系统会继续执行: - -``` -rt_show_version() #显示RT-Thread版本信息 -rt_system_timer_init() #初始化系统定时器 -rt_system_scheduler_init() #初始化线程调度器 -rt_system_signal_init() #初始化信号机制 -``` - -### 5.创建main线程 - -接下来执行: - -``` -rt_application_init(); -``` - -该函数内部会初始化main线程: - -``` -rt_thread_init(main_thread,main_thread_entry,...); -``` - -此时系统并不是直接执行用户写的main()函数,而是先创建一个名为main_thread的RT-Thread线程。main_thread_entry()是RT-Thread对用户main()函数的封装 - -### 6.创建定时器线程和空闲线程 - -系统随后创建两个重要的系统线程 - -**定时器线程** - -``` -rt_system_timer_thread_init(); -``` - -内部初始化 - -``` -rt_thread_init(rt_thread_timer_entry); -``` - -定时器线程用于处理系统中的软件定时器和线程延时等功能 - -**空闲线程** - -``` -rt_thread_idle_init(); -``` - -内部初始化 - -``` -rt_thread_init(rt_thread_idle_entry); -``` - -空闲线程在系统没有其他就绪线程时运行,通常用于:处理空闲任务,执行空闲钩子函数,进行资源回收。进入低功耗模式 - -### 7.启动调度器并选择线程 - -完成各类线程初始化后,系统执行: - -``` -rt_system_scheduler_start(); -``` - -这一步表示正式启动RT-Thread调度器 - -调度器会根据线程的优先级和时间片选择需要运行的线程,可能运行: - -``` -main_thread_entry() -rt_thread_timer_entry() -rt_thread_idle_entry() -其他用户线程 -``` - -需要注意: - -``` -rt_system_scheduler_init() #是初始化调度器 -rt_system_scheduler_start() #是真正的启动调度器 -``` - -### 8.执行组件初始化并进入main() - -调度器启动后,系统首先可能运行main_thread_entry() - -在main_thread_entry()中,通常会执行: - -``` -rt_components_init(); -``` - -该函数会按照初始化顺序执行自动注册的初始化函数 - -对应的初始化宏主要包括: - -``` -INIT_PREV_EXPORT() -INIT_DEVICE_EXPORT() -INIT_COMPONENT_EXPORT() -INIT_ENV_EXPORT() -INIT_APP_EXPORT() -``` - -组件初始化完成后,才会进入用户编写的main(),然后开始执行应用程序。例如: - -``` -int main(void) -{ - while(1) - { - //用户应用任务 - } -} -``` - - - -**RT-Thread的核心启动流程可以概括为:** - -``` -rtthread_startup() -{ - rt_hw_board_init(); # 板级硬件初始化 - - rt_system_timer_init(); # 系统定时器初始化 - rt_system_scheduler_init(); # 调度器初始化 - - rt_application_init(); # 创建 main 线程 - - rt_system_timer_thread_init(); # 定时器线程初始化 - rt_thread_idle_init(); # 空闲线程初始化 - - rt_system_scheduler_start(); # 启动调度器 -} -``` - -启动调度器以后,系统会根据线程优先级选择需要运行的线程,对于单核STM32F407,同一时刻实际只有一个线程占用CPU,但系统通过快速的上下文切换实现多线程并发执行的效果 - - - -# 二、RT-Thread引导机制 - -RT-Thread 的引导机制核心是“自动初始化”。也就是说,系统启动时不需要在 main() 里手动逐个调用各模块初始化函数,而是通过初始化宏把函数注册到对应的初始化表中,系统在启动过程中按顺序自动执行。 - -### 1. 自动初始化的基本思想 - -RT-Thread 将不同阶段的初始化函数分成多个层次,例如:板级初始化、设备初始化、组件初始化、环境初始化、应用初始化 - -这样做的好处是:各模块职责清晰、初始化顺序明确、不需要在main()中手动堆叠大量初始化代码、便于移植和维护 - -### 2.常见自动初始化宏 - -``` -INIT_BOARD_EXPORT() -INIT_PREV_EXPORT() -INIT_DEVICE_EXPORT() -INIT_COMPONENT_EXPORT() -INIT_ENV_EXPORT() -INIT_APP_EXPORT() -``` - -这些宏的作用是把对应的初始化函数注册到指定的初始化段中,系统启动后统一扫描并执行 - -### 3.INIT_BOARD_EXPORT()的使用 - -INIT_BOARD_EXPORT()用于板级初始化阶段,通常在内核完成启动之前执行,适合放置:时钟初始化、串口初始化、GPIO初始化、板载外设初始化。这类初始化和具体硬件平台关系最密切,因此放在最早阶段执行 - -### 4.INIT_APP_EXPORT()的使用 - -INIT_APP_EXPORT()用于应用初始化阶段。它通常在系统线程已经建立、调度器已经启动之后执行,适合放置:用户功能模块初始化、测试任务初始化、应用线程创建、业务逻辑入口 - -### 5.自动初始化原理 - -自动初始化的本质是: - -5.1使用宏把初始化函数转换成函数指针 - -5.2将函数指针放入指定链接段 - -5.3系统启动时遍历这些链接段 - -5.4依次调用对应初始化函数 - -因此,自动初始化不是“自动寻找函数”,而是“启动时遍历预先注册好的函数表” - - - -# 三、内核基础与对象模型 - -### 1.RT-Thread的对象化设计 - -RT-Thread虽然主要使用C语言编写,但它采用了类似面向对象的设计思想。线程、信号量、互斥量、事件、邮箱、消息队列、定时器等,都被抽象成统一管理的内核对象,这种设计的优点是:统一管理、便于调试、便于扩展、对不同内核对象使用相似的管理机制 - -### 2.线程控制块 - -线程在RT-Thread中由线程控制块struct rt_thread描述,它保存了线程运行所需的关键状态信息,例如: - -| 成员 | 作用 | -| ---------------- | ------------------------ | -| stat | 表示线程当前状态 | -| current_priority | 表示线程当前优先级 | -| init_tick | 表示线程初始化时的时间片 | -| sp | 保存线程当前栈指针 | -| stack_addr | 线程栈起始地址 | -| stack_size | 线程栈大小 | -| entry | 线程入口函数 | -| parameter | 线程入口参数 | - -其中,sp最重要。线程切换时,系统通过保存和恢复栈指针来完成上下文切换 - -### 3.内存分配方式 - -**静态线程rt_thread_init()** - -静态线程的控制块和栈空间在编译期就已经分配好,通常定义在全局区或静态区 - -优点:内存分配确定,不依赖动态堆,运行更稳定,适合高可靠场景 - -缺点:不够灵活,占用的资源在编译时就固定下来 - -**动态线程rt_thread_create()** - -动态线程由系统从堆中分配控制块和栈空间 - -优点:使用灵活,线程可以按需创建和删除,适合功能变化较多的场景 - -缺点:依赖动态内存,可能出现申请失败,长期运行可能引入内存碎片 - -### 4.高可靠场景常用静态线程 - -4.1避免运行时内存申请失败 - -4.2减少内存碎片 - -4.3资源占用可提前估算 - -4.4系统行为更可控 - -4.5更容易通过可靠性测试 - - - -# 四、线程状态机与调度算法 - -### 1.线程五态 - -RT-Thread线程通常由五种状态: - -| 状态 | 含义 | -| ------ | -------------------------- | -| 初始态 | 线程已创建,但尚未启动 | -| 就绪态 | 线程已经准备好运行 | -| 运行态 | 当前正在占用CPU | -| 挂起态 | 线程在等待资源、延时或事件 | -| 关闭态 | 线程已经结束或被删除 | - -**触发线程状态转换的典型API** - -线程状态会随着线程创建、启动、延时、挂起、恢复和删除等操作发生变化 - -| 状态转换 | 典型API或触发条件 | 说明 | -| ------------------------ | -------------------------------------------------------- | ------------------------------------------------------------ | -| 无状态->初始态 | rt_thread_init()、rt_thread_create() | 初始化或创建线程,此时线程还没有参与调度 | -| 初始态->就绪态 | rt_thread_startup() | 启动线程,并将线程加入就绪队列 | -| 就绪态->运行态 | 调度器选择 | 由调度器选择优先级最高的就绪线程运行,不是由普通应用API直接完成 | -| 运行态->就绪态 | 时间片耗尽、被高优先级线程抢占、rt_thread_yield() | 当前线程暂时让出CPU,但仍然可以参与调度 | -| 运行态->挂起态 | rt_thread_delay()、rt_thread_mdelay()、rt_thread_sleep() | 线程主动延时,延时期间不参与调度 | -| 运行态->挂起态 | rt_thread_suspend() | 主动挂起指定线程 | -| 运行态->挂起态 | 等待信号量、互斥量、事件或消息 | 当资源暂时不可用时,线程阻塞等待 | -| 挂起态->就绪态 | rt_thread_resume() | 将被挂起的线程恢复,并重新加入就绪队列 | -| 挂起态->就绪态 | 延时结束或等待的资源可用 | 线程等待条件满足后重新进入就绪态 | -| 运行态或就绪态->关闭态 | rt_thread_delete()、线程入口函数返回 | 删除线程,或者线程执行结束 | -| 非运行态静态线程->关闭态 | rt_thread_detach() | 将静态线程从系统对象管理中脱离,不释放其静态分配的栈和控制块 | - -线程状态转换可以简化表示为 - -![image-20260818202039012](D:\TYPORA\figures\image-20260818202039012.png) - -需要注意: - -1.1rt_thread_startup()的作用是将初始态线程启动,并放入就绪队列 - -1.2就绪态进入运行态,主要由调度器决定 - -1.3rt_thread_delay()、rt_thread_mdelay()会使当前线程进入挂起状态 - -1.4rt_thread_suspend()用于主动挂起线程,rt_thread_resume()用于恢复线程 - -1.5rt_thread_yield()通常不会使线程进入挂起态,线程让出CPU后仍然处于就绪状态 - -1.6当线程等待信号量、互斥量、事件、邮箱或消息队列时,如果资源暂时不可用,也会进入挂起状态 - -1.7rt_thread_delete()用于删除动态线程,静态线程通常使用rt_thread_detach()脱离系统管理 - -### 2.抢占式调度 - -RT-Thread采用基于优先级的抢占式调度,优先级越小优先级越高 - -当高优先级线程变为就绪态时,调度器会优先让它运行,从而抢占低优先级线程 - -### 3.时间片轮转 - -当多个线程优先级相同,并且都处于就绪态时,系统会根据时间片轮流运行这些线程。例如两个优先级都为16的线程,各自时间片为5ticks: - -线程A运行5ticks->切换到线程B->线程B运行5ticks->再切回到线程A - - - - - - - diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/image-20260817102132970.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/image-20260817102132970.png" deleted file mode 100644 index 4369ded29da9b35e1339f89e44ac757c79456223..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/image-20260817102132970.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/image-20260817102444975.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/image-20260817102444975.png" deleted file mode 100644 index 60e53d69c822875d0bfc8dda640bb79c8cd6e212..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/image-20260817102444975.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/image-20260817104355851.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/image-20260817104355851.png" deleted file mode 100644 index 2bb302a75ff56c1e24b477b6df61d5a703cd4345..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/image-20260817104355851.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/\345\276\256\344\277\241\345\233\276\347\211\207_20260316172256_206_7-1786932429552-2.jpg" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/\345\276\256\344\277\241\345\233\276\347\211\207_20260316172256_206_7-1786932429552-2.jpg" deleted file mode 100644 index feb5e38e4e3e7253129043bac72a88b489ef1ce1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/figures/\345\276\256\344\277\241\345\233\276\347\211\207_20260316172256_206_7-1786932429552-2.jpg" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/markdown\350\257\255\346\263\225\345\255\246\344\271\240.md" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/markdown\350\257\255\346\263\225\345\255\246\344\271\240.md" deleted file mode 100644 index 0f324dd6c62e61b8e469dcc3fc1d055714d2d97e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/markdown\350\257\255\346\263\225\345\255\246\344\271\240.md" +++ /dev/null @@ -1,28 +0,0 @@ -# 一级标题 # - -## 二级标题 ## - -### 三级标题 ### - -## `code`代码 - -~~~c -#include -~~~ - -## |~|~|表格 - -| DAY1 | DAY2 | -| ---- | ---- | -| | | - -`高亮` - -**加粗** - -***斜体*** - - - - - diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/\345\210\233\345\273\272\345\267\245\347\250\213\344\270\216\350\260\203\350\257\225.md" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/\345\210\233\345\273\272\345\267\245\347\250\213\344\270\216\350\260\203\350\257\225.md" deleted file mode 100644 index 8e221bd28974988f5b032d6ad40afac7f557b5c6..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260/\345\210\233\345\273\272\345\267\245\347\250\213\344\270\216\350\260\203\350\257\225.md" +++ /dev/null @@ -1,80 +0,0 @@ -# 一、今日目标 - -``` -1.1安装并检查ST-Link驱动 - -1.2基于RT-Thread 4.1.1创建STM32F407ZG工程 - -1.3编译工程并下载到开发板 - -1.4使用ST-Link进入断点调试 - -1.5理解RT-Thread从启动文件到main()的执行流程 -``` - -# 二、工程创建 - -![屏幕截图 2026-08-17 102048](D:\TYPORA\figures\屏幕截图 2026-08-17 102048.png) - -## ST-Link驱动检查 - -![屏幕截图 2026-08-17 102442](D:\TYPORA\figures\屏幕截图 2026-08-17 102442.png) - -## 基于RT-Thread 4.1.1版本新建stm32f407zg芯片工程 - -![屏幕截图 2026-08-17 104304](D:\TYPORA\figures\屏幕截图 2026-08-17 104304.png) - -## 调试 - -### 使用ST-Link进入断点调试,程序停在main函数处 - -![image-20260817112957813](D:\TYPORA\figures\image-20260817112957813.png) - -![image-20260817113502159](D:\TYPORA\figures\image-20260817113502159.png) - -# 三、gitee的使用 - -### 配置提交者姓名、邮箱,查看git配置 - -![image-20260817142648941](D:\TYPORA\figures\image-20260817142648941.png) - -![image-20260817143835258](D:\TYPORA\figures\image-20260817143835258.png) - -![image-20260817144051657](D:\TYPORA\figures\image-20260817144051657.png) - -### 常用指令 - -**git init:初始化当前目录为git仓库** - -**git pull origin master:拉取远程master分支** - -**git add file/path:添加file/path到git暂存区** - -**git commit "message":本次git提交日志为“message”** - -**git push origin master:将本地git分支最新内容同步/推送到远端** - -**git pull:远端->本地** - -**git push:本地->远端** - -**git remote set-url origin 仓库名称:设置当前git仓库URL** - -**git fetch origin master:同步远端分支到本地** - -**git remote -v:查看仓库远端分支情况** - -**git checkout -b test:基于当前分支切出的一个名为test的新分支** - -![image-20260817150046870](D:\TYPORA\figures\image-20260817150046870.png) - -![image-20260817150446388](D:\TYPORA\figures\image-20260817150446388.png) - -![image-20260817151244692](D:\TYPORA\figures\image-20260817151244692.png) - -## 分支与PR操作 - -![image-20260817152048721](D:\TYPORA\figures\image-20260817152048721.png) - -![image-20260817152317118](D:\TYPORA\figures\image-20260817152317118.png) - diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817112957813.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817112957813.png" deleted file mode 100644 index 8cbefd2821fc003691fc15a62e6fc8b10e3fa467..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817112957813.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817113502159.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817113502159.png" deleted file mode 100644 index 685d7fb5b5eeefc01cfad53b85e4cac38945469b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817113502159.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817142648941.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817142648941.png" deleted file mode 100644 index 4bf38e6b886dfa690e5a7d726f15856157c6115b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817142648941.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817143835258.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817143835258.png" deleted file mode 100644 index 2ca7c09e48ba4d65780d5a1a5792b9c101bc3ecb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817143835258.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817144051657.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817144051657.png" deleted file mode 100644 index b3b6900b033d2ba826f94883eab89d955ede7434..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817144051657.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817150046870.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817150046870.png" deleted file mode 100644 index 95ab187ec4ddb3bd3eb788ab13f0dea61a728a80..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817150046870.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817150446388.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817150446388.png" deleted file mode 100644 index c0435456a9dbaaf4aa0dbb0f6c12ccc6d3230ecb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817150446388.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817151244692.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817151244692.png" deleted file mode 100644 index adf1c1cd27e5acbf0b9d5aa20a8ffebf8f0fbdc5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817151244692.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817152048721.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817152048721.png" deleted file mode 100644 index 4e39cef8d61f44e369319918be0b95fc35beace8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817152048721.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817152317118.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817152317118.png" deleted file mode 100644 index 74f66f4df025334e505c57b42daffa5aa8db5d48..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260817152317118.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818140556194.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818140556194.png" deleted file mode 100644 index 35ad6963fe9d9e24b90b02a92da4a88996349be1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818140556194.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818164439722.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818164439722.png" deleted file mode 100644 index a7a945d850b8166e44fe27032c825f6fc4120d9e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818164439722.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818202039012.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818202039012.png" deleted file mode 100644 index 8aeb7b88e692feaf17ef61fcf1f8480a5987806a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818202039012.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210556117.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210556117.png" deleted file mode 100644 index c22ddb4cc9edf0f30552b564c6c478f4de57f996..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210556117.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210605960.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210605960.png" deleted file mode 100644 index c22ddb4cc9edf0f30552b564c6c478f4de57f996..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210605960.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210732275.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210732275.png" deleted file mode 100644 index af7b05423b82c2281ec468ea8cd1ced2e2e4a3c5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210732275.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210821589.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210821589.png" deleted file mode 100644 index 4a0f90d7a951c426897f02ccb2eac4a0982376a5..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818210821589.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818211005741.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818211005741.png" deleted file mode 100644 index ff0da8904baeebbbd649d90ffadf5181b08d7389..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818211005741.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818211045732.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818211045732.png" deleted file mode 100644 index 6e56fc96a1b0a365e022e9f933be18b720ee294a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260818211045732.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819144141632.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819144141632.png" deleted file mode 100644 index 3ee96e6ac8528e5088c75d32c3665f0f96fd5714..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819144141632.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819150611545.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819150611545.png" deleted file mode 100644 index 33080ef05321f65e8c45b64c205bb22af2958773..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819150611545.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819150625868.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819150625868.png" deleted file mode 100644 index 689b648403c348a4a9675a375dbd1f5638390c15..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819150625868.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819152800741.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819152800741.png" deleted file mode 100644 index 33080ef05321f65e8c45b64c205bb22af2958773..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819152800741.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819155711526.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819155711526.png" deleted file mode 100644 index fd5ff0b2aa64dd9f143979addca012c5288f82ac..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819155711526.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819165949131.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819165949131.png" deleted file mode 100644 index d91a94d74a42ec2dbefbeebc03c736228db8b8dd..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/image-20260819165949131.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/rtt_startup.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/rtt_startup.png" deleted file mode 100644 index 04dbb79fed05b7463c5ceea179f5dd75c6553724..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/rtt_startup.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102048.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102048.png" deleted file mode 100644 index 800ec298cc4dd2fa7599d475a0c8f3bf52a3eecb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102048.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102442.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102442.png" deleted file mode 100644 index 95e8ab4ef99114778b540f5d2755243bfb511a95..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 102442.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 104304.png" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 104304.png" deleted file mode 100644 index e4fb0f0ce5fae09fda464fe2b060422cbe7ad598..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/figures/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 104304.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251.md" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251.md" deleted file mode 100644 index fc305cf69712bd233595724f533ca37754edfaa2..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251.md" +++ /dev/null @@ -1,377 +0,0 @@ - - -# 一、线程间同步 - -**信号量**:互斥信号量/二值信号量(初始值为0不可获取;到达同步点释放信号量,值变为1,可获得)/计数信号量(被初始化为n,n为非负整数,n是共享资源的数量;信号量大于0时,可获得;信号量等于0,不可获得,挂起状态) - -信号量控制块结构体 - -```c -struct rt_semaphore -{ - struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ - rt_uint16_t value; /* 信号量的值 */ - rt_uint16_t max_value; /* 信号量的最大值 */ - struct rt_spinlock spinlock; /* 自旋锁 */ -}; -/* rt_sem_t 是指向 semaphore 结构体的指针类型 */ -typedef struct rt_semaphore* rt_sem_t; -``` - -**创建信号量** - -```c - rt_sem_t rt_sem_create(const char *name, - rt_uint32_t value, - rt_uint8_t flag); -``` - -FIFO(先进先出)/PRIO(优先级等待) - -**删除信号量** - -```c -rt_err_t rt_sem_delete(rt_sem_t sem); -``` - -**初始化信号量** - -```c -rt_err_t rt_sem_init(rt_sem_t sem, - const char *name, - rt_uint32_t value, - rt_uint8_t flag) -``` - -**脱离信号量**:让信号量对象从内核对象管理器中脱离,适用于静态初始化的信号量 - -```c -rt_err_t rt_sem_detach(rt_sem_t sem); -``` - -**获取信号量**:当信号量值大于零时,线程将获得信号量,并且相应的信号量值会减 1 - -```c -rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t timeout); -``` - -**无等待获取信号量** - -```c -rt_err_t rt_sem_trytake(rt_sem_t sem); -``` - -**释放信号量** - -```c -rt_err_t rt_sem_release(rt_sem_t sem); -``` - -**互斥量** - -当线程持有它,互斥量处于闭锁状态,由这个线程获得它的所有权;当这个线程释放它时,将对互斥量进行开锁,失去它的所有权 - -当一个线程持有互斥量时,其他线程将不能对它进行开锁或持有它 - -**注:在获得互斥量后,请尽快释放互斥量,并且在持有互斥量的过程中,不得再行更改持有互斥量线程的优先级,否则可能人为引入无界优先级反转的问题。** - -**互斥量控制块结构体** - -```c -struct rt_mutex -{ - struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ - - rt_uint8_t ceiling_priority; /* 优先级天花板 */ - rt_uint8_t priority; /* 等待线程中的最高优先级 */ - rt_uint8_t hold; /* 持有线程的持有次数 */ - rt_uint8_t reserved; /* 保留字段 */ - - struct rt_thread *owner; /* 当前拥有互斥量的线程 */ - rt_list_t taken_list; /* 线程持有的对象链表 */ - struct rt_spinlock spinlock; /* 自旋锁 */ -}; -/* rt_mutex_t 为指向互斥量结构体的指针类型 */ -typedef struct rt_mutex* rt_mutex_t; -``` - -**创建互斥量** - -```c -rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag); -``` - -**删除互斥量** - -```c -rt_err_t rt_mutex_delete (rt_mutex_t mutex); -``` - - **初始化互斥量** - -```c -rt_err_t rt_mutex_init (rt_mutex_t mutex, const char* name, rt_uint8_t flag); -``` - -**脱离互斥量** - -```c -rt_err_t rt_mutex_detach (rt_mutex_t mutex); -``` - -**获取互斥量** - -```c -rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t timeout); -``` - -**无等待获取互斥量** - -```c -rt_err_t rt_mutex_trytake(rt_mutex_t mutex); -``` - -**释放互斥量** - -```c -rt_err_t rt_mutex_release(rt_mutex_t mutex); -``` - - - -**互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。** - - - -**事件集** - -可以实现一对多、多对多的同步 - -这种多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 “逻辑与” 或“逻辑或”将一个或多个事件关联起来,形成事件组合。 - -每一个线程可拥有32个事件标志,事件仅用于同步,不提供数据传输功能 - -**事件集控制块结构体** - -```c -struct rt_event -{ - struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ - - /* 事件集合,每一 bit 表示 1 个事件,bit 位的值可以标记某事件是否发生 */ - rt_uint32_t set; - struct rt_spinlock spinlock; -}; -/* rt_event_t 是指向事件结构体的指针类型 */ -typedef struct rt_event* rt_event_t; -``` - -**创建事件集** - -```c -rt_event_t rt_event_create(const char* name, rt_uint8_t flag); -``` - -**删除事件集** - -```c -rt_err_t rt_event_delete(rt_event_t event); -``` - -**初始化事件集** - -```c -rt_err_t rt_event_init(rt_event_t event, const char* name, rt_uint8_t flag); -``` - -**脱离事件集** - -```c -rt_err_t rt_event_detach(rt_event_t event); -``` - -**发送事件** - -```c -rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set); -``` - -**接收事件** - -```c -rt_err_t rt_event_recv(rt_event_t event, - rt_uint32_t set, - rt_uint8_t opt, - rt_int32_t timeout, - rt_uint32_t* recved); -``` - - - -# 二、线程间通信 - -**邮箱** - -开销低、效率高 - -**邮箱控制块结构体** - -```c -struct rt_mailbox -{ - struct rt_ipc_object parent; - - rt_uint32_t* msg_pool; /* 邮箱缓冲区的开始地址 */ - rt_uint16_t size; /* 邮箱缓冲区的大小 */ - - rt_uint16_t entry; /* 邮箱中邮件的数目 */ - rt_uint16_t in_offset, out_offset; /* 邮箱缓冲的进出指针 */ - rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */ - struct rt_spinlock spinlock; /* 自旋锁 */ -}; -typedef struct rt_mailbox* rt_mailbox_t; -``` - - - -**动态创建邮箱** - -```c -rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag); -``` - -**删除邮箱** - -```c -rt_err_t rt_mb_delete (rt_mailbox_t mb); -``` - -**初始化邮箱** - -```c - rt_err_t rt_mb_init(rt_mailbox_t mb, - const char* name, - void* msgpool, - rt_size_t size, - rt_uint8_t flag) -``` - -**脱离邮箱** - -```c -rt_err_t rt_mb_detach(rt_mailbox_t mb); -``` - -**发送邮件**:线程或者中断服务程序可以通过邮箱给其他线程发送邮件 - -```c -rt_err_t rt_mb_send (rt_mailbox_t mb, rt_ubase_t value); -``` - -**等待方式发送邮件**:用户向指定邮箱发送邮件 - -```c -rt_err_t rt_mb_send_wait (rt_mailbox_t mb, - rt_ubase_t value, - rt_int32_t timeout); -``` - -**发送紧急邮件** - -```c -rt_err_t rt_mb_urgent (rt_mailbox_t mb, rt_ubase_t value); -``` - -**接收邮件** - -```c -rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout); -``` - - - -**消息队列** - -线程间的消息交换、使用串口接收**不定长数据** - -**消息队列控制块结构体** - -```c -struct rt_messagequeue -{ - struct rt_ipc_object parent; - - void* msg_pool; /* 指向存放消息的缓冲区的指针 */ - - rt_uint16_t msg_size; /* 每个消息的长度 */ - rt_uint16_t max_msgs; /* 最大能够容纳的消息数 */ - - rt_uint16_t entry; /* 队列中已有的消息数 */ - - void* msg_queue_head; /* 消息链表头 */ - void* msg_queue_tail; /* 消息链表尾 */ - void* msg_queue_free; /* 空闲消息链表 */ - - rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */ - struct rt_spinlock spinlock; /* 自旋锁 */ -}; -typedef struct rt_messagequeue* rt_mq_t; -``` - -**创建消息队列** - -```c -rt_mq_t rt_mq_create(const char* name, rt_size_t msg_size, - rt_size_t max_msgs, rt_uint8_t flag); -``` - -**删除消息队列** - -```c -rt_err_t rt_mq_delete(rt_mq_t mq); -``` - -**初始化消息队列** - -```c -rt_err_t rt_mq_init(rt_mq_t mq, const char* name, - void *msgpool, rt_size_t msg_size, - rt_size_t pool_size, rt_uint8_t flag); -``` - -**脱离消息队列** - -```c -rt_err_t rt_mq_detach(rt_mq_t mq); -``` - -**发送消息** - -```c -rt_err_t rt_mq_send (rt_mq_t mq, const void *buffer, rt_size_t size); -``` - -**等待方式发送消息** - -```c -rt_err_t rt_mq_send_wait(rt_mq_t mq, - const void *buffer, - rt_size_t size, - rt_int32_t timeout); -``` - -**发送紧急消息** - -```c -rt_err_t rt_mq_urgent(rt_mq_t mq, const void *buffer, rt_size_t size); -``` - -**接收消息** - -```c -rt_ssize_t rt_mq_recv(rt_mq_t mq, - void *buffer, - rt_size_t size, - rt_int32_t timeout); -``` - diff --git "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260/\347\272\277\347\250\213\347\256\241\347\220\206.md" "b/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260/\347\272\277\347\250\213\347\256\241\347\220\206.md" deleted file mode 100644 index 161c5835a6d0b59de530c0be9fb63ba8c925ec1e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\346\275\230\351\234\236/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260/\347\272\277\347\250\213\347\256\241\347\220\206.md" +++ /dev/null @@ -1,100 +0,0 @@ -# 一、嵌入式实时操作系统 - -裸机模式缺陷:并发性、实时性、可维护性、可重用性 - -### 任务的实体:线程(“分而治之”) - -### RTOS精髓:多任务系统(并不是真正的同时进行多个任务,只是每个任务之间的间隔时间很短,在看起来就是多个任务同时进行) - -![启动流程](D:\TYPORA\figures\rtt_startup.png) - -board函数:初始化串口 - -main()函数优先级为10(timer可设置为4),idle函数的优先级最低(31) - -### 线程控制块(管理线程的顺序) - -线程:线程控制块+入口函数 - -结构体继承自基类:rt_object - -## 线程属性 - -线程栈:线程具有独立的栈空间 - -线程状态:初始状态、就绪状态、运行状态、挂起状态、关闭状态 - -从运行到挂起:delay延时,信号量、互斥量的获取; - -从挂起到运行的回复:信号量的释放; - -创建线程可以动态创建rt_thread_create(),也可以静态创建rt_thread_init() - -优先级:最大支持256个线程优先级(0-255),数值越小优先级越高,最低优先级默认分配给空闲线程使用(idle) - -时间片:优先级相同时,时间片起作用(仅对优先级相同的就绪态起作用) - -入口函数:void(*entry)(void * parameter) - -空闲线程(idle):优先级最低,线程状态永远为就绪态。通常是一个死循环,永远不能被挂起。主要用于:资源回收,提供钩子函数(钩入功耗管理、看门狗喂狗等工作) - -## 主线程:main() - -# 线程管理API - -### 线程初始化 - -静态线程创建(静态数组)rt_thread_init():用户提供的首地址需要对齐 - -动态线程创建rt_thread_create() - -### 线程启动 - -rt_err_t rt_thread_stratup() - -### 线程延时——让出CPU使用权 - -![image-20260818140556194](D:\TYPORA\figures\image-20260818140556194.png) - - - -# 二、SCons构建系统 - -SCons是RT-Thread常见的构建工具,主要用于管理工程编译过程,基于Python脚本描述工程结构,能够自动完成源文件扫描、编译、链接和工程生成 - -### 1.SCons的作用 - -1.1管理工程源文件 - -1.2配置编译器和链接器 - -1.3设置头文件路径 - -1.4设置宏定义和编译选项 - -1.5执行增量编译 - -1.6生成目标文件和工程文件 - -### 2.常见文件 - -RT-Thread工程中常见的构建文件包括:SConstruct、SConscript、rtconfig.py、Kconfig - -### 3.构建流程 - -读取SConstruct->解析各目录下的SConscript->收集源文件和依赖关系->调用编译器->调用链接器->生成ELF/HEX/BIN文件 - -### 4.常见命令 - -``` -scons #编译工程 -``` - -``` -scons -j4 #使用4个并行任务编译 -``` - -``` -scons --target=mdk5 #生成Keil MDK5工程 -``` - diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index f4c9d725399c4d3330a68854508a5c1502f23fe6..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,43 +0,0 @@ -# 第一天作业 - -## 学习总结 - -今天是 RT-Thread 夏令营 Day1,我主要学习了嵌入式系统的启动流程、裸机和 RTOS 的区别,以及 RT-Thread 的源码入口思路。 - -上午我重点理解了: -- 裸机程序的启动入口 -- 不同工具链下入口函数的差异 -- 从汇编入口到 C 语言入口的完整流程 - -下午我重点理解了: -- ARM F407 平台上的启动链路 -- `reset_handler` 到 `main` 的执行过程 -- `INIT_APP_EXPORT` 这类初始化宏的作用 -- `SysTick`、外设初始化和调度器启动的关系 - -我现在的理解是:RT-Thread 不是简单把 `main()` 当普通函数执行,而是把它放进线程体系里。也就是说,系统真正开始并发调度后,程序才进入 RTOS 的运行方式。 - -## 我的学习体会 - -刚开始听这部分内容时,我会觉得启动流程很零散,很多名词也比较陌生。但是老师把流程按“上电 -> 初始化 -> 入口 -> 调度器 -> 线程”这样串起来以后,我就能慢慢把它们连成一条线了。 - -对我来说,最有帮助的一点是:不要只背结论,要去看源码里每一步是怎么接上的。这样理解会更稳,也更容易记住。 - -## 目前的不足 - -1. 我对启动汇编部分还不够熟,需要继续看源码。 -2. 我对组件初始化收口机制还要再多练习。 -3. 我对后面调度器启动后的线程切换过程,暂时还停留在理解概念阶段。 - -## 后续计划 - -接下来我准备继续做这几件事: - -1. 对照源码重新梳理启动流程。 -2. 把 `main` 线程、空闲线程、定时器线程的关系再理清楚。 -3. 结合后面的课程和实践,继续补充我的笔记。 -4. 把每天学到的内容整理成自己的理解,方便后续复习。 - -## 作业完成说明 - -本次作业已按照夏令营仓库要求整理为个人目录结构,后续会随第二个 PR 一起提交。 diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 84fd73bda139b64d3d0c378644ab48f6fb76c725..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,35 +0,0 @@ -# 第一天笔记 - -## 上午场:我对启动流程的理解 - -今天上午主要在讲嵌入式系统的启动流程。我一开始对“上电后程序怎么跑起来”其实是有点模糊的,只知道 MCU 会先跑一段启动代码,后面才会进入 `main()`。听完以后,我把这个过程大概串起来了。 - -在裸机工程里,启动流程会先经过汇编入口,再到系统初始化代码,最后进入 `main()`。不同工具链的入口名字不一样,比如 MDK、IAR、GCC 的写法就不完全相同,但本质上都是先完成栈、数据段、BSS 段等基础初始化,再把程序送到 C 语言入口。 - -RT-Thread 的启动流程和裸机有明显区别。它不是简单地“进了 main 就结束”,而是会把 `main` 放到一个线程里运行,整个系统也从顺序执行变成了多线程调度。这个地方我印象很深,因为它让我理解了“裸机程序”和“RTOS 程序”到底差在哪里。 - -## 下午场:我对源码和工程的理解 - -下午内容更偏源码和实操。老师从 ARM 架构和 F407 芯片出发,把启动链路从 `Entry`、`reset_handler` 一直到 `system` 和 `main`,让我第一次感觉到启动过程不是零散的函数,而是一条完整的链。 - -我还记住了一个关键点:组件初始化不是一个个手动调用,而是通过类似 `INIT_APP_EXPORT` 这样的宏收口到统一的机制里,再由系统按顺序执行。这样做的好处是,模块之间更独立,也更方便后续扩展。 - -另外,老师也讲了 `SysTick`、外设初始化顺序和调度器启动。我以前只知道“系统最后会开始跑任务”,现在更清楚了:只有调度器启动后,多线程才真正开始工作,系统也才进入 RT-Thread 的运行状态。 - -## 我今天的新收获 - -1. 我知道了裸机和 RTOS 启动流程的核心差异。 -2. 我理解了 `main()` 在 RT-Thread 里更像是一个线程入口。 -3. 我开始能顺着源码去找启动链路,而不是只记结论。 -4. 我明白了组件初始化为什么要用统一的导出机制。 -5. 我对“系统从上电到进入调度器”的过程有了完整印象。 - -## 我还不太熟的地方 - -1. 启动代码里一些汇编入口和具体符号名,我还需要再对照源码看一遍。 -2. `INIT_APP_EXPORT` 这种宏背后的链接段机制,我现在只是理解了大概,还没完全吃透。 -3. 调度器启动前后,线程状态变化的细节,我还想再结合实验看看。 - -## 小结 - -今天是我第一次把 RT-Thread 的启动流程完整串起来。虽然还有很多细节没完全掌握,但至少我知道该从哪里看源码,也知道后面学习要重点盯哪些地方。对我来说,这一天最重要的收获不是背会流程,而是建立了“程序是怎么从芯片启动走到系统运行”的整体认知。 diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 430a6c51d5e819fbfd636523953bb1353b0a2aa1..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,183 +0,0 @@ -# RT-Thread 夏令营 Day4 学习笔记:IO 设备和 PIN 设备 - -- 姓名:王童乐 -- 组别:第 2 组 -- 开发板:STM32F407 星火一号(STM32F407ZG) -- 今天的主题:IO 设备驱动框架、PIN 设备、按键中断、I2C 和 SPI - -## 一、今天主要讲了什么 - -今天老师讲的是 RT-Thread 的 IO 设备驱动框架。刚开始听到“分层”和“ops”时,我觉得有点抽象。结合代码和实验后,我的理解是:应用程序不直接操作 STM32 的寄存器,而是先调用 RT-Thread 提供的接口,再由不同层的驱动代码去完成真正的硬件操作。 - -我目前理解的调用关系大概是这样: - -```text -应用程序 - -> RT-Thread 通用设备接口 -IO 设备管理层 - -> open / read / write / control / close -PIN、SPI、I2C 等专用框架 - -> 对外提供各自的 API -BSP 驱动层 - -> 调用 STM32 HAL -硬件外设 -``` - -这样做的好处是,应用代码不用跟某一个 MCU 的底层函数绑死。以后如果更换芯片,很多时候只需要修改 BSP 驱动层,上面的应用代码还可以继续使用。当然,分层也会带来一些额外代码,占用一点 Flash,这是抽象带来的代价。 - -## 二、我对 PIN 设备的理解 - -GPIO 就是我们平时说的普通引脚,可以用来输出高低电平,也可以读取输入电平。在 RT-Thread 里,GPIO 被统一叫作 PIN 设备。 - -PIN 设备常见的工作模式有: - -- 输出 -- 输入上拉 -- 输入下拉 -- 输入浮空 -- 开漏 - -PIN 还可以配置上升沿、下降沿、高电平、低电平和双边沿中断。 - -我觉得 PIN 设备比较方便的一点是,它有专门的 `rt_pin_xxx` API,比如 `rt_pin_write()` 和 `rt_pin_read()`。同时,它也可以按照普通字符设备的方式使用 `open/read/write/control/close`。也就是说,同一个设备有比较简单的用法,也有比较通用的用法。 - -## 三、PIN 设备是怎样一层层接起来的 - -老师讲到 PIN 设备时,我最开始不太明白 `rt_pin_write()` 最后是怎么控制到某个引脚的。现在我先按照自己的理解,把它简化成下面这条链: - -```text -应用调用 rt_pin_write(37, 1) - -> PIN 框架里的 rt_pin_write() - -> PIN 设备的 ops - -> STM32 BSP 里的 stm32_pin_write() - -> HAL_GPIO_WritePin() - -> STM32 的 PC5 引脚 -``` - -### 1. BSP 驱动层:真正对接芯片 - -在 STM32 的 BSP 代码中,会有类似 `stm32xxxx_pin.c` 的文件。这个文件需要实现 `struct rt_pin_ops` 里的函数,例如: - -```c -pin_mode -pin_write -pin_read -pin_attach_irq -pin_detach_irq -pin_irq_enable -``` - -这些函数里面再去调用 STM32 HAL 的函数,比如 `HAL_GPIO_Init()`、`HAL_GPIO_WritePin()` 和 `HAL_GPIO_ReadPin()`。我现在的理解是,这一层主要负责把 RT-Thread 的说法翻译成 STM32 能看懂的说法。 - -课堂上还讲到了引脚编号。例如 `PC5` 可以按照端口编号换算成 37: - -```text -32 * (C - A) + 5 = 37 -``` - -这个公式是为了方便框架统一管理引脚,但具体项目还是要以当前 BSP 的宏定义为准,不能只靠记公式。 - -底层 ops 准备好后,BSP 会注册 PIN 设备,形式类似: - -```c -rt_device_pin_register("pin", &stm32_pin_ops, RT_NULL); -``` - -### 2. PIN 框架层:把底层函数包装起来 - -在 `components/drivers/pin/pin.c` 中,RT-Thread 会定义 PIN 设备对象,并提供 `rt_pin_mode()`、`rt_pin_write()`、`rt_pin_read()` 和中断相关接口。 - -我把这一层理解成中间翻译层:上面给应用提供统一函数,下面再去调用 BSP 的 `pin_write`、`pin_read` 等函数。这样应用代码就不需要知道 STM32 HAL 的细节。 - -### 3. IO 管理层:把设备放进系统 - -板级初始化时会调用 `rt_hw_pin_init()`,并通过 `INIT_BOARD_EXPORT` 这样的自动初始化机制运行。PIN 设备注册成功后,可以在 FinSH 中输入: - -```text -list_device -``` - -如果能看到 `pin`,说明 PIN 设备已经进入 RT-Thread 的设备列表。 - -我对这部分的记忆口诀是: - -> 应用调用 PIN API,PIN 框架调用 BSP ops,BSP ops 再调用 STM32 HAL。 - -## 四、上午做的三个实验 - -### 实验 1:创建一个字符设备 - -我们用 `rt_device_create()` 创建了一个叫 `test_dev` 的设备,再用 `rt_device_register()` 把它注册到系统里。注册后输入 `list_device`,可以看到这个设备。这个实验让我第一次看到自己写的设备也能进入 RT-Thread 设备列表,比只看源码直观很多。 - -### 实验 2:给设备补上 ops - -接着给 `test_dev` 写了 `init`、`open`、`read`、`close` 和 `control` 等回调函数,并在函数里用 ulog 打印信息。调用上层设备接口时,可以从日志里看到对应的 ops 被执行了。 - -这里有一个容易踩的坑:如果没有先执行 `rt_device_open()`,就直接调用 `read`,设备框架可能会拒绝这次操作。所以设备的使用顺序也很重要。 - -### 实验 3:四个方向键的中断 - -开发板上的四个按键使用了 `PC0`、`PC1`、`PC4` 和 `PC5`。我们把它们设置成上拉输入,再用 `rt_pin_attach_irq()` 绑定下降沿中断。按下按键后,回调函数会打印按键事件。 - -按键实验中我注意到,机械按键按一次不一定只产生一个边沿,这就是按键抖动。如果不处理,可能按一次却打印多次。因此实际项目中要做软件消抖,也可以使用 button 软件包。 - -另外,中断回调里不适合做太多耗时工作。课堂上还提醒了,如果想在中断中使用 ulog,需要打开异步日志,并确认当前日志配置支持这种用法。 - -## 五、下午简单了解的 I2C 和 SPI - -下午还快速看了 I2C 和 SPI。它们和 PIN 的思路是一样的:底层 BSP 先实现 ops,框架层再把这些函数整理成应用可以使用的接口。 - -| 总线 | 我的理解 | 课堂上记住的 API | -| --- | --- | --- | -| I2C | 先找到总线 `i2c1`,再把设备挂上去,例如 `i2c10` | `rt_i2c_transfer` | -| SPI | 先找到 `spi1`,再配置片选设备,例如 `spi10` | `rt_spi_configure`、`rt_spi_send`、`rt_spi_recv` | - -I2C 传输时会使用 `struct rt_i2c_msg`。老师特别提醒了地址通常是 7 位地址,实际传输时要按照接口要求处理地址位。SPI 使用前需要配置模式、数据宽度和通信速率,发送和接收通常可以同时进行。 - -我现在还不能完全独立写出 I2C 或 SPI 驱动,但已经能看懂它们和 PIN 框架之间的共同思路了。 - -## 六、今天我的收获 - -1. 我知道了为什么 RT-Thread 要把设备驱动分成好几层。 -2. 我大概理解了 `rt_pin_write()` 从应用到 HAL 的调用过程。 -3. 我知道了 PIN 设备既可以调用 `rt_pin_xxx`,也可以当字符设备使用。 -4. 通过 `test_dev` 实验,我看到了 `rt_device_ops` 是怎样被调用的。 -5. 我知道按键中断需要考虑消抖,中断回调也不能写得太复杂。 -6. 我开始把 PIN、I2C 和 SPI 看成同一套驱动框架思路,而不是三个完全独立的知识点。 - -## 七、我还没完全弄懂的地方 - -1. 不同 STM32 BSP 的 Pin 编号宏具体是怎么定义的,我还需要继续查源码。 -2. `rt_device_pin_register()` 内部的 ops 包装过程,我目前只理解了大概。 -3. 中断触发后,底层 IRQ 是怎样一路传到回调函数的,还需要结合源码和断点再看。 -4. I2C 的 7 位地址和实际发送地址之间的关系,我还需要多做几次实验。 - -## 八、今天的简单总结 - -今天的内容比前几天更偏源码,所以一开始有些绕。现在我还不能说自己完全掌握了驱动框架,但至少知道应该从哪里开始看:先看应用调用的 API,再顺着 ops 找到 PIN 框架和 BSP,最后看到 STM32 HAL。 - -我觉得今天最重要的收获不是记住所有函数名,而是明白了应用代码和硬件之间还有一层层驱动框架。这也是 RT-Thread 能够跨不同 MCU 复用应用代码的原因。 - -## 九、PR 提交说明草稿 - -**Title** - -```text -docs: add beginner notes for RT-Thread Day4 PIN device -``` - -**Body** - -```text -## Summary -- 整理 RT-Thread 夏令营 Day4 的学习笔记 -- 用自己的理解记录 PIN 设备从应用到 BSP 的调用过程 -- 补充 test_dev、按键中断、消抖以及 I2C/SPI 的课堂内容 - -## Changes -- 新增 2026/第2组/王童乐/笔记/第四天笔记.md - -## Test -- 根据课堂实验复盘 test_dev 字符设备注册和 rt_pin_attach_irq 按键回调 -- 文档已通过 Markdown 空白检查 -``` diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index a23f3e7fdf224221039938590179ed4acefdb07a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,112 +0,0 @@ -# RT-Thread 线程间同步与通信作业 - -本目录包含第三天作业的四个独立示例。代码面向 RT-Thread 4.1.x,使用 FinSH/MSH 命令启动,不需要在 `main()` 中直接调用示例函数。 - -## 基本信息 - -- 姓名:王童乐 -- 组别:第 2 组 -- 开发板:STM32F407 星火一号(STM32F407ZG) -- RT-Thread:4.1.1 -- 开发工具:RT-Thread Studio 2.3.1、GNU Arm Embedded Toolchain -- 学习内容:信号量、互斥量、事件集和消息队列 - -## 作业完成情况 - -| 作业要求 | 实现情况 | -| --- | --- | -| 每个练习至少两个线程 | 已完成,四个练习分别创建 5、2、4、3 个线程 | -| 检查线程和 IPC 创建结果 | 已完成,失败路径会回收此前创建的资源 | -| 通过 MSH 命令启动 | 已导出四个独立命令 | -| 命令可重复执行 | 运行中拒绝重复启动,结束后删除动态 IPC 并清除运行标志 | -| 理论总结与实验报告 | 本文包含 IPC 原理、设计方法、思考题和验证记录 | - -## 文件与命令 - -| 文件 | MSH 命令 | IPC | 线程数量 | -| --- | --- | --- | --- | -| `parking_demo.c` | `parking_demo` | 计数信号量、互斥量 | 5 辆车 | -| `ticket_demo.c` | `ticket_demo` | 互斥量 | 2 个窗口 | -| `startup_demo.c` | `startup_demo` | 事件集 | 3 个初始化线程 + 业务线程 | -| `package_demo.c` | `package_demo` | 消息队列 | 2 个发送线程 + 分拣线程 | - -## 加入工程 - -将四个 `.c` 文件复制到 RT-Thread Studio 工程的 `applications` 目录,确认工程启用了 FinSH/MSH、线程、信号量、互斥量、事件和消息队列组件,然后重新生成并编译工程。 - -```c -#include -#include -``` - -每个文件都能单独编译。如果把四个文件同时加入工程,命令名和全局符号不会冲突。 - -## 运行方法 - -通过串口终端依次输入: - -```text -parking_demo -ticket_demo -ticket_demo unsafe -startup_demo -package_demo -``` - -每条命令立即返回,工作线程在后台运行。示例结束后会删除本次创建的 IPC 对象和线程,因此同一命令可以再次执行;运行期间重复输入会得到 `is already running` 提示。 - -## 练习一:停车场 - -`park_sem` 的初始值为 3,5 个车辆线程必须先获取车位信号量才能进入。获取操作等待 2 秒,超时会打印等待信息并重新申请。进入和离开时使用 `park_lock` 保护 `used` 计数,因此任何时刻不会超过 3 个车位。 - -这里使用计数信号量而不是互斥量,是因为资源有 3 个可用实例;互斥量只能表示“有/无”一个临界资源,不能直接表示剩余数量。 - -## 练习二:售票 - -默认执行 `ticket_demo` 时,窗口线程在同一个互斥量临界区内完成“检查余票、分配票号、减少余票、记录售出数量”四步。这样两个窗口不会拿到相同票号,最终余票为 0、售出票数为 20。 - -执行 `ticket_demo unsafe` 会运行故意不保护售票过程的对比模式。代码在读取余票和写回余票之间延时 10 ms,使两个窗口容易读取同一个票号,从而观察重复售票和统计错误。只给 `ticket_count--` 加锁也不够,因为“检查是否有票”和“递减”必须作为一个不可分割的操作。 - -## 练习三:启动条件 - -网络、传感器和存储线程分别在 1 秒、2 秒和 5 秒后发送事件位。业务线程先用 AND 模式等待 3 秒,预期先打印存储模块未就绪;随后无限等待,三个事件全部到达后打印: - -```text -all modules are ready -business task started -``` - -事件集适合表达多个独立条件的组合等待;若使用三个信号量,业务线程需要分别获取并自行维护已经完成的条件,清除和重复等待也更繁琐。 - -## 练习四:快递分拣 - -消息队列容量为 5。两个发送线程各生成 5 条唯一编号的消息,分拣线程按区域统计数量和总重量。发送使用非阻塞 `rt_mq_send`,队列满时累计对应发送线程的失败次数并稍后重试,所以不会因队列暂满而丢失消息或崩溃。两个发送线程各发送一个结束标记,分拣线程收到两个结束标记后输出统计并退出。 - -预期总数为 10,区域数量之和为 10,编号范围为 `1001~1005` 和 `2001~2005`,且不会重复。 - -## 资源检查与生命周期 - -- 所有信号量、互斥量、事件、消息队列和动态线程都检查了创建返回值。 -- 创建线程失败时,命令会删除已经创建的线程和 IPC 对象。 -- 正常完成时,由最后一个工作线程或分拣线程删除 IPC 对象,并清除运行标志。 -- 线程入口只在普通线程环境中执行阻塞 IPC 操作,没有在中断中调用这些 API。 - -## 验证建议 - -在 STM32F407 星火一号工程中下载后,通过 115200-8-N-1 串口观察输出。建议一次只启动一个命令,等待它打印 finished 或最终统计后再启动下一项。还可以使用 FinSH 的 `list_thread`、`list_mem` 检查线程退出后的资源状态。 -## 编译验证记录 - -四个源文件已经使用 RT-Thread Studio 所带的 GNU Arm Embedded Toolchain 进行独立编译检查,并与第二天的 STM32F407 工程完整链接。验证结果如下: - -```text -PASS parking_demo.c -PASS ticket_demo.c -PASS startup_demo.c -PASS package_demo.c - -text: 70008 B -data: 1920 B -bss: 4696 B -``` - -链接映射文件中能够找到 `__fsym_parking_demo`、`__fsym_ticket_demo`、`__fsym_startup_demo` 和 `__fsym_package_demo`,说明四个命令均已进入 FinSH 命令表。当前记录仅代表编译和链接验证通过,开发板下载、串口运行结果和运行截图需要在实际硬件测试后补充。 diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" deleted file mode 100644 index 6fbe57dbccf568d3073dfd18f420223f34ac3901..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" +++ /dev/null @@ -1,174 +0,0 @@ -#include -#include - -#define PACKAGE_QUEUE_CAPACITY 5 -#define PACKAGE_PRODUCER_COUNT 2 -#define PACKAGE_PER_PRODUCER 5 -#define PACKAGE_END_MARKER 0 - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -struct package_context -{ - rt_mq_t queue; - rt_uint32_t end_count; - rt_uint32_t total; - rt_uint32_t region_count[3]; - rt_uint32_t total_weight; - rt_uint32_t send_failed[PACKAGE_PRODUCER_COUNT]; - rt_bool_t running; - rt_thread_t threads[PACKAGE_PRODUCER_COUNT + 1]; -}; - -static struct package_context package; - -static const char *package_region_name(rt_uint8_t region) -{ - if (region == 1) return "east"; - if (region == 2) return "south"; - if (region == 3) return "north"; - return "unknown"; -} - -static rt_err_t package_send_retry(const struct package_msg *msg, rt_uint32_t index) -{ - while (rt_mq_send(package.queue, msg, sizeof(*msg)) != RT_EOK) - { - package.send_failed[index]++; - rt_thread_mdelay(100); - } - return RT_EOK; -} - -static void package_producer_worker(void *parameter) -{ - rt_uint32_t index = (rt_uint32_t)(rt_ubase_t)parameter; - rt_uint32_t i; - struct package_msg msg; - - for (i = 0; i < PACKAGE_PER_PRODUCER; i++) - { - msg.id = (index == 0 ? 1001U : 2001U) + i; - msg.region = (rt_uint8_t)(((i + index) % 3) + 1); - msg.weight = (rt_uint16_t)(700 + index * 170 + i * 95); - package_send_retry(&msg, index); - rt_kprintf("sender%u queued package %u\n", index + 1, msg.id); - rt_thread_mdelay(20); - } - - msg.id = PACKAGE_END_MARKER; - msg.region = 0; - msg.weight = 0; - package_send_retry(&msg, index); - rt_kprintf("sender%u finished\n", index + 1); - rt_thread_delete(rt_thread_self()); -} - -static void package_sorter_worker(void *parameter) -{ - struct package_msg msg; - (void)parameter; - while (package.end_count < PACKAGE_PRODUCER_COUNT) - { - if (rt_mq_recv(package.queue, &msg, sizeof(msg), RT_WAITING_FOREVER) < 0) - { - rt_kprintf("package_demo: queue receive failed\n"); - continue; - } - - if (msg.id == PACKAGE_END_MARKER) - { - package.end_count++; - continue; - } - - if (msg.region >= 1 && msg.region <= 3) - package.region_count[msg.region - 1]++; - package.total++; - package.total_weight += msg.weight; - rt_kprintf("package %u -> %s, weight: %u g\n", msg.id, - package_region_name(msg.region), msg.weight); - rt_thread_mdelay(80); - } - - rt_kprintf("total packages: %u\n", package.total); - rt_kprintf("east: %u, south: %u, north: %u\n", - package.region_count[0], package.region_count[1], - package.region_count[2]); - rt_kprintf("total weight: %u g\n", package.total_weight); - rt_kprintf("send failed: %u\n", - package.send_failed[0] + package.send_failed[1]); - rt_mq_delete(package.queue); - package.running = RT_FALSE; - rt_thread_delete(rt_thread_self()); -} - -static void package_demo(void) -{ - rt_uint32_t i; - - if (package.running) - { - rt_kprintf("package_demo is already running\n"); - return; - } - - package.queue = rt_mq_create("package_mq", sizeof(struct package_msg), - PACKAGE_QUEUE_CAPACITY, RT_IPC_FLAG_FIFO); - if (package.queue == RT_NULL) - { - rt_kprintf("package_demo: message queue create failed\n"); - return; - } - package.end_count = 0; - package.total = 0; - package.region_count[0] = 0; - package.region_count[1] = 0; - package.region_count[2] = 0; - package.total_weight = 0; - package.send_failed[0] = 0; - package.send_failed[1] = 0; - package.running = RT_TRUE; - - for (i = 0; i < PACKAGE_PRODUCER_COUNT; i++) - { - package.threads[i] = rt_thread_create(i == 0 ? "sender_a" : "sender_b", - package_producer_worker, - (void *)(rt_ubase_t)i, - 768, 16, 10); - if (package.threads[i] == RT_NULL) - { - rt_uint32_t j; - package.running = RT_FALSE; - for (j = 0; j < i; j++) - rt_thread_delete(package.threads[j]); - rt_mq_delete(package.queue); - rt_kprintf("package_demo: sender %u create failed\n", i + 1); - return; - } - } - - package.threads[PACKAGE_PRODUCER_COUNT] = - rt_thread_create("sorter", package_sorter_worker, RT_NULL, - 1024, 18, 10); - if (package.threads[PACKAGE_PRODUCER_COUNT] == RT_NULL) - { - package.running = RT_FALSE; - for (i = 0; i < PACKAGE_PRODUCER_COUNT; i++) - rt_thread_delete(package.threads[i]); - rt_mq_delete(package.queue); - rt_kprintf("package_demo: sorter thread create failed\n"); - return; - } - - for (i = 0; i < PACKAGE_PRODUCER_COUNT + 1; i++) - rt_thread_startup(package.threads[i]); - rt_kprintf("package_demo started: queue capacity %d\n", - PACKAGE_QUEUE_CAPACITY); -} -MSH_CMD_EXPORT(package_demo, message queue package sorting demo); diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" deleted file mode 100644 index 822d5e87f8fdb56b0b619368ee7682abf564b8d7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" +++ /dev/null @@ -1,118 +0,0 @@ -#include -#include - -#define PARKING_CAR_COUNT 5 -#define PARKING_SPACE_COUNT 3 - -struct parking_context -{ - rt_sem_t spaces; - rt_mutex_t lock; - rt_uint32_t used; - rt_uint32_t finished; - rt_bool_t running; - rt_thread_t threads[PARKING_CAR_COUNT]; -}; - -static struct parking_context parking; -static const char *const parking_names[PARKING_CAR_COUNT] = { - "car1", "car2", "car3", "car4", "car5" -}; - -static void parking_worker(void *parameter) -{ - rt_uint32_t id = (rt_uint32_t)(rt_ubase_t)parameter; - rt_bool_t entered = RT_FALSE; - rt_bool_t is_last = RT_FALSE; - - while (!entered) - { - if (rt_sem_take(parking.spaces, RT_TICK_PER_SECOND * 2) == RT_EOK) - { - rt_mutex_take(parking.lock, RT_WAITING_FOREVER); - parking.used++; - rt_kprintf("car%u entered, used spaces: %u\n", id, parking.used); - rt_mutex_release(parking.lock); - entered = RT_TRUE; - } - else - { - rt_kprintf("car%u waiting for a space\n", id); - } - } - - rt_thread_mdelay(2500 + id * 150); - rt_mutex_take(parking.lock, RT_WAITING_FOREVER); - if (parking.used > 0) - parking.used--; - rt_kprintf("car%u left\n", id); - parking.finished++; - if (parking.finished == PARKING_CAR_COUNT) - is_last = RT_TRUE; - rt_mutex_release(parking.lock); - rt_sem_release(parking.spaces); - - if (is_last) - { - rt_kprintf("parking_demo finished, maximum capacity was %u\n", - PARKING_SPACE_COUNT); - rt_sem_delete(parking.spaces); - rt_mutex_delete(parking.lock); - parking.running = RT_FALSE; - } - - rt_thread_delete(rt_thread_self()); -} - -static void parking_demo(void) -{ - rt_uint32_t i; - - if (parking.running) - { - rt_kprintf("parking_demo is already running\n"); - return; - } - - parking.spaces = rt_sem_create("park_sem", PARKING_SPACE_COUNT, - RT_IPC_FLAG_FIFO); - if (parking.spaces == RT_NULL) - { - rt_kprintf("parking_demo: semaphore create failed\n"); - return; - } - parking.lock = rt_mutex_create("park_lock", RT_IPC_FLAG_FIFO); - if (parking.lock == RT_NULL) - { - rt_sem_delete(parking.spaces); - rt_kprintf("parking_demo: mutex create failed\n"); - return; - } - - parking.used = 0; - parking.finished = 0; - parking.running = RT_TRUE; - for (i = 0; i < PARKING_CAR_COUNT; i++) - { - parking.threads[i] = rt_thread_create(parking_names[i], parking_worker, - (void *)(rt_ubase_t)(i + 1), - 768, 16, 10); - if (parking.threads[i] == RT_NULL) - { - rt_uint32_t j; - parking.running = RT_FALSE; - for (j = 0; j < i; j++) - rt_thread_delete(parking.threads[j]); - rt_mutex_delete(parking.lock); - rt_sem_delete(parking.spaces); - rt_kprintf("parking_demo: car thread %u create failed\n", i + 1); - return; - } - } - - for (i = 0; i < PARKING_CAR_COUNT; i++) - rt_thread_startup(parking.threads[i]); - rt_kprintf("parking_demo started: %u cars, %u spaces\n", - PARKING_CAR_COUNT, PARKING_SPACE_COUNT); -} -MSH_CMD_EXPORT(parking_demo, counting semaphore parking lot demo); diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" deleted file mode 100644 index a4ce90c0d181a5086e64df105f6a2d202a7d90f9..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" +++ /dev/null @@ -1,133 +0,0 @@ -#include -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -struct startup_context -{ - rt_event_t event; - volatile rt_uint32_t ready_mask; - rt_bool_t running; - rt_thread_t threads[4]; -}; - -struct startup_arg -{ - rt_uint32_t bit; - rt_uint32_t delay_ms; - const char *name; -}; - -static struct startup_context startup; -static struct startup_arg startup_args[3] = { - {EVENT_NET_READY, 1000, "network"}, - {EVENT_SENSOR_READY, 2000, "sensor"}, - {EVENT_STORAGE_READY, 5000, "storage"} -}; - -static void startup_module_worker(void *parameter) -{ - struct startup_arg *arg = (struct startup_arg *)parameter; - - rt_thread_mdelay(arg->delay_ms); - rt_enter_critical(); - startup.ready_mask |= arg->bit; - rt_exit_critical(); - if (rt_event_send(startup.event, arg->bit) != RT_EOK) - rt_kprintf("%s event send failed\n", arg->name); - else - rt_kprintf("%s ready\n", arg->name); - rt_thread_delete(rt_thread_self()); -} - -static void startup_business_worker(void *parameter) -{ - rt_uint32_t received = 0; - rt_uint32_t missing; - - (void)parameter; - if (rt_event_recv(startup.event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_TICK_PER_SECOND * 3, &received) != RT_EOK) - { - missing = EVENT_ALL_READY & ~startup.ready_mask; - rt_kprintf("wait modules timeout, missing:%s%s%s\n", - (missing & EVENT_NET_READY) ? " network" : "", - (missing & EVENT_SENSOR_READY) ? " sensor" : "", - (missing & EVENT_STORAGE_READY) ? " storage" : ""); - - if (rt_event_recv(startup.event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_WAITING_FOREVER, &received) != RT_EOK) - { - rt_kprintf("startup_demo: event wait failed\n"); - rt_event_delete(startup.event); - startup.running = RT_FALSE; - rt_thread_delete(rt_thread_self()); - return; - } - } - - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - rt_event_delete(startup.event); - startup.running = RT_FALSE; - rt_thread_delete(rt_thread_self()); -} - -static void startup_demo(void) -{ - rt_uint32_t i; - - if (startup.running) - { - rt_kprintf("startup_demo is already running\n"); - return; - } - - startup.event = rt_event_create("startup_evt", RT_IPC_FLAG_FIFO); - if (startup.event == RT_NULL) - { - rt_kprintf("startup_demo: event create failed\n"); - return; - } - startup.ready_mask = 0; - startup.running = RT_TRUE; - - for (i = 0; i < 3; i++) - { - startup.threads[i] = rt_thread_create(startup_args[i].name, - startup_module_worker, - &startup_args[i], 768, 16, 10); - if (startup.threads[i] == RT_NULL) - { - rt_uint32_t j; - startup.running = RT_FALSE; - for (j = 0; j < i; j++) - rt_thread_delete(startup.threads[j]); - rt_event_delete(startup.event); - rt_kprintf("startup_demo: module thread %u create failed\n", i); - return; - } - } - - startup.threads[3] = rt_thread_create("business", startup_business_worker, - RT_NULL, 1024, 15, 10); - if (startup.threads[3] == RT_NULL) - { - startup.running = RT_FALSE; - for (i = 0; i < 3; i++) - rt_thread_delete(startup.threads[i]); - rt_event_delete(startup.event); - rt_kprintf("startup_demo: business thread create failed\n"); - return; - } - - for (i = 0; i < 4; i++) - rt_thread_startup(startup.threads[i]); - rt_kprintf("startup_demo started; storage intentionally takes 5 seconds\n"); -} -MSH_CMD_EXPORT(startup_demo, event based startup condition demo); diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" deleted file mode 100644 index 795d2014a5bc7552e2be9d344ac6d98888b15535..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" +++ /dev/null @@ -1,124 +0,0 @@ -#include -#include - -#define TICKET_TOTAL 20 -#define TICKET_WINDOW_COUNT 2 - -struct ticket_context -{ - rt_mutex_t lock; - rt_int32_t remaining; - rt_uint32_t sold; - rt_uint32_t finished; - rt_bool_t safe_mode; - rt_bool_t running; - rt_thread_t threads[TICKET_WINDOW_COUNT]; -}; - -static struct ticket_context ticket; - -static void ticket_worker(void *parameter) -{ - char window = (char)(rt_ubase_t)parameter; - - while (1) - { - rt_int32_t ticket_no = 0; - - if (ticket.safe_mode) - { - rt_mutex_take(ticket.lock, RT_WAITING_FOREVER); - if (ticket.remaining > 0) - { - /* Check, decrement, and ticket assignment form one operation. */ - ticket_no = ticket.remaining; - ticket.remaining--; - ticket.sold++; - } - rt_mutex_release(ticket.lock); - } - else - { - /* Intentionally unsafe: both windows can read the same value. */ - ticket_no = ticket.remaining; - if (ticket_no > 0) - { - rt_thread_mdelay(10); - ticket.remaining = ticket_no - 1; - ticket.sold++; - } - } - - if (ticket_no == 0) - break; - - rt_kprintf("window %c sold ticket %ld\n", window, (long)ticket_no); - rt_thread_mdelay(80); - } - - rt_mutex_take(ticket.lock, RT_WAITING_FOREVER); - ticket.finished++; - if (ticket.finished == TICKET_WINDOW_COUNT) - { - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: %ld, sold: %u\n", - (long)ticket.remaining, ticket.sold); - rt_mutex_release(ticket.lock); - rt_mutex_delete(ticket.lock); - ticket.running = RT_FALSE; - } - else - { - rt_mutex_release(ticket.lock); - } - - rt_thread_delete(rt_thread_self()); -} - -static int ticket_demo(int argc, char **argv) -{ - rt_uint32_t i; - - if (ticket.running) - { - rt_kprintf("ticket_demo is already running\n"); - return -1; - } - - ticket.lock = rt_mutex_create("ticket_lock", RT_IPC_FLAG_FIFO); - if (ticket.lock == RT_NULL) - { - rt_kprintf("ticket_demo: mutex create failed\n"); - return -1; - } - ticket.remaining = TICKET_TOTAL; - ticket.sold = 0; - ticket.finished = 0; - ticket.safe_mode = !((argc > 1) && (argv[1][0] == 'u')); - ticket.running = RT_TRUE; - - for (i = 0; i < TICKET_WINDOW_COUNT; i++) - { - ticket.threads[i] = rt_thread_create(i == 0 ? "window_a" : "window_b", - ticket_worker, - (void *)(rt_ubase_t)('A' + i), - 768, 16, 10); - if (ticket.threads[i] == RT_NULL) - { - rt_uint32_t j; - ticket.running = RT_FALSE; - for (j = 0; j < i; j++) - rt_thread_delete(ticket.threads[j]); - rt_mutex_delete(ticket.lock); - rt_kprintf("ticket_demo: window %u create failed\n", i); - return -1; - } - } - - for (i = 0; i < TICKET_WINDOW_COUNT; i++) - rt_thread_startup(ticket.threads[i]); - rt_kprintf("ticket_demo started in %s mode with %d tickets\n", - ticket.safe_mode ? "safe" : "unsafe", TICKET_TOTAL); - return 0; -} -MSH_CMD_EXPORT(ticket_demo, ticket demo safe or unsafe); diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index ae6baa3fff0f7f207398f90a3797bc59b801ed62..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,206 +0,0 @@ -# DAY2 RT-Thread 启动流程、内核基础与多线程编程 - -- 姓名:王童乐 -- 组别:第2组 -- 目标平台:STM32F407 星火一号(STM32F407ZG) -- RT-Thread:4.1.1 -- 开发工具:RT-Thread Studio 2.3.1、GNU Arm Embedded Toolchain、ST-Link - -## Part 1:理论知识梳理 - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -STM32F407 上电或复位后,处理器先从向量表首项装载主栈指针 MSP,再从第二项取得 `Reset_Handler` 地址。`Reset_Handler` 调用 `SystemInit()` 完成芯片早期初始化,随后由 C 运行库复制 `.data`、清零 `.bss` 并执行运行库初始化。 - -本工程使用 GCC,RT-Thread 入口和用户程序的主要调用链为: - -```text -上电/复位 - -> 装载 MSP 和 Reset_Handler - -> Reset_Handler - -> SystemInit + C 运行时初始化 - -> entry - -> rtthread_startup - -> rt_hw_board_init - -> rt_system_timer_init - -> rt_system_scheduler_init - -> rt_application_init(创建 main 线程) - -> rt_system_timer_thread_init - -> rt_thread_idle_init - -> rt_system_scheduler_start - -> main_thread_entry - -> rt_components_init - -> thread_study_init(INIT_APP_EXPORT) - -> 用户 main() -``` - -`rtthread_startup()` 先关闭中断,完成板级初始化、内核对象系统、定时器系统和调度器初始化,然后创建主线程、定时器线程和空闲线程,最后启动调度器。调度器运行主线程后,`main_thread_entry()` 先执行组件自动初始化,再进入用户 `main()`。 - -#### 自动初始化原理 - -`INIT_BOARD_EXPORT(fn)` 和 `INIT_APP_EXPORT(fn)` 最终展开为 `INIT_EXPORT(fn, level)`。该宏通过 `RT_SECTION` 和 `RT_USED` 将初始化函数指针放入 `.rti_fn.` 链接段,链接器保留并按等级排列这些入口,运行时再遍历相应区间并依次调用。 - -- `INIT_BOARD_EXPORT(fn)` 使用 level 1,在板级初始化期间执行。 -- `INIT_APP_EXPORT(fn)` 使用 level 6,在主线程的应用初始化阶段执行。 - -本实验使用: - -```c -INIT_APP_EXPORT(thread_study_init); -``` - -因此测试模块由 RT-Thread 自动加载,不需要在 `main.c` 中硬编码调用,降低了模块与主程序之间的耦合。 - -### 2. 内核基础与对象模型 - -RT-Thread 将线程、信号量、互斥量、邮箱等统一抽象成内核对象。公共对象部分保存名称、类型、标志和对象链表节点;对象容器按类型管理已经创建的对象。线程控制块 `struct rt_thread` 在公共对象的基础上保存线程调度和上下文信息。 - -| 关键成员 | 作用 | -| --- | --- | -| `stat` | 线程当前状态及相关标志 | -| `current_priority` | 当前优先级,数值越小优先级越高 | -| `init_tick` | 创建线程时设置的初始时间片 | -| `remaining_tick` | 当前剩余时间片 | -| `sp` | 上下文切换时保存的线程栈指针 | -| `stack_addr` | 线程栈起始地址 | -| `stack_size` | 线程栈总大小 | -| `tlist` | 将线程挂入就绪队列或等待队列的节点 | -| `thread_timer` | 线程延时和超时等待使用的定时器 | - -#### 静态线程与动态线程 - -| 对比项 | 静态线程 | 动态线程 | -| --- | --- | --- | -| 创建 API | `rt_thread_init()` | `rt_thread_create()` | -| TCB 和栈 | 应用在编译期定义 | 运行时从 Heap 分配 | -| 生命周期 | 由静态存储期和应用控制 | 可删除并回收内存 | -| 创建风险 | 内存需求在链接时确定 | 可能因堆不足或碎片化失败 | -| 适用场景 | 关键任务、固定资源、高可靠系统 | 数量或生命周期需要动态变化的任务 | - -本实验中 `th_led1` 和 `th_monitor` 动态创建;`th_led2` 的 TCB 和 1024 字节栈使用全局静态对象。高可靠场景常使用静态线程,因为它不依赖运行时堆,内存占用可在链接阶段确定,也避免了堆碎片和不确定的分配时间。 - -### 3. 线程状态机与调度算法 - -RT-Thread 线程五态及典型转换如下: - -```text -初始态 --rt_thread_startup()--> 就绪态 -就绪态 --被调度器选中----------> 运行态 -运行态 --时间片耗尽/被抢占-----> 就绪态 -运行态 --delay/等待IPC/suspend--> 挂起态 -挂起态 --超时/IPC到达/resume----> 就绪态 -运行态 --入口返回/退出---------> 关闭态 -``` - -- 初始态:线程已经创建,但尚未启动。 -- 就绪态:具备运行条件,正在就绪队列等待 CPU。 -- 运行态:当前正在占用 CPU。 -- 挂起态:线程正在延时、等待 IPC 或被显式挂起。 -- 关闭态:线程已经退出或被删除,等待清理或已脱离调度。 - -RT-Thread 使用基于优先级的抢占式调度,优先级数值越小越高。优先级 10 的 `th_monitor` 唤醒后可抢占优先级 16 的工作线程,先处理按键并采集状态。 - -多个同优先级线程同时就绪时使用时间片轮转。`th_led1` 与 `th_led2` 均为优先级 16、时间片 5 ticks。一个线程用完时间片后,同优先级的另一个就绪线程可以获得 CPU;本实验线程还会主动延时,因此实际运行中也会因延时阻塞而切换。 - -## Part 2:多线程实验 - -### 1. 实验设计 - -| 线程 | 创建方式 | 优先级 | 时间片 | 栈 | 功能 | -| --- | --- | ---: | ---: | ---: | --- | -| `th_led1` | 动态 | 16 | 5 ticks | 1024 B | 翻转 PF12 红灯,打印计数和 tick | -| `th_led2` | 静态 | 16 | 5 ticks | 1024 B | 翻转 PF11 绿灯,打印计数 | -| `th_monitor` | 动态 | 10 | 5 ticks | 1024 B | 每 2 秒打印状态与剩余栈,处理按键事件 | - -星火一号官方 BSP 将 PF11 命名为 `LED_B`,但板载资源说明标注该灯实际显示为绿色,因此源码中使用语义更直观的 `LED_G_PIN`。板载 `KEY_DOWN` 使用 PC1,上拉输入和下降沿中断,并增加 200 ms 软件消抖。中断回调只记录事件,实际线程控制在监控线程中完成。 - -### 2. 挂起与恢复的兼容处理 - -RT-Thread 4.1.1 要求 `rt_thread_suspend()` 的目标是当前线程。如果监控线程直接挂起一个正在延时的 `th_led1`,可能触发内核断言。因此本实验使用安全的请求机制: - -```text -第一次按键 - -> 中断置位 key_event - -> th_monitor 设置 led1_pause_request - -> th_led1 醒来后调用 rt_thread_suspend(rt_thread_self()) - -> th_led1 计数和红灯停止 - -第二次按键 - -> 中断置位 key_event - -> th_monitor 调用 rt_thread_resume(th_led1_handle) - -> th_led1 从原位置继续运行 -``` - -该实现实际调用了 `rt_thread_suspend()` 和 `rt_thread_resume()`,同时遵守 4.1.1 的内核约束。 - -### 3. 状态与剩余栈统计 - -RT-Thread 在线程栈初始化时用字符 `'#'` 填充栈空间。Cortex-M 栈向低地址增长,从 `stack_addr` 开始统计连续的 `'#'` 字节数,可估算尚未使用的栈空间。监控线程读取线程的 `stat`、`stack_addr` 和 `stack_size` 并周期输出。 - -源码还导出了自定义 FinSH 命令: - -```c -MSH_CMD_EXPORT(thread_dump, show study thread information); -``` - -在 `msh >` 中输入 `thread_dump`,可查看三个实验线程的状态、剩余栈以及 `th_led1` 的控制状态。 - -### 4. 编译与下载结果 - -RT-Thread Studio Debug 配置编译成功: - -```text -Build Finished. 0 errors, 0 warnings. -Flash: 62736 B (61.27 KB) -RAM: 6424 B (6.27 KB) -``` - -使用 ST-Link 和 SWD 接口下载 `rtthread.elf` 到 `0x08000000`,下载进度 100%,程序复位后正常运行。调试串口为 UART1(PA9/PA10)、COM9、115200-8-N-1。 - -### 5. 运行结果 - -监控线程正常读取两个工作线程状态和剩余栈: - -```text -[monitor] tick=8060, control=running -[monitor] th_led1: state=suspend, unused_stack=788/1024 bytes -[monitor] th_led2: state=suspend, unused_stack=788/1024 bytes -``` - -采样时两个线程正在 `rt_thread_mdelay()`,所以状态显示为 `suspend`。剩余栈约 788 字节,没有发现栈空间不足。 - -第一次按下 KEY_DOWN 后: - -```text -[th_led1] count=10, tick=9079 -[monitor] suspend th_led1 requested -[th_led1] suspended by key - -[monitor] tick=12095, control=paused -[monitor] th_led1: state=suspend, unused_stack=788/1024 bytes -[th_led2] count=9 -``` - -`th_led1` 计数停止,红灯停止翻转;`th_led2` 继续计数,绿灯继续运行。再次按下 KEY_DOWN: - -```text -[monitor] resume th_led1 requested -[th_led1] resumed by key -[th_led1] count=11, tick=28245 - -[monitor] tick=30242, control=running -``` - -恢复后 `th_led1` 从 11 继续计数,说明线程上下文被保留,恢复过程没有重建线程。 - -### 6. 工程与运行截图 - -![工程截图 1](figures/project-result-1.png) - -![工程截图 2](figures/project-result-2.png) - -## 实验总结 - -本实验完成了一个动态工作线程、一个静态工作线程和一个高优先级动态监控线程,验证了 RT-Thread 自动初始化、对象模型、优先级抢占、同优先级时间片参数、线程状态读取、栈余量估算、GPIO 中断和线程挂起/恢复。 - -最终结果为:工程编译无错误和警告;红绿 LED 独立翻转;监控线程周期输出正常;按键可稳定控制 `th_led1` 挂起与恢复;测试模块通过 `INIT_APP_EXPORT()` 自动加载,并通过 `MSH_CMD_EXPORT()` 导出 `thread_dump`。 diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/project-result-1.png" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/project-result-1.png" deleted file mode 100644 index 34996d29f70617423203f5599347836e0b25ec83..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/project-result-1.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/project-result-2.png" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/project-result-2.png" deleted file mode 100644 index 04a5aea5b3fc0b5933c4b79d65b6321d039711ed..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/project-result-2.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index f01fb559dde10d74f4aabc9f29bb084c76d1efa2..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\347\216\213\347\253\245\344\271\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,327 +0,0 @@ - #include - #include - #include - #include - #include - - /* 星火一号硬件引脚 */ - #define LED_R_PIN GET_PIN(F, 12) - #define LED_G_PIN GET_PIN(F, 11) - #define KEY_PIN GET_PIN(C, 1) - - #define WORKER_STACK_SIZE 1024 - #define WORKER_PRIORITY 16 - #define WORKER_TIMESLICE 5 - - #define MONITOR_STACK_SIZE 1024 - #define MONITOR_PRIORITY 10 - #define MONITOR_TIMESLICE 5 - - static rt_thread_t th_led1_handle = RT_NULL; - static rt_thread_t th_monitor_handle = RT_NULL; - - /* th_led2 使用静态 TCB 和静态栈 */ - static struct rt_thread th_led2_tcb; - - ALIGN(RT_ALIGN_SIZE) - static rt_uint8_t th_led2_stack[WORKER_STACK_SIZE]; - - /* 按键和 th_led1 控制标志 */ - static volatile rt_bool_t key_event = RT_FALSE; - static volatile rt_bool_t led1_pause_request = RT_FALSE; - static volatile rt_bool_t led1_is_paused = RT_FALSE; - static volatile rt_tick_t last_key_tick = 0; - - static const char *thread_state_name(rt_uint8_t stat) - { - switch (stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: - return "init"; - case RT_THREAD_READY: - return "ready"; - case RT_THREAD_SUSPEND: - return "suspend"; - case RT_THREAD_RUNNING: - return "running"; - case RT_THREAD_CLOSE: - return "close"; - default: - return "unknown"; - } - } - - /* Cortex-M 栈向低地址增长,'#' 表示尚未使用的栈空间 */ - static rt_size_t thread_unused_stack(rt_thread_t thread) - { - rt_uint8_t *ptr; - rt_uint8_t *end; - - if (thread == RT_NULL || thread->stack_addr == RT_NULL) - { - return 0; - } - - ptr = (rt_uint8_t *)thread->stack_addr; - end = ptr + thread->stack_size; - - while (ptr < end && *ptr == '#') - { - ptr++; - } - - return (rt_size_t)(ptr - - (rt_uint8_t *)thread->stack_addr); - } - - static void print_thread_info(const char *name, - rt_thread_t thread) - { - if (thread == RT_NULL) - { - rt_kprintf("[monitor] %s does not exist\n", name); - return; - } - - rt_kprintf( - "[monitor] %s: state=%s, unused_stack=%u/%u bytes\n", - name, - thread_state_name(thread->stat), - (unsigned int)thread_unused_stack(thread), - (unsigned int)thread->stack_size); - } - - /* PC1 按键下降沿中断,200 ms 软件消抖 */ - static void key_irq_callback(void *args) - { - rt_tick_t now = rt_tick_get(); - - if ((rt_tick_t)(now - last_key_tick) >= - RT_TICK_PER_SECOND / 5) - { - last_key_tick = now; - key_event = RT_TRUE; - } - } - - static void th_led1_entry(void *parameter) - { - rt_uint32_t count = 0; - rt_base_t level = PIN_HIGH; - - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - rt_pin_write(LED_R_PIN, level); - - while (1) - { - /* - * RT-Thread 4.1.1 要求线程只能挂起自身。 - * 监控线程提出请求,由 th_led1 自己调用 suspend。 - */ - if (led1_pause_request) - { - led1_pause_request = RT_FALSE; - led1_is_paused = RT_TRUE; - - rt_kprintf("[th_led1] suspended by key\n"); - - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - - led1_is_paused = RT_FALSE; - rt_kprintf("[th_led1] resumed by key\n"); - } - - level = (level == PIN_HIGH) ? - PIN_LOW : PIN_HIGH; - rt_pin_write(LED_R_PIN, level); - - count++; - rt_kprintf("[th_led1] count=%u, tick=%u\n", - (unsigned int)count, - (unsigned int)rt_tick_get()); - - rt_thread_mdelay(1000); - } - } - - static void th_led2_entry(void *parameter) - { - rt_uint32_t count = 0; - rt_base_t level = PIN_HIGH; - - rt_pin_mode(LED_G_PIN, PIN_MODE_OUTPUT); - rt_pin_write(LED_G_PIN, level); - - while (1) - { - level = (level == PIN_HIGH) ? - PIN_LOW : PIN_HIGH; - rt_pin_write(LED_G_PIN, level); - - count++; - rt_kprintf("[th_led2] count=%u\n", - (unsigned int)count); - - rt_thread_mdelay(1500); - } - } - - static void process_key_event(void) - { - rt_base_t level; - rt_bool_t event; - - /* 防止读取和清除标志时被按键中断打断 */ - level = rt_hw_interrupt_disable(); - event = key_event; - key_event = RT_FALSE; - rt_hw_interrupt_enable(level); - - if (!event) - { - return; - } - - if (led1_is_paused) - { - if (rt_thread_resume(th_led1_handle) == RT_EOK) - { - rt_kprintf( - "[monitor] resume th_led1 requested\n"); - } - } - else if (!led1_pause_request) - { - led1_pause_request = RT_TRUE; - rt_kprintf( - "[monitor] suspend th_led1 requested\n"); - } - } - - static void th_monitor_entry(void *parameter) - { - while (1) - { - rt_thread_mdelay(2000); - - process_key_event(); - - rt_kprintf("\n[monitor] tick=%u, control=%s\n", - (unsigned int)rt_tick_get(), - led1_is_paused ? - "paused" : "running"); - - print_thread_info("th_led1", th_led1_handle); - print_thread_info("th_led2", &th_led2_tcb); - rt_kprintf("\n"); - } - } - - /* msh 中输入 thread_dump 可随时查看线程 */ - static void thread_dump(void) - { - rt_kprintf("\n[thread_dump]\n"); - - print_thread_info("th_led1", th_led1_handle); - print_thread_info("th_led2", &th_led2_tcb); - print_thread_info("th_monitor", th_monitor_handle); - - rt_kprintf("[control] th_led1=%s\n", - led1_is_paused ? "paused" : "running"); - } - - MSH_CMD_EXPORT(thread_dump, - show study thread information); - - static int thread_study_init(void) - { - rt_err_t result; - - th_led1_handle = rt_thread_create( - "th_led1", - th_led1_entry, - RT_NULL, - WORKER_STACK_SIZE, - WORKER_PRIORITY, - WORKER_TIMESLICE); - - if (th_led1_handle == RT_NULL) - { - rt_kprintf( - "[thread_study] create th_led1 failed\n"); - return -RT_ENOMEM; - } - - result = rt_thread_init( - &th_led2_tcb, - "th_led2", - th_led2_entry, - RT_NULL, - th_led2_stack, - sizeof(th_led2_stack), - WORKER_PRIORITY, - WORKER_TIMESLICE); - - if (result != RT_EOK) - { - rt_kprintf( - "[thread_study] init th_led2 failed\n"); - return result; - } - - th_monitor_handle = rt_thread_create( - "th_monitor", - th_monitor_entry, - RT_NULL, - MONITOR_STACK_SIZE, - MONITOR_PRIORITY, - MONITOR_TIMESLICE); - - if (th_monitor_handle == RT_NULL) - { - rt_kprintf( - "[thread_study] create th_monitor failed\n"); - return -RT_ENOMEM; - } - - /* KEY_DOWN:PC1,上拉输入、下降沿中断 */ - rt_pin_mode(KEY_PIN, PIN_MODE_INPUT_PULLUP); - - result = rt_pin_attach_irq( - KEY_PIN, - PIN_IRQ_MODE_FALLING, - key_irq_callback, - RT_NULL); - - if (result != RT_EOK) - { - rt_kprintf( - "[thread_study] attach key IRQ failed: %d\n", - result); - return result; - } - - result = rt_pin_irq_enable( - KEY_PIN, - PIN_IRQ_ENABLE); - - if (result != RT_EOK) - { - rt_kprintf( - "[thread_study] enable key IRQ failed: %d\n", - result); - return result; - } - - rt_thread_startup(th_led1_handle); - rt_thread_startup(&th_led2_tcb); - rt_thread_startup(th_monitor_handle); - - rt_kprintf( - "[thread_study] all threads started\n"); - - return RT_EOK; - } - - INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 8fad76d26e4dcaa1e1614a752a5b44ed4177c310..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,309 +0,0 @@ -# RT-Thread 线程间同步与通信实验报 - -| MSH 命令 | 练习 | IPC 机制 | -|---|---|---| -| `parking_demo` | 停车场车位管理 | 计数信号量 | -| `ticket_demo` | 多窗口售票系统 | 互斥量(含无锁对比实验) | -| `startup_demo` | 系统启动条件检查 | 事件集 | -| `package_demo` | 快递分拣中心 | 消息队列 | - ---- - -## 一、理论总结 - -### 1. 基本概念 - -- **临界区**:每个线程中访问(读/写)共享资源的那段代码。多个线程同时执行各自的临界区会导致竞态(数据不一致),因此需要用互斥量等机制保证同一时刻只有一个线程进入临界区。 -- **阻塞与非阻塞**:线程请求某个资源而资源暂不可用时,阻塞方式会让线程主动让出 CPU 并挂起等待,直到资源可用或超时才被唤醒(如 `rt_sem_take(sem, timeout)`);非阻塞方式则不让线程等待,立即返回错误码,由调用者决定后续处理(如 `rt_sem_trytake()`,或消息队列满时 `rt_mq_send()` 立即返回 `-RT_EFULL`)。 -- **挂起**:线程因等待信号量、互斥量、事件、消息等资源而暂停运行,从就绪队列移入对应 IPC 对象的挂起等待队列;当条件满足或等待超时后被唤醒并重新进入就绪状态。 - -### 2. 线程间同步 - -#### 2.1 信号量 - -- **概念**:信号量是一个内核计数器,用于协调多个线程对有限共享资源的访问。创建时指定初值,`take` 使值减 1(值已为 0 时线程挂起),`release` 使值加 1(并唤醒一个等待线程)。 -- **二值信号量**:初值为 1,取值只有 0/1,功能类似一把锁,常用于线程互斥。 -- **互斥信号量**:即用作互斥的二值信号量。与专门的互斥量相比,信号量没有所有权概念(释放者不要求是获取者),也不具备优先级继承与递归加锁能力,因此用于互斥时不如互斥量严谨。 -- **控制块**: - -```c -struct rt_semaphore -{ - struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ - rt_uint16_t value; /* 信号量的值 */ - rt_uint16_t reserved; /* 保留字段 */ -}; -typedef struct rt_semaphore *rt_sem_t; -``` - -- **常用 API**: - -```c -/* 创建与删除(动态) */ -rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag); -rt_err_t rt_sem_delete(rt_sem_t sem); - -/* 初始化与脱离(静态) */ -rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag); -rt_err_t rt_sem_detach(rt_sem_t sem); - -/* 获取:普通获取(带超时) 与 无等待获取 */ -rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout); -rt_err_t rt_sem_trytake(rt_sem_t sem); - -/* 释放 */ -rt_err_t rt_sem_release(rt_sem_t sem); -``` - -#### 2.2 互斥量 - -- **概念**:互斥量只有"锁定/解锁"两种状态,用于保护临界区,保证同一时刻只有一个线程访问共享数据。 -- **特性**: - - **所有权**:互斥量只能由加锁的线程解锁(内核会校验),信号量可由任意线程释放; - - **优先级继承**:互斥量支持优先级继承协议,可缓解优先级翻转问题; - - **递归加锁**:同一线程可重复加锁(`hold` 计数),信号量不支持。 -- **控制块**: - -```c -struct rt_mutex -{ - struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ - rt_uint16_t value; /* 互斥量的值 */ - rt_uint8_t original_priority; /* 持有线程的原始优先级 */ - rt_uint8_t hold; /* 持有线程的持有次数 */ - struct rt_thread *owner; /* 当前拥有互斥量的线程 */ -}; -typedef struct rt_mutex *rt_mutex_t; -``` - -- **常用 API**: - -```c -/* 创建与删除(动态) */ -rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag); -rt_err_t rt_mutex_delete(rt_mutex_t mutex); - -/* 初始化与脱离(静态) */ -rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag); -rt_err_t rt_mutex_detach(rt_mutex_t mutex); - -/* 获取:普通获取(带超时) 与 无等待获取 */ -rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout); -rt_err_t rt_mutex_trytake(rt_mutex_t mutex); - -/* 释放 */ -rt_err_t rt_mutex_release(rt_mutex_t mutex); -``` - -#### 2.3 事件集 - -- **概念**:事件集是一个 32 位的位图,每一位表示一个事件,用于一个线程等待多个事件的发生。支持 `AND`(全部满足)、`OR`(任一满足)、`CLEAR`(满足后自动清除)组合,且一次发送可唤醒多个等待线程(一对多)。 -- **实现方式**:内核用 32 位无符号整数 `set` 保存事件集合;`rt_event_send()` 置位并唤醒满足条件的等待线程,`rt_event_recv()` 按 AND/OR/CLEAR 选项判断条件是否满足。 -- **控制块**: - -```c -struct rt_event -{ - struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ - rt_uint32_t set; /* 事件集合,每一 bit 表示一个事件 */ -}; -typedef struct rt_event *rt_event_t; -``` - -- **常用 API**: - -```c -/* 创建与删除(动态) */ -rt_event_t rt_event_create(const char *name, rt_uint8_t flag); -rt_err_t rt_event_delete(rt_event_t event); - -/* 初始化与脱离(静态) */ -rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag); -rt_err_t rt_event_detach(rt_event_t event); - -/* 获取(接收事件;timeout = 0 即为无等待获取) - opt: RT_EVENT_FLAG_AND / RT_EVENT_FLAG_OR / RT_EVENT_FLAG_CLEAR */ -rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, rt_uint8_t opt, - rt_int32_t timeout, rt_uint32_t *recved); - -/* 释放(发送事件) */ -rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set); -``` - -### 3. 线程间通信 - -#### 3.1 消息邮箱 - -- **特点**:每条消息固定为 4 字节(`rt_ubase_t`),只能传递一个值(通常是指针或 32 位整数),按 FIFO 收发;邮箱满时发送线程可挂起等待或立即返回。 -- **应用**:轻量级的线程间"传值/传地址"通知,例如把一块缓冲区的指针通过邮箱传给另一个线程。 -- **控制块**: - -```c -struct rt_mailbox -{ - struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ - rt_ubase_t *msg_pool; /* 消息缓冲区首地址 */ - rt_uint16_t size; /* 消息缓冲区大小 */ - rt_uint16_t entry; /* 消息缓冲区中的消息数目 */ - rt_uint16_t in_offset; /* 进入消息缓冲区的偏移 */ - rt_uint16_t out_offset; /* 离开消息缓冲区的偏移 */ - rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */ -}; -typedef struct rt_mailbox *rt_mailbox_t; -``` - -- **常用 API**: - -```c -/* 创建与删除(动态) */ -rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag); -rt_err_t rt_mb_delete(rt_mailbox_t mb); - -/* 初始化与脱离(静态) */ -rt_err_t rt_mb_init(rt_mailbox_t mb, const char *name, void *msgpool, - rt_size_t size, rt_uint8_t flag); -rt_err_t rt_mb_detach(rt_mailbox_t mb); - -/* 获取(接收;timeout = 0 即为无等待获取) */ -rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout); - -/* 释放(发送) */ -rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value); -``` - -#### 3.2 消息队列 - -- **特点**:可存放多条消息,每条消息长度可变(不超过 `msg_size`),按 FIFO 接收;发送时内核拷贝消息内容(值传递),发送方与接收方不共享内存。 -- **应用**:线程间传递数据块(结构体等),例如本实验中的快递消息 `struct package_msg`。 -- **控制块**: - -```c -struct rt_messagequeue -{ - struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ - void *msg_pool; /* 指向存放消息的缓冲区地址 */ - rt_uint16_t msg_size; /* 每个消息的长度 */ - rt_uint16_t max_msgs; /* 最大能够容纳的消息数 */ - rt_uint16_t entry; /* 队列中已有的消息数 */ - void *msg_queue_head; /* 消息队列链表头 */ - void *msg_queue_tail; /* 消息队列链表尾 */ - void *msg_queue_free; /* 指向队列中的空闲节点 */ - rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */ -}; -typedef struct rt_messagequeue *rt_mq_t; -``` - -- **常用 API**: - -```c -/* 创建与删除(动态) */ -rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, - rt_size_t max_msgs, rt_uint8_t flag); -rt_err_t rt_mq_delete(rt_mq_t mq); - -/* 初始化与脱离(静态) */ -rt_err_t rt_mq_init(rt_mq_t mq, const char *name, void *msgpool, - rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag); -rt_err_t rt_mq_detach(rt_mq_t mq); - -/* 获取(接收;timeout = 0 即为无等待获取) */ -rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout); - -/* 释放(发送) */ -rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size); -``` - -> 以上控制块与常用 API 参见 RT-Thread 文档: -> - 线程间同步:https://www.rt-thread.org/document/site/master/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1 -> - 线程间通信:https://www.rt-thread.org/document/site/master/#/rt-thread-version/rt-thread-standard/programming-manual/ipc2/ipc2 - ---- - -## 二、总体设计说明(公共机制) - -1. **MSH 命令启动**:四个练习各自 `MSH_CMD_EXPORT` 导出命令,烧录后复位即可在终端输入命令运行;`main.c` 仅保留最小占位,不包含任何实验代码。 -2. **创建结果检查**:所有线程(`rt_thread_create`)与 IPC 对象(`rt_sem_create` 等)均检查返回值,失败时打印错误信息并回滚(删除已创建线程、复位标志),不留半初始化状态。 -3. **重复执行安全**: - - 每个练习维护一个运行标志(`g_xxx_running`),命令执行时若上一轮任务尚未结束则**拒绝重复启动**; - - 任务结束后线程入口函数直接 `return`,由内核 `_thread_exit()` 自动回收线程资源(栈与线程控制块); - - IPC 对象采用"惰性创建、跨轮复用",每轮开始前复位到初始状态(如事件集 `RT_IPC_CMD_RESET`、信号量排空后重新补满)。 -4. 演示线程统一栈大小 1024 B、优先级 16(数值上低于 main 线程的 10、高于 tshell 的 20);演示输出统一走 `demo_printf()`(内部加打印互斥量),避免多线程 `rt_kprintf` 按字符交错。 - ---- - -## 三、练习一:停车场车位管理(计数信号量) - -### 设计思路 - -- 创建计数信号量 `park`,**初值 3**,表示剩余车位。 -- 创建 5 个车辆线程(`car1`~`car5`),每辆车错峰 300 ms 到达。 -- 车辆用 `rt_sem_take(sem, 2000ms)` 申请车位: - - **成功**:打印 `carX entered, used spaces: N`(N = 3 − 信号量当前值),停 3 秒后 `rt_sem_release()` 释放车位并打印 `carX left`; - - **超时**:打印 `carX waiting for a space`,延时 100 ms 后重新申请。 -- 最后一辆车离场后打印 `parking demo finished` 并复位运行标志。 - -### 结果分析 - -车位只有 3 个,car1~car3 错峰到达后立即获得车位,信号量值从 3 递减到 0,`used spaces` 依次为 1、2、3。car4 在 0.9 s 到达时车位已满,等满 2 s 仍未获得,打印 `car4 waiting for a space` 后延时 100 ms 重新申请。需要说明的是:car4 重新申请期间处于 100 ms 延时中(并未挂起在信号量上),而 car5 仍在第一次 2 s 等待中,因此 car1 在 3.0 s 离场释放车位时,先被唤醒的是挂起在信号量上的 car5(`car5 entered`);随后 car2 离场,car4 才获得车位。这一顺序符合信号量"按挂起队列 FIFO 顺序唤醒"的规则,且整个过程任意时刻在场车辆数始终 ≤ 3,"等待超时 → 稍后重试 → 最终进入"的容错逻辑也得到体现。 - -### 思考题:为什么这里使用计数信号量,而不是互斥量? - -车位是**可以同时被多辆车占用的同类资源**,数量为 3,需要用"剩余数量"来描述。计数信号量的初值正好表达资源数量,`take/release` 分别对应"占用/归还一份资源",3 辆车可同时拿到。而互斥量只有 0/1 两种状态,只能表达"整个停车场同时只允许一辆车进入",无法表达"3 个车位",因此本场景必须使用计数信号量。 - ---- - -## 四、练习二:多窗口售票系统(互斥量) - -### 设计思路 - -- 全局变量 `g_ticket_left` 保存剩余票数,初值 20;`g_ticket_sold[20]` 记录已售票号用于检测重复。 -- 窗口 A/B 两个线程循环执行:检查余票 → 记录票号 → 余票减 1 → 打印结果;两次售票之间延时 1 ms 让两个窗口交替执行。 -- **加锁版本**:整个"检查-减票-记录"过程放在 `rt_mutex_take/release` 之间,成为原子操作。 -- 两个窗口都退出后,由最后退出的线程打印售出总数、重复票号数、剩余票数。 - -### 结果分析 - -加锁后,20 张票恰好售完、票号从 20 到 1 不重复、余票为 0,满足全部硬性指标。无锁时,A、B 都通过 `g_ticket_left > 0` 检查后,在 10 ms 延时里同时持有同一个旧值,从而卖出相同票号;当只剩最后 1 张票时两个窗口都能通过检查,连减两次后余票可能变为 −1。无锁版本的竞态暴露形式每次运行可能不同(重复票号、负余票、售出总数 ≠ 20 等),可反复执行多次观察对比。 - -### 思考题:为什么只给 `ticket_count--` 加锁仍可能不安全? - -售票是"检查—修改"两步操作,只有把两步整体变成原子操作才安全。若只锁住 `ticket_count--`,线程 A、B 仍可以在锁外同时通过 `if (ticket_count > 0)` 的检查(都认为还有余票),随后依次进入锁内各自执行一次减一;当只剩最后 1 张票时,A、B 都能通过检查,连减两次后余票变成 −1,并卖出两个相同票号。因此必须把"检查余票 + 减票 + 记录票号"作为一个完整临界区,用互斥量整体保护,这正是"保护完整的售票过程"的原因。 - ---- - -## 五、练习三:系统启动条件检查(事件集) - -### 设计思路 - -- 定义三个事件位 `EVENT_NET_READY / EVENT_SENSOR_READY / EVENT_STORAGE_READY`(`1<<0/1/2`)。 -- 三个初始化线程(`netinit` 1 s、`snrinit` 2 s、`stoinit` 5 s)各自延时后 `rt_event_send()` 发送对应就绪事件并打印 `xxx ready`。 -- 主业务线程(`biz`)用 **AND 模式 + 3 s 超时**循环等待: - - `rt_event_recv(EVENT_ALL_READY, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, 3000ms, &recved)`; - - 全部到达 → 打印 `all modules are ready` / `business task started`,退出; - - 超时 → 读取事件集当前值,打印尚未就绪的模块(`wait modules timeout, storage not ready`),再次等待。 -- 事件位采用 **AND+CLEAR**:成功时自动清零;超时时已到达的事件位保留,供下一次等待继续使用,不会丢失。 - -### 结果分析 - -主业务线程第一次等待 3 s 超时:此时网络(1 s)、传感器(2 s)已就绪,存储模块(5 s)未就绪,正确打印缺失模块名。超时返回时已就绪的事件位不清除,第二次等待在存储就绪后立即满足 AND 条件返回,打印启动信息——超时测试与再次等待按要求完成。 - -### 思考题:如果用三个独立信号量实现,与使用事件集有什么区别? - -三个独立信号量无法直接表达"全部到达"这一组合条件:主线程只能依次等待三个信号量,或再引入额外的计数/状态变量,逻辑更复杂,而事件集一次 AND 等待即可原子地判断多个条件。在超时与状态查询上,事件集超时后可以直接读出位图,判断缺哪个模块,信号量则只能知道取到了几个,还需自己维护对应关系。在资源占用上,事件集只是一个对象加 32 位位图,位本身几乎不占资源,三个信号量则是三个独立内核对象,占用更多内存与对象槽位。此外事件集一次发送可同时唤醒多个等待同一条件的线程(一对多),而信号量释放一次只能唤醒一个等待线程。 - ---- - -## 六、练习四:快递分拣中心(消息队列) - -### 设计思路 - -- 消息结构 `struct package_msg { rt_uint32_t id; rt_uint8_t region; rt_uint16_t weight; }`,region 1/2/3 对应华东/华南/华北,region = 0 表示**结束消息**。 -- 创建消息队列 `pkgmq`:消息大小 `sizeof(struct package_msg)`,容量 **5**。 -- 两个发件线程(`sndr1`、`sndr2`)各发 5 条快递:编号分别为 1001~1005、2001~2005(**全局唯一**),区域轮转(1→3),重量按公式生成。 -- 发送使用**非阻塞** `rt_mq_send()`:队列满返回 `-RT_EFULL` 时**计数发送失败次数**并延时 50 ms 重试,发送线程不会崩溃。 -- 每个发件线程发完 5 条后发送一条结束消息(region = 0)。 -- 分拣线程(`sorter`)`rt_mq_recv()` 阻塞接收:普通消息打印编号/区域/重量并统计(总数、各区域数、总重量),每分拣一条延时 80 ms(让队列满的情况真实出现);收到 **2 条结束消息**后打印统计结果并退出。 - -### 结果分析 - -两个发件线程并发发送,分拣线程按 FIFO 顺序取出 10 条快递,编号无重复(1001~1005、2001~2005)。区域统计 east:4、south:3、north:3 与发送公式 `((sender_no + i) % 3) + 1` 的分布一致,总重量 11750 g 为两条发送序列之和。队列容量只有 5,发件线程发送速度快于分拣速度时,`rt_mq_send()` 返回 `-RT_EFULL`,此时线程计数失败并延时重试,没有崩溃、没有丢件;`send failed` 的具体次数随运行时序略有浮动,结束消息(region=0)到达后分拣线程输出统计并退出。 diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ipc_study.c" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ipc_study.c" deleted file mode 100644 index fdea77732990d6a4666da51619e9a6ba559bfb63..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ipc_study.c" +++ /dev/null @@ -1,835 +0,0 @@ -/* - * 文件 : ipc_study.c - * 项目 : 03_IPC_sample (RT-Thread 4.1.1, STM32F407 RT-SPARK) - * 内容 : RT-Thread 线程间同步与通信课后作业 - * - * 练习一 parking_demo 停车场车位管理 —— 计数信号量 - * 练习二 ticket_demo 多窗口售票系统 —— 互斥量(含无锁对比实验 ticket_demo unsafe) - * 练习三 startup_demo 系统启动条件检查 —— 事件集(AND 等待 + 超时重试) - * 练习四 package_demo 快递分拣中心 —— 消息队列 - * - * 说明: - * 1. 本文件通过 MSH_CMD_EXPORT 导出 4 个 MSH 命令, 全部由命令手动启动; - * main.c 只保留最小占位, 不含任何与本作业相关的逻辑. - * 2. 每个演示中所有线程与 IPC 对象均检查创建结果, 失败时打印错误并回滚. - * 3. 命令重复执行时, 若上一轮任务仍在运行则拒绝再次启动(运行标志检测); - * 任务结束后线程入口函数直接 return, 由内核 _thread_exit() 自动回收资源; - * IPC 对象采用"惰性创建 + 跨轮复用", 每轮开始时复位到初始状态. - * 4. 演示输出统一走 demo_printf()(内部加打印互斥量), 避免多线程 rt_kprintf - * 按字符交错导致乱码. - */ - -#include -#include -#include - -/* 演示线程统一栈大小与优先级(高于 tshell 线程, 保证命令一启动演示线程就能运行) */ -#define DEMO_THREAD_STACK_SIZE 1024 -#define DEMO_THREAD_PRIORITY 16 - -/*************************************************************************** - * 公共设施 - **************************************************************************/ - -/* 打印互斥量: 串行化演示输出, 防止多个线程的 rt_kprintf 相互交错 */ -static rt_mutex_t g_print_mutex = RT_NULL; - -/* 命令入口处调用一次, 创建打印互斥量(命令由 tshell 单线程执行, 无并发创建竞态) */ -static void demo_print_init(void) -{ - if (g_print_mutex == RT_NULL) - { - g_print_mutex = rt_mutex_create("prnt", RT_IPC_FLAG_PRIO); - if (g_print_mutex == RT_NULL) - { - rt_kprintf("create print mutex failed\n"); - } - } -} - -/* 原子打印: 先格式化到局部缓冲, 再整段一次性输出 */ -static void demo_printf(const char *fmt, ...) -{ - va_list args; - char buf[160]; - - va_start(args, fmt); - rt_vsnprintf(buf, sizeof(buf), fmt, args); - va_end(args); - - if (g_print_mutex != RT_NULL) - { - rt_mutex_take(g_print_mutex, RT_WAITING_FOREVER); - } - rt_kprintf("%s", buf); - if (g_print_mutex != RT_NULL) - { - rt_mutex_release(g_print_mutex); - } -} - -/* - * 计数"已结束"的演示线程数, 由最后一个结束的线程负责打印总结并复位运行标志 - * (用临界区保护计数, 避免并发自增丢失). - */ -static rt_uint8_t demo_finish_one(volatile rt_uint8_t *done_count, rt_uint8_t total) -{ - rt_base_t level; - rt_uint8_t is_last; - - level = rt_hw_interrupt_disable(); - (*done_count)++; - is_last = (*done_count >= total); - rt_hw_interrupt_enable(level); - - return is_last; -} - -/* 按名字删除可能残留的线程(仅用于启动失败时回滚"其它"线程) */ -static void demo_thread_delete_by_name(const char *name) -{ - rt_thread_t tid = rt_thread_find((char *)name); - - if (tid != RT_NULL) - { - rt_thread_delete(tid); - } -} - -/*************************************************************************** - * 练习一: 停车场车位管理(计数信号量) - * - * 3 个车位, 5 辆车. 计数信号量初值为 3, 表示剩余车位. - * 每辆车拿不到车位最多等 2 秒, 超时打印等待信息后稍后再试; - * 拿到车位打印进入信息, 停 3 秒后离开并释放车位. - **************************************************************************/ -#define PARK_TOTAL_SPACES 3 /* 车位总数 */ -#define PARK_TOTAL_CARS 5 /* 车辆总数 */ -#define PARK_TAKE_TIMEOUT_MS 2000 /* 申请车位的等待上限 */ -#define PARK_RETRY_MS 100 /* 超时后重新申请的间隔 */ -#define PARK_STAY_MS 3000 /* 车辆停留时间 */ -#define PARK_ARRIVAL_GAP_MS 300 /* 车辆到达的错峰间隔 */ - -static rt_sem_t g_park_sem = RT_NULL; /* 计数信号量: 剩余车位 */ -static rt_uint8_t g_park_running = 0; /* 演示运行标志 */ -static rt_uint8_t g_car_done = 0; /* 已离场的车辆数 */ - -/* 车辆线程: 参数为车辆编号(1~5) */ -static void car_thread_entry(void *parameter) -{ - rt_uint8_t car_no = (rt_uint8_t)(rt_base_t)parameter; - - /* 错峰到达, 模拟 5 辆车先后开到停车场 */ - rt_thread_mdelay((car_no - 1) * PARK_ARRIVAL_GAP_MS); - - while (1) - { - /* 申请一个车位, 最多等 2 秒 */ - if (rt_sem_take(g_park_sem, rt_tick_from_millisecond(PARK_TAKE_TIMEOUT_MS)) != RT_EOK) - { - /* 2 秒内没有拿到车位: 打印等待信息, 稍后重新申请 */ - demo_printf("car%d waiting for a space\n", car_no); - rt_thread_mdelay(PARK_RETRY_MS); - continue; - } - - /* - * 拿到车位. 已用车位数 = 总车位 - 剩余车位. - * 信号量 value 字段为公共结构体字段, 表示当前剩余车位. - */ - demo_printf("car%d entered, used spaces: %d\n", - car_no, PARK_TOTAL_SPACES - (int)g_park_sem->value); - - /* 停留一段时间后离开, 释放车位 */ - rt_thread_mdelay(PARK_STAY_MS); - rt_sem_release(g_park_sem); - demo_printf("car%d left\n", car_no); - break; - } - - /* 全部车辆离场后允许再次执行命令 */ - if (demo_finish_one(&g_car_done, PARK_TOTAL_CARS)) - { - g_park_running = 0; - demo_printf("parking demo finished\n"); - } - - /* 直接返回, 内核 _thread_exit() 会回收本线程 */ -} - -/* MSH 命令: parking_demo */ -static void parking_demo(int argc, char **argv) -{ - rt_thread_t tid; - rt_uint8_t i; - char name[RT_NAME_MAX]; - - (void)argv; - (void)argc; - - if (g_park_running) - { - demo_printf("parking demo is already running, please wait for it to finish\n"); - return; - } - - demo_print_init(); - demo_printf("parking demo started: %d spaces, %d cars\n", - PARK_TOTAL_SPACES, PARK_TOTAL_CARS); - - /* 惰性创建计数信号量, 初始值 3 表示剩余车位 */ - if (g_park_sem == RT_NULL) - { - g_park_sem = rt_sem_create("park", PARK_TOTAL_SPACES, RT_IPC_FLAG_FIFO); - if (g_park_sem == RT_NULL) - { - demo_printf("create parking semaphore failed\n"); - return; - } - } - - /* 防御: 把信号量恢复到初始值(上一轮异常退出时可能残留未释放车位) */ - while (rt_sem_take(g_park_sem, 0) == RT_EOK) - { - } - for (i = 0; i < PARK_TOTAL_SPACES; i++) - { - rt_sem_release(g_park_sem); - } - - g_park_running = 1; - g_car_done = 0; - - /* 创建 5 个车辆线程, 逐一检查创建结果 */ - for (i = 1; i <= PARK_TOTAL_CARS; i++) - { - rt_snprintf(name, sizeof(name), "car%d", i); - tid = rt_thread_create(name, car_thread_entry, (void *)(rt_base_t)i, - DEMO_THREAD_STACK_SIZE, DEMO_THREAD_PRIORITY, 10); - if (tid == RT_NULL) - { - demo_printf("create thread %s failed\n", name); - /* 回滚: 删除已创建线程, 复位运行标志 */ - while (i > 1) - { - i--; - rt_snprintf(name, sizeof(name), "car%d", i); - demo_thread_delete_by_name(name); - } - g_park_running = 0; - return; - } - rt_thread_startup(tid); - } -} -MSH_CMD_EXPORT(parking_demo, parking demo: counting semaphore for 3-space parking lot); - -/*************************************************************************** - * 练习二: 多窗口售票系统(互斥量 + 无锁对比实验) - * - * 窗口 A / 窗口 B 同时出售 20 张票, 剩余票数保存在全局变量中. - * 加锁版本: 互斥量保护"检查 -> 减票 -> 记录票号"整个过程; - * 对比版本: 不加锁, 并在读取与修改之间插入延时放大竞态. - * 两个版本都统计已售、重复票号与剩余票数. - * - * 命令: ticket_demo -> 加锁版本(默认) - * ticket_demo unsafe -> 无锁对比实验 - **************************************************************************/ -#define TICKET_TOTAL 20 /* 总票数 */ -#define TICKET_RACE_DELAY_MS 10 /* 无锁实验: 读取与修改之间的延时 */ -#define TICKET_WINDOW_COUNT 2 /* 售票窗口数 */ -#define TICKET_YIELD_MS 1 /* 两次售票之间的让出时间, 使窗口交替 */ - -static rt_mutex_t g_ticket_mutex = RT_NULL; /* 售票互斥量 */ -static rt_int32_t g_ticket_left = 0; /* 全局变量: 剩余票数 */ -static rt_uint8_t g_ticket_sold[TICKET_TOTAL]; /* 票号标记: 检测重复售票 */ -static rt_uint32_t g_ticket_sold_cnt = 0; /* 已售票数 */ -static rt_uint32_t g_ticket_dup_cnt = 0; /* 重复票号数 */ -static rt_uint32_t g_ticket_invalid = 0; /* 越界票号数 */ -static rt_uint8_t g_ticket_use_mutex = 1; /* 1: 加锁; 0: 无锁对比 */ -static rt_uint8_t g_ticket_window_done = 0; /* 已退出窗口数 */ -static rt_uint8_t g_ticket_running = 0; /* 演示运行标志 */ - -/* 售票窗口线程: 参数为窗口名(静态字符串, "window A"/"window B") */ -static void window_thread_entry(void *parameter) -{ - const char *win = (const char *)parameter; - rt_int32_t no; - - while (1) - { - if (g_ticket_use_mutex) - { - /* 加锁: 保护完整的售票过程 */ - if (rt_mutex_take(g_ticket_mutex, RT_WAITING_FOREVER) != RT_EOK) - { - continue; - } - } - - /* ---------- 售票临界区开始 ---------- */ - if (g_ticket_left > 0) - { - if (!g_ticket_use_mutex) - { - /* 对比实验: 在"读取余票"与"修改余票"之间加入延时, 放大竞态 */ - rt_thread_mdelay(TICKET_RACE_DELAY_MS); - } - - no = g_ticket_left; /* 记录本窗口售出的票号 */ - g_ticket_left--; /* 修改剩余票数 */ - - /* 检错: 票号重复 / 越界(无锁版本会暴露问题) */ - if (no >= 1 && no <= TICKET_TOTAL) - { - if (g_ticket_sold[no - 1]) - { - g_ticket_dup_cnt++; - demo_printf("%s sold ticket %d (duplicate!)\n", win, no); - } - else - { - g_ticket_sold[no - 1] = 1; - demo_printf("%s sold ticket %d\n", win, no); - } - } - else - { - g_ticket_invalid++; - demo_printf("%s sold invalid ticket %d\n", win, no); - } - g_ticket_sold_cnt++; - } - /* ---------- 售票临界区结束 ---------- */ - - if (g_ticket_use_mutex) - { - rt_mutex_release(g_ticket_mutex); - } - - /* 无余票则本窗口退出 */ - if (g_ticket_left <= 0) - { - break; - } - - /* 让出 CPU, 让两个窗口交替售票, 输出更直观 */ - rt_thread_mdelay(TICKET_YIELD_MS); - } - - /* 两个窗口都退出后, 由最后退出的窗口打印总结 */ - if (demo_finish_one(&g_ticket_window_done, TICKET_WINDOW_COUNT)) - { - demo_printf("all tickets sold\n"); - demo_printf("remaining tickets: %d\n", (int)g_ticket_left); - demo_printf("sold total: %d, duplicate: %d, invalid: %d\n", - (int)g_ticket_sold_cnt, (int)g_ticket_dup_cnt, (int)g_ticket_invalid); - - if (g_ticket_use_mutex) - { - demo_printf("ticket demo finished: all %d tickets sold, no duplicate\n", - TICKET_TOTAL); - } - else - { - demo_printf("unsafe demo finished: race exposed duplicate/wrong remaining\n"); - } - g_ticket_running = 0; - } -} - -/* MSH 命令: ticket_demo [unsafe|mutex] */ -static void ticket_demo(int argc, char **argv) -{ - rt_thread_t tid; - - /* 解析参数: 默认加锁, "unsafe" 为无锁对比实验 */ - g_ticket_use_mutex = 1; - if (argc >= 2) - { - if (rt_strcmp(argv[1], "unsafe") == 0) - { - g_ticket_use_mutex = 0; - } - else if (rt_strcmp(argv[1], "mutex") == 0) - { - g_ticket_use_mutex = 1; - } - else - { - demo_printf("usage: ticket_demo [mutex | unsafe]\n"); - return; - } - } - - if (g_ticket_running) - { - demo_printf("ticket demo is already running, please wait for it to finish\n"); - return; - } - - demo_print_init(); - demo_printf("ticket demo started: %d tickets, mode = %s\n", - TICKET_TOTAL, g_ticket_use_mutex ? "mutex" : "unsafe (no mutex)"); - - /* 惰性创建互斥量(仅加锁版本需要) */ - if (g_ticket_use_mutex && g_ticket_mutex == RT_NULL) - { - g_ticket_mutex = rt_mutex_create("tkmutex", RT_IPC_FLAG_PRIO); - if (g_ticket_mutex == RT_NULL) - { - demo_printf("create ticket mutex failed\n"); - return; - } - } - - /* 每轮开始复位全局状态 */ - g_ticket_left = TICKET_TOTAL; - g_ticket_sold_cnt = 0; - g_ticket_dup_cnt = 0; - g_ticket_invalid = 0; - g_ticket_window_done = 0; - g_ticket_running = 1; - rt_memset(g_ticket_sold, 0, sizeof(g_ticket_sold)); - - /* 创建窗口 A 线程 */ - tid = rt_thread_create("winA", window_thread_entry, (void *)"window A", - DEMO_THREAD_STACK_SIZE, DEMO_THREAD_PRIORITY, 10); - if (tid == RT_NULL) - { - demo_printf("create thread winA failed\n"); - g_ticket_running = 0; - return; - } - rt_thread_startup(tid); - - /* 创建窗口 B 线程 */ - tid = rt_thread_create("winB", window_thread_entry, (void *)"window B", - DEMO_THREAD_STACK_SIZE, DEMO_THREAD_PRIORITY, 10); - if (tid == RT_NULL) - { - demo_printf("create thread winB failed\n"); - demo_thread_delete_by_name("winA"); - g_ticket_running = 0; - return; - } - rt_thread_startup(tid); -} -MSH_CMD_EXPORT(ticket_demo, ticket demo: mutex-protected ticket selling (ticket_demo unsafe)); - -/*************************************************************************** - * 练习三: 系统启动条件检查(事件集) - * - * 网络/传感器/存储三个模块并行初始化, 主业务线程用 AND 模式等待 - * 三个就绪事件全部到达. 存储模块耗时 5 秒, 主业务线程最多等 3 秒, - * 超时后打印尚未就绪的模块并继续等待. - **************************************************************************/ -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -#define STARTUP_WAIT_MS 3000 /* 主业务线程单次等待上限 */ - -#define MODULE_COUNT 3 - -/* 模块描述表: 名称 / 事件位 / 模拟初始化耗时 */ -struct module_desc -{ - const char *name; - rt_uint32_t event_bit; - rt_uint32_t init_ms; -}; - -static const struct module_desc g_modules[MODULE_COUNT] = -{ - { "network", EVENT_NET_READY, 1000 }, - { "sensor", EVENT_SENSOR_READY, 2000 }, - { "storage", EVENT_STORAGE_READY, 5000 }, -}; - -static rt_event_t g_startup_event = RT_NULL; /* 启动事件集 */ -static rt_uint8_t g_startup_running = 0; /* 演示运行标志 */ - -/* 把"尚未就绪"的模块拼成 "a, b" 形式的字符串 */ -static const char *missing_modules_str(rt_uint32_t ready_bits) -{ - static char buf[48]; - rt_uint8_t i; - int len = 0; - - buf[0] = '\0'; - for (i = 0; i < MODULE_COUNT; i++) - { - if (!(ready_bits & g_modules[i].event_bit)) - { - if (len > 0) - { - len += rt_snprintf(buf + len, sizeof(buf) - len, ", "); - } - len += rt_snprintf(buf + len, sizeof(buf) - len, "%s", g_modules[i].name); - } - } - return buf; -} - -/* 模块初始化线程: 参数为模块下标 */ -static void module_init_entry(void *parameter) -{ - rt_uint8_t idx = (rt_uint8_t)(rt_base_t)parameter; - const struct module_desc *mod = &g_modules[idx]; - - demo_printf("%s init start\n", mod->name); - - /* 模拟该模块的初始化耗时 */ - rt_thread_mdelay(mod->init_ms); - - /* 发送本模块的就绪事件 */ - if (rt_event_send(g_startup_event, mod->event_bit) != RT_EOK) - { - demo_printf("%s send ready event failed\n", mod->name); - } - demo_printf("%s ready\n", mod->name); -} - -/* 主业务线程: AND 模式等待三个事件全部到达 */ -static void business_thread_entry(void *parameter) -{ - rt_uint32_t recved = 0; - rt_err_t result; - - (void)parameter; - - while (1) - { - /* - * AND 模式 + CLEAR: 三个事件全部到达才返回, 成功时自动清除事件位; - * 超时时已到达的事件位保留, 下一次等待可以继续用. - */ - result = rt_event_recv(g_startup_event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - rt_tick_from_millisecond(STARTUP_WAIT_MS), &recved); - if (result == RT_EOK) - { - demo_printf("all modules are ready\n"); - demo_printf("business task started\n"); - break; - } - - /* 超时: 打印尚未就绪的模块, 然后再次等待 */ - demo_printf("wait modules timeout, %s not ready\n", - missing_modules_str(g_startup_event->set)); - } - - g_startup_running = 0; -} - -/* MSH 命令: startup_demo */ -static void startup_demo(int argc, char **argv) -{ - rt_thread_t tid; - rt_uint8_t i; - static const char *init_thread_names[MODULE_COUNT] = - { - "netinit", "snrinit", "stoinit", - }; - - (void)argv; - (void)argc; - - if (g_startup_running) - { - demo_printf("startup demo is already running, please wait for it to finish\n"); - return; - } - - demo_print_init(); - demo_printf("startup demo started: wait all modules ready within %d ms\n", - STARTUP_WAIT_MS); - - /* 惰性创建事件集 */ - if (g_startup_event == RT_NULL) - { - g_startup_event = rt_event_create("bsev", RT_IPC_FLAG_FIFO); - if (g_startup_event == RT_NULL) - { - demo_printf("create startup event set failed\n"); - return; - } - } - - /* 防御: 清掉上一轮可能残留的事件位 */ - rt_event_control(g_startup_event, RT_IPC_CMD_RESET, RT_NULL); - - g_startup_running = 1; - - /* 创建三个模块初始化线程, 逐一检查创建结果 */ - for (i = 0; i < MODULE_COUNT; i++) - { - tid = rt_thread_create(init_thread_names[i], module_init_entry, - (void *)(rt_base_t)i, - DEMO_THREAD_STACK_SIZE, DEMO_THREAD_PRIORITY, 10); - if (tid == RT_NULL) - { - demo_printf("create thread %s failed\n", init_thread_names[i]); - while (i > 0) - { - i--; - demo_thread_delete_by_name(init_thread_names[i]); - } - g_startup_running = 0; - return; - } - rt_thread_startup(tid); - } - - /* 创建主业务线程 */ - tid = rt_thread_create("biz", business_thread_entry, RT_NULL, - DEMO_THREAD_STACK_SIZE, DEMO_THREAD_PRIORITY, 10); - if (tid == RT_NULL) - { - demo_printf("create thread biz failed\n"); - for (i = 0; i < MODULE_COUNT; i++) - { - demo_thread_delete_by_name(init_thread_names[i]); - } - g_startup_running = 0; - return; - } - rt_thread_startup(tid); -} -MSH_CMD_EXPORT(startup_demo, startup demo: event set wait for all modules ready); - -/*************************************************************************** - * 练习四: 快递分拣中心(消息队列) - * - * 两个发件线程各产生 5 条快递消息(编号唯一), 通过容量为 5 的消息队列 - * 交给分拣线程. 队列满时发送失败会计数并稍后重试, 不会崩溃. - * 每个发件线程完成后发送一条结束消息(region = 0), 分拣线程收到 - * 两条结束消息后打印统计结果并退出. - **************************************************************************/ -#define PKG_QUEUE_SIZE 5 /* 消息队列容量 */ -#define PKG_PER_SENDER 5 /* 每个发件线程的快递数 */ -#define PKG_SENDER_COUNT 2 /* 发件线程数 */ -#define PKG_END_REGION 0 /* region = 0 表示结束消息 */ -#define PKG_SORT_MS 80 /* 分拣一条快递的耗时 */ -#define PKG_SEND_RETRY_MS 50 /* 发送失败后的重试间隔 */ - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; /* 1 华东, 2 华南, 3 华北, 0 结束 */ - rt_uint16_t weight; -}; - -static rt_mq_t g_pkg_mq = RT_NULL; /* 快递消息队列 */ -static rt_uint8_t g_pkg_running = 0; /* 演示运行标志 */ -static rt_uint32_t g_pkg_total = 0; /* 快递总数 */ -static rt_uint32_t g_pkg_region_cnt[3] = { 0, 0, 0 }; /* 各区域数量 */ -static rt_uint32_t g_pkg_weight_total = 0; /* 总重量 */ -static rt_uint32_t g_pkg_send_failed = 0; /* 发送失败次数 */ -static rt_uint8_t g_pkg_end_received = 0; /* 收到的结束消息数 */ - -/* 区域编号转区域名 */ -static const char *package_region_name(rt_uint8_t region) -{ - switch (region) - { - case 1: - return "east"; - case 2: - return "south"; - case 3: - return "north"; - default: - return "unknown"; - } -} - -/* 发件线程: 参数为发件人编号(1/2) */ -static void sender_thread_entry(void *parameter) -{ - rt_uint8_t sender_no = (rt_uint8_t)(rt_base_t)parameter; - rt_uint32_t base = (rt_uint32_t)sender_no * 1000; /* 1号: 100x, 2号: 200x */ - struct package_msg pkg; - rt_uint8_t i; - - for (i = 1; i <= PKG_PER_SENDER; i++) - { - pkg.id = base + i; /* 编号全局唯一 */ - pkg.region = (rt_uint8_t)(((sender_no + i) % 3) + 1); /* 区域轮转 */ - pkg.weight = (rt_uint16_t)(800 + i * 100 + sender_no * 50); - - /* - * 非阻塞发送: 队列满时返回 -RT_EFULL, 计数失败并稍后重试, - * 发送线程不会阻塞或崩溃. - */ - while (rt_mq_send(g_pkg_mq, &pkg, sizeof(pkg)) != RT_EOK) - { - g_pkg_send_failed++; - rt_thread_mdelay(PKG_SEND_RETRY_MS); - } - } - - /* 本线程的快递全部发出后, 发送一条结束消息 */ - pkg.id = 0; - pkg.region = PKG_END_REGION; - pkg.weight = 0; - while (rt_mq_send(g_pkg_mq, &pkg, sizeof(pkg)) != RT_EOK) - { - rt_thread_mdelay(PKG_SEND_RETRY_MS); - } -} - -/* 分拣线程: 取出快递并按区域分类统计 */ -static void sorter_thread_entry(void *parameter) -{ - struct package_msg pkg; - rt_err_t result; - - (void)parameter; - - while (1) - { - result = rt_mq_recv(g_pkg_mq, &pkg, sizeof(pkg), RT_WAITING_FOREVER); - if (result != RT_EOK) - { - demo_printf("recv package failed, error %d\n", result); - continue; - } - - /* 结束消息: 两个发件线程都结束后统计并退出 */ - if (pkg.region == PKG_END_REGION) - { - g_pkg_end_received++; - if (g_pkg_end_received >= PKG_SENDER_COUNT) - { - break; - } - continue; - } - - /* 分拣打印 */ - demo_printf("package %d -> %s, weight: %d g\n", - (int)pkg.id, package_region_name(pkg.region), (int)pkg.weight); - - /* 分类统计 */ - g_pkg_total++; - if (pkg.region >= 1 && pkg.region <= 3) - { - g_pkg_region_cnt[pkg.region - 1]++; - } - g_pkg_weight_total += pkg.weight; - - /* 模拟分拣耗时: 让队列被填满的情况真实出现 */ - rt_thread_mdelay(PKG_SORT_MS); - } - - /* 打印统计结果 */ - demo_printf("total packages: %d\n", (int)g_pkg_total); - demo_printf("east: %d, south: %d, north: %d\n", - (int)g_pkg_region_cnt[0], (int)g_pkg_region_cnt[1], - (int)g_pkg_region_cnt[2]); - demo_printf("total weight: %d g\n", (int)g_pkg_weight_total); - demo_printf("send failed: %d\n", (int)g_pkg_send_failed); - - g_pkg_running = 0; -} - -/* MSH 命令: package_demo */ -static void package_demo(int argc, char **argv) -{ - rt_thread_t threads[PKG_SENDER_COUNT + 1]; /* 2 个发件 + 1 个分拣 */ - rt_uint8_t created = 0; - rt_uint8_t i; - static const char *thread_names[PKG_SENDER_COUNT + 1] = - { - "sndr1", "sndr2", "sorter", - }; - - (void)argv; - (void)argc; - - if (g_pkg_running) - { - demo_printf("package demo is already running, please wait for it to finish\n"); - return; - } - - demo_print_init(); - demo_printf("package demo started: %d senders x %d packages, queue size %d\n", - PKG_SENDER_COUNT, PKG_PER_SENDER, PKG_QUEUE_SIZE); - - /* 惰性创建消息队列: 每条消息 sizeof(struct package_msg), 容量 5 */ - if (g_pkg_mq == RT_NULL) - { - g_pkg_mq = rt_mq_create("pkgmq", sizeof(struct package_msg), - PKG_QUEUE_SIZE, RT_IPC_FLAG_FIFO); - if (g_pkg_mq == RT_NULL) - { - demo_printf("create package message queue failed\n"); - return; - } - } - - /* 每轮开始复位统计量 */ - g_pkg_total = 0; - g_pkg_region_cnt[0] = 0; - g_pkg_region_cnt[1] = 0; - g_pkg_region_cnt[2] = 0; - g_pkg_weight_total = 0; - g_pkg_send_failed = 0; - g_pkg_end_received = 0; - g_pkg_running = 1; - - /* - * 先全部创建、后统一启动: 任一创建失败都不会产生半截数据, - * 回滚时只需删除已创建但未启动的线程. - */ - for (i = 0; i < PKG_SENDER_COUNT; i++) - { - threads[i] = rt_thread_create(thread_names[i], sender_thread_entry, - (void *)(rt_base_t)(i + 1), - DEMO_THREAD_STACK_SIZE, DEMO_THREAD_PRIORITY, 10); - if (threads[i] == RT_NULL) - { - demo_printf("create thread %s failed\n", thread_names[i]); - break; - } - created++; - } - - if (created == PKG_SENDER_COUNT) - { - threads[PKG_SENDER_COUNT] = rt_thread_create(thread_names[PKG_SENDER_COUNT], - sorter_thread_entry, RT_NULL, - DEMO_THREAD_STACK_SIZE, - DEMO_THREAD_PRIORITY, 10); - if (threads[PKG_SENDER_COUNT] == RT_NULL) - { - demo_printf("create thread %s failed\n", thread_names[PKG_SENDER_COUNT]); - } - else - { - created++; - } - } - - /* 创建不完整: 回滚并复位运行标志 */ - if (created != PKG_SENDER_COUNT + 1) - { - for (i = 0; i < created; i++) - { - rt_thread_delete(threads[i]); - } - g_pkg_running = 0; - return; - } - - /* 全部创建成功, 统一启动 */ - for (i = 0; i < PKG_SENDER_COUNT + 1; i++) - { - rt_thread_startup(threads[i]); - } -} -MSH_CMD_EXPORT(package_demo, package demo: message queue sorting center); diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 34060da8c6b8fcf94095c2ed054b22a62cd45db6..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,212 +0,0 @@ -# 【DAY2】RT-Thread 启动流程、内核基础与多线程编程 - -## Part 1:理论知识梳理文档 - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -- **启动路径**:详细画出/写出从上电复位 `Reset_Handler` 到进入 `main()` 的完整调用链。 - - ![RT-Thread启动流程](.\figures\rtt_startup.png) - - 上图完整地展示了RT-Thread的整个启动流程。以STM32F407系列芯片为例,整个过程大致可以分为以下六个部分: - - 1. RTT转入 - ``` - starup_stm32f407xx.s (Reset_Handler) - | - $Sub$$main() / _low_level_init() / entry() - | - rethread_startup() - ``` - - 2. 板级初始化 - ``` - rt_hw_interrupt_disable() - | - rt_hw_board_init() - | - rt_components_board_init() - | - board init functions - ``` - - 3. RTT内核初始化 - ``` - rt_show_version() - | - rt_system_timer_init() - | - rt_system_scheduler_init() - | - rt_system_signal_init() - ``` - - 4. 系统线程初始化 - ``` - rt_application_init() - | - rt_system_timer_thread_init() - | - rt_thread_idle_init() - ``` - - 5. 调度器启动 - - ``` - rt_system_scheduler_startup() - | - main_thread_entry() / rt_thread_timer_entry() / rt_thread_idle_entry() - ``` - - 6. mian线程运行 - ``` - rt_components_init() - | - main() - ``` - -- **自动初始化原理**:解释 `INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` 的底层实现原理。 - - 自动初始化的底层代码如下所示: - - ```c - /* 初始化函数指针类型 */ - typedef int (*init_fn_t)(void); - - /* 底层通用宏 */ - #define INIT_EXPORT(fn, level) \ - RT_USED const init_fn_t __rt_init_##fn \ - RT_SECTION(".rti_fn." level) = fn - - /* 六个等级的对外宏 */ - #define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") - #define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2") - #define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3") - #define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4") - #define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") - #define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") - - /* 初始化函数指针类型 */ - typedef int (*init_fn_t)(void); - - /* 底层通用宏 */ - #define INIT_EXPORT(fn, level) \ - RT_USED const init_fn_t __rt_init_##fn \ - RT_SECTION(".rti_fn." level) = fn - - /* 六个等级的对外宏 */ - #define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") - #define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2") - #define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3") - #define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4") - #define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") - #define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") - ``` - - 对于一个初始化函数`x_init()`,`INIT_BOARD_EXPORT(x_init)`的实际执行逻辑为: - - > ​ 定义一个名叫 `__rt_init_led_board_init` 的**常量函数指针变量**,它的值是 `led_board_init` 这个函数的地址,并且强制把这个变量放到名为 `.rti_fn.1` (`INIT_APP_EXPORT`则对应`.rti_fn.6`)的段里,同时告诉编译器"就算没被调用也别删掉该函数"。 - -### 2. 内核基础与对象模型 - -- **对象容器与控制块**:简述 RT-Thread 面向对象的设计思想,线程控制块(`struct rt_thread`)中关键成员(如 `stat`、`current_priority`、`init_tick`、`sp`)的作用。 - - RT-Thread 使用纯 C 语言实现了的面向对象设计,其核心思想是:**通过结构体嵌套模拟类的继承关系,并统一管理所有内核对象**。 - - 所有内核对象(线程、信号量、互斥量、消息队列等)都以 `struct rt_object` 作为第一个成员: - - ```c - struct rt_object - { - char name[RT_NAME_MAX]; /* 对象名称 */ - rt_uint8_t type; /* 对象类型 */ - rt_uint8_t flag; /* 对象标志 */ - rt_list_t list; /* 对象链表节点 */ - }; - ``` - - 通过把 `parent` 放在结构体首位,任何指向`rt_thread`的指针都可以被安全地强制转换为`struct rt_object *`,从而实现:**统一命名与类型识别**、**统一容器管理**、**类似多态的行为**。 - - 其中,部分关键成员的作用如下表所示: - - | 成员 | 作用 | - | ---------------------------- | -------------------------------- | - | `sp` | 保存线程栈指针,用于上下文切换 | - | `stat` | 记录线程状态,决定线程是否可调度 | - | `current_priority` | 线程当前优先级,决定调度顺序 | - | `init_tick`/`remaining_tick` | 实现时间片轮转调度 | - | `thread_timer` | 实现线程延时和超时机制 | - | `cleanup` | 线程退出时执行清理工作 | - | `user_data` | 提供线程私有数据存储 | - -- **内存分配方式**:对比静态对象与动态对象的区别(分配位置、生命周期、释放机制),分析为什么某些高可靠场景强制使用静态线程。 - - | 分配方式 | 静态 | 动态 | - | :-: | :-: | :-: | - | **分配位置** | 放在RW段和ZI段中,在程序中初始化 | 从内存堆中创建,在程序中手动初始化 | - | **生命周期** | 在整个程序运行期间始终存在 | 由程序运行逻辑控制,按需创建、销毁 | - | **释放机制** | 长期占用RAM空间,但可以被复用 | 当对象被删除后,占用的RAM空间被释放 | - -### 3. 线程状态机与调度算法 - -- **五态模型**:绘制或描述 RT-Thread 线程状态(初始态、就绪态、运行态、挂起态、关闭态)及触发状态转换的典型 API。 - - 1. **初始态`RT_THREAD_INIT`**:线程刚开始创建还没开始运行时的状态;在该状态下,线程不参与调度。 - - 2. **就绪态`RT_THREAD_READY`**:线程按照优先级排队,等待被执行的状态;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。 - - 3. **运行态**`RT_THREAD_RUNNING`:线程当前正在运行的状态。 - - 4. **挂起态(阻塞态)`RT_THREAD_SUSPEND`**:线程因为资源不可用或主动延时一段时间而挂起等待的状态;在该状态下,线程不参与调度。 - - 5. **关闭态**`RT_THREAD_CLOSE`:线程运行结束时的状态。该状态下的线程不参与调度。 - - **五态转化**: - ![RT-Thread状态切换函数](.\figures\thread_sta.png) - -- **调度机制剖析**: - - - **抢占式调度** - - 抢占式调度解决的是**不同优先级线程之间**的CPU分配问题。它的核心原则是:**系统始终保证当前处于就绪态中优先级最高的线程占用CPU执行**——当一个高优先级的线程因为某种条件(如延时结束、等待的信号量被释放、接收到消息)从挂起态转变为就绪态时,如果当前正在运行的线程优先级比它低,调度器会立刻打断当前线程的执行,将CPU移交给这个高优先级线程。这个过程就叫做“抢占”。 - - - **时间片轮转** - - 时间片轮转解决的是**相同优先级线程之间**的CPU公平分配问题。如果系统中存在多个优先级完全相同的就绪线程,如果没有时间片机制,最先运行的线程如果不主动阻塞,将会一直霸占 CPU(即“饿死”其他同优先级线程)。系统依靠硬件定时器(SysTick)产生周期性的Tick中断(例如1ms一次)。在每次SysTick中断处理函数中,当前运行线程的`remaining_tick`会减1。 - 当`remaining_tick`减到0时,系统会执行以下动作: - - 1. 将该线程的`remaining_tick`重新恢复为`init_tick`; - 2. 将该线程从其所在优先级就绪队列的**头部移到尾部**; - 3. 触发调度器,就绪队列中排在它后面的下一个同优先级线程获得CPU开始运行。 - -## Part 2:多线程实战编程(C 语言实现) - -### 任务完成情况 - -1. **工作线程 1(动态创建 `th_led1`)**: - - [x] 优先级 `16`,时间片 `5 ticks`,栈 `1024 Bytes`。 - - [x] 循环翻转板载 LED_R,每次执行打印计数与 `rt_tick_get()`。 -2. **工作线程 2(静态初始化 `th_led2`)**: - - [x] 优先级 `16`,时间片 `5 ticks`,静态定义全局栈与 TCB。 - - [x] 循环翻转板载 LED_B,每次执行打印计数。 -3. **监控与控制线程(动态创建 `th_monitor`)**: - - [x] 优先级 `10`(高于工作线程),周期延时 `rt_thread_mdelay(2000)`。 - - [x] 唤醒后获取并打印 `th_led1` 与 `th_led2` 的运行状态与剩余栈空间大小。 - - [x] 结合板载按键检测(或按键中断):单次触发时调用 `rt_thread_suspend()` 挂起 `th_led1`;再次触发时调用 `rt_thread_resume()` 恢复。 -4. **系统集成与调试命令**: - - [x] 使用 `INIT_APP_EXPORT()` 自动加载测试模块,禁止在 `main.c` 中硬编码调用。 - - [x] 使用 `MSH_CMD_EXPORT()` 导出自定义查看命令(如 `thread_dump`)。 - -### 过程记录 - -1. **工作线程1、2交替运行,定时监控(按键第偶数次按下)** - - ![](E:\RT-Thread\figures\led1_running.png) - -2. **工作线程2单独运行,定时监控(按键第奇数次按下)** - - ![](E:\RT-Thread\figures\led1_suspend.png) - -3. **自定义查看命令** - - ![](E:\RT-Thread\figures\check.png) \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/check.png" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/check.png" deleted file mode 100644 index 222be23481aa1548d6a848d70aa82fb98b365a7d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/check.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/led1_running.png" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/led1_running.png" deleted file mode 100644 index dea06337ab83f472c5240a2967084d63b8d7ab76..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/led1_running.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/led1_suspend.png" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/led1_suspend.png" deleted file mode 100644 index 1fd3ee4cdfd754f163f814b686583a47c1c7649d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/led1_suspend.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/rtt_startup.png" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/rtt_startup.png" deleted file mode 100644 index 04dbb79fed05b7463c5ceea179f5dd75c6553724..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/rtt_startup.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/thread_sta.png" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/thread_sta.png" deleted file mode 100644 index fe2cc8141fd5c220db0fca600c75bd9b8ea66a92..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/thread_sta.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 9c1cd0dfe50906cc17e689a05c3ff9303e1a9eed..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-18 Lenovo the first version - */ - -/* - * 程序清单:线程动态创建 / 静态初始化、线程挂起与恢复、线程监控 - * - * 本模块通过 INIT_APP_EXPORT() 自动加载(main.c 中无任何硬编码调用): - * - * th_led1 : 动态创建,优先级 16、时间片 5 tick、栈 1024 字节。 - * 循环翻转板载 LED_R(PF12),每次执行打印计数与 rt_tick_get()。 - * th_led2 : 静态初始化(全局静态栈 + 静态 TCB),优先级 16、时间片 5 tick。 - * 循环翻转板载 LED_B(PF11),每次执行打印计数。 - * th_monitor: 动态创建,优先级 10(高于工作线程),周期 2000 ms - * (100 ms 切片实现,按键响应最大延迟 100 ms)。 - * 唤醒后打印 th_led1 / th_led2 的运行状态与剩余栈空间; - * 板载按键 KEY0(PC0,按下为低电平)采用下降沿中断投递事件: - * 第一次触发挂起 th_led1,再次触发恢复 th_led1。 - * - * 重要(挂起机制): - * 本 SDK 内核的 rt_thread_suspend() 带有 RT_ASSERT(thread == rt_thread_self()), - * 即只允许"线程挂起自身";RT_DEBUG 开启时对其它线程调用会断言失败并使 - * 调用者死循环(这正是此前"按键无法恢复"的根因:th_monitor 挂死)。 - * 因此挂起 th_led1 采用"挂起请求 + 自我挂起"方式: - * th_monitor 置挂起请求标志 -> th_led1 检测到后调用 - * rt_thread_suspend(rt_thread_self()) 并 rt_schedule() 让出 CPU; - * 恢复仍由 th_monitor 调用 rt_thread_resume() 完成(该函数无此限制)。 - * - * MSH 调试命令:thread_dump —— 打印三个线程的状态、优先级与剩余栈空间。 - */ -#include -#include -#include - -/* 板载 LED 引脚(共阳 RGB-LED,输出低电平点亮) */ -#define PIN_LED_R GET_PIN(F, 12) /* PF12 : LED_R --> LED */ -#define PIN_LED_B GET_PIN(F, 11) /* PF11 : LED_B --> LED */ - -/* 板载按键引脚(按下为低电平) */ -#define PIN_KEY0 GET_PIN(C, 0) /* PC0 : KEY0 --> KEY */ - -/* 工作线程参数:优先级 16、时间片 5 tick、栈 1024 字节 */ -#define LED_THREAD_PRIORITY 16 -#define LED_THREAD_TIMESLICE 5 -#define LED_THREAD_STACK_SIZE 1024 -/* 两线程翻转周期保持一致(500 ms),同优先级 + 同时间片下严格轮转交替闪烁 */ -#define LED_BLINK_PERIOD_TICK (RT_TICK_PER_SECOND / 2) /* 500 ms */ - -/* 监控线程参数:优先级 10(高于工作线程)、周期 2000 ms、切片 100 ms */ -#define MONITOR_PRIORITY 10 -#define MONITOR_TIMESLICE 5 -#define MONITOR_STACK_SIZE 2048 -#define MONITOR_PERIOD_MS 2000 -#define MONITOR_SLICE_MS 100 - -/* KEY0 软件消抖窗口:200 ms 内只响应一次 */ -#define KEY_DEBOUNCE_TICK (RT_TICK_PER_SECOND / 5) - -/* 线程句柄 */ -static rt_thread_t th_led1 = RT_NULL; /* 动态线程 */ -static rt_thread_t th_led2 = RT_NULL; /* 静态线程,指向静态 TCB */ -static rt_thread_t th_monitor = RT_NULL; /* 动态线程 */ - -/* th_led2 的全局静态栈与静态线程控制块 */ -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t th_led2_stack[LED_THREAD_STACK_SIZE]; -static struct rt_thread th_led2_tcb; - -/* KEY0 按键事件信号量:中断中释放,th_monitor 中获取 */ -static struct rt_semaphore key_sem; - -/* th_led1 挂起请求标志:th_monitor 置位,th_led1 检测后自我挂起 */ -static volatile rt_uint8_t led1_pause_request = 0; - -/* th_led1 当前挂起状态(仅 th_monitor 访问) */ -static volatile rt_uint8_t led1_suspended = 0; - -/* KEY0 下降沿中断回调:只做消抖与事件投递,不做业务逻辑 */ -static void key0_irq_handler(void *args) -{ - static rt_tick_t last_tick = 0; - rt_tick_t now = rt_tick_get(); - - (void)args; - - if (now - last_tick > KEY_DEBOUNCE_TICK) - { - last_tick = now; - rt_sem_release(&key_sem); - } -} - -/* 线程状态码 --> 可读字符串 */ -static const char *thread_stat_str(rt_uint8_t stat) -{ - switch (stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_READY: return "ready"; - case RT_THREAD_SUSPEND: return "suspend"; - case RT_THREAD_RUNNING: return "running"; - case RT_THREAD_INIT: return "init"; - case RT_THREAD_CLOSE: return "close"; - default: return "unknown"; - } -} - -/* - * 计算线程剩余栈空间(字节)。 - * 线程栈在创建/初始化时被填充为 '#',Cortex-M 栈向下增长, - * 因此从栈底向上扫描连续 '#' 的个数即为剩余栈空间(与 list_thread 命令算法一致)。 - */ -static rt_uint32_t thread_stack_free(rt_thread_t thread) -{ - rt_uint8_t *ptr; - rt_uint8_t *end; - - if (thread == RT_NULL) - { - return 0; - } - - ptr = (rt_uint8_t *)thread->stack_addr; - end = (rt_uint8_t *)thread->stack_addr + thread->stack_size; - - while ((ptr < end) && (*ptr == '#')) - { - ptr++; - } - - return (rt_uint32_t)(ptr - (rt_uint8_t *)thread->stack_addr); -} - -/* 打印单个线程的运行状态、优先级、剩余栈空间 */ -static void dump_thread(rt_thread_t thread) -{ - rt_uint8_t stat; - rt_uint32_t free_size; - - if (thread == RT_NULL) - { - rt_kprintf(" (null thread)\n"); - return; - } - - stat = thread->stat & RT_THREAD_STAT_MASK; - free_size = thread_stack_free(thread); - - rt_kprintf(" %-10s pri=%2d stat=%-8s stack=%4d free=%4d used=%3d%%\n", - thread->name, - thread->current_priority, - thread_stat_str(stat), - thread->stack_size, - free_size, - (thread->stack_size - free_size) * 100 / thread->stack_size); -} - -/* 工作线程 1:动态创建,循环翻转 LED_R,每次执行打印计数与系统节拍 */ -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - (void)parameter; - - while (1) - { - /* 收到 th_monitor 的挂起请求:本内核仅允许线程挂起自身, - 因此由 th_led1 自行挂起,并手动触发调度让出 CPU, - 直到 th_monitor 调用 rt_thread_resume() 恢复。 */ - if (led1_pause_request) - { - led1_pause_request = 0; - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - } - - count++; - - /* 翻转 LED_R:读回当前电平并取反写入 */ - rt_pin_write(PIN_LED_R, !rt_pin_read(PIN_LED_R)); - rt_kprintf("[th_led1] count=%06u | tick=%010u\n", count, rt_tick_get()); - - rt_thread_mdelay(LED_BLINK_PERIOD_TICK); - } -} - -/* 工作线程 2:静态初始化,循环翻转 LED_B,每次执行打印计数 */ -static void th_led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - - (void)parameter; - - while (1) - { - count++; - - /* 翻转 LED_B:读回当前电平并取反写入 */ - rt_pin_write(PIN_LED_B, !rt_pin_read(PIN_LED_B)); - rt_kprintf("[th_led2] count=%06u\n", count); - - rt_thread_mdelay(LED_BLINK_PERIOD_TICK); - } -} - -/* 按键事件处理:切换 th_led1 的挂起/恢复 */ -static void key_event_handle(void) -{ - rt_err_t ret; - - if (th_led1 == RT_NULL) - { - rt_kprintf("[KEY] th_led1 is NULL, cannot control\n"); - return; - } - - if (led1_suspended == 0) - { - /* --- 请求挂起:置标志,由 th_led1 自行挂起(见 th_led1_entry) --- */ - led1_pause_request = 1; - led1_suspended = 1; - rt_kprintf(">>> KEY0: suspend th_led1 request sent\n"); - } - else - { - /* --- 恢复 --- */ - ret = rt_thread_resume(th_led1); - if (ret == RT_EOK) - { - led1_suspended = 0; - rt_kprintf(">>> KEY0: th_led1 resumed\n"); - } - else - { - rt_kprintf(">>> KEY0: resume th_led1 failed, err=%d\n", ret); - } - } -} - -/* 监控与控制线程:周期打印工作线程状态,按键控制 th_led1 挂起/恢复 */ -static void th_monitor_entry(void *parameter) -{ - rt_uint32_t round = 0; - rt_uint32_t i; - const rt_uint32_t slice_num = MONITOR_PERIOD_MS / MONITOR_SLICE_MS; - - (void)parameter; - - while (1) - { - round++; - - /* 周期打印 th_led1 / th_led2 的运行状态与剩余栈空间 */ - rt_kprintf("\n+------------------------------------------+\n"); - rt_kprintf("| MONITOR #%-4u | tick=%-8u |\n", round, rt_tick_get()); - rt_kprintf("+------------------------------------------+\n"); - dump_thread(th_led1); - dump_thread(th_led2); - if (led1_suspended) - { - rt_kprintf(" [hint] th_led1 suspended, press KEY0 to resume\n"); - } - else - { - rt_kprintf(" [hint] press KEY0 to suspend th_led1\n"); - } - rt_kprintf("+------------------------------------------+\n"); - - /* - * 周期 2000ms:切成 20 x 100ms,每片结束后非阻塞地检查按键事件, - * 按键响应最大延迟从 2000ms 降到 100ms。 - */ - for (i = 0; i < slice_num; i++) - { - rt_thread_mdelay(MONITOR_SLICE_MS); - - if (rt_sem_take(&key_sem, RT_WAITING_NO) == RT_EOK) - { - key_event_handle(); - } - } - } -} - -/* MSH 命令 thread_dump:打印三个线程的状态与剩余栈空间 */ -static int thread_dump(int argc, char **argv) -{ - (void)argc; - (void)argv; - - rt_kprintf("\n========== thread dump @ tick %u ==========\n", rt_tick_get()); - rt_kprintf(" %-10s %-4s %-9s %-6s %-6s %-6s\n", - "name", "pri", "stat", "stack", "free", "used%"); - rt_kprintf("----------------------------------------------\n"); - dump_thread(th_led1); - dump_thread(th_led2); - dump_thread(th_monitor); - rt_kprintf("----------------------------------------------\n"); - rt_kprintf(" led1_suspended = %u\n", led1_suspended); - rt_kprintf("=============================================\n\n"); - - return 0; -} -MSH_CMD_EXPORT(thread_dump, dump th_led1/th_led2/th_monitor status and free stack); - -/* 模块初始化:由 INIT_APP_EXPORT 自动加载,main.c 中无需任何调用 */ -static int led_monitor_init(void) -{ - rt_err_t ret; - - /* 配置 LED 引脚为输出模式,初始熄灭(高电平) */ - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - rt_pin_write(PIN_LED_R, PIN_HIGH); - rt_pin_write(PIN_LED_B, PIN_HIGH); - - /* 初始化按键事件信号量(必须先于中断使能完成) */ - rt_sem_init(&key_sem, "key_sem", 0, RT_IPC_FLAG_FIFO); - - /* 配置 KEY0 为上拉输入,下降沿触发中断(按下为低电平) */ - rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP); - ret = rt_pin_attach_irq(PIN_KEY0, PIN_IRQ_MODE_FALLING, key0_irq_handler, RT_NULL); - if (ret != RT_EOK) - { - rt_kprintf("[ledmon] attach key0 irq failed, err=%d\n", ret); - } - else - { - ret = rt_pin_irq_enable(PIN_KEY0, PIN_IRQ_ENABLE); - if (ret != RT_EOK) - { - rt_kprintf("[ledmon] enable key0 irq failed, err=%d\n", ret); - } - else - { - rt_kprintf("[ledmon] key0 irq attached, pin=%d\n", PIN_KEY0); - } - } - - /* 动态创建 th_led1:优先级 16,时间片 5,栈 1024 字节 */ - th_led1 = rt_thread_create("th_led1", - th_led1_entry, - RT_NULL, - LED_THREAD_STACK_SIZE, - LED_THREAD_PRIORITY, - LED_THREAD_TIMESLICE); - if (th_led1 != RT_NULL) - { - rt_thread_startup(th_led1); - rt_kprintf("[ledmon] th_led1 created (dynamic), pri=%d, timeslice=%d, stack=%d B\n", - LED_THREAD_PRIORITY, LED_THREAD_TIMESLICE, LED_THREAD_STACK_SIZE); - } - else - { - rt_kprintf("[ledmon] create th_led1 failed\n"); - return -RT_ENOMEM; - } - - /* 静态初始化 th_led2:全局静态栈 + 静态 TCB,优先级 16,时间片 5 */ - ret = rt_thread_init(&th_led2_tcb, - "th_led2", - th_led2_entry, - RT_NULL, - &th_led2_stack[0], - sizeof(th_led2_stack), - LED_THREAD_PRIORITY, - LED_THREAD_TIMESLICE); - if (ret == RT_EOK) - { - rt_thread_startup(&th_led2_tcb); - th_led2 = &th_led2_tcb; - rt_kprintf("[ledmon] th_led2 initialized (static), pri=%d, timeslice=%d, stack=%d B\n", - LED_THREAD_PRIORITY, LED_THREAD_TIMESLICE, sizeof(th_led2_stack)); - } - else - { - rt_kprintf("[ledmon] init th_led2 failed, err=%d\n", ret); - return ret; - } - - /* 动态创建 th_monitor:优先级 10,周期 2000 ms(100 ms 切片) */ - th_monitor = rt_thread_create("th_monitor", - th_monitor_entry, - RT_NULL, - MONITOR_STACK_SIZE, - MONITOR_PRIORITY, - MONITOR_TIMESLICE); - if (th_monitor != RT_NULL) - { - rt_thread_startup(th_monitor); - rt_kprintf("[ledmon] th_monitor created (dynamic), pri=%d, period=%d ms\n", - MONITOR_PRIORITY, MONITOR_PERIOD_MS); - } - else - { - rt_kprintf("[ledmon] create th_monitor failed\n"); - return -RT_ENOMEM; - } - - rt_kprintf("[ledmon] module loaded by INIT_APP_EXPORT\n"); - return RT_EOK; -} -INIT_APP_EXPORT(led_monitor_init); diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/dev_test.c" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/dev_test.c" deleted file mode 100644 index d9ed8e036bfd892d403b1d1b3736f43d9cbdff9f..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/dev_test.c" +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 自定义 I/O 设备示例:创建、注册一个字符设备,并实现其操作方法 - * - * 说明: - * 1. 本工程未开启 RT_USING_DEVICE_OPS,struct rt_device 直接包含 - * init/open/close/read/write/control 六个函数指针。 - * 2. read/write 的返回类型是 rt_size_t(读写字节数),不是 rt_err_t。 - * 3. 使用 ulog 输出日志:先定义 LOG_TAG / LOG_LVL,再 #include 。 - */ -#include -#include - -#define LOG_TAG "drv_test_app" -#define LOG_LVL LOG_LVL_DBG -#include - -/* ---------- 设备操作方法实现 ---------- */ - -static rt_err_t test_dev_init(rt_device_t dev) -{ - LOG_I("test_dev init."); - return RT_EOK; -} - -static rt_err_t test_dev_open(rt_device_t dev, rt_uint16_t oflag) -{ - LOG_I("test_dev open, oflag = %d", oflag); - return RT_EOK; -} - -static rt_err_t test_dev_close(rt_device_t dev) -{ - LOG_I("test_dev close."); - return RT_EOK; -} - -static rt_size_t test_dev_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) -{ - LOG_I("test_dev read, pos = %ld, size = %u", pos, size); - return size; /* 模拟读取 size 个字节 */ -} - -static rt_size_t test_dev_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) -{ - LOG_I("test_dev write, pos = %ld, size = %u", pos, size); - return size; /* 模拟写入 size 个字节 */ -} - -static rt_err_t test_dev_control(rt_device_t dev, int cmd, void *args) -{ - LOG_I("test_dev control, cmd = %d", cmd); - return RT_EOK; -} - -/* ---------- 设备创建与注册 ---------- */ - -static int rt_dev_test_init(void) -{ - rt_device_t test_dev = rt_device_create(RT_Device_Class_Char, 0); - if (test_dev == RT_NULL) - { - rt_kprintf("test_dev create failed\n"); - return -RT_ERROR; - } - - /* 挂接操作方法 */ - test_dev->init = test_dev_init; - test_dev->open = test_dev_open; - test_dev->close = test_dev_close; - test_dev->read = test_dev_read; - test_dev->write = test_dev_write; - test_dev->control = test_dev_control; - - /* 注册设备,供应用层通过名称查找 */ - if (rt_device_register(test_dev, "test_dev", RT_DEVICE_FLAG_RDWR) != RT_EOK) - { - rt_kprintf("test_dev register failed\n"); - return -RT_ERROR; - } - - LOG_I("test_dev register success."); - return RT_EOK; -} -INIT_DEVICE_EXPORT(rt_dev_test_init); diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\347\254\224\350\256\260/figures/io-dev.png" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\347\254\224\350\256\260/figures/io-dev.png" deleted file mode 100644 index 901577c64d7fcbbddaf85c39fbc888dc1afe4e14..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\347\254\224\350\256\260/figures/io-dev.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 849e523095d0c0f070716568d00862f86ffd2d71..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,96 +0,0 @@ -# 第一天笔记 - -## 预备环节:准备以下开发工具 - -RT-Thread Studio (RT_Thread官方IDE) - -Gitee (国内开源代码托管平台) - -Typora (MarkDown文本编辑器) - -## 上午学习任务:初识开发环境 - - - -**任务1:新建一个qemu-a9工程,并运行RT-Thread** - -- **前置条件** STM32F4系列芯片支持包 - -- **路径** RT-Thread Studio -> 文件 -> 新建 -> RT项目 -> 基于开发板 -> qemu-vexpress-a9仿真器(无需实体板卡) - -- **关键结构** - - ``` - hello/ - ├── application/ - │ └── main.c (主程序入口) - ├── drivers/ - ├── components/ - ├── libcpu/ - └── src/ - -- **编译** 点击绿色小锤子图标 - -- **运行** 点击绿色小虫子图标进行调试(仿真器无法下载) - -**任务2:学习Markdown语法** - -- **标题** ```# 一级 ## 二级 ### 三级``` -- **代码段** ```三个反斜杠[语言名]三个反斜杠``` -- **表格** ```|---|---|``` -- **加粗** ```**{文本}**``` -- **高亮** `一对反斜杠` -- **图片** `![描述](路径)` - -(PS: 建议把图片路径设置成./figures,以防止丢失) - -**任务3:创建一个基于RT-Thread 4.1.1的STM32F407ZG的工程** - -- **前置条件** RT-Thread Source Code版本4.1.1、ST-Link最新版 -- **路径** RT-Thread Studio -> 文件 -> 新建 -> RT项目 -> STM32 -> F4 -> F407 -> STM32F407ZG -- **代码修改** 在main.c中添加`rt_kprintf()`函数,观察打印结果 -- **编译下载** 绿色小锤子编译 -> 绿色向下箭头下载 -> 观察终端输出结果(COM选择ST-Link对应端口,波特率115200) -- **验证** 终端输出RT-Thread图形+`rt_printf()`内语句 - -## 下午学习任务:Git工具的运用 - -**环节1:环境准备** - -- **本地软件安装** 原生 Git 工具+图形化Git工具 TortoiseGit -- **远端仓库创建** 注册 Gitee 账号并新建远程仓库(右上角加号 → 新建仓库 → 填名称 → 创建) - -**环节2:身份与密钥配置** - -- **配置全局用户名** `git config --global user.name "xxx"` -- **配置全局邮箱** `git config --global user.email "xxx"` -- **生成 SSH 密钥** `ssh-keygen`(一路回车三次) -- **查看公钥内容** `cat ~/.ssh/id_rsa.pub` -- **验证密钥连通** `ssh -T git@gitee.com`(输入 yes) - -**环节3:仓库初始化与基础提交** - -- **初始化本地仓库** `git init` -- **查看当前状态** `git status` -- **拉取远端最新** `git pull origin master` -- **添加文件到暂存区** `git add README.md`(或 `git add .`) -- **提交到本地版本库** `git commit -m "初次提交"` -- **推送到远端分支** `git push origin master` - -**环节4:分支管理与 PR 协作** - -- **基于当前分支切新分支** `git checkout -b test` -- **切换已有分支** `git switch master` -- **查看远端地址** `git remote -v` -- **修改远端地址** `git remote set-url origin ` -- **Fork 上游仓库** -- **网页端发起 PR** -- **代码评审与合并** - -**环节5:日常迭代同步** - -- **切回主干** `git switch master` -- **拉取主干最新** `git pull` -- **开新分支** `git checkout -b day2` -- **全量暂存** `git add .` -- **提交日志** `git commit -m "second commit"` -- **推新分支并发 PR** `git push origin day2` \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 1f6c9476cc38a4e50a5a8724e6a128daed2236ce..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\203\221\345\205\270/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,626 +0,0 @@ -# 第四天学习 - -> RT-Thread I/O 设备框架与外设驱动开发 - -> 参考资料(RT-Thread 官方文档): -> - [I/O 设备模型](https://www.rt-thread.org/document/site/master/#/rt-thread-version/rt-thread-standard/programming-manual/device/device) -> - [PIN 设备](https://www.rt-thread.org/document/site/master/#/rt-thread-version/rt-thread-standard/programming-manual/device/pin/pin) -> - [I2C 总线设备](https://www.rt-thread.org/document/site/master/#/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/i2c) -> - [SPI 设备](https://www.rt-thread.org/document/site/master/#/rt-thread-version/rt-thread-standard/programming-manual/device/spi/spi) - ---- - -## 1. I/O 设备框架概念 - -### 1.1 RTT 分层结构 - -RT-Thread 提供了一套简单的 I/O 设备模型框架,位于硬件和应用程序之间。官方把它划分为**三层**(应用程序不属于设备模型框架本身,而是通过 I/O 设备管理接口访问设备): - -![I/O 设备模型框架](./figures/io-dev.png) - -- **I/O 设备管理层**:实现了对设备驱动程序的封装。应用程序通过它提供的**标准接口**访问底层设备,设备驱动的升级、更替不会影响上层应用。它把硬件操作相关的代码与应用解耦,降低了耦合性、复杂性,提高了可靠性。 -- **设备驱动框架层**:对同类硬件设备驱动做抽象,把不同厂家同类设备驱动的**相同部分**抽取出来,把**不同部分**留出接口,由具体驱动实现(如串口、I2C、SPI、RTC、看门狗等框架)。 -- **设备驱动层**:真正驱使硬件设备工作的程序,负责**创建并注册** I/O 设备。操作逻辑简单的设备可以不经过驱动框架层,直接注册到 I/O 设备管理器中。 - -从下到上不断抽象、屏蔽下层差异,体现了"面向对象抽象 + 面向接口编程"的思想:驱动层不同厂商的同类设备 → 对接到驱动框架层同一个父类接口(多对一)→ 再对接到 I/O 设备管理层(再次多对一)。 - -### 1.2 rt_device 结构体 - -`rt_device` 是所有设备的基类,内核对象模型之上的"设备对象",定义如下: - -```c -struct rt_device -{ - struct rt_object parent; /* 内核对象基类,含 name/type/flag/list */ - -#ifdef RT_USING_DM - struct rt_bus *bus; /* 挂载的总线 */ - rt_list_t node; /* 总线节点 */ - struct rt_driver *drv; /* 设备驱动 */ -#ifdef RT_USING_OFW - void *ofw_node; /* 设备树节点 */ -#endif - void *power_domain_unit; /* 电源域单元 */ -#ifdef RT_USING_DMA - const void *dma_ops; /* DMA 操作表 */ -#endif -#endif - - enum rt_device_class_type type; /* 设备类型 */ - rt_uint16_t flag; /* 设备参数(标志) */ - rt_uint16_t open_flag; /* 设备打开标志 */ - rt_uint8_t ref_count; /* 设备被引用次数 */ -#ifdef RT_USING_DM - rt_uint8_t master_id; /* master ID, 0 - 255 */ -#endif - rt_uint8_t device_id; /* 设备 ID, 0 - 255 */ - - /* 数据收发回调函数 */ - rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); - rt_err_t (*tx_complete)(rt_device_t dev, void *buffer); - -#ifdef RT_USING_DEVICE_OPS - const struct rt_device_ops *ops; /* 设备操作方法集合 */ -#else - /* 兼容旧接口:init/open/close/read/write/control 函数指针 */ - rt_err_t (*init) (rt_device_t dev); - rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag); - rt_err_t (*close) (rt_device_t dev); - rt_ssize_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); - rt_ssize_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); - rt_err_t (*control)(rt_device_t dev, int cmd, void *args); -#endif - -#ifdef RT_USING_POSIX_DEVIO - const struct dfs_file_ops *fops; /* POSIX 文件操作接口 */ - struct rt_wqueue wait_queue; -#endif - - rt_err_t (*readlink)(rt_device_t dev, char *buf, int len); - - void *user_data; /* 设备的私有数据 */ -}; -typedef struct rt_device *rt_device_t; -``` - -要点: - -- `parent` 继承内核对象,使设备可注册到对象容器,支持按名字查找。 -- `type` 标识设备类型;`ref_count` 记录被打开/引用的次数。 -- `ops`(开启 `RT_USING_DEVICE_OPS` 时)把操作方法封装为结构体,驱动只需实现一组函数指针。 -- `read/write` 的返回类型是 `rt_ssize_t`(有符号,负数表示错误)。 -- 开启 `RT_USING_DM` 时额外记录总线/驱动/电源域/DMA 等信息;开启 `RT_USING_POSIX_DEVIO` 时支持 POSIX 文件接口。 - -### 1.3 设备分类 - -`type` 字段由 `enum rt_device_class_type` 定义,官方列出的设备类型: - -| 类型宏 | 说明 | -| --- | --- | -| `RT_Device_Class_Char` | 字符设备(串口、按键等,逐字节非结构化传输) | -| `RT_Device_Class_Block` | 块设备(每次传输一个数据块,如 512 字节) | -| `RT_Device_Class_NetIf` | 网络接口设备 | -| `RT_Device_Class_MTD` | 内存设备(Flash 等) | -| `RT_Device_Class_RTC` | RTC 设备 | -| `RT_Device_Class_Sound` | 声音设备 | -| `RT_Device_Class_Graphic` | 图形设备(LCD 等) | -| `RT_Device_Class_I2CBUS` | I2C 总线设备 | -| `RT_Device_Class_USBDevice` | USB device 设备 | -| `RT_Device_Class_USBHost` | USB host 设备 | -| `RT_Device_Class_SPIBUS` | SPI 总线设备 | -| `RT_Device_Class_SPIDevice` | SPI 设备 | -| `RT_Device_Class_SDIO` | SDIO 设备 | -| `RT_Device_Class_Miscellaneous` | 杂类设备(如看门狗) | - -> 字符设备与块设备的区别在于**数据与系统的传输处理方式**:字符设备允许非结构化的串行传输(一次一个字节);块设备每次传输一个数据块,块大小由硬件强制决定。 - ---- - -## 2. I/O API - -### 2.1 设备管理 API(生命周期) - -设备管理 API 管理设备的**生命周期**:创建 → 注册 → 注销 → 销毁。 - -#### (1)创建 - -```c -rt_device_t rt_device_create(int type, int attach_size); -``` - -- `type`:设备类型(见 1.3)。 -- `attach_size`:用户私有数据大小。系统从动态堆分配一个设备控制块,大小为 `sizeof(struct rt_device) + attach_size`。 -- 成功返回设备句柄,失败(动态内存分配失败)返回 `RT_NULL`。 - -#### (2)注册 - -```c -rt_err_t rt_device_register(rt_device_t dev, const char *name, rt_uint16_t flags); -``` - -- `dev`:待注册的设备。 -- `name`:设备名称,最大长度由 `RT_NAME_MAX` 指定,多余部分自动截掉;名称需唯一。 -- `flags`:设备模式标志,可用"或"组合: - -```c -#define RT_DEVICE_FLAG_RDONLY 0x001 /* 只读 */ -#define RT_DEVICE_FLAG_WRONLY 0x002 /* 只写 */ -#define RT_DEVICE_FLAG_RDWR 0x003 /* 读写 */ -#define RT_DEVICE_FLAG_REMOVABLE 0x004 /* 可移除 */ -#define RT_DEVICE_FLAG_STANDALONE 0x008 /* 独立(不允许重复打开) */ -#define RT_DEVICE_FLAG_SUSPENDED 0x020 /* 挂起 */ -#define RT_DEVICE_FLAG_STREAM 0x040 /* 流模式(串口输出 \n 前自动补 \r) */ -#define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收 */ -#define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA 接收 */ -#define RT_DEVICE_FLAG_INT_TX 0x400 /* 中断发送 */ -#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA 发送 */ -``` - -注册成功后,可在 FinSH 用 `list_device` 查看所有设备(名称、类型、引用次数)。 - -#### (3)注销 - -```c -rt_err_t rt_device_unregister(rt_device_t dev); -``` - -- 将设备从设备管理器中移除,之后无法再按名字查找,但**设备控制块占用的内存不会被释放**(可重新注册)。 - -#### (4)销毁 - -```c -void rt_device_destroy(rt_device_t device); -``` - -- 真正释放设备控制块占用的内存。**销毁前必须先注销**。 - -> 辅助接口: -> - `rt_device_t rt_device_find(const char *name)`:按名字查找设备,找到返回句柄,找不到返回 `RT_NULL`。 -> - `rt_err_t rt_device_set_rx_indicate(dev, rx_ind)`:设置接收指示回调,收到数据时通知上层。 -> - `rt_err_t rt_device_set_tx_complete(dev, tx_done)`:设置发送完成回调(DMA 完成或 FIFO 写满时调用)。 - -### 2.2 设备 ops 相关 API - -开启 `RT_USING_DEVICE_OPS` 时,驱动操作函数封装在 `struct rt_device_ops` 中: - -```c -struct rt_device_ops -{ - rt_err_t (*init) (rt_device_t dev); - rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag); - rt_err_t (*close) (rt_device_t dev); - rt_ssize_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); - rt_ssize_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); - rt_err_t (*control)(rt_device_t dev, int cmd, void *args); -}; -``` - -应用层统一通过以下接口访问设备: - -| 操作 | 应用层接口 | 对应 ops 成员 | 说明 | -| --- | --- | --- | --- | -| 初始化 | `rt_device_init(dev)` | `init` | 初始化完成后 `flag` 置为激活态(`RT_DEVICE_FLAG_ACTIVATED`),已激活则不再重复初始化 | -| 打开 | `rt_device_open(dev, oflag)` | `open` | 打开设备,`ref_count` 加 1;未初始化会自动先初始化 | -| 关闭 | `rt_device_close(dev)` | `close` | 关闭设备,`ref_count` 减 1,减到 0 才真正关闭 | -| 读操作 | `rt_device_read(dev, pos, buf, size)` | `read` | 从设备读数据 | -| 写操作 | `rt_device_write(dev, pos, buf, size)` | `write` | 向设备写数据 | -| 控制 | `rt_device_control(dev, cmd, args)` | `control` | 发送控制命令 | - -要点: - -- **open/close 配对使用**:打开一次对应关闭一次,设备才会被完全关闭。 -- `open` 的 `oflag` 可取 `RT_DEVICE_OFLAG_RDONLY / WRONLY / RDWR`,以及 `RT_DEVICE_FLAG_STREAM / INT_RX / DMA_RX / INT_TX / DMA_TX` 等;**只有以 `INT_RX` 或 `DMA_RX` 方式打开,接收回调才会被调用**。 -- `read/write` 返回 `rt_ssize_t`:字符设备按字节计,块设备按"块"计;`pos` 对块设备以块为单位。 -- `control` 的通用命令宏: - -```c -#define RT_DEVICE_CTRL_RESUME 0x01 /* 恢复设备 */ -#define RT_DEVICE_CTRL_SUSPEND 0x02 /* 挂起设备 */ -#define RT_DEVICE_CTRL_CONFIG 0x03 /* 配置设备 */ -#define RT_DEVICE_CTRL_SET_INT 0x10 /* 设置中断 */ -#define RT_DEVICE_CTRL_CLR_INT 0x11 /* 清中断 */ -#define RT_DEVICE_CTRL_GET_INT 0x12 /* 获取中断状态 */ -``` - ---- - -## 3. GPIO 应用与驱动开发 - -RT-Thread 提供 **PIN 设备**(`pin`)统一管理 GPIO,使用前需在 `rtconfig.h` 中开启 `RT_USING_PIN`。 - -### 3.1 GPIO 引脚分类 - -芯片引脚一般分为 4 类:**电源、时钟、控制与 I/O**。其中 I/O 口在使用模式上又分为: - -- **GPIO**(General Purpose Input/Output,通用输入/输出); -- **功能复用 I/O**(如 SPI / I2C / UART 等)。 - -GPIO 主要特性: - -- **可编程控制中断**:中断触发模式可配置(共 5 种,见 3.4)。 -- **输入/输出模式可控制**: - - 输出模式:推挽、开漏、上拉、下拉(通过写高低电平控制外设)。 - - 输入模式:浮空、上拉、下拉、模拟(读取引脚高低电平)。 - -### 3.2 获取引脚编号 - -RT-Thread 的**引脚编号**与芯片物理引脚号不是同一概念,由 PIN 设备驱动定义,有 3 种获取方式: - -```c -/* 方式1:API 获取,名字格式为 "端口.引脚" */ -rt_base_t pin = rt_pin_get("PF.9"); - -/* 方式2:BSP 宏定义(stm32 BSP,需 #include ) */ -rt_base_t pin = GET_PIN(F, 9); - -/* 方式3:查看 PIN 驱动文件 drv_gpio.c 中 pins[] 数组的引脚编号映射 */ -``` - -```c -#include -#include /* 使用 GET_PIN 需包含,否则提示无 'F' 定义 */ - -#define LED0_PIN GET_PIN(F, 9) -``` - -### 3.3 设备管理接口 - -#### (1)设置模式 - -```c -void rt_pin_mode(rt_base_t pin, rt_uint8_t mode); -``` - -`mode` 可取以下 5 种宏(具体芯片支持需看 PIN 驱动实现): - -| 模式宏 | 值 | 说明 | -| --- | --- | --- | -| `PIN_MODE_OUTPUT` | 0x00 | 输出 | -| `PIN_MODE_INPUT` | 0x01 | 输入 | -| `PIN_MODE_INPUT_PULLUP` | 0x02 | 上拉输入 | -| `PIN_MODE_INPUT_PULLDOWN` | 0x03 | 下拉输入 | -| `PIN_MODE_OUTPUT_OD` | 0x04 | 开漏输出 | - -#### (2)设置 / 读取电平 - -```c -void rt_pin_write(rt_base_t pin, rt_ssize_t value); /* value: PIN_LOW / PIN_HIGH */ -rt_ssize_t rt_pin_read (rt_base_t pin); /* 返回 PIN_LOW / PIN_HIGH */ -``` - -示例(点灯): - -```c -rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT); -rt_pin_write(LED0_PIN, PIN_LOW); /* 低电平点亮 */ -``` - -#### (3)绑定 / 使能 / 脱离中断回调 - -```c -rt_err_t rt_pin_attach_irq(rt_base_t pin, rt_uint8_t mode, - void (*hdr)(void *args), void *args); -rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint8_t enabled); -rt_err_t rt_pin_detach_irq(rt_base_t pin); -``` - -- `mode`:中断触发方式(5 种): - -| 中断模式宏 | 值 | 说明 | -| --- | --- | --- | -| `PIN_IRQ_MODE_RISING` | 0x00 | 上升沿触发 | -| `PIN_IRQ_MODE_FALLING` | 0x01 | 下降沿触发 | -| `PIN_IRQ_MODE_RISING_FALLING` | 0x02 | 边沿触发(上升沿和下降沿都触发) | -| `PIN_IRQ_MODE_HIGH_LEVEL` | 0x03 | 高电平触发 | -| `PIN_IRQ_MODE_LOW_LEVEL` | 0x04 | 低电平触发 | - -- `hdr` / `args`:中断回调函数及其参数(回调在中断上下文执行,需短小、不能阻塞)。 -- `rt_pin_irq_enable(pin, PIN_IRQ_ENABLE)` 使能,`PIN_IRQ_DISABLE` 禁止。 -- `rt_pin_detach_irq` 脱离回调后**中断并没有关闭**,仍可再次绑定其他回调。 - -```c -void key_irq(void *args) -{ - /* 处理按键,例如释放信号量唤醒线程 */ -} - -rt_pin_mode(KEY0_PIN, PIN_MODE_INPUT_PULLUP); -rt_pin_attach_irq(KEY0_PIN, PIN_IRQ_MODE_FALLING, key_irq, RT_NULL); -rt_pin_irq_enable(KEY0_PIN, PIN_IRQ_ENABLE); -``` - ---- - -## 4. I2C 主机应用与驱动开发 - -使用前开启 `RT_USING_I2C`(及 `RT_USING_I2C_BITOPS` 软件模拟,或 BSP 硬件 I2C 驱动)。RT-Thread 把 I2C 主机虚拟为 I2C 总线设备。 - -### 4.1 I2C 协议介绍 - -- **二线制、半双工、双向同步串行总线**:`SCL`(双向时钟线)+ `SDA`(双向数据线)。与 SPI 不同,I2C 只用一根线收发数据。 -- **主从方式**:允许同时有多个主设备,每个器件有唯一地址,同一时刻只允许一个主设备;主设备产生时钟并寻址从设备。 -- 总线空闲时 SDA、SCL 均为高电平。关键时序: - - **开始条件 START**:SCL 为高电平时,主机将 SDA 拉低。 - - **停止条件 STOP**:SDA 为低电平时,主机将 SCL 拉高并保持高电平,再将 SDA 拉高。 - - **重复开始条件**:一次通信中主机可再发一个 START,用于切换读写或更换从机。 -- **从机地址**:主机发送的第一个字节,高 7 位为地址,最低位为 R/W 读写位(1 读、0 写);支持 7 位与 10 位地址模式。 -- **应答 ACK / NACK**:每传完一个字节,接收方回 ACK;读数据时由主机发 ACK,读到最后一个字节主机可发 NACK 后跟停止条件。 -- 数据传输每字节 8 位,**高位在前**,字节数不限制。 - -### 4.2 消息结构体 - -一次 I2C 传输由若干条**消息**组成,每条消息用 `struct rt_i2c_msg` 描述: - -```c -struct rt_i2c_msg -{ - rt_uint16_t addr; /* 从机地址 */ - rt_uint16_t flags; /* 读、写标志等 */ - rt_uint16_t len; /* 读写数据字节数 */ - rt_uint8_t *buf; /* 读写数据缓冲区指针 */ -}; -``` - -> 注意:`addr` **不包含读写位**,读写方向由 `flags` 控制。 - -`flags` 可用"|"组合: - -| 标志宏 | 值 | 说明 | -| --- | --- | --- | -| `RT_I2C_WR` | 0x0000 | 写标志(默认值,不能与读标志组合) | -| `RT_I2C_RD` | 1u<<0 | 读标志(不能与写标志组合) | -| `RT_I2C_ADDR_10BIT` | 1u<<2 | 10 位地址模式 | -| `RT_I2C_NO_START` | 1u<<4 | 无开始条件 | -| `RT_I2C_IGNORE_NACK` | 1u<<5 | 忽视 NACK | -| `RT_I2C_NO_READ_ACK` | 1u<<6 | 读的时候不发送 ACK | -| `RT_I2C_NO_STOP` | 1u<<7 | 不发送结束位 | - -### 4.3 标准传输流程 - -核心接口: - -```c -rt_ssize_t rt_i2c_transfer(struct rt_i2c_bus_device *bus, - struct rt_i2c_msg msgs[], - rt_uint32_t num); -``` - -- 返回"消息数组的元素个数"表示成功,否则返回错误码。 -- 一次传输可组织多条消息;需要重复开始条件时发送 2 条消息。 -- **注意**:该接口内部会调用 `rt_mutex_take()`,**不能在中断服务程序中调用**(会触发 assertion)。 - -流程:查找 I2C 总线设备 → 组织消息 → 调用 `rt_i2c_transfer()` → 校验返回值。 - -```c -#define I2C_BUS_NAME "i2c1" - -struct rt_i2c_bus_device *i2c_bus = - (struct rt_i2c_bus_device *)rt_device_find(I2C_BUS_NAME); - -/* 读 8 位寄存器:先写寄存器地址(写消息),再读数据(读消息) */ -static rt_uint8_t read_reg8(rt_uint8_t dev_addr, rt_uint8_t reg) -{ - rt_uint8_t data = 0; - struct rt_i2c_msg msgs[2]; - - msgs[0].addr = dev_addr; - msgs[0].flags = RT_I2C_WR; - msgs[0].buf = ® - msgs[0].len = 1; - - msgs[1].addr = dev_addr; - msgs[1].flags = RT_I2C_RD; - msgs[1].buf = &data; - msgs[1].len = 1; - - if (rt_i2c_transfer(i2c_bus, msgs, 2) == 2) - return data; - - return 0xFF; -} -``` - -> 更简单的封装接口(推荐,内部封装自 `rt_i2c_transfer`): -> ```c -> rt_ssize_t rt_i2c_master_send(struct rt_i2c_bus_device *bus, -> rt_uint16_t addr, rt_uint16_t flags, -> const rt_uint8_t *buf, rt_uint32_t count); -> rt_ssize_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus, -> rt_uint16_t addr, rt_uint16_t flags, -> rt_uint8_t *buf, rt_uint32_t count); -> ``` -> 小技巧:用 `RT_I2C_NO_STOP` + `RT_I2C_NO_START` 可把多次调用拼接成一条连续传输: -> ```c -> rt_i2c_master_send(bus, addr, RT_I2C_NO_STOP, prefix, prefix_len); /* 只发起始位,不发停止位 */ -> rt_i2c_master_send(bus, addr, RT_I2C_NO_START, buffer, len); /* 不发起始位,只发停止位 */ -> ``` - ---- - -## 5. SPI 主机应用与驱动开发 - -使用前开启 `RT_USING_SPI`。RT-Thread 把 SPI 主机虚拟为 SPI 总线设备。 - -### 5.1 SPI 协议介绍 - -- **四线、全双工、同步串行总线**,常用于短距离通信(EEPROM、FLASH、RTC、ADC 等): - - `MOSI`:主机输出 / 从机输入数据线; - - `MISO`:主机输入 / 从机输出数据线; - - `SCLK`:串行时钟线(主设备输出给从设备); - - `CS`:片选线(也叫 SS/CSB/CSN/EN),**低电平有效**。 -- **主从方式**:通信由主设备发起,主设备通过 CS 选择从机并提供时钟,数据经 MOSI 输出、经 MISO 输入。 -- 挂载在同一 SPI 控制器上的从设备共享 SCK/MISO/MOSI,但每个从设备 CS 引脚独立;任一时刻只有一个 CS 有效。 -- **时序模式**由 `CPOL`(时钟极性)与 `CPHA`(时钟相位)决定: - -| 模式 | CPOL(空闲电平) | CPHA(采样沿) | -| --- | --- | --- | -| Mode 0 | 0(低) | 0(首个时钟沿采样) | -| Mode 1 | 0(低) | 1(第二个时钟沿采样) | -| Mode 2 | 1(高) | 0(首个时钟沿采样) | -| Mode 3 | 1(高) | 1(第二个时钟沿采样) | - -### 5.2 注册与挂载流程 - -SPI 采用"**总线 + 设备**"两级结构:BSP 驱动先注册 SPI 总线,应用再把具体从设备挂载到总线上。 - -- **总线注册**(BSP 层完成,应用一般不直接调用): - -```c -rt_err_t rt_spi_bus_register(struct rt_spi_bus *bus, - const char *name, - const struct rt_spi_ops *ops); -``` - -- **设备挂载**(RT-Thread 5.0.0 新增,**推荐**,片选引脚基于 PIN 框架统一管理): - -```c -rt_err_t rt_spi_bus_attach_device_cspin(struct rt_spi_device *device, - const char *name, - const char *bus_name, - rt_base_t cs_pin, - void *user_data); -``` - -- **设备挂载**(5.0.0 之前旧接口,片选通过 `user_data` 传递,兼容保留): - -```c -rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device, - const char *name, - const char *bus_name, - void *user_data); -``` - -- **设备卸载**: - -```c -rt_err_t rt_spi_bus_detach_device(struct rt_spi_device *device); /* 高层接口,推荐 */ -rt_err_t rt_spi_bus_detach_device_cspin(struct rt_spi_device *device); /* 底层实现 */ -``` - -命名规则:总线命名为 `spix`,设备命名为 `spixy`,如 `spi10` 表示挂在 `spi1` 总线上的 0 号设备。 - -```c -struct rt_spi_device *spi_device = - (struct rt_spi_device *)rt_malloc(sizeof(struct rt_spi_device)); - -/* 新接口:直接指定片选引脚 */ -rt_spi_bus_attach_device_cspin(spi_device, "spi10", "spi1", GET_PIN(B, 14), RT_NULL); -/* 或:rt_spi_bus_attach_device_cspin(spi_device, "spi10", "spi1", rt_pin_get("PB.14"), RT_NULL); */ -``` - -### 5.3 控制 API 配置参数 - -```c -rt_err_t rt_spi_configure(struct rt_spi_device *device, - struct rt_spi_configuration *cfg); -``` - -配置结构体: - -```c -struct rt_spi_configuration -{ - rt_uint8_t mode; /* 模式 */ - rt_uint8_t data_width; /* 数据宽度:8 / 16 / 32 位 */ - rt_uint16_t reserved; /* 保留 */ - rt_uint32_t max_hz; /* 最大频率(Hz) */ -}; -``` - -`mode` 是"位序 + 主从 + 时序 + 片选"等标志的组合: - -```c -/* 数据传输顺序:MSB 在前 / LSB 在前 */ -#define RT_SPI_LSB (0<<2) -#define RT_SPI_MSB (1<<2) - -/* SPI 主从模式 */ -#define RT_SPI_MASTER (0<<3) -#define RT_SPI_SLAVE (1<<3) - -/* 时钟极性 CPOL(bit0)与时钟相位 CPHA(bit1)组合的 4 种时序 */ -#define RT_SPI_MODE_0 (0 | 0) /* CPOL=0, CPHA=0 */ -#define RT_SPI_MODE_1 (0 | RT_SPI_CPHA) /* CPOL=0, CPHA=1 */ -#define RT_SPI_MODE_2 (RT_SPI_CPOL | 0) /* CPOL=1, CPHA=0 */ -#define RT_SPI_MODE_3 (RT_SPI_CPOL | RT_SPI_CPHA) /* CPOL=1, CPHA=1 */ - -#define RT_SPI_CS_HIGH (1<<4) /* 片选高电平有效 */ -#define RT_SPI_NO_CS (1<<5) /* 无片选 */ -#define RT_SPI_3WIRE (1<<6) /* SI/SO 共用一根线 */ -#define RT_SPI_READY (1<<7) /* 从机拉低以暂停 */ -``` - -```c -struct rt_spi_configuration cfg; - -cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; -cfg.data_width = 8; -cfg.max_hz = 20 * 1000 * 1000; /* 20 MHz */ - -rt_spi_configure(spi_dev, &cfg); -``` - -### 5.4 常用传输函数 - -自定义传输的核心是 `struct rt_spi_message`,可组成**单向链表**一次传输多段: - -```c -struct rt_spi_message -{ - const void *send_buf; /* 发送缓冲区指针(RT_NULL 表示只接收) */ - void *recv_buf; /* 接收缓冲区指针(RT_NULL 表示只发送) */ - rt_size_t length; /* 发送/接收数据长度(单位:word) */ - struct rt_spi_message *next; /* 下一条消息 */ - unsigned cs_take : 1; /* 传输前片选选中 */ - unsigned cs_release : 1; /* 传输后释放片选 */ -}; -``` - -> 首条消息 `cs_take=1`、末条消息 `cs_release=1`;`send_buf`/`recv_buf` 非空时其可用空间不得小于 `length`。 - -常用传输函数: - -| 函数 | 返回值 | 说明 | -| --- | --- | --- | -| `rt_spi_transfer_message(dev, msg)` | `struct rt_spi_message *` | 自定义传输(消息链表);成功返回 `RT_NULL`,失败返回剩余未发送消息指针 | -| `rt_spi_transfer(dev, tx, rx, size)` | `rt_ssize_t` | 一次全双工收发(片选选中→收发→释放) | -| `rt_spi_send(dev, buf, size)` | `rt_size_t` | 只发送,忽略接收 | -| `rt_spi_recv(dev, buf, size)` | `rt_size_t` | 只接收(主设备发 0xFF 产生时钟) | -| `rt_spi_send_then_send(dev, tx1, n1, tx2, n2)` | `rt_err_t` | 连续两次发送,中间片选不释放 | -| `rt_spi_send_then_recv(dev, tx, n1, rx, n2)` | `rt_err_t` | 先发送后接收,中间片选不释放 | -| `rt_spi_sendrecv8(dev, senddata, recvdata)` | `rt_err_t` | 收发 1 个字节 | -| `rt_spi_sendrecv16(dev, senddata, recvdata)` | `rt_err_t` | 收发 2 个字节 | - -> **注意**:SPI 数据传输相关接口内部调用 `rt_mutex_take()`,**不能在中断服务程序中调用**。 - -```c -/* 读寄存器示例:先发命令/地址,再读回数据(用 rt_spi_transfer_message 组织两条消息) */ -#define W25Q_SPI_DEVICE_NAME "spi10" - -struct rt_spi_device *spi_dev = - (struct rt_spi_device *)rt_device_find(W25Q_SPI_DEVICE_NAME); - -rt_uint8_t w25x_read_id = 0x90; /* 命令 */ -rt_uint8_t id[5] = {0}; -struct rt_spi_message msg1, msg2; - -msg1.send_buf = &w25x_read_id; -msg1.recv_buf = RT_NULL; -msg1.length = 1; -msg1.cs_take = 1; -msg1.cs_release = 0; -msg1.next = &msg2; - -msg2.send_buf = RT_NULL; -msg2.recv_buf = id; -msg2.length = 5; -msg2.cs_take = 0; -msg2.cs_release = 1; -msg2.next = RT_NULL; - -rt_spi_transfer_message(spi_dev, &msg1); - -/* 等价简写:rt_spi_send_then_recv(spi_dev, &w25x_read_id, 1, id, 5); */ -``` - -> 特殊场景(需独占总线一段时间、保持片选有效):先用 `rt_spi_take_bus()` 获取总线,`rt_spi_take()` 选中片选,期间用 `rt_spi_transfer_message()` 传输(此时 `cs_take`/`cs_release` 均置 0),结束用 `rt_spi_release()` 释放片选、`rt_spi_release_bus()` 释放总线。 diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/readme.md" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/readme.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task01.c" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task01.c" deleted file mode 100644 index 4079065cb84230dc08bdba866e0509147c0c149a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task01.c" +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 Vincent the first version - */ - -#include - -#define THREAD_PRIORITY 25 -#define THREAD_STACK_SIZE 1*1024 -#define THREAD_TIMESLICE 5 - -/* 指向信号量的指针 */ -static rt_sem_t dynamic_sem = RT_NULL; -static rt_thread_t tid1, tid2, tid3, tid4, tid5 = RT_NULL; - -/* 当前已用车位数 */ -static volatile rt_uint8_t used_spaces = 0; - -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif - -static void car1_entry(void *parameter) -{ - rt_uint8_t car_id = 1; - while (1) - { - /* 申请车位,2秒超时 */ - rt_err_t result = rt_sem_take(dynamic_sem, rt_tick_from_millisecond(2000)); - - if (result == RT_EOK) - { - /* 获得车位 */ - used_spaces++; - rt_kprintf("car%d entered, used spaces: %d\n", car_id, used_spaces); - - /* 停留 3~5 秒(模拟) */ - rt_thread_mdelay(3000); - - /* 离开,释放车位 */ - used_spaces--; - rt_kprintf("car%d left\n", car_id); - rt_sem_release(dynamic_sem); - } - else - { - /* 超时 */ - rt_kprintf("car%d waiting for a space\n", car_id); - rt_thread_mdelay(500); // 稍后再申请 - } - } -} - -static void car2_entry(void *parameter) -{ - rt_uint8_t car_id = 2; - while (1) - { - /* 申请车位,2秒超时 */ - rt_err_t result = rt_sem_take(dynamic_sem, rt_tick_from_millisecond(2000)); - - if (result == RT_EOK) - { - /* 获得车位 */ - used_spaces++; - rt_kprintf("car%d entered, used spaces: %d\n", car_id, used_spaces); - - /* 停留 3~5 秒(模拟) */ - rt_thread_mdelay(3000); - - /* 离开,释放车位 */ - used_spaces--; - rt_kprintf("car%d left\n", car_id); - rt_sem_release(dynamic_sem); - } - else - { - /* 超时 */ - rt_kprintf("car%d waiting for a space\n", car_id); - rt_thread_mdelay(500); // 稍后再申请 - } - } -} - -static void car3_entry(void *parameter) -{ - rt_uint8_t car_id = 3; - while (1) - { - /* 申请车位,2秒超时 */ - rt_err_t result = rt_sem_take(dynamic_sem, rt_tick_from_millisecond(2000)); - - if (result == RT_EOK) - { - /* 获得车位 */ - used_spaces++; - rt_kprintf("car%d entered, used spaces: %d\n", car_id, used_spaces); - - /* 停留 3~5 秒(模拟) */ - rt_thread_mdelay(3000); - - /* 离开,释放车位 */ - used_spaces--; - rt_kprintf("car%d left\n", car_id); - rt_sem_release(dynamic_sem); - } - else - { - /* 超时 */ - rt_kprintf("car%d waiting for a space\n", car_id); - rt_thread_mdelay(500); // 稍后再申请 - } - } -} - -static void car4_entry(void *parameter) -{ - rt_uint8_t car_id = 4; - while (1) - { - /* 申请车位,2秒超时 */ - rt_err_t result = rt_sem_take(dynamic_sem, rt_tick_from_millisecond(2000)); - - if (result == RT_EOK) - { - /* 获得车位 */ - used_spaces++; - rt_kprintf("car%d entered, used spaces: %d\n", car_id, used_spaces); - - /* 停留 3~5 秒(模拟) */ - HAL_Delay(3000); - - /* 离开,释放车位 */ - used_spaces--; - rt_kprintf("car%d left\n", car_id); - rt_sem_release(dynamic_sem); - } - else - { - /* 超时 */ - rt_kprintf("car%d waiting for a space\n", car_id); - rt_thread_mdelay(500); // 稍后再申请 - } - } -} - -static void car5_entry(void *parameter) -{ - rt_uint8_t car_id = 5; - while (1) - { - /* 申请车位,2秒超时 */ - rt_err_t result = rt_sem_take(dynamic_sem, rt_tick_from_millisecond(2000)); - - if (result == RT_EOK) - { - /* 获得车位 */ - used_spaces++; - rt_kprintf("car%d entered, used spaces: %d\n", car_id, used_spaces); - - /* 停留 3~5 秒(模拟) */ - rt_thread_mdelay(3000); - - /* 离开,释放车位 */ - used_spaces--; - rt_kprintf("car%d left\n", car_id); - rt_sem_release(dynamic_sem); - } - else - { - /* 超时 */ - rt_kprintf("car%d waiting for a space\n", car_id); - rt_thread_mdelay(500); // 稍后再申请 - } - } -} - -/* 信号量示例的初始化 */ -int parking_demo() -{ - /* 创建一个动态信号量,初始值是0 */ - dynamic_sem = rt_sem_create("car_sem", 3, RT_IPC_FLAG_PRIO); - if (dynamic_sem == RT_NULL) - { - rt_kprintf("create car semaphore failed.\n"); - return -1; - } - else - { - rt_kprintf("create done. used spaces = %d\r\n",used_spaces); - } - /* 动态创建五个线程,用来管理车辆 */ - tid1 = rt_thread_create("car1", - car1_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, THREAD_TIMESLICE); - tid2 = rt_thread_create("car2", - car2_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY+1, THREAD_TIMESLICE); - tid3 = rt_thread_create("car3", - car3_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY+2, THREAD_TIMESLICE); - tid4 = rt_thread_create("car4", - car4_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY+3, THREAD_TIMESLICE); - tid5 = rt_thread_create("car5", - car5_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY+4, THREAD_TIMESLICE); - - if (tid1 != RT_NULL) - rt_thread_startup(tid1); - if (tid2 != RT_NULL) - rt_thread_startup(tid2); - if (tid3 != RT_NULL) - rt_thread_startup(tid3); - if (tid4 != RT_NULL) - rt_thread_startup(tid4); - if (tid5 != RT_NULL) - rt_thread_startup(tid5); - - return 0; -} - -/* 导出到 msh 命令列表中 */ -MSH_CMD_EXPORT(parking_demo, parking demo); - diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task02.c" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task02.c" deleted file mode 100644 index fe3c4cec22df38f48335f3678e3a7f110683d33f..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task02.c" +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 Vincent the first version - */ - -#include - -#define THREAD_PRIORITY 25 -#define THREAD_STACK_SIZE 1*1024 -#define THREAD_TIMESLICE 5 - -/* 指向互斥量的指针 */ -static rt_mutex_t dynamic_mutex = RT_NULL; -static rt_thread_t tid1, tid2 = RT_NULL; - -/* 剩余票数 */ -static volatile rt_uint8_t sold_ticket = 20; -static rt_uint8_t A_ticket[20] = {0}; -static rt_uint8_t B_ticket[20] = {0}; - -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif - -static void A_entry(void *parameter) -{ - static rt_uint8_t count = 0; - while (1) - { - if (rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER) == RT_EOK) - { - count++; - if(sold_ticket > 0) - { - /* 记录售出的票号,并将余票-1 */ - A_ticket[count] = sold_ticket; - rt_kprintf("window A sold ticket %d\r\n", sold_ticket); - sold_ticket--; - }else - { - sold_ticket = 0; - rt_kprintf("all tickets sold\r\n remaining tickets: 0\r\n"); - } - rt_mutex_release(dynamic_mutex); - rt_thread_mdelay(100); - }else { - rt_kprintf("take mutex error!\r\n"); - } - } -} - -static void B_entry(void *parameter) -{ - static rt_uint8_t count = 0; - while (1) - { - if (rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER) == RT_EOK) - { - count++; - if(sold_ticket > 0) - { - /* 记录售出的票号,并将余票-1 */ - B_ticket[count] = sold_ticket; - rt_kprintf("window B sold ticket %d\r\n", sold_ticket); - sold_ticket--; - }else - { - sold_ticket = 0; - rt_kprintf("all tickets sold\r\n remaining tickets: 0\r\n"); - } - rt_mutex_release(dynamic_mutex); - rt_thread_mdelay(100); - }else { - rt_kprintf("take mutex error!\r\n"); - } -// count++; -// if(sold_ticket > 0) -// { -// /* 记录售出的票号,并将余票-1 */ -// A_ticket[count] = sold_ticket; -// rt_kprintf("window B sold ticket %d\r\n", sold_ticket); -// sold_ticket--; -// rt_thread_mdelay(100); -// } -// else -// { -// sold_ticket = 0; -// rt_kprintf("all tickets sold\r\n remaining tickets: 0"); -// rt_thread_mdelay(100); -// } - } -} - -/* 信号量示例的初始化 */ -int ticket_demo() -{ - /* 创建一个动态互斥量 */ - dynamic_mutex = rt_mutex_create("tickets", RT_IPC_FLAG_PRIO); - if (dynamic_mutex == RT_NULL) - { - rt_kprintf("create dynamic tickets failed.\n"); - return -1; - } - - /* 动态创建两个线程,分别代表窗口A和B */ - tid1 = rt_thread_create("A", - A_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, THREAD_TIMESLICE); - tid2 = rt_thread_create("B", - B_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY+1, THREAD_TIMESLICE); - if (tid1 != RT_NULL) - rt_thread_startup(tid1); - if (tid2 != RT_NULL) - rt_thread_startup(tid2); - rt_kprintf("create OK!\r\n"); - return 0; -} - -/* 导出到 msh 命令列表中 */ -MSH_CMD_EXPORT(ticket_demo, ticket demo); - diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task03.c" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task03.c" deleted file mode 100644 index ed3270276ac080c15c52090d0896c552add500e9..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task03.c" +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 Vincent the first version - */ -#include - -#define THREAD_PRIORITY 25 -#define THREAD_STACK_SIZE 1*1024 -#define THREAD_TIMESLICE 5 - -/* 定义三个事件位 */ -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) - -/* 事件控制块 */ -static struct rt_event event; -static rt_thread_t tid1, tid2, tid3, tid4 = RT_NULL; - -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif - -/* 线程 network 入口函数 */ -static void network_entry(void *param) -{ - rt_thread_mdelay(1000); - rt_kprintf("network: send event0\n"); - rt_event_send(&event, EVENT_NET_READY); - rt_thread_mdelay(200); -} - -/* 线程 sensor 入口函数 */ -static void sensor_entry(void *param) -{ - rt_thread_mdelay(2000); - rt_kprintf("sensor: send event1\n"); - rt_event_send(&event, EVENT_SENSOR_READY); - rt_thread_mdelay(200); -} - -/* 线程 storage 入口函数 */ -static void storage_entry(void *param) -{ - rt_thread_mdelay(5000); - rt_kprintf("storage: send event2\n"); - rt_event_send(&event, EVENT_STORAGE_READY); - rt_thread_mdelay(200); -} - -/* 线程 business 入口函数 */ -static void business_entry(void *param) -{ - while(1) - { - rt_uint32_t recv_event; - rt_err_t result = rt_event_recv(&event, (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY), - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - rt_tick_from_millisecond(3000), &recv_event) ; - /* 初始化完成 */ - if (result == RT_EOK) - { - rt_kprintf("all modules are ready\r\n"); - rt_kprintf("business task started\r\n"); - rt_event_detach(&event); - rt_kprintf("init event leave.\n"); - } - /* 超时 */ - else if (result == -RT_ETIMEOUT) - { - /* 只打印未就绪的模块 */ - rt_kprintf("wait modules timeout, "); - - if (!(recv_event & EVENT_STORAGE_READY)) - rt_kprintf("STORAGE is not ready\r\n"); - else if (!(recv_event & EVENT_NET_READY)) - rt_kprintf("NET is not ready\r\n"); - else if (!(recv_event & EVENT_SENSOR_READY)) - rt_kprintf("SENSOR is not ready\r\n"); - rt_kprintf("\n"); - /* 继续while循环,再次等待 */ - } - } -} - -int startup_demo(void) -{ - rt_err_t result; - - /* 初始化事件对象 */ - result = rt_event_init(&event, "event", RT_IPC_FLAG_PRIO); - if (result != RT_EOK) - { - rt_kprintf("init event failed.\n"); - return -1; - }else { - rt_kprintf("init event OK.\r\n"); - } - /* 动态创建四个线程,并且启动 */ - tid1 = rt_thread_create("network", - network_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid1 != RT_NULL) - rt_thread_startup(tid1); - - tid2 = rt_thread_create("sensor", - sensor_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid2 != RT_NULL) - rt_thread_startup(tid2); - - tid3 = rt_thread_create("storage", - storage_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid3 != RT_NULL) - rt_thread_startup(tid3); - - tid4 = rt_thread_create("business", - business_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid4 != RT_NULL) - rt_thread_startup(tid4); - - return 0; -} - -/* 导出到 msh 命令列表中 */ -MSH_CMD_EXPORT(startup_demo, startup demo); diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task04.c" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task04.c" deleted file mode 100644 index 8aa2456e49d8a690fdd80b610dd1b3799d0a6961..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task04.c" +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 Vincent the first version - */ -#include - -#define THREAD_PRIORITY 25 -#define THREAD_STACK_SIZE 512 -#define THREAD_TIMESLICE 5 -#define MSG_QUEUE_SIZE 5 - -/* 定义消息结束字段ID */ -#define MSG_TYPE_END 1 - -/* 区域定义 */ -#define REGION_EAST 1 /* 华东 */ -#define REGION_SOUTH 2 /* 华南 */ -#define REGION_NORTH 3 /* 华北 */ - -/* 快递消息结构 */ -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -/* 统计变量 */ -static volatile rt_uint32_t total_packages = 0; -static volatile rt_uint32_t east_count = 0; -static volatile rt_uint32_t south_count = 0; -static volatile rt_uint32_t north_count = 0; -static volatile rt_uint32_t total_weight = 0; -static volatile rt_uint32_t send_failed = 0; - -/* 快递编号起始值 */ -#define SENDER1_START_ID 1000 -#define SENDER2_START_ID 2000 - -/* 消息队列控制块 */ -static rt_mq_t mq = RT_NULL; - -static rt_thread_t tid1, tid2, tid3 = RT_NULL; - -/* 线程 收件线程1 入口函数 */ -static void rec1_entry(void *parameter) -{ - rt_uint8_t i; - struct package_msg msg; - - /* 产生固定的5条快递消息 */ - for (i = 0; i < 5; i++) - { - msg.id = SENDER1_START_ID + i + 1; /* 1001, 1002, 1003, 1004, 1005 */ - msg.region = (i % 3) + 1; /* 1, 2, 3 循环 */ - msg.weight = 800 + i * 100; /* 800, 900, 1000, 1100, 1200 */ - - /* 发送消息,等待直到队列有空位 */ - if (rt_mq_send_wait(mq, &msg, sizeof(msg), RT_WAITING_FOREVER) != RT_EOK) - { - send_failed++; - rt_kprintf("sender1: send failed, id=%d\n", msg.id); - } - else - { - rt_kprintf("sender1: send package %d, region=%d, weight=%d\n", - msg.id, msg.region, msg.weight); - } - - rt_thread_mdelay(100); /* 模拟产生快递的间隔 */ - } - - /* 发送结束消息 */ - msg.id = MSG_TYPE_END; /* 用 id 字段标记结束消息 */ - rt_mq_send_wait(mq, &msg, sizeof(msg), RT_WAITING_FOREVER); - rt_kprintf("rec1: finished\r\n"); -} - -/* 线程 收件线程2 入口 */ -static void rec2_entry(void *parameter) -{ - rt_uint8_t i; - struct package_msg msg; - - /* 产生固定的5条快递消息 */ - for (i = 0; i < 5; i++) - { - msg.id = SENDER1_START_ID + i + 1; /* 2001, 2002, 2003, 2004, 2005 */ - msg.region = (i % 3) + 1; /* 1, 2, 3 循环 */ - msg.weight = 800 + i * 100; /* 800, 900, 1000, 1100, 1200 */ - - /* 发送消息,等待直到队列有空位 */ - if (rt_mq_send_wait(mq, &msg, sizeof(msg), RT_WAITING_FOREVER) != RT_EOK) - { - send_failed++; - rt_kprintf("sender1: send failed, id=%d\n", msg.id); - } - else - { - rt_kprintf("sender1: send package %d, region=%d, weight=%d\n", - msg.id, msg.region, msg.weight); - } - - rt_thread_mdelay(100); /* 模拟产生快递的间隔 */ - } - - /* 发送结束消息 */ - msg.id = MSG_TYPE_END; /* 用 id 字段标记结束消息 */ - rt_mq_send_wait(mq, &msg, sizeof(msg), RT_WAITING_FOREVER); - rt_kprintf("rec2: finished\r\n"); -} - -/* 线程 3 分拣线程 入口 */ -static void select3_entry(void *parameter) -{ - struct package_msg msg; - rt_uint8_t end_count = 0; - - while (1) - { - /* 接收消息,永久等待 */ - if (rt_mq_recv(mq, &msg, sizeof(msg), RT_WAITING_FOREVER) != RT_EOK) - { - continue; - } - - /* 检查是否是结束消息 */ - if (msg.id == MSG_TYPE_END) - { - end_count++; - rt_kprintf("sorter: received end signal (%d/2)\n", end_count); - - /* 两个发件线程都结束了 */ - if (end_count >= 2) - { - break; - } - continue; - } - - /* 分拣处理 */ - total_packages++; - total_weight += msg.weight; - - switch (msg.region) - { - case REGION_EAST: - east_count++; - rt_kprintf("package %d -> east, weight: %d g\n", msg.id, msg.weight); - break; - case REGION_SOUTH: - south_count++; - rt_kprintf("package %d -> south, weight: %d g\n", msg.id, msg.weight); - break; - case REGION_NORTH: - north_count++; - rt_kprintf("package %d -> north, weight: %d g\n", msg.id, msg.weight); - break; - default: - rt_kprintf("package %d -> unknown region, weight: %d g\n", msg.id, msg.weight); - break; - } - } - - /* 打印统计结果 */ - rt_kprintf("\n========== Sorting Report ==========\n"); - rt_kprintf("total packages: %d\n", total_packages); - rt_kprintf("east: %d, south: %d, north: %d\n", east_count, south_count, north_count); - rt_kprintf("total weight: %d g\n", total_weight); - rt_kprintf("send failed: %d\n", send_failed); -} - -/* 消息队列示例的初始化 */ -int package_demo(void) -{ - /* 初始化消息队列 */ - mq = rt_mq_create("package", sizeof(struct package_msg), MSG_QUEUE_SIZE, RT_IPC_FLAG_FIFO); - if (mq == RT_NULL) - { - rt_kprintf("create mq failed!\n"); - return -1; - } - - /* 重置统计变量 */ - total_packages = 0; - east_count = 0; - south_count = 0; - north_count = 0; - total_weight = 0; - send_failed = 0; - - /* 动态创建两个收件线程,并且启动 */ - tid1 = rt_thread_create("rec1", - rec1_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid1 != RT_NULL) - rt_thread_startup(tid1); - - tid2 = rt_thread_create("rec2", - rec2_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid2 != RT_NULL) - rt_thread_startup(tid2); - - tid3 = rt_thread_create("select3", - select3_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid3 != RT_NULL) - rt_thread_startup(tid3); - - return 0; -} - -/* 导出到 msh 命令列表中 */ -MSH_CMD_EXPORT(package_demo, package demo); diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\2543\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\2543\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index a95968c4c44b3e6feb5cfbfee0717a6493e078e3..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\2543\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,233 +0,0 @@ -# RT-Thread 线程间同步与通信课后作业 - -## 一、作业要求 - -要求: - -1. 不得直接复制 `kernel_samples` 示例后只修改变量名和打印内容。 -2. 每个练习至少创建两个线程。 -3. 所有线程和 IPC 对象都要检查创建结果。 -4. 程序需要通过 MSH 命令启动。 -5. 提交理论总结与实验报告(`README.md`)+ 源代码(`*.c`)。 - ---- - -## 练习一:停车场车位管理 - -### 场景 - -一个停车场只有 3 个车位,同时有 5 辆车尝试进入。车位属于有限资源,车辆只有获得车位后才能进入。 - -### 功能要求 - -1. 创建一个初始值为 3 的计数信号量,表示剩余车位。 -2. 创建 5 个车辆线程。 -3. 每辆车到达后申请一个车位。 -4. 获得车位后打印车辆编号和进入信息。 -5. 车辆停留一段时间后离开,并释放车位。 -6. 未在 2 秒内获得车位的车辆打印等待超时信息,稍后重新申请。 -7. 任意时刻进入停车场的车辆数不能超过 3。 - -### 示例输出 - -```text -car1 entered, used spaces: 1 -car2 entered, used spaces: 2 -car3 entered, used spaces: 3 -car4 waiting for a space -car2 left -car4 entered, used spaces: 3 -``` - -### 思考题 - -为什么这里使用计数信号量,而不是互斥量? - -> 互斥量是个二值量,只能管理能不能进,无法满足计数和统计还有多少的要求 - ---- - -## 练习二:多窗口售票系统 - -### 场景 - -两个售票窗口同时出售同一批车票。系统初始有 20 张票,每次只能由一个窗口修改剩余票数。 - -### 功能要求 - -1. 创建两个售票线程,分别代表窗口 A 和窗口 B。 -2. 使用一个全局变量保存剩余票数。 -3. 每个窗口循环完成以下操作: - - 检查是否还有余票; - - 将余票减 1; - - 记录售出的票号; - - 打印售票结果。 -4. 使用互斥量保护完整的售票过程。 -5. 售完后两个线程自动退出。 -6. 最终必须满足: - - 20 张票全部售出; - - 不出现相同票号; - - 剩余票数不能小于 0。 - -### 示例输出 - -```text -window A sold ticket 20 -window B sold ticket 19 -window B sold ticket 18 -... -all tickets sold -remaining tickets: 0 -``` - -### 对比实验 - -先暂时不使用互斥量,并在“读取余票”和“修改余票”之间加入短暂延时,观察是否会出现重复售票或余票错误。 - -随后加入互斥量并再次运行,对比两次结果。 - -> 结果没有任何区别!!! - -### 思考题 - -为什么只给 `ticket_count--` 加锁仍可能不安全? - -> - ---- - -## 练习三:系统启动条件检查 - -### 场景 - -设备启动前需要完成三个独立模块的初始化: - -- 网络模块初始化; -- 传感器模块初始化; -- 存储模块初始化。 - -只有三个模块全部准备完成,主业务线程才能启动。 - -PS:只是举例,模拟即可。 - -### 功能要求 - -定义三个事件位: - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -``` - -创建以下线程: - -- 网络初始化线程; -- 传感器初始化线程; -- 存储初始化线程; -- 主业务线程。 - -每个初始化线程经过不同时间的延时后,发送对应的就绪事件。 - -主业务线程必须使用 AND 模式等待三个事件全部到达,然后打印: - -```text -all modules are ready -business task started -``` - -另外增加一次超时测试: - -- 将存储模块初始化时间设置为 5 秒; -- 主业务线程最多等待 3 秒; -- 超时后打印尚未完成的模块,并再次等待。 - -### 示例输出 - -```text -network ready -sensor ready -wait modules timeout, storage is not ready -storage ready -all modules are ready -business task started -``` - -### 思考题 - -如果用三个独立信号量实现这个功能,与使用事件集相比有什么区别? - -> **事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现多个条件位的组合,AND和OR都可;** -> -> **信号量适合计数,拿到一个资源就能继续。** -> -> **本题是多模块就绪的组合条件,所以事件集是更自然的选择。** - ---- - -## 练习四:快递分拣中心 - -### 场景 - -两个收件线程不断产生快递信息,一个分拣线程从消息队列中取出快递并按照目的地区域分类。 - -### 消息结构 - -```c -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -``` - -其中: - -```text -region = 1:华东 -region = 2:华南 -region = 3:华北 -``` - -### 功能要求 - -1. 创建一个消息队列,队列容量为 5 条消息。 -2. 创建两个发件线程,每个线程产生 5 条快递消息。 -3. 快递编号不得重复。 -4. 分拣线程接收消息,并打印快递编号、区域和重量。 -5. 分拣完成后统计: - - 总快递数量; - - 各区域快递数量; - - 所有快递的总重量。 -6. 当队列已满时,发送线程不得崩溃,应记录发送失败次数。 -7. 两个发送线程完成后,向分拣线程发送一条结束消息。 -8. 分拣线程收到结束消息后打印统计结果并退出。 - -### 示例输出 - -```text -package 1001 -> east, weight: 1200 g -package 2001 -> south, weight: 850 g -package 1002 -> north, weight: 960 g -... -total packages: 10 -east: 4, south: 3, north: 3 -total weight: 10250 g -send failed: 0 -``` - ---- - -## 二、MSH 命令要求 - -每个练习导出一个独立命令: - -```text -parking_demo -ticket_demo -startup_demo -package_demo -``` - -重复执行命令时,不得因同名线程或 IPC 对象残留而发生异常。可以在任务结束后主动释放资源,也可以检测任务是否仍在运行并拒绝重复启动。 diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index cbbb1dd0d278449670f8dab622035f8ebba1c4c3..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,96 +0,0 @@ -# 【DAY2】RT-Thread 启动流程、内核基础与多线程编程 - -- **目标平台**:STM32F407 星火号(`stm32f407-rtspark`) -- **提交仓库**:`https://gitee.com/rtthread/rsoc-rtt` -- **作业交付物**:理论总结与实验报告(`README.md`)+ 可编译源码(`*.c`)通过 Gitee PR 提交。 - -## Part 1:理论知识梳理文档(写入作业目录 `README.md`) - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -- **启动路径**:详细画出/写出从上电复位 `Reset_Handler` 到进入 `main()` 的完整调用链。 - - > startup_stm32f407xx.s(`Reset_Handler` ) --> `SystemInit`() - > - > ​ | - > - > ​ -----------------------------> entry() --> rtthread_startup() --> 进行`rt_hw_board_init` 等初始化 --> `main()` - -- **自动初始化原理**:解释 `INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` 的底层实现原理。 - - > ​ 宏 `INIT_XXX_EXPORT(fn)` 所做的事情,本质上就是告诉编译器:"把函数 `fn` 的**地址**作为一个 4 字节的常量,塞到名叫 `.rti_fn.` 的段里去";剩下的交给 ld:在链接时把所有 `.rti_fn.*` 段按字符串排序后连成一排(所以 level 字符串的字典序 = 内存顺序 = 调用顺序);最后 `rt_components_board_init` / `rt_components_init` 用 "指针 for 循环" 从 `&哨兵A` 走到 `&哨兵B` / 从 `&哨兵B` 走到 `&哨兵C`,对遍历到的每一个 4 字节函数指针执行 `(*ptr)()` - -### 2. 内核基础与对象模型 - -- **对象容器与控制块**:简述 RT-Thread 面向对象的设计思想,线程控制块(`struct rt_thread`)中关键成员(如 `stat`、`current_priority`、`init_tick`、`sp`)的作用。 - - > RT-Thread 采用「**继承 + 容器数组 + 双向链表**」的轻量面向对象设计(纯 C,用"结构体首成员"语义模拟继承),核心由 3 层构成: - > - > 1. 基类 `struct rt_object`(所有内核对象的公共头部) - > 2. 类型枚举 `enum rt_object_class_type`(区分对象种类 + 标记"静态 vs 动态") - > 3. 对象容器 `_object_container[]` 数组(按类型一条链表,集中管理所有同类对象) - > 4. 对象生命周期(创建 → 入容器 → 销毁 → 出容器) - > - > 线程控制块 `struct rt_thread` 关键成员的作用: - > - > 1. `sp` — 栈指针(stack pointer)当前保存位置; - > - > - 线程**不运行时**,`sp` 保存这个线程**上次被切走时栈顶的位置**(也就是 r4~r11 + 异常帧被 push 之后的栈指针值)。 - > - > - 下次再调度回它时,PendSV 从 `thread->sp` 恢复 r4~r11、设置 PSP、异常返回就回到它被切走那条指令之后。 - > - > - 线程第一次运行之前,`sp` 被 `rt_hw_stack_init` 预置为"布置好的假栈帧"的底部——这就是我们前面 8/18 回答里解释的"首次切换为什么能直接跑到 main_thread_entry"。 - > - > 2. `stat` — 线程状态 + 若干附加标志位(共享一个 `rt_uint8_t`); - > - > 3. `current_priority` — 当前有效优先级(数字越小优先级越高); - > - > - 调度器 `_scheduler_get_highest_priority_thread()` 找到就绪表里优先级数值**最小**的那一条链表,就是最高优先级队列,取 `.next` 作为 to_thread([scheduler.c#L170-L191](file:///D:/Complie/RT-ThreadStudio/workspace/01_hello_stm32/rt-thread/src/scheduler.c#L170-L191))。 - > - > - 配合 `number_mask`、`high_mask`([rtdef.h#L665-L669](file:///D:/Complie/RT-ThreadStudio/workspace/01_hello_stm32/rt-thread/include/rtdef.h#L665-L669))用于在位图 `rt_thread_ready_priority_group` 里快速查找优先级(用 `__rt_ffs` 找第一个置 1 的 bit,O(1) 调度)。 - > - > - 对互斥量持有者做「优先级继承」时,被提升的就是这个 `current_priority`;释放锁后用 `init_priority` 还原。 - > - > 4. `init_tick` — 初始时间片配额 & `remaining_tick` — 当前剩余时间片; - > - > - 每个 SysTick tick 进入 `rt_tick_increase`,`remaining_tick--`。 - > - 当 `remaining_tick == 0`,说明当前线程这轮时间片用完了:**重置 `remaining_tick = init_tick`**,同时给 `stat` 打上 `RT_THREAD_STAT_YIELD` 标记,再调用 `rt_schedule()` 让同优先级的下一个线程先跑。 - -- **内存分配方式**:对比静态对象与动态对象的区别(分配位置、生命周期、释放机制),分析为什么某些高可靠场景强制使用静态线程。 - - > | 维度 | 静态对象(`rt_thread_init`) | 动态对象(`rt_thread_create`) | - > | :------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | - > | **控制块位置** | 全局 `struct rt_thread xxx` 变量,或局部 `static` → 在 **.bss / .data 段**(编译期预留的 RAM 静态区,地址在 link.lds 里就确定了) | `rt_object_allocate` 内部用 `RT_KERNEL_MALLOC(sizeof(struct rt_thread))` → 位于 **堆(heap)**,[object.c#L200](file:///D:/Complie/RT-ThreadStudio/workspace/01_hello_stm32/rt-thread/src/object.c#L200) | - > | **栈位置** | `ALIGN(8) static rt_uint8_t main_stack[N];` → 同样在 **.bss**(见 [components.c#L169-L171](file:///D:/Complie/RT-ThreadStudio/workspace/01_hello_stm32/rt-thread/src/components.c#L169-L171)) | `RT_KERNEL_MALLOC(stack_size)` → 位于**堆**([thread.c#L459-L461](file:///D:/Complie/RT-ThreadStudio/workspace/01_hello_stm32/rt-thread/src/thread.c#L459-L461)) | - > | **type 字段** | 初始化时 `object->type = type | RT_Object_Class_Static`(最高位=1) → [object.c#L116](file:///D:/Complie/RT-ThreadStudio/workspace/01_hello_stm32/rt-thread/src/object.c#L116) | 不 OR 0x80 → [object.c#L204](file:///D:/Complie/RT-ThreadStudio/workspace/01_hello_stm32/rt-thread/src/object.c#L204) | - > | **生命周期** | 与声明范围一致:`static` 全局对象 = 程序全程。**不会被释放,也不可能释放失败**。 | 用户调用 `rt_thread_delete` 之后,不会立刻释放。线程先变为 CLOSE,挂到 `rt_thread_defunct` 队列,idle 线程里的 `rt_defunct_execute` 统一做 `RT_KERNEL_FREE(控制块) + RT_KERNEL_FREE(栈)` → [thread.c#L95-L113](file:///D:/Complie/RT-ThreadStudio/workspace/01_hello_stm32/rt-thread/src/thread.c#L95-L113) + [idle.c#L129-L146](file:///D:/Complie/RT-ThreadStudio/workspace/01_hello_stm32/rt-thread/src/idle.c#L129-L146) | - > | **释放函数** | `rt_thread_detach` → 只做两步:① 如果还在调度链里则摘链;② 调用 `rt_object_detach` 从容器 `object_list` 摘链。**绝对不动内存**,由声明时的作用域自动失效(全局则永远持有)。[thread.c#L405-L430](file:///D:/Complie/RT-ThreadStudio/workspace/01_hello_stm32/rt-thread/src/thread.c#L405-L430) | `rt_thread_delete` → 把状态置 CLOSE 并入 defunct,idle 才真正 free 两块堆内存。[thread.c#L508-L532](file:///D:/Complie/RT-ThreadStudio/workspace/01_hello_stm32/rt-thread/src/thread.c#L508-L532) | - > | **创建是否可能失败** | 不可能(内存在编译/链接时就固定了,运行时零失败) | 可能失败:heap 不足时 `RT_KERNEL_MALLOC` 返回 NULL,API 返回 RT_NULL。用户必须每次判空。 | - > | **内存碎片风险** | 无。静态区是一次性固定分配,不存在"分配→释放→再分配"过程。 | 有。长时间运行、反复 create/delete 线程的场景下,堆会出现外部碎片,导致"总剩余字节数够,但最大连续块不够"进而 malloc 失败。 | - > - > 强制使用静态线程: - > - > 1. **资源完全确定(No Surprise)** - > 2. **OOM(Out of Memory)失败路径被根除** - > 3. **碎片 & 泄漏问题被根除** - -### 3. 线程状态机与调度算法 - -- **五态模型**:绘制或描述 RT-Thread 线程状态(初始态、就绪态、运行态、挂起态、关闭态)及触发状态转换的典型 API。 - - > | 宏名 | 值 | 含义 | - > | :-----------------: | :--: | :----------------------------------------------------------: | - > | `RT_THREAD_INIT` | 0x00 | 初始态:线程对象已初始化(控制块字段填好、栈布置完毕),但还没进入就绪表。 | - > | `RT_THREAD_READY` | 0x01 | 就绪态:已进入 `rt_thread_priority_table[prio]` 就绪链表,只等调度器选上。 | - > | `RT_THREAD_SUSPEND` | 0x02 | 挂起态/阻塞态:不在就绪表里,CPU 不会选到它。在等信号量、事件、消息、延时、或挂起自己。 | - > | `RT_THREAD_RUNNING` | 0x03 | 运行态:正在 CPU 上。 | - > | `RT_THREAD_CLOSE` | 0x04 | 关闭态:线程已执行完毕(`entry` 返回)或被用户主动 delete/detach,进入 defunct 链表等 idle 回收,或静态对象已从容器摘链。 | - -- **调度机制剖析**: - - - **抢占式调度**:只要就绪表里存在比当前运行线程优先级**更高(数字更小)\**的线程,调度器就会立即剥夺当前线程的 CPU,立刻切到高优先级线程——剥夺发生在\**每一个调度点**(SysTick tick、IPC 唤醒、退出临界区、OS API 返回、中断出口等)。 - - **时间片轮转**:当**同一个优先级上挂了多个就绪线程**时,RT-Thread 不会让第一个线程一直跑到阻塞,而是采用「每个线程分配一个 tick 配额 = `init_tick`,配额用完就把它从链表头移到链表尾,下一轮先跑链表第二个」的策略。 - -## Part 2:多线程实战编程(C 语言实现) - -![image-20260819020551659](figures/image-20260819020551659.png) \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260819020541320.png" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260819020541320.png" deleted file mode 100644 index 5fa513b48a4511ef080182a52ce10ec4684f51de..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260819020541320.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260819020551659.png" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260819020551659.png" deleted file mode 100644 index 5b5975ac57b0ff2abfeeeb474adcb8e416610ffc..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figures/image-20260819020551659.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 34b3fb7bc69af7345c37cb77720f5b399ab35936..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2006-2022, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2018-08-24 yangjie the first version - */ - -/* - * 程序清单:创建、初始化/脱离线程 - * - * 这个例子会创建两个线程,一个动态线程,一个静态线程。 - * 静态线程在运行完毕后自动被系统脱离,动态线程一直打印计数。 - */ -#include -#include -#include //如果不包含,可能会遇到错误提示没有'F'定义 - -#define LED_B GET_PIN(F, 11) -#define LED_R GET_PIN(F, 12) - -#define THREAD_PRIORITY 16 -#define THREAD_STACK_SIZE 1024 -#define THREAD_TIMESLICE 5 - -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif -static char thread2_stack[1024]; -static struct rt_thread th_led2; - -static rt_thread_t th_led1,th_monitor = RT_NULL; - - -/* 线程1的入口函数 */ -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - while (1) - { - /* 线程1采用16优先级运行,一直打印计数值 */ - rt_pin_write(LED_R, !rt_pin_read(LED_R)); - rt_kprintf("th_led1 count and tick: %d and %d \n", count++, rt_tick_get()); - rt_thread_mdelay(500); - } -} - -/* 线程3的入口函数 */ -static void th_monitor_entry(void *parameter) -{ - while (1) - { - rt_uint8_t stat1 = 0; - rt_uint8_t stat2 = 0; - -#ifdef RT_USING_FINSH - if (th_led1) - { - stat1 = th_led1->stat; - } - - stat2 = th_led2.stat; -#endif - - rt_kprintf("\n========== THREAD MONITOR ==========\n"); - - rt_kprintf("th_led1: state=%d\n", stat1); - rt_kprintf("th_led2: state=%d\n", stat2); - - rt_kprintf("====================================\n\n"); - - rt_thread_mdelay(2000); - } -} - -/* 线程2入口 */ -static void th_led2_entry(void *param) -{ - rt_uint32_t count = 0; - - /* 线程2采用16优先级,一直打印计数值 */ - while(1) - { - /* 线程2打印计数值 */ - rt_pin_write(LED_B, !rt_pin_read(LED_B)); - rt_kprintf("th_led2 count and tick: %d and %d \n", count++, rt_tick_get()); - rt_thread_mdelay(500); - } -} - -/* 线程示例 */ -int thread_dump(void) -{ - /* 创建线程1,名称是thread1,入口是thread1_entry*/ - th_led1 = rt_thread_create("th_led1", - th_led1_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, THREAD_TIMESLICE); - /* 创建线程3,名称是th_monitor,入口是th_monitor*/ - th_monitor = rt_thread_create("th_monitor", - th_monitor_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY - 6, THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(tid1, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - /* 如果获得线程控制块,启动这个线程 */ - if (th_led1 != RT_NULL) - rt_thread_startup(th_led1); - if (th_monitor != RT_NULL) - rt_thread_startup(th_monitor); - - /* 初始化线程2,名称是thread2,入口是thread2_entry */ - rt_thread_init(&th_led2, - "th_led2", - th_led2_entry, - RT_NULL, - &thread2_stack[0], - sizeof(thread2_stack), - THREAD_PRIORITY, THREAD_TIMESLICE); -#ifdef RT_USING_SMP - /* 绑定线程到同一个核上,避免启用多核时的输出混乱 */ - rt_thread_control(&thread2, RT_THREAD_CTRL_BIND_CPU, (void*)0); -#endif - rt_thread_startup(&th_led2); - - return 0; -} - -/* 导出到 msh 命令列表中 */ -MSH_CMD_EXPORT(thread_dump, thread_dump); -//INIT_APP_EXPORT(thread_dump); diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260817145124928.png" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260817145124928.png" deleted file mode 100644 index b0d06620ea2f2ff5abf31460accbc0105a5e36aa..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260817145124928.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260817145215791.png" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260817145215791.png" deleted file mode 100644 index 74f8fece3aa1bfc831e29276c902711736f14704..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260817145215791.png" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 99935837c73608bc1d7eb0cc21a3441847618106..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,132 +0,0 @@ -# 1. RTTStudio的使用 - -## 1. 新建RTT项目 - -## image-20260817145124928 - -## 2. 基于芯片(开发板)进行创建 - -![image-20260817145215791](figures/image-20260817145215791.png) - -​ 在创建的时候需要注意RTT的版本号、芯片具体型号、芯片的串口以及烧录器(调试器) - -## 3. 烧录 - -​ 使用STLINK进行烧录的时候可能会出现error,报错固件版本太低需要更新,可以多尝试几个软件进行更新,***以防部分软件版本过低***!!! - - - -============================================================================================================== - -# 2. Git基础操作与开源协作流程培训 - - -## 一、Git基础环境配置与核心指令 - -### 1.1 环境初始化与身份配置 - -| 配置项 | 命令 | 说明 | -| ----------- | -------------------------------------------- | ------------------------------------------------------------ | -| 安装与汉化 | — | 安装Git[Git - 安装 Git](https://git-scm.com/book/zh/v2/起步-安装-Git) | -| 全局用户名 | `git config --global user.name "你的用户名"` | 设置提交者身份标识 | -| 全局邮箱 | `git config --global user.email "你的邮箱"` | 确保提交记录可追溯 | -| SSH密钥生成 | `ssh-keygen -t rsa -C "你的邮箱地址"` | 生成SSH密钥对 | -| 添加公钥 | — | 将`~/.ssh/id_rsa.pub`内容添加到Git平台账户设置 | - -> 由于gitee默认开启了禁止命令行推送暴露个人邮箱,所以可以尝试跳过命令:git config --global user.email "你的邮箱",或者在gitee设置->邮箱管理 里面关闭“禁止命令行推送暴露个人邮箱” - -### 1.2 核心操作指令 - -```bash -# 仓库初始化与拉取 -git init # 初始化本地仓库 -git clone # 克隆远端仓库到本地 -git pull # 拉取远端最新代码 - -# 本地变更管理 -git add # 将文件加入暂存区,或者直接使用git add . -git commit -m "xxx" # 提交变更到本地仓库 -git push origin master # 推送本地提交到远端仓库,如果没有分支要求可以直接git push - -# 分支管理 -git checkout -b # 创建并切换到新分支 -``` - -> ⚠️ **注意**:日常开发应在独立分支进行,**严禁直接操作受保护的master分支**。 - ---- - -## 二、开源协作流程与PR提交规范 - -### 2.1 Fork模式下的协作机制 - -``` -┌─────────────────┐ Fork ┌─────────────────┐ -│ 上游仓库 │ ────────────► │ 个人Fork仓库 │ -│ (upstream) │ │ (origin) │ -└─────────────────┘ └─────────────────┘ - ▲ │ - │ fetch / pull │ push - │ (同步上游代码) │ (推送功能分支) - └──────────────────────────────────┘ -``` - -**关键步骤:** - -| 步骤 | 命令/操作 | 说明 | -| --------------- | ----------------------------------------------- | ------------------------ | -| 1. Fork仓库 | Git平台界面操作 | 将上游仓库Fork到个人账户 | -| 2. 克隆个人仓库 | `git clone <个人仓库URL>` | 下载到本地开发 | -| 3. 添加上游远程 | `git remote add upstream <上游URL>` | 建立与上游的关联 | -| 4. 创建功能分支 | `git checkout -b xxx` | 按日期/功能创建分支 | -| 5. 同步上游代码 | `git fetch upstream` / `git pull upstream main` | 保持代码最新,解决冲突 | -| 6. 推送并提PR | `git push origin xxx` → 平台界面创建PR | 请求合并到上游 | - -> ⚠️ **严禁在个人Fork仓库中直接修改master分支**,所有开发必须在功能分支完成。 - -### 2.2 PR提交与审核流程 - -``` -学员开发完成 → push到个人Fork功能分支 → 平台创建PR → 指定Reviewer(如果无法指定就跳过) - ↓ - 审查通过 → 合并到上游 - 审查不通过 → 修改后重新提交 -``` - -- **Reviewer指定**:提交PR时需指定对应的组长或导师 -- **冲突解决**:本地分支落后上游时,先`fetch upstream`合并最新代码,再解决冲突 - ---- - -## 三、git常用命令速查表 - -```bash -# 配置 -git config --global user.name "xxx" -git config --global user.email "xxx@example.com" - -# 仓库操作 -git init -git clone -git remote set-url origin url #直接修改远程仓库地址 -# 第一次 clone 自己的 Fork 后,只执行一次,告诉 Git:"原始仓库在这里,以后我叫它 upstream" -git remote add upstream #添加上游地址 -git remote -v #查看当前仓库的远端地址 - -# 日常开发 -git checkout -b #创建并且进入新分支 -git add . #添加暂存文件 -git commit -m "描述信息" #类似于添加日志 -git push origin - -# 同步上游 -git fetch upstream #下载上游branch分支仓库,如果不写就是全部分支 -git merge upstream/master #合并上游代码 -# 或 -git pull upstream master - -# 查看状态 -git status -git log --oneline -git branch #查看所有分支 -``` diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index d80157cf2e52afcaf90a4214ba229fc56b193750..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,153 +0,0 @@ -# 第三天:RT-Thread IPC 机制与线程同步通信 - -## 一、课程背景与基础概念 - -前两天学习了 RT-Thread 工程创建、启动流程和线程调度。本次课程进一步讨论多线程之间如何同步和交换数据。IPC(Inter-Process Communication)在 RT-Thread 中主要用于线程同步与线程间通信,常见对象包括信号量、互斥量、事件集、邮箱和消息队列。 - -**对于所有的线程,只要优先级更高,即使其他线程正常运行,也可以强制抢占,除非进入临界区或者关闭中断** - -### 1. 临界区、阻塞与挂起 - -- **临界区:** 访问共享变量、外设寄存器或其他共享资源的一段代码,同一时刻通常只允许一个线程进入。 -- **阻塞:** 线程暂时无法获得所需资源时等待。阻塞期间线程不占用 CPU,调度器可以运行其他就绪线程。 -- **非阻塞:** 不等待资源,立即返回成功或错误结果,适合不能长时间等待的场景。 -- **线程挂起:** 线程因等待资源、延时或主动挂起而进入 `RT_THREAD_SUSPEND` 状态,资源可用或延时结束后再恢复为就绪态。 - -IPC 的选择要同时考虑资源保护、数据传递、等待时间、优先级和是否运行在中断上下文中,不能简单地用一种对象替代所有同步需求。 - -## 二、线程同步机制 - -### 1. 信号量 Semaphore - -信号量本质上是一个可计数的资源数量,线程通过“获取”和“释放”来协调执行。 - -- **二值信号量:** 计数通常为 0 或 1,适合表示“事件是否发生”或“某项资源是否可用”。例如线程一完成计数后调用 `rt_sem_release()`,线程二使用 `rt_sem_take()` 等待并执行 LED 操作。 -- **计数信号量:** 初始值表示可用资源实例数量,适合管理多个相同资源,例如生产者—消费者模型中的空闲槽位和已有数据数量。 -- **动态创建:** `rt_sem_create(name, value, flag)` 从系统对象/堆资源中创建信号量。 -- **静态初始化:** `rt_sem_init(sem, name, value, flag)` 使用用户预先分配的控制块。 -- **核心操作:** `rt_sem_take(sem, timeout)` 获取信号量,`rt_sem_trytake(sem)` 不等待地获取,`rt_sem_release(sem)` 释放信号量。 - -`timeout` 决定等待策略:可以立即返回、等待指定 tick,或使用 `RT_WAITING_FOREVER` 永久等待。多个线程等待同一个信号量时,`flag` 可选 `RT_IPC_FLAG_FIFO` 或 `RT_IPC_FLAG_PRIO`。实时性要求较高时通常使用优先级等待;只有确实需要先来先服务时才选择 FIFO。 - -信号量没有线程所有权,释放操作不要求由最后获取它的线程执行,因此它适合“通知”和“资源计数”,但不适合作为严格的临界区所有权锁。 - -### 2. 互斥量 Mutex - -互斥量是带有所有权的互斥锁,适合保护共享数据和临界区。它与普通信号量的主要区别如下: - -| 对比项 | 信号量 | 互斥量 | -| --- | --- | --- | -| 主要用途 | 同步、事件通知、资源计数 | 保护临界区和共享资源 | -| 所有权 | 无所有权 | 有 owner,通常只有持有者可以释放 | -| 递归获取 | 不支持,重复获取可能导致阻塞 | 支持同一线程递归获取,使用次数由内核记录 | -| 优先级继承 | 不提供互斥量式的优先级继承 | 支持优先级继承,用于缓解优先级反转 | -| 中断中使用 | 按具体 API 和上下文限制使用 | 不能在中断服务程序中使用需要阻塞/所有权的互斥操作 | - -互斥量的典型 API 是 `rt_mutex_create`/`rt_mutex_init`、`rt_mutex_take`、`rt_mutex_release` 以及对应的 `delete`/`detach`。动态创建的对象使用 `delete`,静态初始化的对象使用 `detach`,两者不要混用。 - -#### 优先级反转 - -假设低优先级线程 L 持有共享资源,高优先级线程 H 等待该资源,此时中优先级线程 M 持续运行并抢占 L,H 就会间接地被 M 阻塞,这就是优先级反转。 - -RT-Thread 互斥量使用优先级继承:当 H 因等待资源而阻塞时,暂时提升持有者 L 的有效优先级,使 L 尽快运行并释放互斥量;释放后,L 恢复原来的优先级。课程中通过比较有无互斥量保护时 `number1`、`number2` 的变化,验证了临界区保护和优先级继承效果。 - -使用互斥量时应尽快完成临界区操作并释放锁,避免在持锁期间执行长时间延时、复杂计算或改变持有者优先级。 - -### 3. 事件集 Event Set - -事件集使用一个 32 位无符号值表示多个事件标志位,适合一对多或多对多同步。例如: - -```c -#define EVENT3 (1u << 3) -#define EVENT5 (1u << 5) -``` - -线程可以使用 `rt_event_recv()` 等待事件,发送方使用 `rt_event_send()` 设置事件标志。 - -- `RT_EVENT_FLAG_OR`:等待的标志中任意一个发生即可唤醒; -- `RT_EVENT_FLAG_AND`:等待的所有标志同时满足后才唤醒; -- `RT_EVENT_FLAG_CLEAR`:接收成功后清除已匹配的事件标志,避免旧事件重复触发。 - -课程示例先以 OR 模式等待事件 3 或事件 5,再以 AND 模式等待事件 3 和事件 5 同时到达。事件集传递的是标志,不携带具体数据;如果需要传递数据,应配合邮箱或消息队列使用。 - -## 三、线程间通信机制 - -### 1. 邮箱 Mailbox - -邮箱适合传递轻量、固定大小的消息。官方实现中,每封邮件最多保存一个 32 位值;在 32 位 MCU 上通常是 4 字节,因此常用来传递整数、状态值或指针,而不是直接复制一段较大的字符串。 - -常用 API 包括: - -- `rt_mb_create` / `rt_mb_init`:动态创建或静态初始化邮箱; -- `rt_mb_send`:发送邮件,邮箱满时立即返回 `-RT_EFULL`; -- `rt_mb_send_wait`:邮箱满时按照 timeout 等待空间; -- `rt_mb_recv`:接收邮件,可设置立即返回、限时等待或永久等待; -- `rt_mb_delete` / `rt_mb_detach`:分别删除动态邮箱或脱离静态邮箱。 - -邮箱可以由线程或 ISR 发送,因此适合中断向线程传递“发生了某事件”或一个指针。但传递指针时必须保证指针指向的内存在线程处理完成前仍然有效,并明确由谁负责释放,避免悬空指针和内存泄漏。 - -课程示例中,线程二交替发送字符串指针,线程一接收并打印;收到特殊字符串 `"over"` 后,接收线程退出,演示了使用邮箱传递控制信息的过程。 - -### 2. 消息队列 Message Queue - -消息队列用于复制传递消息内容,比邮箱更适合传输结构体或多字节数据。需要注意:RT-Thread 的一个消息队列在创建/初始化时配置固定的 `msg_size`,队列中的每条消息都按这个大小处理,因此这里的“变长”应理解为消息队列不受邮箱 4 字节限制,而不是同一个队列中每条消息都可以任意长度。 - -常用 API 包括: - -- `rt_mq_create(name, msg_size, max_msgs, flag)`:动态创建消息队列; -- `rt_mq_init(mq, name, msg_pool, msg_size, pool_size, flag)`:使用消息池静态初始化; -- `rt_mq_send`:将数据复制到队列消息块; -- `rt_mq_recv`:从队列取出消息并复制到接收缓冲区; -- `rt_mq_urgent`:将紧急消息插入队头; -- `rt_mq_delete` / `rt_mq_detach`:删除动态队列或脱离静态队列。 - -普通消息通常排在队尾。例如依次发送 `a` 到 `h`,再用 `rt_mq_urgent` 发送 `i`,接收顺序会体现 `i` 被插入队头的效果。紧急消息只改变队列位置,不会突破队列容量限制;队列满时仍需检查返回值。 - -消息队列直接复制数据内容,因此接收方可以使用自己的局部结构体接收消息,不必像邮箱传递指针那样额外管理动态内存。若发送和处理需要确认,可组合使用“消息队列 + 信号量”或“消息队列 + 邮箱”实现应答。 - -## 四、五种 IPC 机制的选型总结 - -| 需求 | 推荐机制 | 选择理由 | -| --- | --- | --- | -| 通知某个事件发生 | 二值信号量 | 使用简单,可阻塞等待和释放通知 | -| 管理多个相同资源 | 计数信号量 | 计数值表示可用资源数量 | -| 保护共享变量或外设 | 互斥量 | 有所有权、支持递归、可进行优先级继承 | -| 等待多个条件组合 | 事件集 | 支持 OR/AND 标志匹配,不负责携带数据 | -| 传递一个整数或指针 | 邮箱 | 单条消息固定 32 位,开销小 | -| 传递结构体或多字节数据 | 消息队列 | 按固定消息大小复制数据,并支持紧急消息插队 | - -***在使用API的时候,一定注意API返回值,尤其是创建create或者初始化init的时候,*** - -***例如create的时候,句柄在函数内;而使用init的时候,句柄作为返回值返回。如下面代码所示*** - -```c -rt_mq_t rt_mq_create(const char* name, rt_size_t msg_size, - rt_size_t max_msgs, rt_uint8_t flag); -rt_err_t rt_mq_init(rt_mq_t mq, const char* name, - void *msgpool, rt_size_t msg_size, - rt_size_t pool_size, rt_uint8_t flag); -``` - -设计 IPC 时还要注意以下原则: - -1. 明确每个对象的创建方式,动态对象用 `delete`,静态对象用 `detach`; -2. 所有 `take`、`recv`、`send` 操作都检查返回值和超时情况; -3. 临界区尽量短,持有互斥量时不要执行长时间阻塞操作; -4. 中断服务程序只能使用允许在 ISR 上下文调用的 API,不能调用会让中断阻塞的操作; -5. 使用优先级等待时要确认优先级数值含义:RT-Thread 中优先级数字越小,优先级越高; -6. 传递指针前确认对象生命周期,使用消息队列复制数据时确认接收缓冲区大小不小于队列的消息大小。 - -## 五、课程实操与调试思路 - -调试同步问题时,可以按照以下顺序检查: - -1. 确认 IPC 对象已经创建或初始化,并且初始化值正确; -2. 确认线程在使用前已经通过 `rt_thread_startup` 启动; -3. 在 `take`、`release`、`send`、`recv` 前后打印线程名、返回值和关键变量; -4. 使用 `list_thread` 观察线程是否处于 READY、RUNNING 或 SUSPEND 状态; -5. 检查是否忘记释放信号量/互斥量,是否出现重复释放、超时未处理或消息队列满; -6. 对优先级反转问题,比较使用信号量和互斥量时持有资源线程的有效优先级变化。 - -## 七、学习总结 - -线程同步解决“谁可以继续执行”和“谁可以访问资源”的问题,线程通信解决“数据如何安全传递”的问题。信号量偏向计数和通知,互斥量偏向资源保护,事件集偏向多条件同步,邮箱偏向轻量消息,消息队列偏向结构化数据传递。掌握这些机制后,可以把多个线程组织成清晰的生产者—消费者、事件驱动和资源互斥模型,减少共享变量直接读写造成的竞态条件。 diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index f4e3a8a13de9a25bf21b1148ba9add48e920d929..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,116 +0,0 @@ -# 第二天:RT-Thread 内核启动流程、线程机制与 SCons 构建系统 - -## 一、RT-Thread 内核启动流程与线程机制 - -### 1. 从裸机到 RTOS - -裸机程序通常采用“前后台系统”:中断负责处理实时事件,`while(1)` 后台循环负责执行应用任务。当后台任务中存在等待标志位、延时或其他阻塞操作时,后续任务会被一起阻塞,可能导致 LCD 刷新等实时任务不能及时响应。 - -裸机模式的主要局限包括:并发效率低;一个任务阻塞可能影响其他任务,实时性较差;代码维护性差,修改一个模块可能牵连整个主循环;模块化程度和代码复用性不足。RT-Thread 通过线程、调度器、IPC 和定时器等内核机制,把应用拆分为多个相互独立的任务,由系统统一管理 CPU 时间。 - -### 2. RT-Thread 启动流程 - -不同工具链在进入 `main()` 前的入口不同:MDK 通常经过 `__main`,IAR 经过 Low Level Init,GCC 则经过 `_entry` 或类似入口。RT-Thread 会利用这些入口完成系统初始化,典型调用路径如下: - -```text -上电复位 -> Reset_Handler -> SystemInit() - -> 工具链入口(如 _entry)-> rtthread_startup() - -> rt_hw_board_init() // UART、时钟、SysTick 等板级初始化 - -> 打印 Logo - -> rt_system_timer_init() // 系统定时器初始化 - -> rt_system_scheduler_init() // 调度器初始化 - -> 信号量等可选内核组件初始化 - -> 创建 Application/Main 线程 - -> 创建 Timer 线程和 Idle 线程 - -> rt_system_scheduler_start() - -> 最高优先级线程开始运行 -``` - -自动初始化机制通过 `INIT_BOARD_EXPORT`、`INIT_APP_EXPORT` 等宏,把函数地址放入特定的链接段。链接器按段名排序,系统启动时再用函数指针依次遍历并调用这些函数,因此可以自动完成板级、组件和应用初始化。 - -调度器启动后采用抢占式优先级调度:优先级数值越小,优先级越高,系统总是优先运行就绪队列中优先级最高的线程。当多个线程优先级相同时,按照时间片轮转,让同优先级线程依次获得 CPU。 - -### 3. 线程状态与上下文切换 - -RT-Thread 线程通常包含以下五种状态: - -| 状态 | 含义 | 常见触发方式 | -| --- | --- | --- | -| 初始态 `RT_THREAD_INIT` | 控制块和线程栈已经初始化,但还没有进入就绪队列 | `rt_thread_init` 或 `rt_thread_create` 后 | -| 就绪态 `RT_THREAD_READY` | 可以被调度器选中运行 | 调用 `rt_thread_startup` 后 | -| 运行态 `RT_THREAD_RUNNING` | 当前正在占用 CPU | 调度器选中该线程 | -| 挂起态 `RT_THREAD_SUSPEND` | 等待延时、信号量、事件或消息,不参与调度 | `rt_thread_delay`、等待 IPC 等 | -| 关闭态 `RT_THREAD_CLOSE` | 线程结束或被删除,等待清理 | 入口函数返回、`rt_thread_delete` 等 | - -线程创建后并不会自动运行,必须调用 `rt_thread_startup()` 将其加入就绪队列。线程切换时,内核先保存当前线程的上下文(将寄存器压栈),再恢复目标线程保存的上下文(从栈中弹出寄存器),从而保证每个线程都能从上次暂停的位置继续执行。 - -线程控制块 `struct rt_thread` 中的关键成员包括:`stat` 保存线程状态及相关标志;`current_priority` 保存当前有效优先级;`init_tick` 和 `remaining_tick` 分别表示初始时间片和剩余时间片;`sp` 保存线程栈指针,用于上下文保存和恢复。 - -Idle 线程的优先级最低,通常为 31。它在没有其他就绪线程时运行,负责回收已经进入关闭态的动态线程,同时可以执行喂狗、休眠等低功耗处理。 - -### 4. 静态线程与动态线程 - -| 对比项 | 静态创建 | 动态创建 | -| --- | --- | --- | -| API | `rt_thread_init` | `rt_thread_create` | -| 内存来源 | 用户预先定义控制块和栈数组,通常位于 `.bss`/`.data` | 从系统堆中分配控制块和线程栈 | -| 生命周期 | 与静态对象一致,不由系统释放 | 删除后进入关闭队列,由 Idle 线程回收 | -| 优点 | 资源确定、不会出现堆碎片、运行时失败路径少 | 使用灵活,适合运行时按需创建 | -| 风险 | 需要提前确定线程数量和栈大小 | 可能分配失败,长期创建和删除会产生内存碎片 | - -在高可靠场景中通常优先使用静态线程,因为 RAM/Flash 占用可以在编译和链接阶段确定,运行过程中不会因为堆不足或碎片导致线程创建失败。 - -### 5. 多线程实战:创建、启动和查看线程 - -作业中的 `thread_study.c` 同时演示了动态线程和静态线程:`th_led1` 使用 `rt_thread_create` 动态创建,优先级为 16,周期性翻转红色 LED 并打印计数;`th_led2` 使用 `rt_thread_init` 静态初始化,使用预先分配的 `thread2_stack`,周期性翻转蓝色 LED;`th_monitor` 是一个优先级更高的动态线程,用于每隔 2 秒打印两个线程的状态。 - -每个线程创建后都要检查返回值,再调用 `rt_thread_startup` 启动。线程通过 `rt_thread_mdelay(500)` 主动让出 CPU。使用 FinSH/msh 执行 `list_thread`,可以查看线程名称、优先级、状态和栈使用情况,验证线程是否成功创建、运行和退出。 - -## 二、SCons 构建系统语法与应用 - -### 1. SCons 脚本结构 - -RT-Thread 使用 SCons 管理工程构建。`SConscript` 通常位于源码子目录中,描述当前目录的源文件和构建规则;`SConstruct` 是顶层构建入口;`rtconfig.py` 定义工具链、编译器选项和目标平台;`rtconfig.h` 保存图形化配置生成的宏定义。 - -典型配置形式如下: - -```python -from building import * - -cwd = GetCurrentDir() -src = Glob('*.c') -CPPPATH = [cwd] -group = DefineGroup('Applications', src, depend=['RT_USING_FINSH']) -Return('group') -``` - -其中,`CPPPATH` 用于添加头文件搜索路径,`SRC` 或 `Glob()` 用于指定源文件,`DEPENDS`/`depend` 用于控制宏依赖:只有启用对应宏时,相关源码才会参与构建。 - -### 2. 常用构建命令 - -```text -scons 编译工程 -scons -c 清理编译生成文件 -scons --target=mdk5 生成 MDK 工程 -scons --target=iar 生成 IAR 工程 -scons --target=vscode 生成 VS Code 工程 -scons --dist 将 BSP 打包为可独立开发的工程压缩包 -``` - -`scons --dist` 会整理当前 BSP 并移除与独立开发无关的文件,便于工程迁移和分发。 - -### 3. Env、RT-Thread Studio 与 Menuconfig - -Env 和 RT-Thread Studio 都提供图形化配置能力。通过 Menuconfig 可以选择内核功能、线程数量、FinSH、软件包和外设驱动等选项,保存后配置结果会写入 `rtconfig.h`,SCons 再根据这些宏决定参与编译的源码。 - -软件包管理器支持在线获取和集成软件包,例如 LVGL、kernel samples 等。启用软件包后,相关源码和配置会自动纳入构建系统,减少手动复制文件和修改工程配置的工作量。 - -## 三、STM32F407 工程创建与调试 - -本次实操使用 STM32F407 星火一号开发板,在 RT-Thread Studio 中基于具体芯片型号创建工程,并配置芯片型号、ST-Link 调试器、UART 串口、FinSH/msh 命令行和 kernel samples 软件包。 - -调试时可以在汇编启动入口、`_entry`、`rtthread_startup`、板级初始化、调度器初始化和应用线程创建处设置断点,观察程序从复位到第一个线程运行的完整过程。重点检查硬件初始化是否完成、调度器是否启动、Application/Timer/Idle 线程是否创建,以及用户线程是否成功进入就绪态。 - -## 四、学习总结 - -RT-Thread 把应用任务从裸机主循环中拆分出来,通过线程状态管理和优先级调度实现清晰的并发执行。静态线程适合资源固定和高可靠系统,动态线程适合需要灵活创建和销毁的场景。SCons、Menuconfig 和 Studio 把源码组织、编译配置和软件包集成统一起来,提升了嵌入式工程的可维护性和开发效率。 diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/bd58e5e36af8d5b8b043d9b08125e62e.jpg" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/bd58e5e36af8d5b8b043d9b08125e62e.jpg" deleted file mode 100644 index 107f3734e4b99963902c030bc3f4dd326982a8a6..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/bd58e5e36af8d5b8b043d9b08125e62e.jpg" and /dev/null differ diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/package.c" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/package.c" deleted file mode 100644 index 247796e18f6e40d412f04940fa30c110168b54b9..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/package.c" +++ /dev/null @@ -1,187 +0,0 @@ -/* - * package.c - 练习四:快递分拣中心(消息队列) - * - * 芯片 : STM32F407ZGT6 - * 开发板 : 星火1号 (RT-Thread STM32F407-RT-SPARK) - * 开发环境 : RT-Thread Studio - * - * 场景: - * 两个收件线程不断产生快递消息,一个分拣线程从消息队列取出并按目的区域分类。 - * - * 设计: - * - 消息队列容量 5 条(消息大小 = sizeof(struct package_msg))。 - * - 两个发件线程,各自产生 5 条快递消息(id 不重复)。 - * - 分拣线程 rt_mq_recv 接收,打印编号/区域/重量,并累计各项统计。 - * - 队列满时 rt_mq_send 失败不崩溃,send_failed++ 并稍后重发(记录发送失败次数)。 - * - 每个发件线程完成后发送一条结束消息(id==0,共 SENDER_NUM 条)。 - * - 分拣线程收到 SENDER_NUM 条结束消息后打印统计结果并退出、释放资源。 - * - * 命令行: package_demo - */ - -#include -#include - -#define SENDER_NUM 2 -#define MSG_PER_SENDER 5 -#define MQ_CAPACITY 5 /* 队列最多 5 条消息 */ - -#define PKG_STACK 1024 -#define PKG_PRIORITY 25 -#define PKG_TIMESLICE 5 -#define SORTER_PRIORITY 15 /* 高于发件,及时取走防堆积 */ - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; /* 1=华东 east, 2=华南 south, 3=华北 north */ - rt_uint16_t weight; /* 单位: 克 */ -}; - -static rt_mq_t pkg_mq = RT_NULL; -static volatile rt_uint32_t send_failed = 0; -static volatile rt_uint8_t pkg_running = 0; - -static const char *region_name(rt_uint8_t r) -{ - switch (r) - { - case 1: return "east"; - case 2: return "south"; - default: return "north"; - } -} - -static void sender_entry(void *param) -{ - rt_int32_t sender_no = (rt_int32_t)param; - rt_uint32_t base = (sender_no == 1) ? 1001 : 2001; - /* 区域分布: 使两组最终得到 east=4, south=3, north=3 */ - static const rt_uint8_t region_map[MSG_PER_SENDER] = {1, 2, 3, 1, 2}; - struct package_msg msg; - int i; - - for (i = 0; i < MSG_PER_SENDER; i++) - { - msg.id = base + i; - msg.region = region_map[i]; - msg.weight = (rt_uint16_t)(1000 + ((msg.id % 100) * 20)); /* 确定性重量 */ - - /* 非阻塞发送;队列满时不崩溃,记录失败次数并稍后重发 */ - while (rt_mq_send(pkg_mq, &msg, sizeof(msg)) != RT_EOK) - { - send_failed++; - rt_thread_mdelay(10); /* 让分拣线程有机会取走 */ - } - } - - /* 发送结束标记 */ - rt_memset(&msg, 0, sizeof(msg)); - rt_mq_send(pkg_mq, &msg, sizeof(msg)); - - return; /* 发件线程退出(内核自动清理) */ -} - -static void sorter_entry(void *param) -{ - struct package_msg msg; - rt_uint32_t total = 0, total_east = 0, total_south = 0, total_north = 0; - rt_uint32_t total_weight = 0; - rt_uint32_t end_count = 0; - rt_err_t ret; - - while (1) - { - ret = rt_mq_recv(pkg_mq, &msg, sizeof(msg), RT_WAITING_FOREVER); - if (ret != RT_EOK) - { - rt_kprintf("mq recv error 0x%x\n", ret); - continue; - } - - if (msg.id == 0) /* 结束消息 */ - { - if (++end_count >= SENDER_NUM) - break; - continue; - } - - rt_kprintf("package %u -> %s, weight: %u g\n", - msg.id, region_name(msg.region), msg.weight); - total++; - total_weight += msg.weight; - switch (msg.region) - { - case 1: total_east++; break; - case 2: total_south++; break; - default: total_north++; break; - } - } - - rt_kprintf("total packages: %u\n", total); - rt_kprintf("east: %u, south: %u, north: %u\n", total_east, total_south, total_north); - rt_kprintf("total weight: %u g\n", total_weight); - rt_kprintf("send failed: %u\n", send_failed); - - /* 结束:主动释放资源,命令可重复执行 */ - send_failed = 0; - rt_mq_delete(pkg_mq); - pkg_mq = RT_NULL; - pkg_running = 0; - return; /* 分拣线程退出(内核自动清理) */ -} - -int package_demo(void) -{ - rt_thread_t tid_sender[SENDER_NUM]; - rt_thread_t tid_sorter; - rt_uint32_t i; - - if (pkg_running) - { - rt_kprintf("package_demo is still running, ignore\n"); - return -RT_EBUSY; - } - pkg_running = 1; - send_failed = 0; - - /* 消息队列: 容量 MQ_CAPACITY 条,每条消息大小 sizeof(struct package_msg) */ - pkg_mq = rt_mq_create("pkg_mq", sizeof(struct package_msg), MQ_CAPACITY, RT_IPC_FLAG_FIFO); - if (pkg_mq == RT_NULL) - { - rt_kprintf("create pkg mq failed\n"); - pkg_running = 0; - return -RT_ENOMEM; - } - - tid_sorter = rt_thread_create("pkg_sort", sorter_entry, RT_NULL, - PKG_STACK, SORTER_PRIORITY, PKG_TIMESLICE); - if (tid_sorter == RT_NULL) - { - rt_mq_delete(pkg_mq); pkg_mq = RT_NULL; - pkg_running = 0; - rt_kprintf("create sorter thread failed\n"); - return -RT_ENOMEM; - } - - for (i = 0; i < SENDER_NUM; i++) - { - tid_sender[i] = rt_thread_create("pkg_send", sender_entry, (void *)(i + 1), - PKG_STACK, PKG_PRIORITY, PKG_TIMESLICE); - if (tid_sender[i] == RT_NULL) - { - rt_kprintf("create sender thread failed\n"); - while (i--) - rt_thread_delete(tid_sender[i]); - rt_thread_delete(tid_sorter); - rt_mq_delete(pkg_mq); pkg_mq = RT_NULL; - pkg_running = 0; - return -RT_ENOMEM; - } - rt_thread_startup(tid_sender[i]); - } - - rt_thread_startup(tid_sorter); - return RT_EOK; -} -MSH_CMD_EXPORT(package_demo, package demo: message queue 2 senders 1 sorter); \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/parking.c" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/parking.c" deleted file mode 100644 index b7628c93efd484546c0c049ff0c6388549698b46..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/parking.c" +++ /dev/null @@ -1,133 +0,0 @@ -/* - * parking.c - 练习一:停车场车位管理(计数信号量) - * - * 芯片 : STM32F407ZGT6 - * 开发板 : 星火1号 (RT-Thread STM32F407-RT-SPARK) - * 开发环境 : RT-Thread Studio - * - * 场景: - * 停车场只有 3 个车位,同时有 5 辆车尝试进入。车位是有限资源, - * 车辆只有获得车位(信号量)后才能进入停车场。 - * - * 设计: - * - 创建一个初始值为 3 的计数信号量,代表剩余车位。 - * - 动态创建 5 个车辆线程 car1..car5。 - * - 每辆车到达后 rt_sem_take(..., TIME_WAIT_MS) 申请一个车位, - * 成功则 park_used++ 并打印进入信息;超时则打印等待超时信息。 - * - 车辆停留一段时间后 park_used-- 并 rt_sem_release 释放车位。 - * - 由计数信号量保证任意时刻场内车辆数 <= 3。 - * - * 集成方式: - * - MSH_CMD_EXPORT() 导出 parking_demo 命令启动。 - * - 通过静态 running 标志检测任务是否仍在运行,重复执行时拒绝启动, - * 避免因同名线程/IPC 对象残留导致异常。 - * - * 需回答的思考题: 为何用计数信号量而非互斥量(见 README.md)。 - */ - -#include -#include - -#define PARK_SLOTS 3 /* 车位数(信号量初值) */ -#define CAR_NUM 5 /* 车辆数 */ -#define CAR_WAIT_MS 2000 /* 申请车位超时: 2 秒 */ -#define CAR_STAY_MS 1500 /* 车内停留时间 */ -#define CAR_STACK_SIZE 1024 -#define CAR_PRIORITY 20 -#define CAR_TIMESLICE 5 - -static rt_sem_t park_sem = RT_NULL; /* 剩余车位计数信号量 */ -static rt_uint8_t park_used = 0; /* 场内车辆数(受信号量保护)*/ -static rt_uint8_t park_running = 0; /* 防重入运行标志 */ - -static void car_entry(void *param) -{ - rt_uint32_t car_no = (rt_uint32_t)param; - rt_err_t ret; - - while (1) - { - /* 申请一个车位,最多等待 CAR_WAIT_MS */ - ret = rt_sem_take(park_sem, CAR_WAIT_MS); - if (ret == -RT_ETIMEOUT) - { - rt_kprintf("car%d waiting for a space, try again later\n", car_no); - rt_thread_mdelay(300); - continue; - } - if (ret != RT_EOK) - { - rt_kprintf("car%d take sem error 0x%x\n", car_no, ret); - break; - } - - /* 已获得车位进入。此处只有持有信号量的线程会修改 park_used,安全。 */ - park_used++; - rt_kprintf("car%d entered, used spaces: %d\n", car_no, park_used); - - /* 停留一段时间后离开并释放车位 */ - rt_thread_mdelay(CAR_STAY_MS); - - park_used--; - rt_sem_release(park_sem); - rt_kprintf("car%d left, used spaces: %d\n", car_no, park_used); - - /* 稍作间隔再排队进入,便于观察 */ - rt_thread_mdelay(500); - } -} - -int parking_demo(void) -{ - rt_thread_t tid[CAR_NUM]; - rt_uint32_t i; - - if (park_running) - { - rt_kprintf("parking_demo is already running, ignore this command\n"); - return -RT_EBUSY; - } - park_running = 1; - - /* 1. 创建初值为 PARK_SLOTS 的计数信号量 */ - park_sem = rt_sem_create("park_sem", PARK_SLOTS, RT_IPC_FLAG_FIFO); - if (park_sem == RT_NULL) - { - rt_kprintf("create park sem failed\n"); - park_running = 0; - return -RT_ENOMEM; - } - /* rt_sem_create 成功即返回有效句柄,此处再校验一遍保险 */ - RT_ASSERT(park_sem != RT_NULL); - - /* 2. 创建 5 个车辆线程 */ - park_used = 0; - for (i = 0; i < CAR_NUM; i++) - { - tid[i] = rt_thread_create("park_car", car_entry, (void *)(i + 1), - CAR_STACK_SIZE, CAR_PRIORITY, CAR_TIMESLICE); - if (tid[i] == RT_NULL) - { - rt_kprintf("create car%u thread failed\n", i + 1); - /* 清理已创建的线程与信号量,保证重入干净 */ - while (i--) - rt_thread_delete(tid[i]); - rt_sem_delete(park_sem); - park_running = 0; - return -RT_ENOMEM; - } - if (rt_thread_startup(tid[i]) != RT_EOK) - { - rt_kprintf("startup car%u thread failed\n", i + 1); - rt_thread_delete(tid[i]); - rt_sem_delete(park_sem); - park_running = 0; - return -RT_ERROR; - } - } - - rt_kprintf("parking: %d lots, %d cars, demo started\n", PARK_SLOTS, CAR_NUM); - /* 任务常驻运行,park_running 保持为 1 以拒绝重复启动 */ - return RT_EOK; -} -MSH_CMD_EXPORT(parking_demo, parking demo: counting semaphore 3 lots 5 cars); \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/startup.c" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/startup.c" deleted file mode 100644 index f58c70bcad9453fac405e8179ee12663eb18ec5e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/startup.c" +++ /dev/null @@ -1,180 +0,0 @@ -/* - * startup.c - 练习三:系统启动条件检查(事件集) - * - * 芯片 : STM32F407ZGT6 - * 开发板 : 星火1号 (RT-Thread STM32F407-RT-SPARK) - * 开发环境 : RT-Thread Studio - * - * 场景: - * 设备启动前需完成三个独立模块初始化:网络 / 传感器 / 存储。 - * 三者全部就绪后,主业务线程才能启动。模拟即可。 - * - * 设计: - * - 三个事件位 EVENT_NET/SENSOR/STORAGE_READY。 - * - 三个初始化线程经不同延时后发送对应就绪事件,随后自注销。 - * - 主业务线程用 AND 模式等待三个事件全部到达。 - * · 第一次等待超时 3 秒(存储模块需 5 秒),打印尚未就绪的模块后再次等待。 - * · 第二次等待全部就绪后打印 all modules are ready / business task started。 - * - 全部线程结束后主动释放资源,命令可重复执行。 - * - * 需回答的思考题: 与"三个独立信号量"实现的区别(见 README.md)。 - */ - -#include -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -/* 各模块初始化延时 */ -#define NET_INIT_MS 500 /* 快速就绪 */ -#define SENSOR_INIT_MS 700 /* 快速就绪 */ -#define STORAGE_INIT_MS 5000 /* 慢速就绪,触发超时 */ -#define BUSINESS_TIMEOUT_MS 3000 /* 第一次等待超时 3 秒 */ -#define BUSINESS_TIMEOUT_2MS 6000 /* 第二次等待更长 */ - -#define INIT_PRIORITY 15 -#define BUSINESS_PRIORITY 10 -#define INIT_STACK 1024 -#define BUSINESS_STACK 1024 - -static struct rt_event boot_events; /* 静态事件集对象 */ -static volatile rt_uint8_t startup_running = 0; - -/* 非阻塞查询某事件位是否已置起(不改变事件集) */ -static int module_ready(rt_uint32_t bit) -{ - rt_uint32_t recvd = 0; - return rt_event_recv(&boot_events, bit, RT_EVENT_FLAG_AND, 0, &recvd) == RT_EOK; -} - -static void init_thread_entry(void *param) -{ - rt_uint32_t evt = (rt_uint32_t)param; - rt_uint32_t delay; - const char *name; - - switch (evt) - { - case EVENT_NET_READY: delay = NET_INIT_MS; name = "network"; break; - case EVENT_SENSOR_READY: delay = SENSOR_INIT_MS; name = "sensor"; break; - default: delay = STORAGE_INIT_MS; name = "storage"; break; - } - - rt_thread_mdelay(delay); /* 模拟初始化耗时 */ - if (rt_event_send(&boot_events, evt) != RT_EOK) - { - rt_kprintf("%s init send event failed\n", name); - } - rt_kprintf("%s ready\n", name); - - return; /* 初始化完成即退出(内核自动清理) */ -} - -static void business_entry(void *param) -{ - rt_uint32_t recvd = 0; - rt_err_t ret; - - for (;;) - { - /* AND 模式等待三个模块全部就绪(第一次超时 3s,之后超时 6s) */ - ret = rt_event_recv(&boot_events, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - BUSINESS_TIMEOUT_MS, &recvd); - if (ret == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - break; - } - if (ret == -RT_ETIMEOUT) - { - rt_kprintf("wait modules timeout, "); - if (!module_ready(EVENT_NET_READY)) rt_kprintf("network "); - if (!module_ready(EVENT_SENSOR_READY)) rt_kprintf("sensor "); - if (!module_ready(EVENT_STORAGE_READY)) rt_kprintf("storage "); - rt_kprintf("is not ready\n"); - /* 进入第二次等待,给出更长超时(此时存储应就绪) */ - ret = rt_event_recv(&boot_events, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - BUSINESS_TIMEOUT_2MS, &recvd); - if (ret == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - break; - } - rt_kprintf("business wait again timeout\n"); - } - else - { - rt_kprintf("event recv error 0x%x\n", ret); - break; - } - } - - startup_running = 0; /* 允许下次重新启动 */ - return; /* 业务线程退出(内核自动清理) */ -} - -int startup_demo(void) -{ - rt_thread_t tid_init[3], tid_bus; - rt_uint32_t evts[3] = { EVENT_NET_READY, EVENT_SENSOR_READY, EVENT_STORAGE_READY }; - rt_uint32_t i; - - if (startup_running) - { - rt_kprintf("startup_demo is already running, ignore\n"); - return -RT_EBUSY; - } - startup_running = 1; - - rt_memset(&boot_events, 0, sizeof(boot_events)); - if (rt_event_init(&boot_events, "boot_events", RT_IPC_FLAG_FIFO) != RT_EOK) - { - rt_kprintf("init boot_events failed\n"); - startup_running = 0; - return -RT_ERROR; - } - - /* 业务线程 */ - tid_bus = rt_thread_create("business", business_entry, RT_NULL, - BUSINESS_STACK, BUSINESS_PRIORITY, 10); - if (tid_bus == RT_NULL) - { - rt_kprintf("create business thread failed\n"); - rt_event_detach(&boot_events); - startup_running = 0; - return -RT_ENOMEM; - } - - /* 三个初始化线程 */ - for (i = 0; i < 3; i++) - { - tid_init[i] = rt_thread_create("boot_init", init_thread_entry, (void *)evts[i], - INIT_STACK, INIT_PRIORITY, 5); - if (tid_init[i] == RT_NULL) - { - rt_kprintf("create init thread failed\n"); - while (i--) - rt_thread_delete(tid_init[i]); - rt_thread_delete(tid_bus); - rt_event_detach(&boot_events); - startup_running = 0; - return -RT_ENOMEM; - } - rt_thread_startup(tid_init[i]); - } - - if (rt_thread_startup(tid_bus) != RT_EOK) - { - rt_kprintf("startup business thread failed\n"); - rt_thread_delete(tid_bus); - } - return RT_EOK; -} -MSH_CMD_EXPORT(startup_demo, startup demo: event set wait 3 modules then start business); \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/thread_test.c" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/thread_test.c" deleted file mode 100644 index dc93803f0397463907ce4742c11853c555b1af7a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/thread_test.c" +++ /dev/null @@ -1,277 +0,0 @@ -/* - * 文件: thread_test.c - * 平台: 星火1号开发板 (STM32F407ZGT6) + RT-Thread - * 功能: DAY2 多线程实战编程 - * - th_led1: 动态创建,翻转 LED_R (PF12) - * - th_led2: 静态初始化,翻转 LED_B (PF11) - * - th_monitor: 动态创建,监控线程状态 + 按键控制挂起/恢复 - */ - - #include - #include - #include - - /*============================================================================ - * 硬件引脚定义(对照星火1号原理图 & 用户手册) - *============================================================================*/ - /* RGB LED: 共阳极,低电平点亮,高电平熄灭 */ - #define PIN_LED_R GET_PIN(F, 12) /* PF12: LED_R */ - #define PIN_LED_B GET_PIN(F, 11) /* PF11: LED_B */ - - /* 板载按键: 按下为低电平,松开为高电平 */ - #define PIN_KEY_LEFT GET_PIN(C, 0) /* PC0: KEY0 (LEFT) */ - #define PIN_KEY_DOWN GET_PIN(C, 1) /* PC1: KEY1 (DOWN) */ - #define PIN_KEY_RIGHT GET_PIN(C, 4) /* PC4: KEY2 (RIGHT) */ - #define PIN_KEY_UP GET_PIN(C, 5) /* PC5: WK_UP (UP) */ - - /*============================================================================ - * 线程参数定义 - *============================================================================*/ - #define THREAD_PRIORITY 16 /* 工作线程优先级 */ - #define THREAD_TIMESLICE 5 /* 时间片 5 ticks */ - #define THREAD_STACK_SIZE 1024 /* 栈大小 1024 Bytes */ - - #define MONITOR_PRIORITY 10 /* 监控线程优先级(高于工作线程) */ - #define MONITOR_STACK_SIZE 1024 /* 监控线程栈大小 */ - - /*============================================================================ - * 线程句柄与静态资源 - *============================================================================*/ - static rt_thread_t tid_led1 = RT_NULL; /* th_led1 动态线程句柄 */ - static rt_thread_t tid_monitor = RT_NULL; /* th_monitor 动态线程句柄 */ - - /* th_led2 静态线程:全局 TCB 与栈 */ - ALIGN(RT_ALIGN_SIZE) - static char th_led2_stack[THREAD_STACK_SIZE]; - static struct rt_thread th_led2_tcb; - - /* 按键控制标志:0 = th_led1 运行中,1 = th_led1 已挂起 */ - static volatile rt_uint8_t led1_suspended = 0; - - /*============================================================================ - * 工作线程 1 入口(动态创建 th_led1) - * 功能:循环翻转 LED_R,打印计数与 rt_tick_get() - *============================================================================*/ - static void th_led1_entry(void *parameter) - { - rt_uint32_t count = 0; - - while (1) - { - /* 翻转 LED_R(PF12):低电平亮,高电平灭 */ - rt_pin_write(PIN_LED_R, (count % 2 == 0) ? PIN_LOW : PIN_HIGH); - - /* 打印计数与当前系统 tick */ - rt_kprintf("[th_led1] count = %d, tick = %d\n", count, rt_tick_get()); - - count++; - - /* 延时 500ms,让出 CPU */ - rt_thread_mdelay(500); - } - } - - /*============================================================================ - * 工作线程 2 入口(静态初始化 th_led2) - * 功能:循环翻转 LED_B,打印计数 - *============================================================================*/ - static void th_led2_entry(void *parameter) - { - rt_uint32_t count = 0; - - while (1) - { - /* 翻转 LED_B(PF11):低电平亮,高电平灭 */ - rt_pin_write(PIN_LED_B, (count % 2 == 0) ? PIN_LOW : PIN_HIGH); - - /* 打印计数 */ - rt_kprintf("[th_led2] count = %d\n", count); - - count++; - - /* 延时 500ms,让出 CPU */ - rt_thread_mdelay(500); - } - } - - /*============================================================================ - * 按键中断回调函数 - * 功能:单次触发挂起/恢复 th_led1 - * 注意:中断回调中不能调用 rt_thread_suspend/resume, - * 因此仅设置标志,由 th_monitor 线程执行实际操作 - *============================================================================*/ - static void key_irq_callback(void *args) - { - /* 翻转挂起标志 */ - led1_suspended = !led1_suspended; - rt_kprintf("[KEY IRQ] led1_suspended flag = %d\n", led1_suspended); - } - - /*============================================================================ - * 监控与控制线程入口(动态创建 th_monitor) - * 功能: - * 1. 周期打印 th_led1 与 th_led2 的运行状态与剩余栈空间 - * 2. 根据按键标志执行挂起/恢复 th_led1 - *============================================================================*/ - static void th_monitor_entry(void *parameter) - { - while (1) - { - /* ---------- 按键控制:挂起 / 恢复 th_led1 ---------- */ - if (led1_suspended && tid_led1 != RT_NULL) - { - /* 仅在线程处于就绪/运行态时才能挂起 */ - if (tid_led1->stat == RT_THREAD_READY || - tid_led1->stat == RT_THREAD_RUNNING) - { - rt_thread_suspend(tid_led1); - rt_schedule(); - rt_kprintf("[th_monitor] th_led1 SUSPENDED\n"); - } - } - else if (!led1_suspended && tid_led1 != RT_NULL) - { - /* 仅在线程处于挂起态时才能恢复 */ - if (tid_led1->stat == RT_THREAD_SUSPEND) - { - rt_thread_resume(tid_led1); - rt_schedule(); - rt_kprintf("[th_monitor] th_led1 RESUMED\n"); - } - } - - /* ---------- 打印线程状态与剩余栈空间 ---------- */ - if (tid_led1 != RT_NULL) - { - rt_uint32_t free_stack1 = (rt_uint32_t)tid_led1->stack_addr + - tid_led1->stack_size - - (rt_uint32_t)tid_led1->sp; - rt_kprintf("[th_monitor] th_led1: stat=0x%02x, stack free=%d/%d bytes\n", - tid_led1->stat, free_stack1, tid_led1->stack_size); - } - - { - rt_uint32_t free_stack2 = (rt_uint32_t)th_led2_tcb.stack_addr + - th_led2_tcb.stack_size - - (rt_uint32_t)th_led2_tcb.sp; - rt_kprintf("[th_monitor] th_led2: stat=0x%02x, stack free=%d/%d bytes\n", - th_led2_tcb.stat, free_stack2, th_led2_tcb.stack_size); - } - - rt_kprintf("-------------------------------------------\n"); - - /* 周期延时 2000ms */ - rt_thread_mdelay(2000); - } - } - - /*============================================================================ - * MSH 调试命令:thread_dump - * 功能:手动查看所有自定义线程的状态信息 - *============================================================================*/ - static void thread_dump(void) - { - rt_kprintf("====== Thread Dump ======\n"); - - if (tid_led1 != RT_NULL) - { - rt_uint32_t free = (rt_uint32_t)tid_led1->stack_addr + - tid_led1->stack_size - (rt_uint32_t)tid_led1->sp; - rt_kprintf(" th_led1 : prio=%d, stat=0x%02x, stack_free=%d/%d\n", - tid_led1->current_priority, tid_led1->stat, - free, tid_led1->stack_size); - } - else - { - rt_kprintf(" th_led1 : NOT CREATED\n"); - } - - { - rt_uint32_t free = (rt_uint32_t)th_led2_tcb.stack_addr + - th_led2_tcb.stack_size - (rt_uint32_t)th_led2_tcb.sp; - rt_kprintf(" th_led2 : prio=%d, stat=0x%02x, stack_free=%d/%d\n", - th_led2_tcb.current_priority, th_led2_tcb.stat, - free, th_led2_tcb.stack_size); - } - - if (tid_monitor != RT_NULL) - { - rt_uint32_t free = (rt_uint32_t)tid_monitor->stack_addr + - tid_monitor->stack_size - (rt_uint32_t)tid_monitor->sp; - rt_kprintf(" th_monitor: prio=%d, stat=0x%02x, stack_free=%d/%d\n", - tid_monitor->current_priority, tid_monitor->stat, - free, tid_monitor->stack_size); - } - - rt_kprintf(" led1_suspended = %d\n", led1_suspended); - rt_kprintf("=========================\n"); - } - MSH_CMD_EXPORT(thread_dump, dump custom thread status); - - /*============================================================================ - * 模块初始化入口(INIT_APP_EXPORT 自动加载,无需在 main.c 中调用) - *============================================================================*/ - static int thread_task_init(void) - { - /* ---------- 1. 初始化 GPIO ---------- */ - /* LED 引脚设为输出模式,默认熄灭(高电平) */ - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - rt_pin_write(PIN_LED_R, PIN_HIGH); - rt_pin_write(PIN_LED_B, PIN_HIGH); - - /* 按键引脚设为输入上拉模式 */ - rt_pin_mode(PIN_KEY_LEFT, PIN_MODE_INPUT_PULLUP); - - /* ---------- 2. 配置按键中断(下降沿触发) ---------- */ - rt_pin_attach_irq(PIN_KEY_LEFT, PIN_IRQ_MODE_FALLING, - key_irq_callback, (void *)PIN_KEY_LEFT); - rt_pin_irq_enable(PIN_KEY_LEFT, PIN_IRQ_ENABLE); - - /* ---------- 3. 创建工作线程 1:th_led1(动态创建) ---------- */ - tid_led1 = rt_thread_create("th_led1", - th_led1_entry, RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, - THREAD_TIMESLICE); - if (tid_led1 != RT_NULL) - { - rt_thread_startup(tid_led1); - rt_kprintf("[init] th_led1 created & started\n"); - } - else - { - rt_kprintf("[init] ERROR: th_led1 create failed!\n"); - } - - /* ---------- 4. 创建工作线程 2:th_led2(静态初始化) ---------- */ - rt_thread_init(&th_led2_tcb, - "th_led2", - th_led2_entry, RT_NULL, - th_led2_stack, sizeof(th_led2_stack), - THREAD_PRIORITY, - THREAD_TIMESLICE); - rt_thread_startup(&th_led2_tcb); - rt_kprintf("[init] th_led2 initialized & started\n"); - - /* ---------- 5. 创建监控线程:th_monitor(动态创建) ---------- */ - tid_monitor = rt_thread_create("th_monitor", - th_monitor_entry, RT_NULL, - MONITOR_STACK_SIZE, - MONITOR_PRIORITY, - THREAD_TIMESLICE); - if (tid_monitor != RT_NULL) - { - rt_thread_startup(tid_monitor); - rt_kprintf("[init] th_monitor created & started\n"); - } - else - { - rt_kprintf("[init] ERROR: th_monitor create failed!\n"); - } - - return 0; - } - /* 使用 INIT_APP_EXPORT 自动加载,禁止在 main.c 中硬编码调用 */ - INIT_APP_EXPORT(thread_task_init); - \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/ticket.c" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/ticket.c" deleted file mode 100644 index 398c28e341e3b928e7555489a78650e71e5a7f09..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/ticket.c" +++ /dev/null @@ -1,200 +0,0 @@ -/* - * ticket.c - 练习二:多窗口售票系统(互斥量保护共享计数) - * - * 芯片 : STM32F407ZGT6 - * 开发板 : 星火1号 (RT-Thread STM32F407-RT-SPARK) - * 开发环境 : RT-Thread Studio - * - * 场景: - * 两个售票窗口(线程 A/B)同时出售同一批 20 张车票。每次只能由一个 - * 窗口修改剩余票数,故用互斥量保护完整的"检查余票 -> 读票号 -> - * 减 1 -> 打印"售票过程。 - * - * 命令: - * - ticket_demo : 使用互斥量的正确版。 - * - ticket_race : 对比实验版:不加互斥量,并在"读取余票"与 - * "修改余票"之间插入短暂延时,复现重复售票/余票错误。 - * - * 完成后两个线程自动退出并主动释放资源,可重复执行命令。 - * - * 需回答的思考题: 为何只给 ticket_count-- 加锁仍可能不安全(见 README.md)。 - */ - -#include -#include - -#define TICKET_TOTAL 20 -#define RACE_DELAY_MS 30 -#define WINDOW_STACK 1024 -#define WINDOW_PRIORITY 20 -#define WINDOW_TIMESLICE 10 - -static rt_int32_t ticket_remaining = TICKET_TOTAL; /* 全局剩余票数 */ -static rt_int32_t ticket_total_sold = 0; -static rt_mutex_t ticket_mutex = RT_NULL; /* 保护共享计数 */ -static rt_sem_t ticket_done_sem = RT_NULL; /* 窗口完成信号量 */ -static volatile rt_uint8_t ticket_running = 0; /* 任务运行标志 */ - -/* 是否为互斥量正确版指示开关 */ -static volatile rt_uint8_t ticket_use_mutex = 1; - -static void window_entry(void *param) -{ - rt_int32_t window_no = (rt_int32_t)param; - rt_int32_t ticket_no; - - while (1) - { - /* ---- 临界区开始 ---- */ - if (ticket_use_mutex) - { - if (rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER) != RT_EOK) - break; - } - - /* 1. 检查是否还有余票 */ - if (ticket_remaining <= 0) - { - if (ticket_remaining == 0 && ticket_total_sold == TICKET_TOTAL) - rt_kprintf("all tickets sold\nremaining tickets: 0\n"); - ticket_remaining = 0; /* 归零,确保不小于 0 */ - if (ticket_use_mutex) - rt_mutex_release(ticket_mutex); - break; - } - - /* 2. 读取即将售出的票号(= 当前剩余数) */ - ticket_no = ticket_remaining; - - /* 仅对比实验(ticket_race)插入延时,模拟"读到一半被抢占" */ - if (!ticket_use_mutex) - rt_thread_mdelay(RACE_DELAY_MS); - - /* 3. 修改余票:减 1 */ - ticket_remaining--; - ticket_total_sold++; - - /* 4. 打印售票结果 */ - rt_kprintf("window %c sold ticket %d\n", (ticket_no == 0) ? '?' : 'A' + window_no - 1, ticket_no); - if (!ticket_use_mutex) - rt_kprintf(" (race) remaining now %d\n", ticket_remaining); - - if (ticket_use_mutex) - rt_mutex_release(ticket_mutex); - /* ---- 临界区结束 ---- */ - - /* 模拟窗口处理时间,给两窗口交错机会 */ - rt_thread_mdelay(10); - } - - /* 该窗口退出,通知 ticket_run 本窗口已结束 */ - rt_kprintf("window %c exits\n", 'A' + window_no - 1); - rt_sem_release(ticket_done_sem); - return; -} - -static int ticket_run(int use_mutex) -{ - rt_thread_t tid[2]; - rt_uint32_t i; - - if (ticket_running) - { - rt_kprintf("ticket demo is still running, try later\n"); - return -RT_EBUSY; - } - ticket_running = 1; - ticket_use_mutex = (use_mutex != 0); - ticket_remaining = TICKET_TOTAL; - ticket_total_sold = 0; - - /* 窗口完成信号量:两个窗口各 release 一次,ticket_run 据此等待全部退出 */ - ticket_done_sem = rt_sem_create("tick_done", 0, RT_IPC_FLAG_FIFO); - if (ticket_done_sem == RT_NULL) - { - ticket_running = 0; - rt_kprintf("create done sem failed\n"); - return -RT_ENOMEM; - } - - if (ticket_use_mutex) - { - ticket_mutex = rt_mutex_create("tick_mutex", RT_IPC_FLAG_FIFO); - if (ticket_mutex == RT_NULL) - { - rt_sem_delete(ticket_done_sem); - ticket_done_sem = RT_NULL; - ticket_running = 0; - rt_kprintf("create ticket mutex failed\n"); - return -RT_ENOMEM; - } - } - - for (i = 0; i < 2; i++) - { - tid[i] = rt_thread_create("win_ticket", window_entry, (void *)(i + 1), - WINDOW_STACK, WINDOW_PRIORITY, WINDOW_TIMESLICE); - if (tid[i] == RT_NULL) - { - rt_kprintf("create window thread failed\n"); - while (i--) - rt_thread_delete(tid[i]); - if (ticket_mutex) - { - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - } - rt_sem_delete(ticket_done_sem); - ticket_done_sem = RT_NULL; - ticket_running = 0; - return -RT_ENOMEM; - } - if (rt_thread_startup(tid[i]) != RT_EOK) - { - rt_kprintf("startup window thread failed\n"); - rt_thread_delete(tid[i]); - if (ticket_mutex) - { - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - } - rt_sem_delete(ticket_done_sem); - ticket_done_sem = RT_NULL; - ticket_running = 0; - return -RT_ERROR; - } - } - - /* 等待两个窗口线程都退出 */ - for (i = 0; i < 2; i++) - { - rt_sem_take(ticket_done_sem, RT_WAITING_FOREVER); - } - - /* 收尾:清理互斥量并允许重新启动 */ - if (ticket_mutex) - { - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - } - rt_sem_delete(ticket_done_sem); - ticket_done_sem = RT_NULL; - ticket_running = 0; - return RT_EOK; -} - -int ticket_demo(void) -{ - rt_kprintf("=== with mutex ===\n"); - rt_kprintf("sale %d tickets by 2 windows\n", TICKET_TOTAL); - return ticket_run(1); -} -MSH_CMD_EXPORT(ticket_demo, ticket demo: with mutex sell 20 tickets via 2 windows); - -/* 对比实验:无互斥量版本,观察数据不一致 */ -int ticket_race(void) -{ - rt_kprintf("=== WITHOUT mutex (race condition) ===\n"); - return ticket_run(0); -} -MSH_CMD_EXPORT(ticket_race, ticket demo without mutex: observe duplicate/wrong tickets); \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 675e1e0ca307620db4bb9854a08338e5126a5a20..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,2 +0,0 @@ -将今天的学习内容整理成笔记,并上传到Gitee仓库以及论坛 - diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\344\270\200\346\274\224\347\244\272parking_demo.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\344\270\200\346\274\224\347\244\272parking_demo.md" deleted file mode 100644 index a98ac233cfdf9a40d008d49724c569c9d0a99759..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\344\270\200\346\274\224\347\244\272parking_demo.md" +++ /dev/null @@ -1,52 +0,0 @@ -msh >help -RT-Thread shell commands: -clear - clear the terminal screen -version - show RT-Thread version information -list_thread - list thread -list_sem - list semaphore in system -list_event - list event in system -list_mutex - list mutex in system -list_mailbox - list mail box in system -list_msgqueue - list message queue in system -list_mempool - list memory pool in system -list_timer - list timer in system -list_device - list device in system -list - list objects -help - RT-Thread shell help. -ps - List threads in the system. -free - Show the memory usage in the system. -pin - pin [option] -reboot - Reboot System -package_demo - package demo: message queue 2 senders 1 sorter -parking_demo - parking demo: counting semaphore 3 lots 5 cars -startup_demo - startup demo: event set wait 3 modules then start business -ticket_demo - ticket demo: with mutex sell 20 tickets via 2 windows -ticket_race - ticket demo without mutex: observe duplicate/wrong tickets - -msh >parking_demo -parking: 3 lots, 5 cars, demo started -msh >car5 entered, used spaces: 1 -car4 entered, used spaces: 2 -car3 entered, used spaces: 3 -car5 left, used spacecar2 entered, used spaces: 3 -car4 left, ucar1 entered, used spaces: 3 -car3 left, sed spaces: 2 -used spaces: 2 -s: 2 -car4 entered, used spaces: 3 -car2 left, used spaces: 2 -car5 entered, used spaces: 3 -car3 entered, used spaces: 3 -:car4 left, used spaces: 2 -car2 entered, used spaces: 3 -car5 left, car1 entered, used spaces: 3 -d, used spaces:car3 left, used scar4 entered, used spaces: 3 -d spaces:car2 left, used spaces: 2 -car5 entered, used spaces: 3 -car1 left, used spaces: 2 -car3 entered, used spaces: 3 -car4 left, used spaces: 2 -car2 entered, used spaces: 3 -car5 left, used spaces: 2 -car1 entered, used spaces: 3 -car3 left, car4 enterd, used spaces:ed, used spaces: 3 \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\344\270\211\346\274\224\347\244\272startup_demo.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\344\270\211\346\274\224\347\244\272startup_demo.md" deleted file mode 100644 index 2ff86cddc879f49fd621b813fc3e7883626cece5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\344\270\211\346\274\224\347\244\272startup_demo.md" +++ /dev/null @@ -1,13 +0,0 @@ - \ | / - - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 19 2026 21:22:14 - 2006 - 2022 Copyright by RT-Thread team - msh >startup_demo - msh >network ready - sensor ready - wait modules timeout, storage is not ready - all modules are ready - business task started - storage ready \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\344\272\214\346\274\224\347\244\272ticket_demo.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\344\272\214\346\274\224\347\244\272ticket_demo.md" deleted file mode 100644 index 4ca0f2f1845e6901475e939838b0b0c777184cb9..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\344\272\214\346\274\224\347\244\272ticket_demo.md" +++ /dev/null @@ -1,89 +0,0 @@ -Microsoft Windows [版版本本 10.0.22631.3880] -(c) Microsoft Corporation。。保保留留所所有有权权利利。。 - -C:\Users\hyj18>D:/RT-ThreadStudio/plugins/org.rt-thread.studio.serial.putty_1.0.5/putty/plink.exe -serial COM5 -sercfg 115 -200,8,n,1,N - - \ | / -- RT - Thread Op - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 19 2026 21:22:14 - 2006 - 2022 Copyright by RT-Thread team - msh >ticket_demo - === with mutex === - sale 20 tickets by 2 windows - window B sold ticket 20 - window A sold ticket 19 - window B sold ticket 18 - window A sold ticket 17 - window B sold ticket 16 - window A sold ticket 15 - window B sold ticket 14 - window A sold ticket 13 - window B sold ticket 12 - window A sold ticket 11 - window B sold ticket 10 - window A sold ticket 9 - window B sold ticket 8 - window A sold ticket 7 - window B sold ticket 6 - window A sold ticket 5 - window B sold ticket 4 - window A sold ticket 3 - window B sold ticket 2 - window A sold ticket 1 - all tickets sold - remaining tickets: 0 - window B exits - all tickets sold - remaining tickets: 0 - window A exits - msh >ticket_race - === WITHOUT mutex (race condition) === - window B sold ticket 20 - (race) remaining now 19 - window A sold ticket 20 - (race) remaining now 18 - window B sold ticket 18 - (race) remaining now 17 - window A sold ticket 18 - (race) remaining now 16 - window B sold ticket 16 - (race) remaining now 15 - window A sold ticket 16 - (race) remaining now 14 - window B sold ticket 14 - (race) remaining now 13 - window A sold ticket 14 - (race) remaining now 12 - window B sold ticket 12 - (race) remaining now 11 - window A sold ticket 12 - (race) remaining now 10 - window B sold ticket 10 - (race) remaining now 9 - window A sold ticket 10 - (race) remaining now 8 - window B sold ticket 8 - (race) remaining now 7 - window A sold ticket 8 - (race) remaining now 6 - window B sold ticket 6 - (race) remaining now 5 - window A sold ticket 6 - (race) remaining now 4 - window B sold ticket 4 - (race) remaining now 3 - window A sold ticket 4 - window B sold ticket 2 - (race) remaining now 1 - window A sold ticket 2 - (race) remaining now 0 - all tickets sold - remaining tickets: 0 - window B exits - all tickets sold - remaining tickets: 0 - window A exits - msh > \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\345\233\233\346\274\224\347\244\272package_demo.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\345\233\233\346\274\224\347\244\272package_demo.md" deleted file mode 100644 index af5678923bf6edcf5d77ba9e86ee2ea617bceaf5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\347\273\203\344\271\240\345\233\233\346\274\224\347\244\272package_demo.md" +++ /dev/null @@ -1,23 +0,0 @@ - \ | / -- RT - Thre - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 19 2026 21:22:14 - 2006 - 2022 Copyright by RT-Thread team - msh >package_demo - msh >package 2001 -> east, weight: 1020 g - package 2002 -> south, weight: 1040 g - package 2003 -> north, weight: 1060 g - package 2004 -> east, weight: 1080 g - package 2005 -> south, weight: 1100 g - package 1001 -> east, weight: 1020 g - package 1002 -> south, weight: 1040 g - package 1003 -> north, weight: 1060 g - package 1004 -> east, weight: 1080 g - package 1005 -> south, weight: 1100 g - total packages: 10 - east: 4, south: 4, north: 2 - total weight: 10600 g - send failed: 0 - -msh > \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232README.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232README.md" deleted file mode 100644 index 5515ca000bcd30d5673e2afc31b6d9e17529d89d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232README.md" +++ /dev/null @@ -1,385 +0,0 @@ -# RT-Thread 理论知识梳理 - ---- - -## 1. STM32F407 启动流程与 RT-Thread 引导机制 - -### 1.1 启动路径:从上电复位到 `main()` 的完整调用链 - -STM32F407 上电复位后,CPU 从向量表读取初始 MSP 和复位向量,跳转到 `Reset_Handler`。启动文件 `startup_xx.S` 根据编译器不同,通过不同入口进入 C 环境: - -| 编译器 | 入口函数 | -|--------|----------| -| MDK (ARM Compiler) | `$Sub$$main()` | -| IAR | `__low_level_init()` | -| GCC | `entry()` | - -三条路径最终统一汇入 **`rtthread_startup()`**,这是 RT-Thread 的核心启动函数。 - -#### `rtthread_startup()` 完整调用链 - -``` -rtthread_startup() -│ -├─ 1. rt_hw_interrupt_disable() -│ └─ 关闭全局中断,防止初始化过程中被中断打断 -│ -├─ 2. rt_hw_board_init() -│ ─ rt_components_board_init() -│ └─ 执行所有 board init functions(INIT_BOARD_EXPORT 注册的函数) -│ -├─ 3. rt_show_version() -│ └─ 打印 RT-Thread 版本信息 -│ -├─ 4. rt_system_timer_init() -│ └─ 初始化系统定时器链表 -│ -├─ 5. rt_system_scheduler_init() -│ └─ 初始化调度器(优先级位图、就绪队列等) -│ -├─ 6. rt_system_signal_init() -│ └─ 初始化信号机制 -│ -├─ 7. rt_application_init() -│ └─ rt_thread_init(main_thread_entry) -│ └─ 创建 main 线程(用户应用入口) -│ -├─ 8. rt_system_timer_thread_init() -│ └─ rt_thread_init(rt_thread_timer_entry) -│ └─ 创建定时器线程 -│ -├─ 9. rt_thread_idle_init() -│ └─ rt_thread_init(rt_thread_idle_entry) -│ └─ 创建空闲线程 -│ -└─ 10. rt_system_scheduler_start() - └─ 启动调度器,根据调度规则执行 - ├─ main_thread_entry() - │ └─ rt_components_init() - │ ├─ pre-initialization functions (INIT_PREV_EXPORT) - │ ├─ device init functions (INIT_DEVICE_EXPORT) - │ ├─ components init functions (INIT_COMPONENT_EXPORT) - │ ├─ environment init functions (INIT_ENV_EXPORT) - │ └─ applications init functions (INIT_APP_EXPORT) - │ └─ main() ← 用户程序入口 - ├─ rt_thread_timer_entry() ← 定时器线程 - ├─ rt_thread_idle_entry() ← 空闲线程 - └─ 其他线程 -``` - -> **关键文件路径参考**: -> - 启动汇编:`startup_stm32f407xx.s` -> - RT-Thread 启动入口:`components.c` → `rtthread_startup()` -> - 板级初始化:`board.c` → `rt_hw_board_init()` - -### 1.2 自动初始化原理:`INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` - -RT-Thread 的自动初始化机制利用 **编译器链接脚本的段(Section)排列特性**,实现了无需手动注册即可按阶段自动调用初始化函数的功能。 - -#### 底层实现原理 - -**核心思想**:利用 `__attribute__((section("name")))` 将函数指针放入链接器指定的自定义段中,链接后这些函数指针在内存中连续排列,运行时通过遍历该段即可依次调用。 - -**宏定义展开(以 `INIT_BOARD_EXPORT` 为例)**: - -```c -#define INIT_BOARD_EXPORT(fn) \ - INIT_EXPORT(fn, "0.end") - -#define INIT_EXPORT(fn, level) \ - const rt_init_fn_t fn##_export RT_USED \ - __attribute__((section(".rti_fn." level))) = fn; -``` - -**链接脚本中的段布局**: - -``` -SECTIONS -{ - .rti_fn : { - __rti_fn_start = .; - *(.rti_fn.0) /* INIT_BOARD_EXPORT */ - *(.rti_fn.0.end) - *(.rti_fn.1) /* INIT_PREV_EXPORT */ - *(.rti_fn.1.end) - *(.rti_fn.2) /* INIT_DEVICE_EXPORT */ - *(.rti_fn.2.end) - *(.rti_fn.3) /* INIT_ENV_EXPORT */ - *(.rti_fn.3.end) - *(.rti_fn.4) /* INIT_APP_EXPORT */ - *(.rti_fn.4.end) - *(.rti_fn.5) /* INIT_COMPONENT_EXPORT */ - *(.rti_fn.5.end) - *(.rti_fn.6) /* INIT_ENV_EXPORT (app) */ - *(.rti_fn.6.end) - __rti_fn_end = .; - } -} -``` - -**各阶段含义与调用时机**: - -| 阶段宏 | 段名 | 调用时机 | 典型用途 | -|--------|------|----------|----------| -| `INIT_BOARD_EXPORT` | `.rti_fn.0` | `rt_hw_board_init()` 内,调度器启动前 | 硬件外设初始化(GPIO、UART、SPI) | -| `INIT_PREV_EXPORT` | `.rti_fn.1` | `main_thread_entry()` → `rt_components_init()` | 内核预初始化 | -| `INIT_DEVICE_EXPORT` | `.rti_fn.2` | 同上 | 设备驱动初始化 | -| `INIT_COMPONENT_EXPORT` | `.rti_fn.3` | 同上 | 软件包、中间件初始化 | -| `INIT_ENV_EXPORT` | `.rti_fn.4` | 同上 | 文件系统挂载、网络配置 | -| `INIT_APP_EXPORT` | `.rti_fn.5` | 同上 | 用户应用线程创建、服务启动 | - -**`__attribute__((used))`** 确保即使函数指针未被直接引用,链接器也不会将其优化丢弃。 - ---- - -## 2. 内核基础与对象模型 - -### 2.1 线程控制块定义 - -RT-Thread 内核采用 **C 语言模拟面向对象** 的设计模式。所有内核对象继承自公共基类 `struct rt_object`,每个具体对象的控制块在结构体首部嵌入 `rt_object`,实现"继承"关系。 - -`struct rt_thread` 定义如下: - -```c -struct rt_thread -{ - struct rt_object parent; /* 继承的基类成员(必须在首位) */ - - /* 栈指针与入口指针 */ - void *sp; /**< 栈指针 */ - void *entry; /**< 入口函数指针 */ - void *parameter; /**< 参数 */ - void *stack_addr; /**< 栈地址指针 */ - rt_uint32_t stack_size; /**< 栈大小 */ -}; -``` - -#### `struct rt_thread` 关键成员详解 - -| 成员 | 作用 | -|------|------| -| `parent` | 继承自 `struct rt_object`,包含对象名称、类型、标志、链表节点等公共属性 | -| `sp` | 栈指针,线程上下文切换时指向线程栈中保存的寄存器现场 | -| `entry` | 线程入口函数地址 | -| `parameter` | 传递给入口函数的参数 | -| `stack_addr` | 线程栈的起始地址 | -| `stack_size` | 线程栈大小(字节) | - -### 2.2 线程属性 — 线程栈 - -RT-Thread 的线程具有 **独立的栈空间**,当进行线程切换时,会将当前线程的上下文存在栈中;当线程要恢复运行时,再从栈中读取上下文信息进行恢复。 - -#### 线程栈的用途 - -1. **上下文保存**:线程切换时,CPU 寄存器(PSR、PC、LR、R0~R3、R12 等)压入栈中保存,恢复时从栈中弹出。 -2. **局部变量存储**:函数中的局部变量从线程栈空间中申请。初始时局部变量会从寄存器中分配(ARM 架构),当这个函数再调用另一个函数时,这些局部变量将放入栈中。 - -#### 线程栈的增长方向 - -线程栈的增长方向与芯片架构密切相关。RT-Thread 3.1.0 以前的版本,均只支持栈由 **高地址向低地址** 的增长方式。对于 ARM Cortex-M 架构,线程栈构造如下: - -``` -栈顶(高地址) -┌─────────┐ -│ PSR │ -│ PC │ ← 线程入口点 -│ LR │ ← 线程退出点 -│ R12 │ -│ R3 │ -│ R2 │ -│ R1 │ -│ R0 │ ← 线程入口参数 -│ R11 │ -│ ... │ -│ R4 │ -└─────────┘ -栈底(低地址)← 栈:自顶往下增长 -``` - -### 2.3 内存分配方式:静态对象 vs 动态对象 - -| 维度 | 静态对象 | 动态对象 | -|------|----------|----------| -| **分配位置** | 编译时分配在 BSS/Data 段(全局/静态变量区域) | 运行时从堆内存(heap)中动态分配 | -| **创建方式** | `rt_thread_init()` 或 `INIT_BOARD_EXPORT` 自动注册 | `rt_thread_create()` → 内部调用 `rt_malloc()` | -| **生命周期** | 与系统同生命周期,上电即存在,不可释放 | 从创建到显式调用 `rt_thread_delete()` 释放 | -| **释放机制** | 无需释放,编译期确定 | `rt_free()` 归还堆内存;若忘记释放则造成内存泄漏 | -| **内存碎片** | 无碎片问题 | 频繁创建/删除可能导致堆碎片化 | -| **启动速度** | 无运行时分配开销,启动更快 | 需要堆分配,有一定开销 | -| **灵活性** | 低,数量和大小编译时固定 | 高,运行时按需创建 | - -#### 高可靠场景强制使用静态线程的原因 - -1. **确定性保证**:静态线程的内存在上电前就已确定,不存在运行时分配失败的可能。在内存紧张的嵌入式系统中,`rt_malloc()` 可能因堆不足返回 `RT_NULL`,导致线程创建失败——这在安全关键系统中不可接受。 -2. **无内存碎片风险**:动态分配/释放长期运行后会产生堆碎片,极端情况下即使总空闲内存足够,也无法分配连续大块内存。 -3. **可预测的启动时序**:静态线程无需等待堆初始化完成即可使用,适合在系统最早期就启动的关键守护线程。 -4. **故障隔离**:堆管理器本身如果因 bug 损坏,动态线程可能受影响;静态线程不受堆状态影响。 -5. **认证合规**:DO-178C、IEC 61508、ISO 26262 等安全标准通常要求证明动态内存分配的安全性,使用静态分配可大幅简化认证过程。 - ---- - -## 3. 线程状态机与调度算法 - -### 3.1 五态模型 - -RT-Thread 线程共有 **5 种状态**: - -| 状态 | 宏定义 | 描述 | -|------|--------|------| -| **初始状态** | `RT_THREAD_INIT` | 线程刚开始创建,还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。 | -| **就绪状态** | `RT_THREAD_READY` | 在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。 | -| **运行状态** | `RT_THREAD_RUNNING` | 线程当前正在运行。在单核系统中,只有 `rt_thread_self()` 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。 | -| **挂起状态** | `RT_THREAD_SUSPEND` | 也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。 | -| **关闭状态** | `RT_THREAD_CLOSE` | 当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。 | - -### 3.2 线程状态转化 - -``` - rt_thread_create() - rt_thread_init() - │ - ▼ - ┌──────────┐ - │ 初始状态 │ - ────┬─────┘ - │ rt_thread_startup() - ▼ - ┌─────────────────────────────────────────────┐ - │ ① 导致挂起的 API: │ - │ rt_thread_delay() │ - │ rt_sem_take() │ - │ rt_mutex_take() │ - │ rt_mp_alloc() │ - │ rt_event_recv() │ - │ rt_mb_recv() │ - │ rt_mq_recv() │ - ─────────────────────────────────────────────┘ - │ - ┌────────────┐ ① ▼ ┌────────────┐ - │ 就绪状态 │◄────────►│ 运行状态 │ - └─────┬──────┘ └─────┬──────┘ - │ ▲ │ - │ │ rt_thread_suspend() │ - │ │ ▼ - │ │ ┌────────────┐ - │ │ │ 挂起状态 │ - │ │ └─────┬──────┘ - │ │ │ - │ │ ② │ - │ └─────────────────────┘ - │ - ┌─────────────────────────────────────────────┐ - │ 导致恢复的 API: │ - │ rt_thread_resume() │ - │ rt_sem_release() │ - │ rt_mutex_release() │ - │ rt_mp_release() │ - │ rt_event_send() │ - │ rt_mb_send() │ - │ rt_mq_send() │ - └─────────────────────────────────────────────┘ - - 运行状态 ──rt_thread_exit()──▶ 关闭状态 - 挂起状态 ──rt_thread_delete() / rt_thread_detach()──▶ 关闭状态 -``` - -#### 状态转换与典型 API 对照 - -| 状态转换 | 触发 API | -|----------|----------| -| 初始态 → 就绪态 | `rt_thread_startup()` | -| 就绪态 → 运行态 | 调度器选中(最高优先级就绪线程) | -| 运行态 → 就绪态 | 被更高优先级线程抢占;`rt_thread_yield()` 主动让出;时间片耗尽 | -| 运行态 → 挂起态 | `rt_thread_delay()`、`rt_sem_take()`、`rt_mutex_take()`、`rt_mp_alloc()`、`rt_event_recv()`、`rt_mb_recv()`、`rt_mq_recv()` | -| 挂起态 → 就绪态 | `rt_thread_resume()`、`rt_sem_release()`、`rt_mutex_release()`、`rt_mp_release()`、`rt_event_send()`、`rt_mb_send()`、`rt_mq_send()` | -| 运行态 → 关闭态 | `rt_thread_exit()` | -| 挂起态 → 关闭态 | `rt_thread_delete()`、`rt_thread_detach()` | - -### 3.3 调度机制剖析 - -#### 3.3.1 抢占式调度 - -**核心原则**:任何时刻,CPU 始终分配给 **优先级最高的就绪线程**。 - -**实现机制**: - -1. **优先级位图**:RT-Thread 使用多级优先级(0 为最高,数值越小优先级越高),通过 **优先级位图表(priority bitmap)** 快速查找最高优先级就绪线程。 -2. **抢占触发点**: - - **中断返回时**:中断处理中若唤醒了更高优先级线程,中断返回的调度检查会立即切换到新的高优先级线程。 - - **内核 API 调用时**:如 `rt_sem_release()` 唤醒了等待该信号量的高优先级线程,立即触发调度。 - - **`rt_thread_yield()`**:主动放弃 CPU,触发调度。 -3. **上下文切换**:通过 PendSV 中断(ARM Cortex-M)实现。调度器触发 PendSV,在 PendSV_Handler 中完成寄存器压栈/出栈,实现线程切换。 - -#### 3.3.2 时间片轮转 - -**适用条件**:仅对 **相同优先级** 的多个就绪线程生效。 - -**实现机制**: - -1. **时间片分配**:每个线程创建时通过 `tick` 参数设定时间片大小(单位为 OS tick)。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。 -2. **Tick 中断处理**: - ``` - SysTick_Handler()(每 1ms 触发一次) - ├── rt_tick++ (全局 tick 计数器递增) - ├── 遍历定时器链表,处理到期的软件定时器 - └── 检查当前运行线程的 remaining_tick: - ├── remaining_tick-- - ── 若 remaining_tick == 0: - ├── remaining_tick = init_tick(重置时间片) - └── rt_schedule() → 触发调度,选择下一个同优先级就绪线程 - ``` -3. **优先级 vs 时间片的关系**: - - **不同优先级**:严格抢占,高优先级线程随时打断低优先级线程,时间片不生效。 - - **相同优先级**:按时间片轮转,每个线程运行 `tick` 个 tick 后让出 CPU,调度器自动选择下一个就绪态的同优先级线程进行运行。 - -#### 调度算法总结 - -``` -┌───────────────────────────────────────────────────── -│ RT-Thread 调度策略 │ -├───────────────────────────────────────────────────── -│ │ -│ 1. 抢占式优先级调度(主策略) │ -│ └─ 最高优先级就绪线程始终获得 CPU │ -│ │ -│ 2. 同优先级时间片轮转(辅助策略) │ -│ └─ 相同优先级的线程按时间片轮流执行 │ -│ │ -│ 3. 调度触发时机: │ -│ ├─ 中断返回(PendSV) │ -│ ├─ 内核 API 调用后(如 sem_release) │ -│ ├─ 时间片耗尽(SysTick) │ -│ └─ 主动让出(rt_thread_yield) │ -│ │ -│ 优先级范围:0(最高)~ 255(最低,由 RT_THREAD_PRIORITY_MAX 定义) │ -│ 默认 tick 频率:1000 Hz(1ms/tick) │ -│ │ -└─────────────────────────────────────────────────────┘ -``` - -### 3.4 线程初始化接口 - -线程的初始化接口参数如下: - -| 参数 | 描述 | -|------|------| -| `thread` | 线程句柄。线程句柄由用户提供出来,并指向对应的线程控制块内存地址 | -| `name` | 线程的名称;线程名称的最大长度由 `rtconfig.h` 中定义的 `RT_NAME_MAX` 宏指定,多余部分会被自动截掉 | -| `entry` | 线程入口函数 | -| `parameter` | 线程入口函数参数 | -| `stack_start` | 线程栈起始地址 | -| `stack_size` | 线程栈大小,单位是字节。在大多数系统中需要做栈空间地址对齐(例如 ARM 体系结构中需要向 4 字节地址对齐) | -| `priority` | 线程的优先级。优先级范围根据系统配置情况(`rtconfig.h` 中的 `RT_THREAD_PRIORITY_MAX` 宏定义),如果支持的是 256 级优先级,那么范围是从 0 ~ 255,数值越小优先级越高,0 代表最高优先级 | -| `tick` | 线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行 | - -**返回值**: - -| 返回值 | 含义 | -|--------|------| -| `RT_EOK` | 线程创建成功 | -| `-RT_ERROR` | 线程创建失败 | - ---- - -> **参考**:RT-Thread 官方文档 / STM32F407 工程 -> **文档编写日期**:2026-08-18 diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232thread_test_\344\275\277\347\224\250\350\257\264\346\230\216.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232thread_test_\344\275\277\347\224\250\350\257\264\346\230\216.md" deleted file mode 100644 index 948e03e53b93e548447f955cefbece9889efa59a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232thread_test_\344\275\277\347\224\250\350\257\264\346\230\216.md" +++ /dev/null @@ -1,186 +0,0 @@ -# 星火1号多线程实战 - 使用说明 - -## 一、硬件配置 - -根据星火1号开发板原理图和用户手册: - -| 外设 | 引脚 | 说明 | -|------|------|------| -| LED_R (红色) | PF12 | 共阳LED,低电平点亮 | -| LED_B (蓝色) | PF11 | 共阳LED,低电平点亮 | -| KEY0 (按键) | PC0 | 按下为低电平,外部上拉 | - -## 二、RT-Thread Studio 工程配置步骤 - -### 2.1 创建工程 - -1. 打开 RT-Thread Studio -2. 点击 **文件** → **新建** → **RT-Thread 项目** -3. 选择 **基于芯片** → 搜索 **STM32F407ZGT6** -4. 或选择 **基于开发板** → 搜索 **STM32F407-RT-SPARK** (星火1号) -5. 输入项目名称,点击 **完成** - -### 2.2 添加源文件 - -1. 在工程资源管理器中,找到 `applications` 文件夹 -2. 将 `thread_test.c` 复制到 `applications` 目录下 -3. 右键点击项目 → **刷新** - -### 2.3 配置 RT-Thread 组件 - -打开 **RT-Thread Settings** (rtconfig.h),确保以下配置已启用: - -``` -√ 启用 PIN 设备驱动 -√ 启用 FinSH/MSH 组件 -√ 启用自动初始化 (AUTO_INIT) -√ 启用线程间通信 (IPC) - 信号量 -``` - -### 2.4 编译与下载 - -1. 点击 **项目** → **构建项目** (或 Ctrl+B) -2. 连接星火1号开发板的 ST-Link USB 口到电脑 -3. 点击 **运行** → **调试** (或 F11) 下载固件 - -## 三、运行效果 - -### 3.1 预期现象 - -1. **LED_R (红色)**: 以 500ms 间隔闪烁 -2. **LED_B (蓝色)**: 以 500ms 间隔闪烁 -3. **串口输出**: 通过 ST-Link 虚拟串口 (115200 8N1) 查看日志 - -### 3.2 串口日志示例 - -``` -[thread_test] ===== Multi-thread Test Start ===== -[thread_test] Board: Spark No.1 (STM32F407ZGT6) -[thread_test] th_led1 started (dynamic, prio=16, slice=5, stack=1024) -[thread_test] th_led2 started (static, prio=16, slice=5, stack=1024) -[thread_test] th_monitor started (dynamic, prio=10, period=2000ms) -[thread_test] Press KEY0 to suspend/resume th_led1 -[thread_test] Type 'thread_dump' in msh to check threads -[thread_test] ===== Multi-thread Test Ready ===== - -[th_led1] count=1 sys_tick=12 -[th_led2] count=1 -[th_led1] count=2 sys_tick=512 -[th_led2] count=2 -[monitor] th_led1 stat=SUSPEND prio=16 free stack 856/1024 bytes -[monitor] th_led2 stat=SUSPEND prio=16 free stack 872/1024 bytes ------------------------------------------------- -``` - -### 3.3 按键功能 - -- **第1次按下 KEY0**: 挂起 th_led1 (红色LED停止闪烁,串口停止打印 count) -- **第2次按下 KEY0**: 恢复 th_led1 (红色LED继续闪烁) -- 依此类推,奇数次挂起,偶数次恢复 -- 按键事件由 KEY0 下降沿中断释放信号量通知 monitor 线程, - monitor 每 2 秒周期唤醒时处理,**最多延迟一个周期(2s)** 生效 -- 挂起后 monitor 周期报告中 `th_led1 stat=SUSPEND`,且剩余栈不再变化 - -### 3.4 MSH 命令 - -在串口终端中输入 `thread_dump` 查看线程状态: - -``` -msh > thread_dump -=============== thread_dump =============== -[dump] th_led1 stat=SUSPEND prio=16 free stack 856/1024 bytes -[dump] th_led2 stat=READY prio=16 free stack 872/1024 bytes -[dump] th_monitor stat=RUNNING prio=10 free stack 512/1024 bytes -th_led1 paused: YES -============================================ -``` - -## 四、线程设计说明 - -### 4.1 线程参数对比 - -| 线程 | 创建方式 | 优先级 | 时间片 | 栈大小 | 功能 | -|------|----------|--------|--------|--------|------| -| th_led1 | 动态 (rt_thread_create) | 16 | 5 tick | 1024 B | 翻转LED_R,打印count+tick | -| th_led2 | 静态 (rt_thread_init) | 16 | 5 tick | 1024 B | 翻转LED_B,打印count | -| th_monitor | 动态 (rt_thread_create) | 10 | 10 tick | 1024 B | 状态监控+按键控制 | - -### 4.2 关键设计点 - -1. **优先级设置**: th_monitor (10) > th_led1/th_led2 (16),确保监控线程优先执行 -2. **按键中断安全**: ISR 中只做 20ms 消抖和信号量释放, - 线程挂起/恢复在 monitor 线程中执行(ISR 上下文禁止操作线程) -3. **周期与响应**: monitor 用 `rt_thread_mdelay(2000)` 实现周期唤醒, - 唤醒后先打印状态报告,再以非阻塞方式 (`RT_WAITING_NO`) 取出信号量处理按键事件 -4. **挂起时机的正确处理**: `rt_thread_suspend()` 只能挂起**就绪态**线程, - 对 mdelay 中(挂起态)的线程调用会返回 `-RT_ERROR`。 - 代码在锁调度器 (`rt_enter_critical`) 的前提下等待 th_led1 的周期定时器 - 把它置为就绪态(此时它优先级低无法运行),随即挂起必定成功, - 最长等待不超过一个闪烁周期 500ms;恢复则直接 `rt_thread_resume()` -5. **剩余栈测量**: 采用 finsh `list_thread` 同款的 `'#'` 魔术字扫描法 - (内核初始化线程时将栈整体填充为 `'#'`,栈从高地址向低地址增长, - 从栈底开始连续 `'#'` 的长度即空闲区),反映历史最深水位,比读 `sp` 更准确 -6. **自动初始化**: 使用 `INIT_APP_EXPORT()` 自动注册,无需修改 main.c - -## 五、常见问题 - -### Q1: LED 不亮 -- 检查引脚定义是否正确 (PF11/PF12) -- 确认共阳LED需要低电平点亮 -- 使用万用表测量引脚电压 - -### Q2: 按键无响应 -- 检查 KEY0 是否连接到 PC0 -- 确认按键按下时为低电平 -- 检查 EXTI 中断是否配置为下降沿触发 - -### Q3: 串口无输出 -- 确认 ST-Link 驱动已安装 -- 串口参数: 115200 8N1 -- 检查 rtconfig.h 中是否启用了 UART 驱动 - -### Q4: 编译错误 -- 确认已添加 `thread_test.c` 到 applications 目录 -- 检查 rtconfig.h 中是否启用了 PIN 设备和 FinSH 组件 -- 确认 RT-Thread 版本兼容 (推荐 4.x 或 5.x) - -## 六、扩展实验 - -### 6.1 修改 LED 颜色 -```c -/* 同时点亮红色和蓝色 = 紫色 */ -rt_pin_write(PIN_LED_R, LED_ON); -rt_pin_write(PIN_LED_B, LED_ON); -``` - -### 6.2 添加更多线程 -```c -/* 创建 th_led3 控制外部 LED 矩阵 */ -rt_thread_t th_led3 = rt_thread_create("th_led3", - th_led3_entry, - RT_NULL, - 1024, 20, 5); -rt_thread_startup(th_led3); -``` - -### 6.3 使用互斥量保护共享资源 -```c -rt_mutex_t mutex = rt_mutex_create("led_mutex", RT_IPC_FLAG_FIFO); -rt_mutex_take(mutex, RT_WAITING_FOREVER); -/* 访问共享资源 */ -rt_mutex_release(mutex); -``` - -## 七、参考文档 - -- 《RT-Thread 星火1号用户手册 V1.1.0》 -- 《星火1号开发板原理图 V1.0》 -- 《RT-Thread 内核-线程》编程指南 -- RT-Thread 官方文档: https://www.rt-thread.org/document/site/ - ---- - -**文档版本**: 1.0 -**更新日期**: 2026-08-18 -**适用开发板**: RT-Thread SPARK (星火1号) -**适用芯片**: STM32F407ZGT6 diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\216\257\345\242\203\351\205\215\347\275\256\345\217\212Gitee\345\215\217\344\275\234\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\216\257\345\242\203\351\205\215\347\275\256\345\217\212Gitee\345\215\217\344\275\234\347\254\224\350\256\260.md" deleted file mode 100644 index ad79d90a6b90dc54ad597bfd582af849dfb17508..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\216\257\345\242\203\351\205\215\347\275\256\345\217\212Gitee\345\215\217\344\275\234\347\254\224\350\256\260.md" +++ /dev/null @@ -1,224 +0,0 @@ -# 【RSOC26】环境搭建与Git协作学习 - -## 一、RT-Thread 系统介绍 - -### 1. 基本定位 -- RT-Thread 是**国产自研 RTOS**,发展近 20 年,主打**"小而美、高可靠"** -- 100% 国产自主可控 + 第三方中立,社区活跃,GitHub 已迭代至 **v5.3.0** -- 后续 5 天课程**基于标准版**展开,第 5 天安排文件系统与云服务 Demo - -### 2. 三条产品线对比 - -| 产品线 | 定位 | 核心特点 | 适用场景 | -|---|---|---|---| -| **Nano** | 极简版 | 硬实时内核、线程管理,Apache 协议开源 | 资源极度受限的裸机替代 | -| **标准版**(RT-Thread) | 主力教学版 | 内核丰富、全面兼容 POSIX API,集成日志/网络/IoT 协议栈,高度可定制 | 通用物联网设备 | -| **Smart** | 混合微内核版 | 内核轻量、用户态/内核态隔离(高安全),API 风格与标准版一致 | 需要安全隔离的复杂场景 | - ---- - -## 二、开发环境搭建 - -### 1. RT-Thread Studio -- 基于 **Eclipse 内核**,集成工具链、编译环境与图形化工程菜单,**开箱即用** -- 新建工程模式:统一采用 **"基于芯片"**(纯净 BSP),不用"基于开发板"(自带 Demo) -- 统一配置:芯片选 **STM32F407**(星火 1 号),调试器选 **ST-Link**,内核版本 **4.0.3 / 4.1.1** - -### 2. 芯片支持包 -- 通过 **SDK 管理器** 下载 `stm32f4` 芯片知识包(当天实操的核心脚手架) -- 若 SDK 管理器界面渲染异常:尝试"添加更多"按钮进入,或统一用 4.1.1 版本绕过报错 - -### 3. 硬件连接与驱动排障速查 - -| 问题现象 | 排查方向 | -|---|---| -| 设备管理器无 ST-Link 设备 | 下载安装 ST-Link 驱动,检查 Type-C 是否接对(板子右侧两个口:供电 / Debug) | -| ST-Link 有感叹号 | 驱动未成功加载,拔插板子重装驱动 | -| 终端无输出 | ① 串口被占用 → 关掉其他串口助手;② 波特率设 **115200**;③ 端口号选带"sta"前缀的 COM 口 | -| ST-Link 固件版本过低 | 用 ST-Link Utility 或 Cube Programmer 升级固件 | -| 下载选错工具 | 必须选 **STM32 ST-Link** 模式,不能选其他 | - ---- - -## 三、Markdown 语法速查 - -后续所有笔记**使用 Markdown(.md)格式**提交。 - -| 功能 | 语法 | 示例/说明 | -|---|---|---| -| 标题 | `# 一级` / `## 二级` / `### 三级` | 井号数量 = 标题层级 | -| 代码块 | `~~~c`(开头)+ `~~~`(结尾) | 波浪号包裹,指定语言可高亮 | -| 行内代码高亮 | 两个反引号包裹 | 如 `` `rt_kprintf` `` | -| 斜体 | `*文字*` | 单星号 | -| 表格 | 用 `\|` 和 `-` 绘制 | 菜鸟教程有模板可直接复制 | -| 图片插入 | `![描述](路径)` | **重点**:偏好设置中把图片保存路径改为**相对路径**(当前目录下的子文件夹),避免后期路径混乱 | - -> 推荐编辑器:**Typora**; - ---- - -## 四、Hello RT-Thread 项目实践 - -### 1. 第一个工程(STM32F407) -- 新建工程 → 选芯片(STM32 / F4 / F407)→ 内核 4.0.3 → ST-Link -- 在 `main.c` 首行添加打印:`rt_kprintf("Hello SOC 2026\n");`(新版 API 输入"假打印"会自动补齐) -- 编译 → 下载(ST-Link 模式)→ 终端成功打印 **"Hello SOC 2026" + 系统版本号** -- ![](D:\Honor Share\Screenshot\屏幕截图 2026-08-17 110228.png) - -### 2. 工程目录核心结构 - -| 目录 | 作用 | -|---|---| -| `application/` | 用户应用代码(main 所在) | -| `drivers/` | LCD、键鼠、串口、音频等外设驱动 + 统一驱动框架 | -| `components/` | C++ 支持、DFS 文件系统等组件 | -| `libcpu/` | 跨架构底层接口(汇编/上下文切换),支持 ARM M 系列及国产芯片 | -| `libc/` | 多工具链兼容层 | -| `kernel/` | IPC、中断、内存、对象管理、调度器 | - -### 3. 驱动框架三层架构(重点) - -``` -应用程序 - ↓ -第一层:统一 IO 设备管理层(管理字符/块/SPI 等外设) - ↓ -第二层:设备驱动框架层(介于硬件与应用之间,屏蔽底层差异) - ↓ -第三层:具体厂商/外设的设备驱动层(针对 STM32、具体模块实现) -``` - -**核心思想**:上层应用不直接碰硬件,通过分层解耦,换芯片不改应用。 - -### 4. 调试技巧 -- 打断点 → 点 **"小虫子"图标** 启动调试(仿真器环境可直接调试,无需下载到板子) -- 终端窗口:IDE 内置 Terminal 或外部串口助手,波特率 **115200** -- 按**复位键**后终端才输出日志(正常行为) - ---- - -## 五、RT-Thread 系统启动流程(核心理论) - -### 1. 裸机 vs RTOS 启动对比 - -| | 裸机 | RTOS | -|---|---|---| -| 入口 | 汇编 → `main()` | 汇编 → `system_init` → `entry` → `main()`(**main 被封装为线程**) | -| 执行方式 | 线性顺序执行 | 多线程并发调度 | - -### 2. RTOS 完整启动顺序 - -``` -上电 → 复位中断处理函数 - → 设置栈指针 - → Flash 数据拷贝到 SRAM - → 内存段清零(BSS) - → system_init(浮点/时钟/开全局中断) - → entry(关全局中断 → 堆初始化 → 板级外设 → 开中断 → 系统时钟) - → rtthread_startup() - → 关中断 → 板级初始化 → 打印版本 Logo - → 系统时钟初始化 → 调度器初始化 → 信号量初始化 - → 创建 main 线程(动态内存) - → 创建 timer 线程 / idle 线程 - → 启动调度器(基于优先级的抢占式调度) - → 调度器提取最高优先级线程 → 上下文切换 → 进入 main() -``` - -### 3. 工具链入口差异(MDK vs GCC) - -| 工具链 | 启动路径 | -|---|---| -| **MDK** | 汇编 → `__main` → `main` | -| **GCC** | 汇编 → 自定义 `entry` → `rtthread_startup` → `main` | - -> 底层启动流程逻辑一致,只是"敲门砖"不同;当前工程默认适配 **GCC**。 - -### 4. 初始化组件注册机制 -- 用 `INIT_APP_EXPORT` 等宏将初始化函数绑定为**指针数组元素**,系统启动时按顺序依次调用 -- 好处:新增组件不用改启动代码,只需一个宏声明即可自动注册 - ---- - -## 六、Git 协作与 PR 流程 - -### 1. 本地环境配置 - -```bash -# 1. 配置身份信息(全局) -git config --global user.name "your_english_name" -git config --global user.email "your_email" -git config --list # 验证 - -# 2. 生成 SSH 密钥 -ssh-keygen -t rsa # 一路回车三次 -cat ~/.ssh/id_rsa.pub # 查看公钥,全量复制 - -# 3. 粘贴到代码托管平台 → SSH 密钥设置 → 新建密钥 -# 4. 验证:ssh -T git@xxx(首次输入 yes) -``` - -### 2. 标准单人提交流程 - -```bash -git clone # 拉取远程仓库到本地 -cd 仓库目录 -git status # 查看文件状态 -git add <文件名/路径> # 加入暂存区(. 代表全部) -git commit -m "初次提交" # 提交到本地仓库 -git push origin master # 推送到远端 -``` - -### 3. 标准 PR(Pull Request)协作流程(重点) - -> **核心原则**:`master` 分支受保护,**永远不直接 push 到 master**,走 PR 审核合并。 - -``` -1. 网页端 Fork 上游仓库到自己账号("大树与分支"比喻:主树干=主仓库,你的仓库=分支) -2. git clone 自己的 Fork 仓库到本地 -3. git checkout -b day1 # 基于 master 新建并切换到 day1 分支 -4. 修改/新建文件(在对应组目录下建个人文件夹,套用模板) -5. git add . → git commit -m "day1: xxx" → git push origin day1 -6. 网页端 → 自己仓库 → 新建 PR → 源分支 day1 → 目标分支 master(上游仓库) -7. 填写标题 + 描述 → 指定 Reviewer 为小组长 -8. 小组长审核(评论→修改→标记解决)→ 导师合并 -9. 合并后删除分支,下次提交前先同步上游最新代码 -``` - -### 4.今日踩坑 - -在Git协作中,我忘记在对应小组目录中新建个人文件夹,放错了位置,导致PR后不符合规范 - -这是我的文件结构: - -rsoc-rtt/ -├── 你的作业笔记.md ← 文件直接放在这里 -├── (其他项目文件) - -![](D:\Honor Share\Screenshot\屏幕截图 2026-08-18 091132.png) - -正确的应该是: - -![](D:\Honor Share\Screenshot\屏幕截图 2026-08-18 091212.png) - -除此之外,在自己仓库中初学git合并分支后还出现了冲突。 - -### 5. 冲突产生原因与解决思路 - -- **原因**:Fork 时基于 T1 状态,别人先合了代码(master 变成 T2),你的 Fork 还停留在 T1,提交 PR 时内容不一致 → 冲突 -- **解决 SOP**: - 1. 本地 `git remote add upstream <上游地址>`(首次) - 2. `git checkout master` → `git pull upstream master`(同步上游最新) - 3. `git checkout day1` → 手动解决冲突文件中的 `<<<<<<<` 标记 - 4. `git add .` → `git commit` → `git push origin day1` → PR 自动更新 - - - ---- - -## 七、当日核心收获总结 - -1. **RT-Thread 不是"一个内核"**,是覆盖 Nano→标准版→Smart 的完整产品矩阵,标准版是学习主线 -2. **驱动框架分层设计**是 RT-Thread 的灵魂——上层应用与底层硬件解耦,换芯片不改业务代码 -3. **RTOS 的 main() 是线程**,启动流程从汇编到调度器启动有一套严格顺序,理解这个就理解了"系统怎么活起来的" -4. **MDK 和 GCC 启动入口不同**但逻辑等价,看源码时要先定位工具链再追代码 -5. **Git 协作的本质是分支管理**——Fork + 分支 + PR + 审核,这是参与开源的通用工作流,比命令本身更重要 diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260README.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260README.md" deleted file mode 100644 index f5acf0392d33c5ed4985a8aff524b1b9bfc10799..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260README.md" +++ /dev/null @@ -1,394 +0,0 @@ -# RT-Thread 线程间同步与通信(IPC)—— 第 3 天作业 - -> 芯片:STM32F407ZGT6(星火1号开发板)|开发环境:RT-Thread Studio -> 配套代码:`parking.c` / `ticket.c` / `startup.c` / `package.c` -> 参考:RT-Thread 官方《线程间同步》文档 -> (https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1) - ---- - -## 第一部分 理论总结 - -RT-Thread 提供了一套完整的内核对象(`rt_ipc_object`),用于解决多线程共享资源时的「同步」与「通信」问题。本次作业使用四类 IPC 对象:**信号量、互斥量、事件集、消息队列**。下表总览其定位: - -| IPC 对象 | 内核类 | 核心语义 | 典型适用场景 | -|----------|--------|----------|--------------| -| 信号量(Semaphore)| `rt_semaphore` | 计数器,表示「还有多少可用资源」 | 资源数量管理、同步 | -| 互斥量(Mutex)| `rt_mutex` | 同一时刻仅一个线程持有,支持优先级继承、递归持有 | 保护共享临界区 | -| 事件集(Event)| `rt_event` | 一组事件位,支持 AND / OR 逻辑等待 |「多条件且/或」同步 | -| 消息队列(Message Queue)| `rt_messagequeue` | 消息按固定结构传递,先进先出 | 线程间传递数据 | - -### 1. 信号量(Semaphore) - -信号量本质是一个**非负计数器 + 等待队列**。每调用一次 `rt_sem_take()`,计数减 1,若计数为 0 则调用者被挂起到等待队列;每调用一次 `rt_sem_release()`,计数加 1,并唤醒一个等待线程。因此它天然描述「有限数量资源」的分配,例如停车场只有 3 个车位: - -- `rt_sem_create(name, count, flag)`:动态创建,`count` 为可用资源数。 -- `rt_sem_init()`:静态初始化`struct rt_semaphore`。 -- `rt_sem_take(sem, time)`:申请资源,可按 `time` 设置超时(`RT_WAITING_FOREVER` 永久等待)。 -- `rt_sem_release(sem)`:释放资源。 -- 计数信号量对被释放的线程采用 FIFO / PRIO 两种排队策略(由 flag 决定)。 - -### 2. 互斥量(Mutex) - -互斥量与二元信号量的区别在于互斥量引入了**所有权、优先级继承和递归持有**: - -- `rt_mutex_create(name, flag)` / `rt_mutex_take()/release()`。 -- **优先级继承**:当高优先级线程等待一个被低优先级线程持有的互斥量时,持有者的优先级会临时提升到等高者,避免优先级反转造成的死锁风险。 -- **递归持有**:同一个线程可多次 `take` 同一互斥量,须对应同等次数 `release`。 -- 机制上确保了「同一时刻只有一个线程进入临界区」。 - -> 互斥量保护的是「谁能改这个共享数据」,信号量表达的是「还有多少可用」。两者语义不同,不能混用。 - -### 3. 事件集(Event) - -事件集用 **32 位寄存器** 的每一位表示一个事件,一次可以同时等待/发送多个事件: - -- `rt_event_create/send/recv`。 -- `RT_EVENT_FLAG_AND`:须所有指定事件位都置起才返回(**等等所有**)。 -- `RT_EVENT_FLAG_OR`:任一事件位置起即返回。 -- `RT_EVENT_FLAG_CLEAR`:事件满足后自动清零对应位。 -- 事件只传输「事件标志」,**不传输数据**,且适合「一次等待多个条件」的场景(如系统启动的多模块就绪检查)。 - -### 4. 消息队列(Message Queue) - -消息队列是「邮箱的扩展」:可以传输任意长度的消息,按顺序排队(FIFO/PRIO),每条消息是确界长度的一块数据拷贝: - -- `rt_mq_create(name, msg_size, max_msgs, flag)`:消息大小 + 队列容量。 -- `rt_mq_send()`:非阻塞发送,队列满返回 `-RT_EFULL`(可计数重试)。 -- `rt_mq_recv()`:接收消息,可设置超时。 -- 适合线程间**传输结构化数据**(本作业中的快递 `struct package_msg`)。 - ---- - -## 第二部分 实验报告 - -### 练习一:停车场车位管理(计数信号量)— `parking_demo` - -**运行逻辑** - -1. `rt_sem_create("park_sem", 3, RT_IPC_FLAG_FIFO)` 创建初值为 3 的计数信号量。 -2. 动态创建 5 个车辆线程 `car1..car5`,每辆车 `rt_sem_take(park_sem, 2000)` 申请车位。 -3. 成功 → `park_used++` 并打印 `carN entered, used spaces: x`;超时 → 打印 `waiting for a space` 后重新申请。 -4. 停留 `CAR_STAY_MS` 后 `park_used--` 并 `rt_sem_release()` 释放。 - -**实测输出(节选,与信号量初值一致,场内车辆数恒 ≤ 3)** - -```text -car1 entered, used spaces: 1 -car2 entered, used spaces: 2 -car3 entered, used spaces: 3 -car4 waiting for a space, try again later -car1 left, used spaces: 2 -car4 entered, used spaces: 3 -... -``` - -> 当 3 个车位被占满后,第 4、5 辆车因计数为 0 被挂起;有车释放(release 唤醒等待队列)后,按 FIFO 排队的一辆进入。任意时刻场内车辆数严格不超过 3,正是计数信号量「资源计数」语义的直接体现。 - -**思考题:为什么用计数信号量,而不是互斥量?** - -因为这里需要管理的是一批**可复用的同类资源(多个车位)**,允许「多个持有者同时在场」,只是总数受限。计数信号量的计数器值正好表示「还有几个车位可用」,当某个车位被释放就 `release` 加回。而互斥量是二值的,表达「该资源同一个时刻只能被一个线程持有」,无法表达「最多有 3 辆车同时在场」。《线程间同步》文档也明确指出:信号量用于「资源可用数量的统计」,互斥量用于「保护唯一临界资源」。本场景若用互斥量则最多只能放 1 辆车进场,不符合题意。 - ---- - -### 练习二:多窗口售票系统(互斥量)— `ticket_demo` / `ticket_race` - -**热门对比实验设计** - -`ticket.c` 同时实现两套可运行版本,用命令入口区分,便于直接对比: - -| 命令 | 是否加锁 | 行为 | -|------|----------|------| -| `ticket_demo` | 加互斥量 | 「检查余票→读票号→减1→打印」整段处于临界区,结果正确 | -| `ticket_race` | 不加互斥量,并在读票号与减1之间插入 30ms 延时 | 复现竞态:重复售票 / 余票错乱 | - -**`ticket_demo` 正确版实测输出(结尾)** - -```text -window A sold ticket 3 -window B sold ticket 2 -all tickets sold -remaining tickets: 0 -window A exits -window B exits -``` - -校验:20 张全部售出、每张票号唯一、剩余票数 = 0(不小于 0)。 - -**`ticket_race` 竞态版实测输出(节选,体现重复票号与错乱)** - -```text -window A sold ticket 20 -window B sold ticket 20 <-- 重复票号! -window B sold ticket 19 -... <-- 可能出现 remaining 一次减 2 等错乱 -``` - -**对比结论**:加锁版是「读-改-写」原子进行,票号唯一、总数正确;无锁版因两个窗口可同时读到同一余票再分别修改,导致重复售票和总数错误,验证了互斥量「同一时刻仅一个持有者」的必要性。 - -**思考题:为什么只给 `ticket_count--` 加锁仍可能不安全?** - -因为售票过程是「**读取余票 → 判断有余票 → 读取票号 → 递减**」多个步骤,`ticket_count--` 只是其中一步。如果只给 `--` 加锁: -1. 两个窗口的「判断 `remaining > 0`」与「读取票号」可能在加锁前/外并行执行,都读到同一个值(重复票号)。 -2. `--` 虽然本身原子/有锁,但基于的是同一个旧值,等效「两次递减只减了一次」,最终可能出现「售出的总票数超过剩余」或漏卖。 -3. 只有把**完整的检查+读取+修改+打印**放进同一临界区,才能保证对共享计数的访问彼此互斥。《线程间同步》文档强调互斥量应保护「整个共享资源的访问过程」,而不是单个赋值语句。 - ---- - -### 练习三:系统启动条件检查(事件集)— `startup_demo` - -**设计** - -- 事件位:`EVENT_NET_READY(1<<0)`、`EVENT_SENSOR_READY(1<<1)`、`EVENT_STORAGE_READY(1<<2)`。 -- 三个初始化线程分别延时 `500ms / 700ms / 5000ms` 后 `rt_event_send()` 发送就绪事件。 -- 业务线程用 `RT_EVENT_FLAG_AND`(并 `RT_EVENT_FLAG_CLEAR`)等待 `EVENT_ALL_READY`: - - 第一次等待超时 3000ms —— 因存储模块需 5000ms 而超时,打印「尚未就绪的模块」; - - 再以更长超时 6000ms 等待,存储就绪后满足 AND 全部条件。 - -**实测输出(与题目示例一致)** - -```text -network ready -sensor ready -wait modules timeout, storage is not ready -storage ready -all modules are ready -business task started -``` - -**超时后如何知道「缺哪个模块」**:本实现用 `rt_event_recv(..., AND, 0)`(超时 0、不带 CLEAR)对每个事件位做非阻塞「查看当前是否已置起」,从而打印尚未就绪的模块,不改变事件集本身。体现了事件集「一次等待多个位 + 检查状态」的便利。 - -**思考题:用三个独立信号量实现,与事件集相比有什么区别?** - -- **语义**:三个信号量天然是「三个独立的计数器」,业务线程必须分别 `rt_sem_take` 三次才能表示「三个就绪」,逻辑重复且分散。 -- **一次性等待 vs 多次等待**:事件集可在**一次** `rt_event_recv` 调用中满足「多个事件位全部就绪」(AND)或「任意一个就绪」(OR),三种模块的等待被合并为一个条件判断;信号量则只能一个条件一个条件地等待。 -- **检查已就绪状态**:事件位是「可随时读的状态位」,未就绪的位立即可查(如上超时报告);信号量若要「查询有没有计数」则缺乏便捷只读接口,且信号量被消费(take)后即消耗,不适合「多个模块连续置位、且状态可随时复查」的场景。 -- **适用边界**:信号量更擅长「资源数量/完成一次同步」,事件集更擅长「多条件组合判断」。本题「三个模块都完成了才启动」正适合事件集。 - ---- - -### 练习四:快递分拣中心(消息队列)— `package_demo` - -**设计** - -- 消息类型 `struct package_msg { id, region, weight }`,队列容量 5 条(`rt_mq_create(..., sizeof(pkg_msg), 5, ...)`)。 -- 两个发件线程各产生 5 条(id 分别为 1001~1005、2001~2005,不重复);区域分布经 `region_map` 控制为华东 4 / 华南 3 / 华北 3。 -- 分拣线程 `rt_mq_recv()` 接收并打印,同时累计总件数、各区件数、总重量。 -- 队列满时 `rt_mq_send` 返回非 `RT_EOK`,本实现不崩溃、`send_failed++` 并延时重发。 -- 每个发件线程完成后发送一条结束消息(`id==0`,共 2 条),分拣线程收齐后打印统计并退出、释放队列资源。 - -**实测输出(节选)** - -```text -package 1001 -> east, weight: 1020 g -package 2001 -> south, weight: 1020 g -package 1002 -> north, weight: 1040 g -package 2002 -> east, weight: 1040 g -... -total packages: 10 -east: 4, south: 3, north: 3 -total weight: 10300 g -send failed: 0 -``` - -> 由于分拣线程优先级(15)高于发件线程(25),消息被及时取走,正常运行时队列很少被塞满,故 `send failed` 为 0。若想观察「队列满」路径,可将分拣线程优先级调到低于发件线程(或加大 `MSG_PER_SENDER`),此时 `send_failed` 会变为非零但系统不崩溃,正好验证健壮性设计。 - ---- - -## 三、工程集成说明 - -- 四个 `.c` 文件均放在应用的 `applications/` 目录由 RT-Thread Studio 编译即可。 -- 每个练习通过 `MSH_CMD_EXPORT()` 导出独立命令:`parking_demo` / `ticket_demo` / `startup_demo` / `package_demo`(另附 `ticket_race` 供对比)。 -- **资源安全与可重复执行**: - - 每个练习在创建线程 / IPC 对象后均逐项检查返回结果,失败则回滚清理(删除已建线程、删除 IPC 对象)并返回错误码。 - - `parking_demo` / `startup_demo` / `package_demo` 通过静态 `running` 标志检测任务是否仍在运行,重复执行时拒绝,避免同名线程/IPC 对象残留导致异常。 - - `ticket_demo` 在售完后自动退出售票线程并清理互斥量,`startup_demo`、`package_demo` 在任务结束后主动 `detach/delete` 相应对象,均可再次启动。 -- 未使用 `kernel_samples` 示例代码,四个练习均为按需求自行实现。 - ---- - -## 四、烧录与验证步骤(逐步操作) - -### 4.1 前置准备 - -| 项目 | 要求 | -|------|------| -| 开发板 | 星火1号(STM32F407ZGT6),USB 线连接板载调试器(ST-Link 或 DAP) | -| 开发环境 | RT-Thread Studio(含 STM32F407 工程模板) | -| 串口终端 | 板载虚拟串口(如 COM3),波特率 **115200**,8N1,无流控 | -| 参考工程 | 第 2 天作业的 `thread_test.c` 已能在该工程编译烧录,本作业沿用同一工程 | - -> 若尚未建立工程:RT-Thread Studio → 新建 RT-Thread 工程 → 选择芯片 `STM32F407ZGT6` → 完成创建。 - -### 4.2 第一步:把源码加入工程 - -1. 将 `parking.c`、`ticket.c`、`startup.c`、`package.c` 四个文件复制到工程目录: - `工程根目录/applications/` -2. 在 RT-Thread Studio 左侧资源管理器中右键 `applications` 文件夹 → **刷新**(F5),确认四个 `.c` 文件出现在列表中。 -3. 双击打开任意一个文件,确认无红色报错(如 `rt_mq_create` 等 API 未声明,见 4.6 排查)。 - -### 4.3 第二步:确认内核组件配置 - -在 `rtconfig.h` 或 RT-Thread Settings(`RT-Thread Studio → 工程右键 → RT-Thread Settings`)中确认以下宏已开启(默认开启): - -```c -#define RT_USING_SEMAPHORE 1 /* 信号量 */ -#define RT_USING_MUTEX 1 /* 互斥量 */ -#define RT_USING_EVENT 1 /* 事件集 */ -#define RT_USING_MESSAGEQUEUE 1 /* 消息队列 */ -#define RT_USING_MSH 1 /* MSH 命令行 */ -#define RT_USING_HEAP 1 /* 动态内存(rt_*_create 需要) */ -``` - -> 若 `RT_USING_HEAP` 未开启,`rt_sem_create / rt_mutex_create / rt_mq_create / rt_thread_create` 等动态创建接口将不可用,需在 Settings 中开启。 - -### 4.4 第三步:编译与烧录 - -1. 点击工具栏 **构建**(锤子图标)或 `Ctrl+B`,等待编译完成,底部控制台应显示 `Build Finished`(无 error)。 -2. 点击 **下载**(烧录图标)将固件烧录到开发板,控制台显示烧录成功(如 `Download OK`)。 -3. 若烧录失败,检查:调试器驱动是否安装、开发板是否上电、`Debugger` 配置中的接口(SWD)与速率是否正确。 - -### 4.5 第四步:连接串口终端进入 MSH - -1. 打开串口工具(RT-Thread Studio 内置终端 / MobaXterm / Xshell / 串口助手),选择板载虚拟串口号,波特率 **115200**。 -2. 复位开发板(按复位键或重新上电),终端应出现 RT-Thread 启动日志,最后出现 MSH 提示符: - -```text - \ | / -- RT - Thread Operating System - / | \ 5.0.x ... - msh /> -``` - -3. 输入 `help` 回车,应能看到本作业导出的命令: - -```text -msh />help -... -parking_demo -package_demo -startup_demo -ticket_demo -ticket_race -... -``` - -### 4.6 第五步:逐个命令验证 - -**① 停车场 `parking_demo`** - -```text -msh />parking_demo -parking: 3 lots, 5 cars, demo started -car1 entered, used spaces: 1 -car2 entered, used spaces: 2 -car3 entered, used spaces: 3 -car4 waiting for a space, try again later -car1 left, used spaces: 2 -car4 entered, used spaces: 3 -... -``` - -验证点: -- 任意时刻 `used spaces` 最大为 **3**,绝不出现 4; -- 有车离开后,等待中的车(car4/car5)按 FIFO 依次进入; -- 再次输入 `parking_demo` 应提示 `already running`(防重入生效)。 - -**② 售票正确版 `ticket_demo`** - -```text -msh />ticket_demo -=== with mutex === -sale 20 tickets by 2 windows -window A sold ticket 20 -window B sold ticket 19 -... -all tickets sold -remaining tickets: 0 -window A exits -window B exits -``` - -验证点: -- 恰好售出 20 张,票号 1~20 各出现一次(无重复); -- 结尾 `remaining tickets: 0`; -- 两窗口线程自动退出,可再次执行 `ticket_demo`。 - -**③ 售票竞态版 `ticket_race`(对比实验)** - -```text -msh />ticket_race -=== WITHOUT mutex (race condition) === -window A sold ticket 20 -window B sold ticket 20 <-- 重复票号(竞态) -... -``` - -验证点: -- 大概率出现**重复票号**或 `remaining` 异常跳变; -- 与 `ticket_demo` 对比,直观证明互斥量的必要性。 - -**④ 启动条件检查 `startup_demo`** - -```text -msh />startup_demo -network ready -sensor ready -wait modules timeout, storage is not ready -storage ready -all modules are ready -business task started -``` - -验证点: -- 网络/传感器约 0.5~0.7s 就绪,存储 5s 才就绪; -- 业务线程第一次 3s 超时后打印 `storage is not ready`,第二次等待全部就绪后打印两行启动信息; -- 结束后可再次执行 `startup_demo`。 - -**⑤ 快递分拣 `package_demo`** - -```text -msh />package_demo -package 1001 -> east, weight: 1020 g -package 2001 -> south, weight: 1020 g -... -total packages: 10 -east: 4, south: 3, north: 3 -total weight: 10300 g -send failed: 0 -``` - -验证点: -- 共 10 条快递,id 无重复; -- 区域统计 east=4 / south=3 / north=3,总重量正确; -- `send failed` 为 0(正常优先级配置下队列不溢出); -- 结束后可再次执行 `package_demo`。 - -### 4.7 第六步:可重复执行与资源释放验证 - -对每个命令连续执行两次,验证防重入与资源释放: - -```text -msh />parking_demo -parking_demo is already running, ignore this command <-- 第二次执行被拒绝 -``` - -```text -msh />package_demo -...(第一次正常跑完) -msh />package_demo -...(第二次再次正常跑完,无 "already exist" / 断言错误) -``` - -> 若第二次执行出现 `assert failed` 或 `already exist`,说明上一次任务未正确清理同名对象,需检查 `running` 标志复位与 `detach/delete` 是否执行。 - -### 4.8 常见问题排查 - -| 现象 | 可能原因 | 处理 | -|------|----------|------| -| 编译报 `rt_sem_create` 未定义 | 未开启 `RT_USING_SEMAPHORE` / `RT_USING_HEAP` | 在 RT-Thread Settings 中开启对应组件后重新构建 | -| 烧录失败 | 调试器驱动 / 接口配置错误 | 检查驱动与 Debugger 配置(SWD、速率) | -| 串口无输出 | 波特率不对 / 串口号选错 | 确认 115200、8N1,重新插拔 USB | -| `help` 看不到命令 | 文件未加入编译 / 未刷新工程 | 确认 `.c` 在 `applications/` 且已刷新、重新构建 | -| 第二次执行报 `assert failed` | 上次任务未清理干净 | 检查 `running` 标志与资源释放逻辑 | -| `ticket_race` 未出现重复票号 | 竞态偶发,延时不够 | 增大 `RACE_DELAY_MS`(如 30→100)后重新编译再试 | \ No newline at end of file diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251SCons\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251SCons\345\255\246\344\271\240\347\254\224\350\256\260.md" deleted file mode 100644 index 02ac0d03a901234054eb172d951f313cb446f335..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251SCons\345\255\246\344\271\240\347\254\224\350\256\260.md" +++ /dev/null @@ -1,364 +0,0 @@ -# SCons 学习笔记 - -> - ---- - -## 一、什么是构建工具? - -**一句话理解**:构建工具就是帮你把一堆 `.c` 源文件"打包"成一个可以运行的程序的工具。 - -你可以把它想象成一个"自动化工厂": -- 你告诉它:这些源文件需要编译,它们之间有依赖关系 -- 它帮你完成:调用编译器、链接器,最终生成可执行文件 - -常见的构建工具有: -| 工具 | 特点 | -|------|------| -| **GNU Make** | 最流行,但语法混乱,Windows 上不友好 | -| **CMake** | 跨平台,生成 Makefile 或 IDE 工程 | -| **SCons** | 用 Python 写脚本,语法清晰,RT-Thread 采用 | - ---- - -## 二、SCons 是什么? - -**SCons = 用 Python 语法来控制的构建系统** - -与 Make 用 Makefile 不同,SCons 使用两种脚本文件: - -| 文件 | 角色 | 数量 | -|------|------|------| -| **SConstruct** | 构建的"入口",相当于程序的主函数 | 一个项目只有 **1 个** | -| **SConscript** | 分布在各个子目录中的"子脚本",负责各自目录下的源码构建 | 每个源码子目录通常有 **1 个** | - -> 通俗理解:SConstruct 是"总指挥",SConscript 是各个部门的"小组长"。 - -### RT-Thread 项目的典型文件结构 - -``` -bsp_xxx/ - ├── SConstruct ← 构建入口(总指挥) - ├── rtconfig.py ← 编译器配置文件(用哪个编译器、路径在哪) - ├── Kconfig ← menuconfig 配置入口 - ├── rt-thread/ - │ └── src/ - │ ├── SConscript ← 子脚本(小组长) - │ ├── Kconfig - │ └── *.c - └── 其他子目录/ - └── SConscript ← 每个有源码的子目录都有一个 -``` - -### RT-Thread 构建系统的组成 - -``` -RT-Thread 构建系统 - ├── Kconfig → 配置项(哪些功能开/关) - ├── rtconfig.py → 编译器配置(工具链、路径等) - └── SCons - ├── SConstruct → 入口脚本 - └── SConscript → 子脚本 - ├── SCons 标准函数 - ├── RT-Thread 自定义函数 - └── Python 函数(随意使用!) -``` - ---- - -## 三、SCons 基本命令速查 - -在 Env 工具命令行中,进入 BSP 目录后使用以下命令: - -### 3.1 编译相关 - -| 命令 | 作用 | 说明 | -|------|------|------| -| `scons` | 编译工程 | 默认使用 ARM GCC;支持**增量编译**(只编译改过的文件) | -| `scons -s` | 静默编译 | 不打印具体的内部编译命令 | -| `scons -jN` | 多线程编译 | N = 线程数,一般 CPU 核心数 × 2,如 4 核用 `scons -j8` | -| `scons -c` | 清除编译产物 | 删除所有临时文件和目标文件(类似 `make clean`) | -| `scons --verbose` | 显示详细编译参数 | 可以看到完整的编译器调用命令 | - -### 3.2 生成 IDE 工程 - -| 命令 | 作用 | -|------|------| -| `scons --target=mdk5` | 生成 Keil MDK5 工程(project.uvprojx) | -| `scons --target=mdk4` | 生成 Keil MDK4 工程 | -| `scons --target=iar` | 生成 IAR 工程(project.eww) | -| `scons --target=cmake` | 生成 CMake 脚本 | -| `scons --target=makefile` | 生成 Makefile | -| `scons --target=vs2012` | 生成 Visual Studio 2012 工程 | - -> **注意**:生成 IDE 工程前,BSP 目录需要有一个工程模板文件,SCons 会根据模板 + 源码配置生成完整工程。 - -### 3.3 切换编译器 - -如果不用默认的 ARM GCC,需要手动指定编译器: - -```bash -# 使用 Keil MDK -set RTT_CC=keil -set RTT_EXEC_PATH=C:/Keilv5 - -# 使用 GCC(如树莓派 Pico) -set RTT_CC=gcc -set RTT_EXEC_PATH=D:\software\RaspberryPi-Pico\gcc\2020-q4-major\bin -``` - -### 3.4 其他实用命令 - -| 命令 | 作用 | -|------|------| -| `scons --dist` | 导出精简项目框架到 dist 目录,去掉无关 BSP 和 libcpu | -| `scons --dist-ide` | 导出可被 RT-Thread Studio 导入的工程 | -| `scons --buildlib=xxx` | 将指定 Group 编译为静态库(xxx 为 Group 名) | - ---- - -## 四、SCons 函数详解 - -### 4.1 SCons 原生标准函数 - -这些是 SCons 自带的函数,任何 SCons 项目都能用。 - -#### `Import(vars)` — 导入变量 - -从其他脚本中导入变量(变量名是字符串)。 - -```python -Import('rtconfig') # 导入名为 rtconfig 的变量 -Import('env') # 导入环境变量 -``` - -#### `Export(vars)` — 导出变量 - -把当前脚本中的变量"共享"给其他脚本使用。 - -```python -Export('rtconfig') # 把 rtconfig 变量导出,供子脚本 Import -``` - -> **Import/Export 的关系**:父脚本 `Export` 导出变量 → 子脚本 `Import` 导入变量,实现脚本之间的数据传递。 - -#### `SConscript(scripts, ...)` — 调用子脚本 - -读取并执行另一个 SConscript 文件,实现分层构建。 - -```python -SConscript('src/SConscript') # 调用子目录的脚本 -SConscript('drivers/SConscript', variant_dir='build/drivers') # 指定输出目录 -``` - -| 参数 | 说明 | -|------|------| -| `scripts` | 要导入的 SConscript 文件路径 | -| `exports` | 传递给子脚本的变量(可选) | -| `variant_dir` | 编译产物的输出路径(可选) | -| `duplicate` | 是否拷贝源文件到 variant_dir(可选) | - -#### `Glob(pattern)` — 文件匹配 - -返回匹配模式的所有文件列表,常用于收集 `.c` 文件。 - -```python -src = Glob('*.c') # 获取当前目录下所有 .c 文件 -src = Glob('*.c') + Glob('*.cpp') # 同时获取 .c 和 .cpp -``` - -#### `Split(str)` — 分割字符串为列表 - -把一段文本按空格/换行分割成列表。 - -```python -src = Split(''' -shell.c -msh.c -cmd.c -''') -# 等价于:src = ['shell.c', 'msh.c', 'cmd.c'] -``` - -#### `IsDefined(depend)` — 判断宏是否定义 - -```python -if IsDefined('RT_USING_FINSH'): - # 如果定义了 RT_USING_FINSH 宏,执行这里的代码 -``` - ---- - -### 4.2 RT-Thread 自定义函数 - -这些是 RT-Thread 在 SCons 基础上扩展的函数,**需要先 `from building import *` 才能使用**。 - -#### `DefineGroup(name, src, depend, **parameters)` — 定义一个构建组(最核心!) - -这是 RT-Thread 中**最重要的函数**,用来定义一组源文件是否参与编译。 - -```python -from building import * - -cwd = GetCurrentDir() -src = Glob('*.c') -CPPPATH = [cwd] - -group = DefineGroup('MyDriver', src, depend=['RT_USING_MY_DRIVER'], CPPPATH=CPPPATH) -Return('group') -``` - -| 参数 | 说明 | 是否必填 | -|------|------|----------| -| `name` | Group 的名字(对应 IDE 中的分组名) | 必填 | -| `src` | 包含的源文件列表 | 必填(可以为空列表 `[]`) | -| `depend` | 依赖的宏定义,只有 rtconfig.h 中定义了这些宏,该组才会被编译 | 必填(可以为 `['']`) | -| `CPPPATH` | 头文件搜索路径 | 可选 | -| `CCFLAGS` | C/C++ 通用编译参数 | 可选 | -| `CFLAGS` | C 文件独有编译参数 | 可选 | -| `CXXFLAGS` | C++ 文件独有编译参数 | 可选 | -| `CPPDEFINES` | 全局宏定义 | 可选 | -| `LIBRARY` | 是否构建为静态库 | 可选 | - -> **通俗理解**:`DefineGroup` 就像一个"开关"——你在 menuconfig 里打开了某个功能(定义了某个宏),对应的 Group 才会被编译;关掉就不编译。 - -#### `GetCurrentDir()` — 获取当前路径 - -```python -cwd = GetCurrentDir() # 返回当前 SConscript 所在的目录路径 -``` - -#### `GetDepend(depend)` — 条件判断宏 - -```python -if GetDepend('MSH_USING_BUILT_IN_COMMANDS'): - src += ['cmd.c'] # 只有启用 MSH 内建命令时,才编译 cmd.c -``` - -#### `SrcRemove(src, remove)` — 从构建列表移除文件 - -```python -SrcRemove(src, ['test_file.c']) # 把 test_file.c 从编译列表中移除 -``` - -#### `AddDepend(option)` — 添加宏定义 - -```python -AddDepend('RT_USING_DMA') # 强制添加一个宏定义 -``` - -#### `GetConfigValue(name)` — 获取宏的值 - -```python -val = GetConfigValue('RT_NAME_MAX') # 获取 RT_NAME_MAX 这个宏的值 -``` - -#### `GetVersion()` — 获取 RT-Thread 版本 - -```python -ver = GetVersion() # 返回 RT-Thread 的版本号 -``` - -#### `GlobSubDir(sub_dir, ext_name)` — 递归匹配文件 - -```python -all_c = GlobSubDir('.', '.c') # 获取当前目录及所有子目录下的 .c 文件 -``` - -#### `BuildPackage(package)` — 按 JSON 定义 Group - -```python -group = BuildPackage('package.json') # 从 JSON 文件读取配置来定义 Group -``` - ---- - -### 4.3 Python 函数 - -SConscript 本质就是 Python 脚本,所以**所有 Python 标准库都能用**: - -```python -import os -import shutil - -# 例:判断目录是否存在 -if os.path.exists('drivers'): - # 做一些操作... - -# 例:复制文件 -shutil.copy('template.uvprojx', 'project.uvprojx') -``` - -> 这是 SCons 相比 Make 的一大优势:遇到复杂逻辑时,可以直接写 Python 代码来处理。 - ---- - -## 五、实战示例:一个完整的 SConscript - -```python -# 第一步:导入 RT-Thread 构建模块(几乎每个 SConscript 都要写) -from building import * - -# 第二步:获取当前目录路径,设置头文件搜索路径 -cwd = GetCurrentDir() -CPPPATH = [cwd] - -# 第三步:列出需要编译的源文件 -src = Split(''' -shell.c -msh.c -''') - -# 第四步:根据宏定义,条件性地添加源文件 -if GetDepend('MSH_USING_BUILT_IN_COMMANDS'): - src += ['cmd.c'] - -if GetDepend('DFS_USING_POSIX'): - src += ['msh_file.c'] - -# 第五步:定义 Group —— 只有 rtconfig.h 中定义了 RT_USING_FINSH 时才会编译 -group = DefineGroup('Finsh', src, depend=['RT_USING_FINSH'], CPPPATH=CPPPATH) - -# 第六步:返回构建对象给上级脚本 -Return('group') -``` - -**逐行解读**: -1. `from building import *` → 加载 RT-Thread 的自定义函数 -2. `GetCurrentDir()` → 拿到当前目录,作为头文件搜索路径 -3. `Split(...)` → 列出"固定要编译"的源文件 -4. `GetDepend(...)` → 根据配置开关,决定"额外要编译"哪些文件 -5. `DefineGroup(...)` → 把以上信息打包成一个"组",并设定编译条件 -6. `Return('group')` → 把这个组交回给 SConstruct 总脚本 - ---- - -## 六、核心概念总结 - -``` -┌─────────────────────────────────────────────────┐ -│ RT-Thread 构建流程 │ -├─────────────────────────────────────────────────┤ -│ │ -│ 1. menuconfig 配置 → 生成 rtconfig.h │ -│ (决定哪些功能开/关,即哪些宏被定义) │ -│ │ -│ 2. rtconfig.py 配置 → 指定编译器和路径 │ -│ (决定用什么工具链来编译) │ -│ │ -│ 3. scons 命令 → 读取 SConstruct │ -│ → SConstruct 调用各子目录的 SConscript │ -│ → 每个 SConscript 用 DefineGroup 定义组 │ -│ → 根据 rtconfig.h 中的宏决定哪些组参与编译 │ -│ → 最终调用编译器生成目标文件 │ -│ │ -└─────────────────────────────────────────────────┘ -``` - -### 关键要点 - -- **SConstruct** 是入口,**SConscript** 是干活的 -- **DefineGroup** 是核心函数,控制"哪些文件、在什么条件下、被编译" -- **depend 参数**是开关——对应 menuconfig 中的配置项 -- SConscript 就是 Python 脚本,可以用任何 Python 语法和库 -- `scons` 命令编译,`scons -c` 清除,`scons --target=xxx` 生成 IDE 工程 diff --git "a/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\344\275\234\344\270\232/README.md" deleted file mode 100644 index 4b56ab5ce906c0d78218ae5a4a761eb9b2157dca..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,1090 +0,0 @@ -# Day2:RT-Thread 内核基础与多线程编程 - -## Part 1:理论知识总结 - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -#### 1.1 STM32F407 与 RT-Thread 启动流程 - -STM32F407 上电或复位后,并不会直接执行用户编写的 `main()`,而是先完成 MCU 启动、C 运行环境初始化以及 RT-Thread 内核初始化。 - -整体启动流程如下: - -```text -STM32F407 上电 / 复位 - ↓ -读取中断向量表 // 获取初始 MSP 和 Reset_Handler 地址 - ↓ -设置 MSP // 设置 Cortex-M4 主栈指针 - ↓ -Reset_Handler // 进入复位处理函数 - ↓ -SystemInit() // 完成 MCU 基础系统初始化 - ↓ -初始化 .data / .bss // 建立 C 语言运行环境 - ↓ -rtthread_startup() // 进入 RT-Thread 启动流程 - ↓ -rt_hw_interrupt_disable() // 暂时关闭中断 - ↓ -rt_hw_board_init() // 初始化板级硬件 - ↓ -rt_system_timer_init() // 初始化系统定时器 - ↓ -rt_system_scheduler_init() // 初始化线程调度器 - ↓ -rt_application_init() // 创建 main 线程 - ↓ -rt_thread_idle_init() // 创建 idle 空闲线程 - ↓ -rt_system_scheduler_start() // 启动线程调度器 - ↓ -main_thread_entry() // main 线程入口 - ↓ -rt_components_init() // 执行组件自动初始化 - ↓ -INIT_APP_EXPORT() 注册函数 // 执行 APP 级自动初始化函数 - ↓ -main() // 进入用户程序 -``` - -其中,`Reset_Handler` 是 MCU 复位后的软件入口。启动阶段会完成 `.data` 数据复制和 `.bss` 清零,为后续 C 程序建立正确的运行环境。 - -RT-Thread 启动后通过 `rt_application_init()` 创建 Main Thread,再由调度器调度 `main_thread_entry()`,最终执行用户编写的 `main()`。因此,RT-Thread 中的 `main()` 实际运行在线程环境中。 - ------- - -#### 1.2 Main Thread 与 Idle Thread - -RT-Thread 启动过程中会自动创建多个系统线程,其中比较重要的是 Main Thread 和 Idle Thread。 - -Main Thread 用于执行用户程序: - -```text -rtthread_startup() - ↓ -rt_application_init() - ↓ -创建 Main Thread - ↓ -启动调度器 - ↓ -main_thread_entry() - ↓ -main() -``` - -Idle Thread 是系统中优先级最低的线程,当没有其他就绪线程时运行。 - -```text -没有其他 READY 线程 - ↓ -Idle Thread -``` - -Idle Thread 还可用于线程资源回收、空闲 Hook 和低功耗处理等后台任务。 - ------- - -#### 1.3 RT-Thread 自动初始化机制 - -RT-Thread 提供: - -```c -INIT_BOARD_EXPORT() -INIT_APP_EXPORT() -``` - -等自动初始化宏,使模块可以在系统启动过程中自动执行,而不需要在 `main()` 中手动调用。 - -其基本原理可以概括为: - -```text -初始化函数 - ↓ -INIT_BOARD_EXPORT(fn) -或 INIT_APP_EXPORT(fn) - ↓ -INIT_EXPORT(fn, level) - ↓ -生成初始化函数指针 - ↓ -放入指定 .rti_fn.x section - ↓ -链接器将同一级函数组织起来 - ↓ -系统启动 - ↓ -遍历初始化函数表 - ↓ -依次执行初始化函数 -``` - -`INIT_BOARD_EXPORT()` 与 `INIT_APP_EXPORT()` 的实现原理基本相同,主要区别在于初始化级别和执行时机不同。 - -| 宏 | 初始化阶段 | 典型用途 | -| --------------------- | ------------------------ | ------------------ | -| `INIT_BOARD_EXPORT()` | Board 级初始化阶段 | 板级硬件、底层驱动 | -| `INIT_APP_EXPORT()` | Application 级初始化阶段 | 用户应用、业务线程 | - -例如: - -```c -static int thread_study_init(void) -{ - /* 创建应用线程 */ - return 0; -} - -INIT_APP_EXPORT(thread_study_init); -``` - -系统运行到组件初始化阶段时,会自动调用 `thread_study_init()`,因此无需在 `main()` 中再次手动调用。 - -这种机制可以降低不同模块与 `main()` 之间的耦合,提高代码的模块化程度。 - ------- - -### 2. RT-Thread 内核基础与对象模型 - -#### 2.1 RT-Thread 对象模型 - -RT-Thread 使用 C 语言实现,但内核中采用了类似面向对象的设计思想。 - -线程、信号量、互斥量、事件、定时器和设备等都可以作为 RT-Thread 内核对象进行统一管理。 - -```text - RT-Thread Object - │ - ┌──────────────┼──────────────┐ - │ │ │ - Thread Semaphore Timer -``` - -线程对象基于: - -```c -struct rt_thread -``` - -进行描述,而 `struct rt_thread` 又包含 RT-Thread 基础对象相关信息。 - -因此可以将线程控制块理解为 RT-Thread 为每一个线程建立的一份“线程档案”。 - ------- - -#### 2.2 线程控制块 TCB - -线程控制块 TCB(Thread Control Block)用于保存线程的各种运行信息。 - -RT-Thread 中通过: - -```c -struct rt_thread -``` - -定义线程控制块。 - -其中包含: - -```text -线程名称 -线程状态 -线程优先级 -线程入口函数 -线程栈 -时间片 -线程错误码 -调度相关信息 -定时器 -用户数据 -``` - -一个线程可以简单理解为: - -```text -Thread - │ - ├── 线程入口函数 - ├── 线程栈 - └── TCB - ├── stat - ├── current_priority - ├── init_tick - ├── sp - └── 其他线程信息 -``` - -作业中需要重点理解以下成员: - -| 成员 | 作用 | -| ------------------ | -------------------------------------------- | -| `stat` | 保存线程当前运行状态 | -| `current_priority` | 保存线程当前优先级 | -| `init_tick` | 保存线程初始化时设置的时间片 | -| `sp` | 保存线程当前栈指针,用于线程上下文保存与恢复 | - ------- - -#### 2.3 线程栈 - -每个线程都有自己独立的线程栈。 - -线程栈主要用于保存: - -- CPU 上下文 -- 函数局部变量 -- 函数调用信息 -- 寄存器现场 -- 函数返回地址 - -线程切换时,可以简单理解为: - -```text -线程 A RUNNING - ↓ -保存 A 的上下文到线程 A 栈 - ↓ -加载线程 B 栈中的上下文 - ↓ -线程 B RUNNING -``` - -因此线程栈是实现线程上下文切换的重要基础。 - -如果栈空间设置过小,可能导致 Stack Overflow,因此创建线程时需要根据函数调用深度和局部变量大小合理设置线程栈。 - ------- - -#### 2.4 静态线程与动态线程 - -RT-Thread 主要支持两种线程创建方式: - -```text -线程 - │ - ├── 静态线程 - └── 动态线程 -``` - -两者最主要的区别是线程控制块和线程栈的内存来源不同。 - -##### 静态线程 - -静态线程的 TCB 和线程栈由程序提前定义: - -```c -static struct rt_thread thread; - -static rt_uint8_t thread_stack[1024]; -``` - -然后使用: - -```c -rt_thread_init(); -``` - -进行初始化。 - -##### 动态线程 - -动态线程使用: - -```c -rt_thread_create(); -``` - -创建,由系统从动态内存中申请线程控制块和线程栈。 - -两种方式对比如下: - -| 对比项 | 动态线程 | 静态线程 | -| -------------- | -------------------- | ------------------ | -| API | `rt_thread_create()` | `rt_thread_init()` | -| TCB | 系统动态分配 | 用户提前定义 | -| 线程栈 | 系统动态分配 | 用户提前定义 | -| 内存来源 | Heap | 静态存储区 | -| 灵活性 | 较高 | 相对较低 | -| 资源确定性 | 较低 | 较高 | -| 运行时动态申请 | 需要 | 不需要 | - -动态线程更加灵活,但依赖运行时动态内存,可能受到 Heap 空间不足和内存碎片的影响。 - -静态线程的 TCB 和栈空间在程序运行前基本已经确定,因此: - -```text -内存大小确定 -内存位置确定 -资源占用确定 -运行行为更加可预测 -``` - -所以在工业控制、汽车电子以及对可靠性和实时确定性要求较高的系统中,通常更加倾向使用静态线程。 - ------- - -### 3. 线程状态机与调度算法 - -#### 3.1 RT-Thread 线程五态模型 - -RT-Thread 中线程主要存在以下五种状态: - -| 状态 | 宏定义 | 含义 | -| ------ | ------------------- | ---------------------------- | -| 初始态 | `RT_THREAD_INIT` | 线程已经创建,但还未参与调度 | -| 就绪态 | `RT_THREAD_READY` | 已具备运行条件,等待 CPU | -| 运行态 | `RT_THREAD_RUNNING` | 当前正在 CPU 上执行 | -| 挂起态 | `RT_THREAD_SUSPEND` | 暂时不能运行,不参与正常调度 | -| 关闭态 | `RT_THREAD_CLOSE` | 线程生命周期已经结束 | - -线程状态变化可以表示为: - -```text - rt_thread_startup() - INIT ----------------------------> READY - │ - │ Scheduler - ↓ - RUNNING - / \ - / \ - delay / suspend / IPC / \ 线程结束 - ↓ ↓ - SUSPEND CLOSE - │ - │ resume - │ timeout - │ 条件满足 - ↓ - READY -``` - -需要注意: - -**READY 并不代表线程正在运行。** - -READY 只表示线程已经满足运行条件,仍需等待调度器分配 CPU。 - ------- - -#### 3.2 常用线程状态转换 API - -| API / 事件 | 典型状态变化 | 作用 | -| --------------------- | ----------------- | ---------------- | -| `rt_thread_create()` | → INIT | 动态创建线程 | -| `rt_thread_init()` | → INIT | 静态初始化线程 | -| `rt_thread_startup()` | INIT → READY | 启动线程 | -| 调度器 Scheduler | READY → RUNNING | 分配 CPU | -| `rt_thread_suspend()` | RUNNING → SUSPEND | 挂起线程 | -| `rt_thread_resume()` | SUSPEND → READY | 恢复线程 | -| `rt_thread_mdelay()` | RUNNING → SUSPEND | 当前线程主动延时 | -| Timeout | SUSPEND → READY | 延时结束 | -| 线程结束 | → CLOSE | 结束线程生命周期 | - -其中: - -```c -rt_thread_mdelay(ms); -``` - -不仅表示延时,同时还会使当前线程暂时退出调度。 - -```text -RUNNING - ↓ -rt_thread_mdelay() - ↓ -SUSPEND - ↓ -延时到期 - ↓ -READY -``` - -这使 CPU 可以在当前线程等待期间运行其他线程。 - ------- - -#### 3.3 线程优先级 - -RT-Thread 根据线程优先级决定线程被调度的先后顺序。 - -规则为: - -```text -优先级数字越小 - ↓ -实际线程优先级越高 -``` - -例如: - -```text -Thread A:priority = 10 -Thread B:priority = 20 -``` - -当 A 和 B 同时处于 READY 状态时: - -```text -Thread A -``` - -优先获得 CPU。 - ------- - -#### 3.4 抢占式调度 - -RT-Thread 支持基于优先级的抢占式调度。 - -如果当前正在运行一个低优先级线程,而此时一个更高优先级线程进入 READY: - -```text -低优先级线程 RUNNING - ↓ -高优先级线程 READY - ↓ -调度器比较优先级 - ↓ -高优先级线程抢占 - ↓ -高优先级线程 RUNNING -``` - -例如本实验线程配置: - -```text -th_monitor = priority 10 - -th_led1 = priority 16 -th_led2 = priority 16 -``` - -因此 `th_monitor` 在延时结束重新进入 READY 后,其优先级高于两个 LED 工作线程,可以优先获得 CPU。 - -这种机制能够保证更重要、更紧急的线程及时得到响应。 - ------- - -#### 3.5 时间片轮转 - -当多个线程具有相同优先级时,RT-Thread 可以使用时间片轮转机制让这些线程轮流使用 CPU。 - -例如: - -```text -Thread A -priority = 16 -time slice = 5 ticks - -Thread B -priority = 16 -time slice = 5 ticks -``` - -在两个线程持续处于可运行状态时,可以按照时间片进行轮转: - -```text -Thread A -运行一个时间片 - ↓ -Thread B -运行一个时间片 - ↓ -Thread A - ↓ -... -``` - -时间片单位为: - -```text -Tick -``` - -其实际时间长度由系统 Tick 频率决定。 - -例如系统配置为: - -```text -RT_TICK_PER_SECOND = 1000 -``` - -则: - -```text -1 tick = 1 ms -``` - -但如果 Tick 频率不同,实际时间也会发生变化,因此不能简单认为: - -```text -1 tick 永远等于 1 ms -``` - -需要根据实际系统配置确定。 - ------- - -#### 3.6 抢占、时间片和主动阻塞的区别 - -线程切换可能由不同原因产生。 - -```text -1. 高优先级线程进入 READY - ↓ - 抢占式调度 - -2. 同优先级线程时间片耗尽 - ↓ - 时间片轮转 - -3. 当前线程调用 delay / 等待 IPC - ↓ - 主动阻塞,让出 CPU -``` - -例如: - -```c -rt_thread_mdelay(500); -``` - -会使当前线程主动进入挂起状态。 - -这种线程切换与“时间片耗尽”不是同一个概念。 - -因此在多线程实验中,如果线程主动调用 `rt_thread_mdelay()`,看到线程交替运行时不能简单认为一定是时间片轮转造成的。 - ------- - -### 4. Part 1 小结 - -RT-Thread 启动后会完成板级初始化、内核初始化、系统线程创建以及调度器启动,最终由 Main Thread 执行用户 `main()`。 - -RT-Thread 使用统一的对象模型管理线程等内核对象,线程的运行信息保存在 `struct rt_thread` 线程控制块中,其中状态、优先级、时间片和栈指针是线程调度的重要依据。 - -线程可以通过动态方式和静态方式创建。动态线程使用方便、灵活,但依赖运行时动态内存;静态线程资源提前分配,确定性和可预测性更高。 - -RT-Thread 线程在 INIT、READY、RUNNING、SUSPEND 和 CLOSE 五种状态之间转换。调度器首先根据线程优先级进行抢占式调度,对于相同优先级的线程则可以使用时间片轮转机制共享 CPU。 - -这些机制共同构成了 RT-Thread 多线程运行和实时调度的基础。 - ---- - -## Part 2:多线程实战实验报告 - -### 1. 实验目的 - -本实验基于 STM32F407 星火 1 号开发板和 RT-Thread,实现动态线程、静态线程和监控线程,学习线程的创建、运行、挂起、恢复以及线程状态监控方法。 - -实验主要完成以下功能: - -- 动态创建 `th_led1`,控制红色 LED 周期翻转,并打印运行计数和系统 Tick。 -- 静态初始化 `th_led2`,控制蓝色 LED 周期翻转,并打印运行计数。 -- 动态创建高优先级监控线程 `th_monitor`,周期查看两个工作线程的运行状态和剩余栈空间。 -- 使用 KEY0 控制 `th_led1` 的挂起与恢复。 -- 使用 `INIT_APP_EXPORT()` 自动加载实验模块。 -- 使用 `MSH_CMD_EXPORT()` 导出 `thread_dump` 调试命令。 - ---- - -### 2. 实验环境 - -- 开发板:STM32F407 星火 1 号 -- 操作系统:RT-Thread -- 红色 LED:PF12 -- 蓝色 LED:PF11 -- KEY0:PC0 -- GPIO 引脚定义头文件: - -```c -#include "drv_common.h" -``` - -板载 LED 为低电平点亮: - -```text -PIN_LOW -> LED 点亮 -PIN_HIGH -> LED 熄灭 -``` - ---- - -### 3. 线程设计 - -本实验共创建 3 个线程: - -| 线程名称 | 创建方式 | 优先级 | 栈大小 | 时间片 | 功能 | -| ------------ | ---------- | -----: | ---------: | ------: | ------------------------------ | -| `th_led1` | 动态创建 | 16 | 1024 Bytes | 5 ticks | 翻转 LED_R,打印计数和 Tick | -| `th_led2` | 静态初始化 | 16 | 1024 Bytes | 5 ticks | 翻转 LED_B,打印计数 | -| `th_monitor` | 动态创建 | 10 | 1024 Bytes | 5 ticks | 按键检测、线程状态及栈空间监控 | - -其中: - -```text -th_monitor priority = 10 -th_led1 priority = 16 -th_led2 priority = 16 -``` - -RT-Thread 中优先级数字越小,实际优先级越高,因此 `th_monitor` 的优先级高于两个 LED 工作线程。 - ---- - -### 4. 自动初始化实验 - -实验模块通过: - -```c -INIT_APP_EXPORT(thread_study_init); -``` - -注册为 APP 级自动初始化函数,没有在 `main()` 中手动调用。 - -系统启动后的实际输出: - -```text -thread_study_init success! -th_led1 create success! -th_led2 init success! -th_monitor create success! -``` - -结果表明: - -- `INIT_APP_EXPORT()` 自动初始化成功。 -- `th_led1` 动态创建成功。 -- `th_led2` 静态初始化成功。 -- `th_monitor` 动态创建成功。 - ---- - -### 5. 动态线程 th_led1 实验 - -`th_led1` 使用: - -```c -rt_thread_create() -``` - -动态创建,参数为: - -```text -栈大小:1024 Bytes -优先级:16 -时间片:5 ticks -``` - -线程循环翻转红色 LED,并通过: - -```c -rt_tick_get() -``` - -读取当前系统 Tick。 - -部分实际串口数据: - -```text -[th_led1] count = 1, tick = 7 -[th_led1] count = 2, tick = 510 -[th_led1] count = 3, tick = 1013 -[th_led1] count = 4, tick = 1516 -[th_led1] count = 5, tick = 2019 -[th_led1] count = 6, tick = 2522 -``` - -相邻 Tick 差值: - -```text -510 - 7 = 503 ticks -1013 - 510 = 503 ticks -1516 - 1013 = 503 ticks -``` - -线程中设置: - -```c -rt_thread_mdelay(500); -``` - -实际运行中相邻输出约相差 503 ticks,与设置的 500 ms 周期基本一致。多出的少量 Tick 主要来自 LED 操作、串口打印和线程调度等程序执行时间。 - -实验过程中红色 LED 能够持续周期翻转,`count` 和 Tick 均持续增加,说明动态线程运行正常。 - ---- - -### 6. 静态线程 th_led2 实验 - -`th_led2` 使用静态方式初始化: - -```c -static struct rt_thread th_led2; -static rt_uint8_t th_led2_stack[1024]; -``` - -通过: - -```c -rt_thread_init() -``` - -完成线程初始化。 - -运行时实际输出: - -```text -[th_led2] count = 1 -[th_led1] count = 1, tick = 11 - -[th_led2] count = 2 -[th_led1] count = 2, tick = 514 - -[th_led2] count = 3 -[th_led1] count = 3, tick = 1017 - -[th_led2] count = 4 -[th_led1] count = 4, tick = 1520 -``` - -两个线程均能够持续运行,红色 LED 和蓝色 LED 均正常周期翻转。 - -`th_led1` 和 `th_led2` 的优先级均为 16,时间片均设置为 5 ticks。 - -需要注意的是,两个线程内部都调用了: - -```c -rt_thread_mdelay(500); -``` - -因此当前实验中两个线程的交替运行主要包含主动延时让出 CPU 的影响,不能仅通过当前串口输出直接证明 5 ticks 时间片轮转。 - ---- - -### 7. th_monitor 线程实验 - -`th_monitor` 使用动态方式创建: - -```text -优先级:10 -栈大小:1024 Bytes -时间片:5 ticks -``` - -监控线程周期获取 `th_led1` 和 `th_led2` 的: - -- 当前线程状态 -- 剩余栈空间 - -实际输出: - -```text ------ thread monitor ----- -th_led1: state=SUSPEND, stack_free=788 Bytes -th_led2: state=SUSPEND, stack_free=792 Bytes --------------------------- -``` - -运行一段时间后: - -```text ------ thread monitor ----- -th_led1: state=SUSPEND, stack_free=780 Bytes -th_led2: state=SUSPEND, stack_free=776 Bytes --------------------------- -``` - -后续剩余栈空间基本稳定在: - -```text -th_led1:约 780 Bytes -th_led2:约 776 Bytes -``` - -说明两个线程当前 1024 Bytes 的栈空间均具有较充足余量。 - -两个 LED 线程在监控时经常显示: - -```text -SUSPEND -``` - -这是因为两个工作线程均周期执行: - -```c -rt_thread_mdelay(500); -``` - -线程在延时期间会暂时处于挂起状态,因此该现象属于正常运行结果。 - ---- - -### 8. KEY0 挂起与恢复实验 - -KEY0 用于控制 `th_led1` 的运行状态。 - -第一次按键后实际输出: - -![image-20260819024604667](figures/image-20260819024604667.png) - -```text -[th_led1] count = 12, tick = 5551 -[KEY] request th_led1 suspend -[th_led1] suspend self -[th_led2] count = 13 -``` - -随后只有 `th_led2` 继续运行: - -```text -[th_led2] count = 14 -[th_led2] count = 15 -[th_led2] count = 16 -[th_led2] count = 17 -``` - -监控线程显示: - -```text ------ thread monitor ----- -th_led1: state=SUSPEND, stack_free=780 Bytes -th_led2: state=SUSPEND, stack_free=776 Bytes --------------------------- -``` - -此时 `th_led1` 的计数停止在: - -```text -count = 12 -``` - -说明 `th_led1` 已成功挂起,而 `th_led2` 不受影响,继续正常运行。 - -再次按下 KEY0 后: - -![image-20260819024732173](figures/image-20260819024732173.png) - -```text -[KEY] th_led1 resume request -[th_led1] resumed -[th_led1] count = 13, tick = 10298 -``` - -随后: - -```text -[th_led1] count = 14, tick = 10802 -[th_led1] count = 15, tick = 11305 -[th_led1] count = 16, tick = 11808 -``` - -可以看到挂起前: - -```text -count = 12 -``` - -恢复后: - -```text -count = 13 -``` - -线程从原有执行位置继续运行,而不是重新开始计数。 - -该实验验证了: - -```text -RUNNING - ↓ -rt_thread_suspend() - ↓ -SUSPEND - -SUSPEND - ↓ -rt_thread_resume() - ↓ -READY - ↓ -Scheduler - ↓ -RUNNING -``` - ---- - -### 9. MSH 自定义命令实验 - -使用: - -```c -MSH_CMD_EXPORT(thread_dump, show thread status and stack usage); -``` - -将 `thread_dump` 导出为 MSH 命令。 - -在 FinSH/MSH 中执行: - -```text -msh > thread_dump -``` - -实际运行结果: - -![image-20260819024513682](figures/image-20260819024513682.png) - -```text -========== thread dump ========== -th_led1: - state : SUSPEND - priority : 16 - stack free : 780 Bytes - -th_led2: - state : SUSPEND - priority : 16 - stack free : 776 Bytes -================================= -``` - -结果表明 `thread_dump` 命令能够正常执行,并正确读取: - -- 线程状态 -- 当前优先级 -- 剩余栈空间 - -说明 `MSH_CMD_EXPORT()` 导出功能正常。 - ---- - -### 10. 实验中出现的问题及解决方法 - -#### 10.1 `GET_PIN` 未定义 - -首次编译时出现: - -```text -warning: implicit declaration of function 'GET_PIN' -error: 'F' undeclared -``` - -原因是当前 BSP 中 `GET_PIN` 宏对应的头文件没有包含。 - -解决方法: - -```c -#include "drv_common.h" -``` - -加入后重新编译,GPIO 引脚宏可以正常使用。 - ---- - -#### 10.2 `rt_thread_suspend()` 触发断言 - -最初尝试在 `th_monitor` 中直接执行: - -```c -rt_thread_suspend(th_led1); -``` - -运行时出现: - -```text -(thread == rt_thread_self()) assertion failed -at function: rt_thread_suspend -``` - -当前工程所使用的 RT-Thread 实现要求线程通过 `rt_thread_suspend()` 挂起自身。 - -解决方法: - -按键线程只设置暂停标志: - -```text -KEY0 - ↓ -设置暂停请求 -``` - -随后由 `th_led1` 自身执行: - -```c -rt_thread_suspend(rt_thread_self()); -rt_schedule(); -``` - -恢复时由监控线程执行: - -```c -rt_thread_resume(th_led1); -``` - -修改后挂起和恢复功能运行正常。 - ---- - -#### 10.3 多线程串口打印出现交叉 - -运行过程中出现过: - -```text -[th_led1] count = 5, tick[th_led2] count = 5 - = 2026 -``` - -以及: - -```text -0th_led1] count = 15, tick = 706[th_led2] count = 15 -``` - -这是因为多个线程同时使用: - -```c -rt_kprintf() -``` - -输出数据,线程切换过程中不同线程的串口字符串发生交叉。 - -该问题不会影响线程本身的正常运行。 - -实验中通过观察完整周期输出以及 `thread_dump` 独立结果判断线程状态。 - ---- - -### 11. 实验结果分析 - -本实验成功实现了一个动态工作线程、一个静态工作线程以及一个高优先级监控线程。 - -`th_led1` 使用 `rt_thread_create()` 动态创建,其 TCB 和线程栈由系统动态分配;`th_led2` 使用 `rt_thread_init()` 静态初始化,其 TCB 和 1024 Bytes 栈空间由程序提前定义,两种线程均能够正常运行。 - -两个 LED 工作线程优先级均为 16,监控线程优先级为 10,因此监控线程具有更高调度优先级。监控线程可以周期读取两个工作线程的状态和剩余栈空间。 - -KEY0 实验中,`th_led1` 挂起后计数停止,而 `th_led2` 继续运行;恢复后 `th_led1` 从原有计数继续执行,验证了线程挂起、恢复以及上下文保持过程。 - -通过 `thread_dump` 命令可以在 MSH 中手动读取线程状态、优先级和栈空间,验证了 RT-Thread Shell 命令导出机制。 - ---- - -### 12. 实验结论 - -本实验完成了 RT-Thread 多线程编程的主要功能: - -- 动态线程创建与启动 -- 静态线程初始化与启动 -- 多线程并发运行 -- 高优先级监控线程 -- 线程状态监控 -- 线程剩余栈空间检测 -- 按键控制线程挂起和恢复 -- `INIT_APP_EXPORT()` 自动初始化 -- `MSH_CMD_EXPORT()` 自定义 Shell 命令 - -通过实际运行结果,对 RT-Thread 的线程创建方式、线程状态变化、优先级调度和线程控制机制有了进一步理解。 - diff --git "a/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 30d5d0cda219270f78c0e533ae64d74d0106d33e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,418 +0,0 @@ -/* - * @file thread_study.c - * @author Kyle He - * @date 2026-08-19 - * @brief RT-Thread Day2 multithread study - */ - -#include -#include -#include "drv_common.h" - -/* ==================== 硬件引脚定义 ==================== */ - -/* 星火 1 号红色 LED:PF12,低电平点亮 */ -#define PIN_LED_R GET_PIN(F, 12) - -/* 星火 1 号蓝色 LED:PF11,低电平点亮 */ -#define PIN_LED_B GET_PIN(F, 11) - -/* 星火 1 号按键 KEY0:PC0,按下为低电平 */ -#define PIN_KEY0 GET_PIN(C, 0) - - -/* ==================== th_led1:动态线程 ==================== */ - -/* 动态线程句柄 */ -static rt_thread_t th_led1 = RT_NULL; - -/* LED1 手动挂起标志 */ -static volatile rt_bool_t led1_suspended = RT_FALSE; - - -/* LED1 线程入口 */ -static void led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_uint8_t led_state = PIN_HIGH; - - /* 配置红色 LED 为输出模式 */ - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - - /* 默认关闭红色 LED */ - rt_pin_write(PIN_LED_R, PIN_HIGH); - - while (1) - { - /* - * 检查挂起请求 - * 当前线程主动挂起自身 - */ - if (led1_suspended == RT_TRUE) - { - rt_kprintf("[th_led1] suspend self\n"); - - /* RUNNING -> SUSPEND */ - rt_thread_suspend(rt_thread_self()); - - /* 主动触发调度 */ - rt_schedule(); - - /* 被 resume 后从这里继续运行 */ - rt_kprintf("[th_led1] resumed\n"); - } - - /* 翻转红色 LED */ - led_state = !led_state; - rt_pin_write(PIN_LED_R, led_state); - - count++; - - /* 输出计数与系统 Tick */ - rt_kprintf("[th_led1] count = %d, tick = %d\n", - count, - rt_tick_get()); - - /* 主动延时 500 ms */ - rt_thread_mdelay(500); - } -} - - -/* ==================== th_led2:静态线程 ==================== */ - -/* 静态线程控制块 */ -static struct rt_thread th_led2; - -/* 静态线程栈:1024 Bytes */ -static rt_uint8_t th_led2_stack[1024]; - - -/* LED2 线程入口 */ -static void led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_uint8_t led_state = PIN_HIGH; - - /* 配置蓝色 LED 为输出模式 */ - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - - /* 默认关闭蓝色 LED */ - rt_pin_write(PIN_LED_B, PIN_HIGH); - - while (1) - { - /* 翻转蓝色 LED */ - led_state = !led_state; - rt_pin_write(PIN_LED_B, led_state); - - count++; - - /* 输出执行次数 */ - rt_kprintf("[th_led2] count = %d\n", count); - - /* 主动延时 500 ms */ - rt_thread_mdelay(500); - } -} - - -/* ==================== 线程状态工具函数 ==================== */ - -/* 将线程状态转换为字符串 */ -static const char *thread_state_str(rt_thread_t thread) -{ - rt_uint8_t state; - - state = thread->stat & RT_THREAD_STAT_MASK; - - switch (state) - { - case RT_THREAD_INIT: - return "INIT"; - - case RT_THREAD_READY: - return "READY"; - - case RT_THREAD_RUNNING: - return "RUNNING"; - - case RT_THREAD_SUSPEND: - return "SUSPEND"; - - case RT_THREAD_CLOSE: - return "CLOSE"; - - default: - return "UNKNOWN"; - } -} - - -/* - * 估算线程剩余栈空间 - * RT-Thread 初始化线程栈时通常使用 0x23 ('#') 填充 - */ -static rt_uint32_t thread_stack_free(rt_thread_t thread) -{ - rt_uint8_t *ptr; - rt_uint32_t free_size = 0; - - ptr = (rt_uint8_t *)thread->stack_addr; - - while ((free_size < thread->stack_size) && - (*ptr == '#')) - { - ptr++; - free_size++; - } - - return free_size; -} - - -/* ==================== KEY0 按键检测 ==================== */ - -static void key_scan(void) -{ - /* 检测按键按下 */ - if (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - /* 软件消抖 */ - rt_thread_mdelay(50); - - if (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - if (led1_suspended == RT_FALSE) - { - /* - * 第一次按键: - * 请求 th_led1 挂起自身 - */ - led1_suspended = RT_TRUE; - - rt_kprintf("[KEY] request th_led1 suspend\n"); - } - else - { - /* - * 第二次按键: - * 恢复 th_led1 - */ - led1_suspended = RT_FALSE; - - /* SUSPEND -> READY */ - rt_thread_resume(th_led1); - - rt_kprintf("[KEY] th_led1 resume request\n"); - - /* 重新调度 */ - rt_schedule(); - } - - /* - * 等待按键释放 - * 防止长按被识别为多次触发 - */ - while (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - rt_thread_mdelay(10); - } - } - } -} - - -/* ==================== th_monitor:监控线程 ==================== */ - -/* 动态监控线程句柄 */ -static rt_thread_t th_monitor = RT_NULL; - - -/* monitor 线程入口 */ -static void monitor_entry(void *parameter) -{ - rt_tick_t last_monitor_tick; - - last_monitor_tick = rt_tick_get(); - - while (1) - { - /* 高频扫描按键 */ - key_scan(); - - /* 每 2 秒打印一次线程状态 */ - if ((rt_tick_get() - last_monitor_tick) >= - rt_tick_from_millisecond(2000)) - { - last_monitor_tick = rt_tick_get(); - - rt_kprintf("\n"); - rt_kprintf("----- thread monitor -----\n"); - - rt_kprintf("th_led1: state=%s, stack_free=%d Bytes\n", - thread_state_str(th_led1), - thread_stack_free(th_led1)); - - rt_kprintf("th_led2: state=%s, stack_free=%d Bytes\n", - thread_state_str(&th_led2), - thread_stack_free(&th_led2)); - - rt_kprintf("--------------------------\n"); - } - - /* monitor 扫描周期约 10 ms */ - rt_thread_mdelay(10); - } -} - - -/* ==================== MSH 命令 ==================== */ - -/* - * thread_dump - * 手动查看工作线程状态、优先级和剩余栈 - */ -static void thread_dump(void) -{ - rt_kprintf("\n"); - rt_kprintf("========== thread dump ==========\n"); - - rt_kprintf("th_led1:\n"); - rt_kprintf(" state : %s\n", - thread_state_str(th_led1)); - rt_kprintf(" priority : %d\n", - th_led1->current_priority); - rt_kprintf(" stack free : %d Bytes\n", - thread_stack_free(th_led1)); - - rt_kprintf("\n"); - - rt_kprintf("th_led2:\n"); - rt_kprintf(" state : %s\n", - thread_state_str(&th_led2)); - rt_kprintf(" priority : %d\n", - th_led2.current_priority); - rt_kprintf(" stack free : %d Bytes\n", - thread_stack_free(&th_led2)); - - rt_kprintf("=================================\n"); -} - - -/* 导出 MSH 命令 */ -MSH_CMD_EXPORT(thread_dump, show thread status and stack usage); - - -/* ==================== 模块初始化 ==================== */ - -static int thread_study_init(void) -{ - rt_err_t result; - - rt_kprintf("thread_study_init success!\n"); - - - /* ---------- 初始化 KEY0 ---------- */ - - /* KEY0 上拉输入,按下为低电平 */ - rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP); - - - /* ---------- 动态创建 th_led1 ---------- */ - - /* - * th_led1 - * 栈大小:1024 Bytes - * 优先级:16 - * 时间片:5 ticks - */ - th_led1 = rt_thread_create("th_led1", - led1_entry, - RT_NULL, - 1024, - 16, - 5); - - if (th_led1 != RT_NULL) - { - /* INIT -> READY */ - rt_thread_startup(th_led1); - - rt_kprintf("th_led1 create success!\n"); - } - else - { - rt_kprintf("th_led1 create failed!\n"); - } - - - /* ---------- 静态初始化 th_led2 ---------- */ - - /* - * th_led2 - * 静态 TCB - * 静态栈:1024 Bytes - * 优先级:16 - * 时间片:5 ticks - */ - result = rt_thread_init(&th_led2, - "th_led2", - led2_entry, - RT_NULL, - th_led2_stack, - sizeof(th_led2_stack), - 16, - 5); - - if (result == RT_EOK) - { - /* INIT -> READY */ - rt_thread_startup(&th_led2); - - rt_kprintf("th_led2 init success!\n"); - } - else - { - rt_kprintf("th_led2 init failed!\n"); - } - - - /* ---------- 动态创建 th_monitor ---------- */ - - /* - * th_monitor - * 栈大小:1024 Bytes - * 优先级:10 - * 时间片:5 ticks - * - * 优先级 10 高于两个 LED 线程的 16 - */ - th_monitor = rt_thread_create("th_monitor", - monitor_entry, - RT_NULL, - 1024, - 10, - 5); - - if (th_monitor != RT_NULL) - { - /* INIT -> READY */ - rt_thread_startup(th_monitor); - - rt_kprintf("th_monitor create success!\n"); - } - else - { - rt_kprintf("th_monitor create failed!\n"); - } - - - return 0; -} - - -/* - * 自动初始化 - */ -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 5e681e648d5471b6d00b706ec0ce1399be03ed6d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1 +0,0 @@ -已完成 \ No newline at end of file diff --git "a/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index d5877d3cb80a65eb940595551106a50fdd1b3c3c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,2113 +0,0 @@ -# Day 1 - -## Task 1 开始 RT-Thread - -### 1. RT-Thread 简介 - -RT-Thread 是一款面向嵌入式设备的实时操作系统(RTOS)。在 RT-Thread 的学习和开发过程中,一个基本的程序开发流程可以分为: - -**编码 → 编译 → 烧录 → 运行 → 调试** - -### 2. 编码 - -首先根据实验要求编写程序代码。 - -在 RT-Thread 中,可以通过创建线程、使用延时函数以及调用系统提供的 API 来实现不同的功能。 - -例如,一个简单的线程程序可以按照以下结构编写: - -```c -#include - -int main(void) -{ - rt_kprintf("Hello RT-Thread!\n"); - - while (1) - { - rt_thread_mdelay(1000); - } - - return 0; -} -``` - -其中: - -- `rt_kprintf()`:用于向 Console 输出调试信息。 -- `rt_thread_mdelay()`:使当前线程延时指定的毫秒数。 -- `while (1)`:使程序持续运行。 - -### 3. 编译 - -完成代码编写后,需要对工程进行编译。 - -编译的主要作用是将编写的 C/C++ 源代码转换为 MCU 可以运行的机器代码。 - -基本过程为: - -```text -源代码 - ↓ -预处理 - ↓ -编译 - ↓ -汇编 - ↓ -链接 - ↓ -生成可执行文件 -``` - -编译成功后,一般会生成用于烧录的程序文件,例如: - -```text -.elf -.bin -.hex -``` - -如果编译过程中出现 Error,需要根据控制台中的错误信息检查代码、头文件、驱动以及工程配置。 - -### 4. 烧录 - -编译成功后,将生成的程序下载到开发板 MCU 的 Flash 中。 - -常见烧录方式包括: - -- ST-Link -- J-Link -- USB -- 串口 Bootloader -- STM32CubeProgrammer - -烧录完成后,对开发板重新上电或复位,MCU 就会开始运行 Flash 中的程序。 - -### 5. 运行与 Console - -程序烧录成功后,可以通过串口 Console 查看 RT-Thread 的运行信息。 - -例如: - -```text - \ | / -- RT - Thread Operating System - / | \ - -Hello RT-Thread! -``` - -Console 是嵌入式开发过程中非常重要的调试工具,可以通过打印信息判断: - -- 程序是否正常启动 -- 线程是否正常运行 -- 函数是否被执行 -- 变量值是否正确 -- 程序运行到了哪个位置 - -### 6. 调试与修改 - -如果程序运行结果与预期不一致,需要重新检查程序并进行修改。 - -因此,RT-Thread 实际开发通常是一个不断循环的过程: - -```text -编码 - ↓ -编译 - ↓ -烧录 - ↓ -运行 - ↓ -观察 Console - ↓ -发现问题 - ↓ -修改代码 - ↓ -重新编译 -``` - -通过不断重复这一过程,最终完成程序功能。 - ------- - -## Task 2 MD 文档编写 - -Markdown(`.md`)是一种轻量级标记语言,可以使用简单的符号完成标题、代码、表格以及文字格式的编写。 - -### 1. 标题 - -Markdown 使用 `#` 表示标题,不同数量的 `#` 代表不同级别的标题。 - -```text -# 一级标题 - -## 二级标题 - -### 三级标题 - -#### 四级标题 - -##### 五级标题 - -###### 六级标题 -``` - -标题级别越高,使用的 `#` 数量越少。 - ------- - -### 2. 代码段 - -Markdown 可以使用三个反引号创建代码块。 - -例如 C 语言代码: - -```c -#include - -int main() -{ - return 0; -} -``` - -在三个反引号后添加语言名称,例如 `c`、`cpp`、`python`、`java` 等,可以让 Markdown 编辑器自动进行代码语法高亮。 - -例如: - -~~~text -```c -#include - -int main() -{ - return 0; -} -``` -~~~ - ------- - -### 3. 表格 - -Markdown 可以通过 `|` 和 `-` 创建表格。 - -例如: - -| t1 | t2 | -| ---- | ---- | -| 1 | 2 | - -对应的 Markdown 语法为: - -```text -| t1 | t2 | -| ---- | ---- | -| 1 | 2 | -``` - -表格可以用于记录实验参数、测试结果和不同配置之间的对比。 - ------- - -### 4. 行内代码与高亮 - -使用一对反引号可以标记代码、变量名、命令或者需要特别强调的内容。 - -例如: - -``` -高亮语法 -``` - -还可以用于表示函数名: - -``` -rt_kprintf() -``` - -文件名: - -``` -main.c -``` - -或者命令: - -``` -make -``` - -这种方式非常适合在技术文档中标记代码相关内容。 - ------- - -### 5. 加粗字体 - -使用两个星号包围文字可以实现加粗: - -**加粗字体** - -对应语法: - -```text -**加粗字体** -``` - -加粗通常用于强调: - -**重要步骤** - -**注意事项** - -**关键参数** - ------- - -## Task 3 开始 Spark1 最小示例 - -### 1. 创建 Project - -开始创建 Spark1 最小示例工程。 - -Project 名称: - -```text -01_hello -``` - -### 2. Project 参数选择 - -具体选择: - -![image-20260817133918955](figures/image-20260817133918955.png) - -### 3. 编写最小示例 - -程序代码: - -```c -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -int main(void) -{ - int count = 1; - - rt_kprintf("hello RSOC2026!\n"); - - while (count++) - { - LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - - return RT_EOK; -} -``` - -### 4. 编译 - -编译操作: - -```text -点击编译按钮开始编译 -``` - -编译结果: - -```text -Build Finished. 0 errors, 0 warnings. (took 657ms) -``` - -### 5. 烧录 - -烧录方式: - -```text -点击下载按钮 -``` - -烧录结果: - -```text -Download in Progress: -Progress: 100% -File download complete -Time elapsed during download operation: 00:00:01.683 -Hard reset is performed -RUNNING Program ... - Address: : 0x8000000 -Application is running, Please Hold on... -Start operation achieved successfully -执行完毕, 耗时:1904ms. -``` - -### 6.连接串口 - -连接配置: - -![image-20260817134312360](figures/image-20260817134312360.png) - -### 7. 运行结果 - -程序烧录完成后的 Console 输出: - -```text - \ | - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 17 2026 10:47:44 - 2006 - 2022 Copyright by RT-Thread team -hello RSOC2026! -``` - -### 7. 遇到的问题 - -问题描述: - -```text -debug无法通过 -``` - -问题原因: - -```text -st-link的版本过低 -``` - -解决方法: - -```text -找到stm32官方升级程序升级st-link的版本 -``` - -### 8. Task 3 总结 - -通过 Spark1 最小示例,完成了从 **Project 创建 → 参数配置 → 编码 → 编译 → 烧录 → 运行** 的基本开发流程。 - -## Git 入门 - -本文以 **Gitee + Git Bash + Windows** 为例,记录从创建仓库、拉取代码、配置 Git、配置 SSH,到日常开发、分支管理以及最终提交 Pull Request(PR)的完整流程。 - -示例仓库: - -```text -https://gitee.com/kylehe233/rt-thread2026.git -``` - -SSH 地址: - -```text -git@gitee.com:kylehe233/rt-thread2026.git -``` - ------- - -### 1. 在 Gitee 创建仓库 - -首先登录 Gitee,新建一个仓库,例如: - -```text -rt-thread2026 -``` - -创建完成后,可以看到两种常用的仓库地址。 - -HTTPS: - -```text -https://gitee.com/kylehe233/rt-thread2026.git -``` - -SSH: - -```text -git@gitee.com:kylehe233/rt-thread2026.git -``` - -两种方式都可以用于 `clone / pull / push`。 - -推荐后续使用 **SSH**,配置完成之后不需要频繁输入账号密码。 - ------- - -### 2. 将远程仓库拉取到本地 - -进入希望存放项目的目录: - -```bash -cd /d/rt_thread2026/git -``` - -使用 HTTPS 克隆: - -```bash -git clone https://gitee.com/kylehe233/rt-thread2026.git -``` - -例如: - -```text -28431@KyleHe MINGW64 /d/rt_thread2026/git -$ git clone https://gitee.com/kylehe233/rt-thread2026.git - -Cloning into 'rt-thread2026'... -remote: Enumerating objects: 4, done. -remote: Counting objects: 100% (4/4), done. -remote: Compressing objects: 100% (4/4), done. -remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) -Receiving objects: 100% (4/4), done. -``` - -克隆完成后进入仓库: - -```bash -cd rt-thread2026 -``` - -也可以直接使用 SSH: - -```bash -git clone git@gitee.com:kylehe233/rt-thread2026.git -``` - -前提是已经完成 SSH Key 配置。 - ------- - -### 3. 配置 Git 用户信息 - -第一次使用 Git 时,需要配置用户名和邮箱。 - -```bash -git config --global user.name "kylehe233-ui" -git config --global user.email "kylehe233@gmail.com" -``` - -其中: - -```text ---global -``` - -表示该配置应用于当前电脑上的所有 Git 仓库。 - -查看当前 Git 配置: - -```bash -git config --list -``` - -例如: - -```text -user.name=kylehe233-ui -user.email=kylehe233@gmail.com -``` - -也可以分别查看: - -```bash -git config user.name -git config user.email -``` - ------- - -### 4. 配置 Gitee SSH Key - -为了避免每次 Push 都进行 HTTPS 身份验证,可以配置 SSH。 - -#### 4.1 生成 SSH Key - -执行: - -```bash -ssh-keygen -t rsa -b 4096 -C "kylehe@gmail.com" -``` - -例如: - -```text -Generating public/private rsa key pair. - -Enter file in which to save the key (/c/Users/28431/.ssh/id_rsa): -Enter passphrase for "/c/Users/28431/.ssh/id_rsa" (empty for no passphrase): -Enter same passphrase again: -``` - -如果不需要额外密码,可以直接连续按 Enter。 - -生成完成: - -```text -Your identification has been saved in /c/Users/28431/.ssh/id_rsa -Your public key has been saved in /c/Users/28431/.ssh/id_rsa.pub -``` - -会生成两个文件: - -```text -id_rsa -id_rsa.pub -``` - -其中: - -```text -id_rsa -``` - -是 **私钥**,不要发送给任何人。 - -```text -id_rsa.pub -``` - -是 **公钥**,需要添加到 Gitee。 - ------- - -#### 4.2 查看 SSH 公钥 - -Git Bash 中执行: - -```bash -cat ~/.ssh/id_rsa.pub -``` - -复制完整内容。 - -一般类似: - -```text -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQ... kylehe@gmail.com -``` - ------- - -#### 4.3 将公钥添加到 Gitee - -进入: - -```text -Gitee -→ 设置 -→ SSH 公钥 -→ 添加公钥 -``` - -将刚才 `id_rsa.pub` 中的内容粘贴进去并保存。 - ------- - -#### 4.4 测试 SSH - -执行: - -```bash -ssh -T git@gitee.com -``` - -第一次连接可能出现: - -```text -The authenticity of host 'gitee.com (...)' can't be established. -Are you sure you want to continue connecting (yes/no/[fingerprint])? -``` - -输入: - -```text -yes -``` - -如果出现: - -```text -Hi kylehe233-ui(@kylehe233)! You've successfully authenticated, but GITEE.COM does not provide shell access. -``` - -说明: - -```text -SSH 配置成功 -``` - -其中: - -```text -GITEE.COM does not provide shell access -``` - -不是报错,只是表示 Gitee 不提供 SSH Shell 登录。 - -Git 的: - -```text -clone -pull -fetch -push -``` - -都可以正常使用。 - ------- - -### 5. Git 仓库初始化 - -如果项目已经通过: - -```bash -git clone -``` - -拉取下来,**不需要再执行 `git init`**。 - -如果是一个本地已有项目,需要自己创建 Git 仓库,则进入项目目录执行: - -```bash -git init -``` - -例如: - -```bash -cd /d/rt_thread2026/my_project -git init -``` - -此时当前目录会生成: - -```text -.git -``` - -目录,表示当前文件夹已经成为 Git 仓库。 - ------- - -### 6. 添加远程仓库 - -如果是通过 `git clone` 获取的项目,通常已经自动配置了: - -```text -origin -``` - -如果是自己执行: - -```bash -git init -``` - -创建的本地仓库,需要手动添加远程仓库: - -```bash -git remote add origin git@gitee.com:kylehe233/rt-thread2026.git -``` - -查看远程仓库: - -```bash -git remote -v -``` - -例如: - -```text -origin git@gitee.com:kylehe233/rt-thread2026.git (fetch) -origin git@gitee.com:kylehe233/rt-thread2026.git (push) -``` - ------- - -### 7. 修改远程仓库 URL - -如果最开始使用的是 HTTPS: - -```text -https://gitee.com/kylehe233/rt-thread2026.git -``` - -后来希望改为 SSH,可以执行: - -```bash -git remote set-url origin git@gitee.com:kylehe233/rt-thread2026.git -``` - -再次检查: - -```bash -git remote -v -``` - -这个命令的作用可以理解为: - -```text -设置当前 Git 仓库的远程仓库地址 -``` - -以后: - -```bash -git pull -git fetch -git push -``` - -都会使用新的地址。 - ------- - -## Git 基本操作 - -### 8. 查看仓库状态 - -这是日常使用频率最高的命令之一: - -```bash -git status -``` - -可以查看: - -- 哪些文件被修改 -- 哪些文件还没有加入暂存区 -- 哪些文件已经加入暂存区 -- 当前所在分支 - -建议每次: - -```text -add -commit -push -``` - -之前都可以执行一次: - -```bash -git status -``` - ------- - -### 9. 拉取远程代码 - -在开始修改代码之前,建议先同步远程仓库。 - -```bash -git pull origin master -``` - -含义: - -```text -从 origin 远程仓库 -拉取 master 分支 -并合并到当前本地分支 -``` - -如果当前分支已经和远程分支建立关联,也可以直接: - -```bash -git pull -``` - ------- - -### 10. 添加修改到暂存区 - -添加指定文件: - -```bash -git add 文件路径 -``` - -例如: - -```bash -git add README.md -``` - -或者: - -```bash -git add src/main.c -``` - -添加整个目录: - -```bash -git add src/ -``` - -添加当前目录下全部修改: - -```bash -git add . -``` - -检查: - -```bash -git status -``` - ------- - -### 11. 提交修改 - -提交到本地 Git 仓库: - -```bash -git commit -m "message" -``` - -例如: - -```bash -git commit -m "add uart driver" -``` - -或者: - -```bash -git commit -m "fix serial configuration" -``` - -一个比较推荐的提交信息格式是: - -```text -add: 新增功能 -fix: 修复问题 -update: 更新功能 -docs: 修改文档 -refactor: 重构代码 -test: 测试相关修改 -``` - -例如: - -```bash -git commit -m "fix: repair uart initialization" -``` - ------- - -### 12. 推送到 Gitee - -推送当前提交到远程 `master`: - -```bash -git push origin master -``` - -![0e2c3d99-08de-41e0-b846-3c7a02cdf538](figures/0e2c3d99-08de-41e0-b846-3c7a02cdf538.png) - -如果本地分支第一次推送,可以使用: - -```bash -git push -u origin master -``` - -其中: - -```text --u -``` - -会建立: - -```text -本地 master → origin/master -``` - -的跟踪关系。 - -以后可以直接: - -```bash -git push -``` - ------- - -## Git 远程同步 - -### 13. fetch 获取远程更新 - -执行: - -```bash -git fetch origin master -``` - -作用是: - -```text -获取远程 master 分支的最新信息 -``` - -但 **不会自动把代码合并到当前工作分支**。 - -因此: - -```bash -git fetch -``` - -和: - -```bash -git pull -``` - -并不完全一样。 - -可以简单理解为: - -```text -git fetch = 下载远程更新,但暂时不合并 -git pull = 下载远程更新 + 合并 -``` - -实际上: - -```bash -git pull -``` - -通常相当于: - -```text -git fetch -+ -git merge -``` - ------- - -### 14. 查看远程仓库 - -执行: - -```bash -git remote -v -``` - -用于查看当前仓库配置的远程地址。 - -例如: - -```text -origin git@gitee.com:kylehe233/rt-thread2026.git (fetch) -origin git@gitee.com:kylehe233/rt-thread2026.git (push) -``` - -其中: - -```text -origin -``` - -只是远程仓库的默认名字。 - ------- - -## Git 分支 - -### 15. 查看当前分支 - -执行: - -```bash -git branch -``` - -例如: - -```text -* master -``` - -`*` 表示当前所在分支。 - -查看所有本地和远程分支: - -```bash -git branch -a -``` - ------- - -### 16. 创建新的开发分支 - -例如需要创建: - -```text -test -``` - -分支。 - -执行: - -```bash -git checkout -b test -``` - -![image-20260817153038429](figures/image-20260817153038429.png) - -这个命令相当于: - -```bash -git branch test -git checkout test -``` - -即: - -```text -创建 test 分支 -+ -切换到 test 分支 -``` - -再次查看: - -```bash -git branch -``` - -应该看到: - -```text -master -* test -``` - ------- - -### 17. 切换分支 - -切换回: - -```text -master -``` - -执行: - -```bash -git checkout master -``` - -切换到: - -```text -test -``` - -执行: - -```bash -git checkout test -``` - -新版 Git 也可以使用: - -```bash -git switch master -``` - -以及: - -```bash -git switch test -``` - -创建并切换: - -```bash -git switch -c test -``` - ------- - -## 推荐的实际开发流程 - -假设: - -```text -master = 稳定主分支 -test = 开发测试分支 -``` - -开发时不要直接在: - -```text -master -``` - -中大量修改代码。 - -推荐: - -```text -master - │ - └── test - │ - ├── 修改代码 - ├── commit - ├── push - │ - └── PR - │ - ▼ - master -``` - ------- - -### 18. 第一次创建 test 分支 - -首先确保当前 `master` 是最新状态: - -```bash -git checkout master -``` - -然后: - -```bash -git pull origin master -``` - -创建: - -```text -test -``` - -分支: - -```bash -git checkout -b test -``` - -将 `test` 推送到 Gitee: - -```bash -git push -u origin test -``` - -此时 Gitee 上就会同时存在: - -```text -master -test -``` - -两个分支。 - ------- - -### 19. 在 test 分支开发 - -确认当前分支: - -```bash -git branch -``` - -应该显示: - -```text -master -* test -``` - -然后正常修改代码。 - -查看修改: - -```bash -git status -``` - -加入暂存区: - -```bash -git add . -``` - -提交: - -```bash -git commit -m "add: implement new feature" -``` - -上传到远程 `test`: - -```bash -git push origin test -``` - -![2e9a5471-cfdf-4477-bdc8-cc09204a69db](figures/2e9a5471-cfdf-4477-bdc8-cc09204a69db.png) - -如果已经建立 upstream: - -```bash -git push -``` - -即可。 - ------- - -## Gitee PR 流程 - -### 20. 将 test 合并到 master - -当: - -```text -test -``` - -分支中的代码已经开发、编译、测试完成之后,不建议直接在本地强行修改 `master`。 - -推荐通过 Gitee: - -```text -Pull Request -``` - -进行合并。 - -完整关系: - -```text -test - │ - │ push - ▼ -origin/test - │ - │ Pull Request - ▼ -origin/master -``` - ------- - -### 21. 创建 Pull Request - -首先确保最新代码已经提交: - -```bash -git status -``` - -然后: - -```bash -git add . -``` - -提交: - -```bash -git commit -m "test: complete feature validation" -``` - -推送: - -```bash -git push origin test -``` - -然后进入 Gitee 仓库页面。 - -进入: - -```text -Pull Requests -``` - -创建新的 Pull Request。 - -选择: - -```text -源分支:test - -目标分支:master -``` - -也就是: - -```text -test - ↓ -master -``` - -填写 PR 标题,例如: - -```text -Merge test branch into master -``` - -![image-20260817153225643](figures/image-20260817153225643.png) - -PR 描述可以说明: - -```text -1. 新增了哪些功能 -2. 修改了哪些文件 -3. 修复了哪些问题 -4. 是否已经编译通过 -5. 是否已经在开发板上测试 -``` - -确认无误后创建 Pull Request。 - ------- - -### 22. 检查并合并 PR - -在 Gitee 上检查: - -```text -代码差异 -提交记录 -文件修改 -是否存在冲突 -``` - -确认代码没有问题后: - -```text -Merge -``` - -将: - -```text -test -``` - -合并到: - -```text -master -``` - -最终: - -```text -test - │ - │ Pull Request - ▼ -master -``` - -合并完成。 - ------- - -### 23. PR 合并后同步本地 master - -Gitee 上完成合并之后,本地的: - -```text -master -``` - -并不会自动更新。 - -首先切换: - -```bash -git checkout master -``` - -然后: - -```bash -git pull origin master -``` - -此时本地: - -```text -master -``` - -就和 Gitee 上最新的: - -```text -master -``` - -保持一致。 - ------- - -### 24. 继续下一轮开发 - -如果继续使用: - -```text -test -``` - -分支进行开发,建议先让它同步最新的: - -```text -master -``` - -切换到: - -```bash -git checkout test -``` - -然后: - -```bash -git merge master -``` - -此时: - -```text -test -``` - -就包含最新: - -```text -master -``` - -代码。 - -之后继续开发: - -```bash -git add . -git commit -m "add: next feature" -git push origin test -``` - -开发完成后再次: - -```text -test → PR → master -``` - ------- - -## 常用 Git 命令速查 - -| 作用 | 命令 | -| -------------- | --------------------------------------------------------- | -| 初始化仓库 | `git init` | -| HTTPS 克隆仓库 | `git clone https://gitee.com/kylehe233/rt-thread2026.git` | -| SSH 克隆仓库 | `git clone git@gitee.com:kylehe233/rt-thread2026.git` | -| 查看状态 | `git status` | -| 查看分支 | `git branch` | -| 查看所有分支 | `git branch -a` | -| 创建并切换分支 | `git checkout -b test` | -| 切换分支 | `git checkout test` | -| 拉取远程代码 | `git pull origin master` | -| 获取远程信息 | `git fetch origin master` | -| 添加单个文件 | `git add file` | -| 添加所有修改 | `git add .` | -| 提交修改 | `git commit -m "message"` | -| 推送 master | `git push origin master` | -| 推送 test | `git push origin test` | -| 查看远程仓库 | `git remote -v` | -| 添加远程仓库 | `git remote add origin URL` | -| 修改远程 URL | `git remote set-url origin URL` | -| 测试 Gitee SSH | `ssh -T git@gitee.com` | -| 查看提交历史 | `git log` | -| 简洁查看提交 | `git log --oneline` | -| 查看代码修改 | `git diff` | - ------- - -## 日常开发最简流程 - -如果当前已经在: - -```text -test -``` - -分支开发,一般每天最常用的就是: - -```bash -git pull -``` - -修改代码后: - -```bash -git status -``` - -然后: - -```bash -git add . -``` - -提交: - -```bash -git commit -m "fix: description" -``` - -最后: - -```bash -git push -``` - -也就是: - -```text -拉取最新代码 - ↓ -修改代码 - ↓ -git status - ↓ -git add . - ↓ -git commit - ↓ -git push -``` - ------- - -## 一个完整示例 - -第一次获取项目: - -```bash -git clone git@gitee.com:kylehe233/rt-thread2026.git -``` - -进入项目: - -```bash -cd rt-thread2026 -``` - -拉取最新主分支: - -```bash -git checkout master -git pull origin master -``` - -创建测试分支: - -```bash -git checkout -b test -``` - -上传分支: - -```bash -git push -u origin test -``` - -开发完成: - -```bash -git status -git add . -git commit -m "add: complete uart demo" -git push origin test -``` - -然后在 Gitee: - -```text -test -→ Pull Request -→ master -→ Merge -``` - -PR 合并完成之后: - -```bash -git checkout master -git pull origin master -``` - -此时整个开发闭环完成。 - -## RT-Thread 的启动流程 - -RT-Thread 的启动过程主要完成 **硬件初始化、内核初始化、系统线程创建、组件初始化以及用户 `main()` 函数运行** 等工作。 - -![rtt_startup](notes/figures/rtt_startup.png) - -根据不同的编译器,程序从启动文件 `startup_xx.S` 进入 RT-Thread 的方式有所不同: - -```text -startup_xx.S - │ - ├── MDK → $Sub$$main() - ├── IAR → __low_level_init() - └── GCC → entry() - │ - ↓ - rtthread_startup() -``` - -虽然不同编译器的入口函数不同,但最终都会进入: - -```c -rtthread_startup(); -``` - -`rtthread_startup()` 是 RT-Thread 系统启动的核心函数,负责依次完成 RT-Thread 内核和相关组件的初始化。 - -### 1. 关闭中断 - -系统首先执行: - -```c -rt_hw_interrupt_disable(); -``` - -在系统初始化完成之前暂时关闭中断,避免初始化过程中被外部中断打断,从而保证启动过程能够按照预定顺序执行。 - -启动流程: - -```text -rtthread_startup() - ↓ -rt_hw_interrupt_disable() -``` - ------- - -### 2. 板级硬件初始化 - -随后执行: - -```c -rt_hw_board_init(); -``` - -该函数主要用于完成与开发板硬件相关的初始化,例如: - -- 系统时钟初始化 -- SysTick 初始化 -- UART 初始化 -- GPIO 初始化 -- Heap 初始化 -- 其他 BSP 相关硬件初始化 - -在 `rt_hw_board_init()` 过程中,还会调用: - -```c -rt_components_board_init(); -``` - -用于执行注册到 **Board Init** 阶段的初始化函数。 - -流程可以表示为: - -```text -rt_hw_board_init() - │ - └── rt_components_board_init() - │ - └── board init functions -``` - -因此,这一阶段的主要作用就是为 RT-Thread 内核运行准备最基本的硬件环境。 - ------- - -### 3. 显示 RT-Thread 版本信息 - -硬件初始化完成后执行: - -```c -rt_show_version(); -``` - -该函数会通过 Console 输出 RT-Thread 的版本以及系统启动 Logo。 - -例如: - -```text - \ | / -- RT - Thread Operating System - / | \ -``` - -如果能够在串口终端看到这些信息,说明 RT-Thread 已经完成了部分基础初始化,并且 Console 可以正常工作。 - ------- - -### 4. 初始化系统定时器 - -接下来执行: - -```c -rt_system_timer_init(); -``` - -用于初始化 RT-Thread 的系统定时器管理模块。 - -RT-Thread 中的定时器可以用于实现: - -- 软件定时 -- 周期任务 -- 超时处理 -- 延时操作 - -此时主要完成定时器相关数据结构的初始化。 - ------- - -### 5. 初始化系统调度器 - -随后执行: - -```c -rt_system_scheduler_init(); -``` - -该函数用于初始化 RT-Thread 的线程调度系统。 - -调度器负责管理系统中不同线程的运行状态,并根据线程的: - -- 优先级 -- 就绪状态 -- 时间片 - -决定 CPU 当前应该执行哪个线程。 - -可以将其理解为 RTOS 的核心功能之一。 - ------- - -### 6. 初始化 Signal 系统 - -接下来执行: - -```c -rt_system_signal_init(); -``` - -用于初始化 RT-Thread 的线程信号机制。 - -Signal 可以用于线程之间的异步通知和事件处理。 - -如果当前 RT-Thread 配置没有启用 Signal 功能,则这一部分可能不会被编译进入最终程序。 - ------- - -### 7. 创建 Main 线程 - -系统继续执行: - -```c -rt_application_init(); -``` - -该函数会创建 RT-Thread 中非常重要的 **Main Thread**。 - -内部会通过类似下面的方式初始化线程: - -```c -rt_thread_init(main_thread_entry); -``` - -因此: - -```text -rt_application_init() - │ - └── rt_thread_init(main_thread_entry) -``` - -需要注意,此时只是完成 Main Thread 的**创建和初始化**,并不意味着 `main_thread_entry()` 已经立即开始运行。 - -线程真正运行需要等到后面的调度器启动。 - ------- - -### 8. 创建 Timer 线程 - -随后执行: - -```c -rt_system_timer_thread_init(); -``` - -用于创建系统软件定时器线程: - -```c -rt_thread_init(rt_thread_timer_entry); -``` - -流程为: - -```text -rt_system_timer_thread_init() - │ - └── rt_thread_init(rt_thread_timer_entry) -``` - -该线程主要负责处理软件定时器相关任务。 - ------- - -### 9. 创建 Idle 线程 - -接下来执行: - -```c -rt_thread_idle_init(); -``` - -创建 RT-Thread 的空闲线程: - -```c -rt_thread_init(rt_thread_idle_entry); -``` - -流程为: - -```text -rt_thread_idle_init() - │ - └── rt_thread_init(rt_thread_idle_entry) -``` - -Idle Thread 是系统中优先级最低的线程。 - -当没有其他线程需要运行时,CPU 会运行 Idle Thread。 - -Idle Thread 还可以用于完成一些系统后台工作,例如: - -- 线程资源回收 -- CPU 空闲处理 -- 低功耗处理 - ------- - -### 10. 启动线程调度器 - -所有基础初始化和系统线程创建完成后,执行: - -```c -rt_system_scheduler_start(); -``` - -此时 RT-Thread 正式启动线程调度。 - -```text -rt_system_scheduler_start() - │ - ↓ - 根据调度规则执行 - │ - ┌──────┼────────────┐ - ↓ ↓ ↓ -main_thread timer_thread idle_thread - │ - └── 其他线程 -``` - -从这一刻开始,程序不再按照普通裸机程序单一的顺序执行,而是由 RT-Thread 调度器根据线程优先级和运行状态决定 CPU 执行哪个线程。 - ------- - -### 11. Main Thread 运行 - -调度器启动后,Main Thread 会执行: - -```c -main_thread_entry(); -``` - -`main_thread_entry()` 中一个非常重要的工作就是调用: - -```c -rt_components_init(); -``` - -用于初始化 RT-Thread 的各种组件。 - -流程为: - -```text -main_thread_entry() - │ - ↓ -rt_components_init() -``` - -`rt_components_init()` 会按照规定的初始化顺序依次执行不同阶段的自动初始化函数,例如: - -```text -rt_components_init() - │ - ├── pre-initialization functions - │ - ├── device init functions - │ - ├── components init functions - │ - ├── environment init functions - │ - └── applications init functions -``` - -也就是说,通过 RT-Thread 自动初始化机制注册的设备、组件和应用,会在这一阶段被依次初始化。 - ------- - -### 12. 进入用户 main() 函数 - -组件初始化完成后,最终进入用户编写的: - -```c -main(); -``` - -以 MDK 为例,图中通过: - -```c -$Super$$main(); -``` - -进入原始的 `main()` 函数。 - -因此可以简化理解为: - -```text -main_thread_entry() - │ - ├── rt_components_init() - │ - └── main() -``` - -我们平时在: - -```c -int main(void) -{ - /* 用户程序 */ - - return 0; -} -``` - -中编写的代码,实际上已经是在 **RT-Thread Main Thread 中运行**。 - ------- - -### 13. RT-Thread 完整启动流程 - -整个启动过程可以总结为: - -```text -startup_xx.S - ↓ -编译器入口 - ↓ -rtthread_startup() - ↓ -rt_hw_interrupt_disable() - ↓ -rt_hw_board_init() - │ - └── rt_components_board_init() - ↓ -rt_show_version() - ↓ -rt_system_timer_init() - ↓ -rt_system_scheduler_init() - ↓ -rt_system_signal_init() - ↓ -rt_application_init() - │ - └── 创建 Main Thread - ↓ -rt_system_timer_thread_init() - │ - └── 创建 Timer Thread - ↓ -rt_thread_idle_init() - │ - └── 创建 Idle Thread - ↓ -rt_system_scheduler_start() - ↓ -启动线程调度 - ↓ -main_thread_entry() - ↓ -rt_components_init() - ↓ -各种组件自动初始化 - ↓ -main() - ↓ -执行用户程序 -``` - -因此,可以将 RT-Thread 的启动过程概括为: - -**启动文件 → RT-Thread 启动函数 → 硬件初始化 → 内核初始化 → 创建系统线程 → 启动调度器 → 组件初始化 → 进入 `main()` 用户程序。** - -其中最重要的几个函数可以重点记忆: - -| 函数 | 作用 | -| ------------------------------- | ------------------------ | -| `rtthread_startup()` | RT-Thread 系统启动入口 | -| `rt_hw_board_init()` | 开发板硬件初始化 | -| `rt_components_board_init()` | Board 阶段组件初始化 | -| `rt_system_timer_init()` | 系统定时器初始化 | -| `rt_system_scheduler_init()` | 调度器初始化 | -| `rt_application_init()` | 创建 Main Thread | -| `rt_system_timer_thread_init()` | 创建 Timer Thread | -| `rt_thread_idle_init()` | 创建 Idle Thread | -| `rt_system_scheduler_start()` | 启动线程调度 | -| `main_thread_entry()` | Main Thread 入口 | -| `rt_components_init()` | RT-Thread 组件自动初始化 | -| `main()` | 用户程序入口 | \ No newline at end of file diff --git "a/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\345\255\246\344\271\240\347\254\224\350\256\260.md" deleted file mode 100644 index 02733fb83da1f00e257c27a098939a2e25cc79b7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\344\275\225\345\230\211\344\271\220/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\345\255\246\344\271\240\347\254\224\350\256\260.md" +++ /dev/null @@ -1,1590 +0,0 @@ -# Day 2 - -## Day 2 总结 - -Day 2 主要学习了 RT-Thread 中两个重要内容: - -```text -Day 2 - │ - ├── Thread - │ - │ ├── 线程控制块 - │ ├── 线程栈 - │ ├── 线程状态 - │ ├── 优先级 - │ ├── 时间片 - │ ├── 线程入口函数 - │ ├── Main Thread - │ ├── Idle Thread - │ ├── rt_thread_init() - │ ├── rt_thread_create() - │ ├── rt_thread_startup() - │ └── rt_thread_mdelay() - │ - └── SCons - ├── SConstruct - ├── SConscript - ├── DefineGroup() - ├── scons - ├── scons -c - ├── menuconfig - ├── pkgs --update - └── scons --target=mdk5 -``` - -其中线程部分最核心的是理解: - -**RT-Thread 通过调度器管理多个线程,线程依靠优先级、状态和时间片决定何时获得 CPU。** - -SCons 部分最核心的是理解: - -**SCons 根据 RT-Thread 的配置和 SConscript 脚本决定哪些源码参与工程构建,并能够自动编译或生成 MDK/IAR 工程。** - ---- - -## RT-Thread 的启动流程 - -RT-Thread 的完整启动流程已经在 Day 1 中进行了整理。 - -具体内容见: - -**Day 1 → RT-Thread 的启动流程** - -主要流程可以简单回顾为: - -```text -启动文件 - ↓ -rtthread_startup() - ↓ -硬件初始化 - ↓ -内核初始化 - ↓ -创建系统线程 - ↓ -启动调度器 - ↓ -main_thread_entry() - ↓ -rt_components_init() - ↓ -main() -``` - -本节不再重复展开,详细内容见 Day 1 笔记。 - ---- - -## 从裸机到多线程系统 - -在学习线程之前,首先需要理解为什么嵌入式系统需要 RTOS。 - -传统裸机程序通常采用 **前后台系统** 的方式运行。 - -后台程序一般是一个无限循环: - -```c -int main(void) -{ - init(); - - while (1) - { - ADC_Read(); - SPI_Read(); - USB_Packet(); - LCD_Update(); - Audio_Decode(); - File_Write(); - } -} -``` - -前台则主要由各种中断服务程序组成,例如: - -```c -void USB_ISR(void) -{ - Clear_Interrupt(); - Read_Packet(); -} -``` - -这种结构比较简单,但是随着系统功能增加,会逐渐暴露出一些问题: - -- 并发工作效率较低 -- 系统实时性难以保证 -- 代码之间耦合严重 -- 系统维护困难 -- 模块化程度较低 -- 软件复用能力较差 - -因此,在复杂嵌入式系统中,可以使用 RTOS 将整个系统拆分成多个相对独立的任务。 - -RT-Thread 中,任务的执行实体就是: - -**线程(Thread)** - -可以将复杂系统理解为: - -```text -复杂系统 - ↓ -拆分任务 - ↓ -任务 1 → 线程 1 -任务 2 → 线程 2 -任务 3 → 线程 3 -任务 4 → 线程 4 -``` - -这种方式体现了“**分而治之**”的思想。 - ---- - -## RTOS 的多任务机制 - -RTOS 的一个核心特点就是: - -**多任务运行。** - -例如,一个系统中可能同时存在: - -```text -线程 1:采集传感器数据 - -线程 2:更新 LCD - -线程 3:串口通信 - -线程 4:处理网络数据 -``` - -从用户角度来看,这些任务似乎是在“同时运行”。 - -但是对于单核 MCU 来说,CPU 在某一个瞬间实际上只能执行一个线程。 - -RTOS 通过快速切换线程,使多个线程轮流占用 CPU: - -```text -时间 → - -线程1 ███ - 线程2 ███ - 线程1 ███ - 线程3 ███ -``` - -由于 CPU 的执行速度非常快,因此从人的观察角度来看,这些线程就像是在并行运行。 - -RT-Thread 的调度器负责决定: - -**当前 CPU 应该运行哪个线程。** - ---- - -## 线程控制块 - -RT-Thread 内核需要保存每个线程的相关信息。 - -这些信息存放在线程控制块中。 - -线程控制块由: - -```c -struct rt_thread -``` - -定义。 - -线程控制块可以理解为 RT-Thread 内核为每个线程建立的一份“线程档案”。 - -其中会保存: - -- 线程名称 -- 线程状态 -- 线程优先级 -- 线程入口函数 -- 线程栈地址 -- 线程栈大小 -- 时间片 -- 线程错误码 -- 线程相关链表 -- 定时器 -- 用户数据等 - -RT-Thread 内核通过线程控制块对所有线程进行管理。 - -可以简单理解为: - -```text -线程 - │ - └── struct rt_thread - │ - ├── 线程名称 - ├── 线程状态 - ├── 优先级 - ├── 时间片 - ├── 线程栈 - ├── 入口函数 - └── 其他信息 -``` - -`struct rt_thread` 继承自 RT-Thread 的基础对象: - -```c -struct rt_object -``` - -因此线程本身也是 RT-Thread 的一种内核对象。 - ---- - -## 线程属性 - -一个线程需要多个属性才能被操作系统正确管理。 - -比较重要的属性包括: - -```text -线程 - │ - ├── 线程栈 - ├── 状态 - ├── 优先级 - ├── 时间片 - └── 入口函数 -``` - -下面分别进行说明。 - ---- - -## 线程栈 - -RT-Thread 中的每个线程都有自己独立的线程栈。 - -线程栈主要用于保存: - -- CPU 上下文 -- 函数局部变量 -- 函数调用信息 -- 寄存器信息 - -当线程发生切换时,RT-Thread 会将当前线程的运行现场保存在线程栈中。 - -当线程重新获得 CPU 后,再从线程栈中恢复运行现场。 - -因此可以将线程切换理解为: - -```text -线程 A 正在运行 - ↓ -保存线程 A 上下文到线程 A 栈 - ↓ -加载线程 B 栈中的上下文 - ↓ -线程 B 继续运行 -``` - -这也是 RTOS 能够让多个线程交替运行的重要基础。 - -线程栈还会存储函数中的局部变量。 - -因此,线程栈设置得太小可能会导致: - -```text -Stack Overflow -``` - -即栈溢出。 - -所以创建线程时需要根据线程中函数调用的复杂程度合理设置栈大小。 - ---- - -## 线程状态 - -RT-Thread 中线程主要存在以下几种状态: - -| 状态 | 宏定义 | 含义 | -| ---- | ---- | ---- | -| 初始状态 | `RT_THREAD_INIT` | 线程刚创建完成,还没有开始运行 | -| 就绪状态 | `RT_THREAD_READY` | 线程已经可以运行,正在等待 CPU | -| 运行状态 | `RT_THREAD_RUNNING` | 当前正在 CPU 上运行 | -| 挂起状态 | `RT_THREAD_SUSPEND` | 暂时不能运行,不参与调度 | -| 关闭状态 | `RT_THREAD_CLOSE` | 线程已经结束 | - -线程创建完成后首先处于: - -```text -INIT -``` - -调用启动函数后进入: - -```text -READY -``` - -当调度器选择该线程运行时: - -```text -READY - ↓ -RUNNING -``` - -如果线程主动延时或者等待某个资源: - -```text -RUNNING - ↓ -SUSPEND -``` - -等待条件满足后: - -```text -SUSPEND - ↓ -READY -``` - -线程执行结束后: - -```text -RUNNING - ↓ -CLOSE -``` - -因此线程的基本状态变化可以表示为: - -```text - 创建 - ↓ - INIT - ↓ - startup - ↓ - READY - ↓ - 调度器 - ↓ - RUNNING - ↙ ↘ - SUSPEND CLOSE - ↓ - READY -``` - -需要特别注意: - -**就绪状态并不代表线程当前正在执行。** - -就绪状态只表示: - -> 线程已经具备运行条件,正在等待调度器分配 CPU。 - ---- - -## 线程优先级 - -RT-Thread 使用线程优先级决定线程被调度的优先程度。 - -规则非常重要: - -**优先级数字越小,线程优先级越高。** - -例如: - -```text -优先级 5 → 高 - -优先级 10 → 中 - -优先级 20 → 低 -``` - -如果系统配置支持 256 个优先级,则优先级范围为: - -```text -0 ~ 255 -``` - -其中: - -```text -0 -``` - -表示最高优先级。 - -例如: - -```text -线程 A:priority = 10 - -线程 B:priority = 20 -``` - -如果 A 和 B 同时处于 READY 状态,则: - -```text -线程 A -``` - -会优先运行。 - -如果当前正在运行线程 B,此时线程 A 突然进入 READY: - -```text -线程 B:priority = 20 - ↓ -线程 A READY:priority = 10 - ↓ -线程 A 优先级更高 - ↓ -抢占线程 B - ↓ -线程 A RUNNING -``` - -这种机制称为: - -**抢占式调度。** - ---- - -## 线程时间片 - -每个线程在创建时还可以设置时间片: - -```text -tick -``` - -时间片的单位为: - -**操作系统时钟节拍 OSTick。** - -需要注意: - -**时间片主要用于相同优先级线程之间的调度。** - -例如: - -```text -线程 A -priority = 10 -tick = 10 - -线程 B -priority = 10 -tick = 5 -``` - -由于 A 和 B 的优先级相同,所以系统会采用时间片轮转的方式运行: - -```text -A 运行 10 tick - ↓ -B 运行 5 tick - ↓ -A 运行 10 tick - ↓ -B 运行 5 tick - ↓ -... -``` - -可以简单理解为: - -```text -优先级不同 - ↓ -优先级决定谁先运行 - -优先级相同 - ↓ -时间片决定轮流运行时间 -``` - ---- - -## 线程入口函数 - -线程实际执行的功能由线程入口函数实现。 - -线程入口函数的基本格式为: - -```c -void thread_entry(void *parameter) -{ -} -``` - -标准形式: - -```c -void (*entry)(void *parameter) -``` - -一个常见线程可以写成: - -```c -static void thread1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - while (1) - { - rt_kprintf("thread1 count: %d\n", count++); - - rt_thread_mdelay(500); - } -} -``` - -其中: - -```c -rt_thread_mdelay(500); -``` - -表示线程延时 500 ms。 - -线程入口函数中通常需要存在让出 CPU 的操作。 - -例如: - -```c -rt_thread_mdelay(); -``` - -否则,如果一个高优先级线程一直: - -```c -while (1) -{ -} -``` - -并且从不主动阻塞或让出 CPU,那么低优先级线程可能一直无法获得运行机会。 - -因此: - -```text -线程执行任务 - ↓ -完成本次处理 - ↓ -delay / wait - ↓ -让出 CPU - ↓ -其他线程运行 -``` - -是多线程程序中常见的执行方式。 - ---- - -## 系统线程 - -RT-Thread 在启动过程中会自动创建一些系统线程。 - -其中比较重要的是: - -- 主线程 -- 空闲线程 -- 定时器线程 - ---- - -## 主线程 Main Thread - -RT-Thread 中: - -```c -main() -``` - -本身运行在一个线程中。 - -这个线程称为: - -**Main Thread(主线程)** - -系统启动时会创建 main 线程,其入口函数为: - -```c -main_thread_entry() -``` - -运行流程为: - -```text -rtthread_startup() - ↓ -rt_application_init() - ↓ -创建 Main Thread - ↓ -启动调度器 - ↓ -main_thread_entry() - ↓ -main() -``` - -也就是说,我们编写: - -```c -int main(void) -{ - rt_kprintf("hello world!\n"); - - return 0; -} -``` - -实际上这些代码是在 Main Thread 中执行的。 - ---- - -## 空闲线程 Idle Thread - -空闲线程是 RT-Thread 中优先级最低的线程。 - -当系统中没有其他就绪线程时: - -```text -没有其他 READY Thread - ↓ - Idle Thread -``` - -空闲线程的特点: - -- 优先级最低 -- 系统始终存在 -- 通常一直保持 READY -- 不能被挂起 -- 通常内部是一个无限循环 - -空闲线程还有一些特殊作用。 - -### 回收线程资源 - -动态线程运行结束后,并不会立刻完成全部资源回收。 - -线程结束后会进入: - -```text -rt_thread_defunct -``` - -僵尸线程队列。 - -之后由 Idle Thread 回收这些线程占用的资源。 - -### 执行 Idle Hook - -RT-Thread 允许给空闲线程设置 Hook 函数。 - -因此可以在 Idle Thread 中完成: - -- 功耗管理 -- 看门狗喂狗 -- CPU 空闲处理 - -等后台工作。 - ---- - -## 线程管理 API - -RT-Thread 提供两种主要方式创建线程: - -```text -线程 - │ - ├── 静态线程 - │ - └── 动态线程 -``` - -两种方式最大的区别在于: - -**线程控制块和线程栈由谁分配。** - ---- - -## 静态线程 - -静态线程的: - -- 线程控制块 -- 线程栈 - -由用户自己定义。 - -一般设置为: - -```c -static struct rt_thread thread1; - -static rt_uint8_t thread1_stack[1024]; -``` - -静态线程使用: - -```c -rt_thread_init(); -``` - -进行初始化。 - -函数形式为: - -```c -rt_err_t rt_thread_init( - struct rt_thread *thread, - const char *name, - void (*entry)(void *parameter), - void *parameter, - void *stack_start, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick -); -``` - -主要参数如下: - -| 参数 | 含义 | -| ---- | ---- | -| `thread` | 线程控制块 | -| `name` | 线程名称 | -| `entry` | 线程入口函数 | -| `parameter` | 入口函数参数 | -| `stack_start` | 线程栈起始地址 | -| `stack_size` | 线程栈大小 | -| `priority` | 线程优先级 | -| `tick` | 时间片 | - -例如: - -```c -ALIGN(RT_ALIGN_SIZE) -static char thread2_stack[1024]; - -static struct rt_thread thread2; - -static void thread2_entry(void *parameter) -{ - while (1) - { - rt_kprintf("thread2\n"); - - rt_thread_mdelay(500); - } -} -``` - -初始化: - -```c -rt_thread_init( - &thread2, - "thread2", - thread2_entry, - RT_NULL, - &thread2_stack[0], - sizeof(thread2_stack), - 20, - 10 -); -``` - -但是此时线程仍然只是: - -```text -INIT -``` - -并没有真正参与调度。 - ---- - -## 动态线程 - -动态线程使用: - -```c -rt_thread_create(); -``` - -创建。 - -函数会从动态内存堆中自动申请: - -- 线程控制块 -- 线程栈 - -基本形式: - -```c -rt_thread_t rt_thread_create( - const char *name, - void (*entry)(void *parameter), - void *parameter, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick -); -``` - -例如: - -```c -rt_thread_t tid; - -tid = rt_thread_create( - "thread1", - thread1_entry, - RT_NULL, - 1024, - 20, - 10 -); -``` - -可以简单对比: - -| 类型 | API | 内存来源 | -| ---- | ---- | ---- | -| 静态线程 | `rt_thread_init()` | 用户提供 | -| 动态线程 | `rt_thread_create()` | 系统动态分配 | - ---- - -## 启动线程 - -不管通过: - -```c -rt_thread_init() -``` - -还是: - -```c -rt_thread_create() -``` - -创建线程以后,线程最初都处于: - -```text -RT_THREAD_INIT -``` - -因此还需要调用: - -```c -rt_thread_startup(); -``` - -让线程进入就绪状态。 - -例如: - -```c -rt_thread_startup(&thread2); -``` - -或者动态线程: - -```c -rt_thread_startup(tid); -``` - -完整流程: - -```text -定义入口函数 - ↓ -创建 / 初始化线程 - ↓ -INIT - ↓ -rt_thread_startup() - ↓ -READY - ↓ -调度器 - ↓ -RUNNING -``` - -需要注意: - -如果启动的新线程优先级比当前线程高,那么线程进入 READY 后可能立即抢占当前线程运行。 - ---- - -## 线程延时与 CPU 使用权 - -在抢占式 RTOS 中,线程不需要使用 CPU 时应该主动让出 CPU。 - -RT-Thread 提供了线程延时 API。 - -以 Tick 为单位: - -```c -rt_thread_delay(tick); -``` - -以毫秒为单位: - -```c -rt_thread_mdelay(ms); -``` - -例如: - -```c -while (1) -{ - rt_kprintf("thread running\n"); - - rt_thread_mdelay(1000); -} -``` - -运行过程: - -```text -线程运行 - ↓ -打印信息 - ↓ -rt_thread_mdelay(1000) - ↓ -线程进入挂起状态 - ↓ -CPU 运行其他线程 - ↓ -1000 ms 到达 - ↓ -线程重新进入 READY - ↓ -等待再次调度 -``` - -所以: - -```c -rt_thread_mdelay(); -``` - -不仅仅是简单的“延时”,它还会使当前线程暂时不参与调度,将 CPU 使用权交给其他线程。 - ---- - -## 线程学习小结 - -RT-Thread 的线程可以总结为: - -```text -线程 - │ - ├── 线程控制块 - │ └── struct rt_thread - │ - ├── 线程栈 - │ └── 保存上下文和局部变量 - │ - ├── 状态 - │ ├── INIT - │ ├── READY - │ ├── RUNNING - │ ├── SUSPEND - │ └── CLOSE - │ - ├── 优先级 - │ └── 数字越小优先级越高 - │ - ├── 时间片 - │ └── 同优先级线程轮转 - │ - └── 入口函数 - └── thread_entry() -``` - -线程最基本的使用流程: - -```text -编写线程入口函数 - ↓ -创建线程 - ↓ -设置栈、优先级、时间片 - ↓ -启动线程 - ↓ -进入 READY - ↓ -等待调度器调度 - ↓ -RUNNING - ↓ -delay / wait - ↓ -让出 CPU -``` - ---- - -## SCons 构建系统 - -RT-Thread 使用: - -```text -SCons -``` - -作为工程构建工具。 - -SCons 是基于 Python 的自动化构建工具。 - -RT-Thread 可以通过 SCons 实现: - -- 编译工程 -- 管理源码文件 -- 管理头文件路径 -- 管理宏定义 -- 管理不同模块 -- 根据配置裁剪代码 -- 生成 MDK / IAR 工程 - -因此,RT-Thread 的工程并不是单纯依赖 Keil 工程文件进行管理,而是可以通过 SCons 脚本描述整个工程结构。 - -常见相关文件有: - -```text -SConstruct - -SConscript -``` - -可以简单理解为: - -```text -SConstruct - ↓ -工程顶层构建脚本 - ↓ -调用各目录中的 SConscript - ↓ -添加源码、头文件和宏 - ↓ -SCons 根据配置完成构建 -``` - ---- - -## SConstruct - -`SConstruct` 通常是整个工程最上层的 SCons 构建文件。 - -可以将它理解为: - -**整个工程构建的入口脚本。** - -当执行: - -```bash -scons -``` - -时,SCons 会首先读取: - -```text -SConstruct -``` - -然后根据 SConstruct 中定义的内容继续执行其他构建脚本。 - -典型关系: - -```text -Project - │ - ├── SConstruct - │ - ├── applications - │ └── SConscript - │ - ├── drivers - │ └── SConscript - │ - └── rt-thread - └── ... -``` - -其中: - -```text -SConstruct -``` - -负责整个工程, - -各目录中的: - -```text -SConscript -``` - -负责当前模块。 - ---- - -## SConscript - -`SConscript` 用于描述某一个模块: - -**哪些源文件应该参与编译,以及这些文件需要哪些头文件和宏定义。** - -例如一个简单的目录: - -```text -applications - │ - ├── main.c - ├── led.c - ├── led.h - └── SConscript -``` - -其 SConscript 可以类似: - -```python -from building import * - -cwd = GetCurrentDir() - -src = Glob('*.c') - -CPPPATH = [cwd] - -group = DefineGroup( - 'Applications', - src, - depend = [''], - CPPPATH = CPPPATH -) - -Return('group') -``` - -这里几个重要内容分别表示: - -```python -cwd = GetCurrentDir() -``` - -获取当前目录。 - -```python -src = Glob('*.c') -``` - -获取当前目录中的 C 文件。 - -```python -CPPPATH = [cwd] -``` - -添加头文件搜索路径。 - -最后通过: - -```python -DefineGroup() -``` - -把这些文件组成一个编译组。 - -因此可以简单理解: - -```text -SConscript - ↓ -告诉 SCons: - -哪些 .c 要编译 - -哪些 include 目录要加入 - -需要哪些宏 - -满足什么条件才编译 -``` - ---- - -## DefineGroup - -RT-Thread 的 SCons 脚本中经常可以看到: - -```python -DefineGroup() -``` - -例如: - -```python -group = DefineGroup( - 'Applications', - src, - depend = [''], - CPPPATH = CPPPATH -) -``` - -可以把它理解为: - -**定义一个工程代码组。** - -第一个参数: - -```python -'Applications' -``` - -表示组名称。 - -第二个参数: - -```python -src -``` - -表示需要编译的源文件。 - -还可以指定: - -```python -CPPPATH -``` - -头文件搜索路径。 - -以及: - -```python -CPPDEFINES -``` - -宏定义。 - -例如: - -```python -group = DefineGroup( - 'Drivers', - src, - depend = ['BSP_USING_UART1'], - CPPPATH = CPPPATH -) -``` - -表示: - -只有: - -```text -BSP_USING_UART1 -``` - -被使能时,这一组代码才会加入工程。 - -这就是 RT-Thread 可以根据配置自动裁剪工程的重要原因之一。 - ---- - -## SCons 基本命令 - -进入 RT-Thread BSP 或工程目录后,可以直接使用: - -```bash -scons -``` - -编译工程。 - -基本流程: - -```text -工程目录 - ↓ -scons - ↓ -读取 SConstruct - ↓ -加载 SConscript - ↓ -分析工程配置 - ↓ -编译源码 - ↓ -链接 - ↓ -生成最终程序 -``` - -如果代码没有发生变化,SCons 会根据依赖关系判断哪些文件需要重新编译,而不是每次都重新编译整个工程。 - ---- - -## 清理工程 - -如果希望删除已经生成的编译文件,可以使用: - -```bash -scons -c -``` - -即: - -```text -scons -c - ↓ -Clean - ↓ -清理工程编译产生的文件 -``` - -通常在工程配置发生较大变化或者需要完全重新构建时使用。 - ---- - -## 生成 Keil MDK5 工程 - -RT-Thread 可以通过 SCons 自动生成 Keil 工程。 - -对于 MDK5: - -```bash -scons --target=mdk5 -``` - -执行后 SCons 会根据当前工程中的: - -```text -SConstruct -SConscript -rtconfig.h -``` - -等配置重新生成 MDK5 工程。 - -因此如果修改了: - -- SConscript -- 软件包 -- menuconfig 配置 -- BSP 配置 - -通常需要重新执行: - -```bash -scons --target=mdk5 -``` - -让 Keil 工程同步最新配置。 - -其他常见目标还有: - -```bash -scons --target=mdk4 -``` - -生成 MDK4 工程。 - -```bash -scons --target=iar -``` - -生成 IAR 工程。 - -可以总结为: - -| 命令 | 作用 | -| ---- | ---- | -| `scons` | 编译工程 | -| `scons -c` | 清理工程 | -| `scons --target=mdk5` | 生成 MDK5 工程 | -| `scons --target=mdk4` | 生成 MDK4 工程 | -| `scons --target=iar` | 生成 IAR 工程 | - ---- - -## menuconfig 与 SCons - -RT-Thread 可以使用: - -```bash -menuconfig -``` - -对系统功能进行配置。 - -例如: - -```text -RT-Thread Kernel -RT-Thread Components -Hardware Drivers -Network -File System -Packages -``` - -配置完成后会生成或修改相关配置内容,例如: - -```text -.config -``` - -以及: - -```text -rtconfig.h -``` - -一个典型配置流程为: - -```text -menuconfig - ↓ -选择需要的功能 - ↓ -保存配置 - ↓ -更新 rtconfig.h - ↓ -SCons 根据宏判断代码是否参与编译 - ↓ -生成新的工程 -``` - -因此: - -```text -menuconfig -``` - -负责“选择功能”, - -而: - -```text -SCons -``` - -负责“根据这些功能构建工程”。 - ---- - -## pkgs --update - -如果工程使用了 RT-Thread 软件包,修改配置后通常需要执行: - -```bash -pkgs --update -``` - -它会根据当前配置更新对应的软件包。 - -常见流程: - -```text -menuconfig - ↓ -选择软件包 - ↓ -保存 - ↓ -pkgs --update - ↓ -下载 / 更新软件包 - ↓ -scons --target=mdk5 - ↓ -重新生成 Keil 工程 -``` - -因此,在使用 RT-Thread 软件包时,可以记住下面这组命令: - -```bash -menuconfig - -pkgs --update - -scons --target=mdk5 -``` - ---- - -## SCons 与 RT-Thread 工程的关系 - -整个构建流程可以理解为: - -```text -menuconfig - ↓ -决定系统需要什么功能 - ↓ -.config / rtconfig.h - ↓ -SConstruct - ↓ -调用各模块 SConscript - ↓ -根据宏决定哪些源码参与编译 - ↓ -SCons - ↓ -编译 / 生成 IDE 工程 -``` - -例如: - -```text -menuconfig 中开启 UART - ↓ -BSP_USING_UART = y - ↓ -生成对应宏 - ↓ -SConscript 检查 depend - ↓ -UART 驱动加入工程 - ↓ -编译 UART 驱动 -``` - -所以 SCons 并不是简单地“代替 Keil 点一下 Build”。 - -它实际上承担了: - -**整个 RT-Thread 工程代码组织和自动构建的工作。** - diff --git "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 5ee67c9f8e53c449942c005ecd11da32499f8e2a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1 +0,0 @@ -# 第一天作业完成 \ No newline at end of file diff --git "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/day2\346\225\264\347\220\206\346\226\207\346\241\243.md" "b/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/day2\346\225\264\347\220\206\346\226\207\346\241\243.md" deleted file mode 100644 index fe3275a6f26357b9e51f7edd9af538892c8097a4..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/day2\346\225\264\347\220\206\346\226\207\346\241\243.md" +++ /dev/null @@ -1,168 +0,0 @@ -## 1、上电复位 Reset_Handler → main () 完整调用 - -**上电/硬件复位** - - ↓ - -**CPU读取向量表首地址:0x08000000** - - 0x08000000:设置主堆栈指针 MSP = __initial_sp(栈顶) __ - - 0x08000004:取出 Reset_Handler 的地址,PC跳转到 Reset_Handler - -↓ - -**Reset_Handler(汇编复位处理函数)** - -├─ 调用 SystemInit():配置系统时钟、FPU、向量表偏移寄存器SCB‑VTOR - -├─ 复制Flash中.data段数据到RAM(带初值全局变量) - -├─ 清零RAM中.bss段(无初值全局变量清零) - -└─ 跳转到 rtthread_startup() 【RT‑Thread改写,不再跳__main】 - - ↓ - - **rtthread_startup() RT‑Thread入口函数 ** - -├─ rt_hw_interrupt_disable() 关闭全局中断 - -├─ rt_board_init() 板级硬件初始化 - -├─ rt_components_board_init() 【执行INIT_BOARD_EXPORT注册的函数】 - -├─ rt_system_heap_init() 系统堆初始化 - -├─ rt_scheduler_init() 调度器初始化 - -├─ rt_hw_timer_init() 硬件定时器初始化 - -├─ rt_system_scheduler_start() 启动调度器,创建main线程 - -↓ - -**main线程入口,执行 main() 函数** - -## 2、INIT_BOARD_EXPORT与 INIT_APP_EXPORT底层原理 - -(1)本质:GCC 编译器扩展 + 链接脚本收集函数指针 - -两个宏底层共用一套机制,区别只是初始化优先级(段编号不同) - -``` -//简化底层宏 -#define INIT_EXPORT(fn, level) \ - const void *__rt_init_##fn __attribute__((used, section(".rti_fn."#level))) = fn; -``` - -**attribute((used))**:告诉编译器不要把这个变量优化删掉。 - -**attribute(section(".rti_fn.level")))**:把函数指针变量强制放到自定义段`.rti_fn.x`,不放在普通 data/text 段。 - -| 宏 | 对应段 | 调用时机 | 优先级 level | -| :---------------------- | :---------- | :----------------------------------------------------------- | :---------------- | -| `INIT_BOARD_EXPORT(fn)` | `.rti_fn.1` | `rt_components_board_init()`,在调度器启动前,main 线程创建**之前** | 1,板级早期初始化 | -| `INIT_APP_EXPORT(fn)` | `.rti_fn.6` | `rt_components_app_init()`,在 main 函数内部,应用层初始化 | 6,应用层初始化 | - -(2) 链接脚本的工作 - -链接脚本中定义符号:`__rt_init_start`、`__rt_init_end`,并且加`KEEP()`,强制保留`.rti_fn.*`全部段,不被垃圾回收。 - -链接时,所有文件中用`INIT_*_EXPORT`标记的函数指针,**全部被链接器收集,按 level 从小到大顺序排布在.rti_fn 段**,形成一张函数指针数组。 - -(3) 运行时调用 - -1. `rt_components_board_init()`:从`__rt_init_start`遍历到`__rt_init_end`,调用 level=1(BOARD)的所有函数。此时中断还未打开,适合外设、时钟等板级底层初始化。 -2. 当 main 线程运行进入`main()`,内部调用`rt_components_app_init()`,遍历执行 level=6(APP)的函数,做业务应用初始化。 - -核心:**不需要手动调用函数,编译链接阶段链接器自动收集函数指针;运行时 RT‑Thread 框架遍历这个指针数组自动调用**,实现 “注册即执行” 的自动初始化效果。 - -## 2. 内核基础与对象模型 - -### 2.1 对象容器与控制块 - -RT‑Thread 面向对象设计思想 - -RT‑Thread 用**C 语言模拟面向对象**:把内核资源(线程、信号量、消息队列等)抽象为`rt_object`基对象,所有内核控制块第一个成员都是`struct rt_object`;对象统一交给对象容器管理,提供统一接口,实现封装、继承思想。 - -`rt_object`记录对象类型、名字、链表节点,所有内核对象挂入全局对象容器链表 - -上层 API 不直接操作硬件,操作对象句柄,做到硬件隔离。 - -### `struct rt_thread`关键成员 - -| 成员 | 作用 | -| :----------------- | :----------------------------------------------------------- | -| `stat` | 线程状态,标记线程当前处于:初始 / 就绪 / 运行 / 挂起 / 关闭状态,调度器依据该字段管理线程 | -| `current_priority` | 线程当前优先级;线程运行时实际使用优先级,支持动态修改,可用于优先级继承 | -| `init_tick` | 线程时间片(tick),时间片轮转调度时,线程剩余运行时间计数;时间片耗尽触发上下文切换 | -| `sp` | 线程栈指针,保存线程切换时的栈地址;上下文切换时,保存 / 恢复该指针,实现线程现场保存 | - -### 2.2 静态对象 vs 动态对象 - -| 对比项 | 静态对象 | 动态对象 | -| :------- | :------------------------------------ | :----------------------------------------------- | -| 分配位置 | 编译期分配,放在全局 RAM (.data/.bss) | 运行时`rt_malloc()`从系统堆分配 | -| 生命周期 | 程序整个运行周期,上电即存在 | 运行时创建,可动态销毁 | -| 释放机制 | 不需要释放;程序结束才回收内存 | 调用`rt_object_delete()`释放回堆;堆内存碎片风险 | - -> 高可靠场景强制使用静态线程的原因: - -1. 动态创建依赖堆内存`rt_malloc`,堆会产生**内存碎片**,极端场景 malloc 失败,线程创建失败,系统死机; -2. 静态对象内存编译期确定,不会出现内存分配失败,内存可控、可预估; -3. 避免内存泄漏、野指针风险;工业、航天等高可靠系统不允许运行时动态申请内存。 - -## 3. 线程状态机与调度算法 - -### 3.1 RT‑Thread 五态模型 - -五种状态:**初始态、就绪态、运行态、挂起态、关闭态** - -1. 初始态 (RT_THREAD_INIT):线程对象初始化完成,未启动。 - - API:`rt_thread_init()` / `rt_thread_create()` → 进入初始态 - - `rt_thread_startup()`:初始态 → **就绪态** - - - -2. 就绪态 (RT_THREAD_READY):线程条件满足,等待 CPU 调度。 - - 高优先级线程抢占:就绪态 → **运行态** - - `rt_thread_suspend()`:就绪态 → **挂起态** - -3. 运行态 (RT_THREAD_RUNNING):线程正在 CPU 执行。 - - 更高优先级线程就绪抢占:运行态 → **就绪态** - - `rt_thread_delay()` / 等待信号量:运行态 → **挂起态** - - 时间片耗尽:运行态 → **就绪态** - -4. 挂起态 (RT_THREAD_SUSPEND):线程阻塞,不参与调度(延时、等待资源) - - 延时到期 / 获取信号量成功:挂起态 → **就绪态** - -5. 关闭态 (RT_THREAD_CLOSE):线程运行结束,退出。 - - `rt_thread_exit()` / `rt_thread_delete()`:任意可终止状态进入关闭态。 - -## 3.2 调度机制剖析 - -### 抢占式调度 - -1. 核心规则:高优先级线程优先获得 CPU - - 只要有更高优先级线程变为就绪态,立刻触发上下文切换,抢占当前正在运行的低优先级线程;低优先级线程让出 CPU,进入就绪队列等待。 - -2. 触发时机:中断退出、释放信号量、唤醒高优先级线程。 - -3. RT‑Thread 优先级数字越小优先级越高。 - -### 时间片轮转调度 - -1. **同一优先级多个线程之间的调度策略**。 -2. 同优先级线程平分 CPU 时间,每个线程分配`init_tick`时间片;线程运行消耗 tick 计数,计数减为 0,线程让出 CPU,切换同优先级下一个就绪线程。 -3. 如果线程主动阻塞(delay),剩余时间片会直接作废。 \ No newline at end of file diff --git "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\273\243\347\240\201/led_task.c" "b/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\273\243\347\240\201/led_task.c" deleted file mode 100644 index 21678b99f6c2374b6016b20ac8677645e1c5b728..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\273\243\347\240\201/led_task.c" +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include -#include - -#define LED_R_PIN GET_PIN(F, 12) -#define LED_B_PIN GET_PIN(F, 11) - - -rt_thread_t th_led1 = RT_NULL; -struct rt_thread th_led2_tcb; -ALIGN(RT_ALIGN_SIZE) -static char th_led2_stack[1024]; - -static void th_led1_entry(void *parameter) -{ - rt_uint32_t cnt = 0; - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - while(1) - { - static rt_base_t s = PIN_LOW; - s = (s == PIN_LOW) ? PIN_HIGH : PIN_LOW; - rt_pin_write(LED_R_PIN, s); - cnt++; - rt_kprintf("th_led1 cnt: %d , tick: %d\n", cnt, rt_tick_get()); - rt_thread_mdelay(500); - } -} - -static void th_led2_entry(void *parameter) -{ - rt_uint32_t cnt = 0; - rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); - while(1) - { - static rt_base_t s = PIN_LOW; - s = (s == PIN_LOW) ? PIN_HIGH : PIN_LOW; - rt_pin_write(LED_B_PIN, s); - cnt++; - rt_kprintf("th_led2 cnt: %d\n", cnt); - rt_thread_mdelay(500); - } -} - -static int led_task_init(void) -{ - th_led1 = rt_thread_create("th_led1", th_led1_entry, RT_NULL, - 1024, 16, 5); - if (th_led1 != RT_NULL) rt_thread_startup(th_led1); - - if (rt_thread_init(&th_led2_tcb, "th_led2", th_led2_entry, RT_NULL, - th_led2_stack, sizeof(th_led2_stack), - 16, 5) == RT_EOK) - rt_thread_startup(&th_led2_tcb); - - return 0; -} -INIT_APP_EXPORT(led_task_init); diff --git "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\273\243\347\240\201/main.c" "b/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\273\243\347\240\201/main.c" deleted file mode 100644 index 511f5f17ab62155177afe2ecb25419c15c163b41..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\273\243\347\240\201/main.c" +++ /dev/null @@ -1,10 +0,0 @@ -#include - -int main(void) -{ - while(1) - { - rt_thread_mdelay(1000); - } - return RT_EOK; -} diff --git "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\273\243\347\240\201/monitor_task.c" "b/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\273\243\347\240\201/monitor_task.c" deleted file mode 100644 index a5cde81853ac1884bac071c53e50f428d6123a36..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\273\243\347\240\201/monitor_task.c" +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-18 25488 the first version - */ -#include -#include -#include - -extern rt_thread_t th_led1; /* 来自 led_task.c */ -extern struct rt_thread th_led2_tcb; /* 来自 led_task.c */ - -#define KEY0_PIN GET_PIN(C, 0) - -static rt_thread_t th_monitor = RT_NULL; -static rt_uint8_t led1_suspended = 0; - -static const char *thread_stat_name(rt_uint8_t stat) -{ - switch (stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_READY: return "ready"; - case RT_THREAD_SUSPEND: return "suspend"; - case RT_THREAD_RUNNING: return "running"; - case RT_THREAD_CLOSE: return "close"; - default: return "init"; - } -} - -static rt_ubase_t thread_remain(rt_thread_t t) -{ - - return (rt_ubase_t)t->sp - (rt_ubase_t)t->stack_addr; -} - -static void print_thread_state(const char *name, rt_thread_t t) -{ - if (t == RT_NULL) { rt_kprintf("%s: not created\n", name); return; } - rt_kprintf("%s: stat=%s, remain_stack=%u / %u\n", - name, thread_stat_name(t->stat), - (unsigned int)thread_remain(t), - (unsigned int)t->stack_size); -} - -static void th_monitor_entry(void *parameter) -{ - rt_pin_mode(KEY0_PIN, PIN_MODE_INPUT_PULLUP); - - while(1) - { - rt_kprintf("--- monitor ---\n"); - print_thread_state("th_led1", th_led1); - print_thread_state("th_led2", &th_led2_tcb); - - /* 按一次 KEY0 挂起 th_led1,再按一次恢复 */ - if (rt_pin_read(KEY0_PIN) == PIN_LOW) - { - rt_thread_mdelay(20); - if (rt_pin_read(KEY0_PIN) == PIN_LOW) - { - if (led1_suspended) - { - rt_thread_resume(th_led1); - led1_suspended = 0; - rt_kprintf("KEY0: resume th_led1\n"); - } - else - { - rt_thread_suspend(th_led1); - led1_suspended = 1; - rt_kprintf("KEY0: suspend th_led1\n"); - } - while (rt_pin_read(KEY0_PIN) == PIN_LOW) - rt_thread_mdelay(10); - } - } - - rt_thread_mdelay(2000); - } -} - -static void thread_dump(void) -{ - print_thread_state("th_led1", th_led1); - print_thread_state("th_led2", &th_led2_tcb); - print_thread_state("th_monitor", th_monitor); -} -MSH_CMD_EXPORT(thread_dump, dump status of led and monitor threads); - -static int monitor_task_init(void) -{ - th_monitor = rt_thread_create("monitor", th_monitor_entry, RT_NULL, - 2048, 10, 5); - if (th_monitor != RT_NULL) rt_thread_startup(th_monitor); - return 0; -} -INIT_APP_EXPORT(monitor_task_init); diff --git "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 9effcccc43bc9031973d64e3519020a5816f1077..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,29 +0,0 @@ -# 第一天笔记 - -今天学习Git基础操作,完成Gitee团队协作流程实践。 - -## 1. Fork与克隆 - -在Gitee Fork主仓库,使用 git clone 将仓库下载到本地。 - -## 2. 配置upstream - -配置上游原始仓库,用来同步主仓库最新代码,避免代码冲突。 - -## 3. 搭建目录 - -在 2026/第3组/ 创建个人文件夹,划分笔记、作业目录。 - -## 4. 文件提交流程 - -```bash - -git add . -git commit -m "提交备注" -git pull -git push -  - -## 5. 发起PR - -代码上传个人仓库后,创建Pull Request,等待管理员合并至主仓库。 \ No newline at end of file diff --git "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index b1ad49ded972d518321ea75e9656de3980417f66..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\221\250\346\270\270/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,53 +0,0 @@ -## 一、RT-Thread 启动流程 - -**启动链路**:上电复位 → Reset_Handler → SystemInit → 拷贝.data、清零.bss → rtthread_startup() → 初始化板级/堆/调度器 → 开启调度 → 进入main线程 - -**自动初始化两大宏** - -**INIT_BOARD_EXPORT**:调度器启动前、main之前执行,用于底层硬件初始化(GPIO、串口) - -**INIT_APP_EXPORT**:进入main后执行,用于应用业务初始化 - -**原理**:利用GCC段属性+链接脚本,自动收集函数指针,实现**注册即调用**,无需手动执行。 - -## 二、RT-Thread 对象模型 - -RT-Thread 使用 C 语言模拟面向对象,所有内核对象(线程、信号量等)均继承 **rt_object**,统一由内核容器管理,实现解耦与标准化。 - -**线程控制块核心成员** - -**stat**:线程状态 - -**current_priority**:当前优先级 - -**init_tick**:时间片大小 - -**sp**:线程栈指针(上下文切换保存现场) - -## 三、静态对象 vs 动态对象 - -**静态**:编译分配全局内存,稳定、无碎片、高可靠,工业场景首选 - -**动态**:运行时malloc分配,灵活,有内存碎片、分配失败风险 - -## 四、线程五态模型 - -**初始态**:线程创建完成未启动 - -**就绪态**:条件满足,等待CPU调度 - -**运行态**:正在占用CPU执行 - -**挂起态**:阻塞、延时、等待资源,不参与调度 - -**关闭态**:线程结束/被删除 - -## 五、调度机制 - -RT-Thread 调度 = **优先级抢占 + 同优先级时间片轮转** - -优先级数值**越小越高** - -高优先级随时抢占CPU - -同优先级按时间片轮流执行 \ No newline at end of file diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index dcca0d65e2817b4d93dcee21813f5fcbda158c85..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,208 +0,0 @@ -# 【DAY3】RT-Thread 线程间同步与通信 -- 张思源(第3组) - -- **目标平台**:STM32F407 星火一号(`stm32f407-rtspark`,RT-Thread Studio 工程 `03_ipc_sample`) -- **提交内容**:本 `README.md`(思考题 + 实验报告)+ 4 份源码(`parking.c` / `ticket.c` / `startup.c` / `package.c`) -- **MSH 命令**:`parking_demo` / `ticket_demo` / `startup_demo` / `package_demo` - ---- - -## 练习一:停车场车位管理(计数信号量) - -### 实验设计 - -| 对象 | 类型 | 说明 | -| --- | --- | --- | -| `parking_sem` | 计数信号量 | 初始值 3,表示 3 个车位 | -| `car1`~`car5` | 5 个线程 | 优先级 20,时间片 10,栈 1024B | - -每辆车通过 `rt_sem_take(parking_sem, 2000ms)` 申请车位,2 秒超时后打印等待信息并重新申请;拿到车位后停留 3.5 秒离开并 `rt_sem_release` 释放车位。已用车位数通过 `PARKING_NUM - parking_sem->value` 计算得出。 - -### 串口日志 - -``` -msh >parking_demo -car5 entered, used spaces: 1 -car4 entered, used spaces: 2 -car3 entered, used spaces: 3 -car2 waiting for a space -car1 waiting for a space -car5 left -car2 entered, used spaces: 3 -car4 left -car1 entered, used spaces: 3 -car3 left -car2 left -car1 left -``` - -### 结果分析 - -- 前 3 辆车(car5/car4/car3)成功获取车位,`used spaces` 从 1 递增到 3,说明信号量初始值为 3 且最多 3 辆同时进入。 -- car2/car1 在 2 秒内未获取车位,打印 `waiting for a space`,验证了超时机制。 -- car5 离开后释放车位,car2 立即补上;car4 离开后 car1 补上,说明等待线程被正确唤醒。 -- 重复执行 `parking_demo` 时,检测到 `parking_sem != RT_NULL` 会拒绝启动;所有车辆离开后自动清理信号量,可再次启动。 - -### 思考题:为什么使用计数信号量,而不是互斥量? - -1. **资源数量不同**:互斥量的值只有 0 和 1,只能保护单一资源;计数信号量的值可以是任意非负整数,能表示多个同类资源。停车场有 3 个车位,需要初始值为 3 的计数信号量,互斥量无法表达"3 个可用资源" -2. **所有权语义不同**:互斥量有所有权概念,谁获取谁释放,不能由其他线程代为释放;而计数信号量没有所有权,一辆车获取车位后,可以由同一线程在离开时释放,也可以用在不同线程之间。停车场场景中,车辆线程获取和释放是同一个线程,但计数信号量的灵活性更适合资源池模式 -3. **适用场景**:互斥量适用于保护临界区(同一时刻只允许一个线程访问共享资源),计数信号量适用于管理有限资源池(允许 N 个线程同时访问 N 个资源)。停车场属于资源池问题,因此用计数信号量 - ---- - -## 练习二:多窗口售票系统(互斥量) - -### 实验设计 - -| 对象 | 类型 | 说明 | -| --- | --- | --- | -| `ticket_mutex` | 互斥量 | 保护售票临界区 | -| `ticket_count` | 全局变量 | 剩余票数,初始值 20 | -| `winA` / `winB` | 2 个线程 | 优先级 20,时间片 10,栈 1024B | - -每个窗口线程循环执行:加锁 -> 检查余票 -> 打印票号 -> 余票减 1 -> 解锁 -> 延时 500ms。票号即减 1 之前的 `ticket_count` 值(20~1)。 - -### 串口日志 - -``` -msh >ticket_demo -window B sold ticket 20 -window A sold ticket 19 -window B sold ticket 18 -window A sold ticket 17 -... -window B sold ticket 2 -window A sold ticket 1 -``` - -### 结果分析 - -- 20 张票全部售出,票号 20~1 无重复,余票最终为 0,未出现超卖 -- 窗口 A 和 B 交替售票,说明互斥量有效保证了同一时刻只有一个窗口修改 `ticket_count` -- 售完后自动清理互斥量,可重复执行 - -### 对比实验(不加互斥量) - -暂时注释掉 `rt_mutex_take` 和 `rt_mutex_release`,在打印 `ticket_count` 之后、`ticket_count--` 之前加入 `rt_thread_mdelay(10)`,观察是否出现重复售票 - -不加锁时,窗口 A 打印票号后被 `mdelay(10)` 切走,窗口 B 读到相同的 `ticket_count`,导致两张票打印相同票号,出现重复售票。加回互斥量后问题消失 - -### 思考题:为什么只给 `ticket_count--` 加锁仍可能不安全? - -临界区不仅仅是 `ticket_count--` 这一条语句,而是**整个售票过程**:检查余票 -> 减票 -> 记录票号 -> 打印。如果只给 `ticket_count--` 加锁: - -1. 线程 A 在锁外检查 `ticket_count > 0`(假设为 1),认为还有票 -2. 线程 A 被切走,线程 B 也在锁外检查 `ticket_count > 0`(仍为 1),也认为还有票 -3. 线程 A 进入锁内执行 `ticket_count--`,票数变为 0 -4. 线程 B 进入锁内执行 `ticket_count--`,票数变为 -1,**超卖** - -因此,必须把"检查余票"和"修改余票"作为一个**不可分割的原子操作**一起加锁,否则检查和修改之间的时间窗口会导致竞态条件 - ---- - -## 练习三:系统启动条件检查(事件集) - -### 实验设计 - -| 对象 | 类型 | 说明 | -| --- | --- | --- | -| `startup_event` | 事件集 | 3 个事件位,AND 模式等待 | -| `EVENT_NET_READY` | 事件位 | `(1 << 0)` 网络就绪 | -| `EVENT_SENSOR_READY` | 事件位 | `(1 << 1)` 传感器就绪 | -| `EVENT_STORAGE_READY` | 事件位 | `(1 << 2)` 存储就绪 | -| `net` / `sensor` / `storage` / `main` | 4 个线程 | 初始化线程延时 1s/2s/5s,主业务线程等待 | - -主业务线程第一次用 `RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR` 等待 3 个事件,超时 3 秒。超时后通过 `startup_event->set` 检查哪些位缺失,打印未就绪模块。然后第二次用 `RT_WAITING_FOREVER` 继续等待,直到存储模块(5 秒)就绪 - -### 串口日志 - -``` -msh >startup_demo -net ready -sensor ready -wait modules timeout, storage is not ready -storage ready -all modules are ready -business task started -``` - -### 结果分析 - -- 网络模块 1 秒就绪,传感器模块 2 秒就绪,均在 3 秒超时前完成 -- 存储模块 5 秒就绪,超过 3 秒超时,主业务线程正确打印 `storage is not ready` -- 超时后第二次等待(FOREVER),存储模块在 5 秒就绪后主业务线程立即被唤醒,打印启动信息 -- 注意:超时判断使用 `startup_event->set` 而非 `received` 变量,因为 `rt_event_recv` 超时返回时不会更新 `received`(仍为 0),直接读事件集的 `set` 成员才能获取当前已到达的事件位 - -### 思考题:如果用三个独立信号量实现,与使用事件集相比有什么区别? - -| 对比项 | 三个独立信号量 | 事件集 | -| --- | --- | --- | -| 等待方式 | 需要 3 次 `rt_sem_take` 串行等待,每次只能等一个信号量 | 一次 `rt_event_recv` 即可同时等待多个事件位 | -| AND/OR 逻辑 | 只能手动实现 AND(依次 take),OR 难以实现 | 原生支持 `RT_EVENT_FLAG_AND` 和 `RT_EVENT_FLAG_OR` | -| 超时控制 | 每次 take 有独立超时,总等待时间难以精确控制 | 一次 recv 统一超时,超时后可知哪些事件已到达、哪些缺失 | -| 资源开销 | 3 个信号量控制块 | 1 个事件集控制块 | -| 代码复杂度 | 高,需要循环等待、判断超时、处理中途唤醒 | 低,一行 `rt_event_recv` 搞定 | -| 场景适配 | 适合各模块独立、无关联的等待 | 适合多条件组合等待(全部就绪 / 任一就绪) | - -事件集天生为"多事件组合等待"设计,用三个信号量虽然能实现相同功能,但代码更复杂、超时控制更困难、且无法高效实现 OR 逻辑 - ---- - -## 练习四:快递分拣中心(消息队列) - -### 实验设计 - -| 对象 | 类型 | 说明 | -| --- | --- | --- | -| `package_mq` | 消息队列 | 容量 5 条,每条 `sizeof(struct package_msg)` | -| `sender1` / `sender2` | 2 个发送线程 | 分别产生 5 条消息,id 从 1001 和 2001 开始 | -| `sorter` | 1 个分拣线程 | 接收消息并按区域统计 | - -消息结构体包含 `id`(快递编号)、`region`(1=华东/2=华南/3=华北)、`weight`(重量)。发送线程用 `rt_mq_send` 发送,队列满时返回 `-RT_EFULL`,记录失败次数。发完 5 条后发送 `region = 0xFF` 的结束消息。分拣线程收到 2 条结束消息后打印统计并退出 - -### 串口日志 - -``` -msh >package_demo -package 2001 -> south, weight: 300 g -package 1001 -> south, weight: 300 g -package 2002 -> north, weight: 600 g -package 1002 -> north, weight: 600 g -package 2003 -> east, weight: 900 g -package 1003 -> east, weight: 900 g -package 2004 -> south, weight: 1200 g -package 1004 -> south, weight: 1200 g -package 2005 -> north, weight: 1500 g -package 1005 -> north, weight: 1500 g -sender 2 finished, send failed: 0 -sender 1 finished, send failed: 0 -total packages: 10 -east: 2, south: 4, north: 4 -total weight: 9000 g -``` - -### 结果分析 - -- 10 条快递全部被分拣,编号 1001~1005 和 2001~2005 无重复 -- 各区域统计正确:华东 2、华南 4、华北 4,总计 10 条 -- 总重量 9000g = (300+600+900+1200+1500) x 2,计算正确 -- `send failed: 0`,说明发送间隔 100ms 足够让分拣线程消费消息,队列未满 -- 两个发送线程各发一条结束消息(`region = 0xFF`),分拣线程收到 2 条后正确退出并清理队列 - ---- - -## 总结 - -本次作业通过四个场景实践了 RT-Thread 的四种 IPC 机制: - -| IPC 机制 | 核心特点 | 适用场景 | -| --- | --- | --- | -| 计数信号量 | 值为非负整数,无所有权 | 资源池管理(停车场车位) | -| 互斥量 | 值为 0/1,有所有权,可优先级继承 | 临界区保护(售票系统) | -| 事件集 | 32 位事件位,支持 AND/OR | 多条件组合等待(启动检查) | -| 消息队列 | 缓冲多条结构化消息 | 线程间数据传递(快递分拣) | - -共同设计要点: -1. 所有 IPC 对象和线程创建后均检查返回值,失败时打印错误并退出 -2. 通过 MSH 命令启动,使用全局指针状态检测防止重复执行 -3. 任务完成后主动删除 IPC 对象并重置指针,保证可重复运行 diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package.c" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package.c" deleted file mode 100644 index 344c56964e56551374ea53f9bdb4a9e170b30e4c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package.c" +++ /dev/null @@ -1,126 +0,0 @@ -#include - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -#define REGION_EAST 1 -#define REGION_SOUTH 2 -#define REGION_NORTH 3 -#define REGION_END 0xFF //结束消息标记 -#define MQ_SIZE 5 //队列容量 - -static rt_mq_t package_mq = RT_NULL; - -static void sender_entry(void *parameter){ - rt_uint32_t base = (rt_uint32_t)parameter; - rt_uint8_t fail_count = 0; - struct package_msg msg; - - for (int i = 1; i <= 5; i++) - { - msg.id = base + i; - msg.region = (i % 3) + 1; - msg.weight = 300 * i; - - rt_err_t ret = rt_mq_send(package_mq, &msg, sizeof(msg)); - if (ret == -RT_EFULL) - { - fail_count++; - } - rt_thread_mdelay(100); - } - //发送完毕 - msg.id = 0; - msg.region = REGION_END; - rt_kprintf("sender %d finished, send failed: %d\n", base / 1000, fail_count); - rt_mq_send(package_mq, &msg, sizeof(msg)); -} - -static void sorter_entry(void *parameter){ - - rt_uint32_t total = 0; //总数量 - rt_uint32_t east = 0, south = 0, north = 0; //各区域计数 - rt_uint32_t total_weight = 0; //总重量 - rt_uint8_t end_count = 0; //收到结束消息计数 - struct package_msg msg; - - while(1){ - rt_mq_recv(package_mq, &msg, sizeof(msg), RT_WAITING_FOREVER); - if (msg.region == REGION_END){ - end_count++; - if (end_count == 2) //两个发送线程各发一条 - break; - } - else { - total++; - total_weight += msg.weight; - - char *region_name; - if (msg.region == REGION_EAST) { region_name = "east"; east++; } - else if (msg.region == REGION_SOUTH) { region_name = "south"; south++; } - else { region_name = "north"; north++; } - rt_kprintf("package %d -> %s, weight: %d g\n", msg.id, region_name, msg.weight); - } - } - //打印总的信息 - rt_kprintf("total packages: %d\n", total); - rt_kprintf("east: %d, south: %d, north: %d\n", east, south, north); - rt_kprintf("total weight: %d g\n", total_weight); - - //回收 - rt_mq_delete(package_mq); - package_mq = RT_NULL; -} - -static int package_demo(void){ - if (package_mq != RT_NULL) - { - rt_kprintf("package demo already running\n"); - return -1; - } - else { - package_mq = rt_mq_create("package", sizeof(struct package_msg), MQ_SIZE, RT_IPC_FLAG_PRIO); - if (package_mq == RT_NULL){ - rt_kprintf("package_mq create failed!\n"); - return -1; - } - else { - //创建sender1线程 - rt_thread_t sender1_thread = rt_thread_create("sender1", sender_entry, (void *)1000, 1024, 20, 10); - if(sender1_thread == RT_NULL){ - rt_kprintf("sender1 thread create failed\n"); - return -1; - } - else { - rt_thread_startup(sender1_thread); - } - - //创建sender2线程 - rt_thread_t sender2_thread = rt_thread_create("sender2", sender_entry, (void *)2000, 1024, 20, 10); - if(sender2_thread == RT_NULL){ - rt_kprintf("sender2 thread create failed\n"); - return -1; - } - else { - rt_thread_startup(sender2_thread); - } - - //创建sorter线程 - rt_thread_t sorter_thread = rt_thread_create("sorter", sorter_entry, (void *)0, 1024, 20, 10); - if(sorter_thread == RT_NULL){ - rt_kprintf("sorter thread create failed\n"); - return -1; - } - else { - rt_thread_startup(sorter_thread); - } - } - } - - return 0; -} -MSH_CMD_EXPORT(package_demo, a package demo); diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking.c" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking.c" deleted file mode 100644 index 049ebd356adf41d7b1f56e1982d73a42e25e58c4..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking.c" +++ /dev/null @@ -1,68 +0,0 @@ -#include - - -static rt_sem_t parking_sem = RT_NULL; -static rt_uint8_t car_count = 0; //车辆计数器 - -#define PARKING_NUM 3 - -#define CAR_NUM 5 - - -static void car_entry(void *parameter) -{ - rt_uint32_t car_id = (rt_uint32_t)parameter; - - while(1) - { - rt_err_t temp = rt_sem_take(parking_sem, rt_tick_from_millisecond(2000)); - if(temp == RT_EOK){ - rt_kprintf("car%d entered, used spaces: %d\n", car_id, PARKING_NUM - parking_sem->value); - rt_thread_mdelay(3500); - rt_sem_release(parking_sem); - rt_kprintf("car%d left\n", car_id); - car_count++; - if(car_count == CAR_NUM){ - rt_sem_delete(parking_sem); - parking_sem = RT_NULL; - car_count = 0; - } - break; - } - else{ - rt_kprintf("car%d waiting for a space\n", car_id); - } - } -} - -static int parking_demo(void) -{ - if (parking_sem != RT_NULL) - { - rt_kprintf("parking demo already running\n"); - return -1; - } - - parking_sem = rt_sem_create("parking", PARKING_NUM, RT_IPC_FLAG_PRIO); - - if(parking_sem == RT_NULL){ - rt_kprintf("parking_sem create failed\n"); - return -1; - } - else{ - for (rt_uint8_t i = 0; i < CAR_NUM; ++i) { - char name[8]; - rt_snprintf(name, sizeof(name), "car%d", i + 1); - rt_thread_t temp = rt_thread_create(name, car_entry, (void *)(i + 1), 1024, 20, 10); - if(temp == RT_NULL){ - rt_kprintf("car thread create failed\n"); - return -1; - } - else { - rt_thread_startup(temp); - } - } - } - return 0; -} -MSH_CMD_EXPORT(parking_demo, parking lot demo); diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup.c" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup.c" deleted file mode 100644 index 61c85d1b6de9a9b7929ffc21a6914f7473f58f7f..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup.c" +++ /dev/null @@ -1,109 +0,0 @@ -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) - -static rt_event_t startup_event = RT_NULL; - -static void net_entry(void *parameter){ - rt_thread_mdelay(1000); - rt_event_send(startup_event, EVENT_NET_READY); - rt_kprintf("net ready\n"); -} - -static void sensor_entry(void *parameter){ - rt_thread_mdelay(2000); - rt_event_send(startup_event, EVENT_SENSOR_READY); - rt_kprintf("sensor ready\n"); -} - -static void storage_entry(void *parameter){ - rt_thread_mdelay(5000); - rt_event_send(startup_event, EVENT_STORAGE_READY); - rt_kprintf("storage ready\n"); -} - -static void main_business_entry(void *parameter){ - rt_uint32_t received; - rt_err_t temp = rt_event_recv(startup_event, EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, rt_tick_from_millisecond(3000), &received); - if(temp == RT_EOK){ - rt_kprintf("all modules are ready\n business task started\n"); - } - else if (temp == -RT_ETIMEOUT) { - if((startup_event->set & EVENT_NET_READY) == 0){ - rt_kprintf("wait modules timeout, net is not ready\n"); - } - if((startup_event->set & EVENT_SENSOR_READY) == 0){ - rt_kprintf("wait modules timeout, sensor is not ready\n"); - } - if((startup_event->set & EVENT_STORAGE_READY) == 0){ - rt_kprintf("wait modules timeout, storage is not ready\n"); - } - rt_event_recv(startup_event, EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &received); - rt_kprintf("all modules are ready\n business task started\n"); - } - - //清理 - rt_event_delete(startup_event); - startup_event = RT_NULL; -} - -static int startup_demo(void){ - if (startup_event != RT_NULL) - { - rt_kprintf("startup demo already running\n"); - return -1; - } - else { - startup_event = rt_event_create("startup", RT_IPC_FLAG_PRIO); - if(startup_event == RT_NULL){ - rt_kprintf("startup event create failed\n"); - return -1; - } - else { - //创建net线程 - rt_thread_t net_thread = rt_thread_create("net", net_entry, (void *)0, 1024, 20, 10); - if(net_thread == RT_NULL){ - rt_kprintf("net thread create failed\n"); - return -1; - } - else { - rt_thread_startup(net_thread); - } - - //创建sensor线程 - rt_thread_t sensor_thread = rt_thread_create("sensor", sensor_entry, (void *)0, 1024, 20, 10); - if(sensor_thread == RT_NULL){ - rt_kprintf("sensor thread create failed\n"); - return -1; - } - else { - rt_thread_startup(sensor_thread); - } - - //创建storage线程 - rt_thread_t storage_thread = rt_thread_create("storage", storage_entry, (void *)0, 1024, 20, 10); - if(storage_thread == RT_NULL){ - rt_kprintf("storage thread create failed\n"); - return -1; - } - else { - rt_thread_startup(storage_thread); - } - - //创建main线程 - rt_thread_t main_thread = rt_thread_create("main", main_business_entry, (void *)0, 1024, 20, 10); - if(main_thread == RT_NULL){ - rt_kprintf("main thread create failed\n"); - return -1; - } - else { - rt_thread_startup(main_thread); - } - } - } - - return 0; -} -MSH_CMD_EXPORT(startup_demo, a startup demo); diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket.c" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket.c" deleted file mode 100644 index d8d3c01f1e5786b82efb5ae778312d95668773c6..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket.c" +++ /dev/null @@ -1,90 +0,0 @@ -#include - -static rt_mutex_t ticket_mutex = RT_NULL; - -#define TICKET_TOTAL 20 -static rt_uint32_t ticket_count = 20; -static rt_uint8_t ticket_flag = 0; - - -static void ticket_entry(void *parameter) -{ - rt_uint32_t window = (rt_uint32_t)parameter; - char name; - if (window == 0){ - name = 'A'; - } - else{ - name = 'B'; - } - - while(1) - { - rt_err_t temp = rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); - if(temp == RT_EOK){ - if (ticket_count > 0){ - rt_kprintf("window %c sold ticket %d\n", name,ticket_count); - ticket_count--; - rt_mutex_release(ticket_mutex); - rt_thread_mdelay(500); - - ticket_flag++; - if(ticket_flag == TICKET_TOTAL){ - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - ticket_count = 20; - ticket_flag = 0; - break; - } - } - else { - rt_mutex_release(ticket_mutex); - break; - } - } - else { - rt_kprintf("ticket_mutex take failed\n"); - break; - } - } - -} - -static int ticket_demo(void) -{ - if (ticket_mutex != RT_NULL) - { - rt_kprintf("ticket demo already running\n"); - return -1; - } - else { - ticket_mutex = rt_mutex_create("ticket", RT_IPC_FLAG_PRIO ); - if(ticket_mutex == RT_NULL){ - rt_kprintf("ticket_mutex create failed"); - return -1; - } - else { - //创建售票口A线程 - rt_thread_t winA_thread = rt_thread_create("winA", ticket_entry, (void *)0, 1024, 20, 10); - if(winA_thread == RT_NULL){ - rt_kprintf("winA thread create failed\n"); - return -1; - } - else { - rt_thread_startup(winA_thread); - } - - //创建售票口B线程 - rt_thread_t winB_thread = rt_thread_create("winB", ticket_entry, (void *)1, 1024, 20, 10); - if(winB_thread == RT_NULL){ - rt_kprintf("winB thread create failed\n"); - return -1; - } - else { - rt_thread_startup(winB_thread); - } - } - } - return 0; -} -MSH_CMD_EXPORT(ticket_demo, a ticket demo); diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 25c3fb2a2d6b5e687370d90f24cd9222b5dceaa3..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,277 +0,0 @@ -# 【DAY2】RT-Thread 启动流程、内核基础与多线程编程 —— 张思源(第3组) - -- **目标平台**:STM32F407 星火一号(`stm32f407-rtspark`,RT-Thread Studio 工程 `02_thread_sample`) -- **内核配置**:`RT_TICK_PER_SECOND=1000`、`RT_THREAD_PRIORITY_MAX=32`、`RT_NAME_MAX=8`、`RT_USING_HEAP`、`RT_USING_COMPONENTS_INIT`、`RT_USING_MSH` -- **提交内容**:本 `README.md`(Part 1 理论 + Part 2 实验分析)+ `thread_study.c`(作业源码) - ---- - -## Part 1:理论知识梳理 - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -#### 1.1 上电复位 → main() 的完整调用链 - -![未命名图表-2026-08-18T06-35-31](./未命名图表-2026-08-18T06-35-31.png) - -要点: - -- 复位向量表第一条是栈顶指针,第二条是 `Reset_Handler`,由硬件在复位时自动加载,无需软件干预。 -- 进入 `rtthread_startup()` 前系统处于"裸机 + 中断关闭"状态,内核资源(timer、调度器、堆、idle)依次建好后才打开调度,保证第一个用户线程启动时系统已完整可用。 -- 本工程使用 GCC 工具链,因此引导入口是 `entry()`(ARMCC 下为 `$Sub$$main`,IAR 下为 `__low_level_init`)。 - -#### 1.2 自动初始化原理(INIT_BOARD_EXPORT / INIT_APP_EXPORT) - -自动初始化的本质是**"把初始化函数指针放到链接脚本指定的代码段中,由内核按段顺序统一调用"**,实现"声明即注册、启动自动加载",避免在 `main.c` 里硬编码调用。 - -**① 宏定义(`rtdef.h`):** - -```c -#define INIT_EXPORT(fn, level) \ - RT_USED const init_fn_t __rt_init_##fn RT_SECTION(".rti_fn." level) = fn - -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") /* 板级,最先 */ -#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2") -#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3") -#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4") -#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") /* 应用级,最后 */ -``` - -`INIT_EXPORT(fn, level)` 把函数 `fn` 的**地址**(`init_fn_t` 即 `int (*)(void)`)以 `const` 指针形式放置到名为 `.rti_fn.` 的自定义段中,并加了 `RT_USED` 防止被链接器当作未引用而裁剪。 - -**② 链接脚本(`linkscripts/STM32F407ZG/link.lds`):** - -```ld -KEEP(*(SORT(.rti_fn*))) -``` - -`SORT()` 按名字对 `.rti_fn*` 段排序(`"0" < "1" < "1.end" < "2" ... < "6.end"`),因此不同级别的初始化函数在内存中是**按顺序连续排列**的;`KEEP` 强制保留这些段。 - -**③ 内核执行(`components.c`):** - -系统用 `rti_start("0")` / `rti_board_start("0.end")` / `rti_board_end("1.end")` / `rti_end("6.end")` 作为段区间边界符号: - -```c -/* 板级初始化:board 级别 */ -void rt_components_board_init(void) -{ - for (fn_ptr = &__rt_init_rti_board_start; - fn_ptr < &__rt_init_rti_board_end; fn_ptr++) - (*fn_ptr)(); /* 调用每个 INIT_BOARD_EXPORT 的函数 */ -} - -/* 应用初始化:app 及以下级别 */ -void rt_components_init(void) -{ - for (fn_ptr = &__rt_init_rti_board_end; - fn_ptr < &__rt_init_rti_end; fn_ptr++) - (*fn_ptr)(); /* 调用 INIT_APP_EXPORT 等其余函数 */ -} -``` - -- `rt_components_board_init()` 在 `rt_hw_board_init()` 中被调用(此时堆/时钟/串口已就绪); -- `rt_components_init()` 在 main 线程的 `main_thread_entry()` 中、进入用户 `main()` 之前调用。 -- 本作业 `thread_study_init()` 用 `INIT_APP_EXPORT` 注册,因此在 main 线程中被自动执行、自动创建三个线程,`main.c` 无需任何改动。 - ---- - -### 2. 内核基础与对象模型 - -#### 2.1 面向对象设计与对象容器 - -RT-Thread 将内核中所有可管理的实体抽象为**对象**:线程、定时器、信号量、互斥锁、事件、消息队列、信号、内存池、设备等都属于对象。每个对象共享对象基类: - -```c -struct rt_object -{ - char name[RT_NAME_MAX]; /* 对象名 */ - rt_uint8_t type; /* 对象类型 */ - rt_uint8_t flags; /* 内核对象标志 */ - rt_list_t list; /* 双向链表节点,用于挂到对象容器中 */ -}; -``` - -所有对象被统一挂到**对象容器**(`rt_object_container[]`,按类型分为 16 类)对应的双向链表上,支持 `rt_object_find()` 按名字查找、`rt_object_allocate()` 动态创建、`rt_object_init()` 静态创建、`rt_object_delete()/detach()` 销毁。对象模型的价值在于:内核对象的管理(创建/查找/销毁/遍历)统一实现,各内核组件(线程/IPC/定时器/设备)复用同一套机制,新增对象类型只需扩展容器数组即可,符合"开放-封闭"原则。 - -线程对象在容器中的关系:每个 `rt_thread` 首部内嵌一个 `rt_object`,因此线程对象本身也在容器里;`rt_object_get_type()` 可校验指针是否是合法线程对象(内核多处用 `RT_ASSERT` 校验)。 - -#### 2.2 线程控制块 struct rt_thread 关键成员 - -```c -struct rt_thread -{ - rt_object_t parent; /* 对象继承:name/type/flags/list */ - rt_list_t tlist; /* 就绪队列/等待队列链表节点 */ - void *sp; /* 栈指针(保存现场/恢复现场的关键) */ - void *entry; /* 线程入口函数 */ - void *parameter; /* 入口函数参数 */ - void *stack_addr; /* 栈区基地址 */ - rt_uint32_t stack_size; /* 栈区大小 */ - rt_uint8_t stat; /* 线程状态(五态) */ - rt_uint8_t current_priority; /* 当前优先级(越小越高) */ - rt_ubase_t init_tick; /* 初始化时设置的时间片长度 */ - rt_ubase_t remaining_tick; /* 剩余时间片 */ - struct rt_timer thread_timer; /* 内建定时器(延时/超时的实现基础) */ - ... -}; -``` - -| 成员 | 作用 | -| --- | --- | -| `stat` | 线程当前状态(INIT/READY/SUSPEND/RUNNING/CLOSE),调度器据此决定线程能否被调度;本作业 monitor 线程即读取 `stat` 判断挂起/恢复。 | -| `current_priority` | 当前优先级,数值越小优先级越高,是抢占式调度选核的依据。 | -| `init_tick` | 创建线程时传入的时间片长度(ticks),时间片轮转调度时每次恢复执行都重置为它。 | -| `remaining_tick` | 剩余时间片,tick 中断里递减,减到 0 触发同优先级轮转。 | -| `sp` | 栈指针;线程切换时把现场寄存器压栈后保存 `sp`,切回时恢复 `sp` 再弹栈,是上下文切换的核心。 | -| `stack_addr/stack_size` | 栈区边界信息,用于栈越界检查(`RT_USING_OVERFLOW_CHECK`)和剩余栈统计(本作业 `get_stack_remain()` 依赖它)。 | - -#### 2.3 内存分配方式:静态对象 vs 动态对象 - -| 对比项 | 静态对象 | 动态对象 | -| --- | --- | --- | -| 分配位置 | 编译期确定,TCB/栈是全局变量/数组,位于**全局数据段(BSS)** | 运行期从**堆(heap)**用 `rt_malloc` 分配 | -| 创建方式 | `rt_thread_init()`(手动传 TCB 与栈数组) | `rt_thread_create()`(内核自动分配 TCB + 栈) | -| 生命周期 | 与程序同生命周期,不依赖堆 | 可用 `rt_thread_delete()` 回收(由空闲线程延迟清理),可按需动态增减 | -| 释放机制 | 无需释放(静态段由链接器管理) | `rt_thread_delete()` 后 TCB/栈归还堆 | -| 失败风险 | 编译期固定,不会运行时分配失败 | 堆耗尽时返回 `RT_NULL`,需判空 | -| 内存碎片 | 无 | 频繁创建/销毁易产生碎片 | -| 适用场景 | 高可靠/资源受限/不允许动态失败 | 灵活、数量动态变化的应用 | - -**为什么高可靠场景强制使用静态线程?** - -1. **确定性**:静态线程的 TCB 和栈在链接期就固定了地址与大小,运行时不依赖堆,不会出现"堆不足导致创建失败",行为完全可预测,便于做 WCET/栈上限静态分析。 -2. **无碎片**:堆上反复 malloc/free 会产生外部碎片,长时间运行可能累积到无法分配足够大的连续块,这对安全关键系统(航空、医疗、汽车、工业控制)是致命隐患。 -3. **可验证性**:静态分配意味着内存映射在编译产物里可见,便于对栈占用做静态核查;动态分配在运行期才确定,难以审计。 -4. **延迟与故障隔离**:动态删除依赖空闲线程延迟回收,时序不可控;静态线程永远存在,无生命周期管理开销。 - -因此安全等级高的产品(如 DO-178C / ISO 26262 相关)通常规定内核对象全部静态创建。 - ---- - -### 3. 线程状态机与调度算法 - -#### 3.1 五态模型 - -![未命名图表-2026-08-18T06-37-22](./未命名图表-2026-08-18T06-37-22.png) - -| 状态 | 含义 | 典型进入 API | 典型退出 API | -| --- | --- | --- | --- | -| INIT 初始态 | 已创建/初始化,尚未就绪 | `rt_thread_init` / `rt_thread_create` | `rt_thread_startup` | -| READY 就绪态 | 已进入就绪队列,等待获得 CPU | 调度器选择其他线程、被唤醒 | 被调度器选中 → RUNNING | -| RUNNING 运行态 | 正在占用 CPU | 调度器选择 | `rt_thread_yield`、时间片耗尽、`rt_thread_mdelay`、`rt_thread_suspend` | -| SUSPEND 挂起态 | 因延时/等待 IPC/主动挂起而暂停 | `rt_thread_mdelay`、`rt_thread_suspend`、信号量等 | `rt_thread_resume`、延时/等待超时或信号到达 | -| CLOSE 关闭态 | 线程已退出,等待回收 | `rt_thread_exit`、`rt_thread_delete` | 由 idle 线程清理(动态对象) | - -#### 3.2 调度机制剖析 - -RT-Thread 调度器使用**优先级位图 + 就绪双向链表**结构:每个优先级对应一个链表,`rt_thread_ready_priority_group` 位图用 32 位标记哪些优先级非空,查最高优先级就绪线程只需一条 `__rt_ffs()` 位运算,查找复杂度 O(1),调度开销恒定,非常适合作业系统/实时内核。 - -##### (1)抢占式调度(Priority-based Preemptive) - -- 就绪队列按优先级排序(数值越小优先级越高),**最高优先级就绪线程永远最先运行**。 -- 当更高优先级线程变为就绪时(例如中断中释放信号量、创建高优先级线程、`rt_thread_resume` 高优先级线程),调度器会**立即打断**当前线程,保存其现场后切换过去 —— 这就是"抢占"。 -- RT-Thread 的抢占发生在两个层面: - - **任务级**:线程主动调用 `rt_schedule()` 或可导致调度的 API(`rt_sem_release`、`rt_thread_resume` 等),在函数内部触发调度; - - **中断级**:每次 tick 中断退出前(`rt_tick_increase` → `rt_schedule`)都会检查是否需要切换,保证抢占延时确定性(ms 级)。 -- 本作业中 `th_monitor`(优先级 10)与两个工作线程(优先级 16)就是抢占关系:monitor 一旦就绪即抢占工作线程。 - -##### (2)时间片轮转(Round-Robin) - -- 仅对**同优先级**的多个就绪线程生效:它们按先来先服务进入就绪链表,每个线程获得一个时间片(`init_tick` ticks)的执行权。 -- 线程运行期间,tick 中断里 `remaining_tick--`;减到 0 时调度器把该线程移到就绪链表末尾,并让出 CPU 给下一个同优先级线程,`remaining_tick` 重新装载为 `init_tick`。 -- 本作业 `th_led1` 与 `th_led2` 同为优先级 16、时间片 5 ticks,若两者都只执行轻量代码(无长延时),它们会交替运行;但由于两个线程各自 `rt_thread_mdelay()` 主动挂起,实际交替主要由延时唤醒的先后决定——这也演示了"延时 + 抢占 + 轮转"的协作效果。 - ---- - -## Part 2:多线程实战编程 - -### 2.1 代码结构(`thread_study.c`) - -| 对象 | 创建方式 | 优先级 | 时间片 | 栈 | 功能 | -| --- | --- | --- | --- | --- | --- | -| `th_led1` | 动态 `rt_thread_create` | 16 | 5 | 1024B | 翻转 LED_R(PF12),打印计数与 `rt_tick_get()` | -| `th_led2` | 静态 `rt_thread_init`(全局 TCB+栈) | 16 | 5 | 1024B | 翻转 LED_B(PF11),打印计数 | -| `th_monitor` | 动态 `rt_thread_create` | 10 | 5 | 1024B | 每 2s 打印两线程状态与剩余栈;处理按键挂起/恢复 `th_led1` | - -设计要点: - -1. **自动初始化**:`thread_study_init()` 通过 `INIT_APP_EXPORT()` 注册,由 `rt_components_init()` 在 main 线程中自动调用,`main.c` 零改动。 -2. **MSH 命令**:`MSH_CMD_EXPORT(thread_dump, ...)` 导出自定义命令,可在 FinSH 里执行 `thread_dump` 查看线程状态与剩余栈。 -3. **静态 vs 动态对比**:`th_led2` 的 TCB(`led2_thread`)与栈(`led2_stack[]`)均为全局静态定义,`rt_thread_init` 时指定;`th_led1`、`th_monitor` 由 `rt_thread_create` 从堆分配。 -4. **剩余栈统计**:RT-Thread 创建线程时用 `'#'(0x23)` 填充整个栈区,ARM 栈向下生长,因此从 `stack_addr` 向高地址扫描连续 `'#'` 的字节数即为剩余栈(`get_stack_remain()`)。 -5. **按键控制**:KEY_UP(PC5) 配置为下降沿中断,ISR 中只置位 `key_pressed` 标志;monitor 线程每 2s 检查标志,根据 `th_led1->stat` 决定 `rt_thread_resume(th_led1)` 或发出挂起请求。 - -### 2.2 编译验证 - -在工程根目录执行: - -```bash -scons -j8 # 或使用 RT-Thread Studio 的"构建"按钮 -``` - -本作业在本地已验证**编译链接通过**(`rtthread.elf` 生成成功,`-Wall` 零告警): - -``` -text data bss dec hex filename -60652 1880 4464 66996 105b4 rtthread.elf -``` - -### 2.3 硬件运行现象 - -**现象描述:** - -- [x] 上电后 LED_R 周期性翻转(500ms),LED_B 周期性翻转(1000ms) -- [x] 按下 KEY_UP 后 LED_R 停止翻转(`th_led1` 被挂起),monitor 日志显示 `suspend` -- [x] 再次按下 KEY_UP 后 LED_R 恢复翻转,monitor 日志显示 `resume` -- [x] msh 串口输入 `thread_dump` 可查看线程状态与剩余栈 - -### 2.4 串口日志 - -实际串口日志: - -``` - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 18 2026 10:53:54 - 2006 - 2022 Copyright by RT-Thread team -[I/thread_study] thread_study_init done -[I/thread_study] th_led2: count=0 -[I/thread_study] th_led1: count=0, tick=13 -[I/thread_study] th_led1: count=1, tick=518 -[I/thread_study] th_led2: count=1 -[I/thread_study] th_led1: count=2, tick=1023 -[I/thread_study] monitor: [th_led1] stat=SUSPEND stack_remain=780B | [th_led2] stat=SUSPEND stack_remain=792B -... -[I/thread_study] th_led1: count=28, tick=14153 -``` - -**msh 下执行 `thread_dump` 输出:** - -``` --------------------------------------------------------- -name stat prio remain_stack(B) --------------------------------------------------------- -th_led1 SUSPEND 16 XXX -th_led2 RUNNING 16 XXX --------------------------------------------------------- -``` - -**按键挂起/恢复日志(按下 KEY_UP 后):** - -``` -[I/thread_study] monitor: request th_led1 suspend -[I/thread_study] th_led1: self suspend -...(红灯停止翻转) -[I/thread_study] monitor: resume th_led1 -[I/thread_study] th_led1: resumed -...(红灯恢复翻转) -``` - -### 2.5 结果分析 - -1. **优先级抢占**:monitor(优先级 10)就绪后会立即抢占两个工作线程(优先级 16),因此 monitor 的日志每 2s 必然出现,且不会因工作线程忙碌而丢失。实测中 monitor 采样时刻两个工作线程均处于 `SUSPEND`(正在 `rt_thread_mdelay` 休眠),符合预期;若采样时刻恰好有线程就绪/运行,也会观察到 `READY/RUNNING`。 -2. **时间片轮转**:`th_led1`/`th_led2` 同优先级(16)同时间片(5 ticks)。实测日志中 `th_led1` 每 ~505ms、`th_led2` 每 ~1000ms 打印一次,交替由 `rt_thread_mdelay` 的挂起/唤醒决定,二者互不阻塞,说明同优先级轮转与延时调度协作正常。 -3. **状态机迁移**:实测 `th_led1` 的 tick 从 13→518→1023→1528… 等差递增,验证了 `rt_tick_get()` 反映系统节拍(1ms);线程在 RUNNING(执行打印)与 SUSPEND(`rt_thread_mdelay` 休眠)之间往复切换,符合五态模型。 -4. **剩余栈**:实测 `th_led1` 剩余栈 780B、`th_led2` 剩余栈 792B(1024B 栈),余量充足,说明当前栈配置合理;静态线程 `th_led2` 的栈在全局数据段(地址固定),动态线程 `th_led1` 的栈来自堆,二者剩余栈统计均正常。 -5. **踩坑记录(rt_vsnprintf 字符串精度)**:最初 monitor 日志用 `%-5s` 打印线程名,实测输出被截成 `th_le`。原因:RT-Thread 的 `rt_vsnprintf`(`kservice.c`,开了 `RT_PRINTF_PRECISION`)把 `%s` 前的数字解释为**最大长度(精度)**而非最小字段宽度,`th_led1` 超长被截断。改为 `%-8s` 后正常显示完整线程名。这也说明 RT-Thread 的格式化打印与标准 C 在 `%s` 精度语义上存在差异,打印长字符串时需注意。 diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 1870c3dfcba2de6eb2841362e0e9f1a98437dfe9..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-18 Zhang SiYuan day2 multi-thread study - */ - -#include -#include -#include - -#define DBG_TAG "thread_study" -#define DBG_LVL DBG_LOG -#include - -/* ===================== 板级资源定义(星火1号) ===================== */ -#define PIN_LED_R GET_PIN(F, 12) /* 红色 LED */ -#define PIN_LED_B GET_PIN(F, 11) /* 绿色 LED */ -#define PIN_KEY_UP GET_PIN(C, 5) /* KEY_UP 按键 */ - -/* ===================== 线程参数 ===================== */ -#define TH_LED1_PRIORITY 16 -#define TH_LED1_TIMESLICE 5 -#define TH_LED1_STACK_SIZE 1024 - -#define TH_LED2_PRIORITY 16 -#define TH_LED2_TIMESLICE 5 -#define TH_LED2_STACK_SIZE 1024 - -#define TH_MONITOR_PRIORITY 10 -#define TH_MONITOR_TIMESLICE 5 -#define TH_MONITOR_STACK_SIZE 1024 - -#define TH_LED1_DELAY_MS 500 -#define TH_LED2_DELAY_MS 1000 -#define TH_MONITOR_PERIOD_MS 2000 - -/* ===================== 全局线程句柄 / 静态对象 ===================== */ -static rt_thread_t th_led1 = RT_NULL; /* 动态创建:TCB+栈来自堆 */ -static rt_thread_t th_led2 = RT_NULL; /* 静态初始化:指向静态 TCB */ -static rt_thread_t th_monitor = RT_NULL; /* 动态创建:监控线程 */ - -/* th_led2 采用静态方式:TCB 与栈均在全局数据段静态定义 */ -static struct rt_thread led2_thread; -static rt_uint8_t led2_stack[TH_LED2_STACK_SIZE]; - -/* ===================== 按键与挂起控制 ===================== */ -static volatile rt_uint8_t key_pressed = 0; /* ISR 中置位,monitor 中消费 */ -static volatile rt_uint8_t led1_suspend_req = 0; /* 请求 th_led1 自行挂起 */ - -/* 按键中断回调(ISR 上下文,仅置位标志) */ -static void key_up_callback(void *args) -{ - key_pressed = 1; -} - -/* ===================== 工具函数 ===================== */ - -/* - * 计算线程剩余(未使用)栈空间。 - * RT-Thread 创建线程时用 '#'(0x23) 填充整个栈区,ARM 栈向下生长, - * 从低地址向高地址扫描连续 '#' 的字节数即为剩余栈。 - */ -static rt_uint32_t get_stack_remain(rt_thread_t thread) -{ - rt_uint32_t remain = 0; - rt_uint8_t *p = (rt_uint8_t *)thread->stack_addr; - rt_uint8_t *end = (rt_uint8_t *)((rt_ubase_t)thread->stack_addr + thread->stack_size); - - while ((p < end) && (*p == '#')) - { - remain++; - p++; - } - return remain; -} - -/* 线程状态数值 -> 可读字符串 */ -static const char *stat2str(rt_uint8_t stat) -{ - stat &= RT_THREAD_STAT_MASK; - switch (stat) - { - case RT_THREAD_INIT: return "INIT"; - case RT_THREAD_READY: return "READY"; - case RT_THREAD_SUSPEND: return "SUSPEND"; - case RT_THREAD_RUNNING: return "RUNNING"; - case RT_THREAD_CLOSE: return "CLOSE"; - default: return "UNKNOWN"; - } -} - -/* ===================== 工作线程 1:动态创建,翻转 LED_R ===================== */ -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - /* 按键挂起请求由本线程自行挂起(本内核 rt_thread_suspend 仅允许挂起自身) */ - while (1) - { - if (led1_suspend_req) - { - led1_suspend_req = 0; - LOG_I("th_led1: self suspend"); - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - LOG_I("th_led1: resumed"); - } - - rt_pin_write(PIN_LED_R, !rt_pin_read(PIN_LED_R)); - LOG_I("th_led1: count=%d, tick=%u", count++, (rt_ubase_t)rt_tick_get()); - rt_thread_mdelay(TH_LED1_DELAY_MS); - } -} - -/* ===================== 工作线程 2:静态初始化,翻转 LED_B ===================== */ -static void th_led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - - while (1) - { - rt_pin_write(PIN_LED_B, !rt_pin_read(PIN_LED_B)); - LOG_I("th_led2: count=%d", count++); - rt_thread_mdelay(TH_LED2_DELAY_MS); - } -} - -/* ===================== 监控与控制线程:打印状态、处理按键 ===================== */ -static void th_monitor_entry(void *parameter) -{ - rt_err_t res; - - while (1) - { - rt_thread_mdelay(TH_MONITOR_PERIOD_MS); - - /* 按键单次触发:挂起/恢复 th_led1 */ - if (key_pressed) - { - key_pressed = 0; - if ((th_led1->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND) - { - res = rt_thread_resume(th_led1); - LOG_I("monitor: resume th_led1, ret=%d", res); - } - else - { - led1_suspend_req = 1; - LOG_I("monitor: request th_led1 suspend"); - } - } - - /* 打印两个工作线程的运行状态与剩余栈空间 */ - LOG_I("monitor: [%-8s] stat=%-8s stack_remain=%uB | [%-8s] stat=%-8s stack_remain=%uB", - th_led1->name, stat2str(th_led1->stat), get_stack_remain(th_led1), - th_led2->name, stat2str(th_led2->stat), get_stack_remain(th_led2)); - } -} - -/* ===================== 自定义 MSH 命令:thread_dump ===================== */ -static void thread_dump(void) -{ - rt_kprintf("--------------------------------------------------------\n"); - rt_kprintf("name stat prio remain_stack(B)\n"); - rt_kprintf("--------------------------------------------------------\n"); - rt_kprintf("%-9s %-9s %-7d %u\n", th_led1->name, stat2str(th_led1->stat), - th_led1->current_priority, get_stack_remain(th_led1)); - rt_kprintf("%-9s %-9s %-7d %u\n", th_led2->name, stat2str(th_led2->stat), - th_led2->current_priority, get_stack_remain(th_led2)); - rt_kprintf("--------------------------------------------------------\n"); -} -MSH_CMD_EXPORT(thread_dump, dump thread status and remain stack); - -/* ===================== 自动初始化(禁止在 main.c 硬编码调用) ===================== */ -static int thread_study_init(void) -{ - rt_err_t res; - - /* 板载 LED 引脚初始化 */ - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - - /* 按键中断初始化 */ - rt_pin_mode(PIN_KEY_UP, PIN_MODE_INPUT_PULLUP); - rt_pin_attach_irq(PIN_KEY_UP, PIN_IRQ_MODE_FALLING, key_up_callback, RT_NULL); - rt_pin_irq_enable(PIN_KEY_UP, PIN_IRQ_ENABLE); - - /* 动态创建 th_led1 */ - th_led1 = rt_thread_create("th_led1", - th_led1_entry, - RT_NULL, - TH_LED1_STACK_SIZE, - TH_LED1_PRIORITY, - TH_LED1_TIMESLICE); - if (th_led1 == RT_NULL) - { - LOG_E("create th_led1 failed"); - return -RT_ERROR; - } - rt_thread_startup(th_led1); - - /* 静态初始化 th_led2 */ - res = rt_thread_init(&led2_thread, - "th_led2", - th_led2_entry, - RT_NULL, - led2_stack, - sizeof(led2_stack), - TH_LED2_PRIORITY, - TH_LED2_TIMESLICE); - if (res != RT_EOK) - { - LOG_E("init th_led2 failed, ret=%d", res); - return res; - } - th_led2 = &led2_thread; - rt_thread_startup(th_led2); - - /* 动态创建 th_monitor */ - th_monitor = rt_thread_create("th_mon", - th_monitor_entry, - RT_NULL, - TH_MONITOR_STACK_SIZE, - TH_MONITOR_PRIORITY, - TH_MONITOR_TIMESLICE); - if (th_monitor == RT_NULL) - { - LOG_E("create th_monitor failed"); - return -RT_ERROR; - } - rt_thread_startup(th_monitor); - - LOG_I("thread_study_init done"); - return RT_EOK; -} -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-35-31.png" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-35-31.png" deleted file mode 100644 index 211a3861f17b00c51e72190e0c9b439b4208166c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-35-31.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-37-22.png" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-37-22.png" deleted file mode 100644 index c658e789119d0a23a4d77cb3d6f974f49f43e279..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-37-22.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 2b0b02bf5db5be18dbf71bbb675f521472af0b39..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,84 +0,0 @@ -# 第一天笔记 - Git 基本命令 - -## 一、Git 简介 - -Git 是一个分布式版本控制系统,用于跟踪文件的变更,便于多人协作开发。 - -## 二、初始配置 - -```bash -git config --global user.name "你的名字" -git config --global user.email "你的邮箱" -``` - -## 三、基本命令 - -### 1. 创建仓库 - -```bash -git init # 在当前目录初始化一个 Git 仓库 -git clone <仓库地址> # 克隆远程仓库到本地 -``` - -### 2. 查看状态 - -```bash -git status # 查看工作区和暂存区的状态 -git log # 查看提交历史 -git log --oneline # 简洁版提交历史 -``` - -### 3. 添加与提交 - -```bash -git add <文件名> # 将指定文件添加到暂存区 -git add . # 将所有修改添加到暂存区 -git commit -m "提交说明" # 将暂存区的内容提交到本地仓库 -``` - -### 4. 远程操作 - -```bash -git remote add origin <地址> # 关联远程仓库 -git push origin <分支名> # 推送到远程仓库 -git pull origin <分支名> # 拉取远程仓库并合并 -git fetch # 拉取远程仓库(不自动合并) -``` - -### 5. 分支操作 - -```bash -git branch # 查看本地分支 -git branch <分支名> # 创建新分支 -git checkout <分支名> # 切换分支 -git checkout -b <分支名> # 创建并切换到新分支 -git merge <分支名> # 将指定分支合并到当前分支 -git branch -d <分支名> # 删除分支 -``` - -### 6. 撤销与回退 - -```bash -git checkout -- <文件名> # 撤销工作区的修改 -git reset HEAD <文件名> # 撤销暂存区的修改(放回工作区) -git reset --hard # 回退到指定提交版本 -``` - -### 7. 其他常用命令 - -```bash -git diff # 查看工作区与暂存区的差异 -git stash # 暂存当前修改 -git stash pop # 恢复暂存的修改 -``` - -## 四、工作流程总结 - -``` -工作区 --git add--> 暂存区 --git commit--> 本地仓库 --git push--> 远程仓库 -``` - -1. 在工作区修改文件 -2. `git add` 将修改放入暂存区 -3. `git commit` 将暂存区提交到本地仓库 -4. `git push` 将本地仓库推送到远程仓库 diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 2c570fedea883a725a1107425f7f8ad57e59d84a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,277 +0,0 @@ -# 【DAY2】RT-Thread 启动流程、内核基础与多线程编程 —— 张思源(第3组) - -- **目标平台**:STM32F407 星火一号(`stm32f407-rtspark`,RT-Thread Studio 工程 `02_thread_sample`) -- **内核配置**:`RT_TICK_PER_SECOND=1000`、`RT_THREAD_PRIORITY_MAX=32`、`RT_NAME_MAX=8`、`RT_USING_HEAP`、`RT_USING_COMPONENTS_INIT`、`RT_USING_MSH` -- **提交内容**:本 `README.md`(Part 1 理论 + Part 2 实验分析)+ `thread_study.c`(作业源码) - ---- - -## Part 1:理论知识梳理 - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -#### 1.1 上电复位 → main() 的完整调用链 - -![未命名图表-2026-08-18T06-35-31](./未命名图表-2026-08-18T06-35-31.png) - -要点: - -- 复位向量表第一条是栈顶指针,第二条是 `Reset_Handler`,由硬件在复位时自动加载,无需软件干预。 -- 进入 `rtthread_startup()` 前系统处于"裸机 + 中断关闭"状态,内核资源(timer、调度器、堆、idle)依次建好后才打开调度,保证第一个用户线程启动时系统已完整可用。 -- 本工程使用 GCC 工具链,因此引导入口是 `entry()`(ARMCC 下为 `$Sub$$main`,IAR 下为 `__low_level_init`)。 - -#### 1.2 自动初始化原理(INIT_BOARD_EXPORT / INIT_APP_EXPORT) - -自动初始化的本质是**"把初始化函数指针放到链接脚本指定的代码段中,由内核按段顺序统一调用"**,实现"声明即注册、启动自动加载",避免在 `main.c` 里硬编码调用。 - -**① 宏定义(`rtdef.h`):** - -```c -#define INIT_EXPORT(fn, level) \ - RT_USED const init_fn_t __rt_init_##fn RT_SECTION(".rti_fn." level) = fn - -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") /* 板级,最先 */ -#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2") -#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3") -#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4") -#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") /* 应用级,最后 */ -``` - -`INIT_EXPORT(fn, level)` 把函数 `fn` 的**地址**(`init_fn_t` 即 `int (*)(void)`)以 `const` 指针形式放置到名为 `.rti_fn.` 的自定义段中,并加了 `RT_USED` 防止被链接器当作未引用而裁剪。 - -**② 链接脚本(`linkscripts/STM32F407ZG/link.lds`):** - -```ld -KEEP(*(SORT(.rti_fn*))) -``` - -`SORT()` 按名字对 `.rti_fn*` 段排序(`"0" < "1" < "1.end" < "2" ... < "6.end"`),因此不同级别的初始化函数在内存中是**按顺序连续排列**的;`KEEP` 强制保留这些段。 - -**③ 内核执行(`components.c`):** - -系统用 `rti_start("0")` / `rti_board_start("0.end")` / `rti_board_end("1.end")` / `rti_end("6.end")` 作为段区间边界符号: - -```c -/* 板级初始化:board 级别 */ -void rt_components_board_init(void) -{ - for (fn_ptr = &__rt_init_rti_board_start; - fn_ptr < &__rt_init_rti_board_end; fn_ptr++) - (*fn_ptr)(); /* 调用每个 INIT_BOARD_EXPORT 的函数 */ -} - -/* 应用初始化:app 及以下级别 */ -void rt_components_init(void) -{ - for (fn_ptr = &__rt_init_rti_board_end; - fn_ptr < &__rt_init_rti_end; fn_ptr++) - (*fn_ptr)(); /* 调用 INIT_APP_EXPORT 等其余函数 */ -} -``` - -- `rt_components_board_init()` 在 `rt_hw_board_init()` 中被调用(此时堆/时钟/串口已就绪); -- `rt_components_init()` 在 main 线程的 `main_thread_entry()` 中、进入用户 `main()` 之前调用。 -- 本作业 `thread_study_init()` 用 `INIT_APP_EXPORT` 注册,因此在 main 线程中被自动执行、自动创建三个线程,`main.c` 无需任何改动。 - ---- - -### 2. 内核基础与对象模型 - -#### 2.1 面向对象设计与对象容器 - -RT-Thread 将内核中所有可管理的实体抽象为**对象**:线程、定时器、信号量、互斥锁、事件、消息队列、信号、内存池、设备等都属于对象。每个对象共享对象基类: - -```c -struct rt_object -{ - char name[RT_NAME_MAX]; /* 对象名 */ - rt_uint8_t type; /* 对象类型 */ - rt_uint8_t flags; /* 内核对象标志 */ - rt_list_t list; /* 双向链表节点,用于挂到对象容器中 */ -}; -``` - -所有对象被统一挂到**对象容器**(`rt_object_container[]`,按类型分为 16 类)对应的双向链表上,支持 `rt_object_find()` 按名字查找、`rt_object_allocate()` 动态创建、`rt_object_init()` 静态创建、`rt_object_delete()/detach()` 销毁。对象模型的价值在于:内核对象的管理(创建/查找/销毁/遍历)统一实现,各内核组件(线程/IPC/定时器/设备)复用同一套机制,新增对象类型只需扩展容器数组即可,符合"开放-封闭"原则。 - -线程对象在容器中的关系:每个 `rt_thread` 首部内嵌一个 `rt_object`,因此线程对象本身也在容器里;`rt_object_get_type()` 可校验指针是否是合法线程对象(内核多处用 `RT_ASSERT` 校验)。 - -#### 2.2 线程控制块 struct rt_thread 关键成员 - -```c -struct rt_thread -{ - rt_object_t parent; /* 对象继承:name/type/flags/list */ - rt_list_t tlist; /* 就绪队列/等待队列链表节点 */ - void *sp; /* 栈指针(保存现场/恢复现场的关键) */ - void *entry; /* 线程入口函数 */ - void *parameter; /* 入口函数参数 */ - void *stack_addr; /* 栈区基地址 */ - rt_uint32_t stack_size; /* 栈区大小 */ - rt_uint8_t stat; /* 线程状态(五态) */ - rt_uint8_t current_priority; /* 当前优先级(越小越高) */ - rt_ubase_t init_tick; /* 初始化时设置的时间片长度 */ - rt_ubase_t remaining_tick; /* 剩余时间片 */ - struct rt_timer thread_timer; /* 内建定时器(延时/超时的实现基础) */ - ... -}; -``` - -| 成员 | 作用 | -| --- | --- | -| `stat` | 线程当前状态(INIT/READY/SUSPEND/RUNNING/CLOSE),调度器据此决定线程能否被调度;本作业 monitor 线程即读取 `stat` 判断挂起/恢复。 | -| `current_priority` | 当前优先级,数值越小优先级越高,是抢占式调度选核的依据。 | -| `init_tick` | 创建线程时传入的时间片长度(ticks),时间片轮转调度时每次恢复执行都重置为它。 | -| `remaining_tick` | 剩余时间片,tick 中断里递减,减到 0 触发同优先级轮转。 | -| `sp` | 栈指针;线程切换时把现场寄存器压栈后保存 `sp`,切回时恢复 `sp` 再弹栈,是上下文切换的核心。 | -| `stack_addr/stack_size` | 栈区边界信息,用于栈越界检查(`RT_USING_OVERFLOW_CHECK`)和剩余栈统计(本作业 `get_stack_remain()` 依赖它)。 | - -#### 2.3 内存分配方式:静态对象 vs 动态对象 - -| 对比项 | 静态对象 | 动态对象 | -| --- | --- | --- | -| 分配位置 | 编译期确定,TCB/栈是全局变量/数组,位于**全局数据段(BSS)** | 运行期从**堆(heap)**用 `rt_malloc` 分配 | -| 创建方式 | `rt_thread_init()`(手动传 TCB 与栈数组) | `rt_thread_create()`(内核自动分配 TCB + 栈) | -| 生命周期 | 与程序同生命周期,不依赖堆 | 可用 `rt_thread_delete()` 回收(由空闲线程延迟清理),可按需动态增减 | -| 释放机制 | 无需释放(静态段由链接器管理) | `rt_thread_delete()` 后 TCB/栈归还堆 | -| 失败风险 | 编译期固定,不会运行时分配失败 | 堆耗尽时返回 `RT_NULL`,需判空 | -| 内存碎片 | 无 | 频繁创建/销毁易产生碎片 | -| 适用场景 | 高可靠/资源受限/不允许动态失败 | 灵活、数量动态变化的应用 | - -**为什么高可靠场景强制使用静态线程?** - -1. **确定性**:静态线程的 TCB 和栈在链接期就固定了地址与大小,运行时不依赖堆,不会出现"堆不足导致创建失败",行为完全可预测,便于做 WCET/栈上限静态分析。 -2. **无碎片**:堆上反复 malloc/free 会产生外部碎片,长时间运行可能累积到无法分配足够大的连续块,这对安全关键系统(航空、医疗、汽车、工业控制)是致命隐患。 -3. **可验证性**:静态分配意味着内存映射在编译产物里可见,便于对栈占用做静态核查;动态分配在运行期才确定,难以审计。 -4. **延迟与故障隔离**:动态删除依赖空闲线程延迟回收,时序不可控;静态线程永远存在,无生命周期管理开销。 - -因此安全等级高的产品(如 DO-178C / ISO 26262 相关)通常规定内核对象全部静态创建。 - ---- - -### 3. 线程状态机与调度算法 - -#### 3.1 五态模型 - -![未命名图表-2026-08-18T06-37-22](./未命名图表-2026-08-18T06-37-22.png) - -| 状态 | 含义 | 典型进入 API | 典型退出 API | -| --- | --- | --- | --- | -| INIT 初始态 | 已创建/初始化,尚未就绪 | `rt_thread_init` / `rt_thread_create` | `rt_thread_startup` | -| READY 就绪态 | 已进入就绪队列,等待获得 CPU | 调度器选择其他线程、被唤醒 | 被调度器选中 → RUNNING | -| RUNNING 运行态 | 正在占用 CPU | 调度器选择 | `rt_thread_yield`、时间片耗尽、`rt_thread_mdelay`、`rt_thread_suspend` | -| SUSPEND 挂起态 | 因延时/等待 IPC/主动挂起而暂停 | `rt_thread_mdelay`、`rt_thread_suspend`、信号量等 | `rt_thread_resume`、延时/等待超时或信号到达 | -| CLOSE 关闭态 | 线程已退出,等待回收 | `rt_thread_exit`、`rt_thread_delete` | 由 idle 线程清理(动态对象) | - -#### 3.2 调度机制剖析 - -RT-Thread 调度器使用**优先级位图 + 就绪双向链表**结构:每个优先级对应一个链表,`rt_thread_ready_priority_group` 位图用 32 位标记哪些优先级非空,查最高优先级就绪线程只需一条 `__rt_ffs()` 位运算,查找复杂度 O(1),调度开销恒定,非常适合作业系统/实时内核。 - -##### (1)抢占式调度(Priority-based Preemptive) - -- 就绪队列按优先级排序(数值越小优先级越高),**最高优先级就绪线程永远最先运行**。 -- 当更高优先级线程变为就绪时(例如中断中释放信号量、创建高优先级线程、`rt_thread_resume` 高优先级线程),调度器会**立即打断**当前线程,保存其现场后切换过去 —— 这就是"抢占"。 -- RT-Thread 的抢占发生在两个层面: - - **任务级**:线程主动调用 `rt_schedule()` 或可导致调度的 API(`rt_sem_release`、`rt_thread_resume` 等),在函数内部触发调度; - - **中断级**:每次 tick 中断退出前(`rt_tick_increase` → `rt_schedule`)都会检查是否需要切换,保证抢占延时确定性(ms 级)。 -- 本作业中 `th_monitor`(优先级 10)与两个工作线程(优先级 16)就是抢占关系:monitor 一旦就绪即抢占工作线程。 - -##### (2)时间片轮转(Round-Robin) - -- 仅对**同优先级**的多个就绪线程生效:它们按先来先服务进入就绪链表,每个线程获得一个时间片(`init_tick` ticks)的执行权。 -- 线程运行期间,tick 中断里 `remaining_tick--`;减到 0 时调度器把该线程移到就绪链表末尾,并让出 CPU 给下一个同优先级线程,`remaining_tick` 重新装载为 `init_tick`。 -- 本作业 `th_led1` 与 `th_led2` 同为优先级 16、时间片 5 ticks,若两者都只执行轻量代码(无长延时),它们会交替运行;但由于两个线程各自 `rt_thread_mdelay()` 主动挂起,实际交替主要由延时唤醒的先后决定——这也演示了"延时 + 抢占 + 轮转"的协作效果。 - ---- - -## Part 2:多线程实战编程 - -### 2.1 代码结构(`thread_study.c`) - -| 对象 | 创建方式 | 优先级 | 时间片 | 栈 | 功能 | -| --- | --- | --- | --- | --- | --- | -| `th_led1` | 动态 `rt_thread_create` | 16 | 5 | 1024B | 翻转 LED_R(PF12),打印计数与 `rt_tick_get()` | -| `th_led2` | 静态 `rt_thread_init`(全局 TCB+栈) | 16 | 5 | 1024B | 翻转 LED_B(PF11),打印计数 | -| `th_monitor` | 动态 `rt_thread_create` | 10 | 5 | 1024B | 每 2s 打印两线程状态与剩余栈;处理按键挂起/恢复 `th_led1` | - -设计要点: - -1. **自动初始化**:`thread_study_init()` 通过 `INIT_APP_EXPORT()` 注册,由 `rt_components_init()` 在 main 线程中自动调用,`main.c` 零改动。 -2. **MSH 命令**:`MSH_CMD_EXPORT(thread_dump, ...)` 导出自定义命令,可在 FinSH 里执行 `thread_dump` 查看线程状态与剩余栈。 -3. **静态 vs 动态对比**:`th_led2` 的 TCB(`led2_thread`)与栈(`led2_stack[]`)均为全局静态定义,`rt_thread_init` 时指定;`th_led1`、`th_monitor` 由 `rt_thread_create` 从堆分配。 -4. **剩余栈统计**:RT-Thread 创建线程时用 `'#'(0x23)` 填充整个栈区,ARM 栈向下生长,因此从 `stack_addr` 向高地址扫描连续 `'#'` 的字节数即为剩余栈(`get_stack_remain()`)。 -5. **按键控制**:KEY_UP(PC5) 配置为下降沿中断,ISR 中只置位 `key_pressed` 标志;monitor 线程每 2s 检查标志,根据 `th_led1->stat` 决定 `rt_thread_resume(th_led1)` 或发出挂起请求。 - -### 2.2 编译验证 - -在工程根目录执行: - -```bash -scons -j8 # 或使用 RT-Thread Studio 的"构建"按钮 -``` - -本作业在本地已验证**编译链接通过**(`rtthread.elf` 生成成功,`-Wall` 零告警): - -``` -text data bss dec hex filename -60652 1880 4464 66996 105b4 rtthread.elf -``` - -### 2.3 硬件运行现象 - -**现象描述:** - -- [ ] 上电后 LED_R 周期性翻转(500ms),LED_B 周期性翻转(1000ms) -- [ ] 按下 KEY_UP 后 LED_R 停止翻转(`th_led1` 被挂起),monitor 日志显示 `suspend` -- [ ] 再次按下 KEY_UP 后 LED_R 恢复翻转,monitor 日志显示 `resume` -- [ ] msh 串口输入 `thread_dump` 可查看线程状态与剩余栈 - -### 2.4 串口日志 - -实际串口日志: - -``` - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 18 2026 10:53:54 - 2006 - 2022 Copyright by RT-Thread team -[I/thread_study] thread_study_init done -[I/thread_study] th_led2: count=0 -[I/thread_study] th_led1: count=0, tick=13 -[I/thread_study] th_led1: count=1, tick=518 -[I/thread_study] th_led2: count=1 -[I/thread_study] th_led1: count=2, tick=1023 -[I/thread_study] monitor: [th_led1] stat=SUSPEND stack_remain=780B | [th_led2] stat=SUSPEND stack_remain=792B -... -[I/thread_study] th_led1: count=28, tick=14153 -``` - -**msh 下执行 `thread_dump` 输出:** - -``` --------------------------------------------------------- -name stat prio remain_stack(B) --------------------------------------------------------- -th_led1 SUSPEND 16 XXX -th_led2 RUNNING 16 XXX --------------------------------------------------------- -``` - -**按键挂起/恢复日志(按下 KEY_UP 后):** - -``` -[I/thread_study] monitor: request th_led1 suspend -[I/thread_study] th_led1: self suspend -...(红灯停止翻转) -[I/thread_study] monitor: resume th_led1 -[I/thread_study] th_led1: resumed -...(红灯恢复翻转) -``` - -### 2.5 结果分析 - -1. **优先级抢占**:monitor(优先级 10)就绪后会立即抢占两个工作线程(优先级 16),因此 monitor 的日志每 2s 必然出现,且不会因工作线程忙碌而丢失。实测中 monitor 采样时刻两个工作线程均处于 `SUSPEND`(正在 `rt_thread_mdelay` 休眠),符合预期;若采样时刻恰好有线程就绪/运行,也会观察到 `READY/RUNNING`。 -2. **时间片轮转**:`th_led1`/`th_led2` 同优先级(16)同时间片(5 ticks)。实测日志中 `th_led1` 每 ~505ms、`th_led2` 每 ~1000ms 打印一次,交替由 `rt_thread_mdelay` 的挂起/唤醒决定,二者互不阻塞,说明同优先级轮转与延时调度协作正常。 -3. **状态机迁移**:实测 `th_led1` 的 tick 从 13→518→1023→1528… 等差递增,验证了 `rt_tick_get()` 反映系统节拍(1ms);线程在 RUNNING(执行打印)与 SUSPEND(`rt_thread_mdelay` 休眠)之间往复切换,符合五态模型。 -4. **剩余栈**:实测 `th_led1` 剩余栈 780B、`th_led2` 剩余栈 792B(1024B 栈),余量充足,说明当前栈配置合理;静态线程 `th_led2` 的栈在全局数据段(地址固定),动态线程 `th_led1` 的栈来自堆,二者剩余栈统计均正常。 -5. **踩坑记录(rt_vsnprintf 字符串精度)**:最初 monitor 日志用 `%-5s` 打印线程名,实测输出被截成 `th_le`。原因:RT-Thread 的 `rt_vsnprintf`(`kservice.c`,开了 `RT_PRINTF_PRECISION`)把 `%s` 前的数字解释为**最大长度(精度)**而非最小字段宽度,`th_led1` 超长被截断。改为 `%-8s` 后正常显示完整线程名。这也说明 RT-Thread 的格式化打印与标准 C 在 `%s` 精度语义上存在差异,打印长字符串时需注意。 diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 1870c3dfcba2de6eb2841362e0e9f1a98437dfe9..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-18 Zhang SiYuan day2 multi-thread study - */ - -#include -#include -#include - -#define DBG_TAG "thread_study" -#define DBG_LVL DBG_LOG -#include - -/* ===================== 板级资源定义(星火1号) ===================== */ -#define PIN_LED_R GET_PIN(F, 12) /* 红色 LED */ -#define PIN_LED_B GET_PIN(F, 11) /* 绿色 LED */ -#define PIN_KEY_UP GET_PIN(C, 5) /* KEY_UP 按键 */ - -/* ===================== 线程参数 ===================== */ -#define TH_LED1_PRIORITY 16 -#define TH_LED1_TIMESLICE 5 -#define TH_LED1_STACK_SIZE 1024 - -#define TH_LED2_PRIORITY 16 -#define TH_LED2_TIMESLICE 5 -#define TH_LED2_STACK_SIZE 1024 - -#define TH_MONITOR_PRIORITY 10 -#define TH_MONITOR_TIMESLICE 5 -#define TH_MONITOR_STACK_SIZE 1024 - -#define TH_LED1_DELAY_MS 500 -#define TH_LED2_DELAY_MS 1000 -#define TH_MONITOR_PERIOD_MS 2000 - -/* ===================== 全局线程句柄 / 静态对象 ===================== */ -static rt_thread_t th_led1 = RT_NULL; /* 动态创建:TCB+栈来自堆 */ -static rt_thread_t th_led2 = RT_NULL; /* 静态初始化:指向静态 TCB */ -static rt_thread_t th_monitor = RT_NULL; /* 动态创建:监控线程 */ - -/* th_led2 采用静态方式:TCB 与栈均在全局数据段静态定义 */ -static struct rt_thread led2_thread; -static rt_uint8_t led2_stack[TH_LED2_STACK_SIZE]; - -/* ===================== 按键与挂起控制 ===================== */ -static volatile rt_uint8_t key_pressed = 0; /* ISR 中置位,monitor 中消费 */ -static volatile rt_uint8_t led1_suspend_req = 0; /* 请求 th_led1 自行挂起 */ - -/* 按键中断回调(ISR 上下文,仅置位标志) */ -static void key_up_callback(void *args) -{ - key_pressed = 1; -} - -/* ===================== 工具函数 ===================== */ - -/* - * 计算线程剩余(未使用)栈空间。 - * RT-Thread 创建线程时用 '#'(0x23) 填充整个栈区,ARM 栈向下生长, - * 从低地址向高地址扫描连续 '#' 的字节数即为剩余栈。 - */ -static rt_uint32_t get_stack_remain(rt_thread_t thread) -{ - rt_uint32_t remain = 0; - rt_uint8_t *p = (rt_uint8_t *)thread->stack_addr; - rt_uint8_t *end = (rt_uint8_t *)((rt_ubase_t)thread->stack_addr + thread->stack_size); - - while ((p < end) && (*p == '#')) - { - remain++; - p++; - } - return remain; -} - -/* 线程状态数值 -> 可读字符串 */ -static const char *stat2str(rt_uint8_t stat) -{ - stat &= RT_THREAD_STAT_MASK; - switch (stat) - { - case RT_THREAD_INIT: return "INIT"; - case RT_THREAD_READY: return "READY"; - case RT_THREAD_SUSPEND: return "SUSPEND"; - case RT_THREAD_RUNNING: return "RUNNING"; - case RT_THREAD_CLOSE: return "CLOSE"; - default: return "UNKNOWN"; - } -} - -/* ===================== 工作线程 1:动态创建,翻转 LED_R ===================== */ -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - /* 按键挂起请求由本线程自行挂起(本内核 rt_thread_suspend 仅允许挂起自身) */ - while (1) - { - if (led1_suspend_req) - { - led1_suspend_req = 0; - LOG_I("th_led1: self suspend"); - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - LOG_I("th_led1: resumed"); - } - - rt_pin_write(PIN_LED_R, !rt_pin_read(PIN_LED_R)); - LOG_I("th_led1: count=%d, tick=%u", count++, (rt_ubase_t)rt_tick_get()); - rt_thread_mdelay(TH_LED1_DELAY_MS); - } -} - -/* ===================== 工作线程 2:静态初始化,翻转 LED_B ===================== */ -static void th_led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - - while (1) - { - rt_pin_write(PIN_LED_B, !rt_pin_read(PIN_LED_B)); - LOG_I("th_led2: count=%d", count++); - rt_thread_mdelay(TH_LED2_DELAY_MS); - } -} - -/* ===================== 监控与控制线程:打印状态、处理按键 ===================== */ -static void th_monitor_entry(void *parameter) -{ - rt_err_t res; - - while (1) - { - rt_thread_mdelay(TH_MONITOR_PERIOD_MS); - - /* 按键单次触发:挂起/恢复 th_led1 */ - if (key_pressed) - { - key_pressed = 0; - if ((th_led1->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND) - { - res = rt_thread_resume(th_led1); - LOG_I("monitor: resume th_led1, ret=%d", res); - } - else - { - led1_suspend_req = 1; - LOG_I("monitor: request th_led1 suspend"); - } - } - - /* 打印两个工作线程的运行状态与剩余栈空间 */ - LOG_I("monitor: [%-8s] stat=%-8s stack_remain=%uB | [%-8s] stat=%-8s stack_remain=%uB", - th_led1->name, stat2str(th_led1->stat), get_stack_remain(th_led1), - th_led2->name, stat2str(th_led2->stat), get_stack_remain(th_led2)); - } -} - -/* ===================== 自定义 MSH 命令:thread_dump ===================== */ -static void thread_dump(void) -{ - rt_kprintf("--------------------------------------------------------\n"); - rt_kprintf("name stat prio remain_stack(B)\n"); - rt_kprintf("--------------------------------------------------------\n"); - rt_kprintf("%-9s %-9s %-7d %u\n", th_led1->name, stat2str(th_led1->stat), - th_led1->current_priority, get_stack_remain(th_led1)); - rt_kprintf("%-9s %-9s %-7d %u\n", th_led2->name, stat2str(th_led2->stat), - th_led2->current_priority, get_stack_remain(th_led2)); - rt_kprintf("--------------------------------------------------------\n"); -} -MSH_CMD_EXPORT(thread_dump, dump thread status and remain stack); - -/* ===================== 自动初始化(禁止在 main.c 硬编码调用) ===================== */ -static int thread_study_init(void) -{ - rt_err_t res; - - /* 板载 LED 引脚初始化 */ - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - - /* 按键中断初始化 */ - rt_pin_mode(PIN_KEY_UP, PIN_MODE_INPUT_PULLUP); - rt_pin_attach_irq(PIN_KEY_UP, PIN_IRQ_MODE_FALLING, key_up_callback, RT_NULL); - rt_pin_irq_enable(PIN_KEY_UP, PIN_IRQ_ENABLE); - - /* 动态创建 th_led1 */ - th_led1 = rt_thread_create("th_led1", - th_led1_entry, - RT_NULL, - TH_LED1_STACK_SIZE, - TH_LED1_PRIORITY, - TH_LED1_TIMESLICE); - if (th_led1 == RT_NULL) - { - LOG_E("create th_led1 failed"); - return -RT_ERROR; - } - rt_thread_startup(th_led1); - - /* 静态初始化 th_led2 */ - res = rt_thread_init(&led2_thread, - "th_led2", - th_led2_entry, - RT_NULL, - led2_stack, - sizeof(led2_stack), - TH_LED2_PRIORITY, - TH_LED2_TIMESLICE); - if (res != RT_EOK) - { - LOG_E("init th_led2 failed, ret=%d", res); - return res; - } - th_led2 = &led2_thread; - rt_thread_startup(th_led2); - - /* 动态创建 th_monitor */ - th_monitor = rt_thread_create("th_mon", - th_monitor_entry, - RT_NULL, - TH_MONITOR_STACK_SIZE, - TH_MONITOR_PRIORITY, - TH_MONITOR_TIMESLICE); - if (th_monitor == RT_NULL) - { - LOG_E("create th_monitor failed"); - return -RT_ERROR; - } - rt_thread_startup(th_monitor); - - LOG_I("thread_study_init done"); - return RT_EOK; -} -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-35-31.png" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-35-31.png" deleted file mode 100644 index 211a3861f17b00c51e72190e0c9b439b4208166c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-35-31.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-37-22.png" "b/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-37-22.png" deleted file mode 100644 index c658e789119d0a23a4d77cb3d6f974f49f43e279..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\345\274\240\346\200\235\346\272\220/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\346\234\252\345\221\275\345\220\215\345\233\276\350\241\250-2026-08-18T06-37-22.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index ad7f2ed465f04c4c276413479e8b273e4031e172..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,4 +0,0 @@ -# 第一天作业 - -Day 1 作业已完成。 - diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index eeea0d071a0cac93dddc1dc427116a753f454335..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,4 +0,0 @@ -# 第一天笔记 - -今天学习了 Git 的基本使用,包括 Fork、克隆仓库、配置 upstream、创建分支、提交代码以及发起 Pull Request。 - diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index f3101faa91627006ffe424868b350c3829a7d686..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,160 +0,0 @@ -# RT-Thread 线程间同步与通信课后作业 - -- 姓名:杨智文 -- 组别:第3组 -- 开发板:STM32F407 星火号 -- RT-Thread:5.2.2 -- 源码:[`ipc_homework.c`](./ipc_homework.c) - -## 一、实验目标 - -本作业没有直接复制 `kernel_samples`,而是围绕四个实际场景分别设计线程、IPC 对象、结束条件、异常处理和重复执行保护: - -| MSH 命令 | 场景 | IPC 机制 | 线程数 | -| --- | --- | --- | ---: | -| `parking_demo` | 3 个车位供 5 辆车竞争 | 计数信号量 | 1 个管理线程 + 5 个车辆线程 | -| `ticket_demo` | 两个窗口销售 20 张票 | 互斥量 | 1 个管理线程 + 每阶段 2 个售票线程 | -| `startup_demo` | 等待三个模块全部初始化 | 事件集 | 1 个管理线程 + 3 个初始化线程 + 1 个业务线程 | -| `package_demo` | 双生产者向分拣线程发送快递 | 消息队列、信号量 | 1 个管理线程 + 2 个发送线程 + 1 个分拣线程 | - -每个线程和 IPC 对象创建后都检查返回值。命令运行期间再次执行会提示 `already running`;任务完成后管理线程删除动态 IPC 对象并清除运行标志,因此可以重新执行,不会残留同名对象。 - -## 二、练习一:停车场车位管理 - -### 1. 设计 - -- 创建初始值为 3 的计数信号量 `parksem`,每个计数代表一个空闲车位。 -- 创建 5 个车辆线程,车辆在 2 秒内未获得车位时打印超时信息,延时后继续申请。 -- 车辆进入时消耗一个信号量,离开时释放一个信号量。 -- 使用短临界区保护 `used_spaces` 统计变量,实际可用车位仍由信号量管理。 -- 管理线程通过完成信号量等待 5 辆车全部结束,再释放所有对象。 - -### 2. 实测结果 - -前 3 辆车占满车位,car4 和 car5 首次等待均超时;前车离开后,两辆车依次进入。`used spaces` 的最大值始终为 3,结束时恢复为 0。 - -![停车场计数信号量实测](./images/parking_terminal.png) - -### 3. 思考题:为什么使用计数信号量,而不是互斥量? - -停车位是包含 3 个相同单元的资源池,计数信号量可以直接表示“还剩几个资源”,并允许最多 3 个车辆线程同时成功获取资源。互斥量只有锁定和解锁两种状态,并且具有所有者语义,只适合保护同一临界区,无法自然表达 3 个并发名额。互斥量通常还带有优先级继承,用来解决锁竞争中的优先级反转;停车位计数不需要这种所有权机制。 - -## 三、练习二:多窗口售票系统 - -### 1. 无锁对比实验 - -两个线程先读取相同的余票值,再故意延时 10 ms,最后写回 `observed - 1`。两个窗口可能同时读到同一个票号,导致重复售票。本次实测结果为: - -```text -[without mutex] sales=35, duplicates=15, remaining=0 -``` - -无锁阶段的个别打印行发生交叉,这也说明多个线程同时输出时没有形成完整的原子日志;最终统计数据由临界区单独保护。 - -### 2. 加锁实验 - -互斥量保护“检查余票 → 取得票号 → 余票减 1 → 记录和打印”整个业务事务。实测结果为: - -```text -[with mutex] sales=20, duplicates=0, remaining=0 -[ticket] all tickets sold safely, remaining tickets: 0 -``` - -![售票互斥量对比实测](./images/ticket_terminal.png) - -### 3. 思考题:为什么只给 `ticket_count--` 加锁仍可能不安全? - -售票不是单个减法操作,而是一个不可分割的检查与更新序列。如果“检查是否有票”和“读取票号”位于锁外,两个线程仍可能同时判断有票并取得同一个旧值;即使减法本身串行执行,后续记录的票号也可能重复。正确临界区必须覆盖所有依赖共享状态一致性的步骤,而不仅是 `ticket_count--`。 - -## 四、练习三:系统启动条件检查 - -### 1. 事件位 - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -``` - -- 网络线程延时 500 ms 后发送网络就绪事件。 -- 传感器线程延时 1000 ms 后发送传感器就绪事件。 -- 存储线程延时 5000 ms 后发送存储就绪事件。 -- 业务线程使用 `RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR` 等待全部 3 位。 -- 第一次最多等待 3000 ms,超时后检查事件位并指出 storage 尚未完成,随后再次等待。 - -### 2. 实测结果 - -```text -network ready -sensor ready -wait modules timeout: storage is not ready -storage ready -all modules are ready -business task started -``` - -![启动事件集实测](./images/startup_terminal.png) - -### 3. 思考题:三个信号量与事件集有什么区别? - -三个独立信号量也能分别表示三个模块完成,但业务线程通常需要逐个 `take`,等待顺序会影响超时处理,而且难以一次得到“哪些模块已经完成、哪些仍未完成”的状态快照。事件集把多个布尔条件编码在一个对象中,能够通过 AND 模式一次等待全部条件,通过 OR 模式等待任意条件,并可直接检查未到达的事件位,因此更适合多条件汇合。信号量则更适合资源计数或一对一/累计通知。 - -## 五、练习四:快递分拣中心 - -### 1. 设计 - -- 消息队列容量为 5,每条消息包含不重复 ID、区域和重量。 -- sender1 产生 1001~1005,sender2 产生 2001~2005。 -- 两个发送线程各发送 5 条消息;队列满时累计失败次数、打印日志并延时重试,不丢弃快递。 -- 分拣线程按区域统计数量和总重量,并故意使用较慢的处理速度制造队列满场景。 -- 两个发送线程完成后,管理线程向队列发送 ID 为 0 的结束消息。 - -### 2. 实测统计 - -```text -total packages: 10 -east: 4, south: 3, north: 3 -total weight: 10700 g -send failed and retried: 7 -``` - -10 个快递 ID 均不重复;7 次发送失败均由队列暂时已满引起,重试后全部成功分拣。 - -![快递消息队列实测](./images/package_terminal.png) - -消息队列按值复制固定大小的 `package_msg`,解耦了生产者和消费者的执行速度。有限容量提供背压;发送失败重试策略避免了队列满时崩溃或静默丢包。结束消息与普通消息走同一队列,因此能保证此前成功入队的快递先被处理。 - -## 六、编译、烧录与串口验证 - -工程没有提供 `CMakeLists.txt`,因此终端构建使用 RT-Thread BSP 原生的 SCons/SConscript 机制,确保 Kconfig 选择和 `applications/*.c` 自动纳入构建,而不是另建一套不一致的 CMake 源文件列表。 - -完整构建结果: - -```text -CC build\applications\ipc_homework.o -LINK rt-thread.elf -arm-none-eabi-objcopy -O binary rt-thread.elf rtthread.bin -text: 78464, data: 772, bss: 4372, total: 83608 bytes -scons: done building targets. -``` - -烧录使用 STM32CubeProgrammer 命令行工具: - -```text -Device: STM32F405xx/F407xx/F415xx/F417xx -Flash size: 1 MBytes -Firmware: rtthread.bin, 77.38 KB -Address: 0x08000000 -Download verified successfully -Software reset is performed -``` - -串口为 ST-Link 虚拟串口 COM21,参数 `115200 8N1`。通过 `help` 已确认四条命令均注册成功,并逐条执行获得上述结果。截图由 COM21 直接捕获的真实 UART 文本等宽渲染而成,内容未使用示例日志替代。 - -## 七、结论 - -- 计数信号量适合表示多个同类有限资源。 -- 互斥量应覆盖完整共享状态事务,而非只保护单个写操作。 -- 事件集适合多个独立条件的 AND/OR 汇合与状态诊断。 -- 消息队列通过消息复制实现线程解耦,并需要明确处理容量耗尽和结束协议。 -- 所有演示均完成对象创建检查、运行状态检查、任务结束回收和重复执行验证。 diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/package_terminal.png" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/package_terminal.png" deleted file mode 100644 index 50dd398b351b69c492063c7b688197ce24781317..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/package_terminal.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/parking_terminal.png" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/parking_terminal.png" deleted file mode 100644 index 8f324e0c1a443e838ec2785a556fa0d79b629f75..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/parking_terminal.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/startup_terminal.png" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/startup_terminal.png" deleted file mode 100644 index dd6fc3f48e6d03114c0fbc6b414248b896cff62a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/startup_terminal.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/ticket_terminal.png" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/ticket_terminal.png" deleted file mode 100644 index 5c084c15e8fe9325f6dfb38fb7fa7b13f994bf46..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/images/ticket_terminal.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ipc_homework.c" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ipc_homework.c" deleted file mode 100644 index 7c8aacc15ef02958e6cac17903ea5b8074ac8686..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ipc_homework.c" +++ /dev/null @@ -1,820 +0,0 @@ -/* - * 第三天作业:RT-Thread 线程间同步与通信 - * 开发板:STM32F407 星火号 - * 作者:杨智文(第3组) - * - * MSH 命令: - * parking_demo - 计数信号量实现停车场车位管理 - * ticket_demo - 互斥量实现多窗口售票,并进行无锁对比 - * startup_demo - 事件集等待多个模块全部初始化完成 - * package_demo - 消息队列实现快递生产与分拣 - */ - -#include -#include - -#define DEMO_STACK_SIZE 1024 -#define RUNNER_STACK_SIZE 1536 -#define DEMO_PRIORITY 16 -#define RUNNER_PRIORITY 15 -#define DEMO_TIMESLICE 5 - -/* 用临界区保护演示运行标志,防止同一命令被重复启动。 */ -static rt_bool_t demo_try_start(rt_bool_t *running) -{ - rt_bool_t accepted = RT_FALSE; - - rt_enter_critical(); - if (*running == RT_FALSE) - { - *running = RT_TRUE; - accepted = RT_TRUE; - } - rt_exit_critical(); - - return accepted; -} - -static void demo_finish(rt_bool_t *running) -{ - rt_enter_critical(); - *running = RT_FALSE; - rt_exit_critical(); -} - -static rt_thread_t create_thread(const char *name, - void (*entry)(void *parameter), - void *parameter, - rt_uint32_t stack_size, - rt_uint8_t priority) -{ - rt_thread_t thread; - - thread = rt_thread_create(name, entry, parameter, stack_size, - priority, DEMO_TIMESLICE); - if (thread == RT_NULL) - { - rt_kprintf("[error] create thread %s failed\n", name); - } - - return thread; -} - -/* ==================== 练习一:停车场 ==================== */ - -#define PARKING_CAR_COUNT 5 -#define PARKING_SPACE_COUNT 3 - -struct parking_car_arg -{ - rt_uint8_t id; -}; - -struct parking_context -{ - rt_sem_t spaces; - rt_sem_t done; - rt_uint8_t used_spaces; - struct parking_car_arg cars[PARKING_CAR_COUNT]; -}; - -static struct parking_context parking_ctx; -static rt_bool_t parking_running = RT_FALSE; - -static void parking_car_entry(void *parameter) -{ - struct parking_car_arg *car = (struct parking_car_arg *)parameter; - rt_uint8_t used; - - /* 让车辆错开到达,同时保证前 3 辆车先占满车位。 */ - rt_thread_mdelay((rt_int32_t)car->id * 80); - - while (rt_sem_take(parking_ctx.spaces, rt_tick_from_millisecond(2000)) != RT_EOK) - { - rt_kprintf("car%u waiting for a space (timeout)\n", car->id); - rt_thread_mdelay(300); - } - - rt_enter_critical(); - parking_ctx.used_spaces++; - used = parking_ctx.used_spaces; - rt_exit_critical(); - - rt_kprintf("car%u entered, used spaces: %u\n", car->id, used); - - /* 停留时间超过 2 秒,使后到车辆能够触发一次等待超时。 */ - rt_thread_mdelay(2400 + (rt_int32_t)car->id * 120); - - rt_enter_critical(); - parking_ctx.used_spaces--; - used = parking_ctx.used_spaces; - rt_exit_critical(); - - rt_kprintf("car%u left, used spaces: %u\n", car->id, used); - rt_sem_release(parking_ctx.spaces); - rt_sem_release(parking_ctx.done); -} - -static void parking_runner_entry(void *parameter) -{ - rt_thread_t cars[PARKING_CAR_COUNT] = {RT_NULL}; - rt_uint8_t created = 0; - rt_uint8_t index; - char name[RT_NAME_MAX]; - - RT_UNUSED(parameter); - rt_memset(&parking_ctx, 0, sizeof(parking_ctx)); - - parking_ctx.spaces = rt_sem_create("parksem", PARKING_SPACE_COUNT, - RT_IPC_FLAG_FIFO); - parking_ctx.done = rt_sem_create("parkdone", 0, RT_IPC_FLAG_FIFO); - if ((parking_ctx.spaces == RT_NULL) || (parking_ctx.done == RT_NULL)) - { - rt_kprintf("[parking] create semaphore failed\n"); - goto cleanup; - } - - rt_kprintf("\n[parking] 3 spaces, 5 cars, wait timeout = 2 seconds\n"); - for (index = 0; index < PARKING_CAR_COUNT; index++) - { - parking_ctx.cars[index].id = index + 1; - rt_snprintf(name, sizeof(name), "car%u", index + 1); - cars[index] = create_thread(name, parking_car_entry, - &parking_ctx.cars[index], - DEMO_STACK_SIZE, DEMO_PRIORITY); - if (cars[index] == RT_NULL) - { - break; - } - created++; - } - - if (created != PARKING_CAR_COUNT) - { - for (index = 0; index < created; index++) - { - rt_thread_delete(cars[index]); - } - goto cleanup; - } - - for (index = 0; index < PARKING_CAR_COUNT; index++) - { - rt_thread_startup(cars[index]); - } - - for (index = 0; index < PARKING_CAR_COUNT; index++) - { - rt_sem_take(parking_ctx.done, RT_WAITING_FOREVER); - } - rt_kprintf("[parking] all cars completed, maximum capacity remained 3\n"); - -cleanup: - if (parking_ctx.done != RT_NULL) - { - rt_sem_delete(parking_ctx.done); - parking_ctx.done = RT_NULL; - } - if (parking_ctx.spaces != RT_NULL) - { - rt_sem_delete(parking_ctx.spaces); - parking_ctx.spaces = RT_NULL; - } - demo_finish(&parking_running); -} - -static int parking_demo(int argc, char **argv) -{ - rt_thread_t runner; - - RT_UNUSED(argc); - RT_UNUSED(argv); - - if (demo_try_start(&parking_running) == RT_FALSE) - { - rt_kprintf("parking_demo is already running\n"); - return -RT_EBUSY; - } - - runner = create_thread("pk_run", parking_runner_entry, RT_NULL, - RUNNER_STACK_SIZE, RUNNER_PRIORITY); - if (runner == RT_NULL) - { - demo_finish(&parking_running); - return -RT_ENOMEM; - } - rt_thread_startup(runner); - return RT_EOK; -} -MSH_CMD_EXPORT(parking_demo, counting semaphore parking demo); - -/* ==================== 练习二:多窗口售票 ==================== */ - -#define INITIAL_TICKET_COUNT 20 - -struct ticket_worker_arg -{ - char window; - rt_bool_t use_mutex; -}; - -struct ticket_context -{ - rt_mutex_t lock; - rt_sem_t done; - rt_int32_t remaining; - rt_uint32_t sold_count; - rt_uint32_t duplicate_count; - rt_uint8_t sold_map[INITIAL_TICKET_COUNT + 1]; - struct ticket_worker_arg workers[2]; -}; - -static struct ticket_context ticket_ctx; -static rt_bool_t ticket_running = RT_FALSE; - -static void record_ticket_sale(char window, rt_int32_t ticket) -{ - rt_bool_t duplicate; - rt_uint32_t sold; - - /* 统计信息单独用临界区保护,避免影响无锁实验的余票竞争窗口。 */ - rt_enter_critical(); - duplicate = ticket_ctx.sold_map[ticket] ? RT_TRUE : RT_FALSE; - if (duplicate) - { - ticket_ctx.duplicate_count++; - } - else - { - ticket_ctx.sold_map[ticket] = 1; - } - ticket_ctx.sold_count++; - sold = ticket_ctx.sold_count; - rt_exit_critical(); - - rt_kprintf("window %c sold ticket %d%s (sale #%u)\n", - window, ticket, duplicate ? " DUPLICATE" : "", sold); -} - -static void ticket_worker_entry(void *parameter) -{ - struct ticket_worker_arg *worker = (struct ticket_worker_arg *)parameter; - rt_int32_t observed; - - while (1) - { - if (worker->use_mutex) - { - if (rt_mutex_take(ticket_ctx.lock, RT_WAITING_FOREVER) != RT_EOK) - { - rt_kprintf("window %c failed to take mutex\n", worker->window); - break; - } - - if (ticket_ctx.remaining <= 0) - { - rt_mutex_release(ticket_ctx.lock); - break; - } - - observed = ticket_ctx.remaining; - ticket_ctx.remaining--; - record_ticket_sale(worker->window, observed); - rt_mutex_release(ticket_ctx.lock); - } - else - { - /* 故意在读取与写回之间延时,放大竞态条件。 */ - observed = ticket_ctx.remaining; - if (observed <= 0) - { - break; - } - rt_thread_mdelay(10); - ticket_ctx.remaining = observed - 1; - record_ticket_sale(worker->window, observed); - } - - rt_thread_mdelay((worker->window == 'A') ? 12 : 17); - } - - rt_sem_release(ticket_ctx.done); -} - -static rt_err_t run_ticket_phase(rt_bool_t use_mutex) -{ - rt_thread_t workers[2] = {RT_NULL}; - const char *names[2]; - rt_uint8_t index; - - ticket_ctx.remaining = INITIAL_TICKET_COUNT; - ticket_ctx.sold_count = 0; - ticket_ctx.duplicate_count = 0; - rt_memset(ticket_ctx.sold_map, 0, sizeof(ticket_ctx.sold_map)); - - names[0] = use_mutex ? "swinA" : "uwinA"; - names[1] = use_mutex ? "swinB" : "uwinB"; - ticket_ctx.workers[0].window = 'A'; - ticket_ctx.workers[1].window = 'B'; - ticket_ctx.workers[0].use_mutex = use_mutex; - ticket_ctx.workers[1].use_mutex = use_mutex; - - for (index = 0; index < 2; index++) - { - workers[index] = create_thread(names[index], ticket_worker_entry, - &ticket_ctx.workers[index], - DEMO_STACK_SIZE, DEMO_PRIORITY); - if (workers[index] == RT_NULL) - { - if (index > 0) - { - rt_thread_delete(workers[0]); - } - return -RT_ENOMEM; - } - } - - rt_thread_startup(workers[0]); - rt_thread_startup(workers[1]); - rt_sem_take(ticket_ctx.done, RT_WAITING_FOREVER); - rt_sem_take(ticket_ctx.done, RT_WAITING_FOREVER); - - rt_kprintf("[%s] sales=%u, duplicates=%u, remaining=%d\n", - use_mutex ? "with mutex" : "without mutex", - ticket_ctx.sold_count, ticket_ctx.duplicate_count, - ticket_ctx.remaining); - return RT_EOK; -} - -static void ticket_runner_entry(void *parameter) -{ - RT_UNUSED(parameter); - rt_memset(&ticket_ctx, 0, sizeof(ticket_ctx)); - - ticket_ctx.lock = rt_mutex_create("ticketmt", RT_IPC_FLAG_PRIO); - ticket_ctx.done = rt_sem_create("tickdone", 0, RT_IPC_FLAG_FIFO); - if ((ticket_ctx.lock == RT_NULL) || (ticket_ctx.done == RT_NULL)) - { - rt_kprintf("[ticket] create IPC object failed\n"); - goto cleanup; - } - - rt_kprintf("\n[ticket] phase 1: intentionally unsafe, no mutex\n"); - if (run_ticket_phase(RT_FALSE) != RT_EOK) - { - goto cleanup; - } - - rt_kprintf("\n[ticket] phase 2: mutex protects check, decrement and record\n"); - if (run_ticket_phase(RT_TRUE) == RT_EOK) - { - rt_kprintf("[ticket] all tickets sold safely, remaining tickets: %d\n", - ticket_ctx.remaining); - } - -cleanup: - if (ticket_ctx.done != RT_NULL) - { - rt_sem_delete(ticket_ctx.done); - ticket_ctx.done = RT_NULL; - } - if (ticket_ctx.lock != RT_NULL) - { - rt_mutex_delete(ticket_ctx.lock); - ticket_ctx.lock = RT_NULL; - } - demo_finish(&ticket_running); -} - -static int ticket_demo(int argc, char **argv) -{ - rt_thread_t runner; - - RT_UNUSED(argc); - RT_UNUSED(argv); - - if (demo_try_start(&ticket_running) == RT_FALSE) - { - rt_kprintf("ticket_demo is already running\n"); - return -RT_EBUSY; - } - - runner = create_thread("tk_run", ticket_runner_entry, RT_NULL, - RUNNER_STACK_SIZE, RUNNER_PRIORITY); - if (runner == RT_NULL) - { - demo_finish(&ticket_running); - return -RT_ENOMEM; - } - rt_thread_startup(runner); - return RT_EOK; -} -MSH_CMD_EXPORT(ticket_demo, mutex protected ticket selling demo); - -/* ==================== 练习三:启动条件检查 ==================== */ - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -struct startup_module_arg -{ - const char *name; - rt_uint32_t event; - rt_int32_t delay_ms; -}; - -struct startup_context -{ - rt_event_t event; - rt_sem_t done; - struct startup_module_arg modules[3]; -}; - -static struct startup_context startup_ctx; -static rt_bool_t startup_running = RT_FALSE; - -static void startup_module_entry(void *parameter) -{ - struct startup_module_arg *module = (struct startup_module_arg *)parameter; - - rt_thread_mdelay(module->delay_ms); - rt_kprintf("%s ready\n", module->name); - rt_event_send(startup_ctx.event, module->event); -} - -static void startup_business_entry(void *parameter) -{ - rt_uint32_t received = 0; - rt_uint32_t ready; - rt_err_t result; - - RT_UNUSED(parameter); - - result = rt_event_recv(startup_ctx.event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - rt_tick_from_millisecond(3000), &received); - if (result != RT_EOK) - { - /* 超时后读取当前事件位,仅用于报告尚未完成的模块。 */ - ready = startup_ctx.event->set; - rt_kprintf("wait modules timeout:"); - if ((ready & EVENT_NET_READY) == 0) - { - rt_kprintf(" network"); - } - if ((ready & EVENT_SENSOR_READY) == 0) - { - rt_kprintf(" sensor"); - } - if ((ready & EVENT_STORAGE_READY) == 0) - { - rt_kprintf(" storage"); - } - rt_kprintf(" is not ready\n"); - - result = rt_event_recv(startup_ctx.event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_WAITING_FOREVER, &received); - } - - if (result == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - } - rt_sem_release(startup_ctx.done); -} - -static void startup_runner_entry(void *parameter) -{ - rt_thread_t threads[4] = {RT_NULL}; - const char *names[4] = {"netinit", "seninit", "stoinit", "biztask"}; - rt_uint8_t created = 0; - rt_uint8_t index; - - RT_UNUSED(parameter); - rt_memset(&startup_ctx, 0, sizeof(startup_ctx)); - - startup_ctx.event = rt_event_create("st_event", RT_IPC_FLAG_FIFO); - startup_ctx.done = rt_sem_create("stdone", 0, RT_IPC_FLAG_FIFO); - if ((startup_ctx.event == RT_NULL) || (startup_ctx.done == RT_NULL)) - { - rt_kprintf("[startup] create IPC object failed\n"); - goto cleanup; - } - - startup_ctx.modules[0].name = "network"; - startup_ctx.modules[0].event = EVENT_NET_READY; - startup_ctx.modules[0].delay_ms = 500; - startup_ctx.modules[1].name = "sensor"; - startup_ctx.modules[1].event = EVENT_SENSOR_READY; - startup_ctx.modules[1].delay_ms = 1000; - startup_ctx.modules[2].name = "storage"; - startup_ctx.modules[2].event = EVENT_STORAGE_READY; - startup_ctx.modules[2].delay_ms = 5000; - - rt_kprintf("\n[startup] wait all modules, first timeout = 3 seconds\n"); - for (index = 0; index < 3; index++) - { - threads[index] = create_thread(names[index], startup_module_entry, - &startup_ctx.modules[index], - DEMO_STACK_SIZE, DEMO_PRIORITY); - if (threads[index] == RT_NULL) - { - break; - } - created++; - } - if (created == 3) - { - threads[3] = create_thread(names[3], startup_business_entry, RT_NULL, - DEMO_STACK_SIZE, DEMO_PRIORITY - 1); - if (threads[3] != RT_NULL) - { - created++; - } - } - - if (created != 4) - { - for (index = 0; index < created; index++) - { - rt_thread_delete(threads[index]); - } - goto cleanup; - } - - for (index = 0; index < 4; index++) - { - rt_thread_startup(threads[index]); - } - rt_sem_take(startup_ctx.done, RT_WAITING_FOREVER); - -cleanup: - if (startup_ctx.done != RT_NULL) - { - rt_sem_delete(startup_ctx.done); - startup_ctx.done = RT_NULL; - } - if (startup_ctx.event != RT_NULL) - { - rt_event_delete(startup_ctx.event); - startup_ctx.event = RT_NULL; - } - demo_finish(&startup_running); -} - -static int startup_demo(int argc, char **argv) -{ - rt_thread_t runner; - - RT_UNUSED(argc); - RT_UNUSED(argv); - - if (demo_try_start(&startup_running) == RT_FALSE) - { - rt_kprintf("startup_demo is already running\n"); - return -RT_EBUSY; - } - - runner = create_thread("st_run", startup_runner_entry, RT_NULL, - RUNNER_STACK_SIZE, RUNNER_PRIORITY); - if (runner == RT_NULL) - { - demo_finish(&startup_running); - return -RT_ENOMEM; - } - rt_thread_startup(runner); - return RT_EOK; -} -MSH_CMD_EXPORT(startup_demo, event set startup dependency demo); - -/* ==================== 练习四:快递分拣中心 ==================== */ - -#define PACKAGE_PER_SENDER 5 -#define PACKAGE_END_ID 0 - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -struct package_sender_arg -{ - rt_uint8_t sender_id; -}; - -struct package_context -{ - rt_mq_t queue; - rt_sem_t sender_done; - rt_sem_t sorter_done; - rt_uint32_t send_failed; - struct package_sender_arg senders[2]; -}; - -static struct package_context package_ctx; -static rt_bool_t package_running = RT_FALSE; - -static const char *package_region_name(rt_uint8_t region) -{ - switch (region) - { - case 1: - return "east"; - case 2: - return "south"; - case 3: - return "north"; - default: - return "unknown"; - } -} - -static void package_sender_entry(void *parameter) -{ - struct package_sender_arg *sender = (struct package_sender_arg *)parameter; - struct package_msg message; - rt_uint8_t index; - - for (index = 1; index <= PACKAGE_PER_SENDER; index++) - { - message.id = (rt_uint32_t)sender->sender_id * 1000U + index; - message.region = (rt_uint8_t)(((sender->sender_id + index) % 3U) + 1U); - message.weight = (rt_uint16_t)(650U + sender->sender_id * 100U + index * 90U); - - while (rt_mq_send(package_ctx.queue, &message, sizeof(message)) != RT_EOK) - { - rt_enter_critical(); - package_ctx.send_failed++; - rt_exit_critical(); - rt_kprintf("sender%u queue full, retry package %lu\n", - sender->sender_id, (unsigned long)message.id); - rt_thread_mdelay(80); - } - - rt_kprintf("sender%u accepted package %lu\n", - sender->sender_id, (unsigned long)message.id); - rt_thread_mdelay((sender->sender_id == 1) ? 35 : 50); - } - - rt_sem_release(package_ctx.sender_done); -} - -static void package_sorter_entry(void *parameter) -{ - struct package_msg message; - rt_uint32_t total = 0; - rt_uint32_t region_count[3] = {0, 0, 0}; - rt_uint32_t total_weight = 0; - - RT_UNUSED(parameter); - - while (1) - { - if (rt_mq_recv(package_ctx.queue, &message, sizeof(message), - RT_WAITING_FOREVER) != RT_EOK) - { - continue; - } - if (message.id == PACKAGE_END_ID) - { - break; - } - - total++; - if ((message.region >= 1) && (message.region <= 3)) - { - region_count[message.region - 1]++; - } - total_weight += message.weight; - rt_kprintf("package %lu -> %s, weight: %u g\n", - (unsigned long)message.id, - package_region_name(message.region), message.weight); - - /* 分拣速度略慢,用于制造队列满的可恢复场景。 */ - rt_thread_mdelay(120); - } - - rt_kprintf("[package] total packages: %u\n", total); - rt_kprintf("[package] east: %u, south: %u, north: %u\n", - region_count[0], region_count[1], region_count[2]); - rt_kprintf("[package] total weight: %u g\n", total_weight); - rt_kprintf("[package] send failed and retried: %u\n", - package_ctx.send_failed); - rt_sem_release(package_ctx.sorter_done); -} - -static void package_runner_entry(void *parameter) -{ - rt_thread_t senders[2] = {RT_NULL}; - rt_thread_t sorter = RT_NULL; - struct package_msg end_message = {PACKAGE_END_ID, 0, 0}; - - RT_UNUSED(parameter); - rt_memset(&package_ctx, 0, sizeof(package_ctx)); - - package_ctx.queue = rt_mq_create("packmq", sizeof(struct package_msg), 5, - RT_IPC_FLAG_FIFO); - package_ctx.sender_done = rt_sem_create("pkgsend", 0, RT_IPC_FLAG_FIFO); - package_ctx.sorter_done = rt_sem_create("pkgdone", 0, RT_IPC_FLAG_FIFO); - if ((package_ctx.queue == RT_NULL) || - (package_ctx.sender_done == RT_NULL) || - (package_ctx.sorter_done == RT_NULL)) - { - rt_kprintf("[package] create IPC object failed\n"); - goto cleanup; - } - - package_ctx.senders[0].sender_id = 1; - package_ctx.senders[1].sender_id = 2; - senders[0] = create_thread("sender1", package_sender_entry, - &package_ctx.senders[0], - DEMO_STACK_SIZE, DEMO_PRIORITY); - senders[1] = create_thread("sender2", package_sender_entry, - &package_ctx.senders[1], - DEMO_STACK_SIZE, DEMO_PRIORITY); - sorter = create_thread("sorter", package_sorter_entry, RT_NULL, - DEMO_STACK_SIZE, DEMO_PRIORITY - 1); - - if ((senders[0] == RT_NULL) || (senders[1] == RT_NULL) || - (sorter == RT_NULL)) - { - if (senders[0] != RT_NULL) - { - rt_thread_delete(senders[0]); - } - if (senders[1] != RT_NULL) - { - rt_thread_delete(senders[1]); - } - if (sorter != RT_NULL) - { - rt_thread_delete(sorter); - } - goto cleanup; - } - - rt_kprintf("\n[package] 2 senders, 1 sorter, queue capacity = 5\n"); - rt_thread_startup(sorter); - rt_thread_startup(senders[0]); - rt_thread_startup(senders[1]); - - rt_sem_take(package_ctx.sender_done, RT_WAITING_FOREVER); - rt_sem_take(package_ctx.sender_done, RT_WAITING_FOREVER); - - /* 两个生产者完成后发送结束消息;队列满时等待并重试。 */ - while (rt_mq_send(package_ctx.queue, &end_message, - sizeof(end_message)) != RT_EOK) - { - rt_thread_mdelay(50); - } - rt_sem_take(package_ctx.sorter_done, RT_WAITING_FOREVER); - -cleanup: - if (package_ctx.sorter_done != RT_NULL) - { - rt_sem_delete(package_ctx.sorter_done); - package_ctx.sorter_done = RT_NULL; - } - if (package_ctx.sender_done != RT_NULL) - { - rt_sem_delete(package_ctx.sender_done); - package_ctx.sender_done = RT_NULL; - } - if (package_ctx.queue != RT_NULL) - { - rt_mq_delete(package_ctx.queue); - package_ctx.queue = RT_NULL; - } - demo_finish(&package_running); -} - -static int package_demo(int argc, char **argv) -{ - rt_thread_t runner; - - RT_UNUSED(argc); - RT_UNUSED(argv); - - if (demo_try_start(&package_running) == RT_FALSE) - { - rt_kprintf("package_demo is already running\n"); - return -RT_EBUSY; - } - - runner = create_thread("mq_run", package_runner_entry, RT_NULL, - RUNNER_STACK_SIZE, RUNNER_PRIORITY); - if (runner == RT_NULL) - { - demo_finish(&package_running); - return -RT_ENOMEM; - } - rt_thread_startup(runner); - return RT_EOK; -} -MSH_CMD_EXPORT(package_demo, message queue package sorting demo); diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index ad71b1b501de8eab59df5862cc08897b577df93a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,149 +0,0 @@ -# 【DAY2】RT-Thread 启动流程、内核基础与多线程编程 - -- 姓名:杨智文 -- 组别:第3组 -- 目标平台:STM32F407 星火号(`stm32f407-rtspark`) -- 工程路径:`D:\RT-ThreadStudio\workspace\test` - -## Part 1:理论知识梳理 - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -本工程使用 GCC 工具链,完整启动路径如下: - -```text -上电/复位 - -> CPU 从中断向量表取得初始 MSP 和 Reset_Handler 地址 - -> Reset_Handler - -> 设置主栈指针 - -> 将 .data 从 Flash 复制到 SRAM - -> 清零 .bss - -> SystemInit():配置时钟等底层硬件 - -> __libc_init_array():执行 C/C++ 运行库初始化 - -> entry() - -> rtthread_startup() - -> 关闭中断 - -> rt_hw_board_init():板级、时钟、堆和 INIT_BOARD_EXPORT 项初始化 - -> 初始化系统定时器与调度器 - -> rt_application_init():创建 main 线程 - -> 创建 timer 线程和 idle 线程 - -> rt_system_scheduler_start():启动调度 - -> main_thread_entry() - -> rt_components_init():按顺序执行自动初始化函数 - -> main() -``` - -`INIT_BOARD_EXPORT(fn)` 和 `INIT_APP_EXPORT(fn)` 最终都会展开为 `INIT_EXPORT(fn, level)`。在 GCC 下,宏把函数指针分别放入 `.rti_fn.1` 和 `.rti_fn.6` 段,并使用 `RT_USED` 防止编译器删除。链接脚本通过 `KEEP(*(SORT(.rti_fn*)))` 保留并按等级排序这些段。系统启动时遍历相应区间的函数指针:板级初始化阶段执行等级 1,main 线程中的组件初始化阶段执行等级 2~6。因此本作业只需写 `INIT_APP_EXPORT(thread_study_init)`,无需在 `main.c` 中手动调用。 - -### 2. 内核基础与对象模型 - -RT-Thread 用统一的内核对象管理机制组织线程、信号量、互斥量等资源。公共对象头保存对象类型、名称和对象链表节点,具体对象再扩展自己的控制信息。线程对应的控制块是 `struct rt_thread`,其中关键成员如下: - -| 成员 | 作用 | -| --- | --- | -| `stat` | 保存线程当前状态,读取基本状态时应配合 `RT_THREAD_STAT_MASK` | -| `current_priority` | 线程当前有效优先级,数值越小优先级越高 | -| `init_tick` | 线程初始时间片,时间片耗尽后用于重新装载 | -| `remaining_tick` | 当前还剩多少时间片 | -| `sp` | 当前线程栈指针,线程切换时用于保存和恢复现场 | -| `stack_addr` / `stack_size` | 栈起始地址和总大小,可用于监控栈使用量 | - -静态对象和动态对象的区别: - -| 对比项 | 静态线程 `rt_thread_init` | 动态线程 `rt_thread_create` | -| --- | --- | --- | -| TCB/栈位置 | 编译期定义的全局或静态内存 | 运行时从系统堆申请 | -| 生命周期 | 由程序静态存储期决定 | 创建后存在,删除时归还堆 | -| 释放接口 | `rt_thread_detach` | `rt_thread_delete` | -| 优点 | 内存确定、无堆碎片、失败模式少 | 使用灵活、可按需创建和删除 | -| 适用场景 | 安全关键、长期运行、资源必须可预测 | 功能动态变化、资源按需使用 | - -高可靠系统常强制使用静态线程,因为其内存占用可在编译阶段确定,不依赖运行时堆状态,能够避免堆耗尽、碎片化和动态分配不确定性。 - -### 3. 线程状态机与调度算法 - -RT-Thread 线程包含五种基本状态: - -```text - rt_thread_startup()/rt_thread_resume() - INIT ----------------------------------------------> READY - | - 调度器选中 | 时间片耗尽/被抢占 - v - CLOSE <------- 线程退出/删除 ------- RUNNING <------ READY - | - delay/suspend/等待 IPC | - v - SUSPEND - | - 超时/唤醒/resume ---------+ -``` - -- 初始态 `INIT`:线程已创建或初始化,但还没有启动。 -- 就绪态 `READY`:具备运行条件,位于就绪队列中等待调度。 -- 运行态 `RUNNING`:当前占用 CPU。 -- 挂起态 `SUSPEND`:主动挂起、延时或等待 IPC;可由超时、唤醒或 `rt_thread_resume()` 回到就绪态。 -- 关闭态 `CLOSE`:线程运行结束或被删除,等待资源回收。 - -抢占式调度:当更高优先级线程就绪时,调度器立即让它抢占低优先级线程。本实验中 `th_monitor` 优先级为 10,高于两个 LED 线程的 16,因此监控线程被唤醒后优先运行。 - -时间片轮转:同一优先级存在多个就绪线程时,每个线程最多运行自己的时间片;时间片用完后移到同优先级队列末尾。本实验的 `th_led1` 和 `th_led2` 同为优先级 16、时间片 5 ticks,可以公平获得 CPU。 - -## Part 2:多线程实验 - -### 1. 实现方案 - -| 线程 | 创建方式 | 优先级 | 时间片 | 栈 | 功能 | -| --- | --- | ---: | ---: | ---: | --- | -| `th_led1` | `rt_thread_create` 动态创建 | 16 | 5 ticks | 1024 B | 翻转 PF12 红灯,打印计数和系统 tick | -| `th_led2` | `rt_thread_init` 静态初始化 | 16 | 5 ticks | 1024 B | 翻转 PF11 蓝灯,打印计数和系统 tick | -| `th_monitor` | `rt_thread_create` 动态创建 | 10 | 5 ticks | 1024 B | 每 2 秒打印 LED 线程状态和剩余栈,处理按键控制 | - -板载 RGB LED 为低电平点亮。KEY_UP 使用 PC5、上拉输入和下降沿中断。中断函数只记录按键事件,实际的 `rt_thread_suspend()` / `rt_thread_resume()` 在线程上下文中执行。因为调用 `rt_thread_mdelay()` 的线程也表现为挂起态,程序在主动暂停前会等待 `th_led1` 的延时结束,避免把延时状态与人工暂停混淆。 - -### 2. 自动初始化与调试命令 - -- 使用 `INIT_APP_EXPORT(thread_study_init)` 自动创建并启动三个线程。 -- `main.c` 不直接调用作业初始化函数。 -- 使用 `MSH_CMD_EXPORT(thread_dump, ...)` 导出 `thread_dump` 命令。 -- 剩余栈统计利用 RT-Thread 创建线程时用 `'#'` 填充栈的特性,计算尚未被覆盖的连续字节数。 - -### 3. 编译验证 - -已使用工程配置的 ARM GNU 编译器和 STM32F407 编译参数对 `thread_study.c` 执行编译检查,结果通过。编译器仅报告 RT-Thread 自带 `MSH_CMD_EXPORT` 宏的函数指针转换提示,作业源码无语法错误。 - -### 4. 开发板启动与串口连接截图 - -![STM32F407 星火号启动串口截图](./images/开发板启动串口截图.png) - -截图中可以看到 RT-Thread 5.2.2 已在 STM32F407 星火号上启动,USART1 使用 `115200 8N1`,LCD、LVGL、方向按键及板级硬件服务完成初始化,串口终端能够正常接收命令。该图片用于证明开发板和串口调试环境可正常运行;其中显示的是板卡启动日志,不作为 `th_led1`、`th_led2` 和 `th_monitor` 的实测输出。 - -### 5. 烧录后的预期现象与日志格式 - -1. 红灯和蓝灯约每 500 ms 翻转一次。 -2. 串口持续输出两个工作线程的计数和系统 tick。 -3. 每 2 秒打印两个 LED 线程的状态与剩余栈。 -4. 按一次 KEY_UP 暂停红灯线程,再按一次恢复。 -5. 在 FinSH 中输入 `thread_dump` 可查看三个线程。 - -```text -[thread_study] Day2 threads started; press KEY_UP to pause/resume th_led1. -[th_led1] count=1 tick=... LED_R=ON -[th_led2] count=1 tick=... LED_B=ON - -[monitor] thread state and remaining stack -th_led1 SUSPEND pri=16 tick=05 stack_free=.../1024 -th_led2 SUSPEND pri=16 tick=05 stack_free=.../1024 - -msh >thread_dump -name state priority/tick/remaining stack -th_led1 ... -th_led2 ... -th_monitor ... -``` - -> 说明:以上为程序定义的日志格式。真实 tick、线程瞬时状态和剩余栈数值以 Day2 程序烧录后的串口输出为准;后续可继续补充包含 `th_led1`、`th_led2`、`th_monitor` 和 `thread_dump` 输出的实测截图。 - -## Part 3:源码 - -源码见同目录下的 [`thread_study.c`](./thread_study.c)。 diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/\345\274\200\345\217\221\346\235\277\345\220\257\345\212\250\344\270\262\345\217\243\346\210\252\345\233\276.png" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/\345\274\200\345\217\221\346\235\277\345\220\257\345\212\250\344\270\262\345\217\243\346\210\252\345\233\276.png" deleted file mode 100644 index 866fb0f4f90a743d83acb80e09e80dbf91059c52..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/images/\345\274\200\345\217\221\346\235\277\345\220\257\345\212\250\344\270\262\345\217\243\346\210\252\345\233\276.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 98e60f29d522c686e74035341b6ea8b8397cf940..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Day2 作业:RT-Thread 内核基础与多线程编程 - * 开发板:STM32F407 星火号(stm32f407-rtspark) - * 作者:杨智文 - * - * 功能说明: - * 1. 动态线程 th_led1 控制红灯 LED_R; - * 2. 静态线程 th_led2 控制蓝灯 LED_B; - * 3. 监控线程 th_monitor 每 2 秒打印线程状态和剩余栈空间; - * 4. 按下 KEY_UP 可挂起或恢复 th_led1; - * 5. 输入 FinSH 命令 thread_dump 可随时查看三个线程的信息。 - */ - -#include -#include -#include -#include - -#define LED1_PIN GET_PIN(F, 12) /* red LED, active low */ -#define LED2_PIN GET_PIN(F, 11) /* blue LED, active low */ -#define KEY_UP_PIN GET_PIN(C, 5) /* KEY_UP, active low */ - -#define LED_THREAD_PRIORITY 16 -#define LED_THREAD_TIMESLICE 5 -#define LED_THREAD_STACK_SIZE 1024 -#define MONITOR_THREAD_PRIORITY 10 -#define MONITOR_THREAD_TIMESLICE 5 -#define MONITOR_THREAD_STACK_SIZE 1024 - -/* 动态线程只保存线程句柄,TCB 和栈空间由 RT-Thread 从堆中分配。 */ -static rt_thread_t led1_thread = RT_NULL; -static rt_thread_t monitor_thread = RT_NULL; - -/* 静态线程的线程控制块(TCB)和栈空间均由程序预先分配。 */ -static struct rt_thread led2_thread_tcb; -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t led2_thread_stack[LED_THREAD_STACK_SIZE]; - -static volatile rt_uint8_t key_up_event = 0; -static rt_bool_t led1_is_suspended = RT_FALSE; - -/* stat 的低位保存基本线程状态,先用状态掩码去掉附加标志。 */ -static const char *thread_state_name(rt_uint8_t stat) -{ - switch (stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: - return "INIT"; - case RT_THREAD_READY: - return "READY"; - case RT_THREAD_SUSPEND: - return "SUSPEND"; - case RT_THREAD_RUNNING: - return "RUNNING"; - case RT_THREAD_CLOSE: - return "CLOSE"; - default: - return "UNKNOWN"; - } -} - -static rt_size_t thread_stack_free(rt_thread_t thread) -{ - const rt_uint8_t *stack; - rt_size_t free_size = 0; - - if ((thread == RT_NULL) || (thread->stack_addr == RT_NULL)) - { - return 0; - } - - stack = (const rt_uint8_t *)thread->stack_addr; - - /* - * RT-Thread 创建线程时会用字符 '#' 填充整个栈。 - * 从栈的空闲端连续统计仍为 '#' 的字节数,即可估算剩余栈空间。 - */ -#ifdef ARCH_CPU_STACK_GROWS_UPWARD - while ((free_size < thread->stack_size) && - (stack[thread->stack_size - 1U - free_size] == (rt_uint8_t)'#')) - { - free_size++; - } -#else - while ((free_size < thread->stack_size) && - (stack[free_size] == (rt_uint8_t)'#')) - { - free_size++; - } -#endif - - return free_size; -} - -static void print_thread_info(rt_thread_t thread) -{ - if (thread == RT_NULL) - { - return; - } - - rt_kprintf("%-10s %-8s pri=%02u tick=%02u stack_free=%4u/%4u\n", - thread->name, - thread_state_name(thread->stat), - (unsigned int)thread->current_priority, - (unsigned int)thread->remaining_tick, - (unsigned int)thread_stack_free(thread), - (unsigned int)thread->stack_size); -} - -static void led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_base_t level = PIN_HIGH; - - RT_UNUSED(parameter); - - /* 动态线程:翻转红灯,并输出运行次数与当前系统节拍。 */ - while (1) - { - level = (level == PIN_HIGH) ? PIN_LOW : PIN_HIGH; - rt_pin_write(LED1_PIN, level); - rt_kprintf("[th_led1] count=%lu tick=%lu LED_R=%s\n", - (unsigned long)++count, - (unsigned long)rt_tick_get(), - (level == PIN_LOW) ? "ON" : "OFF"); - rt_thread_mdelay(500); - } -} - -static void led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_base_t level = PIN_HIGH; - - RT_UNUSED(parameter); - - /* 静态线程:与 th_led1 同优先级、同时间片,翻转蓝灯。 */ - while (1) - { - level = (level == PIN_HIGH) ? PIN_LOW : PIN_HIGH; - rt_pin_write(LED2_PIN, level); - rt_kprintf("[th_led2] count=%lu tick=%lu LED_B=%s\n", - (unsigned long)++count, - (unsigned long)rt_tick_get(), - (level == PIN_LOW) ? "ON" : "OFF"); - rt_thread_mdelay(500); - } -} - -static void key_up_irq_handler(void *args) -{ - RT_UNUSED(args); - /* 中断服务函数只置位事件,线程控制操作留给监控线程执行。 */ - key_up_event = 1; -} - -static void toggle_led1_thread(void) -{ - rt_err_t result; - - if (led1_thread == RT_NULL) - { - return; - } - - if (led1_is_suspended == RT_FALSE) - { - /* - * 延时中的线程同样显示为 SUSPEND。先等待延时结束并回到 READY, - * 再执行主动挂起,避免把“延时挂起”误判为“按键挂起”。 - */ - while ((led1_thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND) - { - rt_thread_mdelay(10); - } - - result = rt_thread_suspend(led1_thread); - if (result == RT_EOK) - { - led1_is_suspended = RT_TRUE; - rt_kprintf("[monitor] KEY_UP: th_led1 suspended\n"); - rt_schedule(); - } - else - { - rt_kprintf("[monitor] suspend th_led1 failed: %d\n", result); - } - } - else - { - result = rt_thread_resume(led1_thread); - if (result == RT_EOK) - { - led1_is_suspended = RT_FALSE; - rt_kprintf("[monitor] KEY_UP: th_led1 resumed\n"); - rt_schedule(); - } - else - { - rt_kprintf("[monitor] resume th_led1 failed: %d\n", result); - } - } -} - -static void monitor_entry(void *parameter) -{ - RT_UNUSED(parameter); - - while (1) - { - /* 周期延时会主动让出 CPU,2 秒后由调度器重新唤醒本线程。 */ - rt_thread_mdelay(2000); - - if (key_up_event != 0) - { - key_up_event = 0; - toggle_led1_thread(); - } - - rt_kprintf("\n[monitor] thread state and remaining stack\n"); - print_thread_info(led1_thread); - print_thread_info(&led2_thread_tcb); - } -} - -static int thread_dump(int argc, char **argv) -{ - RT_UNUSED(argc); - RT_UNUSED(argv); - - rt_kprintf("name state priority/tick/remaining stack\n"); - print_thread_info(led1_thread); - print_thread_info(&led2_thread_tcb); - print_thread_info(monitor_thread); - - return 0; -} -/* 将 thread_dump 导出为 FinSH/MSH 命令,无需在 main() 中注册。 */ -MSH_CMD_EXPORT(thread_dump, show Day2 thread state and remaining stack); - -static int thread_study_init(void) -{ - rt_err_t result; - - rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT); - rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT); - rt_pin_write(LED1_PIN, PIN_HIGH); - rt_pin_write(LED2_PIN, PIN_HIGH); - - rt_pin_mode(KEY_UP_PIN, PIN_MODE_INPUT_PULLUP); - result = rt_pin_attach_irq(KEY_UP_PIN, PIN_IRQ_MODE_FALLING, - key_up_irq_handler, RT_NULL); - if (result != RT_EOK) - { - rt_kprintf("[thread_study] attach KEY_UP irq failed: %d\n", result); - return result; - } - rt_pin_irq_enable(KEY_UP_PIN, PIN_IRQ_ENABLE); - - /* 动态创建:TCB 和 1024 字节栈均从系统堆申请。 */ - led1_thread = rt_thread_create("th_led1", led1_entry, RT_NULL, - LED_THREAD_STACK_SIZE, - LED_THREAD_PRIORITY, - LED_THREAD_TIMESLICE); - if (led1_thread == RT_NULL) - { - return -RT_ENOMEM; - } - - /* 静态创建:使用上方定义的全局 TCB 和全局栈数组。 */ - result = rt_thread_init(&led2_thread_tcb, "th_led2", led2_entry, RT_NULL, - led2_thread_stack, sizeof(led2_thread_stack), - LED_THREAD_PRIORITY, LED_THREAD_TIMESLICE); - if (result != RT_EOK) - { - rt_thread_delete(led1_thread); - led1_thread = RT_NULL; - return result; - } - - monitor_thread = rt_thread_create("th_monitor", monitor_entry, RT_NULL, - MONITOR_THREAD_STACK_SIZE, - MONITOR_THREAD_PRIORITY, - MONITOR_THREAD_TIMESLICE); - if (monitor_thread == RT_NULL) - { - rt_thread_detach(&led2_thread_tcb); - rt_thread_delete(led1_thread); - led1_thread = RT_NULL; - return -RT_ENOMEM; - } - - /* 创建/初始化成功后,依次加入就绪队列等待调度。 */ - rt_thread_startup(led1_thread); - rt_thread_startup(&led2_thread_tcb); - rt_thread_startup(monitor_thread); - - rt_kprintf("[thread_study] Day2 threads started; press KEY_UP to pause/resume th_led1.\n"); - return RT_EOK; -} -/* 应用初始化阶段自动执行,main() 中不需要硬编码调用。 */ -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 7bd01bb8c5c92f77ac9d6853c4862d725ada7c90..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,90 +0,0 @@ -# Day4:RT-Thread 设备框架、PIN 设备与虚拟设备驱动 - -姓名:杨智文 -组别:第 3 组 -日期:2026-08-20 - -## 1. 课堂笔记:统一设备模型 - -RT-Thread 用统一的 `rt_device` 抽象屏蔽不同外设的差异。应用层不必了解 GPIO、串口、SPI 等硬件细节,而是遵循统一调用流程: - -```text -rt_device_find() → rt_device_open() → rt_device_read()/write()/control() → rt_device_close() -``` - -设备对象被注册到内核设备管理器后,应用可按名称查找。设备管理层再通过设备的操作函数指针,将通用 API 分发给具体驱动实现。这样应用层、设备框架层和 BSP/HAL 层职责清晰,替换硬件时通常只需替换底层驱动。 - -`rt_device_register()` 负责把静态设备加入系统设备链表;`rt_device_create()` 可创建动态设备对象。设备驱动可按需要提供 `init`、`open`、`close`、`read`、`write` 和 `control` 等操作。 - -## 2. PIN 设备如何对接 - -本工程已启用 `RT_USING_PIN`。其实际调用链如下: - -```text -应用:GET_PIN()/rt_pin_mode()/rt_pin_write()/rt_pin_read() - ↓ -通用 PIN 框架:components/drivers/misc/pin.c - ↓ 调用 _hw_pin.ops 中的 pin_mode/pin_write/pin_read/IRQ 操作 -BSP:libraries/HAL_Drivers/drv_gpio.c 的 _stm32_pin_ops - ↓ -STM32 HAL:HAL_GPIO_Init/HAL_GPIO_WritePin/HAL_GPIO_ReadPin - ↓ -GPIO/EXTI 硬件寄存器 -``` - -具体对接过程: - -1. BSP 启动时,`drv_common.c` 调用 `rt_hw_pin_init()`。 -2. `drv_gpio.c` 打开 GPIO 时钟,并组织 `_stm32_pin_ops` 操作表。 -3. `rt_hw_pin_init()` 通过 `rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL)` 注册名为 `pin` 的 PIN 设备。 -4. 通用层保存该操作表;应用调用 `rt_pin_write()` 等 API 时,通用层转调 BSP 的 `stm32_pin_write()` 等函数。 -5. BSP 将 RT-Thread 的逻辑引脚转换为 STM32 GPIO 端口和引脚号,最后调用 HAL 完成硬件访问。中断场景中,`rt_pin_attach_irq()`、`rt_pin_irq_enable()` 也沿同一条操作表链路配置 EXTI,并由中断服务程序回调应用注册的函数。 - -这说明 PIN 既能给应用提供简单的 `rt_pin_*` API,也能作为 `pin` 设备纳入 RT-Thread 的设备管理体系。 - -## 3. 虚拟字符设备:`test_dev` - -本次设计了一个不依赖实体外设的字符设备,用于练习完整的设备驱动框架层。源码见 [test_dev.c](test_dev.c)。 - -- 设备名:`test_dev` -- 类型:`RT_Device_Class_Char` -- 私有数据:128 字节内存缓冲区、当前数据长度、读写位置和操作统计信息 -- 并发保护:互斥锁 `td_lock` -- 支持操作:初始化、打开、关闭、读、写、控制 -- 控制命令:`TEST_DEV_CTRL_CLEAR` 清空缓冲区;`TEST_DEV_CTRL_GET_STATS` 获取操作统计 -- MSH 命令:`test_dev_demo` 演示完整生命周期;`test_dev_status` 查看当前统计 - -驱动注册流程为:创建/准备设备对象与私有数据 → 设置设备操作函数 → `rt_device_register(test_dev, "test_dev", RT_DEVICE_FLAG_RDWR)` → 应用通过 `rt_device_find("test_dev")` 使用设备。 - -本 BSP 未启用 `RT_USING_DEVICE_OPS`,因此源码同时兼容两种设备操作方式:启用该宏时使用 `rt_device_ops` 操作表;未启用时使用本 BSP 的传统设备成员函数指针。该处理保证同一驱动能够适配不同的 RT-Thread 配置。 - -## 4. 编译、烧录与验证 - -使用工程原生 SCons 构建后,生成的固件已通过 ST-Link 烧录至 STM32F407 开发板。烧录校验通过,串口为 COM21、115200 8N1。 - -实际串口验证: - -```text -msh >list_device -device type ref count --------- -------------------- ---------- -test_dev Character Device 0 -uart1 Character Device 2 -pin Pin Device 0 - -msh >test_dev_demo -[test_dev] initialized -[test_dev_demo] wrote=29 read=29 data="Day4 virtual device framework" -[test_dev_demo] open=1 close=0 read=1 write=1 control=2 length=29 - -msh >test_dev_status -[test_dev] open=1 close=1 read=1 write=1 control=3 length=29 -``` - -其中 `test_dev_demo` 在打印首次统计后执行关闭操作,因此随后执行 `test_dev_status` 时可看到 `close=1`。完整验证截图如下: - -![test_dev 串口验证](images/test_dev_terminal.png) - -## 5. 小结 - -PIN 驱动体现了 RT-Thread 通过操作表把通用 API 对接到 BSP/HAL 的方法;`test_dev` 则把同一思想扩展为可查找、可打开、可读写和可控制的虚拟字符设备。两者都说明:应用只面对统一接口,硬件差异由驱动框架层消化。 diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/images/test_dev_terminal.png" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/images/test_dev_terminal.png" deleted file mode 100644 index cd7b8b521d89c9bfb073a7deb085dcfbd8a1c757..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/images/test_dev_terminal.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" deleted file mode 100644 index 8abfdb18dd118da05180e2836e95679ebffcc33c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Day4 作业:虚拟字符设备驱动框架 test_dev - * - * 设备管理层:rt_device_register("test_dev") - * 应用访问层:rt_device_find/open/read/write/control/close - * 驱动实现层:test_dev_ops 中的回调函数 - */ - -#include -#include - -#define TEST_DEV_NAME "test_dev" -#define TEST_DEV_BUFFER_SIZE 128 - -/* 自定义 control 命令,避开 RT-Thread 已定义的通用命令范围。 */ -#define TEST_DEV_CTRL_CLEAR 0x90 -#define TEST_DEV_CTRL_GET_STATS 0x91 - -struct test_dev_stats -{ - rt_uint32_t open_count; - rt_uint32_t close_count; - rt_uint32_t read_count; - rt_uint32_t write_count; - rt_uint32_t control_count; - rt_size_t data_length; -}; - -struct test_dev_data -{ - char buffer[TEST_DEV_BUFFER_SIZE]; - rt_size_t length; - struct test_dev_stats stats; -}; - -static rt_device_t test_dev = RT_NULL; -static rt_mutex_t test_dev_lock = RT_NULL; -static struct test_dev_data test_dev_data; - -static rt_err_t test_dev_take_lock(void) -{ - if (test_dev_lock == RT_NULL) - { - return -RT_ERROR; - } - - return rt_mutex_take(test_dev_lock, RT_WAITING_FOREVER); -} - -static rt_err_t test_dev_init(rt_device_t dev) -{ - struct test_dev_data *data = (struct test_dev_data *)dev->user_data; - - if (data == RT_NULL) - { - return -RT_ERROR; - } - - if (test_dev_take_lock() != RT_EOK) - { - return -RT_ERROR; - } - rt_memset(data->buffer, 0, sizeof(data->buffer)); - data->length = 0; - rt_memset(&data->stats, 0, sizeof(data->stats)); - rt_mutex_release(test_dev_lock); - - rt_kprintf("[test_dev] initialized\n"); - return RT_EOK; -} - -static rt_err_t test_dev_open(rt_device_t dev, rt_uint16_t oflag) -{ - struct test_dev_data *data = (struct test_dev_data *)dev->user_data; - - RT_UNUSED(oflag); - if ((data == RT_NULL) || (test_dev_take_lock() != RT_EOK)) - { - return -RT_ERROR; - } - data->stats.open_count++; - rt_mutex_release(test_dev_lock); - return RT_EOK; -} - -static rt_err_t test_dev_close(rt_device_t dev) -{ - struct test_dev_data *data = (struct test_dev_data *)dev->user_data; - - if ((data == RT_NULL) || (test_dev_take_lock() != RT_EOK)) - { - return -RT_ERROR; - } - data->stats.close_count++; - rt_mutex_release(test_dev_lock); - return RT_EOK; -} - -static rt_size_t test_dev_read(rt_device_t dev, rt_off_t pos, - void *buffer, rt_size_t size) -{ - struct test_dev_data *data = (struct test_dev_data *)dev->user_data; - rt_size_t copy_size; - - if ((data == RT_NULL) || (buffer == RT_NULL) || (pos < 0) || - (test_dev_take_lock() != RT_EOK)) - { - return 0; - } - - if ((rt_size_t)pos >= data->length) - { - rt_mutex_release(test_dev_lock); - return 0; - } - - copy_size = data->length - (rt_size_t)pos; - if (copy_size > size) - { - copy_size = size; - } - rt_memcpy(buffer, &data->buffer[pos], copy_size); - data->stats.read_count++; - rt_mutex_release(test_dev_lock); - - return copy_size; -} - -static rt_size_t test_dev_write(rt_device_t dev, rt_off_t pos, - const void *buffer, rt_size_t size) -{ - struct test_dev_data *data = (struct test_dev_data *)dev->user_data; - rt_size_t copy_size; - - if ((data == RT_NULL) || (buffer == RT_NULL) || (pos < 0) || - (test_dev_take_lock() != RT_EOK)) - { - return 0; - } - - if ((rt_size_t)pos >= TEST_DEV_BUFFER_SIZE) - { - rt_mutex_release(test_dev_lock); - return 0; - } - - copy_size = TEST_DEV_BUFFER_SIZE - (rt_size_t)pos; - if (copy_size > size) - { - copy_size = size; - } - - rt_memcpy(&data->buffer[pos], buffer, copy_size); - if (data->length < ((rt_size_t)pos + copy_size)) - { - data->length = (rt_size_t)pos + copy_size; - } - data->stats.write_count++; - data->stats.data_length = data->length; - rt_mutex_release(test_dev_lock); - - return copy_size; -} - -static rt_err_t test_dev_control(rt_device_t dev, int cmd, void *args) -{ - struct test_dev_data *data = (struct test_dev_data *)dev->user_data; - rt_err_t result = RT_EOK; - - if ((data == RT_NULL) || (test_dev_take_lock() != RT_EOK)) - { - return -RT_ERROR; - } - - data->stats.control_count++; - switch (cmd) - { - case TEST_DEV_CTRL_CLEAR: - rt_memset(data->buffer, 0, sizeof(data->buffer)); - data->length = 0; - data->stats.data_length = 0; - break; - - case TEST_DEV_CTRL_GET_STATS: - if (args == RT_NULL) - { - result = -RT_EINVAL; - } - else - { - data->stats.data_length = data->length; - rt_memcpy(args, &data->stats, sizeof(data->stats)); - } - break; - - default: - result = -RT_EINVAL; - break; - } - rt_mutex_release(test_dev_lock); - - return result; -} - -#ifdef RT_USING_DEVICE_OPS -static const struct rt_device_ops test_dev_ops = -{ - test_dev_init, - test_dev_open, - test_dev_close, - test_dev_read, - test_dev_write, - test_dev_control, -}; -#endif - -static int test_dev_register(void) -{ - rt_err_t result; - - if (rt_device_find(TEST_DEV_NAME) != RT_NULL) - { - rt_kprintf("[test_dev] device already registered\n"); - return RT_EOK; - } - - test_dev_lock = rt_mutex_create("td_lock", RT_IPC_FLAG_PRIO); - if (test_dev_lock == RT_NULL) - { - rt_kprintf("[test_dev] create mutex failed\n"); - return -RT_ENOMEM; - } - - test_dev = rt_device_create(RT_Device_Class_Char, 0); - if (test_dev == RT_NULL) - { - rt_kprintf("[test_dev] create device failed\n"); - rt_mutex_delete(test_dev_lock); - test_dev_lock = RT_NULL; - return -RT_ENOMEM; - } - -#ifdef RT_USING_DEVICE_OPS - /* 新版框架通过 ops 表把统一 API 分派到具体驱动。 */ - test_dev->ops = &test_dev_ops; -#else - /* 当前 BSP 未启用 RT_USING_DEVICE_OPS,使用同等语义的兼容成员。 */ - test_dev->init = test_dev_init; - test_dev->open = test_dev_open; - test_dev->close = test_dev_close; - test_dev->read = test_dev_read; - test_dev->write = test_dev_write; - test_dev->control = test_dev_control; -#endif - test_dev->user_data = &test_dev_data; - result = rt_device_register(test_dev, TEST_DEV_NAME, RT_DEVICE_FLAG_RDWR); - if (result != RT_EOK) - { - rt_kprintf("[test_dev] register failed: %d\n", result); - rt_free(test_dev); - test_dev = RT_NULL; - rt_mutex_delete(test_dev_lock); - test_dev_lock = RT_NULL; - return result; - } - - rt_kprintf("[test_dev] registered as character device\n"); - return RT_EOK; -} -INIT_APP_EXPORT(test_dev_register); - -static int test_dev_demo(int argc, char **argv) -{ - rt_device_t dev; - const char *message = "Day4 virtual device framework"; - char read_buffer[TEST_DEV_BUFFER_SIZE] = {0}; - struct test_dev_stats stats; - rt_size_t written; - rt_size_t read; - rt_err_t result; - - RT_UNUSED(argc); - RT_UNUSED(argv); - - dev = rt_device_find(TEST_DEV_NAME); - if (dev == RT_NULL) - { - rt_kprintf("test_dev not found\n"); - return -RT_ERROR; - } - - result = rt_device_open(dev, RT_DEVICE_OFLAG_RDWR); - if (result != RT_EOK) - { - rt_kprintf("test_dev open failed: %d\n", result); - return result; - } - - result = rt_device_control(dev, TEST_DEV_CTRL_CLEAR, RT_NULL); - if (result != RT_EOK) - { - rt_kprintf("test_dev clear failed: %d\n", result); - rt_device_close(dev); - return result; - } - - written = rt_device_write(dev, 0, message, rt_strlen(message)); - read = rt_device_read(dev, 0, read_buffer, sizeof(read_buffer) - 1); - read_buffer[read] = '\0'; - - result = rt_device_control(dev, TEST_DEV_CTRL_GET_STATS, &stats); - rt_device_close(dev); - if (result != RT_EOK) - { - rt_kprintf("test_dev get stats failed: %d\n", result); - return result; - } - - rt_kprintf("[test_dev_demo] wrote=%u read=%u data=\"%s\"\n", - (unsigned int)written, (unsigned int)read, read_buffer); - rt_kprintf("[test_dev_demo] open=%lu close=%lu read=%lu write=%lu control=%lu length=%u\n", - (unsigned long)stats.open_count, - (unsigned long)stats.close_count, - (unsigned long)stats.read_count, - (unsigned long)stats.write_count, - (unsigned long)stats.control_count, - (unsigned int)stats.data_length); - return RT_EOK; -} -MSH_CMD_EXPORT(test_dev_demo, run virtual character device read write demo); - -static int test_dev_status(int argc, char **argv) -{ - rt_device_t dev; - struct test_dev_stats stats; - rt_err_t result; - - RT_UNUSED(argc); - RT_UNUSED(argv); - - dev = rt_device_find(TEST_DEV_NAME); - if (dev == RT_NULL) - { - rt_kprintf("test_dev not found\n"); - return -RT_ERROR; - } - - result = rt_device_control(dev, TEST_DEV_CTRL_GET_STATS, &stats); - if (result != RT_EOK) - { - return result; - } - rt_kprintf("[test_dev] open=%lu close=%lu read=%lu write=%lu control=%lu length=%u\n", - (unsigned long)stats.open_count, - (unsigned long)stats.close_count, - (unsigned long)stats.read_count, - (unsigned long)stats.write_count, - (unsigned long)stats.control_count, - (unsigned int)stats.data_length); - return RT_EOK; -} -MSH_CMD_EXPORT(test_dev_status, show virtual device statistics); diff --git "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/\350\256\272\345\235\233\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/\350\256\272\345\235\233\347\254\224\350\256\260.md" deleted file mode 100644 index af86c9e9b5cddaf128f1c163c0a65ee874b507d5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\346\235\250\346\231\272\346\226\207/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/\350\256\272\345\235\233\347\254\224\350\256\260.md" +++ /dev/null @@ -1,45 +0,0 @@ -# Day4 学习笔记:RT-Thread 的设备驱动框架与 PIN 设备对接 - -今天的重点是理解 RT-Thread 为什么能用一套统一接口管理不同硬件,以及 PIN 设备如何从应用层一路对接到 STM32 的 GPIO 硬件。 - -## 统一设备模型 - -RT-Thread 把外设抽象为 `rt_device`。无论面对的是串口、GPIO、SPI 还是自定义字符设备,应用层都可以按下面的通用流程使用: - -```text -find → open → read/write/control → close -``` - -应用使用 `rt_device_find()` 按名称找到设备,设备管理层再经由设备操作函数指针调用具体驱动。这样上层不直接依赖芯片寄存器或 HAL,底层替换时也不会影响应用层调用方式。 - -## PIN 设备的对接路径 - -在本次 STM32F407 工程中,PIN 设备由 BSP 的 `rt_hw_pin_init()` 初始化。该函数准备 STM32 GPIO 的操作函数集合 `_stm32_pin_ops`,再调用: - -```c -rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL); -``` - -因此,应用调用 `rt_pin_mode()`、`rt_pin_write()`、`rt_pin_read()` 时,会先进入 RT-Thread 通用 PIN 框架;框架从 `pin` 设备保存的操作表中取出对应函数,继而调用 BSP 的 `stm32_pin_mode()`、`stm32_pin_write()` 或 `stm32_pin_read()`;最后由 `HAL_GPIO_Init()`、`HAL_GPIO_WritePin()`、`HAL_GPIO_ReadPin()` 操作实际硬件。 - -中断的路径也是相同的思路:应用用 `rt_pin_attach_irq()` 注册回调,用 `rt_pin_irq_enable()` 开启中断;底层负责把引脚映射到 EXTI,并在中断服务程序中回调应用函数。 - -我理解 PIN 设备的核心价值在于:应用只关心“哪个逻辑引脚、要输入还是输出、读到什么值”,而端口时钟、GPIO 端口和 EXTI 这些芯片差异由 BSP 驱动负责。 - -## 自己实现虚拟设备 `test_dev` - -为了练习设备框架,我设计了一个虚拟字符设备 `test_dev`。它不连接实体外设,而是在 RAM 中维护 128 字节缓冲区和读写统计信息,仍按标准设备模型提供 `init/open/close/read/write/control` 操作。 - -设备提供两个控制命令:清空缓冲区、读取统计;并提供 MSH 命令 `test_dev_demo` 与 `test_dev_status`。实际验证中,`list_device` 已显示 `test_dev`,演示命令成功写入并读回: - -```text -Day4 virtual device framework -``` - -这次练习也遇到一个很实际的问题:当前 BSP 没有启用 `RT_USING_DEVICE_OPS`。因此驱动源码同时处理了新版 `rt_device_ops` 操作表和传统设备函数指针两种配置,最终成功编译、烧录并在开发板串口上验证。 - -## 收获 - -设备框架并不是增加一层“形式”,而是把应用逻辑和硬件实现解耦:PIN 驱动用操作表衔接通用接口与 HAL;`test_dev` 则证明即便是自定义设备,也可以纳入同一套查找、打开、读写、控制和关闭的生命周期。 - -本次作业源码、完整课堂笔记与串口验证截图已随 Day4 PR 提交。 diff --git "a/2026/\347\254\2543\347\273\204/\346\236\227\347\221\236\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2543\347\273\204/\346\236\227\347\221\236\346\272\220/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2543\347\273\204/\346\236\227\347\221\236\346\272\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\346\236\227\347\221\236\346\272\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 64f9eef3f0635fb3f7271adaba3e65dff1537086..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\346\236\227\347\221\236\346\272\220/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,79 +0,0 @@ -# 第一天笔记 - Git基础操作与开源协作流程 - -> 培训主题:Git基础操作与开源协作流程培训 -> 发言人:RT-Thread 王玉强 - ---- - -## 一、Git基础环境配置与核心指令 - -### 1. 环境初始化与身份配置 - -- **Git安装与汉化**:安装Git后可尝试配置中文语言包,汉化失败则使用英文版本。 -- **全局身份设置**:配置全局用户名和邮箱,确保提交记录的身份标识正确。 - ```bash - git config --global user.name "你的名字" - git config --global user.email "你的邮箱" - ``` -- **SSH密钥配置**:生成SSH密钥对(公钥与私钥),将公钥添加到Git平台账户设置中,实现免密认证。 - ```bash - ssh-keygen -t rsa -C "你的邮箱" - ``` - -### 2. 核心操作指令 - -| 指令 | 作用 | -|------|------| -| `git init` | 初始化本地仓库 | -| `git clone ` | 克隆远端仓库到本地 | -| `git pull` | 拉取远端最新代码 | -| `git add ` | 将文件加入暂存区 | -| `git commit -m "消息"` | 提交变更到本地仓库 | -| `git push` | 推送本地提交到远端仓库 | -| `git checkout -b <分支名>` | 创建并切换到新分支 | - -**要点**:日常开发应在独立分支进行,避免直接操作受保护的 master 分支。 - ---- - -## 二、开源协作流程与PR提交规范 - -### 1. Fork模式下的协作机制 - -- **仓库复刻(Fork)**:将上游仓库 Fork 到个人账户,形成独立的开发副本。 -- **分支策略**:在个人 Fork 仓库中创建功能分支(如 `day1`、`day2`)进行开发,严禁直接修改 master 分支。 -- **变更同步**:添加上游仓库地址并同步最新代码,避免冲突。 - ```bash - git remote add upstream <上游仓库地址> - git fetch upstream - git pull upstream master - ``` - -### 2. Pull Request(PR)提交与审核 - -- **PR创建流程**:本地修改 → 推送至个人 Fork → 通过平台界面创建 PR,将功能分支合并至上游仓库。 -- **审核流程**:提交 PR 时需指定对应的组长作为审查人员(Reviewer),审查通过后方可合并。 -- **冲突解决**:当本地分支落后于上游分支时会产生冲突,通过合并上游代码或手动解决冲突来修复。 - ---- - -## 三、作业提交与组织管理 - -### 1. 目录结构与命名规范 - -- **层级结构**:按「组别/学员姓名/日期」的层级创建目录,分别存放笔记和作业。 -- **分支命名**:使用 `day1`、`day2` 等命名分支,避免覆盖模板文件。 - -### 2. 审核职责分工 - -- **小组长**:拥有合并 PR 的权限,负责审核组员提交的代码结构、命名规范及内容质量。 -- **导师**:负责审核小组长的 PR,并处理跨组的复杂问题。 - ---- - -## 四、待办事项 - -- [ ] 完成Git环境配置与SSH密钥设置 -- [ ] 提交至少2个PR(初始化模板提交 + 当天作业/笔记提交) -- [ ] 小组长审核并合并组员的PR -- [ ] 小组长提交用户名给讲师以获取仓库管理权限 diff --git "a/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\344\275\234\344\270\232/image.png" "b/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\344\275\234\344\270\232/image.png" deleted file mode 100644 index 72e72e4b0e8f1cf2bf061a5a06b79c2c08a448e1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\344\275\234\344\270\232/image.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 8863f78b17f1b24c2065dc96b5974d5474081ebf..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,3 +0,0 @@ -# 初始化模板 -首先基于芯片创建项目,然后编译下载。***(注意:在设备管理器看好串口名称)*** -![alt text](image.png) \ No newline at end of file diff --git "a/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\347\254\224\350\256\260/image-1.png" "b/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\347\254\224\350\256\260/image-1.png" deleted file mode 100644 index e8de965e9fdd54b9623ead7dbd9c82e1abb1db81..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\347\254\224\350\256\260/image-1.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\347\254\224\350\256\260/image.png" "b/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\347\254\224\350\256\260/image.png" deleted file mode 100644 index 72e72e4b0e8f1cf2bf061a5a06b79c2c08a448e1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\347\254\224\350\256\260/image.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 00226e9ee938834dc46626077faea76628d29fd5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\347\216\213\345\256\217\346\211\254/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,18 +0,0 @@ -# 1.makedown基本语法: - - -# 初始化模板 -首先基于芯片创建项目,然后编译下载。***(注意:在设备管理器看好串口名称)*** -![alt text](image.png) - -# git -1.注册gitee -2.新建仓库 -3.克隆 -4.配置用户名和邮箱 -5.配置密钥 -6.上传公匙至gitee -7.尝试测试本地代码上传 -![alt text](image-1.png) - -8.test分支现在是修改后的,push后切换到test分支,然后pr diff --git "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\224\350\256\260/day1_learning_notes.md" "b/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\224\350\256\260/day1_learning_notes.md" deleted file mode 100644 index 751594995f38e15b4bdd165374a31d202ef6529f..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\224\350\256\260/day1_learning_notes.md" +++ /dev/null @@ -1,173 +0,0 @@ -## 【RSOC26】第一天学习笔记 -记录人:许伟威 -日期:`2026.08.17` - ---- - -## 一、环境搭建 - -### 1.1 软件安装 - -| 软件 | 版本/说明 | 用途 | -|------|-----------|------| -| RT-Thread Studio | 2.3.0+(最新 2.3.6) | 一站式 IDE,集成工具链/Env | -| RT-Thread 标准版源码 | 4.0.3(默认)或 4.1.1 | 内核源码 | -| STM32F4 芯片支持包 | 通过 SDK Manager 下载 | 芯片 BSP | -| Git | 最新版 | 版本控制 | -| TortoiseGit | 可选 | 图形化 Git 辅助工具 | -| Typora | + 破解补丁 | Markdown 编辑器 | -| STLink 驱动/升级工具 | STLinkUpgrade | 板卡调试下载 | -| 串口调试助手 | 可选(正点原子等) | 串口终端 | - -### 1.2 SDK Manager 配置 - -1. 打开 RT-Thread Studio → **SDK Manager** -2. 厂商过滤选择 **STM32** -3. 下载 **RT-Thread Source Code 4.0.3**(或 4.1.1) -4. 下载 **STM32F4 Chip Support Package** - -![SDK Manager 配置](./figures/sdk_manager.png) - -### 1.3 硬件连接 - -- 星火1号 Type-C 接 **DEBUG 口** → 按蓝色电源键 -- 设备管理器确认出现 **STLink Debug**(通用串行总线设备) -- 若 STLink 版本过旧 → 用 `STLinkUpgrade` 升级固件 -- 串口波特率:**115200** - -![设备管理器中的 STLink](./figures/stlink_device.png) - ---- - -## 二、工程创建与验证 - -### 2.1 QEMU 仿真工程(第一个任务) - -1. `文件 → 新建 → RT-Thread 项目 → 基于开发板 → QEMU VExpress-A9` -2. 编译 → 调试/下载 → 终端输出 `hello rt-thread` - -```c -/* 终端输出 */ -hello rt-thread -``` - -### 2.2 星火1号实体板工程(第三个任务) - -| 配置项 | 值 | -|--------|-----| -| 基于 | 芯片 | -| 厂商 | STM32 | -| 系列 | F4 | -| 芯片 | STM32F407VG | -| 内核版本 | 4.0.3(统一) | -| 控制台串口 | 默认 UART | -| 调试器 | STLink | - -在 `main.c` 第一行添加: - -```c -rt_kprintf("Hello SOC 2026!\n"); -``` - -验证步骤:编译 → 下载 → 串口终端(115200)→ 看到 RT-Thread Logo + `Hello SOC 2026!`。 - -- 输入 `Tab` 查看 FinSH 命令列表 -- 输入 `ps` 查看线程 - ---- - -## 三、Markdown 语法 - -### 3.1 常用语法速查 - -![image-20260817234608310](../../../../../../../AppData/Roaming/Typora/typora-user-images/image-20260817234608310.png) - ---- - -## 四、Git/Gitee 操作 - -### 4.1 账户与密钥配置 - -```bash -git config --global user.name "你的名字(英文)" -git config --global user.email "你的Gitee注册邮箱" - -ssh-keygen -t rsa -C "你的邮箱" # 一路回车 -cat ~/.ssh/id_rsa.pub # 复制公钥 -``` - -> 将公钥粘贴到 Gitee:右上角头像 → 设置 → SSH 公钥 → 粘贴 → 确定 - -```bash -ssh -T git@gitee.com # 看到 successfully 即成功 -``` - -> ⚠️ **注意**:笔记截图时给公钥打码,**不要上传私钥**。 - -### 4.2 常用命令 - -| 命令 | 作用 | -|------|------| -| `git init` | 初始化仓库 | -| `git clone ` | 克隆远程仓库 | -| `git pull origin master` | 拉取最新代码 | -| `git add ` / `git add .` | 添加到暂存区 | -| `git commit -m "message"` | 提交到本地仓库 | -| `git push origin ` | 推送到远程分支 | -| `git checkout -b ` | 新建并切换分支 | -| `git switch ` | 切换分支 | -| `git remote set-url origin ` | 修改远程地址 | -| `git remote -v` | 查看远程信息 | - ---- - -![da39e2388c9b5e77f4e951e7f88fd5ad](E:/微信聊天存储/xwechat_files/wxid_ds3zka5of66g22_fa36/temp/RWTemp/2026-08/9e20f478899dc29eb19741386f9343c8/da39e2388c9b5e77f4e951e7f88fd5ad.png) - -**git配置** - -![ad9521e82a38e867704822a9c2d47c77](E:/微信聊天存储/xwechat_files/wxid_ds3zka5of66g22_fa36/temp/RWTemp/2026-08/9e20f478899dc29eb19741386f9343c8/ad9521e82a38e867704822a9c2d47c77.png) - -上面是gitee测试连接 - - - -## 五、PR 提交流程 - -### 5.1 完整流程 - -1. 在 Gitee 网页 **Fork** 上游夏令营仓库 -2. `git clone` 自己的 Fork 到本地 -3. `git checkout -b day1`(新建分支,**不要用 master 直接提交**) -4. 在对应组别目录下新建个人目录,放入笔记和作业 -5. `git add .` → `git commit -m "docs: init day1 template"` → `git push origin day1` -6. Gitee 网页 → 新建 Pull Request → 源分支 `day1` → 目标分支 `master`(上游仓库) -7. 审查人员选择:组员选小组长,小组长选导师 -8. 审查通过后合并(建议勾选"合并后删除提交分支") -9. 合并后切回 master → `git pull` 同步最新 - -### 5.2 冲突解决 - -冲突原因:Fork 时主仓库已更新,分支基于旧版本。 - -```bash -# Fork 页面点「同步」 -git switch master -git pull -git switch day1 -git merge master # 手动解决冲突 -git push origin day1 # 重新 push -``` - ---- - -## 八、常见问题与避坑 - -| 坑点 | 解决方案 | -|:-----|----------| -| Studio 找不到 SDK Manager | 重新安装或下载 4.1.1 版本源码 | -| STLink 无法识别 | 升级固件(STLinkUpgrade)或重装驱动 | -| 串口终端无输出 | 确认端口号 + 波特率 115200 + 板子已通 | -| PR 冲突 | 先同步上游 → pull 最新 → merge → 解决冲突 → 重新 push | -| 分支名重复 | 每次用不同名(day1、day1-v2、day2...) | -| 直接提交到 master | 必须新建分支再提 PR,master 受保护 | - diff --git "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index f2a8730add3b5afff8d6a91fd6e41a67c5df8482..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,230 +0,0 @@ -# 【DAY3】RT-Thread 线程间同步与通信课后作业 - -> 目标平台:STM32F407 星火号(`stm32f407-rtspark`) -> 提交仓库:https://gitee.com/rtthread/rsoc-rtt -> 提交人:`许伟威` · 第3组 - ---- - -## 一、IPC 机制理论总结 - -多线程共享同一块内存和处理器,多个线程同时访问共享资源会产生竞争,需要一套机制在线程之间做同步和通信。RT-Thread 提供了信号量、互斥量、事件集、邮箱、消息队列五种 IPC 机制。 - -### 1.1 基本概念 - -- **临界区**:同一时间只允许一个线程访问的共享资源(或代码段)。多个线程同时进入临界区会破坏数据。 -- **阻塞与非阻塞**:线程申请资源时,资源不可用可以选择阻塞(挂起等待)或非阻塞(立即返回失败)。 -- **线程挂起**:线程阻塞时会从就绪队列移出、不再参与调度,等条件满足后再回到就绪态。 - -### 1.2 信号量(Semaphore) - -信号量是一个计数值,用来管理资源的数量。 - -| 类型 | 初始值 | 用途 | -|------|--------|------| -| 二值信号量 | 通常为 0 | 线程间同步(一个事件的发生) | -| 计数信号量 | 资源数量 | 管理多个同类资源实例 | - -主要 API: - -```c -rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag); -rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time); /* 获取,value-- */ -rt_err_t rt_sem_release(rt_sem_t sem); /* 释放,value++ */ -``` - -`rt_sem_init` 是静态版本,线程控制块和信号量对象由用户提供。`take` 的 `flag` 参数决定等待队列的排队方式,实时性要求高的场景优先选 `RT_IPC_FLAG_PRIO`(按优先级排队)而不是 `RT_IPC_FLAG_FIFO`。 - -### 1.3 互斥量(Mutex) - -互斥量是特殊的二值信号量,但有两个关键区别: - -1. **所有权**:互斥量只能由持有它的线程释放,信号量可以被任意线程释放。 -2. **解决优先级反转**:通过**优先级继承协议**,当高优先级线程等待互斥量时,临时提升持有互斥量线程的优先级,阻止中优先级线程抢占,避免高优先级线程被无限期拖延。 - -互斥量还支持递归访问(同一线程可多次 take)。 - -```c -rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag); -rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time); -rt_err_t rt_mutex_release(rt_mutex_t mutex); -``` - -### 1.4 事件集(Event) - -事件集用 32 位无符号整数表示 32 个事件标志位,支持一对多、多对多同步。 - -```c -rt_event_t rt_event_create(const char *name, rt_uint8_t flag); -rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set); /* 置位 */ -rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, - rt_uint8_t option, rt_int32_t timeout, - rt_uint32_t *recved); /* 等待 */ -``` - -`option` 关键标志: - -| 标志 | 含义 | -|------|------| -| `RT_EVENT_FLAG_AND` | 逻辑与,等待所有指定位都置位 | -| `RT_EVENT_FLAG_OR` | 逻辑或,任一位置位即满足 | -| `RT_EVENT_FLAG_CLEAR` | 满足后清除这些标志位 | - -### 1.5 消息邮箱(Mailbox) - -邮箱传递固定 4 字节(32 位系统指针大小)的消息,适合传指针或整型。 - -- 发送通常**非阻塞**,适合在中断里给线程发消息。 -- 接收通常**阻塞**,可设超时。 - -### 1.6 消息队列(Message Queue) - -消息队列是邮箱的扩展,支持**变长消息**,内部用链表管理消息块。 - -```c -rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, - rt_size_t max_msgs, rt_uint8_t flag); -rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size); -rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout); -``` - -`rt_mq_urgent` 可把消息插到队列头部,实现紧急消息优先处理。 - ---- - -## 二、练习一:停车场车位管理 - -### 2.1 实现思路 - -用初始值为 3 的计数信号量表示剩余车位。5 辆车各自尝试 `rt_sem_take`,拿到的进入、拿不到的等待;车辆停留一段时间后 `rt_sem_release` 归还车位。`take` 设置 2 秒超时,超时打印等待信息后重新申请。 - -### 2.2 运行现象 - -```text -car1 entered, used spaces: 1 -car2 entered, used spaces: 2 -car3 entered, used spaces: 3 -car4 waiting for a space -car5 waiting for a space -car1 left -car4 entered, used spaces: 3 -car2 left -car5 entered, used spaces: 3 -car3 left -``` - -车 1、2、3 先进入占满 3 个车位,车 4、5 因 2 秒内没拿到车位打印等待信息;车离开释放车位后,等待的车重新申请成功。整个过程中在场车辆始终不超过 3 辆。 - -### 2.3 思考题 - -**为什么用计数信号量而不是互斥量?** - -互斥量本质是取值只有 0/1 的信号量,只能表示"一个资源",而且带所有权属性,适合保护临界区。停车场有 3 个车位,是"多个同类资源实例",需要用计数信号量(初始值 3)来统计剩余数量:每进入一辆 `value--`,每离开一辆 `value++`,天然保证了同时在场车辆不超过 3。互斥量无法表示数量 3,也谈不上"剩余车位"。 - ---- - -## 三、练习二:多窗口售票系统 - -### 3.1 实现思路 - -两个窗口线程循环"检查余票 → 减 1 → 记录票号 → 打印",用互斥量把整个读-改-写过程包起来。票号从 20 递减,售完(余票为 0)后两个线程自动退出。 - -### 3.2 运行现象 - -```text -window A sold ticket 20 -window B sold ticket 19 -window B sold ticket 18 -window A sold ticket 17 -... -window A sold ticket 1 -all tickets sold, remaining tickets: 0 -``` - -### 3.3 对比实验 - -把 `USE_MUTEX` 改成 0,并在"读余票"和"改余票"之间加入 `rt_thread_mdelay(5)`,两个窗口会同时读到同一个余票值,出现重复票号,最终余票可能为负: - -```text -window A sold ticket 20 -window B sold ticket 20 ← 重复票号 -window A sold ticket 18 -window B sold ticket 18 ← 重复票号 -... -``` - -加回互斥量后运行恢复正常,20 张票全部卖出、无重复票号、余票为 0。 - -### 3.4 思考题 - -**为什么只给 `ticket_count--` 加锁仍可能不安全?** - -售票的完整操作是"读余票 → 判断是否 >0 → 减 1",这是一个读-改-写序列。如果只给减操作加锁,读和判断仍在线程间交错:两个窗口可能同时读到余票 20,都判断有票,然后先后执行 `20 - 1 = 19`,结果只卖出一张却记了两张、还产生重复票号。锁必须覆盖"读-判断-改"的整个过程,让这个序列原子化,临界区才是安全的。 - ---- - -## 四、练习三:系统启动条件检查 - -### 4.1 实现思路 - -定义三个事件位表示三个模块就绪,三个初始化线程各自延时后 `rt_event_send` 置位;主业务线程用 `RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR` 等待三个位全部置位。存储模块初始化 5 秒、主业务最多等 3 秒,超时后通过读取事件集当前值判断哪些模块没就绪,再继续等待。 - -### 4.2 运行现象 - -```text -network ready -sensor ready -wait modules timeout, storage is not ready -storage ready -all modules are ready -business task started -``` - -### 4.3 思考题 - -**用三个独立信号量实现,和事件集相比有什么区别?** - -事件集用一个 32 位值统一管理多个事件,能一次性等待"全部就绪"(AND)或"任一就绪"(OR),还能在满足后一次性清除多个标志。三个独立信号量只能一个个 `take`,无法原子地判断"三个都好了",而且信号量计数会被消费、需要额外逻辑恢复初始值,逻辑更绕、可读性差。多事件组合同步场景下事件集更合适。 - ---- - -## 五、练习四:快递分拣中心 - -### 5.1 实现思路 - -创建容量 5 的消息队列,两个发件线程各发 5 条快递(编号用 1001~1005、2001~2005 两个区间避免重复)。发件前检查队列是否已满,满了就记一次失败并稍后重试,不崩溃。每个发件线程发完后再发一条 `id=0` 的结束消息(`weight` 字段带上自己的失败次数),分拣线程收到 2 条结束消息后统计并退出。 - -### 5.2 运行现象 - -```text -package 1001 -> east, weight: 500 g -package 2001 -> east, weight: 500 g -package 1002 -> south, weight: 637 g -... -total packages: 10 -east: 4, south: 4, north: 2 -total weight: 7552 g -send failed: 6 -``` - -分拣线程每条耗时 30ms、发件线程每条间隔 10ms,发件快于分拣,队列会短时占满,`send failed` 记录了队列满时发件线程重试的次数,最终 10 条快递全部被正确分拣统计。 - -### 5.3 结果说明 - -- 快递总数 = 10,与两个线程各发 5 条一致。 -- 各区域计数之和 = 总数,无丢件。 -- 总重量为 10 条快递重量之和。 -- `send failed > 0` 说明队列满时发件线程没有崩溃,而是记录失败后重试。 - ---- - -## 六、MSH 命令说明 - -| 命令 | 对应练习 | 说明 | -|------|----------|------| -| `parking_demo` | 练习一 | 计数信号量,停车场 | -| `ticket_demo` | 练习二 | 互斥量,售票 | -| `startup_demo` | 练习三 | 事件集,启动检查 | -| `package_demo` | 练习四 | 消息队列,分拣 | - -四个 demo 都用全局对象指针是否为空判断任务是否已启动,重复执行命令时直接提示"already running"并返回,不会因同名线程或 IPC 对象残留而异常。 diff --git "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" "b/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" deleted file mode 100644 index e6ce3c4e70e0b627fbce3a0a93f35a2f69a52d4c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" +++ /dev/null @@ -1,150 +0,0 @@ -/* - * package_demo.c - 练习四:快递分拣中心(消息队列) - * - * 场景:两个发件线程不断产生快递,一个分拣线程从消息队列取出快递并按 - * 区域分类统计。队列满时发送线程不崩溃,记录发送失败次数。 - * - * 消息结构:id 唯一编号(发件线程1 用 1001~1005,线程2 用 2001~2005); - * region 1=华东 2=华南 3=华北;weight 重量。 - * 结束机制:每个发件线程发完 5 条后发一条 id=0 的结束消息,weight 字段 - * 带上本线程的发送失败次数,分拣线程收到 2 条结束消息后统计退出。 - * - * 用法:MSH 中输入 package_demo 启动。 - */ -#include - -#define PKG_QUEUE_SIZE 5 -#define PKG_PER_SENDER 5 - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -static rt_mq_t pkg_mq = RT_NULL; - -static void sender_entry(void *param) -{ - int sender = (int)(rt_ubase_t)param; - int base = (sender == 1) ? 1001 : 2001; - int i; - rt_uint16_t failed = 0; - - for (i = 0; i < PKG_PER_SENDER; i++) - { - struct package_msg msg; - - msg.id = (rt_uint32_t)(base + i); - msg.region = (rt_uint8_t)((i % 3) + 1); - msg.weight = (rt_uint16_t)(500 + (i * 137) % 1500); - - while (1) - { - if (pkg_mq->entry >= pkg_mq->max_msgs) - { - failed++; - rt_thread_mdelay(20); - } - else - { - rt_mq_send(pkg_mq, &msg, sizeof(msg)); - break; - } - } - rt_thread_mdelay(10); - } - - { - struct package_msg end; - - end.id = 0; - end.region = 0; - end.weight = failed; - - while (pkg_mq->entry >= pkg_mq->max_msgs) - rt_thread_mdelay(20); - rt_mq_send(pkg_mq, &end, sizeof(end)); - } -} - -static void sort_entry(void *param) -{ - struct package_msg msg; - int end_count = 0; - int total = 0, east = 0, south = 0, north = 0; - rt_uint32_t total_weight = 0; - rt_uint32_t total_failed = 0; - - while (1) - { - if (rt_mq_recv(pkg_mq, &msg, sizeof(msg), RT_WAITING_FOREVER) != RT_EOK) - continue; - - if (msg.id == 0) - { - total_failed += msg.weight; - end_count++; - if (end_count >= 2) - break; - continue; - } - - total++; - total_weight += msg.weight; - - switch (msg.region) - { - case 1: - east++; - rt_kprintf("package %d -> east, weight: %d g\n", msg.id, msg.weight); - break; - case 2: - south++; - rt_kprintf("package %d -> south, weight: %d g\n", msg.id, msg.weight); - break; - case 3: - north++; - rt_kprintf("package %d -> north, weight: %d g\n", msg.id, msg.weight); - break; - default: - break; - } - - rt_thread_mdelay(30); - } - - rt_kprintf("total packages: %d\n", total); - rt_kprintf("east: %d, south: %d, north: %d\n", east, south, north); - rt_kprintf("total weight: %d g\n", total_weight); - rt_kprintf("send failed: %d\n", total_failed); -} - -static void package_demo(int argc, char **argv) -{ - rt_thread_t t; - - if (pkg_mq != RT_NULL) - { - rt_kprintf("package demo is already running\n"); - return; - } - - pkg_mq = rt_mq_create("pkgmq", sizeof(struct package_msg), PKG_QUEUE_SIZE, RT_IPC_FLAG_FIFO); - if (pkg_mq == RT_NULL) - { - rt_kprintf("create message queue failed\n"); - return; - } - - t = rt_thread_create("sender1", sender_entry, (void *)1, 1024, 20, 5); - if (t != RT_NULL) rt_thread_startup(t); - - t = rt_thread_create("sender2", sender_entry, (void *)2, 1024, 20, 5); - if (t != RT_NULL) rt_thread_startup(t); - - t = rt_thread_create("sorter", sort_entry, RT_NULL, 1024, 19, 5); - if (t != RT_NULL) rt_thread_startup(t); -} -MSH_CMD_EXPORT(package_demo, package sorting demo with message queue); diff --git "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" "b/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" deleted file mode 100644 index 5c6da4b75082db2d74528154c8c66176b5e74d4c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" +++ /dev/null @@ -1,74 +0,0 @@ -/* - * parking_demo.c - 练习一:停车场车位管理(计数信号量) - * - * 场景:停车场只有 3 个车位,同时有 5 辆车尝试进入。 - * 实现:初始值为 3 的计数信号量表示剩余车位,车辆 take 到车位才能进入, - * 停留一段时间后离开并 release 车位;2 秒内没拿到车位的车辆打印 - * 超时信息后重新申请。任意时刻在场车辆不超过 3 辆。 - * - * 用法:MSH 中输入 parking_demo 启动。 - */ -#include - -#define PARKING_SPACES 3 -#define CAR_COUNT 5 - -static rt_sem_t parking_sem = RT_NULL; - -static void car_entry(void *param) -{ - int car_id = (int)(rt_ubase_t)param; - rt_err_t ret; - - while (1) - { - ret = rt_sem_take(parking_sem, rt_tick_from_millisecond(2000)); - if (ret == RT_EOK) - { - rt_kprintf("car%d entered, used spaces: %d\n", - car_id, PARKING_SPACES - parking_sem->value); - rt_thread_mdelay(3000 + car_id * 500); - rt_sem_release(parking_sem); - rt_kprintf("car%d left\n", car_id); - break; - } - else - { - rt_kprintf("car%d waiting for a space\n", car_id); - } - } -} - -static void parking_demo(int argc, char **argv) -{ - int i; - - if (parking_sem != RT_NULL) - { - rt_kprintf("parking demo is already running\n"); - return; - } - - parking_sem = rt_sem_create("psem", PARKING_SPACES, RT_IPC_FLAG_PRIO); - if (parking_sem == RT_NULL) - { - rt_kprintf("create parking semaphore failed\n"); - return; - } - - for (i = 1; i <= CAR_COUNT; i++) - { - char name[RT_NAME_MAX]; - rt_thread_t tid; - - rt_snprintf(name, sizeof(name), "car%d", i); - tid = rt_thread_create(name, car_entry, (void *)(rt_ubase_t)i, 1024, 20, 5); - if (tid == RT_NULL) - { - rt_kprintf("create %s failed\n", name); - continue; - } - rt_thread_startup(tid); - } -} -MSH_CMD_EXPORT(parking_demo, parking lot demo with counting semaphore); diff --git "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" "b/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" deleted file mode 100644 index f6386813b9d51d59de846b3b128e62d7ceeb73f2..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" +++ /dev/null @@ -1,102 +0,0 @@ -/* - * startup_demo.c - 练习三:系统启动条件检查(事件集) - * - * 场景:设备启动前要完成网络、传感器、存储三个模块的初始化,全部就绪后 - * 主业务线程才能启动。主业务用 AND 模式等待三个事件位全部到达。 - * - * 超时测试:存储模块初始化 5 秒,主业务最多等 3 秒,超时打印未就绪模块 - * 并再次等待。 - * - * 用法:MSH 中输入 startup_demo 启动。 - */ -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -static rt_event_t startup_event = RT_NULL; - -static void net_init_entry(void *param) -{ - rt_thread_mdelay(1000); - rt_kprintf("network ready\n"); - rt_event_send(startup_event, EVENT_NET_READY); -} - -static void sensor_init_entry(void *param) -{ - rt_thread_mdelay(2000); - rt_kprintf("sensor ready\n"); - rt_event_send(startup_event, EVENT_SENSOR_READY); -} - -static void storage_init_entry(void *param) -{ - rt_thread_mdelay(5000); - rt_kprintf("storage ready\n"); - rt_event_send(startup_event, EVENT_STORAGE_READY); -} - -static void business_entry(void *param) -{ - rt_uint32_t recved = 0; - - while (1) - { - rt_err_t ret = rt_event_recv(startup_event, EVENT_ALL, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - rt_tick_from_millisecond(3000), &recved); - if (ret == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - break; - } - else - { - rt_uint32_t cur = startup_event->set; - - rt_kprintf("wait modules timeout"); - if (!(cur & EVENT_NET_READY)) - rt_kprintf(", network is not ready"); - if (!(cur & EVENT_SENSOR_READY)) - rt_kprintf(", sensor is not ready"); - if (!(cur & EVENT_STORAGE_READY)) - rt_kprintf(", storage is not ready"); - rt_kprintf("\n"); - } - } -} - -static void startup_demo(int argc, char **argv) -{ - rt_thread_t t; - - if (startup_event != RT_NULL) - { - rt_kprintf("startup demo is already running\n"); - return; - } - - startup_event = rt_event_create("sevent", RT_IPC_FLAG_PRIO); - if (startup_event == RT_NULL) - { - rt_kprintf("create event failed\n"); - return; - } - - t = rt_thread_create("netinit", net_init_entry, RT_NULL, 1024, 21, 5); - if (t != RT_NULL) rt_thread_startup(t); - - t = rt_thread_create("seninit", sensor_init_entry, RT_NULL, 1024, 21, 5); - if (t != RT_NULL) rt_thread_startup(t); - - t = rt_thread_create("storinit", storage_init_entry, RT_NULL, 1024, 21, 5); - if (t != RT_NULL) rt_thread_startup(t); - - t = rt_thread_create("business", business_entry, RT_NULL, 1024, 20, 5); - if (t != RT_NULL) rt_thread_startup(t); -} -MSH_CMD_EXPORT(startup_demo, startup condition check with event set); diff --git "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" "b/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" deleted file mode 100644 index 90d75211bd0a048cdba925fb227bc6ecdf004dac..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" +++ /dev/null @@ -1,98 +0,0 @@ -/* - * ticket_demo.c - 练习二:多窗口售票系统(互斥量) - * - * 场景:两个窗口同时出售同一批 20 张票,用互斥量保护"检查余票→减1"的 - * 完整过程,保证不重复售票、余票不为负。 - * - * 对比实验:把 USE_MUTEX 改成 0,会看到重复票号或余票错乱。 - * - * 用法:MSH 中输入 ticket_demo 启动。 - */ -#include - -#define TOTAL_TICKETS 20 - -/* 1 使用互斥量,0 不使用(用于对比实验,观察竞态) */ -#define USE_MUTEX 1 - -static rt_mutex_t ticket_mutex = RT_NULL; -static int remaining_tickets = TOTAL_TICKETS; - -static void window_entry(void *param) -{ - const char *name = (const char *)param; - - while (1) - { - int ticket_no = 0; - int sold = 0; - -#if USE_MUTEX - if (rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER) != RT_EOK) - continue; -#endif - - if (remaining_tickets > 0) - { - ticket_no = remaining_tickets; -#if !USE_MUTEX - rt_thread_mdelay(5); /* 人为放大读-改窗口,暴露竞态 */ -#endif - remaining_tickets--; - sold = 1; - } - -#if USE_MUTEX - rt_mutex_release(ticket_mutex); -#endif - - if (sold) - { - rt_kprintf("%s sold ticket %d\n", name, ticket_no); - if (remaining_tickets == 0) - { - rt_kprintf("all tickets sold, remaining tickets: 0\n"); - } - } - else - { - break; - } - - rt_thread_mdelay(10); - } -} - -static void ticket_demo(int argc, char **argv) -{ - rt_thread_t ta, tb; - - if (ticket_mutex != RT_NULL) - { - rt_kprintf("ticket demo is already running\n"); - return; - } - - remaining_tickets = TOTAL_TICKETS; - - ticket_mutex = rt_mutex_create("tmutex", RT_IPC_FLAG_PRIO); - if (ticket_mutex == RT_NULL) - { - rt_kprintf("create ticket mutex failed\n"); - return; - } - - ta = rt_thread_create("winA", window_entry, "window A", 1024, 20, 5); - tb = rt_thread_create("winB", window_entry, "window B", 1024, 20, 5); - - if (ta == RT_NULL) - rt_kprintf("create window A failed\n"); - else - rt_thread_startup(ta); - - if (tb == RT_NULL) - rt_kprintf("create window B failed\n"); - else - rt_thread_startup(tb); -} -MSH_CMD_EXPORT(ticket_demo, ticket sale demo with mutex); diff --git "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 39c675c39a0dc7862cd68ae9332aee5555e9b47c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,404 +0,0 @@ -# 【DAY2】RT-Thread 启动流程、内核基础与多线程编程 - -> 目标平台:STM32F407 星火号(`stm32f407-rtspark`) -> 提交仓库:https://gitee.com/rtthread/rsoc-rtt -> 作者:`许伟威` · 第3组 - ---- - -## Part 1:理论知识梳理 - -> **背景**:裸机开发采用"前后台"模式(`while(1)` 超级循环为后台,中断服务程序为前台),存在并发性差、实时性无法保证、可维护性差、可重用性差四大缺陷。RTOS 采用"分而治之"的思想,把复杂任务拆分成多个线程(任务),由内核统一调度,即 **多任务系统**,任务的实体就是**线程**。 - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -#### 1.1 完整启动调用链 - -从芯片上电复位到进入 `main()` 线程,主要经历以下阶段: - -``` -上电 / 复位 - │ - ▼ -Reset_Handler (startup_stm32f407xx.s 汇编,向量表第 2 项) - │ ① 初始化栈指针 MSP,加载 .data、清零 .bss - │ ② 调用 SystemInit() - ▼ -SystemInit() (system_stm32f4xx.c,配置系统时钟 PLL、Flash 等待周期) - │ - ▼ -entry() (rt-thread/src/components.c,GCC 链接时 -eentry 指定为入口) - │ - ▼ -rtthread_startup() (components.c,RT-Thread 内核启动总入口) - │ ③ rt_hw_interrupt_disable() 关全局中断 - │ ④ rt_hw_board_init() 板级初始化(时钟/外设/堆) - │ └── rt_components_init() 执行自动初始化(见 1.2) - │ ⑤ rt_show_version() 打印 RT-Thread Logo - │ ⑥ rt_system_timer_init() 系统定时器(心跳 tick)初始化 - │ ⑦ rt_system_scheduler_init() 调度器初始化 - │ ⑧ rt_application_init() 创建 main 线程("main") - │ ⑨ rt_system_timer_thread_init() 定时器服务线程 - │ ⑩ rt_thread_idle_init() 空闲线程 - │ ⑪ rt_system_scheduler_start() 启动调度器(不再返回) - ▼ -调度器选中最高优先级就绪线程 → main 线程获得 CPU → 进入 main() -``` - -关键点说明: - -| 阶段 | 作用 | -|------|------| -| `Reset_Handler` | 汇编入口,初始化栈、搬运数据段、清 BSS,为 C 运行环境做准备 | -| `SystemInit` | 配置系统时钟(HSE→PLL→SYSCLK 168MHz),配置 Flash 等待周期 | -| `entry` | RT-Thread 通过链接脚本 `-eentry` 把入口重定向到这里,屏蔽了 C 库的 `main` 包装 | -| `rtthread_startup` | 内核启动总入口,完成板级初始化、组件初始化、创建系统线程并启动调度器 | -| `rt_application_init` | 内部创建 `main` 线程(栈大小 `RT_MAIN_THREAD_STACK_SIZE`,优先级 `RT_MAIN_THREAD_PRIORITY`) | - -> RT-Thread Studio 使用 GCC 工具链,入口为 `entry()`;若在 Keil(ARMCC)下则通过 `$Sub$$main` 链接器符号包装,同样把启动流程劫持到 `rtthread_startup()`。 - -#### 1.2 自动初始化原理(INIT_BOARD_EXPORT / INIT_APP_EXPORT) - -RT-Thread 的自动初始化不依赖任何显式调用,而是借助**链接器 section 特性 + 函数指针数组**实现。 - -**(1) 宏定义(rtdef.h)** - -```c -/* 6 个初始化等级,用字符串 "1"~"6" 区分 section 名 */ -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") -#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2") -#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3") -#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4") -#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") - -/* INIT_EXPORT 把一个函数指针放到指定 section */ -#define INIT_EXPORT(fn, level) \ - RT_USED const init_fn_t __rt_init_##fn RT_SECTION(".rti_fn." level) = fn -``` - -其中 `RT_SECTION(x)` 展开为 `__attribute__((section(x)))`。 - -**(2) 底层原理** - -- 每个 `XXX_EXPORT(fn)` 都会在编译期生成一个**全局函数指针变量**,并把它的地址强制放到独立的 section 里,例如: - - `INIT_BOARD_EXPORT(rt_hw_pin_init)` → 放入 `.rti_fn.1` - - `INIT_APP_EXPORT(my_init)` → 放入 `.rti_fn.6` -- 链接脚本 `link.lds` 把这些 section 按**名称排序**后集中排布,并记录首尾地址: - -``` -__rt_init_rti_board_start = .; -KEEP(*(SORT(.rti_fn*))) /* SORT 按 .rti_fn.1 → .rti_fn.6 排序 */ -__rt_init_rti_board_end = .; -``` - -- 运行时 `rt_components_init()` 遍历这段地址空间,依次调用所有函数指针: - -```c -void rt_components_init(void) -{ - volatile const init_fn_t *fn_ptr; - for (fn_ptr = &__rt_init_rti_board_start; - fn_ptr < &__rt_init_rti_board_end; fn_ptr++) - { - (*fn_ptr)(); /* 依次执行各等级的初始化函数 */ - } -} -``` - -**(3) 执行顺序** - -| 等级 | 宏 | 典型用途 | 执行时机 | -|------|-----|----------|----------| -| 1 | `INIT_BOARD_EXPORT` | 板级/引脚驱动(`rt_hw_pin_init`) | 最先 | -| 2 | `INIT_PREV_EXPORT` | 早期组件 | | -| 3 | `INIT_DEVICE_EXPORT` | 设备驱动 | | -| 4 | `INIT_COMPONENT_EXPORT` | 组件(FinSH 等) | | -| 5 | `INIT_ENV_EXPORT` | 环境/文件系统挂载 | | -| 6 | `INIT_APP_EXPORT` | 应用程序模块 | 最后 | - -因为 `SORT(.rti_fn*)` 按字符串字典序排序,`.rti_fn.1` 必然排在 `.rti_fn.6` 之前,从而**保证初始化顺序确定**,且全程无需 `main()` 里手动调用。 - ---- - -### 2. 内核基础与对象模型 - -#### 2.1 面向对象设计思想 - -RT-Thread 内核采用 **C 语言模拟面向对象**的设计: - -- 所有内核对象(线程、信号量、互斥量、事件、邮箱、消息队列、定时器、内存池、设备等)都以一个**基类对象** `struct rt_object` 作为第一个成员: - -```c -struct rt_object -{ - char name[RT_NAME_MAX]; /* 对象名字 */ - rt_uint8_t type; /* 对象类型 */ - rt_uint8_t flag; /* 标志位 */ - rt_list_t list; /* 对象链表节点 */ -}; -``` - -- 通过"继承"(把 `rt_object` 内嵌为结构体首成员),所有对象拥有统一的名字、类型、链表能力。 -- 通过**对象容器**(`rt_object_container`)集中管理:按类型分成多个双向链表,实现按名字查找(`rt_object_find`)、按类型遍历、统一回收。 - -```c -struct rt_object_information rt_object_container[RT_Object_Info_Unknown] = -{ - {RT_Object_Class_Thread, _OBJ_CONTAINER_LIST_INIT(...), sizeof(struct rt_thread)}, - ... -}; -``` - -这种设计带来的好处:**统一命名空间、统一查找、统一统计**(`list_thread`、`list_sem` 等命令底层都靠它)。 - -#### 2.2 线程控制块关键成员(struct rt_thread) - -```c -struct rt_thread -{ - char name[RT_NAME_MAX]; /* 线程名字 */ - rt_uint8_t type; /* 对象类型 */ - rt_uint8_t flags; /* 线程标志 */ - rt_list_t list; /* 对象链表 */ - rt_list_t tlist; /* 线程链表 */ - - void *sp; /* 栈指针:保存线程上下文 */ - void *entry; /* 线程入口函数 */ - void *parameter; /* 入口参数 */ - void *stack_addr; /* 栈起始地址 */ - rt_uint32_t stack_size; /* 栈大小 */ - - rt_err_t error; /* 错误码 */ - - rt_uint8_t stat; /* 线程当前状态 */ - - rt_uint8_t current_priority; /* 当前优先级(可能被互斥量提升) */ - rt_uint8_t init_priority; /* 初始优先级 */ - rt_uint32_t number_mask; /* 优先级位掩码(就绪表用) */ - - rt_ubase_t init_tick; /* 时间片长度(tick 数) */ - rt_ubase_t remaining_tick; /* 剩余时间片 */ - - struct rt_timer thread_timer; /* 内置定时器(用于延时/超时) */ - void (*cleanup)(struct rt_thread *tid); /* 退出清理函数 */ -}; -``` - -关键成员作用: - -| 成员 | 作用 | -|------|------| -| `stat` | 线程状态(`RT_THREAD_INIT`/`READY`/`RUNNING`/`SUSPEND`/`CLOSE`),决定线程在哪个队列 | -| `current_priority` | **实时**优先级;使用互斥量发生优先级继承时会临时改变 | -| `init_priority` | 创建时设定的优先级,继承解除后恢复用 | -| `init_tick` | 时间片大小;同优先级线程轮转时每片能运行多少 tick | -| `remaining_tick` | 剩余时间片,减到 0 触发同优先级切换 | -| `sp` | 线程的栈指针,上下文切换时保存/恢复 CPU 寄存器快照 | - -**线程栈(线程属性):** - -- 每个线程拥有**独立的栈空间**。线程切换时,当前线程的上下文(CPU 寄存器)保存到自己的栈中;恢复运行时再从栈中读回。 -- 线程栈也存放函数中的**局部变量**:局部变量先分配在寄存器中(ARM),当函数再调用其他函数时放入栈中。 -- 栈的增长方向与芯片架构相关,**ARM Cortex-M 为高地址向低地址增长**(满递减栈)。 -- 用户提供的栈首地址需**对齐**(ARM 上需 4 字节对齐,对应 `RT_ALIGN_SIZE`),定义静态栈时可用 `ALIGN(RT_ALIGN_SIZE)` 修饰。 - -#### 2.3 静态对象 vs 动态对象 - -| 对比项 | 静态对象 | 动态对象 | -|--------|----------|----------| -| 分配位置 | 编译期确定,位于 `.data` / `.bss` 段 | 运行期从堆(heap)`rt_malloc` 分配 | -| 创建 API | `rt_thread_init`(线程)等 | `rt_thread_create`(线程)等 | -| 生命周期 | 程序运行全程存在,不释放 | 可按需 `delete`/`detach` 释放 | -| 释放机制 | 无需释放(随程序结束) | 显式 `rt_thread_delete` / `rt_free` | -| 内存碎片 | 无 | 频繁分配/释放可能产生碎片 | -| 分配失败风险 | 无 | 堆不足时返回 `NULL` | -| 速度 | 快(无 malloc 开销) | 较慢(有堆管理开销) | - -**为什么高可靠场景强制使用静态线程:** - -1. **确定性**:内存占用在编译期就已固定,不会出现运行期 `malloc` 失败导致功能缺失。 -2. **避免碎片化**:动态分配长期运行易产生堆碎片,最终可能"有空间但申请不到"。 -3. **避免内存泄漏**:静态对象无需管理释放,杜绝了漏 `delete` 的隐患。 -4. **实时性**:省去堆管理的查找/合并开销,创建与切换更稳定。 - -因此在航空航天、汽车电子、工业控制等**安全关键系统**中,通常禁用动态内存,强制使用静态线程。 - ---- - -### 3. 线程状态机与调度算法 - -#### 3.1 五态模型 - -RT-Thread 线程共有五种状态: - -| 状态 | 含义 | -|------|------| -| 初始态(INIT) | 线程已创建(分配了 TCB/栈),但未就绪 | -| 就绪态(READY) | 等待 CPU,已挂入就绪队列 | -| 运行态(RUNNING) | 正在占用 CPU | -| 挂起态(SUSPEND) | 主动挂起或被阻塞(延时/等信号量/等消息),不在就绪队列 | -| 关闭态(CLOSE) | 线程执行完毕或已删除 | - -触发状态转换的典型 API: - -| 转换 | 典型 API | -|------|----------| -| INIT → READY | `rt_thread_startup()` | -| READY → RUNNING | 调度器 `rt_schedule()` 选中 | -| RUNNING → READY | 高优先级就绪(抢占)/ 时间片耗尽 | -| RUNNING → SUSPEND | `rt_thread_suspend()`、`rt_thread_delay()`、`rt_sem_take()`(阻塞) | -| SUSPEND → READY | `rt_thread_resume()`、延时超时、`rt_sem_release()` | -| RUNNING/READY → CLOSE | `rt_thread_delete()` / `rt_thread_detach()`、线程函数 `return` | - -#### 3.2 调度机制 - -**(1) 抢占式调度** - -- 核心原则:**始终让最高优先级的就绪线程运行**。 -- 优先级共 `256` 级,`0` 最高(系统线程 0~7 预留,用户线程 8~255)。 -- 内核维护一张**优先级位图**(`rt_thread_ready_priority_group` + `rt_thread_ready_table[32]`),通过位操作以 **O(1)** 时间复杂度找到最高优先级就绪线程,不随线程数增长而变慢。 -- 抢占时机:高优先级线程因 `rt_sem_release`/`rt_thread_resume` 等变为就绪时,会触发 `rt_schedule()`,立即剥夺当前低优先级线程的 CPU。 - -**(2) 时间片轮转** - -- 作用范围:**仅在同优先级**的多个线程之间。 -- 每个线程有一个时间片(`init_tick`,单位 tick)。运行中 `remaining_tick` 递减,减到 0 时触发 `rt_schedule()`,把 CPU 让给同优先级就绪队列中的下一个线程,并重置自己的时间片。 -- 若时间片设为 0,则该线程采用**完全抢占、不参与轮转**(只有高优先级或主动挂起才会让出 CPU)。 - -``` -示例:优先级相同(16)的两个线程,时间片各 5 tick -[th1 运行 5 tick] → [th2 运行 5 tick] → [th1 运行 5 tick] → ... -``` - ---- - -### 4. 系统线程 - -#### 4.1 空闲线程 - -- 系统创建的最低优先级线程,状态**永远为就绪态**;当系统中无其他就绪线程时,调度器调度到空闲线程。 -- 空闲线程通常是死循环,**永远不能被挂起**。 -- 特殊用途: - 1. **回收资源**:某线程运行完毕后系统自动调用 `rt_thread_exit()`,将其从就绪队列删除、状态改为关闭态,并挂入 `rt_thread_defunct` 僵尸队列(资源未回收的关闭线程队列),最后由空闲线程统一回收其资源。 - 2. **运行钩子函数**:空闲线程运行时调用用户钩子,适合钩入功耗管理、看门狗喂狗等工作。 - -#### 4.2 主线程 - -- RT-Thread 中的 `main()` 本身是一个线程(主线程)。系统启动时创建 main 线程,其入口为 `main_thread_entry()`,用户的应用入口 `main()` 从它真正开始。 -- 调度器启动后 main 线程开始运行,用户可在 `main()` 中添加自己的应用初始化代码。 - ---- - -### 5. 线程管理 API - -| API | 作用 | 说明 | -|-----|------|------| -| `rt_thread_init()` | 初始化**静态线程** | 线程句柄、栈由用户提供(全局变量),栈首地址需对齐 | -| `rt_thread_create()` | 创建**动态线程** | 从堆分配线程句柄 + 栈空间,栈按 `RT_ALIGN_SIZE` 对齐 | -| `rt_thread_startup()` | 启动线程 | 状态改为就绪态并入队;若优先级高于当前线程则立即切换 | -| `rt_thread_mdelay()` | 线程延时 | 让出 CPU 使用权,指定时间后重新运行 | - -**入口函数要求:** - -```c -void (*entry)(void *parameter); /* 线程入口函数格式 */ -``` - -入口函数内部**必须有让出 CPU 的动作**(如 `rt_thread_mdelay()`),否则低优先级线程将得不到调度。 - -**线程创建/启动完整流程:** - -``` -静态:提供栈+句柄 → rt_thread_init() → rt_thread_startup() -动态:rt_thread_create()(内部分配栈+TCB) → rt_thread_startup() -``` - ---- - -## Part 2:多线程实战(源码见 `thread_study.c`) - -### 2.1 实现内容 - -1. `th_led1`:动态创建(`rt_thread_create`),优先级 16,时间片 5 tick,栈 1024B,翻转 LED_R 并打印计数与 `rt_tick_get()`。 -2. `th_led2`:静态初始化(`rt_thread_init`),优先级 16,时间片 5 tick,全局 TCB + 全局栈,翻转 LED_B 并打印计数。 -3. `th_monitor`:动态创建,优先级 10(高于工作线程),`rt_thread_mdelay(2000)` 周期唤醒,打印两线程状态与剩余栈空间;结合按键单次触发挂起 / 再次触发恢复 `th_led1`。 -4. 使用 `INIT_APP_EXPORT()` 自动加载测试模块;使用 `MSH_CMD_EXPORT()` 导出 `thread_dump` 查看命令。 - -### 2.2 运行现象 - -下载运行后,串口(115200)输出如下(预期): - -**系统启动与自动初始化** - -```text - \ | / -- RT - Thread Operating System - / | \ 4.0.3 build Aug 18 2026 - 2006 - 2020 Copyright by rt-thread team -``` - -**工作线程交替翻转 LED,打印计数** - -```text -[th_led1] count=1 tick=103 -[th_led2] count=1 -[th_led1] count=2 tick=603 -[th_led2] count=2 -[th_led1] count=3 tick=1103 -[th_led2] count=3 -... -``` - -> 现象:板载红色 LED 与蓝色 LED 各以约 1Hz 频率闪烁,串口周期性打印两个工作线程的计数。 - -**监控线程每 2s 打印状态与剩余栈** - -```text -[monitor] th_led1 stat=SUSPEND stack=856/1024 -[monitor] th_led2 stat=SUSPEND stack=852/1024 -[monitor] th_led1 stat=SUSPEND stack=856/1024 -[monitor] th_led2 stat=SUSPEND stack=852/1024 -... -``` - -> 说明:打印时刻监控线程自身正在运行,两个工作线程处于挂起(延时)状态;`stack=xxx/1024` 表示剩余栈字节数。 - -**按键挂起 / 恢复 `th_led1`** - -```text -[monitor] th_led1 suspended ← 第一次按键,th_led1 被挂起,红灯停止闪烁 -[monitor] th_led1 resumed ← 第二次按键,th_led1 恢复,红灯继续闪烁 -``` - -**FinSH 命令 `thread_dump`** - -```text -msh />thread_dump ----- thread study dump ---- -th_led1 : stat=SUSPEND prio=16 tick=5 suspended=no -th_led2 : stat=SUSPEND prio=16 tick=5 -th_monitor: stat=SUSPEND prio=10 -``` - -**FinSH 命令 `ps` / `list_thread`** - -```text -msh />ps -thread pri status sp stack size max used left tick error --------- --- ------- ---------- ---------- ------ ---------- --- -tshell 20 running 0x20001378 0x00001000 15% 0x00000009 000 -th_led1 16 suspend 0x20002a58 0x00000400 22% 0x00000005 000 -th_led2 16 suspend 0x20002308 0x00000400 21% 0x00000005 000 -th_monitor 10 suspend 0x200031b8 0x00000400 18% 0x00000005 000 -tidle0 31 ready 0x20004f00 0x00000200 32% 0x0000001c 000 -timer 4 suspend 0x200026c0 0x00000200 12% 0x00000009 000 -``` - -### 2.3 实验结果分析 - -- **动态 vs 静态**:`th_led1` 由 `rt_thread_create` 从堆中分配 TCB 与栈;`th_led2` 由 `rt_thread_init` 使用编译期固定的全局 TCB 与全局栈。二者运行行为一致,但内存来源不同(堆 vs `.bss` 段)。 -- **优先级抢占**:`th_monitor` 优先级(10)高于工作线程(16),唤醒后立即抢占 CPU,故能稳定每 2s 输出监控信息。 -- **时间片轮转**:`th_led1` 与 `th_led2` 同优先级,时间片均为 5 tick,处于就绪态时按轮转调度公平共享 CPU。 -- **挂起/恢复**:按键触发 `rt_thread_suspend(th_led1)` 后红灯熄灭并停止计数;再次触发 `rt_thread_resume(th_led1)` 后恢复,验证了线程状态机中 `RUNNING → SUSPEND → READY` 的转换。 -- **剩余栈空间**:通过扫描栈底 `'#'` 水印计算未使用字节数,各线程剩余栈均在 80% 以上,栈配置(1024B)满足需求。 diff --git "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 355df6831527fa2bfe6047727b965a4b512de5bf..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\350\256\270\344\274\237\345\250\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,198 +0,0 @@ -/* - * thread_study.c - RSOC26 Day2 多线程编程作业 - * - * 目标平台 : STM32F407 星火1号 (stm32f407-rtspark) - * 功能 : 动/静态线程创建、时间片轮转、优先级抢占、线程挂起/恢复、 - * 自动初始化(INIT_APP_EXPORT)、MSH 命令导出(MSH_CMD_EXPORT) - * - * 注意 : PIN_KEY 需按板卡原理图确认,下方为常见 F407 按键引脚,请自行核对。 - */ -#include -#include -#include - -/* ---------- 板载引脚定义 ---------- */ -#define PIN_LED_R GET_PIN(F, 12) /* 红色 LED */ -#define PIN_LED_B GET_PIN(F, 11) /* 蓝色 LED */ -#define PIN_KEY GET_PIN(E, 3) /* 按键,按需修改为板卡实际按键引脚 */ - -/* ---------- 静态线程资源(th_led2 使用,编译期分配) ---------- */ -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t th_led2_stack[1024]; /* 静态栈(全局数组) */ -static struct rt_thread th_led2_tcb; /* 静态线程控制块 */ - -/* ---------- 动态线程句柄(运行期从堆分配) ---------- */ -static rt_thread_t th_led1 = RT_NULL; -static rt_thread_t th_monitor = RT_NULL; - -/* th_led1 是否被挂起 */ -static rt_bool_t led1_suspended = RT_FALSE; - -/* ---------- 工具函数 ---------- */ - -/* 线程状态 -> 字符串 */ -static const char *thread_stat_str(rt_uint8_t stat) -{ - switch (stat) - { - case RT_THREAD_INIT: return "INIT"; - case RT_THREAD_READY: return "READY"; - case RT_THREAD_SUSPEND: return "SUSPEND"; - case RT_THREAD_RUNNING: return "RUNNING"; - case RT_THREAD_CLOSE: return "CLOSE"; - default: return "UNKNOWN"; - } -} - -/* - * 计算剩余栈空间。 - * 内核在线程初始化时把整块栈填成 '#' 水印,运行后从栈底往上扫描, - * 连续 '#' 的字节数即为未使用的栈空间。 - */ -static rt_uint32_t remaining_stack(rt_thread_t thread) -{ - rt_uint8_t *base = (rt_uint8_t *)thread->stack_addr; - rt_uint32_t unused = 0; - - while (unused < thread->stack_size && base[unused] == '#') - { - unused++; - } - return unused; -} - -/* ---------- 工作线程 1:动态创建,翻转 LED_R ---------- */ -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_uint8_t level = PIN_LOW; - - while (1) - { - count++; - level = (level == PIN_LOW) ? PIN_HIGH : PIN_LOW; - rt_pin_write(PIN_LED_R, level); - - rt_kprintf("[th_led1] count=%u tick=%u\n", - count, (rt_uint32_t)rt_tick_get()); - - rt_thread_mdelay(500); - } -} - -/* ---------- 工作线程 2:静态初始化,翻转 LED_B ---------- */ -static void th_led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_uint8_t level = PIN_LOW; - - while (1) - { - count++; - level = (level == PIN_LOW) ? PIN_HIGH : PIN_LOW; - rt_pin_write(PIN_LED_B, level); - - rt_kprintf("[th_led2] count=%u\n", count); - - rt_thread_mdelay(500); - } -} - -/* ---------- 按键检测:单次触发切换 th_led1 挂起/恢复 ---------- */ -static void check_key(void) -{ - if (rt_pin_read(PIN_KEY) == PIN_LOW) /* 按下(低电平有效) */ - { - rt_thread_mdelay(20); /* 消抖 */ - if (rt_pin_read(PIN_KEY) == PIN_LOW) - { - while (rt_pin_read(PIN_KEY) == PIN_LOW) - { - rt_thread_mdelay(10); /* 等待松开 */ - } - - if (led1_suspended) - { - rt_thread_resume(th_led1); - led1_suspended = RT_FALSE; - rt_kprintf("[monitor] th_led1 resumed\n"); - } - else - { - rt_thread_suspend(th_led1); - led1_suspended = RT_TRUE; - rt_kprintf("[monitor] th_led1 suspended\n"); - } - } - } -} - -/* ---------- 监控线程:优先级 10,周期打印状态与剩余栈 ---------- */ -static void th_monitor_entry(void *parameter) -{ - while (1) - { - rt_thread_mdelay(2000); - - check_key(); - - rt_kprintf("[monitor] th_led1 stat=%s stack=%u/%u\n", - thread_stat_str(th_led1->stat), - remaining_stack(th_led1), th_led1->stack_size); - rt_kprintf("[monitor] th_led2 stat=%s stack=%u/%u\n", - thread_stat_str(th_led2_tcb.stat), - remaining_stack(&th_led2_tcb), th_led2_tcb.stack_size); - } -} - -/* ---------- MSH 命令:thread_dump ---------- */ -static void thread_dump(int argc, char **argv) -{ - rt_kprintf("---- thread study dump ----\n"); - rt_kprintf("th_led1 : stat=%s prio=%d tick=%d suspended=%s\n", - thread_stat_str(th_led1->stat), - th_led1->current_priority, - th_led1->init_tick, - led1_suspended ? "yes" : "no"); - rt_kprintf("th_led2 : stat=%s prio=%d tick=%d\n", - thread_stat_str(th_led2_tcb.stat), - th_led2_tcb.current_priority, - th_led2_tcb.init_tick); - rt_kprintf("th_monitor: stat=%s prio=%d\n", - thread_stat_str(th_monitor->stat), - th_monitor->current_priority); -} -MSH_CMD_EXPORT(thread_dump, dump thread study threads); - -/* ---------- 应用自动初始化入口 ---------- */ -static int thread_study_init(void) -{ - /* 初始化 LED 与按键引脚 */ - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_KEY, PIN_MODE_INPUT_PULLUP); - - /* 工作线程 1:动态创建(优先级 16,时间片 5,栈 1024) */ - th_led1 = rt_thread_create("th_led1", th_led1_entry, RT_NULL, - 1024, 16, 5); - if (th_led1 != RT_NULL) - { - rt_thread_startup(th_led1); - } - - /* 工作线程 2:静态初始化(全局栈 + 全局 TCB) */ - rt_thread_init(&th_led2_tcb, "th_led2", th_led2_entry, RT_NULL, - th_led2_stack, sizeof(th_led2_stack), 16, 5); - rt_thread_startup(&th_led2_tcb); - - /* 监控线程:动态创建(优先级 10,高于工作线程) */ - th_monitor = rt_thread_create("th_monitor", th_monitor_entry, RT_NULL, - 1024, 10, 5); - if (th_monitor != RT_NULL) - { - rt_thread_startup(th_monitor); - } - - return 0; -} -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2543\347\273\204/\350\264\272\350\276\276/\344\275\234\344\270\232/\347\203\247\345\275\225\350\277\220\350\241\214\347\273\223\346\236\234.png" "b/2026/\347\254\2543\347\273\204/\350\264\272\350\276\276/\344\275\234\344\270\232/\347\203\247\345\275\225\350\277\220\350\241\214\347\273\223\346\236\234.png" deleted file mode 100644 index 1d1e3f062fab175b7bfb3ed8c635d0e320566da3..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\350\264\272\350\276\276/\344\275\234\344\270\232/\347\203\247\345\275\225\350\277\220\350\241\214\347\273\223\346\236\234.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\350\264\272\350\276\276/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2543\347\273\204/\350\264\272\350\276\276/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 6c3dfdd33dd117439341b2698efc9a11542fae79..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\350\264\272\350\276\276/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,271 +0,0 @@ -# 【RSOC26】RT-Thread 工程构建与 Git 协作流程总结 - -## 一、学习概述 - -RT-Thread 的 BSP 工程通过 Kconfig 描述可配置功能,由 SCons 解析构建脚本、组织源码并调用交叉编译工具链。掌握从源码树导出独立工程、确认配置和完成构建的过程,是后续进行驱动、组件和应用开发的基础。 - -工程内容的迭代则由 Git 记录。本文先总结 RT-Thread 独立工程的导出、目录组成与构建过程,再梳理 Git 的变更流转、分支协作、冲突处理和远程认证。 - -## 二、从源码树导出独立工程 - -RT-Thread 源码树中同时包含内核、公共组件、CPU 架构支持以及多个硬件平台的 BSP。直接复制完整源码树会携带大量与目标硬件无关的文件,因此可以在目标 BSP 目录中生成一份独立工程。 - -基本过程如下: - -1. 准备与目标硬件匹配的 RT-Thread 源码版本。 -2. 打开 Env 终端,进入源码树中对应的 BSP 根目录。 -3. 确认该目录存在 `SConstruct`、`SConscript`、`Kconfig` 和 `rtconfig.py` 等构建文件。 -4. 执行发布工程命令: - -```bash -cd /bsp/ -scons --dist -``` - -`scons --dist` 会在 BSP 下生成 `dist` 目录,复制当前 BSP 所需的 RT-Thread 源码、板级文件和构建脚本,并剔除与目标工程无关的其他 BSP 和 CPU 架构内容。较新版本也可在命令中指定输出目录: - -```bash -scons --dist --project-path= -``` - -生成后可将独立工程移至开发目录。后续配置和编译均在该工程根目录中完成,不再依赖完整的上游源码树。 - -## 三、独立工程的目录结构 - -![独立工程目录结构](./项目结构.png) - -从工程结构可以看出,导出结果已将应用、板级支持、芯片库、软件包和 RT-Thread 源码整合到同一目录中。各主要部分的职责如下: - -| 目录或文件 | 作用 | -| --- | --- | -| `applications/` | 存放应用层源码,通常包括主程序入口及用户任务 | -| `board/` | 存放板级初始化、外设配置、时钟、引脚、链接脚本和底层驱动适配内容 | -| `libraries/` | 存放芯片级支持库,例如 CMSIS、设备头文件及底层外设库 | -| `packages/` | 存放根据工程配置获取的软件包和驱动包 | -| `rt-thread/` | 存放内核、组件、设备框架和 CPU 架构适配等 RT-Thread 主体源码 | -| `figures/` | 存放 README 或其他说明文档引用的图片 | -| `.vscode/` | 存放代码索引、任务或调试等编辑器配置 | -| `.ci/` | 存放持续集成或辅助自动化配置 | -| `Kconfig` | 定义工程可配置项及菜单层级 | -| `.config` | 保存当前选定的 Kconfig 配置 | -| `rtconfig.h` | 根据配置生成的 C 宏定义,用于控制内核、组件和驱动的条件编译 | -| `rtconfig.py` | 定义交叉编译器、工具路径、编译参数、链接参数和输出目标 | -| `SConstruct` | SCons 的顶层构建入口,负责初始化构建环境并汇总各模块 | -| `SConscript` | 按模块组织源文件、头文件路径和编译条件 | -| `README.md` / `README_EN.md` | 提供工程说明、硬件资源、构建方法和使用注意事项 | - -目录中的 `template.*` 是工程模板,`project.*` 是根据当前配置生成的兼容工程文件。它们不参与 SCons 命令本身的核心依赖解析,但可用于其他工程环境中的编辑、下载和调试。 - -## 四、工程配置与 SCons 构建 - -### 1. 确认与更新配置 - -进入独立工程根目录后,可使用以下命令调整组件、驱动和软件包选项: - -```bash -menuconfig -``` - -保存配置后,`.config` 会记录选项状态,同时生成或更新 `rtconfig.h`。如果配置中启用了软件包,还需同步对应依赖: - -```bash -pkgs --update -``` - -### 2. 执行并行构建 - -截图中使用了以下命令: - -```bash -scons -j16 -``` - -`-j16` 表示最多并行执行 16 个构建任务,可以利用多核处理器缩短全量编译时间。若需要按顺序分析报错信息,可暂时去掉 `-j` 参数,直接执行 `scons`。需要清除旧的中间文件时,可先执行: - -```bash -scons -c -``` - -### 3. 构建脚本的执行链路 - -SCons 首先读取顶层 `SConstruct`,再由各级 `SConscript` 收集当前配置所需的源文件和头文件搜索路径。`rtconfig.py` 向构建系统提供工具链与编译、汇编、链接参数,`rtconfig.h` 中的配置宏则决定哪些模块进入当前构建。 - -完整构建过程可概括为: - -```text -Kconfig/.config - ↓ - rtconfig.h - ↓ -SConstruct 读取 rtconfig.py - ↓ -SConscript 递归收集源码 - ↓ -交叉编译器生成目标文件 - ↓ -链接生成 rt-thread.elf - ↓ -objcopy 生成 rtthread.hex / rtthread.bin - ↓ -size 输出程序段与存储占用 -``` - -### 4. 构建结果分析 - -从构建日志可以看出,外设支持包中的 I2C、电源控制、时钟、随机数、定时器和串口等模块已完成编译,随后链接生成 `rt-thread.elf`。构建系统又将 ELF 转换为 `rtthread.hex` 和 `rtthread.bin`,便于后续使用不同方式下载固件。 - -截图中的内存统计如下: - -| 区域 | 已使用 | 区域大小 | 占用率 | -| --- | ---: | ---: | ---: | -| CODE | 81,856 B | 1 MB | 7.81% | -| RAM1 | 5,484 B | 128 KB | 4.18% | -| RAM2 | 0 B | 64 KB | 0.00% | - -`arm-none-eabi-size` 输出中,`text` 为 81,856 B,`data` 为 1,972 B,`bss` 为 3,508 B,总计十进制大小为 87,336 B。日志最后出现 `scons: done building targets.`,表明编译、链接和格式转换均已完成。 - -### 5. 固件下载与运行验证 - -将构建生成的固件下载到目标硬件并复位后,控制台输出了 RT-Thread 启动标识、系统版本和构建时间,如下图所示。 - -![固件下载后的系统启动输出](./烧录运行结果.png) - -终端显示系统版本为 RT-Thread 5.3.0,并输出完整的启动信息。该结果表明固件已成功进入目标设备,启动流程和控制台输出功能正常,从工程导出、配置、构建到下载验证的基本流程已完成。 - -## 五、Git 中的变更流转 - -一次修改从产生到进入远程仓库,通常需要经过以下四个阶段: - -```text -工作区 -- git add --> 暂存区 -- git commit --> 本地仓库 -- git push --> 远程仓库 -``` - -- **工作区**:用于创建或编辑文件,此时的变更尚未纳入版本历史。 -- **暂存区**:用于组织下一次提交的变更集合。`git add` 表示选择待提交内容,不代表文件已上传。 -- **本地仓库**:`git commit` 将暂存内容保存为带有作者、时间和说明信息的版本节点。 -- **远程仓库**:`git push` 将本地已存在的提交发送至远程分支,供协作者访问和审查。 - -若推送时出现 `Everything up-to-date`,表明当前本地分支没有待推送的新提交。此时应依次检查工作区状态、暂存区内容和本地提交记录。 - -## 六、Fork、Clone 与远程关系 - -Fork 与 Clone 位于不同的操作层级,其作用如下: - -| 操作 | 发生位置 | 主要作用 | -| --- | --- | --- | -| Fork | 代码托管平台 | 在个人空间中建立上游项目的副本 | -| Clone | 本地设备 | 将远程仓库复制为可进行编辑和提交的本地仓库 | -| Pull Request | 代码托管平台 | 请求将指定分支的变更审查后合并至目标分支 | - -在 Fork 协作模式中,本地仓库通常配置两个远程: - -```text -origin -> 个人远程副本,主要用于推送分支 -upstream -> 上游仓库,主要用于同步公共基线 -``` - -远程配置可通过以下命令查看和补充: - -```bash -git remote -v -git remote add upstream -git fetch upstream -``` - -将个人推送目标与上游同步目标分开,可以减少对公共基线的直接操作,并使本地分支能够及时获取上游变更。 - -## 七、标准的分支协作流程 - -### 1. 同步主分支 - -开始新任务前,应先将本地主分支更新至最新状态。以下命令中的分支名需根据项目实际配置调整: - -```bash -git switch -git pull --rebase origin -``` - -在配置上游远程的情况下,可执行: - -```bash -git fetch upstream -git rebase upstream/ -``` - -### 2. 创建任务分支 - -```bash -git switch -c -``` - -主分支应保持稳定,各项任务宜在独立分支中完成。这种方式能够限定变更范围,并降低并行开发时的相互影响。 - -### 3. 检查本地变更 - -```bash -git status -git diff -``` - -`git status` 用于确认工作区和暂存区状态,`git diff` 用于查看具体内容差异。提交前进行检查,可以避免临时文件、无关变更或敏感信息进入版本历史。 - -### 4. 创建本地提交 - -```bash -git add -git commit -m "" -``` - -明确指定待提交文件,有助于控制提交范围。提交说明应准确概括变更的目的和性质,并避免使用含义不明的描述。 - -### 5. 推送并发起合并请求 - -```bash -git push -u origin -``` - -推送完成后,可在代码托管平台上创建 Pull Request。提交前应核对以下信息: - -1. 源分支与目标分支选择正确。 -2. 变更列表仅包含当前任务所需内容。 -3. 标题和说明能够准确概括变更。 -4. 测试、文档和审查信息已按项目规范填写。 - -当目录初始化、文档编写或功能修改属于不同目的时,宜拆分为独立的提交或 Pull Request,以保持单次变更的聚焦性和可审查性。 - -## 八、合并冲突的处理 - -当不同分支对同一段内容产生不兼容的修改时,Git 会标记合并冲突,并要求由开发者确定最终内容。基本处理步骤如下: - -1. 使用 `git status` 定位冲突文件。 -2. 结合上下文整合双方变更,并删除冲突标记。 -3. 执行 `git add ` 标记该文件已完成处理。 -4. 继续 merge 或 rebase 流程,验证结果后重新推送。 - -为减少冲突,应在开始任务和发起 Pull Request 前同步上游变更,同时避免对无关模块进行修改。 - -## 九、提交身份与远程认证 - -Git 提交身份与代码托管平台的访问认证属于两组独立配置: - -- `user.name` 和 `user.email` 会记录在 Commit 中,用于标识变更作者。 -- HTTPS 凭据或 SSH 密钥用于验证账户是否具有远程仓库的访问权限。 - -如需为当前项目单独设置提交身份,可使用不带 `--global` 的仓库级配置: - -```bash -git config user.name "xxx" -git config user.email "xxx" -``` - -使用 SSH 认证时,仅可将公钥添加到代码托管平台。私钥、账户密码、访问令牌、内部地址及其他敏感信息不得出现在截图、文档或版本库中。 - -## 十、学习总结 - -通过本次学习,已建立从 BSP 导出、工程配置、依赖同步到编译、链接和固件生成的基本认识。同时,Git 可以将工程变更记录为可追溯的历史,并通过分支和 Pull Request 支持多人协作。在后续实践中,应重点保持以下习惯: - -1. 修改配置后及时更新软件包和生成配置,并通过完整构建验证其一致性。 -2. 任务开始前同步最新基线,降低基于过时版本开发的风险。 -3. 为独立任务创建独立分支,并使每次提交表达单一、明确的变更意图。 -4. 推送前检查工作区状态和内容差异,避免提交无关文件或敏感信息。 -5. 通过 Pull Request 完成变更说明、审查与合并,保证协作过程可追溯。 diff --git "a/2026/\347\254\2543\347\273\204/\350\264\272\350\276\276/\344\275\234\344\270\232/\351\241\271\347\233\256\347\273\223\346\236\204.png" "b/2026/\347\254\2543\347\273\204/\350\264\272\350\276\276/\344\275\234\344\270\232/\351\241\271\347\233\256\347\273\223\346\236\204.png" deleted file mode 100644 index 2370612ea158a4ed1c8af2c423496e9a77509d0c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\350\264\272\350\276\276/\344\275\234\344\270\232/\351\241\271\347\233\256\347\273\223\346\236\204.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package.c" "b/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package.c" deleted file mode 100644 index 02110356cad0fe46d802668f7f92f65db9ba14cf..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package.c" +++ /dev/null @@ -1,183 +0,0 @@ -/* 练习四:快递分拣中心;星火一号 STM32F407ZG。 - * 1. 消息队列容量为 5 条消息。 - * 2. 两个发件线程各产生 5 条快递消息,快递编号不得重复。 - * 3. 分拣线程接收消息,按 region 分类并统计数量和总重量。 - * 4. 队列满时非阻塞发送失败并计数,不崩溃。 - * 5. 两个发送线程完成后发送一条结束消息。 - * 消息队列容量为 5,两个发件线程各产生 5 条快递,分拣线程统计数量和重量。 */ -#include -#define QUEUE_SIZE 5 -#define SENDER_COUNT 2 -#define PACKAGES_PER_SENDER 5 -#define THREAD_STACK 1024 -#define THREAD_PRIORITY 25 -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -static rt_mq_t package_mq; -static rt_mutex_t stats_mutex; -static rt_sem_t done_sem; -static rt_uint32_t send_failed, senders_done; -static rt_uint8_t running; -static const char *region_name(rt_uint8_t region) -{ - return region == 1U ? "east" : (region == 2U ? "south" : "north"); -} -static rt_bool_t start_thread(const char *name, void (*entry)(void *), void *param, - rt_uint8_t priority, rt_uint32_t *started) -{ - rt_thread_t tid = rt_thread_create(name, entry, param, THREAD_STACK, priority, 5); - if (tid == RT_NULL) - return RT_FALSE; - if (rt_thread_startup(tid) != RT_EOK) - { - rt_thread_delete(tid); - return RT_FALSE; - } - (*started)++; - return RT_TRUE; -} -static void sender_entry(void *parameter) -{ - rt_uint32_t sender_id = (rt_uint32_t)parameter; - rt_uint32_t index; - for (index = 0; index < PACKAGES_PER_SENDER; index++) - { - struct package_msg msg; - rt_bool_t sent = RT_FALSE; - - msg.id = (sender_id + 1U) * 1000U + index + 1U; - msg.region = (rt_uint8_t)((index + sender_id) % 3U + 1U); - msg.weight = (rt_uint16_t)(800U + sender_id * 250U + index * 120U); - - while (!sent) - { - rt_err_t result = rt_mq_send(package_mq, &msg, sizeof(msg)); - if (result == RT_EOK) - { - sent = RT_TRUE; - } - else if (result == -RT_EFULL) - { - rt_mutex_take(stats_mutex, RT_WAITING_FOREVER); - send_failed++; - rt_mutex_release(stats_mutex); - rt_thread_mdelay(50); - } - else - { - rt_kprintf("sender %u send failed: %d\n", sender_id + 1U, result); - break; - } - } - if (!sent) - break; - } - - rt_mutex_take(stats_mutex, RT_WAITING_FOREVER); - senders_done++; - if (senders_done == SENDER_COUNT) - { - struct package_msg end_msg; - rt_memset(&end_msg, 0, sizeof(end_msg)); - if (rt_mq_send_wait(package_mq, &end_msg, sizeof(end_msg), - RT_WAITING_FOREVER) != RT_EOK) - rt_kprintf("send package end message failed\n"); - } - rt_mutex_release(stats_mutex); - rt_sem_release(done_sem); -} - -static void sorter_entry(void *parameter) -{ - rt_uint32_t total = 0, east = 0, south = 0, north = 0, total_weight = 0; - (void)parameter; - - while (1) - { - struct package_msg msg; - rt_err_t result = rt_mq_recv(package_mq, &msg, sizeof(msg), RT_WAITING_FOREVER); - if (result != RT_EOK) - { - rt_kprintf("package sorter recv failed: %d\n", result); - break; - } - if (msg.region == 0U) - break; - - total++; - total_weight += msg.weight; - if (msg.region == 1U) - east++; - else if (msg.region == 2U) - south++; - else - north++; - rt_kprintf("package %u -> %s, weight: %u g\n", - msg.id, region_name(msg.region), msg.weight); - } - - rt_kprintf("total packages: %u\n", total); - rt_kprintf("east: %u, south: %u, north: %u\n", east, south, north); - rt_kprintf("total weight: %u g\n", total_weight); - rt_kprintf("send failed: %u\n", send_failed); - rt_sem_release(done_sem); -} - -static int package_demo(int argc, char **argv) -{ - rt_uint32_t i, started = 0; - (void)argc; - (void)argv; - if (running) - { - rt_kprintf("package_demo is already running\n"); - return -RT_EBUSY; - } - - running = 1; - send_failed = 0; - senders_done = 0; - package_mq = rt_mq_create("pkg_mq", sizeof(struct package_msg), - QUEUE_SIZE, RT_IPC_FLAG_FIFO); - stats_mutex = rt_mutex_create("pkg_mtx", RT_IPC_FLAG_FIFO); - done_sem = rt_sem_create("pkg_done", 0, RT_IPC_FLAG_FIFO); - if ((package_mq == RT_NULL) || (stats_mutex == RT_NULL) || (done_sem == RT_NULL)) - { - rt_kprintf("create package IPC failed\n"); - goto cleanup; - } - - for (i = 0; i < SENDER_COUNT; i++) - { - char name[RT_NAME_MAX]; - rt_snprintf(name, sizeof(name), "sender%u", (unsigned int)i); - if (!start_thread(name, sender_entry, (void *)i, THREAD_PRIORITY, &started)) - rt_kprintf("start sender %u failed\n", i); - } - - if (started == SENDER_COUNT && - !start_thread("sorter", sorter_entry, RT_NULL, THREAD_PRIORITY - 1, &started)) - rt_kprintf("start sorter failed\n"); - - for (i = 0; i < started; i++) - rt_sem_take(done_sem, RT_WAITING_FOREVER); - -cleanup: - if (package_mq != RT_NULL) - rt_mq_delete(package_mq); - if (stats_mutex != RT_NULL) - rt_mutex_delete(stats_mutex); - if (done_sem != RT_NULL) - rt_sem_delete(done_sem); - package_mq = RT_NULL; - stats_mutex = RT_NULL; - done_sem = RT_NULL; - running = 0; - return 0; -} - -MSH_CMD_EXPORT(package_demo, package sorting demo); diff --git "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking.c" "b/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking.c" deleted file mode 100644 index 48391da1fe448808d59058aa2477ed22ff83d04b..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking.c" +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 练习一:停车场车位管理 - * 目标板:星火一号,主控芯片:STM32F407ZG - * - * 作业要求: - * - 计数信号量初始值为 3,表示剩余车位 - * - 创建 5 个车辆线程,每辆车尝试获取车位 - * - 获取失败且 2 秒超时则打印等待信息并重试 - * - 任意时刻占用车位不超过 3 个 - */ - -#include - -#define CAR_COUNT 5 -#define PARKING_SPACES 3 -#define THREAD_STACK 1024 -#define THREAD_PRIORITY 25 - -static rt_sem_t parking_sem; -static rt_mutex_t count_mutex; -static rt_sem_t done_sem; -static rt_uint32_t used_spaces; -static rt_uint8_t running; - -/* 车辆线程:获得车位后进入,停留一段时间再离开 */ -static void car_entry(void *parameter) -{ - rt_uint32_t car_id = (rt_uint32_t)parameter; - - rt_thread_mdelay((car_id - 1U) * 50U); - - while (1) - { - rt_err_t result = rt_sem_take(parking_sem, RT_TICK_PER_SECOND * 2); - - if (result != RT_EOK) - { - if (result == -RT_ETIMEOUT) - { - rt_kprintf("car%u waiting for a space\n", car_id); - continue; - } - break; - } - - rt_mutex_take(count_mutex, RT_WAITING_FOREVER); - used_spaces++; - rt_kprintf("car%u entered, used spaces: %u\n", car_id, used_spaces); - rt_mutex_release(count_mutex); - - rt_thread_mdelay(2500U + car_id * 50U); - - rt_mutex_take(count_mutex, RT_WAITING_FOREVER); - used_spaces--; - rt_kprintf("car%u left\n", car_id); - rt_mutex_release(count_mutex); - - rt_sem_release(parking_sem); - break; - } - - rt_sem_release(done_sem); -} - -/* MSH 命令:parking_demo */ -static int parking_demo(int argc, char **argv) -{ - rt_uint32_t i; - rt_uint32_t started = 0; - rt_thread_t tid; - - (void)argc; - (void)argv; - - if (running) - { - rt_kprintf("parking_demo is already running\n"); - return -RT_EBUSY; - } - - running = 1; - used_spaces = 0; - - parking_sem = rt_sem_create("pk_sem", PARKING_SPACES, RT_IPC_FLAG_FIFO); - count_mutex = rt_mutex_create("pk_cnt", RT_IPC_FLAG_FIFO); - done_sem = rt_sem_create("pk_done", 0, RT_IPC_FLAG_FIFO); - if ((parking_sem == RT_NULL) || (count_mutex == RT_NULL) || - (done_sem == RT_NULL)) - { - rt_kprintf("create parking IPC failed\n"); - goto cleanup; - } - - for (i = 1; i <= CAR_COUNT; i++) - { - char name[RT_NAME_MAX]; - - rt_snprintf(name, sizeof(name), "car%u", (unsigned int)i); - tid = rt_thread_create(name, car_entry, (void *)i, - THREAD_STACK, THREAD_PRIORITY, 5); - if (tid == RT_NULL) - { - rt_kprintf("create car%u thread failed\n", i); - continue; - } - - if (rt_thread_startup(tid) != RT_EOK) - { - rt_kprintf("start car%u thread failed\n", i); - rt_thread_delete(tid); - continue; - } - - started++; - } - - for (i = 0; i < started; i++) - rt_sem_take(done_sem, RT_WAITING_FOREVER); - -cleanup: - if (parking_sem != RT_NULL) - rt_sem_delete(parking_sem); - if (count_mutex != RT_NULL) - rt_mutex_delete(count_mutex); - if (done_sem != RT_NULL) - rt_sem_delete(done_sem); - - parking_sem = RT_NULL; - count_mutex = RT_NULL; - done_sem = RT_NULL; - running = 0; - - return 0; -} - -MSH_CMD_EXPORT(parking_demo, parking lot demo); diff --git "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup.c" "b/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup.c" deleted file mode 100644 index fde26372126e1359f8b5922ff95d975175b4b652..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup.c" +++ /dev/null @@ -1,155 +0,0 @@ -/* 练习三:系统启动条件检查;星火一号 STM32F407ZG。 - * - 两个窗口线程共同出售 20 张票 - * - 使用互斥量保护完整售票过程 - * - 售完后线程自动退出,票号不重复 - * - 命令参数 unsafe 用于对比不加锁时的竞态现象 - * 三个初始化线程模拟网络/传感器/存储模块,主业务线程用 AND 事件等待全部就绪。 */ -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) -#define THREAD_STACK 1024 -#define THREAD_PRIORITY 25 - -struct module_info -{ - const char *name; - rt_uint32_t event_bit; - rt_uint32_t delay_ms; -}; - -static const struct module_info modules[] = -{ - { "network", EVENT_NET_READY, 1000 }, - { "sensor", EVENT_SENSOR_READY, 2000 }, - { "storage", EVENT_STORAGE_READY, 5000 }, -}; - -static rt_event_t startup_event; -static rt_sem_t done_sem; -static volatile rt_uint32_t ready_flags; -static rt_uint8_t running; - -static rt_bool_t start_thread(const char *name, void (*entry)(void *), void *param, - rt_uint8_t priority, rt_uint32_t *started) -{ - rt_thread_t tid = rt_thread_create(name, entry, param, THREAD_STACK, priority, 5); - if (tid == RT_NULL) - return RT_FALSE; - if (rt_thread_startup(tid) != RT_EOK) - { - rt_thread_delete(tid); - return RT_FALSE; - } - (*started)++; - return RT_TRUE; -} - -static void print_missing_modules(rt_uint32_t ready) -{ - const char *separator = ""; - rt_kprintf("wait modules timeout, "); - if ((ready & EVENT_NET_READY) == 0U) - { - rt_kprintf("network"); - separator = ", "; - } - if ((ready & EVENT_SENSOR_READY) == 0U) - { - rt_kprintf("%ssensor", separator); - separator = ", "; - } - if ((ready & EVENT_STORAGE_READY) == 0U) - rt_kprintf("%sstorage", separator); - rt_kprintf(" is not ready\n"); -} - -static void module_entry(void *parameter) -{ - const struct module_info *module = &modules[(rt_uint32_t)parameter]; - rt_thread_mdelay(module->delay_ms); - ready_flags |= module->event_bit; - rt_kprintf("%s ready\n", module->name); - if (rt_event_send(startup_event, module->event_bit) != RT_EOK) - rt_kprintf("%s send ready event failed\n", module->name); - rt_sem_release(done_sem); -} - -static void business_entry(void *parameter) -{ - rt_uint32_t received = 0; - (void)parameter; - - while (1) - { - rt_err_t result = rt_event_recv(startup_event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_TICK_PER_SECOND * 3, &received); - if (result == RT_EOK) - { - rt_kprintf("all modules are ready\nbusiness task started\n"); - break; - } - if (result == -RT_ETIMEOUT) - { - print_missing_modules(ready_flags); - continue; - } - rt_kprintf("business wait events failed: %d\n", result); - break; - } - - rt_sem_release(done_sem); -} - -static int startup_demo(int argc, char **argv) -{ - rt_uint32_t i, started = 0; - (void)argc; - (void)argv; - - if (running) - { - rt_kprintf("startup_demo is already running\n"); - return -RT_EBUSY; - } - - running = 1; - ready_flags = 0; - startup_event = rt_event_create("st_event", RT_IPC_FLAG_FIFO); - done_sem = rt_sem_create("st_done", 0, RT_IPC_FLAG_FIFO); - if ((startup_event == RT_NULL) || (done_sem == RT_NULL)) - { - rt_kprintf("create startup IPC failed\n"); - goto cleanup; - } - - for (i = 0; i < 3; i++) - { - char name[RT_NAME_MAX]; - rt_snprintf(name, sizeof(name), "mod%u", (unsigned int)i); - if (!start_thread(name, module_entry, (void *)i, THREAD_PRIORITY, &started)) - rt_kprintf("start module %u failed\n", i); - } - - if (started == 3U && - !start_thread("sbiz", business_entry, RT_NULL, THREAD_PRIORITY, &started)) - rt_kprintf("start business thread failed\n"); - - for (i = 0; i < started; i++) - rt_sem_take(done_sem, RT_WAITING_FOREVER); - -cleanup: - if (startup_event != RT_NULL) - rt_event_delete(startup_event); - if (done_sem != RT_NULL) - rt_sem_delete(done_sem); - startup_event = RT_NULL; - done_sem = RT_NULL; - running = 0; - return 0; -} - -MSH_CMD_EXPORT(startup_demo, startup condition demo); diff --git "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket.c" "b/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket.c" deleted file mode 100644 index b3cd021a455f9ed4f57239c179edca77a61bd809..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket.c" +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 练习二:多窗口售票系统 - * 目标板:星火一号,主控芯片:STM32F407ZG - * - * 作业要求: - * - 两个窗口线程共同出售 20 张票 - * - 使用互斥量保护完整售票过程 - * - 售完后线程自动退出,票号不重复 - * - 命令参数 unsafe 用于对比不加锁时的竞态现象 - */ - -#include - -#define TOTAL_TICKETS 20 -#define WINDOW_COUNT 2 -#define THREAD_STACK 1024 -#define THREAD_PRIORITY 25 - -static rt_mutex_t ticket_mutex; -static rt_sem_t done_sem; -static rt_uint32_t remaining; -static rt_uint8_t use_mutex; -static rt_uint8_t running; - -/* 售票窗口线程 */ -static void window_entry(void *parameter) -{ - rt_uint32_t window_id = (rt_uint32_t)parameter; - char window_name = (window_id == 0U) ? 'A' : 'B'; - - while (1) - { - rt_uint32_t sold_ticket; - - if (use_mutex) - { - if (rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER) != RT_EOK) - break; - - if (remaining == 0U) - { - rt_mutex_release(ticket_mutex); - break; - } - - sold_ticket = remaining; - remaining--; - rt_kprintf("window %c sold ticket %u\n", window_name, sold_ticket); - rt_mutex_release(ticket_mutex); - rt_thread_mdelay(10); - } - else - { - /* 不加锁模式:读票和改票之间故意延时,制造竞态 */ - if (remaining == 0U) - break; - - sold_ticket = remaining; - rt_thread_mdelay(50); - remaining = sold_ticket - 1U; - rt_kprintf("window %c sold ticket %u\n", window_name, sold_ticket); - rt_thread_mdelay(10); - } - } - - rt_sem_release(done_sem); -} - -/* MSH 命令:ticket_demo [unsafe] */ -static int ticket_demo(int argc, char **argv) -{ - rt_uint32_t i; - rt_uint32_t started = 0; - rt_thread_t tid; - - if (running) - { - rt_kprintf("ticket_demo is already running\n"); - return -RT_EBUSY; - } - - use_mutex = 1; - if (argc >= 2 && rt_strcmp(argv[1], "unsafe") == 0) - { - use_mutex = 0; - rt_kprintf("ticket demo runs without mutex\n"); - } - - running = 1; - remaining = TOTAL_TICKETS; - - done_sem = rt_sem_create("tk_done", 0, RT_IPC_FLAG_FIFO); - if (done_sem == RT_NULL) - { - rt_kprintf("create ticket done semaphore failed\n"); - goto cleanup; - } - - if (use_mutex) - { - ticket_mutex = rt_mutex_create("tk_mtx", RT_IPC_FLAG_FIFO); - if (ticket_mutex == RT_NULL) - { - rt_kprintf("create ticket mutex failed\n"); - goto cleanup; - } - } - - for (i = 0; i < WINDOW_COUNT; i++) - { - char name[RT_NAME_MAX]; - - rt_snprintf(name, sizeof(name), "win%u", (unsigned int)i); - tid = rt_thread_create(name, window_entry, (void *)i, - THREAD_STACK, THREAD_PRIORITY, 5); - if (tid == RT_NULL) - { - rt_kprintf("create window %u thread failed\n", i); - continue; - } - - if (rt_thread_startup(tid) != RT_EOK) - { - rt_kprintf("start window %u thread failed\n", i); - rt_thread_delete(tid); - continue; - } - - started++; - } - - for (i = 0; i < started; i++) - rt_sem_take(done_sem, RT_WAITING_FOREVER); - - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: %u\n", remaining); - -cleanup: - if (ticket_mutex != RT_NULL) - rt_mutex_delete(ticket_mutex); - if (done_sem != RT_NULL) - rt_sem_delete(done_sem); - - ticket_mutex = RT_NULL; - done_sem = RT_NULL; - running = 0; - - return 0; -} - -MSH_CMD_EXPORT(ticket_demo, ticket demo [unsafe]); diff --git "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index c83446db5616d8780d53a9476b9d254dc2277140..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,317 +0,0 @@ -# Day2 作业 Part 1:理论知识梳理 - -> 姓名:陈相臣 -> 组别:第3组 -> 目标平台:STM32F407 星火号 -> 文档范围:本文件包含 Day2 作业的 Part 1 理论整理和 Part 2 实验结果分析与串口截图。 - -## 0. 从裸机开发到 RTOS - -传统裸机程序通常采用“前台 + 后台”结构:后台是一个无限循环 `while (1)`,在循环中依次调用各个功能处理函数;前台是中断服务程序,负责响应异步事件并通知后台。 - -裸机模式在简单系统中容易理解,但随着功能变复杂会暴露出几个问题: - -- **并发性弱**:多任务只能通过轮询或中断配合实现,程序并发工作效率低。 -- **实时性难保证**:某个处理函数执行时间过长时,其他任务的响应时间会随之变长。 -- **可维护性差**:功能之间耦合较强,修改一个功能可能影响整个循环。 -- **可重用性差**:模块化程度低,容易重复实现相同功能。 - -RTOS 的核心思想是“分而治之”,把复杂系统拆成多个相对独立的任务。RT-Thread 中任务的基本实体就是线程,由内核根据优先级、时间片和线程状态进行统一调度。 - -## 1. STM32F407 启动流程与 RT-Thread 引导机制 - -### 1.1 从上电复位到 main() 的完整调用链 - -STM32F407 使用 ARM Cortex-M4 内核,上电后从向量表读取初始栈指针和复位入口。以本工程使用的 GCC 启动文件为例,调用链如下: - -```text -Reset_Handler - -> SystemInit() - -> entry() - -> rtthread_startup() - -> rt_hw_interrupt_disable() - -> rt_hw_board_init() - -> rt_system_heap_init() - -> hw_board_init() - -> rt_console_set_device() - -> rt_components_board_init() - -> rt_show_version() - -> rt_system_timer_init() - -> rt_system_scheduler_init() - -> rt_application_init() - -> rt_system_timer_thread_init() - -> rt_thread_idle_init() - -> rt_system_scheduler_start() - -> main_thread_entry() - -> rt_components_init() - -> main() -``` - -各阶段作用如下: - -- `Reset_Handler`:设置初始栈指针 `_estack`,把 `.data` 段从 Flash 复制到 RAM,清零 `.bss` 段。 -- `SystemInit()`:完成芯片时钟系统、FPU 等基础硬件初始化。 -- `entry()`:RT-Thread 在 `components.c` 中提供的入口,内部调用 `rtthread_startup()`。 -- `rtthread_startup()`:关闭中断,依次初始化板级硬件、定时器、调度器和主要系统线程,最后启动调度器。 -- `rt_hw_board_init()`:初始化堆空间、系统时钟、控制台设备,并执行 `INIT_BOARD_EXPORT` 对应的板级初始化函数。 -- `rt_application_init()`:创建主线程,主线程入口为 `main_thread_entry()`。 -- `rt_system_scheduler_start()`:启动调度器,内核开始选择最高优先级就绪线程运行。调度器启动后函数不会正常返回。 -- `main_thread_entry()`:先调用 `rt_components_init()` 执行组件和应用的自动初始化,再调用用户编写的 `main()`。 - -下图概括了启动过程: - -```mermaid -flowchart TD - A["Reset_Handler"] --> B["SystemInit()"] - B --> C["entry()"] - C --> D["rtthread_startup()"] - D --> E["rt_hw_board_init()
rt_components_board_init()
INIT_BOARD_EXPORT"] - E --> F["rt_system_timer_init()
rt_system_scheduler_init()"] - F --> G["rt_application_init()
创建主线程"] - G --> H["rt_system_timer_thread_init()
rt_thread_idle_init()"] - H --> I["rt_system_scheduler_start()"] - I --> J["main_thread_entry()"] - J --> K["rt_components_init()
INIT_APP_EXPORT"] - K --> L["main()"] -``` - -### 1.2 自动初始化机制 - -RT-Thread 使用链接器 section 和函数指针数组实现自动初始化。其核心宏定义如下: - -```c -#define INIT_EXPORT(fn, level) \ - RT_USED const init_fn_t __rt_init_##fn RT_SECTION(".rti_fn." level) = fn - -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") -``` - -`INIT_EXPORT(fn, level)` 会声明一个函数指针常量,并把它放入名为 `.rti_fn.` 的链接器 section。链接脚本中通过以下方式把这些 section 按名称排序并集中到一个地址区间: - -```text -__rt_init_start = .; -KEEP(*(SORT(.rti_fn*))) -__rt_init_end = .; -``` - -初始化等级和调用顺序如下: - -| 等级 | 宏 | 典型用途 | -| --- | --- | --- | -| 0 | `INIT_EXPORT(rti_start, "0")` | 起始标记 | -| 0.end | `INIT_EXPORT(rti_board_start, "0.end")` | 板级初始化起始标记 | -| 1 | `INIT_BOARD_EXPORT(fn)` | 板级外设初始化 | -| 1.end | `INIT_EXPORT(rti_board_end, "1.end")` | 板级初始化结束标记 | -| 2 | `INIT_PREV_EXPORT(fn)` | 纯软件预初始化 | -| 3 | `INIT_DEVICE_EXPORT(fn)` | 设备初始化 | -| 4 | `INIT_COMPONENT_EXPORT(fn)` | 文件系统、网络等组件初始化 | -| 5 | `INIT_ENV_EXPORT(fn)` | 环境初始化 | -| 6 | `INIT_APP_EXPORT(fn)` | 应用程序初始化 | -| 6.end | `INIT_EXPORT(rti_end, "6.end")` | 结束标记 | - -内核在 `rt_components_board_init()` 中遍历 `INIT_BOARD_EXPORT` 对应的函数指针区间;在 `rt_components_init()` 中遍历后续等级的函数指针区间,并逐个调用。 - -两者的关键区别是执行时机: - -- `INIT_BOARD_EXPORT` 在 `rt_hw_board_init()` 阶段执行,此时调度器尚未启动。 -- `INIT_APP_EXPORT` 在主线程的 `main_thread_entry()` 中执行,此时调度器已经启动,可以在初始化函数里创建并启动其他线程。 - -因此,本作业中的 `thread_study_init()` 使用 `INIT_APP_EXPORT(thread_study_init)` 自动加载,不能在 `main.c` 中再次手动调用。 - -## 2. 内核基础与对象模型 - -### 2.1 RT-Thread 的面向对象设计 - -RT-Thread 采用类似面向对象的容器管理模式。所有内核对象都由一个基础结构 `struct rt_object` 派生: - -```c -struct rt_object -{ - char name[RT_NAME_MAX]; - rt_uint8_t type; - rt_uint8_t flag; - rt_list_t list; -}; -``` - -`struct rt_object` 包含对象名称、类型、标志和链表节点。线程、信号量、互斥量、事件、消息队列、定时器和设备等控制块都在自身结构开头包含 `struct rt_object parent`,相当于继承自基类。内核按对象类型维护多个对象容器链表,便于统一查找和管理。 - -### 2.2 线程控制块 `struct rt_thread` - -线程控制块是内核管理线程的核心数据结构,保存线程名称、状态、优先级、入口函数、栈信息和调度参数等。 - -关键成员如下: - -| 成员 | 作用 | -| --- | --- | -| `name` | 线程名称,最大长度由 `RT_NAME_MAX` 决定 | -| `type` / `flags` | 对象类型和标志 | -| `list` / `tlist` | 内核对象链表节点、线程链表节点 | -| `sp` | 线程栈指针,用于保存和恢复线程上下文 | -| `entry` | 线程入口函数 | -| `parameter` | 传给入口函数的参数 | -| `stack_addr` / `stack_size` | 线程栈起始地址和大小,用于栈空间检查 | -| `error` | 线程错误码 | -| `stat` | 线程状态,低 3 位由 `RT_THREAD_STAT_MASK` 提取 | -| `current_priority` | 当前有效优先级,调度器依据它选择线程 | -| `init_tick` | 配置的时间片大小,单位是系统节拍 | -| `remaining_tick` | 当前时间片的剩余节拍数 | -| `thread_timer` | 线程内置定时器,用于延时等待 | -| `cleanup` | 线程退出时的清理函数 | -| `user_data` | 用户私有数据 | - -其中 `stat` 的低 3 位定义如下: - -```c -#define RT_THREAD_INIT 0x00 -#define RT_THREAD_READY 0x01 -#define RT_THREAD_SUSPEND 0x02 -#define RT_THREAD_RUNNING 0x03 -#define RT_THREAD_CLOSE 0x04 -#define RT_THREAD_STAT_MASK 0x07 -``` - -读取线程状态时通常使用 `thread->stat & RT_THREAD_STAT_MASK`,避免高位信号或让出标志影响判断。 - -### 2.3 线程栈 - -每个线程都有独立的栈空间。线程切换时,CPU 寄存器等上下文会保存到当前线程栈中;恢复运行时,再从栈中读取这些上下文。栈还用于存放函数调用过程中的局部变量、返回地址和函数参数。 - -ARM Cortex-M 架构中,线程栈通常由高地址向低地址增长。用户提供静态线程栈时,栈首地址需要按系统要求对齐,例如 ARM 平台按 4 字节或 `RT_ALIGN_SIZE` 对齐。 - -### 2.4 静态对象与动态对象 - -RT-Thread 线程对象可以静态初始化,也可以动态创建。 - -| 对比项 | 静态线程 | 动态线程 | -| --- | --- | --- | -| 创建接口 | `rt_thread_init()` | `rt_thread_create()` | -| 控制块来源 | 用户提供全局 `struct rt_thread` | 内核从动态堆分配 | -| 栈来源 | 用户提供全局栈数组 | 内核从动态堆分配 | -| 内存分配时间 | 编译/链接时确定 | 运行时分配 | -| 启动接口 | `rt_thread_startup()` | `rt_thread_startup()` | -| 删除/回收 | `rt_thread_detach()` | `rt_thread_delete()` | -| 优点 | 内存确定,不依赖堆,可靠性高 | 创建灵活,适合运行期动态任务 | -| 缺点 | 灵活性较低,占用静态内存 | 可能分配失败,存在堆碎片风险 | - -高可靠系统中通常优先使用静态线程,因为静态对象的控制块和栈在链接阶段就已经确定,不依赖动态堆分配,可以避免内存不足导致创建失败,也便于做最坏情况下的内存分析。动态线程则适合任务数量不确定或需要按需创建、销毁的场景。 - -## 3. 线程状态机与调度算法 - -### 3.1 五态模型 - -RT-Thread 线程共有五种基本状态: - -| 状态 | 宏定义 | 说明 | -| --- | --- | --- | -| 初始状态 | `RT_THREAD_INIT` | 线程刚创建或初始化,尚未启动,不参与调度 | -| 就绪状态 | `RT_THREAD_READY` | 等待调度器选中,按优先级进入就绪队列 | -| 运行状态 | `RT_THREAD_RUNNING` | 当前正在使用 CPU;单核系统通常只有一个线程处于该状态 | -| 挂起状态 | `RT_THREAD_SUSPEND` | 因延时、等待资源或主动挂起而暂停,不参与调度 | -| 关闭状态 | `RT_THREAD_CLOSE` | 线程运行结束,等待资源回收,不参与调度 | - -状态转换如下图所示: - -```mermaid -stateDiagram-v2 - [*] --> init - init: 初始状态 - ready: 就绪状态 - running: 运行状态 - suspended: 挂起状态 - closed: 关闭状态 - init --> ready: rt_thread_startup - ready --> running: 调度器选中 - running --> ready: 被抢占 / 时间片耗尽 / rt_thread_yield - running --> suspended: rt_thread_suspend / rt_thread_mdelay / 等待 IPC - suspended --> ready: rt_thread_resume / 延时到达 / 资源释放 - running --> closed: rt_thread_delete / 线程退出 - closed --> [*] -``` - -常见 API 与状态转换的对应关系: - -| API | 状态变化 | -| --- | --- | -| `rt_thread_create()` / `rt_thread_init()` | 创建线程,进入初始状态 | -| `rt_thread_startup()` | 初始状态到就绪状态 | -| 调度器选择该线程 | 就绪状态到运行状态 | -| `rt_thread_suspend()` | 运行状态到挂起状态 | -| `rt_thread_resume()` | 挂起状态到就绪状态 | -| `rt_thread_mdelay()` | 当前线程暂时挂起,延时结束后恢复就绪 | -| `rt_thread_yield()` | 让出 CPU,仍保持就绪状态 | -| `rt_thread_delete()` / `rt_thread_detach()` | 删除或分离线程,进入关闭或回收流程 | - -线程入口函数通常需要主动让出 CPU,例如在循环中调用 `rt_thread_mdelay()`,否则高优先级或同优先级其他线程可能无法及时获得执行机会。 - -### 3.2 抢占式调度 - -RT-Thread 使用基于优先级的抢占式调度: - -- 线程优先级范围为 `0` 到 `RT_THREAD_PRIORITY_MAX - 1`,数值越小优先级越高,`0` 为最高优先级。 -- 调度器始终选择当前就绪队列中优先级最高的线程运行。 -- 当出现比当前运行线程优先级更高的就绪线程时,当前线程会被立即换出,高优先级线程抢占 CPU。 -- 最低优先级通常留给空闲线程,用户程序一般不使用。 - -本作业中,监控线程 `monitor` 优先级为 `10`,两个工作线程 `th_led1` 和 `th_led2` 优先级为 `16`。因此监控线程满足唤醒条件时,可以抢占工作线程并优先执行。 - -### 3.3 时间片轮转调度 - -时间片轮转只对相同优先级的就绪线程有效: - -- 每个线程都配置一个时间片 `tick`,保存在 `init_tick` 中。 -- 系统每个时钟节拍会递减当前线程的 `remaining_tick`。 -- 当 `remaining_tick` 减到 `0` 时,调度器把当前线程放回就绪队列末尾,并从同优先级就绪线程中选择下一个线程运行,同时重新装载其时间片。 -- 时间片只约束同优先级线程的单次运行时长,不能改变不同优先级线程之间的抢占关系。 - -例如两个同优先级线程 A 和 B,A 时间片为 `10`,B 时间片为 `5`。如果没有更高优先级线程就绪,系统会在 A 和 B 之间轮流切换,A 每次运行约 `10` 个节拍,B 每次运行约 `5` 个节拍。 - -本作业中,`th_led1` 和 `th_led2` 优先级同为 `16`,时间片均为 `5`,因此它们以时间片轮转方式交替执行。 - -## 4. 系统线程 - -### 4.1 空闲线程 - -空闲线程是系统创建的最低优先级线程,通常处于就绪状态。当没有其他就绪线程需要运行时,调度器会运行空闲线程。空闲线程不能被挂起,通常是一个无限循环。 - -空闲线程还承担资源回收任务:线程退出后先进入 `rt_thread_defunct` 僵尸队列,由空闲线程回收其控制块和栈资源。空闲线程也提供钩子函数接口,可用于功耗管理、看门狗喂狗等后台任务。 - -### 4.2 主线程 - -RT-Thread 中的 `main()` 本身运行在主线程中。系统启动时创建主线程,入口函数为 `main_thread_entry()`。主线程先执行 `rt_components_init()` 完成组件和应用自动初始化,再调用用户编写的 `main()`。 - -本作业的应用初始化函数 `thread_study_init()` 通过 `INIT_APP_EXPORT` 注册,因此会在主线程进入 `main()` 之前被自动调用。 - -## 5. Part 2 实验结果分析与串口截图 - -### 5.1 实验环境 - -- 开发板:STM32F407 星火号 -- 调试串口:115200 8N1 -- 观察对象:板载 LED_R、LED_B、KEY0 -- 软件:RT-Thread 内核多线程示例 `thread_study.c` - -### 5.2 程序运行现象 - -系统上电后,`thread_study_init()` 通过 `INIT_APP_EXPORT` 自动创建并启动以下线程: - -| 线程 | 创建方式 | 优先级 | 时间片 | 主要功能 | -| --- | --- | --- | --- | --- | -| `th_led1` | 动态创建 | 16 | 5 ticks | 每 500 ms 翻转 LED_R,并打印计数和 `rt_tick_get()` | -| `th_led2` | 静态初始化 | 16 | 5 ticks | 每 500 ms 翻转 LED_B,并打印计数 | -| `monitor` | 动态创建 | 10 | 5 ticks | 每 2000 ms 打印两个工作线程状态和剩余栈空间,并检测 KEY0 | - -串口首先输出 `[thread_study] init done`,随后可以持续看到两个工作线程打印计数。由于 `th_led1` 和 `th_led2` 优先级相同、时间片均为 `5`,它们以时间片轮转方式交替执行。监控线程每 2 秒采样一次,此时两个工作线程通常正处于 `rt_thread_mdelay(500)` 的延时挂起状态,因此状态值显示为 `0x02`,即 `RT_THREAD_SUSPEND`。 - -按下 KEY0 后,监控线程检测到按键低电平,设置 `th_led1_suspend_request`,`th_led1` 检测到请求后主动调用 `rt_thread_suspend()` 挂起自己。此后 LED_R 停止翻转,串口中只继续打印 `th_led2` 的计数。再次按下 KEY0 后,监控线程调用 `rt_thread_resume()` 恢复 `th_led1`,LED_R 重新开始翻转。 - -### 5.3 串口截图 - -正常运行时,两个工作线程周期打印计数,监控线程输出线程状态和剩余栈空间: - -![串口截图1:多线程正常运行](D:\RTT\rsoc-rtt\2026\第3组\陈相臣\作业\第二天作业\p1.png) - -按下 KEY0 后,`th_led1` 被挂起,`th_led2` 仍继续运行: - -![串口截图2:KEY0 挂起 th_led1](D:\RTT\rsoc-rtt\2026\第3组\陈相臣\作业\第二天作业\p2.png) diff --git "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/p1.png" "b/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/p1.png" deleted file mode 100644 index 2a42da9a809a33d4b8f42a42c41e5c6c22805a16..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/p1.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/p2.png" "b/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/p2.png" deleted file mode 100644 index 397acf70122d78e6ff3409512fe34fca1d0c725a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/p2.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index dc814816dfca63ac0070b65b23c6197ee29ca5c7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-18 RT-Thread Day2 multi-thread exercise - */ - -#include -#include -#include - -#ifdef RT_USING_FINSH -#include -#endif - -// 星火号板载资源引脚。LED 为低电平点亮,KEY0 使用内部上拉输入。 -#define PIN_LED_R GET_PIN(F, 12) -#define PIN_LED_B GET_PIN(F, 11) -#define PIN_KEY0 GET_PIN(C, 0) - -// 的工作线程参数,以及监控线程参数。 -#define THREAD_STACK_SIZE 1024 -#define WORKER_PRIORITY 16 -#define WORKER_TIMESLICE 5 -#define MONITOR_PRIORITY 10 -#define MONITOR_TIMESLICE 5 - -// 动态创建的线程使用句柄保存,便于查询状态和挂起/恢复。 -static rt_thread_t th_led1 = RT_NULL; -static rt_thread_t th_monitor = RT_NULL; - -// th_led2 使用静态 TCB 和全局栈,无需动态内存分配。 -static struct rt_thread th_led2; -ALIGN(RT_ALIGN_SIZE) -static char th_led2_stack[THREAD_STACK_SIZE]; - -/* - * 当前 RT-Thread 内核中 rt_thread_suspend() 只能挂起调用它的线程自身。 - * 因此监控线程只设置请求标志,由 th_led1 收到请求后自我挂起。 - */ -static volatile int th_led1_suspended = 0; -static volatile int th_led1_suspend_request = 0; - -//根据栈初始化时填充的 '#' 统计剩余栈空间,供监控线程打印。 -static rt_uint32_t thread_stack_free(struct rt_thread *thread) -{ - rt_uint8_t *ptr = (rt_uint8_t *)thread->stack_addr; - rt_uint8_t *end = ptr + thread->stack_size; - - while (ptr < end && *ptr == '#') - { - ptr++; - } - - return (rt_uint32_t)(ptr - (rt_uint8_t *)thread->stack_addr); -} - -//动态工作线程:翻转 LED_R,并打印计数和系统 tick。 -static void th_led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_base_t level = PIN_HIGH; - - (void)parameter; - - while (1) - { - // 收到监控线程的挂起请求后,由本线程自己进入挂起状态。 - if (th_led1_suspend_request != 0) - { - th_led1_suspend_request = 0; - th_led1_suspended = 1; - rt_kprintf("[th_led1] suspending itself\n"); - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - th_led1_suspended = 0; - rt_kprintf("[th_led1] resumed\n"); - } - - // 每次循环翻转一次红色 LED,周期约 500 ms。 - count++; - level = (level == PIN_LOW) ? PIN_HIGH : PIN_LOW; - rt_pin_write(PIN_LED_R, level); - rt_kprintf("[th_led1] count=%lu tick=%lu\n", - (unsigned long)count, (unsigned long)rt_tick_get()); - rt_thread_mdelay(500); - } -} - -// 静态工作线程:翻转 LED_B,并打印计数。 -static void th_led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_base_t level = PIN_HIGH; - - (void)parameter; - - while (1) - { - count++; - level = (level == PIN_LOW) ? PIN_HIGH : PIN_LOW; - rt_pin_write(PIN_LED_B, level); - rt_kprintf("[th_led2] count=%lu\n", (unsigned long)count); - rt_thread_mdelay(500); - } -} - -// 打印指定线程的运行状态和剩余栈空间。 -static void print_thread_status(const char *name, struct rt_thread *thread) -{ - if (thread == RT_NULL) - { - rt_kprintf("%s: not created\n", name); - return; - } - - rt_kprintf("%s: state=0x%02x free=%lu\n", - name, - (unsigned int)(thread->stat & RT_THREAD_STAT_MASK), - (unsigned long)thread_stack_free(thread)); -} - -// 动态监控线程:周期打印状态,并根据 KEY0 切换 th_led1 的挂起/恢复。 -static void th_monitor_entry(void *parameter) -{ - (void)parameter; - - while (1) - { - // 以 2 秒为周期查看工作线程状态。 - rt_thread_mdelay(2000); - - print_thread_status("th_led1", th_led1); - print_thread_status("th_led2", &th_led2); - - // KEY0 低电平时执行一次挂起或恢复切换。 - if ((th_led1 != RT_NULL) && (rt_pin_read(PIN_KEY0) == PIN_LOW)) - { - if (th_led1_suspended != 0) - { - // 已挂起,则恢复 th_led1。 - rt_thread_resume(th_led1); - rt_kprintf("[monitor] th_led1 resumed\n"); - } - else - { - // 未挂起,则请求 th_led1 自行挂起。 - th_led1_suspend_request = 1; - rt_kprintf("[monitor] th_led1 suspend requested\n"); - } - - // 等待按键释放,避免一次按下被重复处理。 - while (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - rt_thread_mdelay(10); - } - } - } -} - -// 应用初始化入口:配置外设并创建两个工作线程和一个监控线程。 -static int thread_study_init(void) -{ - // 初始化 LED 输出和 KEY0 内部上拉输入。 - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP); - - // 两个 LED 初始置高,即熄灭状态。 - rt_pin_write(PIN_LED_R, PIN_HIGH); - rt_pin_write(PIN_LED_B, PIN_HIGH); - - // 动态创建并启动红色 LED 工作线程。 - th_led1 = rt_thread_create("th_led1", - th_led1_entry, - RT_NULL, - THREAD_STACK_SIZE, - WORKER_PRIORITY, - WORKER_TIMESLICE); - if (th_led1 != RT_NULL) - { - rt_thread_startup(th_led1); - } - - // 静态初始化并启动蓝色 LED 工作线程。 - if (rt_thread_init(&th_led2, - "th_led2", - th_led2_entry, - RT_NULL, - th_led2_stack, - sizeof(th_led2_stack), - WORKER_PRIORITY, - WORKER_TIMESLICE) == RT_EOK) - { - rt_thread_startup(&th_led2); - } - - // 动态创建并启动监控线程。 - th_monitor = rt_thread_create("monitor", - th_monitor_entry, - RT_NULL, - THREAD_STACK_SIZE, - MONITOR_PRIORITY, - MONITOR_TIMESLICE); - if (th_monitor != RT_NULL) - { - rt_thread_startup(th_monitor); - } - - rt_kprintf("[thread_study] init done\n"); - return RT_EOK; -} - -// 注册到应用自动初始化,main.c 中无需手动调用本函数。 -INIT_APP_EXPORT(thread_study_init); - -#ifdef RT_USING_FINSH -// FinSH 命令处理函数:输出三个线程的状态。 -static int thread_dump(int argc, char **argv) -{ - (void)argc; - (void)argv; - - rt_kprintf("thread dump:\n"); - print_thread_status("th_led1", th_led1); - print_thread_status("th_led2", &th_led2); - print_thread_status("monitor", th_monitor); - - return RT_EOK; -} - -// 导出 thread_dump 命令,可在 FinSH 中直接输入查看线程状态。 -MSH_CMD_EXPORT(thread_dump, dump thread_study thread status); -#endif diff --git "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index e88c5e881375d3938231be13a2d579269362e7a5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,57 +0,0 @@ -# 第一天笔记 - RT-Thread 与 Git - -## 一、夏令营安排 - -- 第一周为学习周,上午 9:00-12:00 授课,下午 14:00 后实操与完成作业。 -- 第二周为项目周,学员分组完成项目任务。 -- 每日完成作业并提交 Git 仓库,每日课前线上签到。 -- 笔记统一使用 Markdown 记录,并提交到指定 Git 仓库。 - -## 二、RT-Thread 生态与开发环境 - -### 1. 环境配置 - -1. 安装 RT-Thread Studio。 -2. 通过 SDK Manager 下载 RT-Thread 4.0.3 源码和 STM32F4 芯片支持包。 -3. 在设备管理器中确认 ST-Link 驱动正常识别。 - -### 2. 工程创建与调试 - -1. 基于 STM32F407 创建工程,选择 Nano 或标准版内核,串口波特率设为 115200。 -2. 编译生成 `.elf` 和 `.bin` 文件。 -3. 使用 ST-Link 下载到开发板,通过终端查看启动日志。 -4. 使用断点调试验证程序运行情况。 - -## 三、Markdown 笔记规范 - -- 使用 `#`、`##`、`###` 表示标题层级。 -- 代码块使用三个反引号包裹,表格使用 `|` 分隔,加粗使用 `**文字**`。 -- 图片建议保存到 `./figures`,使用 `![描述](路径)` 引用。 - -## 四、Git 基础操作与 PR 流程 - -### 1. 常用命令 - -```bash -git init # 初始化本地仓库 -git clone <仓库地址> # 克隆远程仓库 -git pull origin master # 拉取远程 master 分支 -git add <文件/路径> # 添加到暂存区 -git commit -m "说明" # 提交到本地仓库 -git push origin master # 推送到远程仓库 -git remote -v # 查看远程仓库地址 -git remote set-url origin <地址> # 修改远程仓库地址 -git checkout -b <分支名> # 创建并切换分支 -git switch <分支名> # 切换分支 -git fetch origin master # 同步远程分支,不自动合并 -``` - -### 2. Fork 与 PR 流程 - -```text -Fork 上游仓库 -> 克隆个人仓库 -> 创建功能分支 -> 开发 -> push -> 创建 PR -> 审核合并 -``` - -- 开发在独立功能分支进行,例如 `day1`、`day2`,不要直接修改 `master`。 -- 使用 `git remote add upstream <上游地址>` 关联上游,通过 `git fetch upstream` 或 `git pull` 同步最新代码。 -- 生成 SSH 密钥并添加公钥到 Git 平台,用于免密认证。 diff --git "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 60c39990515457c55eaed492459a8622a571b3a1..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\231\210\347\233\270\350\207\243/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,391 +0,0 @@ -# RT-Thread 第三天课堂笔记 - -## 一、课程主题 - -第三天的学习重点是 RT-Thread 的线程间同步与通信机制,主要包括: - -- 计数信号量 -- 互斥量 -- 事件集 -- 消息队列 -- MSH 命令的使用 -- 多线程程序结果的观察与分析 - -## 二、开发环境 - -- 开发板:星火一号 -- 主控芯片:STM32F407ZG -- 操作系统:RT-Thread -- 开发工具:RT-Thread Studio -- 调试方式:串口终端 + MSH 命令 - -## 三、核心知识点 - -### 1. 计数信号量 - -计数信号量适合管理有限资源,例如停车场车位。 - -特点: - -- 信号量初始值表示可用资源数量。 -- 线程获取资源时执行 `rt_sem_take()`。 -- 线程释放资源时执行 `rt_sem_release()`。 -- 当信号量为 0 时,后续线程会等待,直到资源被释放。 - -适合场景: - -- 多个相同资源需要分配和回收。 -- 资源数量有限,例如车位、缓冲区、连接数等。 - -### 2. 互斥量 - -互斥量用于保护共享资源,保证同一时间只有一个线程访问临界区。 - -特点: - -- 使用 `rt_mutex_take()` 获取锁。 -- 使用 `rt_mutex_release()` 释放锁。 -- 应保护完整操作过程,而不只是保护某一条语句。 - -售票系统就是典型场景: - -- 检查余票 -- 修改余票 -- 记录票号 -- 打印结果 - -整个过程都必须放在互斥量保护范围内,否则可能出现重复售票。 - -### 3. 事件集 - -事件集适合多个线程之间传递“条件就绪”信号。 - -特点: - -- 一个事件对象可以包含多个事件位。 -- 可以按 OR 模式等待任意事件。 -- 可以按 AND 模式等待所有事件。 -- 可以使用超时时间观察未就绪模块。 - -系统启动检查中,网络、传感器、存储三个模块都就绪后,主业务线程才允许启动。 - -### 4. 消息队列 - -消息队列用于线程之间传递结构化的数据。 - -特点: - -- 消息队列有固定容量。 -- 发送线程使用 `rt_mq_send()` 或 `rt_mq_send_wait()`。 -- 接收线程使用 `rt_mq_recv()`。 -- 队列满时,非阻塞发送会返回失败,不会让线程崩溃。 - -快递分拣中心就是消息队列的典型应用: - -- 发件线程产生快递消息。 -- 分拣线程接收消息。 -- 分拣线程根据区域统计快递数量和总重量。 - -## 四、作业要求与实现 - -本次代码按四个实验拆分为四个独立源文件: - -```text -parking.c -ticket.c -startup.c -package.c -``` - -四个文件都放到 RT-Thread Studio 工程的 `applications/` 目录下。 - -四个实验统一采用以下设计: - -- 线程栈大小为 `1024` 字节。 -- 业务线程优先级为 `25`,时间片为 `5`。 -- 使用 `running` 标志防止 MSH 命令被重复启动。 -- 使用 `done_sem` 等待所有业务线程退出后再清理资源。 -- 线程名和 IPC 对象名通过局部数组动态生成,避免重复执行时重名。 -- 创建线程、信号量、互斥量、事件、消息队列后都检查返回值。 - -程序通过 `MSH_CMD_EXPORT` 导出四个独立命令: - -```text -parking_demo -ticket_demo -startup_demo -package_demo -``` - -### 练习一:停车场车位管理 - -代码文件:`parking.c` - -对应命令: - -```text -parking_demo -``` - -作业要求: - -- 创建初始值为 3 的计数信号量。 -- 创建 5 个车辆线程。 -- 车辆进入前申请车位。 -- 获得车位后打印进入信息。 -- 停留后离开并释放车位。 -- 2 秒内没有获得车位则打印等待信息并重新申请。 -- 任意时刻停车场内车辆数不能超过 3。 - -实现思路: - -- 定义 `CAR_COUNT = 5`、`PARKING_SPACES = 3`。 -- 使用计数信号量 `parking_sem` 表示 3 个车位。 -- 使用互斥量 `count_mutex` 保护共享变量 `used_spaces`。 -- 5 个车辆线程按 `car_id` 设置 50ms 启动间隔,降低同时竞争的概率。 -- 车辆使用 `rt_sem_take(parking_sem, RT_TICK_PER_SECOND * 2)` 申请车位。 -- 获取车位后,在互斥量内完成 `used_spaces++` 和进入信息打印。 -- 车辆停留 `2500ms + car_id * 50ms` 后,在互斥量内减少 `used_spaces`,再释放车位。 -- 主命令线程通过 `done_sem` 等待所有车辆线程结束后,再删除 IPC 对象。 - -预期现象: - -- 任意时刻 `used spaces` 不会超过 3。 -- 没有立即获得车位的车辆会打印 `carX waiting for a space`。 -- 有车离开后,等待车辆继续进入。 - -### 练习二:多窗口售票系统 - -代码文件:`ticket.c` - -对应命令: - -```text -ticket_demo -``` - -对比实验命令: - -```text -ticket_demo unsafe -``` - -作业要求: - -- 创建两个售票线程,分别代表窗口 A 和窗口 B。 -- 使用全局变量保存剩余票数。 -- 售票过程包括检查余票、减 1、记录票号、打印结果。 -- 使用互斥量保护完整售票过程。 -- 售完后两个线程自动退出。 -- 最终 20 张票全部售出。 -- 不出现相同票号。 -- 剩余票数不能小于 0。 - -实现思路: - -- 定义 `TOTAL_TICKETS = 20`、`WINDOW_COUNT = 2`。 -- 安全模式下,每个窗口线程用 `ticket_mutex` 包住“检查余票、减票、打印票号”的完整流程。 -- 安全模式售完票后打印 `all tickets sold` 和 `remaining tickets: 0`。 -- `unsafe` 模式通过参数关闭互斥量,并在读取 `remaining` 和写回 `remaining` 之间加入 50ms 延时。 -- 非安全模式用于演示并发访问共享变量时可能出现的重复售票或负数余票。 -- 对比两次运行结果,观察并发访问共享变量的问题。 - -预期现象: - -- 安全模式中票号唯一。 -- 最后打印 `all tickets sold`。 -- 最后打印 `remaining tickets: 0`。 -- 非安全模式可能出现重复票号。 - -### 练习三:系统启动条件检查 - -代码文件:`startup.c` - -对应命令: - -```text -startup_demo -``` - -作业要求: - -- 定义网络、传感器、存储三个事件位。 -- 创建三个初始化线程。 -- 三个初始化线程分别发送对应事件。 -- 主业务线程使用 AND 模式等待三个事件。 -- 存储模块初始化时间设置为 5 秒。 -- 主业务线程最多等待 3 秒。 -- 超时后打印尚未完成的模块并再次等待。 - -实现思路: - -- 定义三个事件位:`EVENT_NET_READY`、`EVENT_SENSOR_READY`、`EVENT_STORAGE_READY`。 -- 用 `struct module_info` 统一保存模块名称、事件位和初始化延时。 -- 三个初始化线程分别在 1s、2s、5s 后发送就绪事件。 -- 使用 `volatile ready_flags` 记录当前已经就绪的模块位。 -- 主业务线程使用 `RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR` 等待全部事件。 -- 使用 `RT_TICK_PER_SECOND * 3` 设置 3 秒等待。 -- 超时后调用 `print_missing_modules()` 检查并打印未就绪模块,然后继续等待。 -- 所有模块就绪后,主业务线程打印 `all modules are ready` 和 `business task started`。 - -预期现象: - -- 网络模块约 1s 后就绪。 -- 传感器模块约 2s 后就绪。 -- 主业务线程 3 秒超时。 -- 提示 `storage is not ready`。 -- 存储模块 5 秒后就绪。 -- 最终打印 `all modules are ready`。 -- 然后打印 `business task started`。 - -### 练习四:快递分拣中心 - -代码文件:`package.c` - -对应命令: - -```text -package_demo -``` - -作业要求: - -- 创建一个消息队列。 -- 消息队列容量为 5 条消息。 -- 创建两个发件线程。 -- 每个发件线程产生 5 条快递消息。 -- 快递编号不得重复。 -- 分拣线程接收消息。 -- 分拣线程按区域分类。 -- 统计总数量、各区域数量、总重量。 -- 队列满时发送线程不得崩溃。 -- 队列满时记录发送失败次数。 -- 两个发送线程完成后发送结束消息。 -- 分拣线程收到结束消息后打印统计结果并退出。 - -实现思路: - -- 定义 `QUEUE_SIZE = 5`、`SENDER_COUNT = 2`、`PACKAGES_PER_SENDER = 5`。 -- 使用消息队列传递 `struct package_msg { id; region; weight; }`。 -- 两个发件线程分别生成 `1001...1005` 和 `2001...2005`,保证编号不重复。 -- 区域通过 `(index + sender_id) % 3 + 1` 生成,重量按发送线程和序号递增。 -- 分拣线程持续接收消息。 -- 队列满时使用非阻塞 `rt_mq_send()`,返回 `-RT_EFULL` 后累计 `send_failed` 并延时重试。 -- 两个发送线程都完成后,使用 `rt_mq_send_wait()` 发送一条 `region == 0` 的结束消息。 -- 收到 `region == 0` 的结束消息后退出。 - -预期现象: - -- 最终统计到 10 条快递。 -- 区域分别为华东、华南、华北。 -- 打印各区域数量。 -- 打印所有快递总重量。 -- 打印发送失败次数。 -- 每个快递打印格式类似 `package 1001 -> east, weight: 800 g`。 - -## 五、MSH 命令使用 - -在串口终端中输入: - -```text -help -``` - -可以查看系统导出的命令。 - -四个作业命令: - -```text -parking_demo -ticket_demo -startup_demo -package_demo -``` - -售票系统对比实验: - -```text -ticket_demo unsafe -``` - -## 六、在 RT-Thread Studio 中查看运行结果 - -### 1. 放置代码 - -把 `parking.c`、`ticket.c`、`startup.c`、`package.c` 四个文件放到工程中,一般放在 `applications/` 目录下。 - -如果工程中已经有 `main.c`,注意不能同时保留两个 `main()`,需要二选一。 - -### 2. 编译和下载 - -在 RT-Thread Studio 中编译工程,确认没有错误后下载到开发板。 - -### 3. 打开串口终端 - -打开 RT-Thread Studio 的串口终端,选择开发板对应的 COM 口。 - -波特率通常使用 BSP 默认值,常见为 `115200`。 - -### 4. 观察 MSH 提示符 - -复位开发板后,终端会打印 RT-Thread 启动信息。 - -出现类似提示后即可输入命令: - -```text -msh /> -``` - -### 5. 输入命令查看结果 - -```text -msh />parking_demo -msh />ticket_demo -msh />ticket_demo unsafe -msh />startup_demo -msh />package_demo -``` - -由于多线程并发执行,输出顺序可能不同,但关键条件应满足: - -- 停车场占用数不超过 3。 -- 售票安全模式下票号不重复。 -- 系统启动必须等待三个模块全部就绪。 -- 快递总数应为 10,并打印统计结果。 - -## 七、思考题 - -### 1. 不要只给单条语句加锁 - -售票系统中,如果只给 `remaining--` 加锁,检查和修改之间仍然可能发生切换。 - -必须把“检查、修改、记录、打印”完整流程保护起来。 - -### 2. 信号量和互斥量的区别 - -计数信号量适合管理多个同类资源。 - -互斥量适合保护共享数据,防止多个线程同时修改同一个变量。 - -### 3. 重复执行命令 - -重复执行 MSH 命令时,不能因为线程名或 IPC 对象名重复而崩溃。 - -本次代码中使用了: - -- 运行标志防止重复启动。 -- 动态名称避免重名冲突。 -- `done_sem` 等待所有工作线程退出后再清理 IPC 对象。 -- 任务结束后删除 IPC 对象。 - -### 4. 创建结果必须检查 - -所有线程和 IPC 对象创建后,都应该检查返回值。 - -常见检查对象包括: - -- `rt_thread_create()` -- `rt_sem_create()` -- `rt_mutex_create()` -- `rt_event_create()` -- `rt_mq_create()` diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/README.md" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/README.md" deleted file mode 100644 index 2f2cd64ca786338891b02cefa3ae4096cafb37f1..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/README.md" +++ /dev/null @@ -1,600 +0,0 @@ -# 线程的使用与内核基础实验报告(Day2) - ---- - -## 一、实验目的 - -1. 掌握 RT-Thread 系统启动流程:理解从芯片复位到进入 `main()` 的完整启动链路, - 掌握 `$Sub$$main → rtthread_startup → rt_application_init → main_thread_entry` - 的调用关系,以及 `INIT_*_EXPORT` 自动初始化的执行顺序。 - -2. 掌握线程的创建与管理:深入理解动态线程(`rt_thread_create`)与静态线程 - (`rt_thread_init`)的区别与使用场景,熟练掌握线程的启动、挂起、恢复及销毁机制。 - -3. 理解线程调度机制: - - **抢占式调度**:通过不同优先级线程,观察高优先级线程如何抢占低优先级线程的执行权。 - - **时间片轮转**:通过同优先级线程,观察线程在时间片耗尽后如何自动切换。 - -4. 掌握线程同步与状态转换:通过 `rt_thread_mdelay` 延时函数,观察线程从"运行"态 - 转为"挂起"态,以及系统空闲时空闲线程的运行情况。 - -5. 学会使用调试工具:掌握调度器钩子函数(`rt_scheduler_sethook`)、`rt_kprintf`、 - 日志宏(`LOG_D/LOG_I`)以及 msh 命令(`list_thread`、`free` 等)的使用方法。 - -6. 掌握简单外设的驱动方法:通过 `rt_pin` 引脚框架点亮/熄灭 LED、读取按键状态, - 理解 GPIO 输入输出配置与 RT-Thread 设备驱动框架的分层思想。 - ---- - -## 二、实验仪器 - -| 仪器/器件 | 规格/说明 | -|---|---| -| 开发板 | STM32F407 系列(星火一号) | -| 调试器 | ST-LINK | -| 计算机 | Windows 11 操作系统 | -| 开发环境 | RT-Thread Studio | -| 串口调试工具 | RT-Thread Studio 自带终端(FinSH/msh) | -| 外设模块 | 板载 LED(PF9)、按键 KEY0(PA0) | - ---- - -## 三、实验原理 - -### 3.1 系统启动流程 - -RT-Thread 的启动过程分为"芯片级启动"和"内核级启动"两个阶段: - -**芯片级(汇编)**:上电复位 → Reset_Handler(`startup_stm32f407xx.s`)→ -初始化 MSP → `SystemInit()` 配置 168MHz 时钟 → `__main` → `main()`。 - -**内核级(C)**:RT-Thread 通过编译器符号 `$Sub$$main` 截获 `main()`,先执行 -`rtthread_startup()`,完成内核初始化后再跳转回用户 `main()`: - -``` -main() - └─ $Sub$$main() ← 编译器自动插入 - └─ rtthread_startup() ← 内核启动总入口 - ├─ rt_hw_board_init() 板级初始化(时钟/串口/堆区) - ├─ rt_system_tick_init() SysTick 初始化(1ms/tick) - ├─ rt_hw_interrupt_init() 中断向量初始化 - ├─ rt_system_scheduler_init() 调度器初始化 - ├─ rt_system_signal_init() 信号机制初始化 - ├─ rt_application_init() 创建 main 线程(优先级10) - ├─ rt_components_init() 自动初始化链 - └─ rt_system_scheduler_start() 启动调度器 - → main_thread_entry → 用户 main() 执行 -``` - -> **关键点**:用户 `main()` 实际运行在一个优先级为 10 的 main 线程中, -> 调用 `rt_thread_mdelay` 会主动让出 CPU,而非死循环占死 CPU。 - -**自动初始化**:通过 `INIT_*_EXPORT` 宏将函数指针放入特定链接段, -`rt_components_init()` 按优先级(1→6)依次执行各注册函数,无需手动调用。 - -### 3.2 线程控制块(TCB) - -RT-Thread 用 `struct rt_thread` 描述线程,关键字段: - -| 字段 | 说明 | -|---|---| -| `name` | 线程名称 | -| `init_priority / current_priority` | 优先级(数值越小越高) | -| `stack_addr / stack_size / sp` | 线程栈(保存上下文) | -| `entry / parameter` | 入口函数与参数 | -| `stat` | 线程状态(INIT/READY/RUNNING/SUSPEND/CLOSE) | -| `init_tick / remaining_tick` | 时间片初始值/剩余值(Round-Robin 用) | - -### 3.3 线程状态机 - -五态模型:初始 → 就绪 → 运行 → 挂起 → 关闭 - -| 转换 | 触发条件 | -|---|---| -| 初始 → 就绪 | `rt_thread_startup()` | -| 就绪 → 运行 | 调度器选中(最高优先级) | -| 运行 → 挂起 | `rt_thread_mdelay()` / 等待 IPC | -| 挂起 → 就绪 | 延时结束 / 资源可用 | -| 运行 → 关闭 | `return` / `rt_thread_exit()` | - -### 3.4 调度机制 - -- **抢占式调度**:就绪队列中优先级最高(数值最小)的线程获 CPU,高优先级就绪时立即抢占。 -- **时间片轮转(Round-Robin)**:同优先级线程按 tick 时间片轮转执行。 -- 优先级范围 0~31,31 为 idle(最低优先级)。 - -### 3.5 动态线程 vs 静态线程 - -| 对比项 | 动态 `rt_thread_create()` | 静态 `rt_thread_init()` | -|---|---|---| -| TCB/栈分配 | 系统堆分配 | 用户全局变量/数组 | -| 生命周期 | 程序运行期可动态管理 | 编译时确定,与程序同周期 | -| 释放 | `rt_thread_delete()` | `rt_thread_detach()` | -| 适用场景 | 数量不确定,需动态管理 | 高可靠实时系统(无碎片、确定性) | - -### 3.6 简单外设(GPIO:LED 与按键) - -RT-Thread 提供统一的 `rt_pin_*` 引脚框架: - -```c -rt_pin_mode(GET_PIN(F, 9), PIN_MODE_OUTPUT); // 配置输出 -rt_pin_write(GET_PIN(F, 9), PIN_HIGH); // 输出高 -rt_uint8_t v = rt_pin_read(GET_PIN(A, 0)); // 读取电平 -``` - ---- - -## 四、实验步骤 - -### 4.1 实验准备 - -1. **创建工程**:RT-Thread Studio → File → New → RT-Thread Project, - 基于 STM32F407 系列芯片的 RT-Thread 标准版工程。 -2. **连接调试器**:ST-LINK USB 连接电脑与开发板 SWD 接口,确保电源正常。 -3. **检查设备管理器**:确认 ST-LINK Virtual COM Port(COM13)和 ST-LINK Debug 正常识别。 -4. **配置串口**:波特率 115200,8N1,无流控。 - -### 4.2 实验 A:动态与静态线程 + 抢占式调度 - -**目的**:验证动态/静态线程创建方式,以及高优先级线程对低优先级线程的抢占。 - -**代码要点**: - -```c -// ====== 动态线程(thread1)====== -static rt_thread_t tid1 = RT_NULL; -static void thread1_entry(void *parameter) -{ - rt_uint32_t count = 0; - for (count = 0; count < 10; count++) - { - rt_kprintf("thread1 count: %d\n", count); - rt_thread_mdelay(500); - } - rt_kprintf("thread1 exit\n"); -} - -// ====== 静态线程(thread2)====== -static char thread2_stack[1024]; -static struct rt_thread thread2; -static void thread2_entry(void *param) -{ - rt_uint32_t count = 0; - for (count = 0; count < 10; count++) - { - rt_kprintf("thread2 count: %d\n", count); - } - rt_kprintf("thread2 exit\n"); -} - -int main(void) -{ - // 动态创建 thread1(优先级 25) - tid1 = rt_thread_create("thread1", thread1_entry, RT_NULL, - 512, 25, 5); - if (tid1 != RT_NULL) rt_thread_startup(tid1); - - // 静态初始化 thread2(优先级 24,抢占 thread1) - rt_thread_init(&thread2, "thread2", thread2_entry, RT_NULL, - thread2_stack, sizeof(thread2_stack), 24, 5); - rt_thread_startup(&thread2); - - return 0; -} -MSH_CMD_EXPORT(thread_sample, thread sample); -``` - -**实验现象**: -- `thread2`(优先级24)先于 `thread1`(优先级25)执行,说明高优先级线程成功抢占低优先级线程。 -- `thread1` 每次 `rt_thread_mdelay(500)` 后从 RUNNING 转为 SUSPEND,500ms 后恢复 READY。 -- 两个线程执行完毕后系统进入 idle 空闲线程。 - -**编译结果**:0 errors, 0 warnings(截图:图 4-1~图 4-6) -**串口输出**:图 4-7~图 4-8 - -### 4.3 实验 B:时间片轮转调度 - -**目的**:验证同优先级线程之间的时间片轮转(Round-Robin)机制。 - -**代码要点**: - -```c -#define THREAD_PRIORITY 20 -#define THREAD_TIMESLICE 10 - -static void thread_entry(void* parameter) -{ - rt_uint32_t value = (rt_uint32_t)parameter; - rt_uint32_t count = 0; - while (1) - { - if (0 == (count % 5)) - { - rt_kprintf("thread %d is running, count = %d\n", value, count); - if (count > 200) return; - } - count++; - } -} - -int main(void) -{ - rt_thread_t tid = RT_NULL; - tid = rt_thread_create("thread1", thread_entry, (void*)1, 1024, 20, 10); - if (tid != RT_NULL) rt_thread_startup(tid); - - tid = rt_thread_create("thread2", thread_entry, (void*)2, 1024, 20, 5); - if (tid != RT_NULL) rt_thread_startup(tid); - - return 0; -} -``` - -**实验现象**: -- 两个线程优先级相同(20),`thread1` 时间片 10,`thread2` 时间片 5。 -- 两者交替打印 `count`,验证 Round-Robin 调度正常工作。 -- `thread2` 时间片更短,切换频率更高,符合预期。 -- 观察到串口输出交错(`rt_kprintf` 并发访问导致),证明两个线程确实并发运行。 - -**编译结果**:0 errors, 0 warnings(截图:图 4-7~图 4-9) -**串口输出**:图 4-10~图 4-12 - -### 4.4 实验 C:调度器钩子函数 - -**目的**:通过 `rt_scheduler_sethook` 捕获每次线程切换事件,直观观察上下文切换过程。 - -**代码要点**: - -```c -static void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to) -{ - rt_kprintf("from: %s --> to: %s\n", from->name, to->name); -} - -int main(void) -{ - rt_scheduler_sethook(hook_of_scheduler); // ← 先注册钩子 - - rt_thread_t tid1 = rt_thread_create("thread1", thread_entry, (void*)1, 1024, 20, 10); - rt_thread_startup(tid1); - - rt_thread_t tid2 = rt_thread_create("thread2", thread_entry, (void*)2, 1024, 20, 5); - rt_thread_startup(tid2); - - return 0; -} -``` - -**实验现象**: -- 每次线程切换均打印 `from: xxx --> to: yyy`。 -- 典型切换序列:`main → thread2 → thread1 → tshell → tidle → thread2 → thread1 → ...` -- `tidle`(idle 线程)频繁出现,说明用户线程因 `mdelay` 挂起时,CPU 切换至空闲线程。 -- 钩子函数在调试阶段非常有用,发布时应关闭以减少开销。 - -**编译结果**:0 errors, 0 warnings(截图:图 4-13~图 4-14) -**串口输出**:图 4-15~图 4-17 - -### 4.5 实验 D:LED 闪烁 + 按键检测 + msh 调试 - -**目的**:掌握 `rt_pin` 框架驱动 GPIO 的方法,以及 msh 调试命令的使用。 - -**代码要点**: - -```c -#define DBG_TAG "peripheral" -#define DBG_LVL DBG_LOG -#include - -#define LED_PIN GET_PIN(F, 9) // LED_R -#define KEY_PIN GET_PIN(A, 0) // KEY0 - -static void led_thread_entry(void *parameter) -{ - rt_uint8_t level = PIN_HIGH; - rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); - LOG_I("led thread start, pin=%d", LED_PIN); - - while (1) - { - rt_pin_write(LED_PIN, level); - level = (level == PIN_HIGH) ? PIN_LOW : PIN_HIGH; - rt_thread_mdelay(500); - } -} - -static void key_thread_entry(void *parameter) -{ - rt_uint8_t last_state = PIN_HIGH; - rt_pin_mode(KEY_PIN, PIN_MODE_INPUT_PULLUP); - LOG_I("key thread start, pin=%d", KEY_PIN); - - while (1) - { - rt_uint8_t state = rt_pin_read(KEY_PIN); - if (state != last_state) - { - rt_thread_mdelay(20); // 软件消抖 - if (rt_pin_read(KEY_PIN) == state) - { - if (state == PIN_LOW) - LOG_I("KEY0 pressed!"); - else - LOG_I("KEY0 released!"); - last_state = state; - } - } - rt_thread_mdelay(10); - } -} - -int peripheral_sample(void) -{ - rt_thread_t led_tid = rt_thread_create("led", led_thread_entry, RT_NULL, 512, 15, 10); - rt_thread_t key_tid = rt_thread_create("key", key_thread_entry, RT_NULL, 512, 16, 10); - if (led_tid) rt_thread_startup(led_tid); - if (key_tid) rt_thread_startup(key_tid); - return 0; -} -MSH_CMD_EXPORT(peripheral_sample, led blink and key sample); -``` - -**实验现象**: -- LED 以 500ms 为周期闪烁。 -- 按下/松开 KEY0 时,串口分别打印 `KEY0 pressed!` / `KEY0 released!`。 -- `msh >list_thread` 可查看 led、key 线程的优先级/状态/时间片信息。 -- `msh >free` 可查看系统内存堆使用情况。 - ---- - -## 五、实验数据分析 - -### 5.1 动态/静态线程创建对比 - -| 对比项 | thread1(动态) | thread2(静态) | -|---|---|---| -| 创建函数 | `rt_thread_create()` | `rt_thread_init()` | -| 栈空间 | 系统自动分配 512 字节 | 用户手动提供 1024 字节数组 | -| 线程控制块 | 系统自动分配(返回指针) | 用户提供(全局变量) | -| 适用场景 | 数量不确定,需动态管理 | 线程数量固定,节省堆开销 | - -### 5.2 抢占式调度验证 - -thread2(优先级 24)先于 thread1(优先级 25)执行,因为数值越小优先级越高, -高优先级线程就绪后立即抢占 CPU,完全符合 RT-Thread 抢占式调度规则。 - -### 5.3 时间片轮转验证 - -thread1(时间片 10)和 thread2(时间片 5)交替执行,打印 `count` 值交替出现。 -thread2 时间片更短,切换频率更高,证明时间片大小直接影响同优先级线程的调度频率。 - -### 5.4 串口输出交错现象分析 - -两个线程并发调用 `rt_kprintf` 时,输出字符相互穿插(如 "thread 2 thread 1 is running"), -说明 `rt_kprintf` 内部未加锁,是并发访问共享资源的典型案例,也侧面证明了时间片轮转正常工作。 - ---- - -## 六、实验结论 - -1. **系统启动流程**:掌握了 RT-Thread 从复位到 `main()` 的完整启动链路,理解了 - `$Sub$$main` 机制、各 `INIT_*_EXPORT` 初始化函数的执行顺序,以及 main 运行在 - 优先级 10 的 main 线程中的事实。 - -2. **动态/静态线程与抢占式调度**:验证了 `rt_thread_create` 和 `rt_thread_init` - 的正确用法,证明了高优先级线程(数值小)可成功抢占低优先级线程的 CPU。 - -3. **时间片轮转调度**:验证了同优先级线程按时间片 Round-Robin 轮转执行,时间片 - 越小切换频率越高。 - -4. **调度器钩子函数**:通过 `rt_scheduler_sethook` 成功捕获每次上下文切换,直观 - 展示了 main、thread1、thread2、tshell、tidle 等线程的切换序列。 - -5. **简单外设与 msh 调试**:通过 `rt_pin_*` 框架驱动 LED 与按键,掌握了 - `list_thread`、`free` 等 msh 调试命令的使用方法。 - ---- - -## 七、源码附录 - -### 实验 A 源码(动态/静态线程 + 抢占式调度) - -```c -#include -#include - -#define THREAD_PRIORITY 25 -#define THREAD_STACK_SIZE 512 -#define THREAD_TIMESLICE 5 - -static rt_thread_t tid1 = RT_NULL; - -/* 线程 1 入口(动态线程,有延时) */ -static void thread1_entry(void *parameter) -{ - rt_uint32_t count = 0; - for (count = 0; count < 10; count++) - { - rt_kprintf("thread1 count: %d\n", count); - rt_thread_mdelay(500); - } - rt_kprintf("thread1 exit\n"); -} - -/* 线程 2 入口(静态线程,无延时,抢占 thread1) */ -static char thread2_stack[1024]; -static struct rt_thread thread2; -static void thread2_entry(void *param) -{ - rt_uint32_t count = 0; - for (count = 0; count < 10; count++) - { - rt_kprintf("thread2 count: %d\n", count); - } - rt_kprintf("thread2 exit\n"); -} - -int thread_sample(void) -{ - tid1 = rt_thread_create("thread1", thread1_entry, RT_NULL, - THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); - if (tid1 != RT_NULL) rt_thread_startup(tid1); - - rt_thread_init(&thread2, "thread2", thread2_entry, RT_NULL, - thread2_stack, sizeof(thread2_stack), - THREAD_PRIORITY - 1, THREAD_TIMESLICE); - rt_thread_startup(&thread2); - - return 0; -} -MSH_CMD_EXPORT(thread_sample, thread sample); - -int main(void) -{ - thread_sample(); - return 0; -} -``` - -### 实验 B 源码(时间片轮转) - -```c -#include -#include - -#define THREAD_STACK_SIZE 1024 -#define THREAD_PRIORITY 20 - -static void thread_entry(void* parameter) -{ - rt_uint32_t value = (rt_uint32_t)parameter; - rt_uint32_t count = 0; - while (1) - { - if (0 == (count % 5)) - { - rt_kprintf("thread %d is running, count = %d\n", value, count); - if (count > 200) return; - } - count++; - } -} - -int main(void) -{ - rt_thread_t tid = RT_NULL; - - tid = rt_thread_create("thread1", thread_entry, (void*)1, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid != RT_NULL) rt_thread_startup(tid); - - tid = rt_thread_create("thread2", thread_entry, (void*)2, - THREAD_STACK_SIZE, THREAD_PRIORITY, 5); - if (tid != RT_NULL) rt_thread_startup(tid); - - return 0; -} -``` - -### 实验 C 源码(调度器钩子) - -```c -#include -#include - -#define THREAD_STACK_SIZE 1024 -#define THREAD_PRIORITY 20 - -static void thread_entry(void* parameter) -{ - rt_uint32_t value = (rt_uint32_t)parameter; - for (int count = 0; count < 10; count++) - { - rt_kprintf("thread %d is running\n", value); - rt_thread_mdelay(1000); - } -} - -/* 调度器钩子:每次线程切换时自动调用 */ -static void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to) -{ - rt_kprintf("from: %s --> to: %s\n", from->name, to->name); -} - -int main(void) -{ - rt_scheduler_sethook(hook_of_scheduler); // ← 先注册钩子 - - rt_thread_t tid1 = rt_thread_create("thread1", thread_entry, (void*)1, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid1 != RT_NULL) rt_thread_startup(tid1); - - rt_thread_t tid2 = rt_thread_create("thread2", thread_entry, (void*)2, - THREAD_STACK_SIZE, THREAD_PRIORITY, 5); - if (tid2 != RT_NULL) rt_thread_startup(tid2); - - return 0; -} -``` - -### 实验 D 源码(LED 闪烁 + 按键检测 + msh 调试) - -```c -#include -#include - -#define DBG_TAG "peripheral" -#define DBG_LVL DBG_LOG -#include - -#define LED_PIN GET_PIN(F, 9) // LED_R -#define KEY_PIN GET_PIN(A, 0) // KEY0 - -static void led_thread_entry(void *parameter) -{ - rt_uint8_t level = PIN_HIGH; - rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); - LOG_I("led thread start, pin=%d", LED_PIN); - - while (1) - { - rt_pin_write(LED_PIN, level); - level = (level == PIN_HIGH) ? PIN_LOW : PIN_HIGH; - rt_thread_mdelay(500); - } -} - -static void key_thread_entry(void *parameter) -{ - rt_uint8_t last_state = PIN_HIGH; - rt_pin_mode(KEY_PIN, PIN_MODE_INPUT_PULLUP); - LOG_I("key thread start, pin=%d", KEY_PIN); - - while (1) - { - rt_uint8_t state = rt_pin_read(KEY_PIN); - if (state != last_state) - { - rt_thread_mdelay(20); - if (rt_pin_read(KEY_PIN) == state) - { - if (state == PIN_LOW) - LOG_I("KEY0 pressed!"); - else - LOG_I("KEY0 released!"); - last_state = state; - } - } - rt_thread_mdelay(10); - } -} - -int peripheral_sample(void) -{ - rt_thread_t led_tid = rt_thread_create("led", led_thread_entry, RT_NULL, 512, 15, 10); - rt_thread_t key_tid = rt_thread_create("key", key_thread_entry, RT_NULL, 512, 16, 10); - if (led_tid) rt_thread_startup(led_tid); - if (key_tid) rt_thread_startup(key_tid); - return 0; -} -MSH_CMD_EXPORT(peripheral_sample, led blink and key sample); -``` diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213.md" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213.md" deleted file mode 100644 index 39e088e66c3420d70c349e19871f00a349921ac9..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213.md" +++ /dev/null @@ -1,488 +0,0 @@ -# RT-Thread 内核基础与线程 - -## 小结 - -本课程介绍了RTOS的基础线程、启动流程及线程控制块,旨在帮助学员理解RTOS相较于裸机的优势及其核心概念。 - -### 1. RTOS相较于裸机的优势 - -| 优势 | 说明 | -|------|------| -| **并发性** | 支持多任务并行处理,解决了裸机前后台模式下任务阻塞的问题 | -| **实时性** | 任务间的阻塞不影响其他任务,保证了系统的实时响应 | -| **可维护性** | 代码模块化程度高,易于理解和修改 | -| **可重用性** | 标准化的组件库使得代码可重用,减少了重复开发 | - -### 2. RTOS核心概念:线程 - -**线程**:RTOS的基本执行单元,通过"分而治之"的思想将复杂任务拆解为多个独立的线程。 - -**线程生命周期**:包含初始态、就绪态、运行态、挂起态和关闭态。线程通过调度器在不同状态间切换。 - -| 状态 | 说明 | -|------|------| -| **运行态** | 线程正在执行业务代码 | -| **挂起态** | 线程因调用delay、等待信号量、邮箱等IPC操作而暂停,让出CPU | -| **就绪态** | 线程已准备好,等待被调度器选中执行 | -| **关闭态** | 线程执行完毕或被主动终止,进入销毁流程 | - -### 3. 线程控制块 (TCB) - -**定义**:TCB是管理线程的核心数据结构,包含了线程的栈、入口函数、参数、优先级、状态等信息。 - -**结构**:在RT-Thread中,TCB继承自一个基类(rt_object),并通过成员变量(如sp, entry, parameter, stack_addr等)来具体描述线程的属性。 - -### 4. 线程调度与系统线程 - -**调度策略**:RT-Thread采用抢占式优先级调度。优先级高的线程会抢占CPU;当优先级相同时,则依据时间片(tick)进行轮询。 - -**系统线程**:包含main线程、idle(空闲)线程和timer线程。 - -**idle线程**:优先级最低,用于系统空闲时的资源回收、低功耗管理和执行用户定义的钩子函数。 - -### 5. 启动流程 - -**启动阶段**:系统从复位向量开始,经过芯片级初始化(如关闭中断、外设初始化)、RT-Thread内核初始化(如创建main线程、timer线程、idle线程、启动调度器)等步骤。 - ---- - -## 一、系统启动流程 - -### 1.1 整体启动链路 - -以 STM32F407(Cortex-M4)为例,从上电复位到用户 main() 函数的完整流程如下: - -![RT-Thread 启动流程](RTday2笔记_启动流程内核基础与线程/rtt_startup.png) - -``` -上电复位 - │ - ▼ -Reset_Handler(汇编:startup_stm32f407xx.s) - │ 1. 取出向量表 Reset 向量,设置 MSP 主栈 - │ 2. 调用 SystemInit() 配置时钟(168MHz) - │ 3. 跳转 __main 进入 C 运行环境 - ▼ -main()(C 入口) - │ - ▼ -$Sub$$main() ← 编译器自动插入的钩子(RT-Thread 通过它"截获"main) - │ - ▼ -rtthread_startup() ← 内核启动总入口 - │ - ├─ rt_hw_board_init() 板级初始化:时钟、串口、堆区(rt_heap) - ├─ rt_system_tick_init() 系统时钟节拍初始化(SysTick 1ms/tick) - ├─ rt_hw_interrupt_init() 中断向量初始化 - ├─ rt_system_scheduler_init() 调度器初始化(就绪队列/位图) - ├─ rt_system_signal_init() 信号机制初始化 - ├─ rt_application_init() 创建 main 线程(main_thread_entry,优先级 10) - ├─ rt_components_init() 组件自动初始化(INIT_*_EXPORT 依次执行) - └─ rt_system_scheduler_start() 启动调度器 → 切换第一个线程 - │ - ▼ -main_thread_entry → 用户真正的 main() 开始执行 -``` - -### 1.2 关键点 - -- **用户 main() 其实是一个线程**:RT-Thread 把 main 包装成优先级 10 的 `main` 线程, - 所以 main 里调用 `rt_thread_mdelay` 会主动让出 CPU,而不是死循环占死 CPU。 -- **$Sub$$main / $Super$$main 机制**:编译器支持符号替换,`$Sub$$main` 先执行, - 内部处理完再调用 `$Super$$main`(真正的 main)。这是 RT-Thread 不需要改动 - startup 汇编就能接管启动过程的原因。 - -### 1.3 组件自动初始化 - -RT-Thread 提供分级初始化宏,注册的初始化函数在 `rt_components_init()` 里按优先级执行: - -| 宏 | 优先级 | 执行时机/用途 | -| --- | --- | --- | -| `INIT_BOARD_EXPORT` | 1 | 板级外设初始化(时钟/GPIO) | -| `INIT_PREV_EXPORT` | 2 | 纯初始化、无依赖 | -| `INIT_DEVICE_EXPORT` | 3 | 外设驱动初始化 | -| `INIT_COMPONENT_EXPORT` | 4 | 组件初始化(如 FinSH) | -| `INIT_ENV_EXPORT` | 5 | 环境/依赖注入 | -| `INIT_APP_EXPORT` | 6 | 应用初始化(最晚) | - -```c -// 用法示例 -#include -static int app_init(void) -{ - rt_kprintf("app init\n"); - return 0; -} -INIT_APP_EXPORT(app_init); // 自动注册,无需手动调用 -``` - ---- - -## 二、内核基础 - -### 2.1 内核对象模型 - -RT-Thread 内核采用面向对象设计,所有内核对象都继承自基类 `rt_object`: - -![RT-Thread 内核对象继承关系](RTday2笔记_启动流程内核基础与线程/03kernel_object2.png) - -**核心对象说明:** - -| 对象 | 说明 | -|------|------| -| `rt_thread` | 线程对象,OS的基本调度单位 | -| `rt_timer` | 定时器对象 | -| `rt_mempool` | 内存池对象 | -| `rt_device` | 设备对象 | -| `rt_sem` / `rt_mutex` | 信号量 / 互斥量 | -| `rt_event` | 事件 | -| `rt_mb` / `rt_mq` | 邮箱 / 消息队列 | - -### 2.2 内核组成(src/ 目录) - -| 模块 | 源文件 | 作用 | -| --- | --- | --- | -| 调度器 | scheduler.c | 就绪队列、优先级调度、时间片轮转 | -| 线程 | thread.c | 线程创建/删除/挂起/恢复/延时 | -| 时钟 | clock.c | tick 维护,rt_tick_get() | -| 定时器 | timer.c | 软件定时器 rt_timer_* | -| IPC | ipc.c | 信号量/互斥量/事件/邮箱/消息队列 | -| 内存 | mem.c / memheap.c / mempool.c / slab.c | 堆内存管理 | -| 对象 | object.c | 内核对象系统(链表管理所有对象) | -| 设备 | device.c | 设备框架 rt_device_* | -| 中断 | irq.c | 中断安装与进入/退出 | -| 空闲 | idle.c | 空闲线程 + 空闲钩子 | -| 服务 | kservice.c | rt_kprintf 等基础服务 | - -### 2.3 线程控制块(TCB) - -线程的一切信息都存在 TCB 中,`struct rt_thread` 的关键字段: - -| 字段 | 说明 | -|------|------| -| `name` | 线程名(调试/识别) | -| `init_priority / current_priority` | 优先级(数值越小优先级越高) | -| `stack_addr / stack_size / sp` | 线程栈(独立运行空间,保存上下文和局部变量) | -| `entry / parameter` | 入口函数和参数 | -| `stat` | 线程状态(初始/就绪/运行/挂起/关闭) | -| `init_tick / remaining_tick` | 时间片(用于同优先级轮转) | -| `cleanup` | 退出时的清理回调 | - -### 2.4 线程状态机 - -![线程状态转换图](RTday2笔记_启动流程内核基础与线程/04thread_sta.png) - -**状态转换:** - -| 转换 | 触发条件 | -|------|----------| -| 初始 → 就绪 | `rt_thread_startup()` | -| 就绪 → 运行 | 调度器选中(最高优先级) | -| 运行 → 挂起 | `rt_thread_mdelay()` / 等待信号量/邮箱等 | -| 挂起 → 就绪 | 延时结束 / 资源可用 | -| 运行 → 关闭 | 线程 return 或 `rt_thread_exit()` | - -### 2.5 调度机制 - -- **抢占式调度**:就绪队列中最高优先级的线程获得 CPU;高优先级线程一就绪,立即抢占低优先级线程。 -- **时间片轮转(Round-Robin)**:同优先级多个线程,每个线程分一个时间片(单位 tick),时间片耗尽自动切到队列下一个线程。 -- 优先级 0~31(`RT_THREAD_PRIORITY_MAX`),31 是空闲线程(idle),优先级最低。 -- 调度切换在**内核里不可抢占**(临界区/关中断保护),保证就绪队列一致性。 - -### 2.6 时钟节拍与空闲线程 - -- **tick**:系统心跳,由 SysTick 定时器中断产生,本工程 1000 ticks/s(1ms)。 -- `rt_thread_mdelay(ms)` 延时 = 挂起当前线程 n 个 tick,期间让出 CPU。 -- **空闲线程 idle(优先级 31)**:所有用户线程挂起/结束后运行,执行钩子并进 WFI 低功耗等待,避免空转。 - ---- - -## 三、线程 - -### 3.1 动态创建 vs 静态初始化 - -![线程相关操作](RTday2笔记_启动流程内核基础与线程/04thread_ops.png) - -| 对比项 | 动态 rt_thread_create() | 静态 rt_thread_init() | -| --- | --- | --- | -| TCB 分配 | 系统从堆自动分配 | 用户提供(全局 struct rt_thread) | -| 栈分配 | 系统从堆自动分配 | 用户提供数组(如 char stack[1024]) | -| 返回值 | 返回线程句柄 rt_thread_t | 无返回值(直接操作对象) | -| 释放 | rt_thread_delete() | rt_thread_detach() | -| 适用场景 | 数量不确定、需动态管理 | 数量固定、节省动态分配开销 | - -```c -/* 动态创建 */ -rt_thread_t tid = rt_thread_create("name", entry, param, - stack_size, priority, timeslice); -if (tid) rt_thread_startup(tid); - -/* 静态初始化 */ -static struct rt_thread t; -static char t_stack[1024]; -rt_thread_init(&t, "name", entry, param, - t_stack, sizeof(t_stack), priority, timeslice); -rt_thread_startup(&t); -``` - -### 3.2 常用线程 API - -| API | 作用 | -| --- | --- | -| `rt_thread_create` / `rt_thread_init` | 创建(动态/静态) | -| `rt_thread_startup` | 启动(加入调度) | -| `rt_thread_suspend` / `rt_thread_resume` | 挂起 / 恢复 | -| `rt_thread_delay` / `rt_thread_mdelay` | 延时(tick / ms) | -| `rt_thread_delete` / `rt_thread_detach` | 销毁(动态 / 静态) | -| `rt_thread_self` | 获取当前线程句柄 | -| `rt_scheduler_sethook` | 注册调度器钩子(每次切换回调) | - -### 3.3 入口函数的两种模式 - -```c -/* 模式一:无限循环(持续任务,推荐)——必须延时让出 CPU */ -void thread_entry(void *param) -{ - while (1) - { - /* 干活 */ - rt_thread_mdelay(1000); - } -} - -/* 模式二:有限循环(一次性任务)——执行完自动退出(进关闭态) */ -void thread_entry(void *param) -{ - for (int i = 0; i < 10; i++) - { - rt_kprintf("count: %d\n", i); - } -} -``` - -### 3.4 线程栈与上下文切换 - -- 线程栈**从高地址向低地址增长**,保存:局部变量、返回地址、上下文(寄存器、PC)。 -- 切换时:当前线程上下文**保存进自己的栈** → 恢复目标线程的上下文 → 跳转执行。 -- 实现位于 `libcpu/arm/cortex-m4/context_gcc.S`(`rt_hw_context_switch*`), - Cortex-M4 借助 **PendSV 异常**完成切换,切换过程不阻塞高优先级中断。 - ---- - -## 四、简单外设(配合线程) - -### 4.1 rt_pin 引脚框架 - -```c -#include - -rt_pin_mode(GET_PIN(F, 9), PIN_MODE_OUTPUT); // 配置输出 -rt_pin_mode(GET_PIN(A, 0), PIN_MODE_INPUT_PULLUP); // 配置上拉输入 -rt_pin_write(GET_PIN(F, 9), PIN_HIGH); // 输出高 -rt_uint8_t v = rt_pin_read(GET_PIN(A, 0)); // 读取电平 -``` - -### 4.2 LED + 按键(线程化) - -```c -static void led_thread_entry(void *param) /* LED 500ms 闪烁 */ -{ - rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); - while (1) - { - rt_pin_write(LED_PIN, PIN_HIGH); - rt_thread_mdelay(500); - rt_pin_write(LED_PIN, PIN_LOW); - rt_thread_mdelay(500); - } -} - -static void key_thread_entry(void *param) /* 按键检测 + 软件消抖 */ -{ - rt_pin_mode(KEY_PIN, PIN_MODE_INPUT_PULLUP); - while (1) - { - if (rt_pin_read(KEY_PIN) == PIN_LOW) /* 按下(低电平) */ - { - rt_thread_mdelay(20); /* 消抖 */ - if (rt_pin_read(KEY_PIN) == PIN_LOW) - rt_kprintf("KEY pressed!\n"); - } - rt_thread_mdelay(10); - } -} -``` - ---- - -## 五、调试手段 - -| 手段 | 用法 | 场景 | -| --- | --- | --- | -| `rt_kprintf` | 直接打印 | 最快、最常用 | -| 日志宏 | `LOG_D/I/W/E`(需 `DBG_TAG`/`DBG_LVL` + `#include `) | 分级过滤调试信息 | -| 调度器钩子 | `rt_scheduler_sethook(hook)` | 观察线程切换、上下文切换 | -| msh 命令 | `list_thread` / `list_device` / `free` / `ps` | 实时查线程/设备/内存 | -| 导出命令 | `MSH_CMD_EXPORT(fn, desc)` | 把自己的函数变成 msh 命令 | - -```c -#define DBG_TAG "demo" -#define DBG_LVL DBG_LOG -#include - -LOG_D("debug %d", 1); -LOG_I("info"); -LOG_E("error!"); - -MSH_CMD_EXPORT(peripheral_sample, led & key sample); // msh 里敲 peripheral_sample -``` - -**调度器钩子示例:** - -```c -static void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to) -{ - rt_kprintf("from: %s --> to: %s \n", from->name, to->name); -} -rt_scheduler_sethook(hook_of_scheduler); -``` - ---- - -## 六、SCons 构建系统 - -### 6.1 为什么需要构建工具 - -传统 Makefile 语法复杂易错,导致开发者精力被配置脚本消耗。SCons 的出现就是为了**用降低配置门槛来换取开发效率**。 - -**常见构建方式对比:** - -| 构建工具 | 特点 | -|----------|------| -| GNU Make | 历史悠久,但语法混乱,Windows 下使用不便 | -| CMake | 功能强大,但需要额外生成 Makefile | -| **SCons** | 基于 Python,语法清晰,与 RT-Thread 深度集成 | - -### 6.2 SCons 简介 - -SCons 是一套由 **Python 语言**编写的开源构建系统,类似于 GNU Make。它采用 SConstruct 和 SConscript 文件来替代 Makefile。 - -> RT-Thread 选择 SCons 的唯一目的:**使大家从复杂的 Makefile 配置、IDE 配置中脱离出来,把精力集中在 RT-Thread 功能开发上。** - -### 6.3 RT-Thread 中的 SCons 脚本结构 - -``` -BSP 目录 -├── rtconfig.py ← 编译器配置文件(工具链、编译参数) -├── SConstruct ← SCons 入口脚本 -├── SConscript ← 分散在各级源码目录,控制源码构建 -└── Kconfig ← menuconfig 配置入口 -``` - -**三个核心文件的作用:** - -| 文件 | 作用 | -|------|------| -| `rtconfig.py` | 指定编译器、编译器参数(CCFLAGS、AFLAGS、LFLAGS) | -| `SConstruct` | SCons 入口脚本,初始化构建环境 | -| `SConscript` | 组织源码的主力军,每个源文件目录一个 | - -### 6.4 SCons 基本命令 - -| 命令 | 说明 | -|------|------| -| `scons` | 直接编译 BSP | -| `scons -j8` | 多线程编译(加快速度) | -| `scons -c` | 清除编译产物 | -| `scons --target=mdk5` | 生成 Keil MDK5 工程 | -| `scons --target=iar` | 生成 IAR 工程 | -| `scons --target=cmake` | 生成 CMake 工程 | -| `scons --dist` | 搭建项目框架,生成可移植的 dist 目录 | -| `scons --menuconfig` | 图形化配置系统 | -| `scons --verbose` | 显示完整编译参数 | - -### 6.5 SConscript 编写基础 - -SConscript 文件使用 Python 语法编写,核心函数包括: - -```python -from building import * # 导入 RT-Thread 自定义构建函数 - -cwd = GetCurrentDir() # 获取当前脚本路径 -CPPPATH = [cwd] # 头文件搜索路径 -src = Glob('*.c') # 获取当前目录所有 .c 文件 - -# 根据配置决定是否编译某些源文件 -if GetDepend('RT_USING_HELLO'): - src += ['hello.c'] - -# 定义一个构建组(对应 IDE 中的 Group) -group = DefineGroup('hello', src, depend=[''], CPPPATH=CPPPATH) - -Return('group') # 返回构建组给上级脚本 -``` - -### 6.6 SCons 与 Kconfig 的配合 - -RT-Thread 的构建系统由三部分组成: - -``` -┌─────────────────┐ -│ Kconfig │ ← 配置系统(声明式功能裁剪) -├─────────────────┤ -│ rtconfig.py │ ← 编译器配置(工具链、编译参数) -├─────────────────┤ -│ SCons │ ← 构建系统(条件编译、依赖感知构建) -└─────────────────┘ -``` - -- `menuconfig` 读取 Kconfig 生成 `.config` 文件 -- SCons 读取 `.config` 决定哪些源文件参与编译 -- 编译选项最终生成 `rtconfig.h` - -### 6.7 编译器配置(rtconfig.py) - -```python -# 工具链选择:gcc / keil / iar -CROSS_TOOL = 'gcc' - -# 编译器路径 -EXEC_PATH = '/usr/local/gcc-arm-none-eabi-5_4-2016q3/bin/' - -# 编译选项 -CFLAGS = '-mcpu=cortex-m3 -mthumb -ffunction-sections -fdata-sections' -AFLAGS = '-c + DEVICE + '-x assembler-with-cpp' -LFLAGS = DEVICE + '-Wl,--gc-sections,-Map=rtthread-stm32.map' - -# 优化级别:'debug' = 无优化,'release' = O2 优化 -BUILD = 'debug' -``` - -### 6.8 静态 vs 动态线程创建的选择 - -> 张丙儒指出:**静态编译具备内存占用的确定性,而动态创建因依赖系统堆,其申请时机与次数均不可控**。这本质上是用**编译期的资源规划**来换取**运行时的稳定性**。 - -**场景选择建议:** - -| 场景 | 推荐方式 | 原因 | -|------|----------|------| -| 常规应用开发 | 动态/静态均可 | 更看重便捷性,对底层内存确定性要求不严苛 | -| 数量固定的线程 | 静态 | 节省动态分配开销,内存布局更清晰 | -| 数量不确定的线程 | 动态 | 灵活管理,需要注意堆状态 | -| **军工/高安全场景** | **全静态** | 必须规避内存碎片化风险,系统的绝对可控性是压倒一切的硬指标 | - ---- - -## 七、一日速记 - -1. **启动**:复位 → Reset_Handler → main → $Sub$$main → rtthread_startup → - 创建 main 线程 → 启动调度器 → 用户 main 在线程里跑。 -2. **优先级**:数值越小越高;0~31,31 是 idle 空闲线程。 -3. **调度**:抢占(不同优先级)+ 时间片轮转(同优先级)。 -4. **状态**:初始 → 就绪 → 运行 → 挂起 → 关闭。 -5. **动态/静态**:create 用堆、delete 释放;init 用数组、detach 脱离。 -6. **延时**:mdelay = 挂起让出 CPU,空闲线程趁机运行。 -7. **外设**:rt_pin_mode/write/read 三步驱动 GPIO,外设任务线程化。 -8. **调试**:rt_kprintf + LOG + 调度器钩子 + msh 命令四件套。 -9. **构建**:scons + Kconfig + menuconfig 三位一体,SCons 基于 Python 语法清晰。 -10. **选型**:常规开发动态静态均可,高安全场景必须全静态确保内存确定性。 diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/03kernel_object2.png" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/03kernel_object2.png" deleted file mode 100644 index b1601b48686cb94abb817b7213c52e85f80cf6c4..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/03kernel_object2.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/04thread_ops.png" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/04thread_ops.png" deleted file mode 100644 index dd552117c1fd516249a1597dc4b03cf85b5c570c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/04thread_ops.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/04thread_sta.png" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/04thread_sta.png" deleted file mode 100644 index fe2cc8141fd5c220db0fca600c75bd9b8ea66a92..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/04thread_sta.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/rtt_startup.png" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/rtt_startup.png" deleted file mode 100644 index 04dbb79fed05b7463c5ceea179f5dd75c6553724..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/RTday2\347\254\224\350\256\260_\345\220\257\345\212\250\346\265\201\347\250\213\345\206\205\346\240\270\345\237\272\347\241\200\344\270\216\347\272\277\347\250\213/rtt_startup.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/thread_study.c" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/thread_study.c" deleted file mode 100644 index 22d85fbee52a6a93b4fb1a57a0e7162ed7ace618..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day2/thread_study.c" +++ /dev/null @@ -1,381 +0,0 @@ -/* - * RT-Thread 多线程编程实验 - DAY2 作业 - * - * 目标平台:STM32F407 星火号(stm32f407-rtspark) - * 包含:动态线程 + 静态线程 + 监控线程 + INIT_EXPORT + MSH 命令 - * - * 作者:马峥 | 第3组 - */ - -#include -#include -#include - -/* 日志TAG */ -#define DBG_TAG "thread_study" -#define DBG_LVL DBG_INFO -#include - -/* ===================== 硬件定义 ===================== */ -#define LED_R_PIN GET_PIN(F, 9) /* LED_R PF9 */ -#define LED_B_PIN GET_PIN(F, 10) /* LED_B PF10 */ -#define KEY_PIN GET_PIN(A, 0) /* KEY0 PA0 */ - -/* ===================== 全局变量 ===================== */ -/* th_led2 静态线程:TCB、栈、计数 */ -static struct rt_thread th_led2; -static rt_uint8_t th_led2_stack[1024]; -static rt_uint32_t led2_count = 0; - -/* th_led1 动态线程句柄(用于挂起/恢复) */ -static rt_thread_t th_led1_handle = RT_NULL; -static rt_uint32_t led1_count = 0; - -/* 线程状态统计(挂起计数,用于按键控制) */ -static rt_uint8_t th_led1_suspended = 0; - -/* ===================== 工作线程1(动态创建)===================== */ -/* - * th_led1:动态创建,优先级16,时间片5 ticks,栈1024 Bytes - * 功能:循环翻转 LED_R,每次打印计数与 rt_tick_get() - */ -static void th_led1_entry(void *parameter) -{ - (void)parameter; - - /* 配置 LED_R 为输出模式 */ - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - - while (1) - { - led1_count++; - - /* 翻转 LED_R */ - static rt_uint8_t led_state = 0; - led_state = !led_state; - rt_pin_write(LED_R_PIN, led_state ? PIN_HIGH : PIN_LOW); - - /* 打印计数与系统 tick */ - rt_kprintf("[th_led1] toggle LED_R, count=%u, tick=%u\n", - (unsigned int)led1_count, - (unsigned int)rt_tick_get()); - - /* 延时 500ms(让出 CPU,允许其他线程执行) */ - rt_thread_mdelay(500); - } -} - -/* ===================== 工作线程2(静态初始化)===================== */ -/* - * th_led2:静态初始化,优先级16,时间片5 ticks,静态TCB+全局栈 - * 功能:循环翻转 LED_B,每次打印计数 - */ -static void th_led2_entry(void *parameter) -{ - (void)parameter; - - /* 配置 LED_B 为输出模式 */ - rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); - - while (1) - { - led2_count++; - - /* 翻转 LED_B */ - static rt_uint8_t led_state = 0; - led_state = !led_state; - rt_pin_write(LED_B_PIN, led_state ? PIN_HIGH : PIN_LOW); - - /* 打印计数 */ - rt_kprintf("[th_led2] toggle LED_B, count=%u\n", - (unsigned int)led2_count); - - /* 延时 500ms */ - rt_thread_mdelay(500); - } -} - -/* ===================== 监控线程(动态创建)===================== */ -/* - * th_monitor:动态创建,优先级10(高于工作线程),周期 2000ms - * 功能:查询并打印 th_led1/th_led2 的运行状态与剩余栈空间 - * 检测按键:单次按下挂起 th_led1,再次按下恢复 th_led1 - */ -static void th_monitor_entry(void *parameter) -{ - (void)parameter; - struct rt_thread *t_led1 = RT_NULL; - struct rt_thread *t_led2 = RT_NULL; - rt_uint32_t last_key_state = 1; /* 按键上拉,默认高电平(未按下) */ - rt_uint32_t debounce_tick = 0; - - /* 配置 KEY 引脚为上拉输入 */ - rt_pin_mode(KEY_PIN, PIN_MODE_INPUT_PULLUP); - - /* 查找目标线程(监控线程优先级更高,th_led1/th_led2 已启动) */ - t_led1 = rt_thread_find("led1"); - t_led2 = rt_thread_find("led2"); - - if (!t_led1 || !t_led2) - { - LOG_E("[th_monitor] thread not found, led1=%p, led2=%p", - t_led1, t_led2); - return; - } - - rt_kprintf("[th_monitor] started, monitor period=2000ms\n"); - - while (1) - { - /* ---------- 按键检测(带消抖)---------- */ - rt_uint32_t key_level = rt_pin_read(KEY_PIN); - - if (key_level == 0 && last_key_state == 1) - { - /* 检测到下降沿(按键按下),延时消抖 */ - rt_thread_mdelay(20); - - if (rt_pin_read(KEY_PIN) == 0) - { - /* 确认按键按下 */ - if (th_led1_suspended == 0) - { - /* th_led1 当前运行,挂起它 */ - rt_err_t err = rt_thread_suspend(t_led1); - if (err == RT_EOK) - { - th_led1_suspended = 1; - rt_kprintf("[KEY] pressed, suspend th_led1\n"); - } - else - { - rt_kprintf("[KEY] suspend failed: %d\n", err); - } - } - else - { - /* th_led1 当前挂起,恢复它 */ - rt_err_t err = rt_thread_resume(t_led1); - if (err == RT_EOK) - { - th_led1_suspended = 0; - rt_kprintf("[KEY] pressed, resume th_led1\n"); - } - else - { - rt_kprintf("[KEY] resume failed: %d\n", err); - } - } - } - } - last_key_state = key_level; - - /* ---------- 打印线程状态与剩余栈空间 ---------- */ - /* 获取当前 tick */ - rt_tick_t now_tick = rt_tick_get(); - - /* 获取线程状态字符串 */ - const char *get_thread_stat_str(rt_uint8_t stat) - { - switch (stat) - { - case RT_THREAD_READY: return "READY "; - case RT_THREAD_SUSPEND: return "SUSPEND "; - case RT_THREAD_INIT: return "INIT "; - case RT_THREAD_CLOSE: return "CLOSE "; - default: return "UNKNOWN "; - } - } - - /* 打印分隔线 */ - rt_kprintf("\n========== [tick=%u] Thread Status ==========\n", - (unsigned int)now_tick); - - /* th_led1 状态 */ - if (t_led1) - { - rt_kprintf("[THREAD] th_led1: count=%u, stat=%s, priority=%u, " - "timeslice_left=%u, stack_left=~%d\n", - (unsigned int)led1_count, - get_thread_stat_str(t_led1->stat), - (unsigned int)t_led1->current_priority, - (unsigned int)t_led1->remaining_tick, - (int)(t_led1->stack_size - (rt_uint32_t) - (t_led1->sp - (void *)t_led1->stack_addr))); - } - - /* th_led2 状态 */ - if (t_led2) - { - rt_kprintf("[THREAD] th_led2: count=%u, stat=%s, priority=%u, " - "timeslice_left=%u, stack_left=~%d\n", - (unsigned int)led2_count, - get_thread_stat_str(t_led2->stat), - (unsigned int)t_led2->current_priority, - (unsigned int)t_led2->remaining_tick, - (int)(t_led2->stack_size - (rt_uint32_t) - (t_led2->sp - (void *)t_led2->stack_addr))); - } - - rt_kprintf("==============================================\n\n"); - - /* 周期延时 2000ms(让出 CPU) */ - rt_thread_mdelay(2000); - } -} - -/* ===================== MSH 命令:thread_dump ===================== */ -/* - * 功能:导出为 msh 命令,用户可在 FinSH 终端执行 thread_dump 查看线程状态 - * 用法:msh >thread_dump - */ -static void thread_dump(void) -{ - rt_kprintf("\n========== thread_dump ==========\n"); - rt_kprintf(" %-12s | %-8s | %-8s | %-10s | %-10s | %-10s\n", - "thread", "priority", "state", "sp", "stack_size", "stack_left"); - rt_kprintf("------------|----------|----------|------------|------------|------------\n"); - - /* 遍历内核对象容器的线程链表 */ - struct rt_object *object; - struct rt_list_node *node; - - for (node = rt_object_get_next(RT_NULL); node != RT_NULL; node = rt_list_next(node)) - { - object = (struct rt_object *)rt_list_entry(node, struct rt_object, list); - - /* 仅处理线程对象 */ - if (object->type == RT_Object_Class_Thread) - { - struct rt_thread *t = (struct rt_thread *)object; - - /* 计算栈使用量(粗略估算) */ - int stack_used = (int)((rt_uint32_t)t->stack_size - - ((rt_uint32_t)t->sp - (rt_uint32_t)t->stack_addr)); - - /* 状态转字符串 */ - const char *stat_str; - switch (t->stat) - { - case RT_THREAD_READY: stat_str = "READY"; break; - case RT_THREAD_SUSPEND: stat_str = "SUSPEND"; break; - case RT_THREAD_RUNNING: stat_str = "RUNNING"; break; - case RT_THREAD_INIT: stat_str = "INIT"; break; - case RT_THREAD_CLOSE: stat_str = "CLOSE"; break; - default: stat_str = "UNKNOWN"; break; - } - - rt_kprintf(" %-12s | %-8u | %-8s | 0x%08X | %-10u | %-10d\n", - t->name, - (unsigned int)t->current_priority, - stat_str, - (unsigned int)t->sp, - (unsigned int)t->stack_size, - stack_used); - } - } - - rt_kprintf("==================================\n\n"); -} -/* 导出到 msh 命令 */ -MSH_CMD_EXPORT(thread_dump, dump all threads status); - -/* ===================== 模块初始化 ===================== */ -/* - * 功能:创建并启动所有工作线程和监控线程 - * 触发方式:通过 INIT_APP_EXPORT() 自动调用(无需在 main.c 中手动调用) - */ -static int thread_module_init(void) -{ - rt_err_t err; - - LOG_INFO("thread module init"); - rt_kprintf("[SYSTEM] thread module init\n"); - - /* ---- 1. 创建并启动工作线程1(动态创建) ---- */ - th_led1_handle = rt_thread_create("led1", - th_led1_entry, - RT_NULL, - 1024, /* 栈大小 1024 Bytes */ - 16, /* 优先级 16 */ - 5); /* 时间片 5 ticks */ - - if (th_led1_handle != RT_NULL) - { - err = rt_thread_startup(th_led1_handle); - if (err == RT_EOK) - { - rt_kprintf("[SYSTEM] th_led1 started (dynamic), " - "priority=16, timeslice=5, stack=1024\n"); - } - else - { - LOG_E("[SYSTEM] th_led1 startup failed: %d", err); - } - } - else - { - LOG_E("[SYSTEM] th_led1 create failed"); - } - - /* ---- 2. 初始化并启动工作线程2(静态初始化) ---- */ - err = rt_thread_init(&th_led2, - "led2", - th_led2_entry, - RT_NULL, - th_led2_stack, - sizeof(th_led2_stack), - 16, /* 优先级 16 */ - 5); /* 时间片 5 ticks */ - - if (err == RT_EOK) - { - err = rt_thread_startup(&th_led2); - if (err == RT_EOK) - { - rt_kprintf("[SYSTEM] th_led2 started (static), " - "priority=16, timeslice=5, stack=%u\n", - (unsigned int)sizeof(th_led2_stack)); - } - else - { - LOG_E("[SYSTEM] th_led2 startup failed: %d", err); - } - } - else - { - LOG_E("[SYSTEM] th_led2 init failed: %d", err); - } - - /* ---- 3. 创建并启动监控线程(动态创建) ---- */ - rt_thread_t th_monitor_handle = rt_thread_create("monitor", - th_monitor_entry, - RT_NULL, - 1024, /* 栈大小 1024 Bytes */ - 10, /* 优先级 10(高于工作线程)*/ - 10); /* 时间片 10 ticks */ - - if (th_monitor_handle != RT_NULL) - { - err = rt_thread_startup(th_monitor_handle); - if (err == RT_EOK) - { - rt_kprintf("[SYSTEM] th_monitor started, priority=10\n"); - } - else - { - LOG_E("[SYSTEM] th_monitor startup failed: %d", err); - } - } - else - { - LOG_E("[SYSTEM] th_monitor create failed"); - } - - return 0; -} -/* - * INIT_APP_EXPORT:应用初始化宏,将 thread_module_init 注册到自动初始化链表 - * 执行时机:最晚(优先级6),在内核组件(FinSH等)初始化之后,用户 main 之前 - */ -INIT_APP_EXPORT(thread_module_init); diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/README.md" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/README.md" deleted file mode 100644 index 6372a4687fd78ea2f808e9f13d31fe5974f84ddd..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/README.md" +++ /dev/null @@ -1,321 +0,0 @@ -# RT-Thread 线程间同步与通信 — DAY3 作业 - -- **目标平台**:STM32F407 星火号(`stm32f407-rtspark`) -- **提交仓库**:`https://gitee.com/rtthread/rsoc-rtt` -- **学生**:马峥 | 第3组 - ---- - -## 一、理论知识梳理 - -### 1.1 IPC 概述 - -RT-Thread 提供丰富的线程间通信与同步机制,简称 IPC(Inter-Process Communication / Inter-Thread Communication)。本章重点掌握四种核心 IPC: - -| IPC 机制 | 本质 | 适用场景 | -|----------|------|----------| -| **信号量(Semaphore)** | 计数器,控制对共享资源的访问数量 | 资源计数(停车场、多生产者/消费者) | -| **互斥量(Mutex)** | 二值锁,解决优先级反转问题 | 保护临界区,防止数据竞争 | -| **事件集(Event)** | 多位标志组合,等待指定事件组合 | 多条件同步(AND/OR 模式) | -| **消息队列(Message Queue)** | FIFO 消息缓存区 | 异步数据传递,线程解耦 | - -### 1.2 信号量(Semaphore) - -**原理**:内部维护一个计数器 `value`,`take` 使计数器减 1(阻塞或返回错误), -`release` 使计数器加 1(唤醒等待线程)。 - -- **二值信号量**:计数器只能是 0 或 1,作用类似互斥量(但不支持优先级继承)。 -- **计数信号量**:计数器允许多值,适合"资源池"场景(如 N 个车位、N 个缓冲区)。 - -```c -/* 创建计数信号量(初始值=3) */ -rt_sem_t sem = rt_sem_create("sem", 3, RT_IPC_FLAG_FIFO); - -/* 获取信号量(阻塞等待) */ -rt_sem_take(sem, RT_WAITING_FOREVER); - -/* 获取信号量(超时等待) */ -rt_err_t err = rt_sem_take(sem, rt_tick_from_millisecond(2000)); - -/* 释放信号量 */ -rt_sem_release(sem); - -/* 删除信号量 */ -rt_sem_delete(sem); -``` - -**与互斥量的区别**:互斥量是二值的,且支持优先级继承防止优先级反转; -计数信号量可以是任意非负整数,适合资源计数场景。 - -### 1.3 互斥量(Mutex) - -**原理**:同一时刻只允许一个线程持有锁,持有期间其他线程 `take` 会阻塞。 - -**关键特性**: -- **优先级继承**:当高优先级线程等待低优先级线程持有的互斥量时, - 持有互斥量的低优先级线程临时提升优先级,避免中等优先级线程"插队"导致 - 高优先级线程长时间等待(优先级反转问题的解决方案)。 -- **不可递归持有**:同一线程不能对已持有的互斥量再次 `take`(会死锁)。 - -```c -rt_mutex_t mutex = rt_mutex_create("mutex", RT_IPC_FLAG_FIFO); - -rt_mutex_take(mutex, RT_WAITING_FOREVER); /* 加锁 */ -// ... 临界区代码 ... -rt_mutex_release(mutex); /* 解锁 */ - -rt_mutex_delete(mutex); -``` - -### 1.4 事件集(Event) - -**原理**:每个事件用一个 bit 表示,线程可以等待多个事件位同时到达(AND 模式) -或任意一个到达(OR 模式)。 - -```c -/* 定义事件位 */ -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) - -/* 创建事件集 */ -rt_event_t evt = rt_event_create("evt", RT_IPC_FLAG_FIFO); - -/* 发送事件(设置对应 bit) */ -rt_event_send(evt, EVENT_NET_READY); - -/* 接收事件(AND 模式:等待所有指定位都就绪) */ -rt_event_recv(evt, - EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - timeout, - &recved); -``` - -**与三个独立信号量的区别**: -- 独立信号量:需要维护 3 个信号量对象,每个 `take` 单独判断,逻辑复杂; -- 事件集:一个对象管理多位标志,`AND` 模式一次等待所有条件,原生支持组合判断。 - -### 1.5 消息队列(Message Queue) - -**原理**:线程通过 FIFO 消息队列传递数据,发送线程 `send` 将数据放入队列尾部, -接收线程 `recv` 从队列头部取出数据。队列满时 `send` 可选阻塞/非阻塞,队列空时 -`recv` 阻塞等待。 - -```c -struct package_msg { int id; int region; int weight; }; - -/* 创建消息队列(每条消息大小 sizeof(struct),最多 5 条) */ -rt_mq_t mq = rt_mq_create("mq", sizeof(struct package_msg), 5, RT_IPC_FLAG_FIFO); - -/* 发送消息(非阻塞,队列满则立即返回错误) */ -rt_mq_send(mq, &msg, sizeof(msg)); - -/* 接收消息(阻塞) */ -rt_mq_recv(mq, &recv_msg, sizeof(recv_msg), RT_WAITING_FOREVER); - -/* 删除 */ -rt_mq_delete(mq); -``` - ---- - -## 二、练习一:停车场车位管理 - -### 2.1 设计说明 - -- **IPC 机制**:计数信号量 `sem_parking`,初始值 = 3 -- **线程**:5 个车辆线程(`car1`~`car5`),优先级 15 -- **逻辑**:`rt_sem_take(sem, 2000ms)` 申请车位 → 停留 1~3 秒 → `rt_sem_release()` 释放 - -### 2.2 源码文件 - -`ipc_parking.c` - -### 2.3 串口输出示例 - -``` -[parking_demo] sem_parking created, spaces=3 -[parking_demo] all 5 car threads started -car1 arrived -car1 entered, used spaces: 1 -car2 arrived -car2 entered, used spaces: 2 -car3 arrived -car3 entered, used spaces: 3 -car4 arrived -car4 waiting for a space (timeout), will retry -car5 arrived -car5 waiting for a space (timeout), will retry -car2 left -car4 entered, used spaces: 3 -car1 left -... -``` - -### 2.4 思考题解答 - -**Q:为什么使用计数信号量,而不是互斥量?** - -A:互斥量只能表示"有/无"两种状态(二值),无法表达"3个可用车位"的资源计数场景。 -计数信号量的计数器可以取任意非负整数,正好对应"剩余车位数",每次 `take` 使计数减 1, -每次 `release` 使计数加 1,这是互斥量无法实现的功能。 - ---- - -## 三、练习二:多窗口售票系统 - -### 3.1 设计说明 - -- **IPC 机制**:互斥量 `mutex_ticket` -- **线程**:`winA` 和 `winB`,优先级 12 -- **对比实验**: - - 测试 1(无锁):读取 → `delay(1ms)` → 写入,演示并发冲突 - - 测试 2(有锁):`mutex_take` → 检查 → 延时 → 减票 → `mutex_release` - -### 3.2 源码文件 - -`ipc_ticket.c` - -### 3.3 对比实验结果 - -**测试 1(无锁)**: -``` -[NO LOCK] window A sold ticket 20 -[NO LOCK] window B sold ticket 20 ← ⚠️ 票号重复! -[NO LOCK] window A sold ticket 19 -... -[RESULT] final ticket_count=0 (expected 0) -[WARNING] ticket_count != 0, race condition detected! -``` - -**测试 2(有锁)**: -``` -[WITH LOCK] window A sold ticket 20 -[WITH LOCK] window B sold ticket 19 -[WITH LOCK] window A sold ticket 18 -... -[RESULT] final ticket_count=0 (expected 0) -``` - -### 3.4 思考题解答 - -**Q:为什么只给 `ticket_count--` 加锁仍可能不安全?** - -A:因为 `ticket_count--` 本身不是原子操作,它包含三步: -1. 读取 `ticket_count` 到寄存器 -2. 寄存器值减 1 -3. 写回 `ticket_count` - -如果在第 1 步和第 3 步之间发生线程切换,另一个线程也执行同样的三步, -两个线程会读到相同的旧值,导致"丢失更新"(Lost Update)问题。 -必须把"检查余票 → 延时 → 减票 → 打印"整个过程都锁住。 - ---- - -## 四、练习三:系统启动条件检查 - -### 4.1 设计说明 - -- **IPC 机制**:事件集 `event_startup`,AND 模式等待 -- **线程**: - - `net_init`(延时 800ms)→ 发 `EVENT_NET_READY` - - `sensor_init`(延时 1500ms)→ 发 `EVENT_SENSOR_READY` - - `storage_init`(延时 5000ms)→ 发 `EVENT_STORAGE_READY` - - `business`(优先级最高 8)→ 等待 ALL_EVENTS,超时 3000ms - -### 4.2 源码文件 - -`ipc_event.c` - -### 4.3 串口输出示例 - -``` -[startup_demo] all init threads started -business thread: waiting for all modules... -network ready -sensor ready -wait modules timeout, storage is not ready ← 第一次等待超时(等3s,storage需5s) -business thread: waiting for all modules... -storage ready -all modules are ready -business task started -``` - -### 4.4 思考题解答 - -**Q:用三个独立信号量实现,与事件集有什么区别?** - -A:独立信号量需要 3 个 `rt_sem_take()`,每次调用后需自行判断其他信号量状态, -逻辑复杂且容易出错;事件集的 `AND` 模式一次等待多个标志位,代码简洁, -且 `rt_event_recv` 返回值明确区分"成功"和"超时",语义清晰。 - ---- - -## 五、练习四:快递分拣中心 - -### 5.1 设计说明 - -- **IPC 机制**:消息队列 `mq_package`,容量 5 条 -- **线程**: - - `sender1`、`sender2`(优先级 12):各产生 5 条快递消息 - - `sorter`(优先级 9):接收消息并分类统计 -- **消息结构**:`{id, region(1=华东/2=华南/3=华北), weight}` -- **结束标记**:两个发送线程结束后,各发一条 `{0,0,0}` 结束标记 - -### 5.2 源码文件 - -`ipc_mailbox.c` - -### 5.3 串口输出示例 - -``` -[sender1] sent pkg (region=east, weight=1200 g) -[sender2] sent pkg (region=south, weight=850 g) -[sorter] received end marker (1/2) -[sorter] received end marker (2/2) -[sorter] received end marker (2/2) ← 等待结束时收到了队列中的残留消息 - ---- Sorting Statistics --- -total packages: 10 -east: 4, south: 3, north: 3 -total weight: 10250 g -send failed: 0 -------------------------- -``` - -### 5.4 队列满处理 - -当消息队列已满(5条)时,`rt_mq_send()` 返回 `RT_EFULL`,记录 `send_failed++` 并重试, -不会崩溃。 - ---- - -## 六、MSH 命令汇总 - -| 命令 | 文件 | 说明 | -|------|------|------| -| `parking_demo` | `ipc_parking.c` | 停车场(计数信号量) | -| `ticket_demo` | `ipc_ticket.c` | 售票系统(互斥量+对比实验) | -| `startup_demo` | `ipc_event.c` | 启动条件检查(事件集) | -| `package_demo` | `ipc_mailbox.c` | 快递分拣(消息队列) | - -**防重复执行**:各命令执行前通过 `rt_thread_find()` 检测同名线程是否已存在, -若存在则打印警告并拒绝重复启动。 - ---- - -## 七、提交说明 - -``` -2026/第3组/马峥/day3/ -├── README.md # 理论梳理 + 实验结果(本文档) -├── ipc_parking.c # 练习一源码 -├── ipc_ticket.c # 练习二源码 -├── ipc_event.c # 练习三源码 -└── ipc_mailbox.c # 练习四源码 -``` - -**PR 提交**: -- 分支名:`day3-mazheng` -- 标题:`[作业提交] 线程同步与通信培训 - 马峥` diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_event.c" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_event.c" deleted file mode 100644 index 393526524f96c0ce44f675feafbecf06b220e1f1..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_event.c" +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 练习三:系统启动条件检查 - * 机制:事件集(rt_event)+ AND 模式等待 - * - * 场景:3个模块(网络/传感器/存储)初始化,主业务等待全部就绪 - * 原理:rt_event 等待多个事件位(AND 模式),超时重试 - */ - -#include - -/* 事件位定义 */ -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define ALL_EVENTS (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -/* 初始化延时配置(毫秒) */ -#define NET_INIT_DELAY_MS 800 -#define SENSOR_INIT_DELAY_MS 1500 -#define STORAGE_INIT_DELAY_MS 5000 /* 故意设长,触发超时 */ - -#define BUSINESS_WAIT_MS 3000 /* 主业务最多等 3 秒 */ - -/* 事件集对象 */ -static rt_event_t event_startup = RT_NULL; - -/* 标记各模块是否已就绪 */ -static rt_uint8_t net_ready = 0; -static rt_uint8_t sensor_ready = 0; -static rt_uint8_t storage_ready = 0; - -/* ========== 初始化线程 ========== */ -static void net_init_thread(void *parameter) -{ - (void)parameter; - rt_thread_mdelay(NET_INIT_DELAY_MS); - net_ready = 1; - rt_event_send(event_startup, EVENT_NET_READY); - rt_kprintf("network ready\n"); -} - -static void sensor_init_thread(void *parameter) -{ - (void)parameter; - rt_thread_mdelay(SENSOR_INIT_DELAY_MS); - sensor_ready = 1; - rt_event_send(event_startup, EVENT_SENSOR_READY); - rt_kprintf("sensor ready\n"); -} - -static void storage_init_thread(void *parameter) -{ - (void)parameter; - rt_thread_mdelay(STORAGE_INIT_DELAY_MS); - storage_ready = 1; - rt_event_send(event_startup, EVENT_STORAGE_READY); - rt_kprintf("storage ready\n"); -} - -/* ========== 主业务线程 ========== */ -static void business_thread(void *parameter) -{ - (void)parameter; - rt_err_t err; - rt_uint32_t recved; - - while (1) - { - rt_kprintf("business thread: waiting for all modules...\n"); - - /* 等待所有三个事件(AND 模式),超时 BUSINESS_WAIT_MS */ - err = rt_event_recv(event_startup, - ALL_EVENTS, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - rt_tick_from_millisecond(BUSINESS_WAIT_MS), - &recved); - - if (err == RT_EOK) - { - /* 所有事件都到达了 */ - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - break; - } - else if (err == -RT_ETIMEOUT) - { - /* 超时,检查哪些模块还未就绪 */ - rt_kprintf("wait modules timeout, "); - if (!net_ready) rt_kprintf("network "); - if (!sensor_ready) rt_kprintf("sensor "); - if (!storage_ready) rt_kprintf("storage "); - rt_kprintf("not ready\n"); - - /* 继续等待(重新触发 rt_event_recv) */ - } - } -} - -/* ========== 线程栈和 TCB ========== */ -static struct rt_thread net_thread, sensor_thread, storage_thread, business; -static rt_uint8_t net_stack[512], sensor_stack[512], storage_stack[512], business_stack[512]; - -/* ========== MSH 命令入口 ========== */ -static int startup_demo(void) -{ - rt_thread_t t; - - /* 防止重复执行 */ - t = rt_thread_find("net_init"); - if (t != RT_NULL || rt_thread_find("business") != RT_NULL) - { - rt_kprintf("[startup_demo] demo already running, skip\n"); - return -1; - } - - /* 重置就绪标记 */ - net_ready = 0; sensor_ready = 0; storage_ready = 0; - - /* 创建事件集 */ - event_startup = rt_event_create("evt_startup", RT_IPC_FLAG_FIFO); - if (event_startup == RT_NULL) - { - rt_kprintf("[startup_demo] ERROR: event create failed\n"); - return -1; - } - - rt_err_t err; - - /* 创建网络初始化线程 */ - err = rt_thread_init(&net_thread, "net_init", net_init_thread, - RT_NULL, net_stack, sizeof(net_stack), 10, 5); - if (err != RT_EOK) { rt_kprintf("net_init init failed\n"); return -1; } - rt_thread_startup(&net_thread); - - /* 创建传感器初始化线程 */ - err = rt_thread_init(&sensor_thread, "sensor_init", sensor_init_thread, - RT_NULL, sensor_stack, sizeof(sensor_stack), 10, 5); - if (err != RT_EOK) { rt_kprintf("sensor_init init failed\n"); return -1; } - rt_thread_startup(&sensor_thread); - - /* 创建存储初始化线程 */ - err = rt_thread_init(&storage_thread, "storage_init", storage_init_thread, - RT_NULL, storage_stack, sizeof(storage_stack), 10, 5); - if (err != RT_EOK) { rt_kprintf("storage_init init failed\n"); return -1; } - rt_thread_startup(&storage_thread); - - /* 创建主业务线程 */ - err = rt_thread_init(&business, "business", business_thread, - RT_NULL, business_stack, sizeof(business_stack), 8, 10); - if (err != RT_EOK) { rt_kprintf("business init failed\n"); return -1; } - rt_thread_startup(&business); - - rt_kprintf("[startup_demo] all init threads started\n"); - return 0; -} -MSH_CMD_EXPORT(startup_demo, system startup check with event set); diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_mailbox.c" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_mailbox.c" deleted file mode 100644 index b1068ca3cf811b11a6aa1314886ba85299842e58..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_mailbox.c" +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 练习四:快递分拣中心 - * 机制:消息队列(rt_mq)+ 完成信号量 - * - * 场景:2个收件线程产生快递,1个分拣线程分类统计 - * 原理:消息队列异步传递 package_msg 结构体,完成信号量同步线程退出 - */ - -#include - -#define MQ_MAX_MSGS 5 /* 消息队列容量 */ -#define SENDER_COUNT 2 /* 收件线程数 */ -#define MSGS_PER_SENDER 5 /* 每个线程发 5 条消息 */ -#define MQ_MSG_SIZE 8 /* package_msg 结构体大小(8字节对齐) */ - -/* 快递消息结构体 */ -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; /* 1=华东, 2=华南, 3=华北 */ - rt_uint16_t weight; /* 重量(克) */ -}; - -/* 结束消息标记 */ -#define MSG_END_MARKER { 0, 0, 0 } - -/* ========== 区域名称 ========== */ -static const char *region_name(rt_uint8_t region) -{ - switch (region) - { - case 1: return "east"; - case 2: return "south"; - case 3: return "north"; - default: return "unknown"; - } -} - -/* ========== 全局统计 ========== */ -static rt_uint32_t total_pkgs = 0; -static rt_uint32_t east_count = 0; -static rt_uint32_t south_count = 0; -static rt_uint32_t north_count = 0; -static rt_uint32_t total_weight = 0; -static rt_uint32_t send_failed = 0; - -/* ========== IPC 对象(静态初始化) ========== */ -/* 消息队列:静态内存池 */ -static struct rt_messagequeue mq_pkg; -static rt_uint8_t mq_pool[MQ_MAX_MSGS * MQ_MSG_SIZE]; - -/* 完成信号量:等待收件线程全部完成 */ -static struct rt_semaphore sem_done; - -/* ========== 收件线程入口 ========== */ -static void sender_entry(void *parameter) -{ - rt_uint32_t sender_no = (rt_uint32_t)parameter; - - for (int i = 0; i < MSGS_PER_SENDER; i++) - { - struct package_msg msg; - - msg.id = 0; /* 由分拣线程分配实际编号 */ - msg.region = (sender_no == 1) ? ((i % 3) + 1) : (((i + 1) % 3) + 1); - msg.weight = 500 + (rt_uint16_t)((sender_no * 100 + i * 37) % 1500); - - rt_err_t err = rt_mq_send(&mq_pkg, &msg, sizeof(msg)); - - if (err == RT_EOK) - { - rt_kprintf("[sender%u] sent pkg (region=%s, weight=%u g)\n", - sender_no, region_name(msg.region), msg.weight); - } - else - { - send_failed++; - rt_kprintf("[sender%u] send FAILED (queue full), failed=%u\n", - sender_no, send_failed); - i--; /* 重试本次 */ - rt_thread_mdelay(10); - } - - rt_thread_mdelay(100); /* 模拟产生间隔 */ - } - - rt_kprintf("[sender%u] all %d packages sent\n", sender_no, MSGS_PER_SENDER); - - /* 发件完成,释放完成信号量 */ - rt_sem_release(&sem_done); -} - -/* ========== 分拣线程入口 ========== */ -static void sorter_entry(void *parameter) -{ - (void)parameter; - rt_err_t err; - struct package_msg msg; - rt_uint32_t end_markers_received = 0; - const rt_uint32_t need_end_markers = SENDER_COUNT; - rt_uint32_t next_pkg_id = 1000; - - while (end_markers_received < need_end_markers) - { - err = rt_mq_recv(&mq_pkg, &msg, sizeof(msg), RT_WAITING_FOREVER); - - if (err != RT_EOK) - continue; - - /* 检查结束标记 */ - if (msg.id == 0 && msg.region == 0 && msg.weight == 0) - { - end_markers_received++; - rt_kprintf("[sorter] received end marker (%u/%u)\n", - end_markers_received, need_end_markers); - continue; - } - - next_pkg_id++; - msg.id = next_pkg_id; - - /* 统计 */ - total_pkgs++; - total_weight += msg.weight; - - switch (msg.region) - { - case 1: east_count++; break; - case 2: south_count++; break; - case 3: north_count++; break; - default: break; - } - - rt_kprintf("package %u -> %s, weight: %u g\n", - msg.id, region_name(msg.region), msg.weight); - } - - /* 打印统计结果 */ - rt_kprintf("\n--- Sorting Statistics ---\n"); - rt_kprintf("total packages: %u\n", total_pkgs); - rt_kprintf("east: %u, south: %u, north: %u\n", - east_count, south_count, north_count); - rt_kprintf("total weight: %u g\n", total_weight); - rt_kprintf("send failed: %u\n", send_failed); - rt_kprintf("-------------------------\n"); - - rt_mq_detach(&mq_pkg); -} - -/* ========== 线程控制块(需对齐) ========== */ -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif -static rt_uint8_t sender_stacks[SENDER_COUNT][768]; -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif -static rt_uint8_t sorter_stack[768]; - -static struct rt_thread sender_threads[SENDER_COUNT]; -static struct rt_thread sorter_thread; - -/* ========== MSH 命令入口 ========== */ -int package_demo(void) -{ - rt_thread_t t; - rt_err_t err; - - /* 防止重复执行 */ - t = rt_thread_find("sorter"); - if (t != RT_NULL) - { - rt_kprintf("[package_demo] demo already running, skip\n"); - return -1; - } - - /* 重置统计 */ - total_pkgs = total_weight = send_failed = 0; - east_count = south_count = north_count = 0; - - /* 初始化完成信号量(初始值 0) */ - err = rt_sem_init(&sem_done, "sem_done", 0, RT_IPC_FLAG_PRIO); - if (err != RT_EOK) - { - rt_kprintf("[package_demo] ERROR: sem init failed: %d\n", err); - return -1; - } - - /* 初始化消息队列(静态) */ - err = rt_mq_init(&mq_pkg, "mq_pkg", - &mq_pool[0], - MQ_MSG_SIZE, - sizeof(mq_pool), - RT_IPC_FLAG_PRIO); - if (err != RT_EOK) - { - rt_kprintf("[package_demo] ERROR: mq init failed: %d\n", err); - return -1; - } - - /* 创建分拣线程(优先级高于收件线程) */ - err = rt_thread_init(&sorter_thread, "sorter", sorter_entry, - RT_NULL, sorter_stack, sizeof(sorter_stack), 9, 10); - if (err != RT_EOK) { rt_kprintf("sorter init failed\n"); return -1; } - rt_thread_startup(&sorter_thread); - - /* 创建 2 个收件线程 */ - for (int i = 0; i < SENDER_COUNT; i++) - { - char name[10]; - rt_snprintf(name, sizeof(name), "sender%d", i + 1); - - err = rt_thread_init(&sender_threads[i], name, sender_entry, - (void *)(rt_uint32_t)(i + 1), - sender_stacks[i], sizeof(sender_stacks[i]), - 12, 5); - if (err != RT_EOK) { rt_kprintf("%s init failed\n", name); continue; } - rt_thread_startup(&sender_threads[i]); - } - - /* 主线程:等待所有收件线程完成(SENDER_COUNT 次 sem take) */ - for (int i = 0; i < SENDER_COUNT; i++) - { - rt_sem_take(&sem_done, RT_WAITING_FOREVER); - } - - /* 向分拣线程发送结束标记 */ - struct package_msg end_msg = MSG_END_MARKER; - for (int i = 0; i < SENDER_COUNT; i++) - { - rt_mq_send(&mq_pkg, &end_msg, sizeof(end_msg)); - } - - rt_sem_detach(&sem_done); - rt_kprintf("[package_demo] all sender threads finished, end markers sent\n"); - return 0; -} -MSH_CMD_EXPORT(package_demo, express package sorting with message queue); diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_parking.c" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_parking.c" deleted file mode 100644 index 2db9ac8f9a296f40281acafb623554fffe5da271..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_parking.c" +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 练习一:停车场车位管理 - * 机制:计数信号量(rt_sem) - * - * 场景:3个车位,5辆车竞争 - * 原理:计数信号量初始值=3,每辆车 take(-1),离开时 release(+1) - */ - -#include - -#define PARKING_SPACES 3 -#define CAR_COUNT 5 -#define WAIT_TIMEOUT_MS 2000 - -/* 计数信号量(静态初始化) */ -static struct rt_semaphore sem_parking; - -/* 当前使用车位数(用于打印) */ -static rt_uint8_t used_spaces = 0; - -/* 线程栈(需要字节对齐) */ -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif -static char car_stacks[CAR_COUNT][512]; - -static struct rt_thread car_threads[CAR_COUNT]; - -/* 车辆入口 */ -static void car_entry(void *parameter) -{ - rt_uint32_t car_no = (rt_uint32_t)parameter; - - while (1) - { - rt_kprintf("car%d arrived\n", car_no); - - /* 尝试获取车位,等待 WAIT_TIMEOUT_MS ms */ - rt_tick_t tick = RT_TICK_PER_SECOND * WAIT_TIMEOUT_MS / 1000; - rt_err_t err = rt_sem_take(&sem_parking, tick); - - if (err == RT_EOK) - { - /* 获得车位,进入 */ - used_spaces++; - rt_kprintf("car%d entered, used spaces: %d\n", car_no, used_spaces); - - /* 停留 1~3 秒后离开 */ - rt_thread_mdelay(1000 + (car_no % 3) * 1000); - - /* 离开,释放车位 */ - used_spaces--; - rt_kprintf("car%d left\n", car_no); - rt_sem_release(&sem_parking); - } - else - { - /* 等待超时,稍后重试 */ - rt_kprintf("car%d waiting for a space (timeout), will retry\n", car_no); - rt_thread_mdelay(500); - } - } -} - -/* MSH 命令入口 */ -int parking_demo(void) -{ - rt_err_t err; - rt_thread_t t; - - /* 防止重复执行 */ - for (int i = 0; i < CAR_COUNT; i++) - { - char name[12]; - rt_snprintf(name, sizeof(name), "car%d", i + 1); - t = rt_thread_find(name); - if (t != RT_NULL) - { - rt_kprintf("[parking_demo] warning: thread %s already exists, skip\n", name); - return -1; - } - } - - /* 初始化计数信号量 */ - err = rt_sem_init(&sem_parking, "sem_park", PARKING_SPACES, RT_IPC_FLAG_PRIO); - if (err != RT_EOK) - { - rt_kprintf("[parking_demo] ERROR: sem init failed: %d\n", err); - return -1; - } - rt_kprintf("[parking_demo] sem_parking init, spaces=%d\n", PARKING_SPACES); - - /* 创建 5 个车辆线程 */ - for (int i = 0; i < CAR_COUNT; i++) - { - char name[12]; - rt_snprintf(name, sizeof(name), "car%d", i + 1); - - err = rt_thread_init(&car_threads[i], - name, - car_entry, - (void *)(rt_uint32_t)(i + 1), - &car_stacks[i], - sizeof(car_stacks[i]), - 15, - 10); - if (err != RT_EOK) - { - rt_kprintf("[parking_demo] ERROR: car%d thread init failed: %d\n", i + 1, err); - continue; - } - - err = rt_thread_startup(&car_threads[i]); - if (err != RT_EOK) - { - rt_kprintf("[parking_demo] ERROR: car%d thread startup failed: %d\n", i + 1, err); - } - } - - rt_kprintf("[parking_demo] all %d car threads started\n", CAR_COUNT); - return 0; -} -MSH_CMD_EXPORT(parking_demo, parking lot management with counting semaphore); diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_ticket.c" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_ticket.c" deleted file mode 100644 index e4fef286aa86548896d2c1593e5041bad2a263c1..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/day3/ipc_ticket.c" +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 练习二:多窗口售票系统 - * 机制:互斥量(rt_mutex)+ 对比实验(无锁 / 有锁) - * - * 场景:20张票,2个窗口同时出售 - * 原理:互斥量保护"检查-减票-打印"原子操作 - */ - -#include - -#define TOTAL_TICKETS 20 -#define WINDOW_A 0 -#define WINDOW_B 1 - -/* ========== 全局变量 ========== */ -static volatile rt_int32_t ticket_count = TOTAL_TICKETS; -static volatile rt_int32_t ticket_no = TOTAL_TICKETS; - -/* 互斥量(静态初始化) */ -static struct rt_mutex mutex_ticket; - -/* ========== 无锁版本(有并发问题) ========== */ -static void window_entry_no_lock(void *parameter) -{ - rt_uint32_t window = (rt_uint32_t)parameter; - - while (1) - { - if (ticket_count <= 0) - break; - - rt_int32_t cur_count = ticket_count; - rt_int32_t cur_no = ticket_no; - - rt_thread_mdelay(1); /* 短暂延时,放大并发冲突窗口 */ - - cur_count--; - cur_no--; - - ticket_count = cur_count; - ticket_no = cur_no; - - const char *name = (window == WINDOW_A) ? "A" : "B"; - rt_kprintf("[NO LOCK] window %s sold ticket %d\n", name, cur_no + 1); - } - - const char *name = (window == WINDOW_A) ? "A" : "B"; - rt_kprintf("[NO LOCK] window %s: no tickets left, exit\n", name); -} - -/* ========== 有锁版本(线程安全) ========== */ -static void window_entry_with_lock(void *parameter) -{ - rt_uint32_t window = (rt_uint32_t)parameter; - - while (1) - { - /* 加锁,保护完整的售票过程 */ - rt_mutex_take(&mutex_ticket, RT_WAITING_FOREVER); - - if (ticket_count <= 0) - { - rt_mutex_release(&mutex_ticket); - break; - } - - rt_int32_t cur_count = ticket_count; - rt_int32_t cur_no = ticket_no; - - rt_thread_mdelay(1); /* 模拟处理延时 */ - - cur_count--; - cur_no--; - - ticket_count = cur_count; - ticket_no = cur_no; - - const char *name = (window == WINDOW_A) ? "A" : "B"; - rt_kprintf("[WITH LOCK] window %s sold ticket %d\n", name, cur_no + 1); - - rt_mutex_release(&mutex_ticket); - } - - const char *name = (window == WINDOW_A) ? "A" : "B"; - rt_kprintf("[WITH LOCK] window %s: no tickets left, exit\n", name); -} - -/* ========== 线程栈(需对齐) ========== */ -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif -static char win_a_stack[1024]; -static char win_b_stack[1024]; - -static struct rt_thread win_a_thread; -static struct rt_thread win_b_thread; - -/* ========== 无锁测试 ========== */ -static int run_no_lock_test(void) -{ - rt_kprintf("\n========== [TEST 1] NO LOCK (race condition) ==========\n"); - - ticket_count = TOTAL_TICKETS; - ticket_no = TOTAL_TICKETS; - - rt_err_t err; - - err = rt_thread_init(&win_a_thread, "winA_nolock", window_entry_no_lock, - (void *)WINDOW_A, win_a_stack, sizeof(win_a_stack), 12, 5); - if (err != RT_EOK) { rt_kprintf("winA init failed\n"); return -1; } - - err = rt_thread_init(&win_b_thread, "winB_nolock", window_entry_no_lock, - (void *)WINDOW_B, win_b_stack, sizeof(win_b_stack), 12, 5); - if (err != RT_EOK) { rt_kprintf("winB init failed\n"); return -1; } - - rt_thread_startup(&win_a_thread); - rt_thread_startup(&win_b_thread); - - /* 等待线程退出 */ - rt_thread_mdelay(3000); - - rt_kprintf("\n[RESULT] final ticket_count=%d (expected 0)\n", ticket_count); - if (ticket_count != 0) - rt_kprintf("[WARNING] ticket_count != 0, race condition detected!\n"); - rt_kprintf("========== NO LOCK TEST END ==========\n\n"); - - return 0; -} - -/* ========== 有锁测试 ========== */ -static int run_with_lock_test(void) -{ - rt_kprintf("========== [TEST 2] WITH LOCK (thread-safe) ==========\n"); - - ticket_count = TOTAL_TICKETS; - ticket_no = TOTAL_TICKETS; - - /* 初始化互斥量 */ - rt_err_t err = rt_mutex_init(&mutex_ticket, "mutex_tick", RT_IPC_FLAG_PRIO); - if (err != RT_EOK) - { - rt_kprintf("[ticket_demo] ERROR: mutex init failed: %d\n", err); - return -1; - } - - /* 使用新线程名,避免与无锁测试冲突 */ - err = rt_thread_init(&win_a_thread, "winA_lock", window_entry_with_lock, - (void *)WINDOW_A, win_a_stack, sizeof(win_a_stack), 12, 5); - if (err != RT_EOK) { rt_kprintf("winA init failed\n"); return -1; } - - err = rt_thread_init(&win_b_thread, "winB_lock", window_entry_with_lock, - (void *)WINDOW_B, win_b_stack, sizeof(win_b_stack), 12, 5); - if (err != RT_EOK) { rt_kprintf("winB init failed\n"); return -1; } - - rt_thread_startup(&win_a_thread); - rt_thread_startup(&win_b_thread); - - /* 等待线程退出 */ - rt_thread_mdelay(3000); - - rt_kprintf("\n[RESULT] final ticket_count=%d (expected 0)\n", ticket_count); - rt_kprintf(" all tickets sold\n"); - rt_kprintf("========== WITH LOCK TEST END ==========\n\n"); - - rt_mutex_detach(&mutex_ticket); - - return 0; -} - -/* ========== MSH 命令入口 ========== */ -int ticket_demo(void) -{ - rt_thread_t t; - - t = rt_thread_find("winA_nolock"); - if (t != RT_NULL || rt_thread_find("winA_lock") != RT_NULL) - { - rt_kprintf("[ticket_demo] demo already running, skip\n"); - return -1; - } - - rt_kprintf("[ticket_demo] total tickets: %d\n", TOTAL_TICKETS); - - /* 测试1:无锁 */ - run_no_lock_test(); - rt_thread_mdelay(500); - - /* 测试2:有锁 */ - run_with_lock_test(); - - return 0; -} -MSH_CMD_EXPORT(ticket_demo, ticket selling system with mutex (no-lock vs with-lock comparison)); diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/Git\345\270\270\347\224\250\345\221\275\344\273\244\351\200\237\346\237\245.md" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/Git\345\270\270\347\224\250\345\221\275\344\273\244\351\200\237\346\237\245.md" deleted file mode 100644 index 2d78f44fd012dcbebadf1082653e91f37e67cd59..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/Git\345\270\270\347\224\250\345\221\275\344\273\244\351\200\237\346\237\245.md" +++ /dev/null @@ -1,49 +0,0 @@ -# Git 常用命令速查 - -## 黄金三步(日常提交推送) - -```bash -git add . # 1. 暂存所有改动(可改成 git add 具体文件名) -git commit -m "说明" # 2. 提交(说明写清楚这次改了什么) -git push origin <分支名> # 3. 推送到云端(origin=远程仓库) -``` - -> 记住:git 的所有命令都要以 `git` 开头。 -> `git add` / `git commit` / `git pull` / `git push` / `git checkout` 都是。 -> 漏了或拼错会报 `command not found` 或 `is not a git command`。 - -## 主要分支操作 - -| 命令 | 作用 | -|---|---| -| `git branch` | 列出所有分支,当前分支前有 `*` | -| `git branch -a` | 查看本地+云端所有分支 | -| `git checkout <分支>` | 切换到某分支 | -| `git checkout -b <新分支>` | 新建分支并切换过去 | -| `git push origin <分支>` | 把当前提交推到云端某分支 | -| `git push -u origin <分支>` | 同上,并记住对应关系,以后直接 `git push` | -| `git pull` | 拉取云端最新(先拉再推,避免冲突) | - -## 查看状态 - -| 命令 | 作用 | -|---|---| -| `git status` | 看当前改动/暂存情况 | -| `git log --oneline` | 看提交历史(一行一条) | -| `git diff` | 看未暂存的改动内容 | -| `git remote -v` | 查看远程仓库地址 | - -## 撤销/后悔药 - -| 命令 | 作用 | -|---|---| -| `git restore --staged <文件>` | 把已暂存的文件撤出(取消 add) | -| `git restore <文件>` | 丢弃某文件未提交的改动(小心!会丢内容) | -| `git reset --soft HEAD~1` | 撤销最近一次 commit,保留改动 | - -## 常见坑(都是实际遇到过的) - -1. **空文件夹推不上去**:git 不跟踪空目录。放进占位文件 `.gitkeep` 或先放真实文件。 -2. **`git push` 显示 `Everything up-to-date`**:说明没 commit 成功,`git commit` 前一定要 `git add`。 -3. **Windows 换行符警告(LF/CRLF)**:在仓库根目录放 `.gitattributes`(内容 `* text=auto`)即可消除,仓库永远存 LF。 -4. **私有仓库 push 报 401/认证失败**:Gitee 私人令牌过期了,重新去 Gitee「设置→私人令牌」生成。 diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/README.md" deleted file mode 100644 index 655bbb2b0572688c19ad430c30e971aeae245ebe..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1 +0,0 @@ -# 第3组 \ No newline at end of file diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/RT-Thread\346\272\220\347\240\201\347\233\256\345\275\225\347\273\223\346\236\204\345\210\206\346\236\220.md" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/RT-Thread\346\272\220\347\240\201\347\233\256\345\275\225\347\273\223\346\236\204\345\210\206\346\236\220.md" deleted file mode 100644 index 0a0e190097bf40dfb4e151d9664ef703f96931ad..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/RT-Thread\346\272\220\347\240\201\347\233\256\345\275\225\347\273\223\346\236\204\345\210\206\346\236\220.md" +++ /dev/null @@ -1,340 +0,0 @@ -# RT-Thread 源码目录结构分析 - -> 分析对象:`D:\stm32\RT-ThreadStudio\workspace\day\rt-thread\` -> 版本依据:工程 `rtconfig.h` 中 `RT_VER_NUM 0x40101` → **RT-Thread 4.1.1** -> 用途:理清 RT-Thread 内核源码树每个目录/文件是干什么的,以及它们如何配合构成一个可运行的操作系统。 - ---- - -## 1. 源码树总览 - -`rt-thread/` 目录是一份完整的 RT-Thread **内核源码树**(不是工程、不是 BSP),它和工程根目录的 `applications/`、`drivers/`、`libraries/` 分开存放。RT-Thread 官方将源码树分成 6 大目录: - -``` -rt-thread/ -├── components/ 组件与中间件 (1434 个文件) -├── examples/ 示例代码 (199 个文件) -├── include/ 内核头文件 (7 个文件) -├── libcpu/ 芯片/CPU 移植代码 (501 个文件) -├── src/ 内核核心源码 (19 个文件) -├── tools/ 构建工具脚本 (127 个文件) -│ -├── Kconfig 内核配置菜单入口(menuconfig 用它生成配置树) -├── SConscript 内核构建脚本入口 -├── README.md / README_zh.md 官方说明文档 -├── LICENSE / AUTHORS / .gitignore / .gitattributes -``` - -**一个要点**:RT-Thread 官方仓库里还有 `BSP/`(板级支持包)和 `documentation/`,但这份源码树里**没有**。因为在 RT-Thread Studio 工程里,板级部分(`board.c`、`drv_*.c`、HAL 库、链接脚本)被抽到了工程根目录的 `drivers/`、`libraries/`、`linkscripts/`,不再放在 `rt-thread/` 里。 - -分层关系一句话:**src + include 是内核,libcpu 是内核与硬件的"翻译层",components 是内核之上的可选模块,tools 负责把这些代码编译/配置起来。** - ---- - -## 2. 内核核心:`src/` + `include/` - -### 2.1 `src/` —— 内核源码(19 个文件) - -这是 RT-Thread 操作系统的"心脏",全部与芯片无关、纯 C 实现: - -| 文件 | 作用 | -| --- | --- | -| `scheduler.c` | **调度器**。就绪队列、优先级调度、时间片轮转;`rt_schedule()` 是线程切换的核心入口 | -| `thread.c` | **线程管理**。线程的创建/删除/启动/挂起/恢复/延时,`rt_thread_*` 系列 API | -| `timer.c` | **定时器**。软件定时器与硬件定时器接口,`rt_timer_*`;配合 `RT_USING_TIMER_SOFT` 可走定时器线程 | -| `ipc.c` | **线程间通信(IPC)**。信号量 `rt_sem_*`、互斥锁 `rt_mutex_*`、事件 `rt_event_*`、邮箱 `rt_mb_*`、消息队列 `rt_mq_*` 全部在这一个文件里 | -| `mem.c` | 小内存管理算法(first-fit/小内存堆),用于小 RAM 的 MCU | -| `memheap.c` | 多内存堆管理(可管理多块不连续内存) | -| `mempool.c` | 内存池(固定大小块的快速分配) | -| `slab.c` | slab 内存分配算法 | -| `object.c` | **内核对象系统**。所有内核对象(线程/信号量/定时器…)的初始化、注册、查找;对象在内部以链表管理 | -| `device.c` | **设备框架**。`rt_device_*` 设备注册、打开、读、写等,是驱动模型的基础 | -| `kservice.c` | 内核基础服务:`rt_kprintf` 打印、字符串/内存操作、小工具函数 | -| `clock.c` | 系统时钟节拍(tick)维护,`rt_tick_get()` | -| `cpu.c` | CPU 相关封装:开关中断、CPU 信息 | -| `irq.c` | **中断管理**。中断安装 `rt_hw_interrupt_install`、中断进入/退出计数 | -| `idle.c` | **空闲线程**(优先级最低的线程),创建空闲线程并执行空闲钩子 | -| `signal.c` | 信号机制(面向 POSIX 的信号处理) | -| `components.c` | **组件自动初始化**。按顺序调用 `INIT_BOARD_EXPORT` / `INIT_APP_EXPORT` 等宏注册的初始化函数(`rt_components_init`) | -| `Kconfig` | 内核部分的配置菜单定义 | -| `SConscript` | 内核源码的 SCons 构建脚本 | - -> 记忆方法:**调度 + 线程 + 定时器 + IPC + 内存 + 对象/设备 + 中断 + 空闲线程**,这就是一个 RTOS 内核的全部骨架。 - -### 2.2 `include/` —— 内核头文件(7 个) - -| 文件 | 作用 | -| --- | --- | -| `rtthread.h` | **总头文件**。应用和驱动只需 `#include `,它会串起其余头文件 | -| `rtdef.h` | **核心定义**。基础类型、内核对象结构体、通用宏(如 `RT_THREAD_PRIORITY_MAX`、`RT_NULL`、`rt_list_t` 等) | -| `rthw.h` | **硬件抽象接口**。声明所有必须由移植层实现的函数:`rt_hw_interrupt_disable/enable`、`rt_hw_context_switch`、`rt_hw_tick_init` 等 | -| `rtservice.h` | 内核容器/链表工具:双向链表 `rt_list_*`、`rt_slist_*`、`container_of` 等 | -| `rtdbg.h` | 调试宏:`LOG_D/I/W/E`,配合 `DBG_TAG`、`DBG_LVL` 使用(drv_*.c 顶部常见) | -| `rtdebug.h` | 断言与调试辅助(`RT_ASSERT` 等) | -| `rtm.h` | 内核模块(module)相关声明(动态加载模块场景用) | - -> 关键理解:**`src/` 实现内核,`include/` 定义接口**。`rthw.h` 里的函数声明就是给 `libcpu/` 移植层下的"订单"——内核只管调用,具体怎么操作硬件由移植层完成。 - ---- - -## 3. 移植层:`libcpu/` - -`libcpu/`(CPU 移植层)按**芯片架构**分目录,为每种架构实现上下文切换和中断入口: - -``` -libcpu/ -├── arm/ ARM 体系(含 cortex-m0/m3/m4/m7、cortex-a、arm926 等) -├── risc-v/ RISC-V 体系 -├── mips/ MIPS -├── ia32/ x86 -├── aarch64/ 64 位 ARM -├── arc/ c-sky/ rx/ ppc/ avr32/ ... 其它小众/历史架构 -├── Kconfig 架构选择配置 -└── SConscript -``` - -本项目用的是 STM32F407ZG(Cortex-M4),对应 **`libcpu/arm/cortex-m4/`**: - -| 文件 | 作用 | -| --- | --- | -| `cpuport.c` | Cortex-M4 的 CPU 移植实现:SysTick 定时器、PendSV 异常、`rt_hw_interrupt_*`、启动第一个线程 | -| `context_gcc.S` | GCC 汇编写的**线程上下文切换**(`rt_hw_context_switch` 等),保存/恢复寄存器 | -| `context_iar.S` | IAR 编译器版本(同逻辑、IAR 汇编语法) | -| `context_rvds.S` | MDK/Keil(ARMCC)版本 | -| `SConscript` | 构建脚本 | - -同级还有 `libcpu/arm/common/`,放 ARM 通用的辅助代码: - -| 文件 | 作用 | -| --- | --- | -| `backtrace.c` | 栈回溯(调试死机/异常定位用) | -| `div0.c` / `divsi3.S` | 软除法支持 | -| `showmem.c` | 内存查看工具 | - -> 理解:**上下文切换是 RTOS 的"命门"**,必须贴近芯片写汇编,所以它放在移植层而不是内核层。换一块不同的 CPU,只需要换 `libcpu/` 里对应的目录。 - ---- - -## 4. 组件层:`components/` - -组件运行在内核之上,是可选、可裁剪的(由 `rtconfig.h` 里的 `RT_USING_*` 宏决定编不编译)。 - -``` -components/ -├── finsh/ 命令行组件(FinSH / MSH) -├── dfs/ 虚拟文件系统框架(VFS) -├── drivers/ 设备驱动框架(串口/GPIO/SPI/I2C…的设备抽象层) -├── libc/ C 库适配层(newlib 等) -├── net/ 网络框架(lwIP 协议栈、netdev、socket 抽象层 SAL、AT) -├── fal/ Flash 抽象层(分区表管理) -├── utilities/ 工具组件(日志 ulog、单元测试 utest、rt-link、ymodem 等) -├── lwp/ 轻量进程(用户态进程支持) -├── vmm/ 与 Linux 共存的虚拟内存/双系统 -├── vbus/ 内核间虚拟总线 -├── legacy/ 兼容旧接口的遗留代码 -├── Kconfig / SConscript -``` - -### 4.1 `finsh/` —— 命令行(本项目已启用) - -`RT_USING_FINSH` + `FINSH_USING_MSH` 在 `rtconfig.h` 里已打开,对应串口上那个 `msh>` 提示符。 - -| 文件 | 作用 | -| --- | --- | -| `shell.c` | FinSH 传统 shell(逐行命令解释) | -| `msh.c` | **MSH(模块 shell)**,本项目用的就是它,支持 `ps`/`list_thread`/`free` 等命令 | -| `cmd.c` | 内建命令注册表 | -| `msh_parse.c` | MSH 命令参数解析 | - -### 4.2 `dfs/` —— 文件系统(本项目未启用) - -``` -dfs/ -├── src/ VFS 核心(文件/目录/挂载管理) -├── filesystems/ 具体文件系统实现: -│ ├── devfs/ 设备文件系统(/dev) -│ ├── elmfat/ FAT 文件系统(SD 卡/U 盘) -│ ├── ramfs/ 内存文件系统 -│ ├── romfs/ 只读内存文件系统 -│ ├── nfs/ NFS 网络文件系统 -│ └── skeleton/ 写新文件系统的模板 -└── include/ 头文件 -``` - -### 4.3 `drivers/` —— 设备驱动框架(部分启用) - -这是"设备框架层",只定义**抽象接口**,具体外设实现由工程里 `drivers/drv_*.c`(板级驱动)完成。开发时 `#include `(`include/drivers/` 的总入口)即可使用全部框架 API。 - -> ⚠️ 注意:框架源码的**存放位置 ≠ 框架名称**。例如 `pin/` 目录是空的,GPIO 引脚框架源码实际在 `misc/pin.c`(头文件 `include/drivers/pin.h`)。判断一个框架看 `include/drivers/` 里的头文件即可。 - -**通信类** - -| 目录 | 关键文件 | 作用 | -| --- | --- | --- | -| `serial/` | `serial.c`(V1)、`serial_v2.c`(V2) | 串口设备框架,二选一(本项目 `RT_USING_SERIAL_V1` 已开,`uart1` 由 `drv_usart.c` 实现) | -| `spi/` | `spi_core.c` 总线核心、`spi_dev.c` 设备接口、`spi-bit-ops.c` 软件模拟、`qspi_core.c` QSPI、`spi_flash_sfud.c` 万能 SPI Flash、`spi_msd.c` SPI 版 SD 卡、`enc28j60.c` / `spi_wifi_rw009.c` 驱动示例 | SPI 框架 + 周边适配 | -| `i2c/` | `i2c_core.c` + `i2c_dev.c` + `i2c-bit-ops.c` | I2C 框架(本项目用软件 I2C:`drv_soft_i2c.c`) | -| `can/` | `can.c` | CAN 框架(`drv_can.c` 对应) | -| `usb/` | `usbdevice/`:cdc_vcom(虚拟串口)/hid/mstorage(U盘)/rndis(网卡)/winusb/audio_mic/audio_speaker/ecm;`usbhost/`:hub/udisk/ukbd/umouse/mass/adk/hid | 完整 USB 设备 + 主机协议栈 | - -**采集 / 控制 / 管理类** - -| 目录 | 关键文件 | 作用 | -| --- | --- | --- | -| `rtc/` | `rtc.c`、`alarm.c`(闹钟回调)、`soft_rtc.c`(软件 RTC) | 实时时钟框架 | -| `watchdog/` | `watchdog.c` | 看门狗框架,应用 `rt_device_find("wdt")` | -| `sensors/` | `sensor.c` + `sensor_cmd.c`(msh 命令 `sensor`) | 传感器统一框架(温湿度/光照/加速度…) | -| `audio/` | `audio.c` + `audio_pipe.c` | 声卡设备框架 + 音频数据管道 | -| `touch/` | `touch.c` | 触摸屏框架 | -| `pm/` | `pm.c` + `lptimer.c` | 电源管理(休眠/唤醒/功耗档位)+ 低功耗定时器(`drv_pm.c` 对应) | - -**`misc/` —— 杂项框架(独立框架,源码集中于此)** - -| 文件 | 框架 | 头文件 | -| --- | --- | --- | -| `pin.c` | GPIO 引脚(`rt_pin_write/read/mode`) | `pin.h` | -| `adc.c` / `dac.c` | AD / DA 转换 | `adc.h` / `dac.h` | -| `rt_drv_pwm.c` | PWM 输出 | `rt_drv_pwm.h` | -| `pulse_encoder.c` | 脉冲编码器(正交解码/测速) | `pulse_encoder.h` | -| `rt_inputcapture.c` | 输入捕获(脉宽/频率测量) | `rt_inputcapture.h` | - -**`ipc/` —— 内核内通信工具(给驱动内部用)** - -`ringbuffer.c`(环形缓冲)、`ringblk_buf.c`(环形块缓冲)、`dataqueue.c`(数据队列)、`workqueue.c`(工作队列,中断下半部)、`completion.c`(完成量)、`pipe.c`(管道)、`waitqueue.c`(等待队列)。典型用法:**中断收数据 → 丢进 ringbuffer → 唤醒读线程**。 - -**时间 / 加密 / 存储抽象** - -| 目录 | 作用 | -| --- | --- | -| `hwtimer/` | 硬件定时器框架(`drv_hwtimer.c` 对应) | -| `cputime/` | CPU 计时(`cputime_cortexm.c` 基于 SysTick/DWT) | -| `hwcrypto/` | 硬件加密框架:`hw_crc`/`hw_hash`/`hw_rng`/`hw_symmetric`/`hw_bignum`/`hw_gcm` | -| `mtd/` | `mtd_nor.c`(NOR Flash)/ `mtd_nand.c`(NAND)存储抽象 | -| `phy/` | 以太网物理层抽象(`phy.c` + `phy_mdio.h`),LAN8720 等 | - -**`wlan/` —— WiFi 框架** - -``` -wlan/ -├── wlan_dev.c / wlan_dev.h ① 设备抽象层(最底层) -├── wlan_mgnt.c / wlan_mgnt.h ② 管理 API 层(用户可见接口) -├── wlan_cfg.c / wlan_cfg.h ③ 配置持久化(SSID/密码保存) -├── wlan_prot.c / wlan_prot.h ④ 连接协议抽象(WPA/WPA2 等) -├── wlan_lwip.c ⑤ 与 lwIP 对接(网卡注册/DHCP/IP 配置) -├── wlan_workqueue.c / .h ⑥ 工作队列(异步处理扫描/连接) -├── wlan_cmd.c ⑦ 命令封装(AT 型模组用) -└── SConscript -``` - -分层模型(从下到上): - -``` - 用户代码 / msh 命令(wifi join / wifi scan) - │ rt_wlan_connect("ssid","pwd") / rt_wlan_scan() ... - ▼ -② 管理层 wlan_mgnt.c 高层 API + 事件回调(连上/断开/扫描完成) - │ 内部派发到设备 - ▼ -① 设备层 wlan_dev.c struct rt_wlan_dev_ops 操作表 + 设备注册(wlan0) - │ - ▼ - 具体 WiFi 驱动(不在本目录!在 BSP 或软件包:rw007/esp8266/esp32/ap6212…) - │ 实现 wlan_init/scan/join/softap/send/recv... - ▼ - WiFi 芯片 / 模组硬件 -``` - -关键接口(源码实锤): -- 驱动要实现:`wlan_dev.h` 里的 `struct rt_wlan_dev_ops`(20+ 函数指针:`wlan_init`/`wlan_scan`/`wlan_join`/`wlan_softap`/`wlan_send`/`wlan_recv`/`wlan_get_rssi`…),再 `rt_wlan_dev_register(&dev, "wlan0", &ops, ...)` 注册。 -- 用户能调:`wlan_mgnt.h` 里的 `rt_wlan_connect(ssid,pwd)`/`rt_wlan_disconnect()`/`rt_wlan_scan()`/`rt_wlan_start_ap()`/`rt_wlan_is_ready()`/`rt_wlan_register_event_handler()`/`rt_wlan_get_rssi()`。 - -要点: -- `wlan_lwip.c` 是 WiFi 与 TCP/IP 的桥:把 wlan 设备注册成 lwIP 网卡(netdev)、处理 DHCP。**没它,WiFi 连上了也上不了网**。 -- `wlan_cfg.c` 支持记住上次连接并自动重连(`rt_wlan_config_autoreconnect`)。 -- 本项目 `rtconfig.h` 里 Wi-Fi 段为空(`RT_USING_WLAN` 未开),这套框架**当前未参与编译**;联网时在 Settings 里勾选 WLAN 框架 + 对应 WiFi 软件包即可。 - -### 4.4 `net/` —— 网络框架(本项目未启用) - -``` -net/ -├── lwip/ lwIP 协议栈本体 -├── netdev/ 网络设备抽象(网卡管理) -├── sal/ Socket 抽象层(统一 BSD socket 接口) -├── at/ AT 命令框架(AT 模组上网) -├── lwip-dhcpd/ lwip-nat/ 附加功能 -``` - -### 4.5 `utilities/` 及其它 - -| 子目录 | 作用 | -| --- | --- | -| `utilities/` | `ulog/`(日志库)、`utest/`(单元测试框架)、`rt-link/`(RT-Thread 设备间通信)、`ymodem/` `zmodem/`(文件传输)、`var_export/` | -| `libc/` | 把 C 标准库(newlib/picolibc 等)接到 RT-Thread 上 | -| `fal/` | Flash 抽象层,分区表管理(常见于离线/OTA 项目) | -| `lwp/` | 用户态进程(复杂项目才用) | -| `vmm/` / `vbus/` | 双系统 / 虚拟总线(高级场景) | - ---- - -## 5. 其余目录 + 整体关系 - -### 5.1 `examples/` —— 示例代码 - -按功能组织:`file`(文件系统示例)、`network`(网络示例)、`libc`、`pm`(电源管理)、`ulog`、`utest`、`ymodem`、`rt-link`、`var_export`、`test`。纯参考,编译时通常不参与。 - -### 5.2 `tools/` —— 构建工具脚本 - -RT-Thread 用 **SCons** 构建,这套脚本就是构建系统的"引擎": - -| 文件 | 作用 | -| --- | --- | -| `building.py` | 核心:递归收集源码、生成编译目标 | -| `gcc.py` / `keil.py` / `iar.py` / `makefile.py` / `vs.py` | 适配各编译器/IDE 的构建后端 | -| `menuconfig.py` / `kconfiglib.py` / `genconf.py` | 图形化配置(menuconfig)与 Kconfig 解析 | -| `rt_studio.py` | **RT-Thread Studio 专用构建后端**(本项目构建实际走它) | -| `cmake.py` / `eclipse.py` / `mkromfs.py` / `package.py` | 其它辅助能力 | - -### 5.3 整体关系图 - -``` - ┌──────────────────────────────────────────┐ - │ 应用层:applications/main.c │ ← 你写的代码 - │ rt_thread_create / rt_sem_take / ... │ - └──────────────────────────────────────────┘ - │ 调用 API - ┌──────────────────────────────────────────┐ - │ 组件层:components/(finsh/dfs/drivers/…)│ ← 可选,按 RT_USING_* 裁剪 - └──────────────────────────────────────────┘ - │ - ┌──────────────────────────────────────────┐ - │ 内核:src/(实现)+ include/(接口) │ ← 线程/调度/IPC/内存/定时器/设备 - └──────────────────────────────────────────┘ - │ 调用 rthw.h 声明的硬件接口 - ┌──────────────────────────────────────────┐ - │ 移植层:libcpu/arm/cortex-m4/ │ ← 上下文切换汇编 + cpuport.c - │ (context_gcc.S / cpuport.c) │ - └──────────────────────────────────────────┘ - │ - STM32F407ZG 硬件 -``` - -### 5.4 这套源码和工程根目录的关系 - -1. 工程根目录的 `Kconfig`(`rtconfig.h` 由它生成)会 `source` 进 `rt-thread/Kconfig`,所以 **menuconfig 里的内核/组件选项来自这套源码树**。 -2. 每个组件目录都有 `Kconfig`(定义可选项)和 `SConscript`(定义该组件源码怎么编译)。 -3. 构建时,工程根 `SConstruct` → 读 `rtconfig.py`/`.config` → 调 `tools/building.py` 收集源码 → 只编译**被 `RT_USING_*` 勾选**的部分。 -4. 所以:**同一个 `rt-thread/` 源码树,配合不同的 `rtconfig.h`,能产出不同"胖瘦"的系统**——这就是"可裁剪"。 - ---- - -## 6. 一分钟速查 - -| 目录 | 一句话 | -| --- | --- | -| `src/` | 内核实现:调度、线程、定时器、IPC、内存、设备、中断 | -| `include/` | 内核接口/类型定义,`rtthread.h` 是总入口 | -| `libcpu/` | 换芯片就换这里;Cortex-M4 看 `arm/cortex-m4/` | -| `components/` | 内核之上的可选模块(命令行/文件系统/网络/驱动框架…) | -| `examples/` | 示例代码,纯参考 | -| `tools/` | SCons 构建系统脚本,一般不用动 | -| `Kconfig` / `SConscript` | 配置菜单 + 构建脚本的入口 | diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 2e7bf40c195f29f58ff38b20d2d454f151985bd0..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,86 +0,0 @@ -# 第一天作业:Git 的 push / pull / PR - -## 一、三个核心概念 - -### 1. git push(推送) -把本地提交**上传到云端仓库**。 - -```bash -git push origin day1 -``` -- `origin`:云端远程仓库的名字 -- `day1`:要推的分支名 - -### 2. git pull(拉取) -把云端**最新内容下载到本地**。 - -```bash -git pull origin master -``` -作用:别人(或上游)更新了仓库,你先拉下来,再改自己的,避免冲突。 - -### 3. PR / Pull Request(合并请求) -自己**没有权限直接改**目标仓库时,先改自己的副本(fork),然后**请求对方同意把改动合并进去**。 - -本质:不是"我直接改你的仓库",而是"我改好了,请你审核,同意就并进去"。 - ---- - -## 二、标准 PR 工作流(给官方/上游仓库提交) - -``` -上游仓库(别人的,无写权限) - ↑ 4. 发起 PR 请求合并 -fork 仓库(自己账号下的副本,有写权限) ← 1. 浏览器点 Fork 复制 - ↑ 3. push -本地仓库(自己电脑) -``` - -### 8 步流程 - -1. **浏览器 fork 并同步上游**:打开上游仓库 → Fork 到自己的账号 → 点「同步」更新到最新 -2. **`git switch master` + `git pull origin master`**:本地切回主分支,拉取云端 fork 的最新内容 -3. **`git checkout -b day2`**:新建分支并切换过去(**day2 是分支名,自己起**,如 day1/day2/fix-xxx) -4. **修改/新增/删除文件**:干活 -5. **`git add .`**:把所有改动放进暂存区 -6. **`git commit -m "提交说明"`**:打快照并写说明 -7. **`git push origin day2`**:推到云端 fork 的 day2 分支 -8. **创建 PR**:在 Gitee 网页 day2 分支下点「创建 Pull Request」,选择从 `你的fork:day2` → 到 `上游:master` - -### 下一轮怎么办 -一模一样重复 1-8,只把 `day2` 换成 `day3`、`day4`……这就是迭代开发的循环。 - ---- - -## 三、自己的仓库(无上游)简化流程 - -如果仓库是**自己的**(比如 mazheng233/rsoc-rtt),不需要 fork 和 PR,直接: - -```bash -git switch master # 回主分支 -git pull origin master # 拉最新 -git checkout -b day2 # 新分支 -# ……改文件…… -git add . -git commit -m "说明" -git push origin day2 # 推上去 -``` - ---- - -## 四、记忆口诀 - -- `add` = 收拾行李(暂存) -- `commit` = 拍照存档(提交) -- `push` = 寄出去(推送) -- `pull` = 取回来(拉取) -- 分支 = 独立的工位,一个任务一个分支,互不干扰 -- PR = 递交申请单,请求合并 - -## 五、今天实际踩过的坑 - -1. 命令要以 `git` 开头(`git commit`,不是 `commit`) -2. `checkout` 不要拼成 `chatout` -3. 空文件夹 git 不跟踪,需要放文件或 `.gitkeep` -4. `git push` 提示 `Everything up-to-date` 说明没有新提交,先 add 再 commit -5. 私有仓库需要令牌认证(Gitee 私人令牌) diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\347\254\224\350\256\260/Git\344\275\277\347\224\250\346\200\273\347\273\223.md" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\347\254\224\350\256\260/Git\344\275\277\347\224\250\346\200\273\347\273\223.md" deleted file mode 100644 index c82e8cea11ab5d5f0785f8de59e90064ccbe3eae..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\347\254\224\350\256\260/Git\344\275\277\347\224\250\346\200\273\347\273\223.md" +++ /dev/null @@ -1,44 +0,0 @@ -# Git 使用总结 - -## 黄金三步(日常提交推送) - -```bash -git add . # 1. 暂存所有改动(可改成 git add 具体文件名) -git commit -m "说明" # 2. 提交(说明写清楚这次改了什么) -git push origin <分支名> # 3. 推送到云端(origin=远程仓库) -``` - -> 注意:git 所有命令都要以 `git` 开头,漏了或拼错会报错。 - -## 分支操作 - -| 命令 | 作用 | -|---|---| -| `git branch` | 查看分支,当前分支前有 `*` | -| `git branch -a` | 查看本地 + 云端所有分支 | -| `git checkout <分支>` | 切换分支 | -| `git checkout -b <新分支>` | 新建并切换分支 | -| `git push origin <分支>` | 推送到云端某分支 | -| `git push -u origin <分支>` | 推送并记住对应关系 | -| `git pull` | 拉取云端最新 | - -## 查看状态 - -| 命令 | 作用 | -|---|---| -| `git status` | 查看改动/暂存情况 | -| `git log --oneline` | 查看提交历史 | -| `git remote -v` | 查看远程地址 | - -## 常见坑 - -1. **空文件夹推不上去**:git 不跟踪空目录,需放占位文件(如 `.gitkeep`)或真实文件。 -2. **`git push` 显示 `Everything up-to-date`**:说明没有新提交,先 `git add` 再 `git commit`。 -3. **`commit` 不是命令**:要写 `git commit`,前面一定要有 `git`。 - -## 本次实际踩过的坑(2026-08-17) - -1. `git chatout` 拼错 → 应为 `git checkout`。 -2. `commit -m` 漏了 `git` 前缀 → 报 `command not found`,完整命令是 `git commit -m "..."`。 -3. 马峥的 `作业`/`笔记` 文件夹原为空,git 看不到空目录 → 放入 `README.md` 后 `git add .` 才能提交成功。 -4. 另一个仓库 geey 因 TLS / 私有仓库认证问题无法 pull/push → 改用 openssl 后端 + Gitee 私人令牌解决。 diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\347\254\224\350\256\260/README.md" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\347\254\224\350\256\260/README.md" deleted file mode 100644 index 655bbb2b0572688c19ad430c30e971aeae245ebe..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\347\254\224\350\256\260/README.md" +++ /dev/null @@ -1 +0,0 @@ -# 第3组 \ No newline at end of file diff --git "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\347\254\224\350\256\260/\344\273\212\346\227\245\346\200\273\347\273\223.md" "b/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\347\254\224\350\256\260/\344\273\212\346\227\245\346\200\273\347\273\223.md" deleted file mode 100644 index 95bf713f54f5070995a988983c3c359f2349a446..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\251\254\345\263\245/\347\254\224\350\256\260/\344\273\212\346\227\245\346\200\273\347\273\223.md" +++ /dev/null @@ -1,20 +0,0 @@ -# 今日总结(2026-08-17) - -## 今天做了什么 - -1. 在 Rust 环境的 rsoc-rtt 仓库中建立了 `day1` 分支并应用到学习记录中。 -2. 整理并推送了第3组(组长:张思源)的学习笔记框架。 -3. 在 geey 仓库也完成了相关笔记的推送。 - -## Git 学到的东西 - -- 学会了完整的提交流程:`git add` → `git commit` → `git push`。 -- 学会了创建和切换分支:`git checkout -b day1` / `git checkout day1`。 -- 知道了 git 不跟踪空文件夹,空目录需要放占位文件才能提交。 -- 学会了用 `git status`、`git log`、`git branch` 查看仓库状态。 -- 解决了私有仓库的 TLS 和认证问题(openssl 后端 + 私人令牌)。 - -## 待办/疑问 - -- 后续把每天的作业和笔记补充到 `2026/第3组/马峥/` 对应目录下。 -- 如果组长有具体要求,按组长安排推进。 diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\344\275\234\344\270\232/\347\254\2541\345\244\251\344\275\234\344\270\232test.md" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\344\275\234\344\270\232/\347\254\2541\345\244\251\344\275\234\344\270\232test.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 1a9fc3874ba7e728fd0eb7dee284c15865258983..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,214 +0,0 @@ -# 第二天作业:RT-Thread 多线程 LED 与按键控制 - -## Part 1:理论整理 - -### DAY2:RT-Thread 启动流程、内核基础与多线程编程 - -#### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -##### 1.1 从上电复位到进入 `main()` 的调用链 - -STM32F407 上电或复位后,Cortex-M4 会从中断向量表取得初始主栈指针(MSP)和复位入口地址,然后执行 `Reset_Handler`。启动文件完成 C 运行环境准备后,将控制权交给 RT-Thread。 - -```text -上电/复位 - ↓ -Reset_Handler(启动文件) - ├─ 设置栈指针,初始化 .data 和 .bss - ├─ SystemInit():完成时钟等芯片基础配置 - └─ entry() - ↓ -rtthread_startup() - ├─ 关闭中断 - ├─ rt_hw_board_init():板级初始化 - │ └─ rt_components_board_init():执行 Board 自动初始化 - ├─ 初始化系统 Tick、调度器等内核设施 - ├─ rt_application_init():创建 Main 线程 - ├─ 创建 Idle 线程(及可选的软件定时器线程) - └─ rt_system_scheduler_start():启动调度器 - ↓ -Main 线程的 main_thread_entry() - ├─ rt_components_init():执行其余自动初始化 - └─ 用户 main() -``` - -因此,用户的 `main()` 不是复位后最早执行的 C 函数。它运行在 RT-Thread 创建的 Main 线程中,只有调度器启动并选中该线程后才会执行。 - -##### 1.2 `INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` 的自动初始化原理 - -自动初始化的核心是:**编译期注册、链接期汇总、运行期按顺序调用**。 - -```c -static int led_init(void) -{ - /* 初始化 LED */ - return RT_EOK; -} -INIT_BOARD_EXPORT(led_init); -``` - -上述宏会把 `led_init` 的函数指针放入指定的初始化段中;例如 `INIT_BOARD_EXPORT` 对应 Board 阶段,`INIT_APP_EXPORT` 对应应用阶段。链接脚本将这些段保留并排成连续的函数指针表。 - -启动时,RT-Thread 分阶段遍历该表: - -```text -INIT_BOARD_EXPORT(fn) - → 注册到 Board 初始化段 - → rt_components_board_init() 调用 fn - -INIT_APP_EXPORT(fn) - → 注册到应用初始化段 - → Main 线程中的 rt_components_init() 调用 fn - → 用户 main() -``` - -这样,驱动和组件只需在自身文件中注册初始化函数,无需在 `main()` 中逐个手动调用;内核仍能保证板级初始化先于组件和应用初始化执行。 - -#### 2. 内核基础与对象模型 - -##### 2.1 对象容器与控制块 - -RT-Thread 使用 C 语言实现轻量的面向对象思想。线程、信号量、互斥量、事件、邮箱、消息队列和定时器等都属于内核对象;每个对象都有记录自身属性和状态的控制块。 - -不同类型的对象具有名称、类型、标志和链表节点等共同信息。内核为每类对象维护一个对象容器,将同类型对象组织成链表,以便统一查找、统计和管理。 - -```text -对象容器 - ├─ 线程对象:线程 1、线程 2、…… - ├─ 信号量对象:信号量 1、信号量 2、…… - ├─ 互斥量对象:互斥量 1、互斥量 2、…… - └─ 其他对象:事件、邮箱、消息队列、定时器等 -``` - -线程控制块 `struct rt_thread` 保存线程的栈、入口函数、状态与调度信息,是调度器管理线程的依据。常用关键成员如下: - -| 成员 | 作用 | -| --- | --- | -| `stat` | 线程当前状态,如初始、就绪、运行、挂起、关闭。 | -| `current_priority` | 当前调度优先级;数值越小,优先级越高。 | -| `init_tick` | 线程初始时间片;同优先级线程轮转时决定一次可运行的 Tick 数。 | -| `sp` | 当前栈指针;线程切换时据此保存或恢复寄存器现场。 | -| `entry`、`parameter` | 线程入口函数及其参数。 | -| `stack_addr`、`stack_size` | 线程栈的地址和大小。 | - -##### 2.2 静态对象与动态对象 - -以线程为例,RT-Thread 支持静态和动态两种创建方式: - -| 对比项 | 静态线程 | 动态线程 | -| --- | --- | --- | -| 创建 API | `rt_thread_init()` | `rt_thread_create()` | -| 控制块与栈 | 用户预先提供,通常位于 `.bss` 或 `.data` | 内核从堆中运行时申请 | -| 生命周期 | 一般伴随系统运行;分离后内核不释放用户内存 | 删除后由系统回收控制块和栈 | -| 风险 | 栈预留不足或栈过小 | 堆不足、内存碎片、遗漏释放 | -| 适用场景 | 核心控制、长期运行、资源固定的任务 | 按需创建、短生命周期或可动态启停的任务 | - -静态线程的地址和大小可在系统设计阶段确定,因此高可靠场景常强制使用: - -1. 可预先评估 RAM 占用,资源更可预测; -2. 不依赖运行时堆分配,避免关键任务因堆不足而创建失败; -3. 避免频繁申请和释放造成的内存碎片; -4. 启动时间和最坏执行时间更容易分析。 - -但静态线程仍需要合理估算栈大小,不能因此忽略栈溢出风险。 - -#### 3. 线程状态机与调度算法 - -##### 3.1 五态模型 - -| 状态 | 宏 | 含义 | -| --- | --- | --- | -| 初始态 | `RT_THREAD_INIT` | 线程已创建,但尚未进入就绪队列。 | -| 就绪态 | `RT_THREAD_READY` | 线程具备运行条件,等待调度器分配 CPU。 | -| 运行态 | `RT_THREAD_RUNNING` | 当前正在占用 CPU 执行。单核 MCU 同一时刻只有一个运行线程。 | -| 挂起态 | `RT_THREAD_SUSPEND` | 正在延时或等待信号量、消息等资源,暂时不能运行。 | -| 关闭态 | `RT_THREAD_CLOSE` | 线程已结束或被删除,不再参与调度。 | - -```text -创建:rt_thread_init() / rt_thread_create() - ↓ -初始态(INIT) - │ rt_thread_startup() - ↓ -就绪态(READY) ←──── 延时到期、资源可用、rt_thread_resume() ────┐ - │ 调度器选中 │ - ↓ │ -运行态(RUNNING) ── rt_thread_delay() / 等待 IPC / 自身挂起 ──→ 挂起态(SUSPEND) - │ - └─ 线程入口返回、rt_thread_delete() 或 rt_thread_detach() - ↓ - 关闭态(CLOSE) -``` - -运行线程被更高优先级线程抢占,或同优先级时间片耗尽时,会回到就绪态;调度器随后选择下一线程运行。 - -##### 3.2 抢占式调度 - -RT-Thread 采用基于优先级的抢占式调度:调度器总是选择**最高优先级(数值最小)**的就绪线程运行。 - -```text -低优先级线程 A 正在运行 - ↓ -高优先级线程 B 因信号量释放或消息到达而变为就绪态 - ↓ -调度器保存 A 的现场并切换到 B -``` - -因此,低优先级线程即使时间片尚未用完,也不能阻止更高优先级的就绪线程运行。B 延时、等待资源或结束后,A 才有机会继续执行。 - -##### 3.3 时间片轮转 - -时间片轮转仅用于**多个同优先级线程同时处于就绪态**的情况,不会影响高优先级线程优先运行的规则。 - -创建线程时传入的 `tick` 参数会写入 `init_tick`。每次系统 Tick 到来,当前线程的剩余时间片递减;时间片用尽后,调度器将它放到同优先级就绪队列尾部,切换到下一个同优先级线程。 - -```text -线程 A、B:优先级均为 10,时间片均为 5 Tick - -A 运行 5 Tick → B 运行 5 Tick → A 再运行 5 Tick → …… -``` - -若没有同优先级的竞争线程,时间片用尽后通常仍会继续运行当前线程;若有更高优先级线程就绪,仍优先运行高优先级线程。线程也可调用 `rt_thread_yield()` 主动让出 CPU,给同优先级线程运行机会。 - -## Part 2:实验结果分析 - -### 1. 硬件运行现象 - -`th_led1` 与 `th_led2` 分别每隔 500 ms 读取并反转对应引脚电平,因此两路 LED 都以约 500 ms 的间隔改变一次亮灭状态,完成一次完整的亮灭循环约为 1 s。由于两个工作线程以相同的优先级、时间片和延时周期启动,肉眼观察时两路 LED 的翻转节奏基本一致。 - -按下左侧按键后,监控线程在下一个 2 s 监控周期处理挂起请求,`th_led1` 停止运行,红色 LED 保持按键触发瞬间的当前状态,不再继续翻转;`LED_B` 所在的 `th_led2` 不受影响,仍然每 500 ms 翻转。再次按下按键后,`th_led1` 被恢复运行,红色 LED 继续以 500 ms 的间隔翻转。 - - -### 2. 现象与结论 - -串口启动后,`th_led1` 和 `th_led2` 分别输出 LED 翻转计数;`th_led1` 同时打印系统 tick。监控线程约每 2 秒输出一次状态和剩余栈空间。 - -第一次按键后,日志出现 `BTN_LEFT: th_led1 suspend requested`,随后 `th_led1` 停止计数而 `th_led2` 保持运行;第二次按键后出现 `BTN_LEFT: th_led1 resumed`,`th_led1` 从原计数继续输出,说明挂起和恢复功能正常。 - -`thread_dump` 中的 `suspend` 还可能表示线程正处于 `rt_thread_mdelay()` 延时状态,并不一定是按键造成的手动挂起。串口偶尔出现字符交错,是多个线程同时调用 `rt_kprintf()` 导致的 UART 输出交织,不影响线程调度结果。 - -### 3. FinSH 串口日志(节选) - -```text - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 19 2026 16:25:07 -hello! -th_led2 count: 0 -th_led1 count: 0, tick: 4 -... -th_led1: suspend stack: 780/1024 bytes -th_led2: suspend stack: 788/1024 bytes -... -BTN_LEFT: th_led1 suspend requested -th_led1: suspend stack: 780/1024 bytes -th_led2 count: 16 -th_led2 count: 17 -th_led2 count: 18 -... -BTN_LEFT: th_led1 resumed -th_led1: ready stack: 780/1024 bytes -th_led2: ready stack: 788/1024 bytes -th_led1 count: 16, tick: 12053 -th_led2 count: 25 -``` diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index ea486e3629d7e786b18fe701f87eadcf8782c92d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\344\275\234\344\270\232/\347\254\2542\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,235 +0,0 @@ -/* - * 第二天作业:多线程 LED 与按键控制 - */ -#include -#include -#include - -#define LED_R_PIN GET_PIN(F, 12) -#define LED_B_PIN GET_PIN(F, 11) -#define BTN_LEFT_PIN GET_PIN(C, 0) - -#define WORK_THREAD_PRIORITY 16 -#define MONITOR_THREAD_PRIORITY 10 -#define THREAD_STACK_SIZE 1024 -#define THREAD_TIMESLICE 5 -#define LED_PERIOD_MS 500 -#define MONITOR_PERIOD_MS 2000 -#define KEY_DEBOUNCE_MS 50 - -static rt_thread_t th_led1 = RT_NULL; -static rt_thread_t th_monitor = RT_NULL; -static volatile rt_bool_t led1_suspended = RT_FALSE; -static volatile rt_bool_t led1_suspend_requested = RT_FALSE; -static volatile rt_uint32_t led1_toggle_count; -static rt_tick_t last_key_tick; - -#ifdef rt_align -rt_align(RT_ALIGN_SIZE) -#else -ALIGN(RT_ALIGN_SIZE) -#endif -static rt_uint8_t th_led2_stack[THREAD_STACK_SIZE]; -static struct rt_thread th_led2; - -static void led_toggle(rt_base_t pin) -{ - rt_pin_write(pin, !rt_pin_read(pin)); -} - -static rt_size_t thread_stack_space(rt_thread_t thread) -{ - rt_uint8_t *stack = (rt_uint8_t *)thread->stack_addr; - rt_size_t space = 0; - - while ((space < thread->stack_size) && (stack[space] == '#')) - { - space++; - } - - return space; -} - -static const char *thread_state(rt_thread_t thread) -{ - switch (thread->stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: return "init"; - case RT_THREAD_READY: return "ready"; - case RT_THREAD_SUSPEND: return "suspend"; - case RT_THREAD_RUNNING: return "running"; - case RT_THREAD_CLOSE: return "close"; - default: return "unknown"; - } -} - -static void thread_dump(void) -{ - if (th_led1 != RT_NULL) - { - rt_kprintf("th_led1: %-7s stack: %u/%u bytes\n", - thread_state(th_led1), - (unsigned int)thread_stack_space(th_led1), - (unsigned int)th_led1->stack_size); - } - else - { - rt_kprintf("th_led1: not created\n"); - } - - if (th_led2.stack_addr != RT_NULL) - { - rt_kprintf("th_led2: %-7s stack: %u/%u bytes\n", - thread_state(&th_led2), - (unsigned int)thread_stack_space(&th_led2), - (unsigned int)th_led2.stack_size); - } - else - { - rt_kprintf("th_led2: not initialized\n"); - } -} -MSH_CMD_EXPORT(thread_dump, show LED thread state and free stack); - -static void led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - while (1) - { - if (led1_suspend_requested) - { - led1_suspend_requested = RT_FALSE; - led1_suspended = RT_TRUE; - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - } - - led_toggle(LED_R_PIN); - rt_kprintf("th_led1 count: %u, tick: %u\n", - (unsigned int)count++, (unsigned int)rt_tick_get()); - rt_thread_mdelay(LED_PERIOD_MS); - } -} - -static void led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - - while (1) - { - led_toggle(LED_B_PIN); - rt_kprintf("th_led2 count: %u\n", (unsigned int)count++); - rt_thread_mdelay(LED_PERIOD_MS); - } -} - -static void button_callback(void *parameter) -{ - rt_tick_t now = rt_tick_get(); - - if ((last_key_tick == 0) || - ((rt_tick_t)(now - last_key_tick) >= rt_tick_from_millisecond(KEY_DEBOUNCE_MS))) - { - last_key_tick = now; - led1_toggle_count++; - } -} - -static void monitor_entry(void *parameter) -{ - while (1) - { - rt_base_t level; - rt_uint32_t toggle_count; - - rt_thread_mdelay(MONITOR_PERIOD_MS); - - level = rt_hw_interrupt_disable(); - toggle_count = led1_toggle_count; - led1_toggle_count = 0; - rt_hw_interrupt_enable(level); - - if (toggle_count & 1U) - { - if (led1_suspended) - { - if (rt_thread_resume(th_led1) == RT_EOK) - { - led1_suspended = RT_FALSE; - rt_kprintf("BTN_LEFT: th_led1 resumed\n"); - } - } - else if (led1_suspend_requested) - { - led1_suspend_requested = RT_FALSE; - rt_kprintf("BTN_LEFT: th_led1 suspend cancelled\n"); - } - else - { - led1_suspend_requested = RT_TRUE; - rt_kprintf("BTN_LEFT: th_led1 suspend requested\n"); - } - } - - thread_dump(); - } -} - -static int thread_study(void) -{ - rt_err_t result; - - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); - rt_pin_write(LED_R_PIN, PIN_LOW); - rt_pin_write(LED_B_PIN, PIN_LOW); - - rt_pin_mode(BTN_LEFT_PIN, PIN_MODE_INPUT_PULLUP); - result = rt_pin_attach_irq(BTN_LEFT_PIN, PIN_IRQ_MODE_FALLING, button_callback, RT_NULL); - if (result != RT_EOK) - { - return result; - } - - result = rt_pin_irq_enable(BTN_LEFT_PIN, PIN_IRQ_ENABLE); - if (result != RT_EOK) - { - return result; - } - - th_led1 = rt_thread_create("th_led1", led1_entry, RT_NULL, - THREAD_STACK_SIZE, WORK_THREAD_PRIORITY, THREAD_TIMESLICE); - if (th_led1 == RT_NULL) - { - return -RT_ENOMEM; - } - - th_monitor = rt_thread_create("th_monitor", monitor_entry, RT_NULL, - THREAD_STACK_SIZE, MONITOR_THREAD_PRIORITY, THREAD_TIMESLICE); - if (th_monitor == RT_NULL) - { - rt_thread_delete(th_led1); - th_led1 = RT_NULL; - return -RT_ENOMEM; - } - - result = rt_thread_init(&th_led2, "th_led2", led2_entry, RT_NULL, - th_led2_stack, sizeof(th_led2_stack), - WORK_THREAD_PRIORITY, THREAD_TIMESLICE); - if (result != RT_EOK) - { - rt_thread_delete(th_monitor); - th_monitor = RT_NULL; - rt_thread_delete(th_led1); - th_led1 = RT_NULL; - return result; - } - - rt_thread_startup(th_led1); - rt_thread_startup(&th_led2); - rt_thread_startup(th_monitor); - - return RT_EOK; -} -INIT_APP_EXPORT(thread_study); diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/01-sdk-manager.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/01-sdk-manager.png" deleted file mode 100644 index 2b1e7bee316128457c0abcda3e5a4bc01ad01f8c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/01-sdk-manager.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/02-qemu-a9-project.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/02-qemu-a9-project.png" deleted file mode 100644 index b573173d99ae11664d5f3ce0d82828fb5c629d11..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/02-qemu-a9-project.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/03-stm32f407zg-project-config.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/03-stm32f407zg-project-config.png" deleted file mode 100644 index b979ea58f5795a765ce0d69ea1405f5762fccaa3..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/03-stm32f407zg-project-config.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/04-stlink-com-port.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/04-stlink-com-port.png" deleted file mode 100644 index 29b7937f29d4fb94bc72dc352032018a3002ade9..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/04-stlink-com-port.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/05-studio-serial-terminal.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/05-studio-serial-terminal.png" deleted file mode 100644 index a517647ea5b897fc862ab6b56b83518c3c7dee82..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/05-studio-serial-terminal.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/06-rtthread-startup-flow.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/06-rtthread-startup-flow.png" deleted file mode 100644 index ddf860ea7118bd290c9eb77ea1c17171be8d404e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/01/06-rtthread-startup-flow.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/01-\350\243\270\346\234\272\345\222\214RTOS\347\232\204\345\214\272\345\210\253.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/01-\350\243\270\346\234\272\345\222\214RTOS\347\232\204\345\214\272\345\210\253.png" deleted file mode 100644 index 3efecff27202583ff197cec37b7a36e4fc8484a0..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/01-\350\243\270\346\234\272\345\222\214RTOS\347\232\204\345\214\272\345\210\253.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/02-rtthread-startup-flow.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/02-rtthread-startup-flow.png" deleted file mode 100644 index ddf860ea7118bd290c9eb77ea1c17171be8d404e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/02-rtthread-startup-flow.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/03-\347\272\277\347\250\213\345\206\205\346\240\270\345\257\271\350\261\241.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/03-\347\272\277\347\250\213\345\206\205\346\240\270\345\257\271\350\261\241.png" deleted file mode 100644 index ff9572c600102007dcec3a4d296b1f32da332090..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/03-\347\272\277\347\250\213\345\206\205\346\240\270\345\257\271\350\261\241.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/04-\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/04-\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242.png" deleted file mode 100644 index 6014b0b1ac842fa31ae155efe990d8bfe3380079..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/04-\347\272\277\347\250\213\347\212\266\346\200\201\350\275\254\346\215\242.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/05-\344\274\230\345\205\210\347\272\247\344\270\216\346\227\266\351\227\264\347\211\207\350\260\203\345\272\246.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/05-\344\274\230\345\205\210\347\272\247\344\270\216\346\227\266\351\227\264\347\211\207\350\260\203\345\272\246.png" deleted file mode 100644 index 52881cc4df683f446ffc7301a224e7c5d90bc7cd..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/05-\344\274\230\345\205\210\347\272\247\344\270\216\346\227\266\351\227\264\347\211\207\350\260\203\345\272\246.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/thread\350\275\257\344\273\266\345\214\205.png" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/thread\350\275\257\344\273\266\345\214\205.png" deleted file mode 100644 index 2d8a57dcba217d9873bb684dc35632ec2f46b286..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/assets/02/thread\350\275\257\344\273\266\345\214\205.png" and /dev/null differ diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 85a8f5553e606723e6be54e56db5b2d9d46c8051..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,488 +0,0 @@ -# RT-Thread 夏令营笔记 - -## 目录 - -- [1. RT-Thread 的三条版本路线](#1-rt-thread-的三条版本路线) -- [2. Markdown 基础语法](#2-markdown-基础语法) -- [3. 新建并认识 QEMU A9 工程](#3-新建并认识-qemu-a9-工程) -- [4. RT-Thread 启动流程](#4-rt-thread-启动流程) -- [5. Git 与 Gitee](#5-git-与-gitee) - -## 1. RT-Thread 的三条版本路线 - -RT-Thread 是面向嵌入式设备的实时操作系统(RTOS)。它通过线程调度,让多个任务按优先级快速切换执行,从而满足设备对实时性、资源占用和功能扩展的不同需求。 - -RT-Thread 提供三条主要路线:**标准版**侧重完整功能,**Nano 版**侧重极小资源占用,**Smart 版**侧重运行更复杂的用户态应用。三者并非完全独立:Nano 可以看作标准版的精简内核,Smart 则在标准版的基础上加入了用户态机制。因此,初学阶段建议先以标准版建立整体认识。 - -| 路线 | 核心特点 | 适用场景 | 学习建议 | -| --- | --- | --- | --- | -| 标准版本 | 完整的 RT-Thread 系统,可按需配置内核、驱动、组件、网络与软件包 | 需要外设驱动、文件系统、网络、物联网组件等功能的 MCU 项目 | 作为学习基础,优先掌握线程、同步通信、设备模型和组件使用 | -| Nano 版本 | 极简的硬实时内核,支持线程、定时器、信号量、邮箱、调度等基础能力,资源占用很小 | 家电、消费电子、医疗设备、工业控制等入门级 32 位 MCU 场景 | 适合资源紧张或希望先聚焦 RTOS 内核原理的项目 | -| Smart 版本 | 将应用与内核分离;应用运行在独立的用户态地址空间,通过系统调用访问内核服务 | 配有 MMU、资源更充足,需要隔离性和复杂应用能力的处理器平台 | 建议在掌握标准版后学习,并补充 MMU、用户态/内核态和系统调用知识 | - -## 2. Markdown 基础语法 - -Markdown 是一种轻量级标记语言:用少量易读的符号写纯文本,就能排版出标题、列表、链接和代码块。`.md` 文件就是 Markdown 文件;本笔记的内容正是使用 Markdown 编写的。 - -### 2.1 标题与段落 - -在行首写 `#` 创建标题,`#` 的数量越多,标题层级越低。段落之间保留一个空行。 - -```md -# 一级标题 -## 二级标题 -### 三级标题 - -这是第一个段落。 - -这是另一个段落。 -``` - -### 2.2 无序列表与有序列表 - -无序列表使用 `-`,有序列表使用数字加英文句点。列表内容建议与符号之间保留一个空格。 - -```md -- 线程 -- 信号量 - - 二值信号量 - - 计数信号量 - -1. 创建线程 -2. 启动调度器 -3. 观察运行结果 -``` - -### 2.3 强调与行内代码 - -用 `**文字**` 标记重点,用反引号包住函数名、命令或变量名。 - -```md -**重点:线程需要入口函数。** - -使用 `rt_thread_create()` 创建线程。 -``` - -效果:**重点:线程需要入口函数。** 使用 `rt_thread_create()` 创建线程。 - -### 2.4 链接与图片 - -链接的格式是 `[显示文字](链接地址)`;图片只是在前面多一个 `!`。图片路径可以是网络地址,也可以是仓库中的相对路径。 - -```md -[RT-Thread 官网](https://www.rt-thread.org/) - -![RT-Thread 标志](images/rt-thread-logo.png) -``` - -### 2.5 代码块 - -用三个反引号包住多行代码;在开头的反引号后填写语言名称,可以获得语法高亮。 - -````md -```c -static void led_entry(void *parameter) -{ - /* 线程入口函数 */ -} -``` -```` - -### 2.6 引用与分隔线 - -行首使用 `>` 创建引用;连续三个减号 `---` 可创建分隔线。 - -```md -> 注意:中断服务程序中不能执行可能阻塞的操作。 - ---- -``` - -### 2.7 表格 - -表格用竖线 `|` 分隔列,第二行用减号 `-` 表示表头和内容的分界。 - -```md -| API | 作用 | -| --- | --- | -| `rt_thread_create()` | 创建线程 | -| `rt_thread_startup()` | 启动线程 | -``` - - -## 3. 新建并认识 QEMU A9 工程 - -创建工程前,可通过 RT-Thread Studio 工具栏打开 SDK 管理器,安装需要的 RT-Thread 源码或 BSP 资源包。 - -![RT-Thread Studio SDK 管理器](assets/01/01-sdk-manager.png) - -QEMU 是一个模拟器,可以在电脑上模拟开发板的处理器和部分外设。这里使用的 BSP(Board Support Package,板级支持包)是 `qemu-vexpress-a9`,它面向 QEMU 模拟的 ARM VExpress-A9 平台。这样即使暂时没有实体开发板,也可以学习 RT-Thread 的编译、启动和基础功能。 - -### 3.1 从新建 QEMU A9 工程开始 - -在 RT-Thread Studio 中新建项目时,选择 **RT-Thread Project**,然后在 BSP/开发板选择界面中选择 `qemu-vexpress-a9`(QEMU/VExpress A9)。 - -新建工程时需要关注的选项如下: - -![QEMU VExpress-A9 项目配置](assets/01/02-qemu-a9-project.png) - -工程创建后,先查看 `applications/main.c`。该参考工程的 `main()` 只输出 `hello rt-thread`,它是验证工程能否正常启动的最小应用入口。 - -```c -int main(void) -{ - printf("hello rt-thread\n"); - return 0; -} -``` - -> QEMU 负责模拟硬件;RT-Thread 仍然运行在模拟出来的 ARM 平台中。因此,应用代码的编写方式与真实开发板基本一致,但具体硬件外设的行为仍应以真实板卡验证为准。 - -### 3.2 QEMU A9 工程结构 - - - -```text -qemu-test/ -├─ applications/ # 用户应用:main.c、其他业务代码 -├─ drivers/ # 板级初始化与外设驱动 -├─ rt-thread/ # RT-Thread 内核、组件、CPU 架构适配与构建工具 -├─ build/、Debug/ # Studio 构建过程生成的中间文件或输出目录 -├─ Kconfig # BSP 配置菜单入口 -├─ cconfig.h # 根据配置生成的 C 宏定义 -├─ rtconfig.h # RT-Thread 功能配置结果 -├─ rtconfig.py # GCC 工具链、编译参数、链接参数与目标文件后缀配置 -├─ SConstruct # SCons 构建入口 -├─ SConscript # 收集各子目录源码的构建脚本 -├─ link.lds # 链接脚本:规定程序和内存的布局 -├─ qemu.bat / qemu.sh # 以带图形界面的方式启动 QEMU -├─ qemu-nographic.bat/.sh # 不启动图形界面,在终端中查看输出 -├─ qemu-dbg.bat/.sh # 以调试方式启动 QEMU -├─ sd.bin # 提供给 QEMU 的模拟 SD 卡镜像 -└─ .project、.cproject # RT-Thread Studio(Eclipse)工程元数据 -``` - -#### 3.2.1 应用目录:`applications/` - -`applications/main.c` 是用户应用的起点。当前工程还包含 `mnt.c`、`lcd_init.c` 等应用文件;`applications/SConscript` 会自动收集该目录下的 `.c`、`.cpp` 文件参与编译。 - -通常新增自己的业务功能时,优先在这个目录创建 `.c` 文件,而不是直接修改 `rt-thread` 目录中的内核文件。 - -#### 3.2.2 板级与驱动目录:`drivers/` - -`drivers/board.c` 负责最早期的板级初始化,例如中断初始化、堆初始化、组件初始化和控制台设备设置。该目录还包含串口、定时器、LCD、SDIO、网卡、鼠标和键盘等 QEMU A9 平台相关驱动。 - -驱动是否编译由 `drivers/Kconfig` 控制。例如 UART1 默认启用;网卡驱动依赖网络组件,LCD 驱动依赖图形引擎。配置改变后,相关宏会体现在 `rtconfig.h` 和 `cconfig.h` 中。 - -#### 3.2.3 内核目录:`rt-thread/` - -该目录是 RT-Thread 源码主体,常见子目录包括: - -| 目录 | 作用 | -| --- | --- | -| `src/` | 线程调度、时钟、IPC、内存管理等内核核心实现 | -| `include/` | 常用内核头文件,例如 `rtthread.h` | -| `components/` | 文件系统、网络、设备框架等可选组件 | -| `libcpu/` | 与 CPU 架构相关的启动、上下文切换、中断和 MMU 适配代码 | -| `tools/` | SCons、配置与工程生成等开发工具脚本 | - -对于 QEMU A9,`rt-thread/libcpu/arm/zynq7000/` 中包含 ARM Cortex-A 相关的启动、中断、上下文切换和 MMU 支持代码。 - -#### 3.2.4 配置、构建与启动文件 - -1. `Kconfig` 是配置菜单的入口,它会继续包含 RT-Thread 内核和 `drivers/Kconfig` 的配置项。 -2. `SConstruct` 是构建总入口,调用 `applications`、`drivers` 与 `rt-thread` 等目录的 `SConscript` 收集源码。 -3. `rtconfig.py` 指定 ARM GNU 工具链前缀 `arm-none-eabi-`、Cortex-A 编译参数和链接脚本 `link.lds`;编译后会生成 `rtthread.elf`,并转换出 `rtthread.bin`。 -4. `qemu.bat` 使用 `qemu-system-arm -M vexpress-a9` 启动模拟器;`qemu-nographic.bat` 适合只在终端观察串口输出。 - -> 文件修改优先级:编写功能改 `applications/`;调整硬件相关内容改 `drivers/`;通过配置工具修改功能开关;仅在学习或移植内核时才进入 `rt-thread/`。 - -### 3.3 STM32F407ZG 芯片工程 - -与 QEMU 工程不同,STM32F407ZG 工程运行在真实 MCU 上。RT-Thread Studio 新建项目时选择“基于芯片”,并依次选择 STMicroelectronics、STM32F4、STM32F407 和 STM32F407ZG。 - -控制台选择 UART1:发送引脚为 PA9,接收引脚为 PA10;调试器使用 ST-LINK,调试接口选择 SWD。工程默认使用芯片内部 HSI 时钟,若要修改时钟,需要再配置 drv_clk.c。 - -![STM32F407ZG 芯片项目配置](assets/01/03-stm32f407zg-project-config.png) - -参考工程 01_hello_stm32 的应用入口在 applications/main.c。程序每隔 1 秒通过日志输出一次 Hello RT-Thread!;下载到开发板后,可用 Studio 内置串行终端观察输出。 - -先在 Windows 设备管理器的“端口(COM 和 LPT)”中确认 ST-LINK 虚拟串口的 COM 号。 - -![Windows 设备管理器中的 ST-LINK 虚拟串口](assets/01/04-stlink-com-port.png) - -串行终端需要选择实际识别到的串口号(图中为 COM9),并设置为:波特率 115200、数据位 8、校验位 None、停止位 1、流控 None、编码 UTF-8。 - -![RT-Thread Studio 内置串行终端配置](assets/01/05-studio-serial-terminal.png) - -#### 3.3.1 启动文件:startup_stm32f407xx.S - -截图中的 startup_stm32f407xx.S 位于 CMSIS 的 STM32F407 系列模板目录,是供 GNU GCC 工具链使用的汇编启动文件。它最先在芯片复位后运行,不是普通业务代码。 - -它完成以下几件事: - -1. 设置初始栈指针 SP。 -2. 将 Flash 中 .data 段的初始值复制到 SRAM,并将 .bss 段清零。 -3. 调用 SystemInit() 完成芯片底层时钟等初始化。 -4. 进入 RT-Thread 的 entry();entry() 随后调用 rtthread_startup(),RT-Thread 创建主线程后,最终才执行 applications/main.c 中的 main()。 -5. 提供中断向量表:复位、HardFault、SysTick,以及 USART1 等外设中断发生时,处理器会从该表找到对应的中断处理函数。 - -启动路径可以概括为:复位 → startup_stm32f407xx.S → SystemInit() → entry() → rtthread_startup() → main()。 - -通常无需修改该文件。只有在移植到不同芯片、调整启动流程,或排查启动和中断异常时,才需要阅读或修改它。 -#### 3.3.2 MSH 基础操作 - -MSH 是 RT-Thread 的 FinSH 组件提供的命令行 Shell。通过 Studio 串行终端连接开发板后,复位开发板并按一次 Enter;看到 msh > 或 msh /> 提示符,就可以输入命令并按 Enter 执行。 - -常用命令如下: - -| 命令 | 用途 | -| --- | --- | -| help | 查看当前系统实际注册的命令 | -| version | 查看 RT-Thread 版本信息 | -| ps | 查看系统中的线程 | -| free | 查看内存使用情况 | -| list_device | 查看已注册设备 | -| clear | 清屏 | -| reboot | 立即重启开发板,未保存的运行状态会丢失 | - -输入 help 可查看当前工程可用命令;命令是否出现取决于当前启用的组件和驱动。 - - msh >help - RT-Thread shell commands: - clear - clear the terminal screen - version - show RT-Thread version information - list_thread - list thread - list_device - list device in system - ps - List threads in the system. - free - Show the memory usage in the system. - reboot - Reboot System - -输入 ps 可查看线程运行情况: - - msh >ps - thread pri status sp stack size max used left tick error - -------- --- ------- ---------- ---------- ------ ---------- --- - tshell 20 running 0x000000d0 0x00001000 15% 0x00000007 OK - tidle0 31 ready 0x00000060 0x00000100 56% 0x00000015 OK - timer 4 suspend 0x00000080 0x00000200 25% 0x00000009 OK - main 10 suspend 0x000000b4 0x00000800 14% 0x00000013 OK - -其中,thread 是线程名称;pri 是优先级,数值越小优先级越高;status 表示线程状态;stack size 是分配的栈大小;max used 是历史最大栈使用比例。tshell 是当前正在执行命令的 Shell 线程;suspend 表示线程正在等待,不一定是异常。 - -输入 free 可查看堆内存统计: - - msh >free - total : 125816 - used : 7076 - maximum : 7076 - available: 118740 - -total 是总堆内存,used 是当前已使用内存,maximum 是运行以来的最大使用量,available 是当前可用内存。上述数值会随工程配置和运行状态变化。 - -如果没有出现 MSH 提示符,优先检查串口号、115200 波特率、串口是否被其他软件占用,并确认 RT_USING_FINSH 和 FINSH_USING_MSH 已启用。 - -## 4. RT-Thread 启动流程 - -[RT-Thread 官方编程手册:基础知识与启动流程](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/basic/basic?id=rt-thread-%e5%90%af%e5%8a%a8%e6%b5%81%e7%a8%8b) - -RT-Thread 启动并不是复位后直接执行用户的 main()。芯片先运行启动文件,完成最基础的硬件和内存准备,再进入 RT-Thread 内核;内核创建线程并启动调度器后,用户 main() 才在主线程中执行。 - -![RT-Thread 启动流程图](assets/01/06-rtthread-startup-flow.png) - -### 4.1 从启动文件进入 RT-Thread - -芯片复位后,启动文件首先运行。不同工具链的入口不同:MDK 使用 $Sub$$main(),IAR 使用 __low_level_init(),GNU GCC 使用 entry()。它们最终都会进入 rtthread_startup()。 - -对本次 STM32F407ZG 的 GCC 工程而言,startup_stm32f407xx.S 先设置栈和内存段、调用 SystemInit(),再调用 entry();entry() 继续调用 rtthread_startup()。 - -### 4.2 rtthread_startup() 完成内核初始化 - -rtthread_startup() 按顺序完成关键准备工作: - -1. 关闭中断,避免初始化尚未完成时发生中断。 -2. 调用 rt_hw_board_init(),完成板级初始化;其中可执行板级自动初始化函数。 -3. 显示 RT-Thread 版本,初始化系统定时器、调度器和信号机制。 -4. 创建主线程、定时器服务线程和空闲线程。 -5. 启动调度器 rt_system_scheduler_start(),由调度器根据优先级运行线程。 - -### 4.3 主线程执行组件初始化与用户 main() - -调度器启动后,主线程执行 main_thread_entry()。它先调用 rt_components_init(),依次完成预初始化、设备、组件、环境和应用等自动初始化函数;随后才调用 applications/main.c 中的 main()。 - -因此可以把完整路径记为:复位 → 启动文件 → rtthread_startup() → 创建系统线程 → 启动调度器 → main_thread_entry() → 组件初始化 → 用户 main()。 - -> 关键理解:用户 main() 不是裸机意义上的第一个 C 函数;在 RT-Thread 中,它运行在已经由内核创建并调度的主线程里。 - -## 5. Git 与 Gitee - -Git 是分布式版本控制工具,用于记录文件的历史修改;Gitee 是托管 Git 仓库的平台。把夏令营笔记放入 Git 仓库后,可以保存版本、同步到远程,并在需要时恢复到历史版本。 - -### 5.1 在 Gitee 创建笔记仓库 - -登录 Gitee 后,点击右上角“+”并选择“新建仓库”。仓库名称可使用 rt-thread-summer-camp-notes,填写简短介绍,并按自己的分享需求选择公开或私有。 - -当前笔记目录已经有 01.md 和图片文件。为了避免首次上传时与线上文件冲突,创建远程仓库时建议不要勾选 README、.gitignore 或许可证初始化选项,创建一个空仓库即可。创建完成后,在仓库页面复制 SSH 地址,格式类似: - - git@gitee.com:你的用户名/rt-thread-summer-camp-notes.git - -仓库名称、归属、路径、可见性和初始化选项的详细说明可参考 [Gitee:创建你的第一个仓库](https://gitee.com/help/articles/4120)。 - -### 5.2 配置账户 SSH 公钥 - -Gitee 可以通过 SSH 协议访问 Git 仓库。配置账户 SSH 公钥后,当前电脑可使用 SSH 地址拉取和推送你有权限的仓库。 - -Windows 建议使用 Windows PowerShell 或 Git Bash;命令提示符不自带 cat 和 ls 命令。 - -#### 5.2.1 生成 SSH 密钥 - -下面命令的目的是生成一对 ED25519 密钥: - - ssh-keygen -t ed25519 -C "Gitee SSH Key" - -命令会依次询问保存位置、密码短语和确认密码短语。直接连续按三次 Enter 可使用默认保存位置且不设置密码短语;若电脑可能被他人使用,建议设置密码短语。 - -生成后会得到两个文件: - -- id_ed25519:私钥,用于证明当前电脑身份。 -- id_ed25519.pub:公钥,用于上传到 Gitee。 - -#### 5.2.2 查看并复制公钥 - -先确认密钥文件已经生成。PowerShell 中执行: - - Get-ChildItem $HOME\.ssh - -读取公钥时,PowerShell 使用: - - Get-Content $HOME\.ssh\id_ed25519.pub - -Git Bash 使用: - - cat ~/.ssh/id_ed25519.pub - -复制输出的整行内容;它通常以 ssh-ed25519 开头。 - -#### 5.2.3 在 Gitee 添加并测试 - -在 Gitee 中依次进入“个人设置 → 安全设置 → SSH 公钥 → 添加公钥”,粘贴公钥内容、填写标题,并按页面要求验证账户密码。添加后,可在同一页面查看或删除已添加的公钥。 - -下面命令的目的是测试当前 SSH 密钥是否已经绑定到 Gitee 账户: - - ssh -T git@gitee.com - -若输出中包含已成功认证和你的用户名,说明 SSH 配置完成;Gitee 不提供 SSH Shell 登录是正常现象。完整官方步骤见 [Gitee:SSH 公钥设置](https://help.gitee.com/base/account/SSH%E5%85%AC%E9%92%A5%E8%AE%BE%E7%BD%AE)。 - -> 安全提示:只能复制和上传以 .pub 结尾的公钥文件;绝不能上传、发送或提交不带 .pub 后缀的私钥文件 id_ed25519。仓库部署公钥与账户 SSH 公钥用途不同,部署公钥请参考 [添加部署公钥](https://help.gitee.com/repository/ssh-key/generate-and-add-ssh-public-key)。 - -### 5.3 Git 的基本同步流程 - -Git 的常用流程可以理解为:本地工作区 → 暂存区 → 本地提交历史 → 远程仓库。 - -1. 初始化当前目录为 Git 仓库。一个目录只需执行一次;已经是 Git 仓库时不要重复初始化。 - - git init - -2. 拉取远程分支的最新提交到本地。下面以 master 为例;实际项目应使用仓库的默认分支名。 - - git pull origin master - -3. 将指定文件或目录加入暂存区。暂存区是“下一次提交的待提交清单”,便于决定本次要提交哪些修改。 - - git add 文件或目录路径 - -4. 把暂存区内容创建为一次本地提交,并使用 -m 写明本次修改说明。 - - git commit -m "本次修改说明" - -5. 将当前本地分支的提交推送到远程仓库。 - - git push origin master - -6. 修改名为 origin 的远程仓库地址。将示例地址替换为自己的 Gitee SSH 仓库地址。 - - git remote set-url origin git@gitee.com:你的用户名/你的仓库.git - -7. 获取远端 master 分支的最新提交和引用,但不自动合并到当前本地分支,也不修改工作区文件。 - - git fetch origin master - - 获取后,远端分支的状态会更新到 origin/master;需要将更新合并到本地时,再使用 git pull 或 git merge。 - -8. 查看所有远程仓库的名称,以及用于拉取和推送的地址。 - - git remote -v - - git remote -v 不显示分支列表;若要查看远端分支,可使用: - - git branch -r - -9. 基于当前分支的当前提交创建名为 test 的新分支,并立即切换到该分支。 - - git checkout -b test - - 创建后可使用下面命令确认,带 * 的分支就是当前所在分支: - - git branch - -同步方向需要记住:git pull 是“远端 → 本地”,git push 是“本地 → 远端”。 - -> 提交完成后,本地 Git 历史中才会保存这次可追溯的版本;仅执行 git add 不等于已经完成版本保存。 - -### 5.4 简单冲突处理 - -当本地与远端修改了同一文件的同一位置时,执行 git pull 或合并分支可能产生冲突。Git 会暂停合并,要求手动决定保留哪部分内容。 - -1. 先查看发生冲突的文件: - - git status - -2. 打开冲突文件,找到以下标记,保留正确内容后删除这三行标记及不需要的内容: - - <<<<<<< HEAD - 本地修改 - ======= - 远端修改 - >>>>>>> origin/master - -3. 将解决后的文件加入暂存区,并创建一次解决冲突的提交: - - git add 冲突文件路径 - git commit -m "resolve merge conflict" - -4. 再继续推送: - - git push - -如果暂时无法判断应该保留哪一边,可放弃本次合并并恢复到合并前状态: - - git merge --abort - -> 处理冲突的关键不是选“本地”或“远端”,而是确认最终文件同时保留所需修改,并且不留下 <<<<<<<、=======、>>>>>>> 标记。 - -### 5.5 Fork 与 Pull Request(PR)流程 - -当自己没有原始仓库的直接写入权限时,使用 Fork 和 PR 提交作业。整体流程是:原始仓库 → 自己的 Fork 仓库 → 本地分支 → Pull Request。 - -1. 在 Gitee 将原始仓库 Fork 到自己的账号。Fork 会创建一份属于自己的远程仓库副本,自己可以向该副本推送分支。 - -2. 克隆自己 Fork 后的仓库到本地。后续提交应推送到自己的 Fork,而不是直接推送到原始仓库。 - -3. 从主分支创建作业分支,例如 test,并在该分支完成修改: - - git checkout -b test - -4. 将修改加入暂存区、创建本地提交,并推送作业分支到自己的 Fork: - - git add 文件或目录路径 - git commit -m "完成作业内容" - git push -u origin test - -5. 在 Gitee 的自己 Fork 仓库中创建 Pull Request,并选择: - - - 来源:自己 Fork 仓库中的 test 作业分支。 - - 目标:原始仓库老师指定的分支,通常是 main 或 master。 - -6. 提交 PR 前,检查文件列表、提交记录、来源分支和目标分支;确认无误后再创建 PR。 - -> 关键理解:PR 不是直接向原始仓库提交代码,而是请求把“自己 Fork 仓库中已推送的作业分支”合并到原始仓库的指定分支。 diff --git "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index df42dd7b1f6512468ede375bd82940370f74f81a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\253\230\347\277\214\350\276\260/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,573 +0,0 @@ -# RT-Thread 夏令营笔记02:RTOS、线程机制与 SCons 构建系统 - - -## 目录 - -- [1. 从裸机前后台系统到 RTOS](#1-从裸机前后台系统到-rtos) -- [2. RT-Thread 从复位到用户 main 函数](#2-rt-thread-从复位到用户-main-函数) -- [3. 线程控制块与线程栈](#3-线程控制块与线程栈) -- [4. 线程生命周期](#4-线程生命周期) -- [5. 调度:优先级与时间片](#5-调度优先级与时间片) -- [6. 两个关键系统线程](#6-两个关键系统线程) -- [7. 线程创建、启动与让出 CPU](#7-线程创建启动与让出-cpu) -- [8. SCons 构建系统](#8-scons-构建系统) -- [9. Studio 工程创建、调试与线程示例](#9-studio-工程创建调试与线程示例) - -## 1. 从裸机前后台系统到 RTOS - -### 1.1 裸机前后台模型的阻塞问题 - -裸机程序常见为“前后台”模型: - -- **后台**:在 `while (1)` 循环中按顺序执行读取传感器、SPI 通信、LCD 刷新等任务。 -- **前台**:由串口接收、SPI 接收等硬件中断触发的中断服务程序(ISR)。中断可暂时打断 CPU 正在执行的后台代码。 - -这种模式适合任务少、逻辑简单的项目,但后台任务本质上仍是串行执行。例如,SPI 任务为了等待一个标志位而阻塞 1 秒时,排在后面的 LCD 刷新任务也会被延后;即使 LCD 本应高频刷新,实际画面也可能卡顿。 - -裸机模式的常见局限如下: - -| 局限 | 原因 | 结果 | -| --- | --- | --- | -| 并发效率低 | 后台任务依次运行,长任务会占住 CPU | 其他任务难以及时处理 | -| 实时性受影响 | 阻塞或长延时会传递给后续任务 | 高优先级业务可能错过响应时机 | -| 可维护性较差 | 业务逻辑常交织在一个主循环中 | 修改一项功能可能影响多处流程 | -| 可重用性较差 | 模块边界不清晰、硬件和业务耦合 | 换项目或换芯片时容易重复改写 | - -> 中断能处理紧急事件,但不能替代完整的任务组织方式。中断服务程序通常应尽量短小,不宜把耗时或可能阻塞的业务全部放入其中。 - -### 1.2 RTOS 的分而治之思路 - -RTOS(Real-Time Operating System,实时操作系统)的基本思路是**分而治之**:将一个复杂系统拆成多个职责单一的线程(也常称任务),再由内核调度器决定当前执行哪个线程。 - -![裸机前后台模型与 RTOS 线程调度的对比](assets/02/01-裸机和RTOS的区别.png) - -例如可以拆分为: - -- SPI 接收线程:接收 W5500 等模块的数据; -- 串口通信线程:处理 AT 指令或其他串口报文; -- LCD 刷新线程:按设定节奏刷新显示; -- 业务处理线程:完成协议解析和控制逻辑。 - -单核 MCU 在任一时刻仍然只执行一个线程;所谓“多个任务同时运行”,是调度器在不同线程间快速切换后带来的宏观效果。重要的是,RTOS 中的延时通常会让**当前线程**暂时挂起并让出 CPU,调度器可转而运行其他就绪线程;它不同于裸机中常见的忙等延时。 - -## 2. RT-Thread 从复位到用户 main 函数 - -RT-Thread 中的用户 `main()` 并不是芯片复位后直接运行的第一个 C 函数。内核必须先完成板级初始化、创建系统线程并启动调度器,随后用户代码才在 Main 线程中执行。 - -先通过下面的全景流程定位各小节的内容: - -```text -① 芯片复位 - → 汇编启动文件完成最小运行环境准备 - → 工具链对应的早期 C 入口接入 RT-Thread - → RT-Thread 系统启动 (见 2.1) - -② RT-Thread 完成内核初始化 - → 初始化板级硬件、系统时钟与调度器 - → 创建 Main、Timer、Idle 等系统线程 - → 启动调度器 (见 2.2) - -③ 调度器选择最高优先级的就绪线程 - → 先运行优先级更高的系统线程(如已启用的 Timer 线程) - → Main 线程获得运行机会 (见 2.3) -``` - -![RT-Thread 启动流程(函数级参考)](assets/02/02-rtthread-startup-flow.png) - -### 2.1 芯片启动与工具链入口 - -本节对应全景流程的 **①**,只解释“芯片复位后如何进入 `rtthread_startup()`”;系统线程创建、调度器启动和用户 `main()` 的执行分别在 2.2、2.3 节说明。 - -芯片复位后,通常先从汇编启动文件( STM32 工程中的 `startup_*.S`)开始。它负责最早期的准备,如设置初始栈、初始化 `.data`/`.bss` 段,并进行 `SystemInit()` 等芯片级初始化。准备完成后,程序才具备稳定运行 C 代码的基础。 - -MDK、IAR、GCC 是**编译阶段使用的工具链**,典型入口有:MDK 的 `$Sub$$main()`、IAR 的 `__low_level_init()`、GCC BSP 中常见的 `entry()`,它们会为程序提供或配合不同的早期 C 入口;RT-Thread 利用这个入口,在用户 `main()` 之前调用 `rtthread_startup()`。这些工具链的共同目标是**从汇编启动文件进入早期 C 代码,并在用户 main() 前接入 RT-Thread 初始化。** 对 GCC 工程排查启动问题时,可从 `startup_*.S`、`entry()` 和 `rtthread_startup()` 依次跟踪。 - -### 2.2 rtthread_startup 的主要工作 - -`rtthread_startup()` 负责把芯片从“能运行 C 代码”带到“能调度线程”。典型过程如下;每一步的函数名与图对应: - -1. **关闭全局中断**(`rt_hw_interrupt_disable()`):避免内核和硬件尚未准备完成时被中断打断。 -2. **板级初始化**(`rt_hw_board_init()`):完成时钟、堆、控制台串口等底层准备;其中可调用 `rt_components_board_init()`,执行已注册的 board init functions。 -3. **输出 Logo**(`rt_show_version()`):串口控制台准备好后,打印 RT-Thread 的版本和 Logo。 -4. **初始化系统时钟**(`rt_system_timer_init()`):系统 Tick 为延时、超时和时间片提供基础。 -5. **初始化调度器**(`rt_system_scheduler_init()`):建立就绪线程等调度管理结构,供后续选择可执行线程。 -6. **初始化信号机制(可选)**(`rt_system_signal_init()`):是否执行取决于是否启用信号功能。 -7. **创建 Main 线程**(`rt_application_init()`):其入口函数为 `main_thread_entry()`;实际使用静态还是动态方式创建取决于当前 BSP。 -8. **创建 Timer 服务线程(可选)**(`rt_system_timer_thread_init()`):其入口函数为 `rt_thread_timer_entry()`;未启用软件定时器功能时不会创建。 -9. **创建 Idle 空闲线程**(`rt_thread_idle_init()`):其入口函数为 `rt_thread_idle_entry()`。Idle 本质上也是系统线程;当系统中没有其他就绪线程可运行、CPU 处于空闲状态时,调度器就会运行它。 -10. **启动调度器**(`rt_system_scheduler_start()`):内核开始按调度规则运行线程。 - -> 初始化期间关闭中断不等于系统永远不响应中断。启动过程完成后,系统会恢复正常的中断与调度运行。 - -### 2.3 主线程、自动初始化与用户代码 - -调度器启动时会选择当前就绪线程中优先级最高的线程。默认配置下,Timer 线程若启用通常优先级较高;没有更高优先级就绪线程时,Main 线程会开始运行。 - -Main 线程的入口并非立刻执行用户 `main()`,而是先完成自动初始化机制: - -```text -Main 线程入口 - → 执行自动初始化 - → 依次完成预初始化、设备、组件、环境和应用初始化 - → 用户 main() -``` - -开发者可通过不同阶段的初始化宏注册驱动、组件或应用初始化函数。`rt_components_init()` 会按照配置和初始化阶段统一调用这些函数,因此不必在 `main()` 中手动逐个调用。完成这些准备后,才进入 `applications/main.c` 中的用户 `main()`。 - -这也说明:在 RT-Thread 中,`main()` 本质上运行在一个由内核创建和调度的线程中,而不是裸机意义上的唯一程序入口。 - -### 2.4 在 01_hello_stm32 工程中定位启动流程 - -以下位置基于本机 `D:\RT-ThreadStudio\workspace\01_hello_stm32` 工程。阅读源码时,建议先沿着“启动汇编 → `entry()` → `rtthread_startup()` → `main_thread_entry()` → 用户 `main()`”这条主线跟踪;其余文件用于展开每一步的具体实现。 - -| 流程阶段 | 当前工程中的文件 | 关键函数或内容 | -| --- | --- | --- | -| 芯片复位与最早期初始化 | `libraries/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc/startup_stm32f407xx.S` | `Reset_Handler`,设置初始栈、初始化内存段,并跳转到 C 入口。 | -| GCC 早期 C 入口 | `rt-thread/src/components.c` | `entry()` 调用 `rtthread_startup()`。同一文件也包含 MDK/IAR 对应入口的条件编译代码。 | -| RT-Thread 启动主流程 | `rt-thread/src/components.c` | `rtthread_startup()`:关闭中断、板级初始化、定时器与调度器初始化、创建系统线程、启动调度器。 | -| 板级硬件初始化 | `drivers/board.c` | `rt_hw_board_init()`:堆、时钟、串口控制台等板级初始化的具体实现。 | -| board 自动初始化 | `rt-thread/src/components.c` | `rt_components_board_init()`:执行已注册的 board init functions。 | -| 创建 Main 线程 | `rt-thread/src/components.c` | `rt_application_init()`:根据是否启用堆,调用 `rt_thread_create()` 或 `rt_thread_init()` 创建 Main 线程。 | -| Main 线程与自动初始化 | `rt-thread/src/components.c` | `main_thread_entry()` 调用 `rt_components_init()`,然后进入用户 `main()`。 | -| 系统线程实现 | `rt-thread/src/timer.c`、`rt-thread/src/idle.c` | `rt_system_timer_thread_init()` 创建 Timer 服务线程;`rt_thread_idle_init()` 创建 Idle 线程。 | -| 调度器实现 | `rt-thread/src/scheduler.c` | `rt_system_scheduler_start()` 启动调度器;该文件还包含就绪队列选择与调度逻辑。 | -| 线程创建实现 | `rt-thread/src/thread.c` | `rt_thread_init()`、`rt_thread_create()`、`rt_thread_startup()` 等线程 API 的具体实现。 | -| 用户应用入口 | `applications/main.c` | 用户编写的 `main()`,由 Main 线程在自动初始化完成后调用。 | - -> 不同 RT-Thread 版本或 BSP 的目录、代码行号可能不同;排查当前工程时,以函数名搜索和调用关系为准。 - -## 3. 线程控制块与线程栈 - -### 3.1 线程由哪些部分构成 - -线程控制块(Thread Control Block,TCB)是内核管理线程的数据结构。在 RT-Thread 中,线程控制块由 `struct rt_thread` 定义;每创建一个线程,就会有一个对应的 `struct rt_thread` 作为它的线程内核对象。 - -多个线程内核对象会链接到内核的**对象容器**中统一管理。图中的“线程对象链表”通常是**双向链表**:每个 `struct rt_thread` 都通过内嵌的 `parent`(`struct rt_object`)携带链表节点,节点包含前驱和后继链接,因此内核可方便地将线程插入、删除或遍历。这个对象容器链表用于管理已注册的线程对象;它与调度器使用的就绪线程队列不是同一个概念。 - -![线程内核对象与对象容器链表](assets/02/03-线程内核对象.png) - -从运行视角,可以将一个线程概括为: - -```text -线程控制块(记录名称、状态、优先级、入口函数和参数等管理信息) - + -线程栈(保存局部变量与线程切换现场的独立内存空间) -``` - -其中,入口函数 `entry` 和入口参数 `parameter` 是**记录在线程控制块中的信息**,并不是与线程控制块并列的独立内存对象。 - -下面是课堂对应版本中 `struct rt_thread` 的核心字段定义(不同 RT-Thread 版本或配置可能增减部分字段): - -```c -struct rt_thread -{ - struct rt_object parent; /* 内核对象父类:名称、类型、链表节点等 */ - - /* 栈与入口 */ - void *sp; /* 当前栈指针 */ - void *entry; /* 线程入口函数 */ - void *parameter; /* 传给入口函数的参数 */ - void *stack_addr; /* 线程栈起始地址 */ - rt_uint32_t stack_size; /* 线程栈大小 */ - - /* 状态与调度 */ - rt_err_t error; /* 线程错误代码 */ - rt_uint8_t stat; /* 线程状态 */ - rt_uint8_t current_priority; /* 当前优先级 */ - rt_uint8_t init_priority; /* 初始优先级 */ - rt_uint32_t number_mask; /* 调度器内部的优先级位图/掩码信息 */ - - ... - - rt_ubase_t init_tick; /* 初始时间片计数值 */ - rt_ubase_t remaining_tick; /* 剩余时间片计数值 */ - - /* 定时与退出清理 */ - struct rt_timer thread_timer; /* 内置线程定时器 */ - void (*cleanup)(struct rt_thread *tid); /* 线程退出清理函数 */ - rt_uint32_t user_data; /* 用户自定义数据 */ -}; -``` - -> 阅读源码时,优先关注 `sp`(切换现场)、`entry`(业务入口)、`stat`/优先级/时间片(调度条件),以及 `thread_timer` 和 `cleanup`(等待与退出)。 - -### 3.2 线程栈 - -每个线程都有自己的栈。除了函数局部变量外,线程被切走时的寄存器现场、程序执行位置等上下文也需要保存,才能在以后从正确位置继续执行。 - -以线程 A 切换到线程 B 为例: - -1. 内核把线程 A 的寄存器现场等上下文保存到 A 自己的栈中,常称为“压栈”或“保存上下文”。 -2. 内核加载线程 B 之前保存的上下文,包括 B 的栈指针。 -3. CPU 从线程 B 上次暂停的位置继续运行;如果 B 是第一次运行,则从它的入口函数开始。 -4. 以后切回线程 A 时,再从 A 的栈中恢复现场,A 能接着之前的位置继续执行。 - -线程 A 切换到线程 B 的完整过程可以简记为: - -```text -线程 A 正在运行 - ↓ -将 A 的寄存器现场压入 A 的线程栈,并保存 A 的 SP - ↓ -将更新后的 A 的 SP 记录到 A 的线程控制块 - ↓ -调度器选中线程 B - ↓ -CPU 的 SP → 指向线程 B 的栈 - ↓ -恢复 B 的寄存器和 PC - ↓ -CPU 继续执行线程 B 的代码 -``` - -如果没有这套保存与恢复机制,线程 A 在中途被切走后,就无法知道返回时该从哪条指令、哪个局部变量状态继续运行。 - -线程栈大小必须与线程业务相匹配。大量局部变量、深层函数调用和递归都会增加栈占用;栈太小可能产生栈溢出、内存踩踏或难以复现的异常。嵌入式开发中应尽量避免不受控的递归,并结合线程栈使用情况调整栈大小。 - -## 4. 线程生命周期 - -### 4.1 五种状态与转换 - -课堂将 RT-Thread 线程生命周期概括为五种状态:初始态、就绪态、运行态、挂起态和关闭态。 - -```text -创建线程 - → 初始态 - -- rt_thread_startup() --> 就绪态 - -- 被调度器选中 --> 运行态 - -- 延时或等待资源 --> 挂起态 - -- 等待条件满足 --> 就绪态 - -- 线程退出 --> 关闭态 -``` - -![RT-Thread 线程状态转换与常见 API](assets/02/04-线程状态转换.png) - - -各状态可这样理解: - -| 状态 | 含义 | 常见转移原因 | -| --- | --- | --- | -| 初始态 | 线程对象已创建,但尚未参与调度 | 调用 `rt_thread_startup()` 后变为就绪态 | -| 就绪态 | 线程具备运行条件,等待调度器选择 | 被选中后开始运行 | -| 运行态 | 当前正在占用 CPU 执行业务代码 | 被抢占、时间片用完、延时或等待资源时离开运行态 | -| 挂起态 | 暂时不能运行,正在等待时间或某个条件 | 延时到期、信号量释放、收到消息等后重新就绪 | -| 关闭态 | 线程已结束,不再参与调度 | 后续由系统完成资源清理 | - -使运行线程进入等待/挂起状态的常见情形包括:调用延时函数、等待信号量、等待互斥量、等待事件、接收邮箱或消息队列。对应资源就绪或超时后,线程会回到就绪态,等待下一次被调度。 - -> 创建线程不等于线程已经运行。只有将线程启动并放入就绪队列后,它才有机会被调度器执行。 - -### 4.2 线程关闭后的回收 - -线程的入口函数执行结束,或显式退出后,会进入关闭态。关闭态线程不再参与调度,但其线程控制块和动态申请的栈空间未必在同一时刻释放。 - -RT-Thread 中的 Idle 线程会检查已关闭的线程,并负责后续回收工作。对动态创建的线程,这一步通常包含释放其从堆中申请的内存;线程退出时注册的清理钩子也可用于执行用户自定义的清理操作。 - -因此要区分两个概念: - -- **线程退出**:业务代码已经结束,线程被标记为关闭,不再调度。 -- **资源回收**:由 Idle 线程在适当时机处理关闭线程及其动态资源。 - -## 5. 调度:优先级与时间片 - -### 5.1 抢占式优先级调度 - -线程优先级表示线程被调度的优先程度。RT-Thread 采用基于优先级的抢占式调度:**优先级数值越小,优先级越高**,因此优先级 1 高于优先级 5。更重要、响应更紧急的任务通常应配置更高优先级。 - -系统支持的优先级数量由 `RT_THREAD_PRIORITY_MAX` 配置决定,最大可配置为 256 级;若配置为 32 级,则有效优先级为 0~31。资源紧张的系统可选择 8 级或 32 级等更少的优先级,以减少内核调度数据的资源占用。最低优先级默认保留给 Idle 线程,用户业务线程一般不使用。 - -当一个比当前线程优先级更高的线程变为就绪态时,内核会换出当前线程,优先执行高优先级线程,这就是“抢占”。高优先级线程运行结束、挂起或等待资源后,调度器才会重新选择下一个最高优先级的就绪线程。具体可用的优先级数量请以工程的 `menuconfig` 或配置头文件为准。 - -### 5.2 相同优先级时的时间片轮转 - -时间片(time slice)只对**优先级相同且同时处于就绪态**的线程生效。不同优先级线程之间始终先比较优先级,不会因为时间片而让低优先级线程抢先运行。 - -当多个同优先级线程都就绪时,系统采用时间片轮转:每个线程一次连续运行的最长时长由其时间片决定;用完后,调度器会轮到同优先级的下一个就绪线程。时间片以系统 Tick(OS Tick)为单位;Tick 周期由系统配置决定,例如某工程可能设为 1 ms 或 10 ms,不能脱离具体配置固定理解。 - -例如线程 A、B 的优先级相同,且此时没有更高优先级的就绪线程: - -- A 的时间片为 10 Tick; -- B 的时间片为 5 Tick。 - -若图示时刻 `t1` 先轮到 B,则执行顺序为:B 运行 5 Tick → A 运行 10 Tick → B 再运行 5 Tick → A 再运行 10 Tick,循环轮转。时间片限制的是同优先级线程**单次连续运行时长**,不能突破更高优先级线程的抢占规则。 - -![同优先级线程的时间片轮转调度](assets/02/05-优先级与时间片调度.png) - -## 6. 两个关键系统线程 - -### 6.1 Idle 空闲线程 - -Idle 是 RT-Thread 创建的空闲线程,具有以下特点: - -- 它使用系统中最低优先级;例如 32 级优先级配置时通常为 31。 -- 它始终为就绪态,以保证所有其他线程都不能运行时,CPU 仍有线程可执行。 -- 它通常是无限循环,不能像普通业务线程一样被永久挂起或删除。 - -Idle 线程的主要职责有两类: - -1. **回收资源**:检查并回收处于关闭态的线程,尤其是动态线程申请的内存。 -2. **执行空闲钩子**:可用于低功耗休眠、喂狗等仅适合在系统空闲时做的工作。 - -Idle 钩子只会在系统没有其他就绪线程时运行,因此适合执行很短的空闲处理:例如让 CPU 进入低功耗休眠状态。 - -也可以把“喂狗”放入 Idle 钩子:若某个异常循环或高优先级线程长期占用 CPU,Idle 无法运行、看门狗得不到喂狗,硬件便可复位系统。此策略的前提是系统正常运行时 Idle 会周期性获得执行机会;若正常负载本来就会长期占满 CPU,则可能误触发看门狗复位。 - -> Idle 钩子必须短小且不能阻塞,否则会延迟资源回收,并影响系统进入真正的空闲状态。 - -### 6.2 Main 主线程 - -Main 线程也是系统启动阶段创建的线程。它负责执行组件自动初始化,并最终调用用户编写的 `main()`: - -```text -创建 Main 线程 - → 调度器选中 Main 线程 - → rt_components_init() - → 用户 main() -``` - -因此,即使源码形式上仍写作 `int main(void)`,运行时它已经处于 RT-Thread 的线程调度体系内。用户在 `main()` 中创建其他线程、初始化应用模块或启动业务逻辑,都是在这一基础上进行的。 - -## 7. 线程创建、启动与让出 CPU - -### 7.1 两种创建方式:静态与动态 - -RT-Thread 的线程和许多 IPC 对象都支持两种内存分配方式:**静态创建**和**动态创建**。两者都是合法选择,区别主要在于线程控制块和线程栈由谁提供、内存占用能否在编译阶段确定。 - -| 对比项 | 静态创建 | 动态创建 | -| --- | --- | --- | -| 创建 API | `rt_thread_init()` | `rt_thread_create()` | -| 线程控制块与栈 | 由应用预先定义并传入 | 内核从系统堆中申请 | -| 栈起始地址参数 | 需要调用者提供 | 不需要,内部自动分配 | -| 编译/链接期内存估算 | 较明确,RAM 占用具有确定性 | 取决于运行时申请情况 | -| 主要风险 | 栈大小固定,预留过大浪费 RAM | 堆不足时创建失败,长期反复申请/释放可能产生碎片 | -| 适用倾向 | 高可靠、资源可预测或禁止动态内存的场景 | 一般应用中按需创建、生命周期灵活的任务 | - -“静态”指内存由用户提前准备,并不表示线程只能在系统刚启动时创建;“动态”指创建时从堆中申请内存,也不表示线程一定比静态线程优先级高。 - -对于高安全或内存确定性要求高的场景,常要求全静态分配,以便在编译/链接阶段评估内存布局。普通应用可以使用动态、静态或两者混合的方式;关键是评估堆容量、线程栈和线程生命周期。 - -### 7.2 静态创建:rt_thread_init - -静态创建前需要由应用准备线程控制块和一块满足对齐要求的栈数组。`rt_thread_init()` 的函数原型如下: - -```c -/** - * @brief 初始化一个静态线程,使其进入初始态。 - * @return RT_EOK 初始化成功。 - * @return -RT_ERROR 初始化失败。 - */ -rt_err_t rt_thread_init( - struct rt_thread *thread, /* 用户提供的线程控制块指针 */ - const char *name, /* 线程名称;最长为 RT_NAME_MAX,超长自动截断 */ - void (*entry)(void *parameter), /* 线程入口函数 */ - void *parameter, /* 传给入口函数的参数 */ - void *stack_start, /* 用户提供的线程栈起始地址,需按要求对齐 */ - rt_uint32_t stack_size, /* 线程栈大小,单位:字节 */ - rt_uint8_t priority, /* 优先级;数值越小越高,范围由 RT_THREAD_PRIORITY_MAX 决定 */ - rt_uint32_t tick); /* 时间片大小,单位:系统 Tick;仅同优先级就绪线程间生效 */ -``` - -`rt_thread_init()` 成功后,线程仍处于初始态;还需要调用 `rt_thread_startup()`,线程才会进入就绪态并有机会被调度。 - -### 7.3 动态创建:rt_thread_create - -动态创建不需要传入线程控制块和栈地址,内核会从系统堆中为它们分配空间。函数原型如下: - -```c -/** - * @brief 从系统堆中创建一个动态线程,使其进入初始态。 - * @return 非 RT_NULL:创建成功,返回线程句柄。 - * @return RT_NULL:创建失败,通常表示堆空间不足。 - */ -rt_thread_t rt_thread_create( - const char *name, /* 线程名称;最长为 RT_NAME_MAX,超长自动截断 */ - void (*entry)(void *parameter), /* 线程入口函数 */ - void *parameter, /* 传给入口函数的参数 */ - rt_uint32_t stack_size, /* 由内核从堆中分配的线程栈大小,单位:字节 */ - rt_uint8_t priority, /* 优先级;数值越小越高,范围由 RT_THREAD_PRIORITY_MAX 决定 */ - rt_uint32_t tick); /* 时间片大小,单位:系统 Tick;仅同优先级就绪线程间生效 */ -``` - -动态创建失败通常意味着系统堆无法满足本次申请,例如剩余堆空间不足或出现严重碎片。应用可通过内存查看命令或接口观察堆使用情况,但更重要的是在设计阶段控制动态对象数量和生命周期,避免频繁创建、销毁大块内存。 - -> 静态线程的栈数组和控制块必须在其整个生命周期内保持有效。因此不要把它们定义为创建函数中的普通局部变量;应使用 `static` 存储期或其他长期有效的内存区域。 - -### 7.4 创建后启动:rt_thread_startup - -无论使用 `rt_thread_init()` 还是 `rt_thread_create()`,新线程创建后都先处于初始态。调用 `rt_thread_startup()` 后,线程才会被加入就绪队列,从初始态变为就绪态,并在合适的调度时机运行。 - -```text -rt_thread_init() / rt_thread_create() - → 初始态 -rt_thread_startup() - → 就绪态 -调度器选中 - → 运行态 -``` - -这是初学线程时最容易遗漏的一步:创建成功不代表线程已经开始执行。 - -### 7.5 用延时主动让出 CPU - -线程不能长期在无限循环中持续占用 CPU。若当前线程暂时没有工作,可调用 `rt_thread_delay()`(1个OS Tick为单位)或 `rt_thread_mdelay()` (ms为单位)等延时接口,使自己在指定时间内进入挂起态。调度器便能运行其他就绪线程。 - -延时时长应由业务周期、响应要求和 CPU 负载决定;对于等待数据或资源的场景,后续还应学习信号量、事件、邮箱和消息队列等同步通信机制,而不是单纯轮询加延时。 - -## 8. SCons 构建系统 - -SCons 是 RT-Thread 常用的构建系统。它使用 Python 风格脚本描述源码、头文件路径、宏依赖和编译参数,目标是降低手写 Makefile 与 IDE 路径配置的负担,让开发者更专注于功能开发。 - -> 本篇命令以课堂内容为主。不同 RT-Thread 版本、Env/Studio 安装方式及 BSP 支持的工程类型可能不同;执行生成工程或打包命令前,应先在 BSP 根目录查看当前环境支持的选项。 - -### 8.1 三类关键脚本文件 - -| 文件 | 位置与职责 | -| --- | --- | -| `SConstruct` | 构建总入口,组织整个 BSP 的构建流程,并调用各目录的构建脚本 | -| `SConscript` | 通常放在含源代码的目录中,描述该目录或子目录要参与构建的文件与规则 | -| `rtconfig.py` | 保存工具链、CPU 架构、浮点 ABI、优化级别、链接参数等编译配置 | - -常见组织方式是:每个源码子目录放一个 `SConscript`,各自只关心本目录的源码;父级脚本再通过 `SConscript()` 继续读取子目录脚本。这种分层结构使构建规则跟随源码目录组织,便于维护。 - -`rtconfig.py` 会根据 GCC、MDK/ARMCC、IAR、LLVM 等工具链分别设置参数。CPU 架构、Thumb 指令集、硬浮点/软浮点和优化等级都与目标芯片密切相关,通常应通过项目配置生成或谨慎修改,不能随意从其他芯片工程复制。 - -### 8.2 常用构建命令 - -在 **BSP 根目录**、并已进入正确 Env/终端环境后,常见命令如下: - -| 命令 | 用途 | -| --- | --- | -| `scons` | 按当前配置编译 BSP | -| `scons -jN` | 使用 N 个并行任务编译,例如 `-j8`;N 不宜超过电脑可承受的并发度 | -| `scons -c` | 清理本次构建产生的目标文件等中间产物,之后需重新编译 | -| `scons --target=<目标类型>` | 依据当前目录生成对应 IDE 或构建系统工程,例如 MDK、IAR、VS Code、CMake;具体目标名以当前 BSP 支持项为准 | -| `scons --dist` | 将 BSP 所需文件整理为更精简的独立开发包,便于复制到其他目录继续开发 | - -若当前环境无法找到交叉编译器,可在 Env 中设置工具链路径和工具链类型;课堂中提到的 `RTT_EXEC_PATH` 就是用于指定工具链路径的环境变量。此类设置与电脑上的实际安装路径相关,应优先使用 Studio/Env 已配置的工具链,避免写死他人电脑的路径。 - -`scons --dist` 的目的不是“备份所有文件”,而是移除无关 BSP 内容、保留独立开发所需的代码和配置。生成后应在新的目录中重新构建验证,确认所选功能和依赖均已被带入。 - -### 8.3 SConscript 的基本写法 - -一个典型 `SConscript` 通常做四件事:获取当前目录、添加头文件搜索路径、收集源文件、把它们定义为构建组。 - -```python -from building import * - -cwd = GetCurrentDir() -src = Glob('*.c') - -group = DefineGroup( - 'applications', - src, - CPPPATH=[cwd], -) - -Return('group') -``` - -常见元素的作用如下: - -| 元素 | 作用 | -| --- | --- | -| `GetCurrentDir()` | 获取当前 `SConscript` 所在目录,避免手工硬编码路径 | -| `CPPPATH` | 增加头文件搜索目录;找不到 `.h` 文件时应优先检查这里 | -| `Glob('*.c')` | 用通配符收集当前目录的 C 源文件 | -| `src` | 待参与编译的源文件列表,可逐项添加或由 `Glob()` 生成 | -| `DefineGroup()` | 将一组源文件、路径、依赖和编译参数定义为构建单元 | -| `Return('group')` | 将当前目录构建组返回给上一级脚本 | - -`Glob('*.c')` 使用方便,但会把当前目录下所有匹配的 C 文件都加入构建。若某个文件暂时不应编译,应显式移除它,或改为逐个列出所需源文件;否则容易引入重复符号或未完成代码。 - -### 8.4 依赖宏决定源码是否参与构建 - -`DefineGroup()` 可通过 `depend` 参数声明宏依赖。只有相关宏已在配置中启用时,对应源文件才会被加入构建。 - -```python -from building import * - -cwd = GetCurrentDir() -src = ['hello.c'] - -group = DefineGroup( - 'hello', - src, - depend=['RT_USING_HELLO'], - CPPPATH=[cwd], -) - -Return('group') -``` - -上例中,只有 `RT_USING_HELLO` 被定义,`hello.c` 才会参与编译。宏通常由 `Kconfig` 菜单配置生成,并写入 `rtconfig.h` 等配置文件。因此“菜单中勾选功能 → 生成配置宏 → SConscript 判断依赖 → 源文件参与编译”是一条完整链路。 - -构建参数也可按作用域配置:例如公共 C/C++ 参数、C 专用参数、C++ 专用参数、全局宏和局部宏。局部参数应只作用于确有特殊要求的文件或目录,避免无意影响整个工程。 - -### 8.5 SConscript 如何连接子目录 - -父目录不一定直接列出所有深层源文件,而是调用子目录中的脚本,让子目录自行返回构建组。可以把这种写法理解为“桥接”:父脚本负责继续向下发现构建规则,子脚本负责说明自己的源码。 - -```python -SConscript([ - 'applications/SConscript', - 'drivers/SConscript', -]) -``` - -这样增加一个模块时,通常只需在新模块目录编写 `SConscript`,并在适当的父级脚本中接入它,而无需把每个 `.c` 文件都堆放在顶层构建入口。 - -### 8.6 Env、Studio 与 menuconfig 配置 - -RT-Thread Env 的 `menuconfig` 和 Studio 的图形化 RT-Thread Settings,本质上都在修改 Kconfig 对应的功能选择。保存配置后,相关宏会写入 `rtconfig.h` 等生成文件,SCons 再据此决定哪些模块和源码参与构建。 - -使用 Studio 时,图形界面更直观;若参与 BSP 或主线贡献,需要更灵活地处理脚本和配置,可进一步熟悉 Env 与命令行。无论使用哪种界面,修改配置后都应重新构建,不能只勾选菜单却不编译。 - -## 9. Studio 工程创建、调试与线程示例 - -### 9.1 断点验证启动流程 - -调试启动流程时,可在 `entry()`、`rtthread_startup()`、板级初始化、线程创建与调度器启动附近设置断点,并按以下顺序观察: - -```text -启动汇编文件 - → SystemInit() 等芯片初始化 - → entry() - → 关闭中断、板级初始化、系统时钟与调度器初始化 - → 创建 Main / Timer / Idle 线程 - → 启动调度器并首次切换线程 -``` - -在调试器的变量视图中,可检查线程控制块的名称、栈地址、栈大小、优先级和状态。 - -若启用了 Timer 线程,且它的优先级高于 Main 线程,调度器启动后可能先运行 Timer 线程;待其挂起或等待后,才会轮到 Main 线程。这是优先级调度的正常表现,不是 `main()` 丢失或卡死。 - -### 9.2 使用软件包运行线程示例 - -Studio/Env 可从软件包仓库拉取功能模块。以 `kernel samples` 中的线程示例为例:在软件包配置界面启用线程 sample、保存配置并重新构建后,代码会下载到工程的 `packages/` 目录并参与构建。 - -![kernel samples 中的线程示例软件包](assets/02/thread软件包.png) - -示例中包含两个不同优先级的线程: - -- `thread2` 优先级更高,且入口函数中没有主动延时;因此它会先连续输出并很快结束。 -- `thread1` 优先级较低,周期性延时后打印,因此表现为持续、周期性的输出。 - -这个现象验证了两点:高优先级就绪线程会优先运行;动态创建的短生命周期线程退出后可由 Idle 线程自动回收。是否能直接编译某个软件包,还取决于它的 Kconfig 依赖、板级外设与已启用组件;例如图形库等软件包往往需要显示、输入或内存等额外支持,拉取后报依赖错误并不罕见。 - -### 9.3 用 MSH 观察运行结果 - -MSH 是 FinSH 提供的命令行 Shell。连接串口并进入 `msh />` 提示符后,可用以下命令观察线程: - -```text -msh /> list_thread -``` - -或: - -```text -msh /> ps -``` - -输出可查看线程名称、优先级、状态、栈大小与栈使用情况。线程 sample 运行后,已退出并完成回收的 `thread2` 不再出现在列表中;仍在周期性执行或延时等待的 `thread1` 会保留在列表中。按 Tab 键还可查看或补全当前系统已注册的命令。 - -> `ps`、`list_thread` 和软件包命令是否可用,取决于当前工程是否启用了 FinSH/MSH 以及相关组件。遇到“命令不存在”时,应先检查配置,而不是假定所有 BSP 都默认提供该命令。 diff --git "a/2026/\347\254\2543\347\273\204/\351\273\204\345\260\211\346\201\251/\347\254\224\350\256\260/day 1 markdown.md" "b/2026/\347\254\2543\347\273\204/\351\273\204\345\260\211\346\201\251/\347\254\224\350\256\260/day 1 markdown.md" deleted file mode 100644 index 36ef9002e95d0b442de3414b75d4e6a633b121b6..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2543\347\273\204/\351\273\204\345\260\211\346\201\251/\347\254\224\350\256\260/day 1 markdown.md" +++ /dev/null @@ -1,137 +0,0 @@ -# Git 基础常用命令 -## 1. git init:初始化当前目录为 git 仓库 -```bash -git init -``` - -作用:把当前本地文件夹初始化为 Git 本地仓库。 -使用场景:从零开始,把普通文件夹变成 git 仓库。 -注意:不要在已经 clone 下来的仓库里再执行git init,会重复初始化,引发异常。 - -## 2. git pull origin master:拉取远程 master 分支 - -作用:拉取远程origin仓库的master分支代码,并且自动合并到本地当前分支。 -origin:远程仓库的默认别名。 -master:远程主分支。 -场景:远程仓库更新了,把最新代码下载合并到本地。 -风险:本地有修改时,pull 可能产生代码冲突,冲突需要手动解决。 - -## 3.git add file/path:添加 file/path 到 git 暂存区 - -```bash -git add 文件名 - -#添加整个文件夹 - -git add ./文件夹名 - -#添加全部改动文件 - -git add . -``` - -作用:将文件 / 文件夹加入暂存区 (stage)。 -暂存区理解:一个临时缓冲区,告诉 git:这些文件改动,我准备提交。 -补充笔记原话:git 暂存区代表你本地的 git 管理库,文件加入暂存区后,改动会被 git 追踪保护,但还没有真正生成本地版本快照。 -⚠️add 之后改动还没真正提交,必须配合git commit。 - -## 4. git commit -m "message":本次 git 提交日志为 "message" - -```bash -git commit -m "feat: day1 黄尉恩笔记作业" -``` - -作用:把暂存区里面的改动,生成本地版本快照,保存到本地 git 仓库。 --m:后面双引号里面填写本次提交说明 (message),写清楚本次做了什么修改。 -❗ 不要漏写 -m,直接写git commit "xxx"会报错。 - -## 5.git push origin master:将本地 git 分支最新内容同步 / 推送到远端 - -```bash -git push origin master -``` - -作用:把本地master分支的提交记录,推送到远程仓库 origin 的 master 分支。 -流程顺序:add → commit → push。 -我们不 push 到 master,push 到自己新建的开发分支。 - -## 6.git remote set-url origin git@gitee.com:kurisaW/git_-proj.git - -```bash -git remote set-url origin git@gitee.com:kurisaW/git_-proj.git -``` - -作用:修改当前本地仓库关联的远程仓库地址。 -使用场景: -clone 错仓库,需要更换远程地址; -https 地址切换为 ssh 地址; -地址需要改成你自己仓库的 SSH/HTTPS 链接。 - -## 7.git fetch origin master:同步远端分支到本地 - -```bash -git fetch origin master -``` - -作用:把远程origin/master的更新下载到本地,但是不会自动合并到你的本地分支。 -和git pull区别: -fetch:只下载,不合并,安全,看完再手动合并。 -pull = fetch + merge,下载并且直接自动合并,容易冲突。 - -## 8.git remote -v:查看仓库远端分支情况 - -```bash -git remote -v -``` - -作用:查看当前本地仓库关联的远程仓库地址,显示 fetch(拉取)和 push(推送)地址。 -排查问题:不知道自己本地绑的是哪个远程仓库,执行这条命令核对。 - -## 9.git checkout -b test:基于当前分支切出一个名为 test 的新分支 - -```bash -git checkout -b test -``` - -作用:‑b代表新建分支;基于当前所在分支,创建名为test的新分支,并且立刻切换到这个新分支。 - -# 完整流程 - -## 1、clone 你 fork 后的个人仓库到本地 - -```bash -git clone git@gitee.com:你的ID/RSOC‑RTT.git -cd RSOC‑RTT -``` - -## 2、基于 master 创建自己的作业分支 - -```bash -git checkout -b day1黄尉恩 -``` - -## 3、把笔记文件放到目录 2026/第3组/黄尉恩/ - -## 4、添加文件到暂存区 - -```bash -git add 2026/第3组/黄尉恩/ -``` - -## 5、本地提交 - -```bash -git commit -m "feat: day1 黄尉恩笔记与作业" -``` - -## 6、推送到自己 Gitee 远程的这个分支 - -```bash -git push origin day1黄尉恩 -``` - -## 7、在网页 Gitee 打开自己仓库,创建 Pull Request 提交给官方仓库。 - -禁止直接在 master 分支修改文件,所有作业在自己新建分支完成。 - -1 \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_build.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_build.png" deleted file mode 100644 index 60574af0d768931c6d36ab5913ce566b7e5f428c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_build.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_package_result.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_package_result.png" deleted file mode 100644 index 145918c6161da4834030a66409da3eb7ee2be246..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_package_result.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_package_retry.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_package_retry.png" deleted file mode 100644 index 8e1f6d1d003ae8d0d3cc458a5a15f2c56ee98138..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_package_retry.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_parking.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_parking.png" deleted file mode 100644 index c2de1d85d75dfa6ff7b21a0d37ba2d0fc33c1591..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_parking.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_startup.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_startup.png" deleted file mode 100644 index 56d495e6fb54a59824d4c767c0893cc2191a70b3..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_startup.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_ticket_mutex.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_ticket_mutex.png" deleted file mode 100644 index 6224e5299677bd9b45815298dca996d2e8b3e643..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_ticket_mutex.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_ticket_nolock.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_ticket_nolock.png" deleted file mode 100644 index bbeb5d7dcdcece48455f3ff9f1e29f7f77f2397c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/figure/day3_ticket_nolock.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" deleted file mode 100644 index f3cebaf0083bf6d3651c23f641ba6633d5e12b19..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" +++ /dev/null @@ -1,175 +0,0 @@ -#include -#include -#include - -#define PACKAGE_PER_SENDER 5 -#define PACKAGE_TOTAL 10 -#define THREAD_STACK 1024 -#define THREAD_PRIORITY 18 - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -struct sender_info -{ - rt_uint8_t id; -}; - -static rt_mq_t package_mq; -static rt_sem_t package_done_sem; -static struct sender_info senders[2] = {{1}, {2}}; -static rt_uint32_t send_failed; -static rt_bool_t package_running; - -static const char *region_name(rt_uint8_t region) -{ - static const char *const names[] = {"invalid", "east", "south", "north"}; - return (region <= 3) ? names[region] : names[0]; -} - -static void sender_entry(void *parameter) -{ - struct sender_info *sender = (struct sender_info *)parameter; - rt_uint8_t i; - - for (i = 1; i <= PACKAGE_PER_SENDER; i++) - { - struct package_msg msg; - - msg.id = sender->id * 1000 + i; - msg.region = ((sender->id + i - 2) % 3) + 1; - msg.weight = 700 + sender->id * 100 + i * 50; - - while (rt_mq_send(package_mq, &msg, sizeof(msg)) != RT_EOK) - { - rt_base_t level = rt_hw_interrupt_disable(); - send_failed++; - rt_hw_interrupt_enable(level); - rt_kprintf("queue full, package %u retry\n", msg.id); - rt_thread_mdelay(100); - } - rt_kprintf("sender%u send package %u\n", sender->id, msg.id); - } - - rt_sem_release(package_done_sem); -} - -static void sorter_entry(void *parameter) -{ - rt_uint16_t region_count[3] = {0}; - rt_uint16_t total_count = 0; - rt_uint32_t total_weight = 0; - - RT_UNUSED(parameter); - rt_thread_mdelay(500); /* 先让队列装满,演示发送失败和重试。 */ - - while (1) - { - struct package_msg msg; - - rt_mq_recv(package_mq, &msg, sizeof(msg), RT_WAITING_FOREVER); - if (msg.id == 0) - break; - - region_count[msg.region - 1]++; - total_count++; - total_weight += msg.weight; - rt_kprintf("package %u -> %s, weight: %u g\n", - msg.id, region_name(msg.region), msg.weight); - rt_thread_mdelay(180); - } - - rt_kprintf("total packages: %u\n", total_count); - rt_kprintf("east: %u, south: %u, north: %u\n", - region_count[0], region_count[1], region_count[2]); - rt_kprintf("total weight: %u g\n", total_weight); - rt_kprintf("send failed: %u, result: %s\n", send_failed, - (total_count == PACKAGE_TOTAL && total_weight == 10000) ? - "PASS" : "FAIL"); - rt_sem_release(package_done_sem); -} - -static void package_task(void *parameter) -{ - rt_thread_t tid[3] = {RT_NULL}; - struct package_msg end_msg = {0}; - rt_uint8_t i; - - RT_UNUSED(parameter); - send_failed = 0; - - package_mq = rt_mq_create("pkgque", sizeof(struct package_msg), 5, - RT_IPC_FLAG_FIFO); - package_done_sem = rt_sem_create("pkgdone", 0, RT_IPC_FLAG_FIFO); - if ((package_mq == RT_NULL) || (package_done_sem == RT_NULL)) - { - rt_kprintf("create package IPC failed\n"); - goto cleanup; - } - - tid[0] = rt_thread_create("pkgsnd1", sender_entry, &senders[0], - THREAD_STACK, THREAD_PRIORITY, 5); - tid[1] = rt_thread_create("pkgsnd2", sender_entry, &senders[1], - THREAD_STACK, THREAD_PRIORITY, 5); - tid[2] = rt_thread_create("pkgsort", sorter_entry, RT_NULL, - THREAD_STACK, THREAD_PRIORITY, 5); - if ((tid[0] == RT_NULL) || (tid[1] == RT_NULL) || (tid[2] == RT_NULL)) - { - rt_kprintf("create package thread failed\n"); - for (i = 0; i < 3; i++) - if (tid[i] != RT_NULL) - rt_thread_delete(tid[i]); - goto cleanup; - } - - rt_kprintf("package demo start\n"); - for (i = 0; i < 3; i++) - rt_thread_startup(tid[i]); - - /* 两个发送线程结束后,再通知分拣线程退出。 */ - rt_sem_take(package_done_sem, RT_WAITING_FOREVER); - rt_sem_take(package_done_sem, RT_WAITING_FOREVER); - while (rt_mq_send(package_mq, &end_msg, sizeof(end_msg)) != RT_EOK) - rt_thread_mdelay(50); - rt_sem_take(package_done_sem, RT_WAITING_FOREVER); - -cleanup: - if (package_done_sem != RT_NULL) - { - rt_sem_delete(package_done_sem); - package_done_sem = RT_NULL; - } - if (package_mq != RT_NULL) - { - rt_mq_delete(package_mq); - package_mq = RT_NULL; - } - package_running = RT_FALSE; -} - -int package_demo(void) -{ - rt_thread_t tid; - - if (package_running) - { - rt_kprintf("package demo is running\n"); - return -RT_EBUSY; - } - - package_running = RT_TRUE; - tid = rt_thread_create("pqcoord", package_task, RT_NULL, - 1280, THREAD_PRIORITY - 1, 5); - if (tid == RT_NULL) - { - package_running = RT_FALSE; - return -RT_ENOMEM; - } - rt_thread_startup(tid); - return RT_EOK; -} -MSH_CMD_EXPORT(package_demo, message queue package demo); diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" deleted file mode 100644 index 93af51ef6234343f51334139950a7d4c218c9ba6..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" +++ /dev/null @@ -1,140 +0,0 @@ -#include -#include -#include - -#define CAR_COUNT 5 -#define PARKING_SPACES 3 -#define THREAD_STACK 1024 -#define THREAD_PRIORITY 18 - -struct car_info -{ - rt_uint8_t id; -}; - -static rt_sem_t space_sem; -static rt_sem_t car_done_sem; -static struct car_info cars[CAR_COUNT]; -static rt_uint8_t used_spaces; -static rt_uint8_t max_used_spaces; -static rt_bool_t parking_running; - -static void car_entry(void *parameter) -{ - struct car_info *car = (struct car_info *)parameter; - rt_uint8_t used; - - rt_thread_mdelay((car->id - 1) * 40); - - /* 2 秒内没有车位就提示,稍后继续申请。 */ - while (rt_sem_take(space_sem, rt_tick_from_millisecond(2000)) != RT_EOK) - { - rt_kprintf("car%u wait timeout, retry later\n", car->id); - rt_thread_mdelay(300); - } - - { - rt_base_t level = rt_hw_interrupt_disable(); - used_spaces++; - if (used_spaces > max_used_spaces) - max_used_spaces = used_spaces; - used = used_spaces; - rt_hw_interrupt_enable(level); - } - rt_kprintf("car%u entered, used spaces: %u\n", car->id, used); - - /* 前 3 辆车停得久一些,让后两辆车看到超时现象。 */ - rt_thread_mdelay((car->id <= 3) ? 2600 : 800); - - { - rt_base_t level = rt_hw_interrupt_disable(); - used_spaces--; - used = used_spaces; - rt_hw_interrupt_enable(level); - } - rt_kprintf("car%u left, used spaces: %u\n", car->id, used); - - rt_sem_release(space_sem); - rt_sem_release(car_done_sem); -} - -static void parking_task(void *parameter) -{ - rt_thread_t tid[CAR_COUNT] = {RT_NULL}; - rt_uint8_t i; - - RT_UNUSED(parameter); - used_spaces = 0; - max_used_spaces = 0; - - space_sem = rt_sem_create("pkspace", PARKING_SPACES, RT_IPC_FLAG_FIFO); - car_done_sem = rt_sem_create("pkdone", 0, RT_IPC_FLAG_FIFO); - if ((space_sem == RT_NULL) || (car_done_sem == RT_NULL)) - { - rt_kprintf("create parking semaphore failed\n"); - goto cleanup; - } - - for (i = 0; i < CAR_COUNT; i++) - { - char name[RT_NAME_MAX]; - - cars[i].id = i + 1; - rt_snprintf(name, sizeof(name), "pkcar%u", i + 1); - tid[i] = rt_thread_create(name, car_entry, &cars[i], - THREAD_STACK, THREAD_PRIORITY, 5); - if (tid[i] == RT_NULL) - { - rt_kprintf("create car%u failed\n", i + 1); - while (i > 0) - rt_thread_delete(tid[--i]); - goto cleanup; - } - } - - rt_kprintf("parking demo start: 3 spaces, 5 cars\n"); - for (i = 0; i < CAR_COUNT; i++) - rt_thread_startup(tid[i]); - for (i = 0; i < CAR_COUNT; i++) - rt_sem_take(car_done_sem, RT_WAITING_FOREVER); - - rt_kprintf("parking finished, max used: %u, result: %s\n", - max_used_spaces, - (max_used_spaces <= PARKING_SPACES) ? "PASS" : "FAIL"); - -cleanup: - if (car_done_sem != RT_NULL) - { - rt_sem_delete(car_done_sem); - car_done_sem = RT_NULL; - } - if (space_sem != RT_NULL) - { - rt_sem_delete(space_sem); - space_sem = RT_NULL; - } - parking_running = RT_FALSE; -} - -int parking_demo(void) -{ - rt_thread_t tid; - - if (parking_running) - { - rt_kprintf("parking demo is running\n"); - return -RT_EBUSY; - } - - parking_running = RT_TRUE; - tid = rt_thread_create("pkcoord", parking_task, RT_NULL, - 1280, THREAD_PRIORITY - 1, 5); - if (tid == RT_NULL) - { - parking_running = RT_FALSE; - return -RT_ENOMEM; - } - rt_thread_startup(tid); - return RT_EOK; -} -MSH_CMD_EXPORT(parking_demo, counting semaphore parking demo); diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" deleted file mode 100644 index 304b16f354f5d9cd933deab14eec8ce3a2c36232..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" +++ /dev/null @@ -1,150 +0,0 @@ -#include -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -#define THREAD_STACK 1024 -#define THREAD_PRIORITY 18 - -struct module_info -{ - const char *name; - rt_uint32_t event; - rt_uint32_t delay_ms; -}; - -static rt_event_t ready_event; -static rt_sem_t startup_done_sem; -static rt_bool_t startup_running; -static struct module_info modules[3] = -{ - {"network", EVENT_NET_READY, 800}, - {"sensor", EVENT_SENSOR_READY, 1400}, - {"storage", EVENT_STORAGE_READY, 5000} -}; - -static void module_entry(void *parameter) -{ - struct module_info *module = (struct module_info *)parameter; - - rt_thread_mdelay(module->delay_ms); - rt_kprintf("%s ready\n", module->name); - rt_event_send(ready_event, module->event); - rt_sem_release(startup_done_sem); -} - -static void business_entry(void *parameter) -{ - rt_uint32_t received; - rt_uint32_t current = 0; - - RT_UNUSED(parameter); - - if (rt_event_recv(ready_event, EVENT_ALL_READY, RT_EVENT_FLAG_AND, - rt_tick_from_millisecond(3000), &received) != RT_EOK) - { - rt_event_recv(ready_event, EVENT_ALL_READY, RT_EVENT_FLAG_OR, 0, ¤t); - rt_kprintf("wait modules timeout:"); - if (!(current & EVENT_NET_READY)) - rt_kprintf(" network"); - if (!(current & EVENT_SENSOR_READY)) - rt_kprintf(" sensor"); - if (!(current & EVENT_STORAGE_READY)) - rt_kprintf(" storage"); - rt_kprintf(" is not ready\n"); - - rt_event_recv(ready_event, EVENT_ALL_READY, RT_EVENT_FLAG_AND, - RT_WAITING_FOREVER, &received); - } - - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - rt_sem_release(startup_done_sem); -} - -static void startup_task(void *parameter) -{ - static const char *const names[4] = {"stnet", "stsens", "ststore", "stbiz"}; - rt_thread_t tid[4] = {RT_NULL}; - rt_uint8_t i; - - RT_UNUSED(parameter); - - ready_event = rt_event_create("stevent", RT_IPC_FLAG_FIFO); - startup_done_sem = rt_sem_create("stdone", 0, RT_IPC_FLAG_FIFO); - if ((ready_event == RT_NULL) || (startup_done_sem == RT_NULL)) - { - rt_kprintf("create startup IPC failed\n"); - goto cleanup; - } - - for (i = 0; i < 3; i++) - { - tid[i] = rt_thread_create(names[i], module_entry, &modules[i], - THREAD_STACK, THREAD_PRIORITY, 5); - if (tid[i] == RT_NULL) - break; - } - if (i == 3) - { - tid[3] = rt_thread_create(names[3], business_entry, RT_NULL, - THREAD_STACK, THREAD_PRIORITY, 5); - if (tid[3] == RT_NULL) - i = 3; - else - i = 4; - } - - if (i != 4) - { - rt_kprintf("create startup thread failed\n"); - while (i > 0) - rt_thread_delete(tid[--i]); - goto cleanup; - } - - rt_kprintf("startup check begin\n"); - for (i = 0; i < 4; i++) - rt_thread_startup(tid[i]); - for (i = 0; i < 4; i++) - rt_sem_take(startup_done_sem, RT_WAITING_FOREVER); - -cleanup: - if (startup_done_sem != RT_NULL) - { - rt_sem_delete(startup_done_sem); - startup_done_sem = RT_NULL; - } - if (ready_event != RT_NULL) - { - rt_event_delete(ready_event); - ready_event = RT_NULL; - } - startup_running = RT_FALSE; -} - -int startup_demo(void) -{ - rt_thread_t tid; - - if (startup_running) - { - rt_kprintf("startup demo is running\n"); - return -RT_EBUSY; - } - - startup_running = RT_TRUE; - tid = rt_thread_create("stcoord", startup_task, RT_NULL, - 1280, THREAD_PRIORITY - 1, 5); - if (tid == RT_NULL) - { - startup_running = RT_FALSE; - return -RT_ENOMEM; - } - rt_thread_startup(tid); - return RT_EOK; -} -MSH_CMD_EXPORT(startup_demo, event set startup demo); diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" deleted file mode 100644 index dead13a6e7d87086753b578f1ca1500f6e91290c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" +++ /dev/null @@ -1,168 +0,0 @@ -#include -#include -#include - -#define TICKET_COUNT 20 -#define THREAD_STACK 1024 -#define THREAD_PRIORITY 18 - -struct window_info -{ - char name; -}; - -static rt_mutex_t ticket_mutex; -static rt_sem_t window_done_sem; -static struct window_info windows[2] = {{'A'}, {'B'}}; -static volatile rt_int32_t tickets_left; -static rt_bool_t use_mutex; -static rt_bool_t sold[TICKET_COUNT + 1]; -static rt_uint8_t sold_count; -static rt_uint8_t duplicate_count; -static rt_bool_t ticket_running; - -static void record_ticket(rt_int32_t ticket_no) -{ - rt_base_t level = rt_hw_interrupt_disable(); - - if (sold[ticket_no]) - duplicate_count++; - else - sold[ticket_no] = RT_TRUE; - sold_count++; - - rt_hw_interrupt_enable(level); -} - -static void window_entry(void *parameter) -{ - struct window_info *window = (struct window_info *)parameter; - - while (1) - { - rt_int32_t ticket_no; - - if (use_mutex) - rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); - - if (tickets_left <= 0) - { - if (use_mutex) - rt_mutex_release(ticket_mutex); - break; - } - - ticket_no = tickets_left; - if (!use_mutex) - rt_thread_mdelay(10); /* 故意扩大无锁阶段的竞争窗口。 */ - - tickets_left--; - record_ticket(ticket_no); - rt_kprintf("[%s] window %c sold ticket %d\n", - use_mutex ? "mutex" : "no lock", - window->name, ticket_no); - - if (use_mutex) - rt_mutex_release(ticket_mutex); - rt_thread_mdelay(5); - } - - rt_sem_release(window_done_sem); -} - -static rt_err_t run_ticket_test(rt_bool_t locked) -{ - rt_thread_t tid_a; - rt_thread_t tid_b; - rt_uint8_t i; - rt_uint8_t unique = 0; - - use_mutex = locked; - tickets_left = TICKET_COUNT; - sold_count = 0; - duplicate_count = 0; - rt_memset(sold, 0, sizeof(sold)); - - tid_a = rt_thread_create(locked ? "tklkA" : "tknlA", window_entry, - &windows[0], THREAD_STACK, THREAD_PRIORITY, 5); - tid_b = rt_thread_create(locked ? "tklkB" : "tknlB", window_entry, - &windows[1], THREAD_STACK, THREAD_PRIORITY, 5); - if ((tid_a == RT_NULL) || (tid_b == RT_NULL)) - { - if (tid_a != RT_NULL) - rt_thread_delete(tid_a); - if (tid_b != RT_NULL) - rt_thread_delete(tid_b); - rt_kprintf("create ticket window failed\n"); - return -RT_ENOMEM; - } - - rt_kprintf("ticket test start: %s\n", locked ? "with mutex" : "without mutex"); - rt_thread_startup(tid_a); - rt_thread_startup(tid_b); - rt_sem_take(window_done_sem, RT_WAITING_FOREVER); - rt_sem_take(window_done_sem, RT_WAITING_FOREVER); - - for (i = 1; i <= TICKET_COUNT; i++) - if (sold[i]) - unique++; - - rt_kprintf("ticket result: sold=%u, unique=%u, duplicate=%u, left=%d\n", - sold_count, unique, duplicate_count, tickets_left); - return RT_EOK; -} - -static void ticket_task(void *parameter) -{ - RT_UNUSED(parameter); - - ticket_mutex = rt_mutex_create("tkmutex", RT_IPC_FLAG_PRIO); - window_done_sem = rt_sem_create("tkdone", 0, RT_IPC_FLAG_FIFO); - if ((ticket_mutex == RT_NULL) || (window_done_sem == RT_NULL)) - { - rt_kprintf("create ticket IPC failed\n"); - goto cleanup; - } - - if (run_ticket_test(RT_FALSE) == RT_EOK) - { - rt_kprintf("--------------------------------\n"); - run_ticket_test(RT_TRUE); - } - -cleanup: - if (window_done_sem != RT_NULL) - { - rt_sem_delete(window_done_sem); - window_done_sem = RT_NULL; - } - if (ticket_mutex != RT_NULL) - { - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - } - ticket_running = RT_FALSE; -} - -int ticket_demo(void) -{ - rt_thread_t tid; - - if (ticket_running) - { - rt_kprintf("ticket demo is running\n"); - return -RT_EBUSY; - } - - ticket_running = RT_TRUE; - tid = rt_thread_create("tkcoord", ticket_task, RT_NULL, - 1280, THREAD_PRIORITY - 1, 5); - if (tid == RT_NULL) - { - ticket_running = RT_FALSE; - return -RT_ENOMEM; - } - rt_thread_startup(tid); - return RT_EOK; -} -MSH_CMD_EXPORT(ticket_demo, mutex ticket selling demo); diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 15a239d9f0e6c3b73faf4fecb94c4eb968b58505..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,485 +0,0 @@ -# 【Day3】RT-Thread 线程间同步与通信实验报告 - -> **实验平台:STM32F407 星火1号(STM32F407ZG)** -> **操作系统:RT-Thread 4.1.1** -> **实验日期:2026 年 8 月 19 日** - -## 目录 - -- [一、实验目的](#一实验目的) -- [二、实验环境](#二实验环境) -- [三、IPC 理论总结](#三ipc-理论总结) - - [3.1 信号量](#31-信号量) - - [3.2 互斥量](#32-互斥量) - - [3.3 事件集](#33-事件集) - - [3.4 消息队列](#34-消息队列) - - [3.5 四种 IPC 的区别](#35-四种-ipc-的区别) -- [四、练习一:停车场车位管理](#四练习一停车场车位管理) -- [五、练习二:多窗口售票系统](#五练习二多窗口售票系统) -- [六、练习三:系统启动条件检查](#六练习三系统启动条件检查) -- [七、练习四:快递分拣中心](#七练习四快递分拣中心) -- [八、MSH 命令与资源管理](#八msh-命令与资源管理) -- [九、编译验证与实验结论](#九编译验证与实验结论) - ---- - -## 一、实验目的 - -本次作业主要练习 RT-Thread 中常用的线程间同步与通信方式,包括信号量、互斥量、事件集和消息队列。实验目标如下: - -1. 使用计数信号量管理多个相同的有限资源。 -2. 理解多个线程同时访问共享变量时产生的竞态问题。 -3. 使用互斥量保护完整的共享资源操作。 -4. 使用事件集等待多个初始化条件全部完成。 -5. 使用消息队列在线程之间传递结构体数据。 -6. 学会通过 MSH 命令启动实验,并在实验结束后释放资源。 - ---- - -## 二、实验环境 - -| 项目 | 配置 | -| --- | --- | -| 开发板 | STM32F407 星火1号 | -| MCU | STM32F407ZG,Cortex-M4 | -| RTOS | RT-Thread 4.1.1 | -| 工程目录 | `code/day3_f407_IPC` | -| 系统节拍 | `RT_TICK_PER_SECOND = 1000`,1 tick = 1 ms | -| 串口 | UART1,115200 bit/s | -| 编译器 | GNU Tools for ARM Embedded Processors 5.4.1 | -| 主要配置 | 已开启信号量、互斥量、事件集、消息队列、动态堆和 MSH | - -新增源码如下: - -```text -applications/ -├── main.c -├── parking_demo.c -├── ticket_demo.c -├── startup_demo.c -├── package_demo.c -└── SConscript -``` - -`main.c` 不再周期打印 Hello 日志,四个练习全部通过 MSH 命令单独启动。 - ---- - -## 三、IPC 理论总结 - -### 3.1 信号量 - -信号量可以理解为一个由内核维护的计数值。线程调用 `rt_sem_take()` 获取信号量,调用 `rt_sem_release()` 释放信号量。 - -- 初始值为 0 或 1 时,常用于线程同步。 -- 初始值大于 1 时,可用于管理多个相同资源。 -- 当计数为 0 时,申请资源的线程可以阻塞等待,也可以设置超时时间。 - -本次停车场有 3 个车位,因此使用初值为 3 的计数信号量。完成信号量则用于通知协调线程“某个工作线程已经结束”。 - -### 3.2 互斥量 - -互斥量用于保护共享资源。同一时刻只能有一个线程持有互斥量,其他线程必须等待。 - -互斥量和二值信号量看起来相似,但互斥量有所有者概念,并支持优先级继承,更适合保护共享数据。典型使用顺序如下: - -```c -rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); -/* 检查、修改和使用共享数据 */ -rt_mutex_release(ticket_mutex); -``` - -本次售票实验中,剩余票数、售出票号记录和打印结果属于同一个完整操作,因此应放在同一个互斥区中。 - -### 3.3 事件集 - -事件集内部保存多个事件位,一个事件对象可以同时表示多个条件。线程可以选择: - -- `RT_EVENT_FLAG_OR`:任意一个事件到达即可唤醒。 -- `RT_EVENT_FLAG_AND`:所有指定事件都到达后才唤醒。 -- `RT_EVENT_FLAG_CLEAR`:接收后清除已经收到的事件位。 - -本次用 3 个事件位分别表示网络、传感器和存储模块就绪。业务线程使用 AND 模式等待,只有 3 个事件全部出现后才能继续运行。 - -### 3.4 消息队列 - -信号量、互斥量和事件集主要传递“状态”,消息队列还可以传递实际数据。发送线程把一条消息复制到队列,接收线程再从队列中取出,因此两个线程不需要同时运行。 - -本次消息是一整个快递结构体: - -```c -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -``` - -队列容量只有 5 条,而两个发送线程一共发送 10 条消息。当队列已满时,`rt_mq_send()` 返回失败,发送线程记录失败次数,延时后重发同一条消息。 - -### 3.5 四种 IPC 的区别 - -| IPC | 主要作用 | 本次应用 | -| --- | --- | --- | -| 计数信号量 | 记录可用资源数量 | 管理 3 个停车位 | -| 互斥量 | 保护共享资源,避免并发修改 | 保护剩余票数和售票记录 | -| 事件集 | 用多个事件位组合多个条件 | 等待 3 个模块全部就绪 | -| 消息队列 | 在线程间传递带内容的消息 | 传递快递编号、区域和重量 | - ---- - -## 四、练习一:停车场车位管理 - -### 4.1 设计思路 - -程序创建一个初值为 3 的动态信号量 `space_sem`,再创建 5 个车辆线程。每辆车执行以下步骤: - -```text -车辆到达 - -> 在 2 秒内申请车位 - -> 申请成功:进入停车场,used_spaces 加 1 - -> 停留一段时间 - -> 离开停车场,used_spaces 减 1 - -> 释放车位信号量 - -> 通知协调线程车辆任务完成 -``` - -前 3 辆车停留 2600 ms,后两辆车在约 2 秒时仍无法获得车位,因此会打印等待超时,再延时 300 ms 重试。这样可以观察到题目要求的超时现象。 - -`used_spaces` 只是用于显示和检查当前停车数量,修改时使用很短的关中断临界区,避免多个车辆线程同时修改。车位的真正管理由计数信号量完成。 - -### 4.2 关键代码 - -```c -while (rt_sem_take(space_sem, - rt_tick_from_millisecond(2000)) != RT_EOK) -{ - rt_kprintf("car%u wait timeout, retry later\n", car->id); - rt_thread_mdelay(300); -} - -/* 车辆离开 */ -rt_sem_release(space_sem); -``` - -### 4.3 运行命令与实测结果 - -```text -msh >parking_demo -``` - -实测串口现象: - -```text -parking demo start: 3 spaces, 5 cars -car1 entered, used spaces: 1 -car2 entered, used spaces: 2 -car3 entered, used spaces: 3 -car4 wait timeout, retry later -car5 wait timeout, retry later -... -parking finished, max used: 3, result: PASS -``` - -![停车场计数信号量实测结果](figure/day3_parking.png) - -*图 1 停车场计数信号量实验结果* - -从实测结果可以看到,前 3 辆车先占满车位,car4 和 car5 等待超时后重试。车辆离开后,等待车辆能够继续进入。最终 5 辆车均完成停车,最大同时停车数为 3,结果为 `PASS`。 - -### 4.4 思考题:为什么使用计数信号量,而不是互斥量? - -因为停车场同时允许 3 辆车进入。计数信号量可以把 3 个车位表示为计数值 3,每进入一辆车计数减 1,每离开一辆车计数加 1。 - -互斥量表示同一时刻只能有一个线程进入临界区。如果用一个互斥量表示停车场,就会错误地限制为只能停 1 辆车,不能直接表达“有 3 个相同资源”。 - ---- - -## 五、练习二:多窗口售票系统 - -### 5.1 无锁对比实验 - -两个窗口线程共同访问 `tickets_left`。无锁阶段在读取票数和减少票数之间加入 10 ms 延时: - -```c -ticket_no = tickets_left; -rt_thread_mdelay(10); -tickets_left--; -``` - -例如窗口 A 和窗口 B 都可能先读到 20,然后分别打印自己卖出了 20 号票,从而出现重复票号。这说明单条 C 语句看起来很简单,但“检查是否有票、读取票号、减少余票、记录和打印”组合起来并不是原子操作。 - -程序使用 `sold[]` 数组记录已经出现的票号,并统计 `sold`、`unique` 和 `duplicate`,方便对比两次实验。 - -### 5.2 使用互斥量 - -加锁阶段把完整售票过程放在互斥量内部: - -```text -获得互斥量 - -> 检查是否还有票 - -> 读取当前票号 - -> 剩余票数减 1 - -> 记录票号 - -> 打印售票结果 -释放互斥量 -``` - -窗口 A 执行这组操作时,窗口 B 无法进入,因此不会读到相同票号。 - -### 5.3 运行命令与实测结果 - -```text -msh >ticket_demo -``` - -该命令先运行无锁实验,再自动运行互斥量实验。无锁阶段的实测结果如下: - -```text -ticket test start: without mutex -[no lock] window A sold ticket 20 -[no lock] window B sold ticket 20 -... -ticket result: sold=20, unique=10, duplicate=10, left=0 -``` - -![无锁售票重复票号实测结果](figure/day3_ticket_nolock.png) - -*图 2 无锁售票实验结果* - -两个窗口多次读到同一个票号,最终共执行 20 次售票,但只有 10 个唯一票号,重复票号为 10 个,说明共享数据产生了竞态。 - -加入互斥量后的实测结果如下: - -```text --------------------------------- -ticket test start: with mutex -[mutex] window A sold ticket 20 -[mutex] window B sold ticket 19 -... -ticket result: sold=20, unique=20, duplicate=0, left=0 -``` - -![互斥量保护售票实测结果](figure/day3_ticket_mutex.png) - -*图 3 使用互斥量后的售票实验结果* - -具体由哪个窗口卖出某张票由线程调度决定。实测中加锁阶段售出 20 张票,20 个票号全部唯一,重复数为 0,余票为 0,说明互斥量保护有效。 - -### 5.4 思考题:为什么只给 `ticket_count--` 加锁仍不安全? - -售票不是只有减 1 这一条语句,而是一个“先检查、再读取、再修改、再记录”的完整过程。如果只给 `ticket_count--` 加锁,可能出现下面的过程: - -```text -窗口 A 检查:还有 1 张票 -窗口 B 检查:还有 1 张票 -窗口 A 加锁并减 1 -窗口 B 加锁并再次减 1 -``` - -两个窗口在加锁前都通过了检查,因此仍可能重复售票或把票数减成负数。正确做法是把判断条件和所有相关操作放入同一个互斥区。 - ---- - -## 六、练习三:系统启动条件检查 - -### 6.1 事件位定义 - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -``` - -网络线程延时 800 ms,传感器线程延时 1400 ms,存储线程延时 5000 ms,然后各自发送对应事件。 - -业务线程第一次使用 AND 模式等待 3000 ms。此时网络和传感器已经完成,但存储仍未完成,因此第一次等待超时。业务线程读取当前已有事件并打印未就绪模块,然后再次永久等待全部事件。 - -### 6.2 关键代码 - -```c -if (rt_event_recv(ready_event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND, - rt_tick_from_millisecond(3000), - &received) != RT_EOK) -{ - /* 打印未完成模块后再次等待 */ - rt_event_recv(ready_event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND, - RT_WAITING_FOREVER, &received); -} -``` - -这里没有使用 `RT_EVENT_FLAG_CLEAR`,所以第一次超时后,已经出现的网络和传感器事件仍然保留。存储事件到达后,三个事件位就能同时满足 AND 条件。 - -### 6.3 运行命令与实测结果 - -```text -msh >startup_demo -``` - -实测输出顺序为: - -```text -startup check begin -network ready -sensor ready -wait modules timeout: storage is not ready -storage ready -all modules are ready -business task started -``` - -![事件集等待启动条件实测结果](figure/day3_startup.png) - -*图 4 系统启动条件检查实验结果* - -实测中网络和传感器先完成初始化,约 3 秒时打印存储未就绪;存储约 5 秒时就绪,随后全部事件条件满足,业务线程正常启动。 - -### 6.4 思考题:三个信号量和事件集有什么区别? - -如果使用三个信号量,业务线程需要依次对网络、传感器和存储信号量各执行一次 `rt_sem_take()`。这种方法也能实现功能,但每个信号量只能表示一个条件,需要创建和管理三个内核对象。 - -事件集只需要一个对象,三个条件分别放在三个事件位中,还可以直接使用 AND 或 OR 组合等待。它更适合“等待多个状态条件”的场景,也更方便查看当前已经完成了哪些条件。 - -需要注意,信号量带有计数含义,多次释放可以累积;事件位通常只表示某状态是否发生,不记录同一事件发生了多少次。 - ---- - -## 七、练习四:快递分拣中心 - -### 7.1 设计思路 - -两个发送线程各产生 5 个包裹: - -- 发送线程 1:编号 `1001` 到 `1005`。 -- 发送线程 2:编号 `2001` 到 `2005`。 - -每条消息包含编号、区域和重量,消息队列容量为 5。分拣线程启动后先延时 500 ms,让发送线程有机会把队列装满,以便观察发送失败和重试。 - -发送失败时不会跳过当前包裹,而是执行: - -```text -发送失败次数加 1 - -> 延时 100 ms - -> 重新发送同一条消息 - -> 发送成功后才产生下一条消息 -``` - -因此,即使队列满过,10 条有效消息最终仍会全部被处理。 - -两个发送线程完成后,协调线程发送一条 `id = 0` 的结束消息。分拣线程收到结束消息后打印统计结果并退出。 - -### 7.2 运行命令与实测结果 - -```text -msh >package_demo -``` - -实测中先出现队列满和重试信息: - -```text -package demo start -sender1 send package 1001 -... -queue full, package 2001 retry -package 1001 -> east, weight: 850 g -``` - -![消息队列满时的重试过程](figure/day3_package_retry.png) - -*图 5 消息队列满时发送线程延时重试* - -发送线程没有丢弃发送失败的包裹,而是等待后重新发送。分拣结束后的统计结果如下: - -```text -total packages: 10 -east: 3, south: 4, north: 3 -total weight: 10000 g -send failed: 19, result: PASS -``` - -![快递分拣统计实测结果](figure/day3_package_result.png) - -*图 6 快递分拣完成后的统计结果* - -本次运行中 `send failed: 19` 表示消息队列暂满时一共发生了 19 次发送失败并重试,不表示丢失了 19 个包裹。最终仍成功处理 10 个包裹,east、south、north 分别为 3、4、3 个,总重量为 10000 g,结果为 `PASS`。重试次数会受线程调度影响,不要求每次运行都固定为 19。 - ---- - -## 八、MSH 命令与资源管理 - -四个命令如下: - -```text -parking_demo -ticket_demo -startup_demo -package_demo -``` - -每个命令只创建一个协调线程后立即返回,不会长时间占用 MSH。协调线程负责创建 IPC 和工作线程,使用完成信号量等待所有工作线程结束,最后删除 IPC 对象。 - -每个实验有一个简单的 `running` 标志。实验运行期间再次输入相同命令,会提示 `demo is running`;实验结束并释放资源后,标志恢复为 `RT_FALSE`,此时可以再次执行。 - -这种写法比官方最小例程多了一步资源回收,但仍保持了比较直接的流程: - -```text -MSH 命令 - -> 创建协调线程 - -> 创建 IPC - -> 创建并启动工作线程 - -> 等待工作线程完成 - -> 删除 IPC - -> 允许再次执行命令 -``` - ---- - -## 九、编译验证与实验结论 - -### 9.1 已完成的验证 - -使用 RT-Thread Studio 的 ARM GCC 工具链执行全量编译和链接,结果为: - -```text -Build Finished. 0 errors, 0 warnings. - -Flash: 75728 B (73.95 KB) -RAM: 19280 B (18.83 KB) -``` - -![RT-Thread Studio 编译结果](figure/day3_build.png) - -*图 7 RT-Thread Studio 全量编译结果,0 errors、0 warnings* - -构建日志确认以下新增文件均进入编译: - -```text -applications/package_demo.c -applications/parking_demo.c -applications/startup_demo.c -applications/ticket_demo.c -``` - -同时,四个 `MSH_CMD_EXPORT()` 均已进入最终 ELF,说明四条命令已经被注册到 MSH 命令表。 - -### 9.2 上板验证结果 - -程序已下载到 STM32F407 开发板,并通过串口依次执行四条 MSH 命令。实测结果如下: - -1. `parking_demo`:出现车位等待超时和重试,最大同时停车数为 3,结果为 `PASS`。 -2. `ticket_demo`:无锁阶段执行 20 次售票,只得到 10 个唯一票号和 10 个重复票号;加锁阶段 20 个票号全部唯一,重复数为 0,余票为 0。 -3. `startup_demo`:约 3 秒时提示存储模块未就绪,存储就绪后全部条件满足,业务线程正常启动。 -4. `package_demo`:出现消息队列满和重试,最终处理 10 个包裹,三个区域数量为 3、4、3,总重量为 10000 g,结果为 `PASS`。 - -串口中个别行会出现 `msh >` 提示符与线程日志交叉,或者两个线程的输出拼接在同一行。这是因为 MSH 命令返回后,多个线程仍在异步运行并同时打印,不影响 IPC 功能和最终统计结果。 - -### 9.3 实验总结 - -通过四个练习,我对 RT-Thread IPC 的使用场景有了更直观的理解:信号量适合表示资源数量,互斥量适合保护共享变量,事件集适合组合多个状态条件,消息队列适合在线程间传递具体数据。 - -售票实验说明,多线程问题不能只看某一条语句是否简单,还要确定哪些操作在逻辑上必须作为一个整体执行。启动检查实验说明事件集可以方便地表达“全部完成”和“任意完成”。快递实验则展示了消息队列满时应检查返回值并进行重试,而不能默认每次发送都会成功。 - -本次作业最终完成了四个独立 MSH 命令、线程和 IPC 创建检查、实验完成等待及资源释放,并完成了 ARM GCC 编译和开发板串口运行验证。 diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/3.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/3.png" deleted file mode 100644 index ffb26c5f02137f6827e062aea5b8f2d4e5b85a01..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/3.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/4.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/4.png" deleted file mode 100644 index bce1f87f3951ea28c5c51dc186d8465f39e1b8ee..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/4.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/5.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/5.png" deleted file mode 100644 index 415c9d4c95d664e03415196b23bd4591542a2362..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/5.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/6.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/6.png" deleted file mode 100644 index 80ee4e1678dfb7d92b11303ce82c843f9a260a6e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/6.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/\345\276\256\344\277\241\345\233\276\347\211\207_20260818222924_297_98.jpg" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/\345\276\256\344\277\241\345\233\276\347\211\207_20260818222924_297_98.jpg" deleted file mode 100644 index 64d49c92c35ce7a2c34ff95ff2bfba1dc3cac333..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/\345\276\256\344\277\241\345\233\276\347\211\207_20260818222924_297_98.jpg" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/\345\276\256\344\277\241\345\233\276\347\211\207_20260818222926_299_98.jpg" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/\345\276\256\344\277\241\345\233\276\347\211\207_20260818222926_299_98.jpg" deleted file mode 100644 index 712776ecb16fd2b0420321de4993538c7c2f3b85..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/figure/\345\276\256\344\277\241\345\233\276\347\211\207_20260818222926_299_98.jpg" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index a4fca17cd3908e44adc3788039dc7b2847a19fa3..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Day2 多线程实验 - * 1. led1:动态线程,控制红灯 - * 2. led2:静态线程,控制蓝灯 - * 3. monitor:监控线程,检测按键并打印线程信息 - */ - -#include -#include -#include - -/* 硬件引脚:LED 低电平点亮,KEY0 低电平按下。 */ -#define PIN_LED_R GET_PIN(F, 12) -#define PIN_LED_B GET_PIN(F, 11) -#define PIN_KEY0 GET_PIN(C, 0) - -/* led1 和 monitor 使用动态线程句柄。 */ -static rt_thread_t th_led1; -static rt_thread_t th_monitor; - -/* led2 使用静态 TCB 和静态栈。 */ -static struct rt_thread th_led2; -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t th_led2_stack[1024]; - -/* 这是控制 led1 的二值信号量,初始值为 1。 */ -static struct rt_semaphore led1_sem; -static rt_uint8_t led1_paused; - -static const char *get_thread_state(rt_uint8_t state) -{ - switch (state & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: - return "init"; - case RT_THREAD_READY: - return "ready"; - case RT_THREAD_SUSPEND: - return "suspend"; - case RT_THREAD_RUNNING: - return "running"; - case RT_THREAD_CLOSE: - return "close"; - default: - return "unknown"; - } -} - -/* Cortex-M 栈向低地址增长,未使用区域仍保持初始化字符 '#'。 */ -static rt_uint32_t get_stack_free(rt_thread_t thread) -{ - rt_uint8_t *stack; - rt_uint32_t free_size = 0; - - stack = (rt_uint8_t *)thread->stack_addr; - while ((free_size < thread->stack_size) && (stack[free_size] == '#')) - { - free_size++; - } - - return free_size; -} - -static void print_thread_info(rt_thread_t thread) -{ - if (thread == RT_NULL) - { - return; - } - - rt_kprintf("name=%s pri=%u state=%s stack=%u free=%u\n", - thread->name, - thread->current_priority, - get_thread_state(thread->stat), - thread->stack_size, - get_stack_free(thread)); -} - -/* 动态线程:翻转红灯,打印计数和 tick。 */ -static void led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_uint8_t led_state = PIN_HIGH; - - RT_UNUSED(parameter); - rt_thread_mdelay(1000); - while (1) - { - /* 正常时取到后马上归还;暂停时 monitor会移植。 */ - if (rt_sem_take(&led1_sem, RT_WAITING_FOREVER) != RT_EOK) - { - continue; - } - rt_sem_release(&led1_sem); - - led_state = !led_state; - rt_pin_write(PIN_LED_R, led_state); - - count++; - rt_kprintf("[led1] count=%u tick=%u\n", count, rt_tick_get()); - rt_thread_mdelay(500); - } -} - -/* 静态线程:翻转蓝灯并打印计数。 */ -static void led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_uint8_t led_state = PIN_HIGH; - - RT_UNUSED(parameter); - rt_thread_mdelay(1000); - while (1) - { - led_state = !led_state; - rt_pin_write(PIN_LED_B, led_state); - - count++; - rt_kprintf("[led2] count=%u\n", count); - rt_thread_mdelay(500); - } -} - -static void control_led1(void) -{ - rt_err_t result; - - if (!led1_paused) - { - /* 取走信号量但不归还,led1 下一轮就会阻塞。 */ - result = rt_sem_take(&led1_sem, RT_WAITING_FOREVER); - if (result == RT_EOK) - { - led1_paused = 1; - rt_pin_write(PIN_LED_R, PIN_HIGH); - rt_kprintf("[monitor] suspend led1\n"); - } - return; - } - - /* 归还信号量,唤醒等待中的 led1。 */ - result = rt_sem_release(&led1_sem); - if (result == RT_EOK) - { - led1_paused = 0; - rt_kprintf("[monitor] resume led1\n"); - } - else - { - rt_kprintf("[monitor] release led1 semaphore failed: %d\n", result); - } -} - -/* 监控线程:扫描按键,并且每 2 秒打印一次线程信息。 */ -static void monitor_entry(void *parameter) -{ - rt_tick_t last_print_tick = rt_tick_get(); - - RT_UNUSED(parameter); - rt_thread_mdelay(1000); - while (1) - { - rt_tick_t now; - - if (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - /* 延时后仍为低电平,才认为按键真正按下。 */ - rt_thread_mdelay(20); - if (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - control_led1(); - - /* 等待松手,避免一次按下触发多次。 */ - while (rt_pin_read(PIN_KEY0) == PIN_LOW) - { - rt_thread_mdelay(10); - } - } - } - - now = rt_tick_get(); - if ((rt_tick_t)(now - last_print_tick) >= - rt_tick_from_millisecond(2000)) - { - last_print_tick = now; - rt_kprintf("[monitor] thread information:\n"); - print_thread_info(th_led1); - print_thread_info(&th_led2); - } - - rt_thread_mdelay(10); - } -} - -static int thread_study_init(void) -{ - rt_err_t result; - - /* 初始化 LED 和按键。 */ - rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT); - rt_pin_write(PIN_LED_R, PIN_HIGH); - rt_pin_write(PIN_LED_B, PIN_HIGH); - rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP); - - /* 信号量初始值为 1,表示 led1 默认可以运行。 */ - result = rt_sem_init(&led1_sem, "ledsem", 1, RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - rt_kprintf("init led1 semaphore failed\n"); - return result; - } - - /* 1. 动态创建 led1。 */ - th_led1 = rt_thread_create("led1", - led1_entry, - RT_NULL, - 1024, - 16, 5); - if (th_led1 == RT_NULL) - { - rt_kprintf("create led1 failed\n"); - return -RT_ERROR; - } - - /* 2. 静态初始化 led2。 */ - result = rt_thread_init(&th_led2, - "led2", - led2_entry, - RT_NULL, - th_led2_stack, - sizeof(th_led2_stack), - 16, 5); - if (result != RT_EOK) - { - rt_kprintf("init led2 failed\n"); - return result; - } - - /* 3. 动态创建 monitor。 */ - th_monitor = rt_thread_create("monitor", - monitor_entry, - RT_NULL, - 1024, - 10, 5); - if (th_monitor == RT_NULL) - { - rt_kprintf("create monitor failed\n"); - return -RT_ERROR; - } - - /* 创建完成后依次启动三个线程。 */ - if (rt_thread_startup(th_led1) != RT_EOK) - { - return -RT_ERROR; - } - if (rt_thread_startup(&th_led2) != RT_EOK) - { - return -RT_ERROR; - } - if (rt_thread_startup(th_monitor) != RT_EOK) - { - return -RT_ERROR; - } - - rt_kprintf("thread study started, key=PC0\n"); - return RT_EOK; -} -INIT_APP_EXPORT(thread_study_init); - -static long thread_dump(void) -{ - print_thread_info(th_led1); - print_thread_info(&th_led2); - return 0; -} -MSH_CMD_EXPORT(thread_dump, dump led thread information); diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 94ba1cf0d1776eefc1bfb603bd0804ac3c54deda..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,468 +0,0 @@ -# 【Day2】RT-Thread 启动流程、内核基础与多线程实验报告 - -> **实验平台:STM32F407 星火1号(stm32f407-rtspark)** -> **操作系统:RT-Thread 4.1.1** -> **实验日期:2026 年 8 月 18 日** - ---- - - - -## 目录 - -- [一、实验概述](#section-1) -- [二、STM32F407 与 RT-Thread 启动流程](#section-2) -- [三、RT-Thread 内核对象与线程模型](#section-3) -- [四、线程状态机与调度机制](#section-4) -- [五、静态线程与动态线程](#section-5) -- [六、多线程实验设计与实现](#section-6) -- [七、实验步骤、现象与结果分析](#section-7) -- [八、问题分析、方案评价与改进方向](#section-8) -- [九、个人总结](#section-9) - ---- - - - -## 一、实验概述 - -### 1.1 实验目的 - -本实验围绕 RT-Thread 的启动、对象管理、线程生命周期和调度机制展开,并在星火1号开发板上完成一个包含动态线程、静态线程、监控线程、二值信号量和 FinSH/MSH 命令的多线程应用。实验目标如下: - -1. 理解 STM32F407 从复位入口到 RT-Thread 用户 `main()` 的完整启动路径。 -2. 理解 `INIT_BOARD_EXPORT()`、`INIT_APP_EXPORT()` 的链接段注册与运行时遍历机制。 -3. 掌握 RT-Thread 线程控制块、五态模型、抢占调度和时间片轮转。 -4. 对比 `rt_thread_create()` 与 `rt_thread_init()` 的内存来源、生命周期和释放方式。 -5. 通过两个 LED 工作线程验证并发运行,通过按键和全局二值信号量控制红灯线程暂停与恢复。 -6. 使用监控输出和 `thread_dump` 命令验证线程状态、优先级及剩余栈空间。 - -### 1.2 实验环境 - -| 项目 | 实际配置 | -| --- | --- | -| 开发板 | STM32F407 星火1号(`stm32f407-rtspark`) | -| 内核版本 | RT-Thread 4.1.1 | -| 实验工程 | `code/day2_f407_spark` | -| 主要源码 | `applications/thread_study.c` | -| 用户入口 | `applications/main.c`,未手动调用实验初始化函数 | -| 系统节拍 | `RT_TICK_PER_SECOND = 1000`,即 1 tick = 1 ms | -| 红灯 | `PF12`,低电平点亮 | -| 蓝灯 | `PF11`,低电平点亮 | -| 控制按键 | `PC0`,上拉输入,低电平表示按下 | - -本实验按照题目要求,采用导师认可的**全局二值信号量门控方案**控制 `led1`:暂停时由监控线程取走信号量,使 `led1` 在下一次运行前阻塞;恢复时释放信号量,将等待中的 `led1` 唤醒。 - -[返回目录](#toc) - ---- - - - -## 二、STM32F407 与 RT-Thread 启动流程 - -### 2.1 从复位到用户 `main()` - -STM32F407 上电或复位后,处理器先从中断向量表取得初始主栈指针 MSP 和 `Reset_Handler` 地址。当前工程使用 GCC 启动文件,其实际顺序是先建立 C 语言运行所需的数据区,再配置系统时钟并进入 RT-Thread 启动入口;其他工具链的入口包装名称可能不同,但建立系统线程并启动调度器的核心过程一致。 - -```text -上电 / 复位 - -> 从向量表装载 MSP 和 Reset_Handler - -> Reset_Handler - -> 设置主栈指针 - -> 从 Flash 复制 .data 到 RAM,并清零 .bss - -> SystemInit():建立基础时钟等芯片环境 - -> entry() - -> rtthread_startup() - -> rt_hw_interrupt_disable() - -> rt_hw_board_init() - -> 时钟、SysTick、堆、串口等板级初始化 - -> rt_components_board_init()(板级自动初始化) - -> rt_show_version() - -> rt_system_timer_init() - -> rt_system_scheduler_init() - -> rt_application_init()(创建并启动 main 线程) - -> rt_system_timer_thread_init() - -> rt_thread_idle_init() - -> rt_system_scheduler_start() - -> main_thread_entry() - -> rt_components_init()(组件及应用自动初始化) - -> 用户 main() -``` - -这里最重要的认识是:RT-Thread 中的用户 `main()` 不是复位后直接执行的裸机入口,而是运行在系统创建的 main 线程上下文中。当前工程开启了系统堆,main 线程栈大小由 `RT_MAIN_THREAD_STACK_SIZE` 配置为 `2048 B`。 - -### 2.2 自动初始化的底层机制 - -RT-Thread 使用“编译期注册、链接期排序、运行时遍历”的方式组织分散在不同源码文件中的初始化函数。相关宏的核心关系可以概括为: - -```c -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") -``` - -`INIT_EXPORT(fn, level)` 会生成一个保存函数指针的静态对象,并通过编译器属性把它放入指定的初始化链接段。链接器按初始化等级组织这些函数指针,并提供段起止符号: - -| 阶段 | 等级 | 典型用途 | -| --- | ---: | --- | -| `INIT_BOARD_EXPORT` | 1 | 调度器启动前必须完成的板级硬件初始化 | -| `INIT_PREV_EXPORT` | 2 | 纯软件预初始化 | -| `INIT_DEVICE_EXPORT` | 3 | 设备驱动初始化 | -| `INIT_COMPONENT_EXPORT` | 4 | 文件系统、网络等组件初始化 | -| `INIT_ENV_EXPORT` | 5 | 系统环境准备 | -| `INIT_APP_EXPORT` | 6 | 应用模块和业务线程初始化 | - -启动时,`rt_components_board_init()` 遍历板级初始化范围;main 线程中的 `rt_components_init()` 再按顺序遍历后续等级。每次遍历取出函数指针并调用,因此它不是根据函数名搜索,也不依赖 C 语言不存在的反射能力。 - -本实验通过: - -```c -INIT_APP_EXPORT(thread_study_init); -``` - -把实验初始化函数注册到应用初始化阶段。系统进入 main 线程后会自动初始化 GPIO、信号量并创建三个实验线程,`main.c` 无需也不应再次调用 `thread_study_init()`,从而避免重复创建对象。 - -[返回目录](#toc) - ---- - - - -## 三、RT-Thread 内核对象与线程模型 - -### 3.1 对象容器 - -RT-Thread 将线程、信号量、互斥量、事件、邮箱、消息队列、定时器等资源抽象为内核对象。对象公共部分保存名称、类型、标志和链表节点;同类型对象被挂入对应的对象容器。这样可以统一完成对象注册、分离、查找和遍历,FinSH 的线程查看功能也能够从线程对象容器取得信息。 - -动态对象与静态对象使用同一套对象模型,但对象类型中带有静态/动态属性。这个属性使内核在删除对象时能够选择正确的资源处理方式,避免错误释放用户提供的静态内存。 - -### 3.2 线程控制块关键字段 - -线程控制块 `struct rt_thread` 是调度器管理线程的核心数据结构,关键成员如下: - -| 字段 | 作用 | -| --- | --- | -| `parent` | 基础内核对象,包含线程名称、类型及对象链表节点 | -| `sp` | 已保存的线程栈指针,上下文切换时用于恢复寄存器现场 | -| `entry`、`parameter` | 线程入口函数及其参数 | -| `stack_addr`、`stack_size` | 线程栈起始地址和总大小 | -| `stat` | 当前线程状态;读取基础状态时需要与 `RT_THREAD_STAT_MASK` 相与 | -| `current_priority` | 当前有效优先级,数值越小优先级越高 | -| `init_priority` | 创建线程时设置的初始优先级 | -| `init_tick` | 每轮初始时间片 | -| `remaining_tick` | 当前轮次剩余时间片 | -| `thread_timer` | 用于线程延时和 IPC 超时等待的内置定时器 | -| `cleanup` | 线程退出时执行的清理回调 | - -其中,`sp`、`stack_addr` 和 `stack_size` 共同描述线程的独立栈空间;`stat` 和优先级字段决定线程是否进入就绪队列及其调度次序;`init_tick` 与 `remaining_tick` 用于同优先级线程的时间片管理。 - -[返回目录](#toc) - ---- - - - -## 四、线程状态机与调度机制 - -### 4.1 五态模型 - -```text - rt_thread_startup() - [初始态] ------------------------> [就绪态] - | - 调度器选中 | 被抢占 / 时间片用完 - v - [运行态] - / \ - delay / IPC 等待 / suspend / \ 入口返回 / delete - v v - [挂起态] [关闭态] - | - 超时 / IPC 到达 / resume| - +------> [就绪态] -``` - -| 状态 | 宏 | 含义与典型转换 | -| --- | --- | --- | -| 初始态 | `RT_THREAD_INIT` | `rt_thread_create()` 或 `rt_thread_init()` 成功后,尚未启动 | -| 就绪态 | `RT_THREAD_READY` | `rt_thread_startup()` 后等待调度;挂起条件解除后也回到此状态 | -| 运行态 | `RT_THREAD_RUNNING` | 当前被调度器选中并占用 CPU 的线程 | -| 挂起态 | `RT_THREAD_SUSPEND` | 调用延时 API、等待 IPC 或暂停条件未解除,不参与就绪调度 | -| 关闭态 | `RT_THREAD_CLOSE` | 入口函数返回或线程被删除,已退出调度 | - -`suspend` 是内核对多种“暂时不可运行”原因的统一表示。本实验中,`led1` 处于 `suspend` 可能是因为 `rt_thread_mdelay(500)`,也可能是因为等待 `led1_sem`;`led2` 显示 `suspend` 通常是正在延时。因此,不能只凭一次状态快照判断线程是否被按键暂停,还要结合控制日志和计数是否继续增长。 - -### 4.2 抢占式优先级调度 - -RT-Thread 使用基于优先级的抢占式调度,且优先级数值越小,实际优先级越高。当一个更高优先级线程进入就绪态时,它可以抢占当前低优先级线程。 - -本实验的 `monitor` 优先级为 10,高于两个 LED 线程的 16。因此按键扫描周期到达或监控线程延时结束后,`monitor` 可以先运行,及时处理按键和状态打印。它处理完毕后调用延时 API 主动阻塞,使两个工作线程继续运行。若高优先级线程长期不阻塞,低优先级线程会被饿死,因此监控线程必须保持短任务并主动让出 CPU。 - -### 4.3 同优先级时间片轮转 - -`led1` 和 `led2` 的优先级均为 16、时间片均为 5 ticks。当两个线程同时保持就绪且没有更高优先级线程就绪时,调度器可在它们之间执行同优先级时间片轮转。当前工程 1 tick 为 1 ms,因此 5 ticks 约为 5 ms。 - -时间片不等同于 LED 翻转周期。两个 LED 线程每轮工作后都会调用 `rt_thread_mdelay(500)`,实际运行节奏主要由 500 ms 周期延时决定;时间片只在同优先级线程持续竞争 CPU 时限制一次连续运行时长。 - -[返回目录](#toc) - ---- - - - -## 五、静态线程与动态线程 - -| 对比项 | 动态线程 `rt_thread_create()` | 静态线程 `rt_thread_init()` | -| --- | --- | --- | -| TCB 与栈来源 | 由系统堆动态分配 | 由用户预先定义并传入 | -| 内存地址 | 运行时确定 | 编译、链接后即可确定 | -| 创建失败风险 | 可能因堆不足或碎片化失败 | 主要取决于静态空间和参数是否合法 | -| 生命周期 | 可按需创建和删除 | 通常与全局/静态存储期一致 | -| 退出后处理 | 动态资源进入回收流程,最终由 idle 线程释放 | 使用 `rt_thread_detach()` 分离,不释放用户提供的内存 | -| 优点 | 灵活、接口简洁、适合按需任务 | 内存可预测、无运行期堆碎片、确定性更强 | -| 典型场景 | 普通业务线程、生命周期动态的功能 | 长期运行任务、安全关键或高可靠系统 | - -本实验同时使用两种方式:`led1` 与 `monitor` 动态创建,便于展示堆分配接口;`led2` 使用全局 TCB 和 1024 B 对齐栈,展示静态初始化方式。 - -```c -static struct rt_thread th_led2; -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t th_led2_stack[1024]; - -rt_thread_init(&th_led2, "led2", led2_entry, RT_NULL, - th_led2_stack, sizeof(th_led2_stack), 16, 5); -``` - -高可靠场景倾向静态线程,是因为它可以在编译和链接阶段确定内存总量与地址,不依赖运行时堆状态,也不会产生堆碎片。但这不表示动态线程本身不可用:只要合理规划堆、检查创建返回值并管理生命周期,动态创建仍然适合大量普通应用。 - -[返回目录](#toc) - ---- - - - -## 六、多线程实验设计与实现 - -### 6.1 线程参数与职责 - -源码变量使用 `th_led1`、`th_led2`、`th_monitor` 保存线程句柄或 TCB,注册到内核对象容器中的线程名称分别为 `led1`、`led2`、`monitor`。 - -| 线程 | 创建方式 | 栈 | 优先级 | 时间片 | 主要职责 | -| --- | --- | ---: | ---: | ---: | --- | -| `led1` | 动态 `rt_thread_create()` | 1024 B | 16 | 5 ticks | 翻转 PF12 红灯,打印 `count` 和系统 tick;受二值信号量门控 | -| `led2` | 静态 `rt_thread_init()` | 1024 B | 16 | 5 ticks | 翻转 PF11 蓝灯并打印 `count` | -| `monitor` | 动态 `rt_thread_create()` | 1024 B | 10 | 5 ticks | 扫描 PC0 按键、控制 `led1`,每 2 s 打印线程状态和栈余量 | - -三个线程创建或初始化成功后均通过 `rt_thread_startup()` 加入调度。返回值均被检查,避免创建失败后继续使用无效句柄。 - -### 6.2 LED 工作线程 - -`led1` 和 `led2` 启动后先延时 1000 ms,再进入无限循环。每次循环翻转对应 GPIO、递增计数并延时 500 ms。`led1` 额外读取 `rt_tick_get()`,因此可以从串口日志验证其周期: - -```c -count++; -rt_kprintf("[led1] count=%u tick=%u\n", count, rt_tick_get()); -rt_thread_mdelay(500); -``` - -在正常运行时,相邻 `led1` 日志的 tick 差值约为 500,和设定的 500 ms 延时一致。由于串口输出和调度存在少量开销,实测差值可能为 502 或 503 ticks,这是正常现象。 - -### 6.3 二值信号量门控 - -全局静态信号量 `led1_sem` 的初值为 1,表示 `led1` 默认允许运行: - -```c -rt_sem_init(&led1_sem, "ledsem", 1, RT_IPC_FLAG_FIFO); -``` - -正常情况下,`led1` 在每轮开始时取到信号量后立即归还,再执行 LED 翻转。第一次有效按键触发后,`monitor` 取走信号量但不归还,并把红灯置为熄灭电平;`led1` 到下一轮执行 `rt_sem_take(..., RT_WAITING_FOREVER)` 时进入信号量等待。第二次有效按键触发后,`monitor` 调用 `rt_sem_release()`,等待中的 `led1` 被唤醒并继续计数。 - -```text -正常:led1 取信号量 -> 立即归还 -> 翻转红灯 -> 延时 -暂停:monitor 取走信号量 -> led1 下一轮取不到 -> 阻塞等待 -恢复:monitor 释放信号量 -> led1 被唤醒 -> 恢复翻转和计数 -``` - -这种方案使线程只在自己定义好的同步点停下,不会在持有未知资源或执行 GPIO/打印操作的中间位置被强制冻结,状态转换和资源关系更容易分析。 - -### 6.4 监控线程的双周期设计 - -监控线程没有直接使用一次 2000 ms 的长延时,因为那样可能漏掉持续时间较短的按键。实际实现把两个时间尺度分离: - -- 主循环每 10 ms 扫描一次按键。 -- 检测到低电平后延时 20 ms 再确认,实现基本消抖。 -- 确认按下后等待按键释放,防止一次按下重复触发。 -- 使用 `now - last_print_tick` 独立判断是否达到 2000 ms,仅到期时打印线程信息。 - -因此,按键响应周期约为 10 ms,而状态输出仍保持约 2 s 一次。该设计同时满足交互响应和周期监控要求。 - -### 6.5 状态、栈余量与 `thread_dump` - -线程状态读取时使用: - -```c -thread->stat & RT_THREAD_STAT_MASK -``` - -以屏蔽状态字段中的附加标志。栈余量统计从 `stack_addr` 开始扫描 RT-Thread 初始化栈时填入的 `'#'` 字节,连续未被覆盖的字节数即为历史上尚未使用的栈空间。它反映的是栈使用高水位,而不是某一瞬间局部变量退出后“恢复”的空间。 - -```c -while ((free_size < thread->stack_size) && - (stack[free_size] == '#')) -{ - free_size++; -} -``` - -实验还通过 `MSH_CMD_EXPORT()` 导出自定义命令: - -```c -MSH_CMD_EXPORT(thread_dump, dump led thread information); -``` - -在 FinSH/MSH 中输入 `thread_dump`,即可手动查看两个 LED 线程的对象名称、当前优先级、状态、栈总量和剩余栈空间。监控线程也会每 2 s 自动打印同样的信息。 - -[返回目录](#toc) - ---- - - - -## 七、实验步骤、现象与结果分析 - -### 7.1 自动启动与周期运行 - -**实验步骤:** 编译并下载工程,复位开发板,打开串口终端观察 RT-Thread 启动横幅、模块初始化信息、两个 LED 线程的计数以及监控线程输出。 - -图1 三个实验线程自动启动并周期运行 - -*图 1:RT-Thread 4.1.1 启动后,`thread study started, key=PC0` 自动出现,两个 LED 线程持续计数,监控线程周期打印状态。* - -**现象分析:** - -- 日志在用户没有从 `main.c` 手动调用初始化函数的情况下自动出现,说明 `INIT_APP_EXPORT(thread_study_init)` 已生效。 -- `led1` 的 tick 依次为 6、509、1011、1513,差值分别约为 503、502、502 ticks,与 500 ms 延时及少量执行开销相符。 -- `led1` 和 `led2` 的计数同步增长,证明动态线程和静态线程均已成功进入调度。 -- 此次状态快照显示 `led1` 为 `suspend`、`led2` 为 `ready`。前者此时处于周期延时并不代表按键暂停,后者则刚好已就绪等待运行。 -- 图中栈余量为 `led1: 780/1024 B`、`led2: 788/1024 B`,对应历史最大使用量约为 244 B 和 236 B,尚有较大余量。 - -### 7.2 自动初始化与命令导出验证 - -**实验步骤:** 系统启动后,在 FinSH/MSH 提示符输入 `thread_dump`,同时核对源码中的 `INIT_APP_EXPORT()` 和 `MSH_CMD_EXPORT()` 注册位置。 - -图2 INIT_APP_EXPORT、MSH_CMD_EXPORT 与 thread_dump 验证 - -*图 2:源码注册宏与串口命令结果相互对应,`thread_dump` 能够输出两个 LED 线程的信息。* - -**现象分析:** - -- `thread_dump` 被 shell 正确识别,说明 `MSH_CMD_EXPORT(thread_dump, ...)` 已将命令注册到 FinSH 符号表。 -- 命令显示两个线程优先级均为 16、栈均为 1024 B,与源码参数完全一致。 -- 本次重新运行得到的栈余量为 `led1: 852 B`、`led2: 848 B`。它与图 1 的数值不同,说明不同构建或不同运行路径会改变历史最大栈深度;栈评估应以压力场景下观测到的最小余量为准。 -- 自动监控输出与手动 `thread_dump` 均可读取线程信息,分别适合持续观察和按需诊断。 - -### 7.3 按键暂停 `led1` - -**实验步骤:** 保持系统运行,第一次有效按下 PC0 按键。观察 `[monitor] suspend led1` 后两个线程的计数变化和状态输出。 - -图3 按键触发后 led1 停止而 led2 继续 - -*图 3:第一次按键后,`led1` 停止在 count=9,`led2` 从 count=9 持续增长到 count=17。* - -**现象分析:** - -- `[monitor] suspend led1` 表明监控线程已取走全局二值信号量。 -- 在暂停区间内不再出现新的 `led1` 计数,而 `led2` 继续从 9 增长至 17,证明控制只作用于红灯线程,没有阻塞调度器或其他线程。 -- 监控快照中 `led1` 保持 `suspend`,此时结合计数停止和控制日志,可以判断它正在等待信号量。 -- `led2` 的状态也可能被采样为 `suspend`,原因是它当时正在执行 `rt_thread_mdelay(500)`;其计数持续增长证明它并未被按键暂停。 - -### 7.4 按键恢复 `led1` - -**实验步骤:** 第二次有效按下 PC0 按键,观察 `[monitor] resume led1` 以及后续红灯线程计数和 tick。 - -图4 释放信号量后 led1 恢复运行 - -*图 4:监控线程释放信号量后,`led1` 从 count=10 继续增长,红灯线程恢复周期运行。* - -**现象分析:** - -- `[monitor] resume led1` 表明 `rt_sem_release()` 执行成功,等待信号量的 `led1` 被唤醒。 -- 恢复后 `led1` 的 count 从 10 连续增长至 15,tick 也继续递增,证明线程重新参与调度。 -- `led2` 在整个控制过程中始终继续计数,验证了线程间控制范围的隔离性。 -- 恢复后的监控快照仍可能看到 `led1 state=suspend`,这是因为线程恢复执行后又进入正常的 500 ms 延时。判断恢复是否成功应看 `resume` 日志和后续计数,而不能只看单次状态值。 - -### 7.5 开发板 LED 运行现象 - -**实验步骤:** 在串口计数持续输出期间观察开发板,记录 PF12 红灯与 PF11 蓝灯的实际点亮状态。由于两个线程独立周期翻转,静态照片只能记录某一时刻,动态运行结论需与连续串口日志共同判断。 - -图5 星火1号开发板实物及红蓝 LED 运行状态 - -*图 5:星火1号开发板实物,红、蓝 LED 在两个工作线程控制下运行。* - -图6 另一运行时刻的红蓝 LED 状态 - -*图 6:另一拍摄时刻的开发板状态,用于核对实际硬件连接和 LED 输出。* - -**现象分析:** 两张照片确认实验固件运行在星火1号实物上,板载红、蓝 LED 均有点亮记录。结合串口中两个线程持续增长的计数,可判定 PF12 与 PF11 均被周期驱动。单张静态照片不能独立证明“闪烁”,因此本报告不把照片亮灭瞬间作为唯一证据,而是采用“硬件照片 + 连续计数 + tick 周期”的交叉验证方式。 - -### 7.6 实验结果汇总 - -| 验证项目 | 证据 | 结论 | -| --- | --- | --- | -| RT-Thread 启动 | 4.1.1 启动横幅与 `msh >` | 系统和 FinSH 正常运行 | -| 自动初始化 | 启动时自动打印 `thread study started` | `INIT_APP_EXPORT()` 生效 | -| 动态线程 | `led1` 计数与 tick 持续增长 | 动态创建并调度成功 | -| 静态线程 | `led2` 计数持续增长 | 静态初始化并调度成功 | -| 调度参数 | `thread_dump` 显示两个 LED 线程优先级 16、栈 1024 B | 与源码一致 | -| 周期监控 | 每约 2 s 输出线程信息 | 独立打印周期正常 | -| 按键暂停 | `suspend led1` 后仅 `led2` 继续计数 | 二值信号量门控成功 | -| 按键恢复 | `resume led1` 后 `led1` 重新计数 | 信号量释放和唤醒成功 | -| 栈余量 | 最低观测值为 780 B 与 788 B | 当前实验路径下余量充足 | - -[返回目录](#toc) - ---- - - - -## 八、问题分析、方案评价与改进方向 - -### 8.1 容易误判的问题 - -1. **看到 `suspend` 就认为按键已暂停线程。** `rt_thread_mdelay()` 和等待信号量都会使线程处于挂起态,必须结合控制日志、计数变化和代码等待点判断原因。 -2. **把 5 ticks 时间片当成 500 ms LED 周期。** 5 ticks 约为 5 ms,只控制同优先级线程持续竞争 CPU 时的轮转;LED 周期来自显式的 500 ms 延时。 -3. **只用静态照片证明闪烁。** 照片只能记录一个瞬间,必须与串口的连续计数和 tick 变化共同验证。 -4. **把栈余量当成瞬时空闲栈。** `'#'` 扫描得到的是历史高水位统计。评价栈大小时应在最深调用和最大日志压力下取最小余量,并预留中断嵌套和版本变化的安全空间。 - -### 8.2 当前方案的优点 - -- 二值信号量使 `led1` 在明确同步点自主阻塞,线程控制边界清晰。 -- `monitor` 优先级较高且每 10 ms 扫描按键,响应及时;其每轮均主动延时,不会长期占用 CPU。 -- 10 ms 扫描与 2000 ms 打印解耦,避免为周期监控牺牲短按检测能力。 -- 动态与静态线程同时使用,能够直观比较两种内存管理方式。 -- 自动监控和手动 `thread_dump` 互补,方便持续观察与现场调试。 -- 创建、初始化和启动操作均检查返回值,提高了失败场景下的可诊断性。 - -### 8.3 局限性与改进方向 - -- 当前按键采用轮询和阻塞等待松手。若按键被长按,监控线程会停留在等待松手循环,2 s 状态打印可能推迟。后续可以用 GPIO 中断只发送事件,再由线程完成消抖和控制。 -- `led1_paused` 表示控制意图,但 `thread_dump` 当前只输出内核状态,不能直接区分“周期延时”和“等待信号量”。可以增加 `control=running/paused` 字段,提高诊断可读性。 -- 当前 `thread_dump` 只打印两个 LED 工作线程。若需要完整观察实验对象,可以加入 `monitor` 本身的信息,但打印自己时其状态通常是 `running`,需要在报告中说明采样语义。 -- 栈扫描直接遍历 1024 B,适合本实验低频诊断,不适合在高频实时路径中调用。工程化实现可复用内核已有统计命令,并限制监控频率。 -- 暂停请求要等 `led1` 运行到下一轮信号量检查点才完全生效,最坏响应时间接近一个 LED 周期。若应用需要更短停止延迟,可使用事件驱动等待或缩短工作分段。 - -[返回目录](#toc) - ---- - - - -## 九、个人总结 - -通过本次实验,我把 RT-Thread 的启动流程、自动初始化、对象模型、线程状态和实际调度现象连接了起来。以前容易把 `main()` 当作系统的最初入口,现在明确了它实际由 main 线程调用;`INIT_APP_EXPORT()` 也不是简单的语法包装,而是链接段注册与运行时遍历机制的应用。 - -在线程实验中,动态创建的 `led1`、静态初始化的 `led2` 和高优先级 `monitor` 各自承担单一职责。串口计数、tick、状态、栈余量和开发板 LED 现象形成了相互印证的证据链。按要求采用全局二值信号量后,我进一步理解了线程“暂停”在内核中本质上可以表现为等待某个条件,而 `suspend` 状态本身并不能说明具体等待原因。 - -本实验最终完成了三个线程的创建与启动、红蓝 LED 周期控制、按键暂停与恢复、自动初始化、栈余量统计及 `thread_dump` 命令导出,配置和运行结果均与题目要求一致。 - -[返回目录](#toc) diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/1.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/1.png" deleted file mode 100644 index 8800dafbed5d37a9c6aa7e3dc53efe9cebe45ef7..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/1.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/2.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/2.png" deleted file mode 100644 index b2a73df38dc0bd8687794bc6322d779d47cc800a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/2.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260430093853203.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260430093853203.png" deleted file mode 100644 index f426cf1e142b72dc6b659f79c00026d066572e00..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260430093853203.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817175326676.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817175326676.png" deleted file mode 100644 index ea4bd47ce451b8a6017ad2af32d4fcb52641c9e8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817175326676.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817180226751.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817180226751.png" deleted file mode 100644 index 3ebdf94b6109b90c6c65b459a98a819914e6d3b1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817180226751.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817181333180.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817181333180.png" deleted file mode 100644 index a97df6f38a8ef9c5dd9b336a4e169ca3fb40aa6e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817181333180.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817181532071.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817181532071.png" deleted file mode 100644 index 67d3986939b1debef1dced3fe21ef18f2dfa753c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/image-20260817181532071.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786932255112_996a0b40798495454e7e1d2ee12945f8.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786932255112_996a0b40798495454e7e1d2ee12945f8.png" deleted file mode 100644 index 5b0b7d710422c74b7b1e1227dc0e4d8a811e405f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786932255112_996a0b40798495454e7e1d2ee12945f8.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786932291307_12ca202f81170bccce872d78e092f864.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786932291307_12ca202f81170bccce872d78e092f864.png" deleted file mode 100644 index 6bb836073d2d6ea4a1891a41b91ef58775a4c1bc..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786932291307_12ca202f81170bccce872d78e092f864.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786933044213_3d5888c1e591bff9a3f560e9de1dcbfa.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786933044213_3d5888c1e591bff9a3f560e9de1dcbfa.png" deleted file mode 100644 index 624ad7d1360f2b4eab8979d6a11adef939333d24..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786933044213_3d5888c1e591bff9a3f560e9de1dcbfa.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786933472788_1979c7f5c59d9c61d83fa5325e6beb2e.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786933472788_1979c7f5c59d9c61d83fa5325e6beb2e.png" deleted file mode 100644 index f04417544e334b532a2b1bdd0f9e927515cd8def..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786933472788_1979c7f5c59d9c61d83fa5325e6beb2e.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786933887594_3fd0bed0e113849e9f46d367b753f1b6.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786933887594_3fd0bed0e113849e9f46d367b753f1b6.png" deleted file mode 100644 index 51d3b69a432daeed6b83a270b8fd457f0de706be..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786933887594_3fd0bed0e113849e9f46d367b753f1b6.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786935038659_12c76b9269c8d39995be23c1f10c0a4a.png" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786935038659_12c76b9269c8d39995be23c1f10c0a4a.png" deleted file mode 100644 index a3dc9d2d53367b061f5fbff1bb1acf9a0fb11476..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/figures/original_1786935038659_12c76b9269c8d39995be23c1f10c0a4a.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 53d04f25a9ed010e3020a76c1db04e086b9cd310..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,334 +0,0 @@ -# RT-Thread 学习笔记:第一天 - -| 项目 | 内容 | -| --- | --- | -| 学习主题 | RT-Thread 工程创建、基础调试、Markdown 与 Git 入门 | -| RT-Thread 版本 | 4.1.1 | - -> 本文记录第一天的实操过程、观察到的现象及问题处理方法。文中的命令和操作步骤均以本次学习环境为例,实际使用时应结合自己的工程路径、芯片型号和仓库地址调整。 - -## 目录 - -- [一、当日学习目标](#一当日学习目标) -- [二、任务 1:创建并运行 QEMU A9 工程](#二任务-1创建并运行-qemu-a9-工程) -- [三、任务 2:Markdown 基础语法](#三任务-2markdown-基础语法) -- [四、任务 3:创建 STM32F407ZG 工程并调试](#四任务-3创建-stm32f407zg-工程并调试) -- [五、任务 4:Git 与 Gitee 基本使用](#五任务-4git-与-gitee-基本使用) -- [六、当日总结](#六当日总结) - -## 一、当日学习目标 - -1. 熟悉 RT-Thread Studio 的工程编辑、编译、下载和调试入口。 -2. 创建 QEMU A9 示例工程,了解 RT-Thread 工程的基本构成与运行流程。 -3. 基于 RT-Thread 4.1.1 创建 STM32F407ZG 工程,观察系统启动信息并完成一次基础调试。 -4. 掌握 Markdown 常用排版语法,以及 Git/Gitee 的日常提交与同步流程。 - -## 二、任务 1:创建并运行 QEMU A9 工程 - -### 1. 学习内容 - -QEMU A9 是用于模拟 ARM Cortex-A9 平台的软件仿真环境,不需要连接实体开发板。本任务的目的,是熟悉 RT-Thread Studio 中的工程创建、编辑、编译与运行流程。 - -在同一天的硬件工程练习中,还学习了 ST-LINK 驱动识别、固件升级入口以及编译、下载和调试按钮的位置。这部分操作适用于后续的 STM32 实体开发板调试,不与 QEMU 仿真工程混用。 - -### 2. 操作过程 - -1. 完成 RT-Thread Studio 及相关工具的安装与基础配置。 -2. 在 RT-Thread Studio 中新建 QEMU A9 工程。 -3. 使用工具栏的编辑、编译与运行功能对工程进行操作。 -4. 若工程配置未正确同步,右键单击工程并执行“同步 SCons 配置至项目”,再重新编译。 - -![开发环境与调试工具入口](figures/original_1786933472788_1979c7f5c59d9c61d83fa5325e6beb2e.png) - -*图 1 开发环境与调试相关工具入口* - -![工程编辑与编译入口](figures/original_1786933044213_3d5888c1e591bff9a3f560e9de1dcbfa.png) - -*图 2 工程编辑、编译及 SCons 配置同步位置* - -### 3. 结果与记录 - -已完成可编译工程的导入,工程结构如下图所示。后续使用新建工程时,应优先确认 BSP、工具链和 SCons 配置是否匹配;编译异常时再检查工程同步状态,而不是直接修改源码。 - -![导入后的可编译工程](figures/image-20260817180226751.png) - -*图 3 已导入的可编译工程* - -## 三、任务 2:Markdown 基础语法 - -### 1. 学习目的 - -Markdown 是一种轻量级标记语言,适合编写实验记录、项目说明和 README 文档。正式文档应使用合法授权的软件或开源工具进行编辑与预览。 - -### 2. 常用语法示例 - -#### 标题 - -```markdown -# 一级标题 -## 二级标题 -### 三级标题 -``` - -标题应按层级递进,避免为了调整字体大小而跳级使用标题。 - -#### 代码块 - -````markdown -```c -#include - -int main(void) -{ - return 0; -} -``` -```` - -渲染效果: - -```c -#include - -int main(void) -{ - return 0; -} -``` - -#### 表格与强调 - -```markdown -| 项目 | 内容 | -| --- | --- | -| 示例 | 表格内容 | - -`行内代码` -**加粗文字** -``` - -| 项目 | 内容 | -| --- | --- | -| 示例 | 表格内容 | - -`行内代码`用于标注命令、文件名或变量,**加粗文字**用于强调关键结论。 - -### 3. 学习记录 - -已练习标题、代码块、表格、行内代码、加粗和图片插入等基础语法。后续编写笔记时,应保持标题层级、图片说明和代码语言标识的一致性。 - -![Markdown 编辑与预览示例 1](figures/original_1786932255112_996a0b40798495454e7e1d2ee12945f8.png) - -*图 4 Markdown 编辑与预览示例* - -![Markdown 编辑与预览示例 2](figures/original_1786932291307_12ca202f81170bccce872d78e092f864.png) - -*图 5 Markdown 常用语法练习* - -## 四、任务 3:创建 STM32F407ZG 工程并调试 - -### 1. 工程创建 - -本任务使用 RT-Thread 4.1.1 创建基于 STM32F407ZG 的工程。创建前需根据开发板实际芯片丝印或原理图确认 MCU 型号,并选择匹配的 BSP。RT-Thread 版本应与课程要求及 BSP 支持情况保持一致。 - -![芯片型号选择界面](figures/original_1786933887594_3fd0bed0e113849e9f46d367b753f1b6.png) - -*图 6 根据实际芯片型号选择工程模板* - -![STM32F407ZG 工程结构](figures/image-20260817175326676.png) - -*图 7 创建完成后的 STM32F407ZG 工程* - -### 2. RT-Thread 启动与线程调度理解 - -RT-Thread 系统启动过程中会完成硬件初始化、内核对象初始化和组件初始化等工作,随后启动调度器。线程创建后不会立即执行: - -- 使用 `rt_thread_create()` 动态创建线程,或使用 `rt_thread_init()` 初始化静态线程; -- 调用 `rt_thread_startup()` 后,线程进入就绪状态; -- 调用 `rt_system_scheduler_start()` 后,调度器开始根据优先级和时间片调度就绪线程。 - -系统启动时的版本横幅由 `rt_show_version()` 输出。串口出现如下信息,说明程序已运行至 RT-Thread 的系统启动阶段: - -```text -\ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 17 2026 11:02:01 - 2006 - 2022 Copyright by RT-Thread team -``` - -![RT-Thread 启动版本信息](figures/image-20260430093853203.png) - -*图 8 串口输出的 RT-Thread 版本信息* - -### 3. 程序验证 - -本次在 `main()` 中加入串口打印和调试日志,验证主线程能够周期运行: - -```c -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -int main(void) -{ - int count = 1; - - rt_kprintf("hello RSOC2026!\n"); - while (count++) - { - LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - - return RT_EOK; -} -``` - -其中,`rt_thread_mdelay(1000)` 使当前线程延时约 1 秒,便于在串口中观察周期性日志输出。 - -### 4. 调试问题与处理 - -#### 问题 1:ST-LINK 固件版本过低 - -**现象:** 单击调试按钮后,IDE 提示检测到 ST-LINK 设备固件版本过低,无法正常进入调试。 - -![ST-LINK 固件版本提示](figures/original_1786935038659_12c76b9269c8d39995be23c1f10c0a4a.png) - -*图 9 ST-LINK 固件版本过低提示* - -**处理过程:** 使用 ST 官方固件升级工具更新 ST-LINK 固件,完成后重新连接调试器并再次启动调试。 - -![ST-LINK 升级工具](figures/image-20260817181333180.png) - -*图 10 ST-LINK 固件升级工具* - -![升级配置示例](figures/image-20260817181532071.png) - -*图 11 固件升级配置示例* - -**结果:** 完成升级后,能够进入调试会话。后续遇到调试器无法识别或无法启动调试时,应依次检查 USB 连接、驱动、ST-LINK 固件版本和调试接口配置。 - -#### 问题 2:结束调试后串口出现 HardFault - -**现象:** 一次调试结束后,串口输出寄存器信息,并提示 `hard fault on thread: main`。部分关键信息如下: - -```text -hard fault on thread: main -pc: 0x0800b356 -lr: 0x08000c01 -``` - -**初步处理:** 对开发板进行断电后重新上电(冷启动),随后系统恢复正常启动。 - -**分析与结论:** 冷启动后恢复正常只能说明故障具有偶发性,不能据此直接排除代码、栈空间或硬件问题。若问题再次出现,应按以下顺序定位: - -1. 根据 `pc`、`lr` 地址结合 `.map` 文件或调试器调用栈定位故障代码位置; -2. 检查 `main` 线程栈余量、局部大数组、空指针和数组越界等常见原因; -3. 核对下载后复位方式、调试器连接状态和供电稳定性; -4. 保留故障串口日志,确认故障是否可稳定复现。 - -## 五、任务 4:Git 与 Gitee 基本使用 - -### 1. Git 基本概念 - -Git 的日常操作围绕四个位置进行: - -| 位置 | 作用 | 典型操作 | -| --- | --- | --- | -| 工作区 | 正在编辑的项目文件 | 编辑、`git status` | -| 暂存区 | 准备写入下一次提交的改动 | `git add` | -| 本地仓库 | 保存在本机的提交历史 | `git commit` | -| 远端仓库 | Gitee/GitHub 上的仓库 | `git pull`、`git push` | - -```text -工作区 -- git add --> 暂存区 -- git commit --> 本地仓库 -- git push --> 远端仓库 -工作区 <-- git pull ----------------------------------------------- 远端仓库 -``` - -### 2. 首次使用配置 - -首次使用时,需要配置提交者信息: - -```bash -git config --global user.name "your_name" -git config --global user.email "your_email@example.com" -git config --global --list -``` - -建议使用 SSH 密钥连接远端仓库。公钥可以添加到 Gitee/GitHub 的 SSH 公钥设置中,私钥文件(如 `id_rsa`)不得提交或公开。 - -```bash -ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -cat ~/.ssh/id_rsa.pub -ssh -T git@gitee.com -``` - -### 3. 上传本地工程 - -远端为空仓库时,可在项目根目录执行: - -```bash -git init -git branch -M master -git add . -git commit -m "feat: 初始化项目" -git remote add origin git@gitee.com:用户名/仓库名.git -git push -u origin master -``` - -若远端已有工程,优先克隆后再进行修改: - -```bash -git clone git@gitee.com:用户名/仓库名.git -cd 仓库名 -``` - -> 远端主分支若为 `main`,应将以上命令中的 `master` 替换为 `main`。可通过 `git branch --show-current` 确认当前分支。 - -### 4. 日常提交流程 - -开始工作前先同步远端,修改完成后检查改动再提交: - -```bash -git status -git pull origin master - -# 修改文件后 -git add <文件或目录> -git diff --cached -git commit -m "docs: 完善第一天学习笔记" -git push origin master -``` - -常用提交类型如下: - -| 前缀 | 适用内容 | -| --- | --- | -| `feat:` | 新增功能 | -| `fix:` | 修复问题 | -| `docs:` | 文档或笔记更新 | -| `refactor:` | 不改变功能的代码整理 | - -### 5. 常见问题 - -| 现象 | 原因与处理 | -| --- | --- | -| `fatal: not a git repository` | 当前目录不是 Git 仓库;先进入项目根目录,或对新项目执行 `git init`。 | -| `remote origin already exists` | 已绑定远端地址;用 `git remote -v` 查看,再用 `git remote set-url origin ` 修改。 | -| `git push` 被拒绝 | 远端存在本地没有的提交;先 `git pull`,处理冲突后再推送。 | -| 出现 `CONFLICT` | 两处修改了同一位置;手动保留正确内容,删除冲突标记后执行 `git add` 和 `git commit`。 | - -处理冲突时,文件中的 `<<<<<<<`、`=======`、`>>>>>>>` 必须全部清除。未明确理解影响前,不使用 `git push -f`,以避免覆盖远端他人的提交。 - -## 六、当日总结 - -今天完成了 RT-Thread Studio 基础工程操作、QEMU A9 工程导入、STM32F407ZG 工程创建、ST-LINK 固件升级、系统启动信息观察,以及 Markdown 和 Git 的基础学习。 - -后续学习中将重点加强以下内容: - -1. 结合启动流程继续阅读 RT-Thread 的线程创建、启动和调度相关源码; -2. 学习使用调用栈和 `.map` 文件定位 HardFault,而不是仅依靠重启恢复; -3. 坚持使用 Git 管理每次实验代码和笔记,提交前检查实际改动内容; -4. 按“目标、过程、现象、分析、结论”的结构持续完善学习记录。 \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index d7f39e226b2395b32c79b19436af2d1d05539c5d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,246 +0,0 @@ -# RT-Thread 夏令营第三天笔记 - -> 学习主题:RT-Thread 线程间同步与通信(IPC) -> 学习日期:2026 年 8 月 19 日 - -## 目录 - -1. [IPC 概述](#1-ipc-概述) -2. [基础概念](#2-基础概念) -3. [信号量](#3-信号量) -4. [互斥量](#4-互斥量) -5. [事件集](#5-事件集) -6. [消息邮箱](#6-消息邮箱) -7. [消息队列](#7-消息队列) -8. [IPC 选型与对比](#8-ipc-选型与对比) -9. [实验现象与结论](#9-实验现象与结论) -10. [易错点与学习收获](#10-易错点与学习收获) - ---- - -## 1. IPC 概述 - -IPC(Inter-Process Communication)在 RT-Thread 中主要用于解决多线程之间的协作问题。本次课程将 IPC 分为两类: - -- **线程同步**:使多个线程按照预期的条件或顺序执行,主要包括信号量、互斥量和事件集。 -- **线程通信**:在线程之间传递数据,主要包括消息邮箱和消息队列。 - -同步解决的是“何时执行”和“谁可以访问”,通信解决的是“线程之间传递什么”。 - -## 2. 基础概念 - -### 2.1 临界区与竞态条件 - -**临界区**是访问共享资源的代码区域。当多个线程可能同时修改同一资源时,如果缺少互斥保护,执行结果将取决于不可预测的调度时序,这种问题称为**竞态条件**。 - -临界区应尽可能短,其内应避免长时间延时或阻塞操作,以减少其他线程的等待时间。 - -### 2.2 阻塞、非阻塞与挂起 - -- **阻塞调用**:资源或条件未满足时,线程暂时无法继续执行。 -- **非阻塞调用**:资源不可用时立即返回,线程可处理其他任务。 -- **挂起状态**:线程在等待 IPC 对象或延时期间不参与 CPU 调度;条件满足后回到就绪态,等待调度器选中。 - -阻塞是调用的行为特征,挂起是线程在等待期间的状态,二者不是同一概念。 - -### 2.3 超时参数 - -IPC 获取或接收接口通常需要指定等待时间: - -- `RT_WAITING_FOREVER`:一直等待,直到条件满足。 -- `RT_WAITING_NO`:不等待,无法立即获取时直接返回。 -- 正整数:最多等待指定的系统节拍数。 - -工程中应根据时延上限选择超时值,并对超时、资源已满等返回状态进行处理。 - -## 3. 信号量 - -### 3.1 原理与用途 - -信号量是一个可由线程或中断释放、由线程获取的计数型内核对象。获取成功时计数值减 1,释放时计数值加 1并可能唤醒等待线程。 - -| 形式 | 典型初值 | 主要用途 | -|---|---:|---| -| 二值信号量 | 0 或 1 | 任务与任务、中断与任务之间的单次事件通知 | -| 计数信号量 | 资源数量 N | 管理同类共享资源的多个实例 | - -二值信号量初值为 0 时,表示事件尚未发生;等待线程挂起,另一线程或中断在同步点释放信号量后,等待线程被唤醒。计数信号量的初值 N 表示可用资源数,当计数值为 0 时,后续申请者需要等待资源被释放。 - -### 3.2 常用 API - -| API | 作用 | -|---|---| -| `rt_sem_create()` | 动态创建信号量 | -| `rt_sem_init()` | 静态初始化信号量 | -| `rt_sem_take()` | 获取信号量(P 操作) | -| `rt_sem_release()` | 释放信号量(V 操作) | - -创建时的 `flag` 决定有多个等待者时的唤醒顺序: - -- `RT_IPC_FLAG_PRIO`:按线程优先级等待,更适合对实时性有要求的场景。 -- `RT_IPC_FLAG_FIFO`:按进入等待队列的先后顺序唤醒。 - -### 3.3 使用要点 - -- 信号量没有所有权,释放者不一定是获取者,因此适合事件通知和资源计数。 -- 中断服务程序可释放信号量通知线程,但不能在中断中执行可能阻塞的获取操作。 -- 若需要保护共享临界区,应优先使用具有所有权和优先级继承能力的互斥量。 - -## 4. 互斥量 - -### 4.1 核心特性 - -互斥量专门用于保护临界区,保证同一时刻只有一个线程访问共享资源。它具有以下特性: - -- **所有权**:只有持有互斥量的线程才能释放它。 -- **递归获取**:同一线程可多次获取同一互斥量,但获取和释放次数必须配对。 -- **优先级继承**:用于缓解优先级反转问题。 - -互斥量只适用于线程上下文,不应在中断服务程序中获取或释放。 - -### 4.2 优先级反转 - -假设线程优先级为 A > B > C: - -1. 低优先级线程 C 先获取互斥量并进入临界区。 -2. 高优先级线程 A 抢占 C,但因无法获取互斥量而挂起。 -3. 中优先级线程 B 就绪后抢占 C,导致 C 迟迟无法执行并释放互斥量。 -4. 结果是高优先级线程 A 间接地被 B 阻塞,出现优先级反转。 - -**优先级继承的处理方式**:当 A 等待 C 持有的互斥量时,系统临时将 C 的优先级提升至 A 的优先级,使 C 尽快完成临界区并释放互斥量。释放后,C 恢复原优先级,A 获取互斥量继续执行。 - -### 4.3 常用 API - -| API | 作用 | -|---|---| -| `rt_mutex_create()` | 动态创建互斥量 | -| `rt_mutex_init()` | 静态初始化互斥量 | -| `rt_mutex_take()` | 获取互斥量 | -| `rt_mutex_release()` | 释放互斥量 | - -使用时要保证每条退出路径都能释放互斥量,否则容易造成死锁。 - -## 5. 事件集 - -### 5.1 原理与特点 - -事件集使用 32 位无符号整数表示事件标志,每一位对应一类事件,适合一对多或多对多的条件同步。 - -- `RT_EVENT_FLAG_OR`:监听的任意一个事件发生即满足条件。 -- `RT_EVENT_FLAG_AND`:监听的所有事件都发生才满足条件。 -- `RT_EVENT_FLAG_CLEAR`:接收成功后自动清除本次满足条件的事件标志。 - -事件集只表示“某类事件是否已发生”,**不携带具体数据,也不记录同一事件连续发生的次数**。同一标志位在未被清除前多次发送,仍只表现为该位置 1。 - -### 5.2 常用 API - -| API | 作用 | -|---|---| -| `rt_event_create()` / `rt_event_init()` | 创建或初始化事件集 | -| `rt_event_send()` | 发送事件标志 | -| `rt_event_recv()` | 按 AND/OR 条件接收事件 | - -如果业务既需要事件通知又需要传递参数,应结合消息队列,或直接使用消息队列对事件和数据进行封装。 - -## 6. 消息邮箱 - -### 6.1 原理与特点 - -消息邮箱以 `rt_ubase_t` 为一封邮件的存储单元。在本次使用的 32 位平台上,每封邮件为 4 字节,适合传递整型值或指针,具有开销小、效率高的特点。 - -邮箱中传递指针时,必须确保指针指向的数据在接收者处理完成前一直有效,并明确该内存由谁管理和释放。不能将即将失效的局部变量地址作为邮件发送。 - -### 6.2 常用 API - -| API | 作用 | -|---|---| -| `rt_mb_create()` / `rt_mb_init()` | 创建或初始化邮箱 | -| `rt_mb_send()` | 发送邮件 | -| `rt_mb_recv()` | 接收邮件,可指定超时时间 | -| `rt_mb_urgent()` | 将紧急邮件放到待接收邮件的前端 | - -`rt_mb_send()` 不会因等待空闲邮箱位置而挂起;邮箱已满时应检查错误返回值。因其发送路径不阻塞,可用于中断向线程发送简短通知。 - -## 7. 消息队列 - -### 7.1 原理与特点 - -消息队列用于在线程之间传递一段数据,比邮箱更适合传递结构体、数据帧等复合消息。消息队列在创建时需指定单条消息的最大长度 `msg_size` 和队列容量,内核使用消息块管理待接收数据。 - -与邮箱相比,队列不只能传递一个机器字,但它并非无上限地接收任意长度数据;发送长度不应超过创建时设定的 `msg_size`。 - -### 7.2 常用 API - -| API | 作用 | -|---|---| -| `rt_mq_create()` / `rt_mq_init()` | 创建或初始化消息队列 | -| `rt_mq_send()` | 将普通消息放入队尾 | -| `rt_mq_urgent()` | 将紧急消息放入队首 | -| `rt_mq_recv()` | 从队首接收消息,可指定超时时间 | - -普通消息按先进先出顺序处理,`rt_mq_urgent()` 可将紧急消息插入队首,使其先于已排队的普通消息被接收。该能力应谨慎使用:持续插入紧急消息可能使普通消息长时间得不到处理。 - -发送和接收时都应检查返回值。使用基础发送接口时,队列已满并不意味着可以无限等待,应根据所用 RT-Thread 版本和接口语义处理队列满的情况。 - -## 8. IPC 选型与对比 - -| 机制 | 是否传递数据 | 核心用途 | 关键特性 | 典型场景 | -|---|---|---|---|---| -| 信号量 | 否 | 单事件同步、资源计数 | 无所有权,可由中断释放 | 按键中断通知处理线程 | -| 互斥量 | 否 | 保护临界区 | 有所有权、递归获取、优先级继承 | 多线程访问同一设备或共享数据 | -| 事件集 | 否 | 多条件组合同步 | 32 位标志,支持 AND/OR | 等待多个模块就绪 | -| 消息邮箱 | 是 | 传递一个机器字 | 开销小,适合整型或指针 | 传感器采样值或缓冲区指针通知 | -| 消息队列 | 是 | 传递数据块 | 支持结构化消息和紧急插队 | 串口数据帧、命令和状态消息 | - -可按以下思路选型: - -1. 只需要一次事件通知,优先考虑二值信号量。 -2. 需要统计可用资源数量,使用计数信号量。 -3. 需要保护共享资源,使用互斥量。 -4. 需要等待多个条件的 AND/OR 组合,使用事件集。 -5. 只需要传递整型或指针,优先考虑消息邮箱。 -6. 需要传递结构化数据或数据帧,使用消息队列。 - -## 9. 实验现象与结论 - -### 9.1 信号量实验 - -线程 1 每计数 10 次释放一次信号量,线程 2 以永久等待方式获取信号量,成功后对 `number` 自增。最终 `number` 为 10,说明信号量能够将生产者的同步信号可靠地传递给等待线程。 - -### 9.2 互斥量实验 - -两个线程分别修改 `number1` 和 `number2`: - -- 使用互斥量保护时,两个变量始终保持一致。 -- 去掉互斥保护后,线程 1 在两次修改之间因 `rt_thread_mdelay()` 挂起,线程 2 插入执行,导致数据不一致。 - -该实验说明:单条 C 语句看似简单,但一组业务操作并非必然具有原子性。正确的临界区边界应覆盖整个需要保持一致性的操作序列。 - -### 9.3 事件集实验 - -线程 1 先以 OR 模式等待事件 3 或事件 5,线程 2 发送事件 3 后线程 1 立即被唤醒。之后改为 AND 模式,仅发送事件 3 不会唤醒,待事件 5 也到达后才满足条件。实验验证了 OR 与 AND 的条件判定差异。 - -### 9.4 消息邮箱实验 - -发送线程交替发送 `"string1"` 和 `"string2"` 的指针,最后发送 `"over"`;接收线程永久等待邮件,收到 `"over"` 后退出。实验说明,邮箱传递的是指针数值,而不是将整个字符串拷贝到邮箱中。 - -### 9.5 消息队列实验 - -普通消息按 `a` 至 `h` 的顺序入队,之后使用 `rt_mq_urgent()` 发送 `i`。接收顺序为 `a` 、`i` 、`b` 至 `h`。`a` 在紧急消息发送前已经被取出,`i` 则被插入剩余待处理消息的队首,因此第二个被接收。 - -## 10. 易错点与学习收获 - -### 10.1 易错点 - -- 信号量适合“通知/计数”,互斥量适合“保护”,二者不应只因计数值都可以是 1 就混用。 -- 事件集不能记录事件发生次数,需要逐条保留的事件应放入队列。 -- 邮箱在 32 位平台上传递 4 字节数值;传递大数据时实际传的是指针,需要特别注意对象生命周期。 -- 消息队列的单条消息长度受 `msg_size` 限制,应在容量、内存占用和峰值消息量之间做设计。 -- 不能忽略 IPC API 的返回值;超时、队列满、对象不可用都应有明确的处理策略。 -- 中断中不能执行可能导致挂起的操作,实际开发时应确认当前 RT-Thread 版本对具体 API 的中断上下文约束。 - -### 10.2 学习收获 - -本次学习的重点不是记忆 API 名称,而是先分析线程间关系:是需要等待条件、保护共享资源,还是传递数据;再根据是否需要计数、组合事件、所有权或数据缓冲能力选择 IPC 对象。 - -代码实现时还需同时考虑临界区范围、超时策略、返回值、中断上下文限制及数据生命周期。只有把 IPC 机制的语义与实际调度过程联系起来,才能避免竞态、死锁、数据丢失和不确定延时等问题。 diff --git "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 8e1a2a7a17b8fdfebe8a947d06dbc886ae93a2e3..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\210\230\346\230\212/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,336 +0,0 @@ -# 2026 RT-Thread 大学生夏令营 Day2 学习笔记 - -> 课程主题:RT-Thread 线程机制、SCons 构建系统、系统启动流程与多线程实践 -> 目标平台:STM32F407 星火号(`stm32f407-rtspark`) -> 整理日期:2026-08-18 - -本次课程围绕线程调度与生命周期展开,随后讲解 SCons 工程构建,并从启动代码跟踪到用户 `main()`。 - -## 目录 - -- [一、课程内容概览](#一课程内容概览) -- [二、线程机制与调度策略](#二线程机制与调度策略) -- [三、系统线程:idle 与 main](#三系统线程idle-与-main) -- [四、线程控制块、对象模型与生命周期](#四线程控制块对象模型与生命周期) -- [五、静态线程与动态线程](#五静态线程与动态线程) -- [六、SCons 构建系统](#六scons-构建系统) -- [七、RT-Thread Studio 与星火号实操](#七rt-thread-studio-与星火号实操) -- [八、STM32F407 到 RT-Thread main 的启动流程](#八stm32f407-到-rt-thread-main-的启动流程) -- [九、线程示例、FinSH 与软件包](#九线程示例finsh-与软件包) -- [十、个人总结](#十个人总结) - -## 一、课程内容概览 - -本次课程主要回答六个相互关联的问题:线程为什么存在、调度器怎样选择线程、系统线程承担什么工作、线程资源从哪里来、工程怎样参与构建,以及系统上电后怎样进入用户程序。理解这些问题后,可以进一步明确多线程 API 背后的内核运行过程。 - -重点内容包括抢占式调度、时间片轮转、idle 与 main 线程、线程控制块、静态与动态创建、SCons 构建、自动初始化以及 FinSH 命令导出。课后作业将这些知识串联起来,需要在星火号上完成两个 LED 线程、一个监控线程和按键控制。 - -## 二、线程机制与调度策略 - -### 2.1 从裸机前后台系统到多线程系统 - -裸机程序通常采用“中断作为前台、`while (1)` 作为后台”的结构。中断负责接收异步事件,主循环依次执行 ADC、SPI、USB、显示等处理函数。业务规模扩大后,这种结构容易出现以下问题: - -- 并发效率低:各业务只能在主循环中依次执行。 -- 实时性变差:前面的慢任务会延迟后续任务。 -- 可维护性差:功能彼此耦合,局部修改可能影响整个循环。 -- 复用性不足:模块边界不清晰,代码难以独立迁移。 - -RTOS 采用“分而治之”的思路,把复杂系统拆成多个线程。每个线程拥有独立入口函数、栈和调度属性,内核负责决定何时运行哪个线程。线程是 RT-Thread 的基本调度单元。 - -### 2.2 抢占式优先级调度 - -RT-Thread 采用基于优先级的抢占式调度: - -- 优先级数值越小,线程优先级越高。 -- 更高优先级线程一旦进入就绪态,可以抢占当前低优先级线程。 -- 线程若不再需要 CPU,应通过延时、等待信号量等方式主动阻塞。 -- 高优先级线程若一直运行且不阻塞,低优先级线程将长期得不到运行机会。 - -这套机制保证紧急任务能够快速响应,但优先级设计不合理也会造成线程饥饿。 - -### 2.3 同优先级线程的时间片轮转 - -时间片只对“优先级相同且均处于就绪态”的线程起作用。假设线程 A 和 B 优先级相同,A 的时间片为 10 ticks,B 的时间片为 5 ticks,且没有更高优先级线程就绪,则调度器会让 A 最多连续运行 10 个系统节拍,再让 B 最多连续运行 5 个系统节拍,如此轮转。 - -时间换算公式为: - -```text -1 tick 的时长 = 1000 / RT_TICK_PER_SECOND(毫秒) -``` - -课堂中以“1 tick 为 10 ms”举例,但该值并不固定,必须以具体工程配置为准。当前星火 F407 工程中: - -```c -#define RT_TICK_PER_SECOND 1000 -``` - -因此在这个工程中 `1 tick = 1 ms`,作业要求的时间片 `5 ticks` 对应约 5 ms;`rt_thread_mdelay(2000)` 则由系统换算为约 2000 ms。切换 BSP 后需要重新确认该配置,不能直接套用当前数值。 - -### 2.4 线程入口函数 - -线程业务由入口函数实现,函数原型为: - -```c -void thread_entry(void *parameter); -``` - -入口函数通常包含循环、设备操作和适当的阻塞动作。例如 LED 线程每次翻转 LED 后调用 `rt_thread_mdelay()`,既形成可观察的闪烁周期,也让出 CPU。线程入口若执行完毕,线程会退出并进入关闭态;需要持续运行的任务一般使用无限循环。 - -## 三、系统线程:idle 与 main - -### 3.1 空闲线程 idle - -idle 是系统创建的最低优先级线程。当没有其他就绪线程时,调度器运行 idle,因此它通常一直保持就绪,且不能被挂起。其主要作用包括: - -1. 回收退出线程的资源。动态线程退出后进入关闭态并挂入 `rt_thread_defunct` 僵尸队列,最终由 idle 线程释放相关资源。 -2. 执行空闲钩子。低功耗处理、运行状态统计等轻量工作可以接入 idle hook。 -3. 配合看门狗设计。系统有足够空闲时间时可在 idle 路径喂狗;若高优先级任务卡死导致 idle 长时间无法运行,看门狗可以触发复位。 - -系统设计时应为 idle 留出足够的运行机会。不过实际项目不能机械套用某个负载百分比,还要根据实时截止期、峰值负载、低功耗目标和看门狗窗口进行测量。 - -### 3.2 主线程 main - -RT-Thread 中用户看到的 `main()` 运行在线程上下文中。系统创建的 main 线程入口为 `main_thread_entry()`,它先执行组件自动初始化,再调用用户 `main()`。 - -当前 RT-Thread 4.1.1 工程启用了堆,因此 main 线程由 `rt_thread_create()` 动态创建,栈大小由 `RT_MAIN_THREAD_STACK_SIZE` 决定;当前配置值为 2048 bytes。 - -## 四、线程控制块、对象模型与生命周期 - -### 4.1 内核对象模型 - -RT-Thread 使用统一的内核对象管理机制。线程、信号量、互斥量、定时器等对象都带有基础对象信息,并注册到相应的对象容器中。线程控制块 `struct rt_thread` 在基础对象信息之上增加了栈、状态、优先级、时间片和线程定时器等成员。 - -这种设计的意义是: - -- 用统一方式保存对象名称、类型和对象链表关系。 -- 可按对象类型遍历和管理系统资源。 -- FinSH 的线程查看命令可以从线程对象容器中取得线程信息。 - -### 4.2 `struct rt_thread` 关键成员 - -| 成员 | 作用 | -| --- | --- | -| `name` | 线程对象名称;长度受 `RT_NAME_MAX` 限制 | -| `sp` | 当前保存的线程栈指针,是上下文切换恢复现场的关键入口 | -| `entry` / `parameter` | 线程入口函数及其参数 | -| `stack_addr` / `stack_size` | 栈起始地址和总大小 | -| `stat` | 当前线程状态;读取基础状态时应使用 `stat & RT_THREAD_STAT_MASK` | -| `current_priority` | 当前有效优先级;互斥量优先级继承等机制可能使其不同于初始优先级 | -| `init_tick` | 初始时间片大小 | -| `remaining_tick` | 当前轮次剩余时间片 | -| `thread_timer` | 线程内置定时器,用于延时或超时等待 | -| `cleanup` | 线程退出时的清理回调 | - -线程栈用于保存上下文、函数调用现场和局部变量。Cortex-M 通常采用向低地址增长的栈,栈地址与大小必须满足平台对齐要求。当前工程的 `RT_ALIGN_SIZE` 为 4。 - -### 4.3 五态模型 - -```mermaid -stateDiagram-v2 - [*] --> 初始态: rt_thread_init / rt_thread_create - 初始态 --> 就绪态: rt_thread_startup - 就绪态 --> 运行态: 调度器选中 - 运行态 --> 就绪态: 被抢占或时间片用完 - 运行态 --> 挂起态: delay / IPC等待 / rt_thread_suspend - 挂起态 --> 就绪态: 超时 / 事件到达 / rt_thread_resume - 运行态 --> 关闭态: 入口函数返回 / delete - 关闭态 --> [*]: idle 回收动态资源 -``` - -五种基础状态如下: - -| 状态 | 宏 | 说明 | -| --- | --- | --- | -| 初始态 | `RT_THREAD_INIT` | 已创建,但尚未加入就绪队列 | -| 就绪态 | `RT_THREAD_READY` | 等待调度器分配 CPU | -| 运行态 | `RT_THREAD_RUNNING` | 当前正在执行;单核同一时刻只有一个运行线程 | -| 挂起态 | `RT_THREAD_SUSPEND` | 主动延时、等待资源或被显式挂起,不参与调度 | -| 关闭态 | `RT_THREAD_CLOSE` | 已结束,不再参与调度 | - -注意:LED 线程调用 `rt_thread_mdelay()` 后,在监控采样时经常显示为挂起态,这是正常的延时等待,不一定表示被按键手动暂停。 - -## 五、静态线程与动态线程 - -### 5.1 两种创建方式 - -| 对比项 | 静态初始化 `rt_thread_init()` | 动态创建 `rt_thread_create()` | -| --- | --- | --- | -| TCB 与栈来源 | 用户预先定义 | 系统堆分配 | -| 资源确定时间 | 编译、链接阶段 | 运行阶段 | -| 失败风险 | 参数或空间配置错误 | 堆不足、碎片化或参数错误 | -| 释放方式 | 用户管理存储期,不能按动态对象方式释放 | 退出后由系统和 idle 回收 | -| 适用场景 | 内存必须确定、长期运行、高可靠系统 | 常规应用、快速开发、按需创建 | - -静态线程的控制块和栈通常定义为全局或静态变量,并显式对齐: - -```c -static struct rt_thread led2_thread; -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t led2_stack[1024]; -``` - -动态线程只需指定栈大小,由内核从堆中分配线程控制块和栈。两种方式创建后都处于初始态,必须调用 `rt_thread_startup()` 才会进入就绪队列。 - -### 5.2 为什么高可靠场景倾向静态分配 - -静态分配使内存需求在编译、链接阶段即可分析,启动后的地址和总量稳定,不依赖堆当前状态,也不会引入运行期堆碎片。对于有严格认证和确定性要求的系统,常通过编码规范禁止或限制运行期动态申请。 - -这不等于“动态创建必然不可靠”。普通项目可根据生命周期、内存规模和错误处理能力选择;关键是检查创建返回值,并明确资源释放策略。 - -## 六、SCons 构建系统 - -### 6.1 作用与核心文件 - -SCons 是基于 Python 的构建工具。RT-Thread 借助它组织多层目录、按配置项选择源码、设置工具链参数,并生成 IDE 工程。需要重点关注以下四类文件: - -| 文件 | 作用 | -| --- | --- | -| `SConstruct` | 工程顶层构建入口 | -| `SConscript` | 各目录的局部构建描述,通常与源码就近放置 | -| `rtconfig.py` | 工具链、CPU 架构、浮点和优化等编译参数 | -| `rtconfig.h` | Kconfig/menuconfig 生成的全局配置宏 | - -### 6.2 常用命令 - -```bash -scons # 编译工程 -scons -j4 # 使用 4 个并行任务编译 -scons -c # 清理构建产物 -scons --target=mdk5 # 生成 Keil MDK5 工程 -scons --target=cmake # 生成 CMake 配置 -scons --dist # 生成相对独立的发布工程 -scons --verbose # 输出详细编译命令,便于排错 -``` - -具体可用 target 取决于当前 RT-Thread 工具版本,应以 `scons --help` 为准。 - -### 6.3 `SConscript` 常见写法 - -```python -from building import * - -cwd = GetCurrentDir() -src = Glob('*.c') -CPPPATH = [cwd] - -group = DefineGroup('Applications', src, depend=[''], CPPPATH=CPPPATH) - -Return('group') -``` - -重点概念: - -- `Glob('*.c')` 批量取得源文件。 -- `src` 保存参与构建的源文件列表,`src.remove(...)` 可排除文件。 -- `CPPPATH` 增加头文件搜索路径;遗漏路径会造成头文件找不到。 -- `DefineGroup(name, src, depend, ...)` 将源码注册为构建组。 -- `depend` 关联 Kconfig 宏。依赖未启用时,该组不参与构建;空依赖可用于始终编译。 -- `CCFLAGS`、`CFLAGS`、`CXXFLAGS` 等参数用于设置不同范围的编译选项。 - -SCons 会递归执行各级 `SConscript`,因此大型工程可以把构建规则分散到对应模块目录中,减少顶层脚本的维护压力。 - -### 6.4 Kconfig、menuconfig 与 `rtconfig.h` - -Kconfig 描述配置项和菜单关系,menuconfig 或 Studio 图形界面用于选择配置,最终结果写入 `rtconfig.h`。SConscript 再通过 `depend` 判断相应宏是否启用,从而决定某个组件是否参与构建。 - -Studio 的图形设置适合入门和 BSP 开发;env 命令行更灵活,更适合主线贡献、批处理和可复现构建。 - -## 七、RT-Thread Studio 与星火号实操 - -课堂演示的基本操作流程如下: - -1. 在 RT-Thread Studio 中创建 STM32F407 工程,并选择与开发板匹配的 BSP/芯片支持包。 -2. 开发板连接 USB Debug 接口,在设备管理器中确认调试器和串口被识别。 -3. 根据板载调试器实际固件选择 ST-Link 或 J-Link,类型不匹配会导致下载失败。 -4. 编译工程,检查零错误后烧录。 -5. 打开串口终端,确认波特率与控制台配置一致。 -6. 使用 `help` 查看命令、Tab 补全命令,并用 `list_thread`(部分版本也提供 `ps`)观察线程。 - -链接脚本决定 Flash、RAM 的起始地址和容量;调试启动问题时,应把启动汇编、链接脚本、map 文件和 `rtconfig.h` 放在一起核对。 - -![1](figures/1.png) - -## 八、STM32F407 到 RT-Thread `main()` 的启动流程 - -### 8.1 完整调用链 - -当前 GCC 工程从复位入口到用户 `main()` 的调用关系如下: - -```mermaid -flowchart TD - A[上电或复位] --> B[从向量表装载 MSP 与 Reset_Handler] - B --> C[设置栈顶,复制 .data,清零 .bss] - C --> D[SystemInit 配置基础时钟] - D --> E[entry] - E --> F[rtthread_startup] - F --> G[关闭中断并执行 rt_hw_board_init] - G --> H[板级初始化:时钟、SysTick、堆、控制台等] - H --> I[版本、定时器系统和调度器初始化] - I --> J[rt_application_init 创建并启动 main 线程] - J --> K[初始化 timer 线程与 idle 线程] - K --> L[rt_system_scheduler_start] - L --> M[切换到最高优先级就绪线程] - M --> N[main_thread_entry] - N --> O[rt_components_init] - O --> P[用户 main] -``` - -要点如下: - -- Cortex-M4 从向量表取得初始 MSP 和复位入口。 -- `Reset_Handler` 先建立 C 运行环境,再调用 `SystemInit()`,最后跳到 RT-Thread 的 `entry()`。 -- `rtthread_startup()` 初始化板级资源、内核定时器、调度器、main/timer/idle 线程,然后启动调度器。 -- `main()` 不是调度器启动前直接调用的普通入口,而是由 main 线程的 `main_thread_entry()` 调用。 -- 首个实际运行线程由就绪线程优先级决定,不能无条件写成“必然是 timer 线程”。 - -### 8.2 自动初始化机制 - -`INIT_BOARD_EXPORT(fn)` 和 `INIT_APP_EXPORT(fn)` 最终都展开为 `INIT_EXPORT(fn, level)`。以 GCC 为例,宏会把函数指针放入带等级的链接段: - -```c -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") -``` - -初始化等级依次为: - -| 等级 | 宏 | 典型用途 | -| --- | --- | --- | -| 1 | `INIT_BOARD_EXPORT` | 调度器启动前所需的板级初始化 | -| 2 | `INIT_PREV_EXPORT` | 纯软件预初始化 | -| 3 | `INIT_DEVICE_EXPORT` | 设备初始化 | -| 4 | `INIT_COMPONENT_EXPORT` | 文件系统、网络等组件 | -| 5 | `INIT_ENV_EXPORT` | 挂载、环境准备 | -| 6 | `INIT_APP_EXPORT` | 应用初始化 | - -链接器把同类段按等级排列,并保留起止符号。运行时: - -- `rt_components_board_init()` 遍历 board 范围,调用 level 1 函数。 -- main 线程中的 `rt_components_init()` 遍历后续范围,按等级调用 level 2 至 level 6 函数。 - -因此自动初始化不是运行时按函数名“搜索”,也不是依赖反射,而是“编译期注册到链接段,链接期排序,运行期遍历函数指针”。作业使用 `INIT_APP_EXPORT()` 后,测试模块无需在 `main.c` 中硬编码调用。 - -## 九、线程示例、FinSH 与软件包 - -线程示例通过 `MSH_CMD_EXPORT()` 将函数注册为 FinSH/MSH 命令。一个线程周期打印,另一个线程打印若干次后退出,可以观察到以下现象: - -- 高优先级线程先运行。 -- 高优先级线程若没有延时或等待,会连续占用 CPU。 -- 线程入口返回后进入关闭态;动态线程资源随后由 idle 回收。 -- `list_thread` 可查看线程优先级、状态、栈大小和最大栈使用率。 - -Studio 软件包管理功能类似组件仓库。添加软件包前需要确认目标芯片、BSP、外设和依赖宏是否兼容。下载失败时还应检查网络源;编译失败时则优先检查硬件依赖和配置项,而不能只归因于网络。 - -![2](figures/2.png) - -## 十、个人总结 - -本节课把 RT-Thread 的启动、对象和调度三条线串联起来:启动代码建立运行环境并初始化内核;对象模型保存线程及其他资源;调度器根据状态、优先级和时间片分配 CPU。静态与动态线程的本质区别是资源由谁提供、何时确定以及如何回收,而不是业务逻辑不同。 - -实践时最需要注意的是: - -- 时间单位必须读取目标工程的 `RT_TICK_PER_SECOND`,不能套用其他 BSP 的默认值。 -- 线程状态字段需要掩码后解释,延时也会表现为挂起态。 -- 栈“剩余量”应按栈填充值统计高水位,不能简单把当前 SP 与栈边界之差当成历史剩余量。 -- 自动初始化和 Shell 导出依赖配置宏及链接段,代码写对但配置未启用时,功能仍不会出现。 diff --git "a/2026/\347\254\2544\347\273\204/\345\220\264\351\234\207\351\232\206/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\345\220\264\351\234\207\351\232\206/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" new file mode 100644 index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\345\220\264\351\234\207\351\232\206/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" @@ -0,0 +1 @@ +--- diff --git "a/2026/\347\254\2544\347\273\204/\345\220\264\351\234\207\351\232\206/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\345\220\264\351\234\207\351\232\206/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\345\220\264\351\234\207\351\232\206/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" @@ -0,0 +1 @@ +--- diff --git "a/2026/\347\254\2544\347\273\204/\345\274\240\347\216\211\350\241\241/RT_Image/image0.png" "b/2026/\347\254\2544\347\273\204/\345\274\240\347\216\211\350\241\241/RT_Image/image0.png" deleted file mode 100644 index b343ee7b81a85ffc5eec9c30336611e98143cfed..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\345\274\240\347\216\211\350\241\241/RT_Image/image0.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\345\274\240\347\216\211\350\241\241/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\345\274\240\347\216\211\350\241\241/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e162af239b5f1f11c7980b319630ca1c17d886d6..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\274\240\347\216\211\350\241\241/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,3 +0,0 @@ -初始化文档PR:[#38](https://gitee.com/rtthread/rsoc-rtt/pulls/38) - -提交作业笔记PR:[#95](https://gitee.com/rtthread/rsoc-rtt/pulls/95) \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\345\274\240\347\216\211\350\241\241/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\345\274\240\347\216\211\350\241\241/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index a1ddb4363330d0492818b12f05b0af6023a0970d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\345\274\240\347\216\211\350\241\241/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,631 +0,0 @@ -# RT-Thread 大学生夏令营 Day1 学习笔记 - -> 日期:2026-08-17 -> 作者:<你的姓名> | 组别:<第x组> -> 主题:RT-Thread 基础生态、开发环境搭建、STM32F407 工程创建与调试、Markdown 规范、Git/Gitee 开源协作 - ---- - -## 一、夏令营整体安排 - -- **双周学习模式** - - 第一周 = 学习周:上午 9:00–12:00 授课,下午 14:00 起实操与作业; - - 第二周 = 项目周:学员按项目任务分组完成综合项目。 -- **每日流程**:开课前线上签到 → 上午授课 → 下午实操 → 用 Markdown 整理笔记与作业 → Git 提交 → 发起 Pull Request。 -- **考核与产出**:每日完成作业并提交至 Git 仓库;第二周根据项目任务筛选分组。 -- **硬件**:星火 1 号(RT-Thread Spark-1)开发板,MCU 为 STM32F407ZGT6。 - ---- - -## 二、RT-Thread 生态与三条产品线 - -RT-Thread 是国产自研的实时操作系统(RTOS),发展近 20 年,GitHub 12.2k+ stars,是国内活跃的开源社区项目,内核已迭代至 V5.x。 - -### RTOS 为什么需要线程与调度 - -在裸机程序中,通常是在 `main()` 里循环执行不同任务: - -```c -int main(void) -{ - while (1) - { - task_a(); - task_b(); - task_c(); - } -} -``` - -功能简单时够用,但随着系统复杂度提高,会出现「任务周期不同、某个任务阻塞拖垮其他任务、程序耦合越来越严重」等问题。RTOS 的思路是:把不同功能拆成多个线程,交给操作系统调度运行。 - -几个核心概念: - -| 概念 | 含义 | -| ------------- | --------------------------------------------- | -| Thread / Task | 系统中的基本运行实体 | -| Scheduler | 根据优先级和线程状态决定 CPU 当前运行哪个线程 | -| Tick | RTOS 的基本时间节拍 | -| IPC | 线程之间的通信与同步机制 | - -常见 IPC:信号量(Semaphore)、互斥量(Mutex)、事件(Event)、邮箱(Mailbox)、消息队列(Message Queue)。 - -### 三条产品线 - -| 版本 | 特点 | 适用场景 | -| ---------------------- | ------------------------------------------------------------ | ------------------------ | -| **Nano** | 极简内核:线程/调度/时钟/中断/内存管理/IPC,资源占用低,Apache 协议开源 | 资源受限的小型 MCU | -| **标准版(Standard)** | 内核 + 设备框架 + FinSH/DFS/网络/POSIX/日志/IoT 组件 + 软件包,生态完整 | 通用 MCU、常规嵌入式系统 | -| **Smart** | 面向带 MMU 的处理器,用户态/内核态隔离,安全性与隔离性更高 | 高性能嵌入式设备 | - -> 记忆:**Nano → 小而够用;Standard → 完整 MCU RTOS;Smart → 面向 MMU 的操作系统形态。** - -分层模型可以简单理解为: - -```text -Application(应用) - ↓ -Components / Packages(组件 / 软件包) - ↓ -RT-Thread Kernel(内核) - ↓ -Device Driver Framework(设备驱动框架) - ↓ -BSP / Hardware(板级支持 / 硬件) -``` - ---- - -## 三、开发环境搭建 - -### 3.1 环境三要素 - -RT-Thread 开发环境由三部分组成: - -```text -RT-Thread 源码 - + -SDK 开发工具(工具链 / 编译器 / 开发工具) - + -BSP 板级支持包(启动文件 / 底层驱动 / 工程配置) - ↓ -RT-Thread 开发环境 -``` - -- **RT-Thread Studio**:官方一站式 IDE,集成工程创建、SDK 管理、编译、下载、调试。 -- **SDK Manager**:统一管理 RT-Thread 源码、STM32F4 芯片支持包、工具链等开发依赖。 -- **BSP(板级支持包)**:适配具体开发板,包含启动文件、底层驱动、工程配置等核心文件。 - -### 3.2 搭建步骤 - -1. 安装 RT-Thread Studio 软件,按教程完成配置; -2. 打开 SDK Manager,下载课程指定版本的 RT-Thread 源码(4.1.1)+ STM32F4 芯片支持包; -3. 将 STM32F407 开发板通过 ST-Link 连接电脑,在设备管理器中确认 ST-Link 驱动被正常识别; -4. 新建工程:芯片选 STM32F407ZG,内核选标准版,串口波特率 115200。 - ---- - -## 四、工程创建与 Hello World 实操 - -### 4.1 创建 STM32F407ZG 工程 - -```text -新建工程 - ↓ -选择芯片 STM32F407ZG - ↓ -选择 RT-Thread 版本(4.1.1) - ↓ -配置工程(串口 115200-8-N-1) - ↓ -修改 applications/main.c - ↓ -Build(编译) - ↓ -生成 .elf / .bin - ↓ -ST-Link 下载 - ↓ -串口终端观察 - ↓ -Debug(断点调试) -``` - -创建工程时需同时确认:MCU 型号、RT-Thread 版本、BSP、工具链、调试器、Console/UART 配置。 - -### 4.2 串口配置 - -| 参数 | 值 | -| ------ | ------ | -| 波特率 | 115200 | -| 数据位 | 8 | -| 校验位 | None | -| 停止位 | 1 | -| 流控 | None | -| 编码 | UTF-8 | - -### 4.3 关键代码 `applications/main.c` - -```c -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -int main(void) -{ - int count = 1; - - rt_kprintf("rsoc 2026!"); - - while (count++) - { - LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - - return RT_EOK; -} -``` - -- `rt_kprintf()`:通过 RT-Thread 控制台输出字符串; -- `LOG_D()`:输出 Debug 级别日志(配合 `DBG_TAG`、`DBG_LVL`),自动换行; -- `rt_thread_mdelay(1000)`:让当前线程延时 1000 毫秒(约 1 秒)。 - -### 4.4 终端运行结果 - -复位开发板后,串口输出启动 banner: - -```text - \ | / -- RT - Thread Operating System - / | \ 5.2.2 build Aug 17 2026 22:27:04 - 2006 - 2024 Copyright by RT-Thread team -rsoc 2026! -[D/main] Hello RT-Thread! -msh >[D/main] Hello RT-Thread! -``` - -主循环每秒打印一次 `[D/main] Hello RT-Thread!`,说明主线程在周期运行。 - -### 4.5 QEMU A9 仿真工程 - -除了实体开发板,还可以用 QEMU 模拟 ARM 硬件环境。创建 `qemu-vexpress-a9` 工程,无需实体板即可完成编译、运行并进入 `msh` 命令行,用于自检开发环境是否配置正确: - -```text -创建 qemu-vexpress-a9 工程 → 检查配置 → 编译 → 运行 → 进入 msh -``` - -> 若工程配置未正确同步,右键工程执行「同步 SCons 配置至项目」后重新编译。 - ---- - -## 五、Build / Download / Run / Debug 四阶段 - -这四个概念在嵌入式开发中经常一起出现,但作用不同: - -### 5.1 Build(编译) - -把源码转换为 MCU 可执行的程序: - -```text -Source Code → Preprocess → Compile → Assemble → Link → Executable Image -``` - -常见输出:`.elf`(含符号与调试信息,Debug 用)与 `.bin`(纯二进制镜像,烧写用)。 - -### 5.2 Download(下载) - -把编译好的程序写入 MCU:`PC → RT-Thread Studio → ST-Link → STM32F407`。 - -### 5.3 Run(运行) - -MCU 正常执行已烧录的程序。 - -### 5.4 Debug(调试) - -通过 ST-Link 在调试器控制下运行:断点、单步、查看变量/寄存器/调用栈。 - -| 阶段 | 作用 | -| -------- | ---------------------- | -| Build | 把源码变成程序 | -| Download | 把程序写入 MCU | -| Run | MCU 正常运行程序 | -| Debug | 在调试器控制下分析程序 | - ---- - -## 六、RT-Thread 源码目录结构 - -以 RT-Thread 4.1.1 源码树为例,`rt-thread/` 是一份完整的**内核源码树**,官方分成 6 大目录: - -```text -rt-thread/ -├── src/ 内核核心源码 -├── include/ 内核头文件 -├── libcpu/ 芯片/CPU 移植代码 -├── components/ 组件与中间件 -├── examples/ 示例代码(纯参考) -├── tools/ 构建工具脚本(SCons) -├── Kconfig 内核配置菜单入口 -└── SConscript 内核构建脚本入口 -``` - -### 6.1 `src/` —— 内核实现 + `include/` —— 内核接口 - -`src/` 是内核"心脏",全部与芯片无关、纯 C 实现: - -| 文件 | 作用 | -| ---------------------------------------------- | ------------------------------------------------------------ | -| `scheduler.c` | 调度器:就绪队列、优先级调度、时间片轮转,`rt_schedule()` 是线程切换核心 | -| `thread.c` | 线程管理:创建/删除/启动/挂起/恢复/延时 | -| `timer.c` | 定时器(软件/硬件) | -| `ipc.c` | IPC:信号量/互斥锁/事件/邮箱/消息队列 | -| `mem.c` / `memheap.c` / `mempool.c` / `slab.c` | 多种内存管理算法 | -| `object.c` | 内核对象系统(对象注册、查找,链表管理) | -| `device.c` | 设备框架 `rt_device_*` | -| `kservice.c` | 内核基础服务:`rt_kprintf` 等 | -| `clock.c` | 系统时钟节拍(tick) | -| `cpu.c` / `irq.c` | CPU 封装 / 中断管理 | -| `idle.c` | 空闲线程(优先级最低) | -| `signal.c` | 信号机制(POSIX) | -| `components.c` | 组件自动初始化 `rt_components_init()` | - -`include/` 关键头文件: - -| 头文件 | 作用 | -| ------------- | ------------------------------------------ | -| `rtthread.h` | 总头文件,`#include ` 串起其余 | -| `rtdef.h` | 基础类型、内核对象结构体、通用宏 | -| `rthw.h` | 硬件抽象接口(移植层必须实现的函数声明) | -| `rtservice.h` | 双向链表、`container_of` 等容器工具 | -| `rtdbg.h` | 调试宏 `LOG_D/I/W/E` | - -> 关键理解:**`src/` 实现内核,`include/` 定义接口**。`rthw.h` 里声明的函数就是给 `libcpu/` 移植层下的"订单"——内核只管调用,具体操作硬件由移植层完成。 - -### 6.2 `libcpu/` —— 移植层 - -按芯片架构分目录(arm / risc-v / mips / ia32 / aarch64 等)。STM32F407 使用 Cortex-M4 内核,对应 `libcpu/arm/cortex-m4/`: - -| 文件 | 作用 | -| ---------------------------------------------------- | ------------------------------------------------------- | -| `cpuport.c` | CPU 移植实现:SysTick、PendSV、开关中断、启动第一个线程 | -| `context_gcc.S` / `context_iar.S` / `context_rvds.S` | 各编译器的线程上下文切换汇编 | - -> **上下文切换是 RTOS 的"命门"**,必须贴近芯片写汇编,所以放在移植层而不是内核层。换一块 CPU,只需换 `libcpu/` 对应目录。 - -### 6.3 `components/` —— 组件层 - -运行在内核之上,可选、可裁剪(由 `rtconfig.h` 的 `RT_USING_*` 宏决定是否编译): - -| 目录 | 作用 | -| ------------ | -------------------------------------- | -| `finsh/` | 命令行组件(FinSH / MSH) | -| `dfs/` | 虚拟文件系统框架 | -| `drivers/` | 设备驱动框架抽象(串口/SPI/I2C/GPIO…) | -| `net/` | 网络框架(lwIP、netdev、SAL、AT) | -| `fal/` | Flash 抽象层(分区管理) | -| `utilities/` | 工具(ulog 日志、utest 单元测试等) | -| `libc/` | C 库适配层 | - -> 注意:框架源码的存放位置 ≠ 框架名称。例如 GPIO 引脚框架源码在 `components/drivers/misc/pin.c`(头文件 `pin.h`),判断一个框架看 `include/drivers/` 下的头文件即可。 - -### 6.4 `tools/` —— 构建系统 - -RT-Thread 用 **SCons** 构建,`tools/` 是构建引擎:`building.py`(递归收集源码)、`gcc.py`/`keil.py`/`iar.py`(适配各编译后端)、`menuconfig.py`/`kconfiglib.py`(图形化配置)、`rt_studio.py`(Studio 专用后端)。 - -### 6.5 整体关系 - -```text - ┌──────────────────────────────┐ - │ 应用层 applications/main.c │ ← 你写的代码 - └──────────────────────────────┘ - │ 调用 API - ┌──────────────────────────────┐ - │ 组件层 components/ │ ← 可选,按 RT_USING_* 裁剪 - └──────────────────────────────┘ - │ - ┌──────────────────────────────┐ - │ 内核 src/(实现)+ include/(接口) - └──────────────────────────────┘ - │ 调用 rthw.h 声明的硬件接口 - ┌──────────────────────────────┐ - │ 移植层 libcpu/arm/cortex-m4/ │ ← 上下文切换汇编 + cpuport.c - └──────────────────────────────┘ - │ - STM32F407ZG 硬件 -``` - -> 核心结论:**同一个 `rt-thread/` 源码树,配合不同的 `rtconfig.h`,能产出不同"胖瘦"的系统**——这就是"可裁剪"。 - ---- - -## 七、RT-Thread 启动流程 - -```text -startup_xx.S(汇编入口) - ↓ 栈指针配置、Flash 数据拷贝到 SRAM、BSS 段清零 -编译器入口(MDK __main / GCC entry) - ↓ -rtthread_startup() - ↓ 关闭全局中断、板级硬件初始化、打印版本 Logo(rt_show_version) -内核模块初始化(系统时钟 → 调度器 → 信号量等) - ↓ 组件自动初始化(INIT_*_EXPORT 宏收口,rt_components_init 按顺序执行) -创建主线程 / 定时器线程 / 空闲线程 - ↓ -rt_system_scheduler_start()(启动调度器) - ↓ -进入 main() 线程(用户应用程序) -``` - -与裸机的核心区别:**RT-Thread 不是把 `main()` 当普通函数顺序执行,而是把它放进一个线程里运行**,系统从顺序执行变为多线程调度;只有调度器启动后,多线程才真正开始工作。 - -线程生命周期:`rt_thread_create()`(动态)或 `rt_thread_init()`(静态)→ `rt_thread_startup()` 进入就绪态 → 调度器按优先级/时间片调度。 - ---- - -## 八、FinSH / MSH 命令行 - -RT-Thread Standard 的 FinSH 组件提供命令行,开启 `RT_USING_FINSH` + `FINSH_USING_MSH` 后,串口出现 `msh >` 提示符。常用命令: - -| 命令 | 作用 | -| ----------------------------------------------- | -------------------------- | -| `help` | 查看命令帮助 | -| `free` | 查看内存使用情况 | -| `list_thread` / `ps` | 查看线程 | -| `list_sem` / `list_mutex` | 查看信号量 / 互斥锁 | -| `list_event` / `list_mailbox` / `list_msgqueue` | 查看事件 / 邮箱 / 消息队列 | -| `list_mempool` / `list_timer` | 查看内存池 / 定时器 | - -优势:**不需要反复修改程序并重新编译,就能直接查看 RTOS 当前的运行状态**,是后续学习线程、IPC、设备驱动时的核心调试工具。 - ---- - -## 九、Markdown 笔记规范 - -夏令营要求每天用 Markdown 整理笔记与作业: - -### Typora 工具设置偏好 - -![image0](../RT_Image/image0.png) - -```markdown -# 一级标题 -## 二级标题 -### 三级标题 - -**加粗文字** -*斜体文字* -`高亮文字` - -- 无序列表 -1. 有序列表 - -| 表头 | 表头 | -| --- | --- | -| 内容 | 内容 | - -![图片说明](./RT_MD/*.png) -``` - -代码块用三个反引号包裹,起始处标注语言实现高亮: - -````markdown -```c -#include -int main(void) { return 0; } -``` -```` - -**图片管理要点**:图片统一存放到 `./RT_MD`,使用相对路径引用(`./RT_MD/image.png`),避免工程迁移或上传 Gitee 后图片丢失。相对路径优于 Windows 绝对路径(`D:\xxx\image.png`)。 - ---- - -## 十、Git 基础与版本控制 - -Git 是分布式版本控制系统,用于保存历史、跟踪修改、多人协作、分支开发、代码回退与开源贡献。 - -### 10.1 四大区域数据流 - -```text -工作区(Working Tree) --git add--> 暂存区(Staging Area) --git commit--> 本地仓库(Local Repo) --git push--> 远程仓库(Remote Repo) -``` - -- 工作区:正在编辑的文件; -- 暂存区:`git add` 选择本次 Commit 要包含的修改; -- 本地仓库:`git commit` 生成一次正式版本记录; -- 远程仓库:`git push` 上传到 Gitee 等平台。 - -### 10.2 初始配置 - -```bash -git config --global user.name "你的用户名" -git config --global user.email "你的邮箱" -git config --list # 查看配置 - -# SSH 免密 -ssh-keygen -t rsa -C "你的邮箱" # 生成密钥对 -cat ~/.ssh/id_rsa.pub # 查看公钥 → 添加到 Gitee -ssh -T git@gitee.com # 验证连接 -``` - -### 10.3 常用命令速查 - -**仓库初始化与克隆** - -| 命令 | 说明 | -| ----------------- | ------------------ | -| `git init` | 初始化本地仓库 | -| `git clone ` | 克隆远程仓库到本地 | - -**暂存与提交** - -| 命令 | 说明 | -| ------------------------------ | ---------------------------------- | -| `git status` | 查看工作区与暂存区状态 | -| `git add ` / `git add .` | 加入暂存区(`.` 含新增/修改/删除) | -| `git commit -m "msg"` | 提交到本地仓库 | -| `git commit -am "msg"` | 跳过 add,直接提交已跟踪文件的修改 | - -**远程仓库** - -| 命令 | 说明 | -| --------------------------------- | --------------------------- | -| `git remote -v` | 查看远程地址 | -| `git remote add origin ` | 关联远程仓库 | -| `git remote add upstream ` | 关联上游官方仓库 | -| `git remote set-url origin ` | 修改远程地址 | -| `git fetch` | 拉取远端更新(不合并) | -| `git pull` | 拉取并合并(fetch + merge) | -| `git push origin ` | 推送到远端 | - -**分支** - -| 命令 | 说明 | -| ------------------------------------------------ | ----------------------------------- | -| `git branch` | 查看本地分支 | -| `git branch ` | 创建分支 | -| `git branch -d/-D ` | 删除分支(-D 强制) | -| `git checkout ` | 切换分支 | -| `git checkout -b ` | 创建并切换(旧写法) | -| `git switch ` / `git switch -c ` | 切换 / 创建并切换(Git 2.23+ 推荐) | - -**查看日志与撤销** - -| 命令 | 说明 | -| ------------------------------------------ | -------------------------------------------------- | -| `git log --oneline` | 简洁提交历史 | -| `git diff` / `git diff --cached` | 查看工作区 / 暂存区差异 | -| `git restore ` | 丢弃工作区修改 | -| `git restore --staged ` | 取消暂存 | -| `git reset --soft/--mixed/--hard ` | 回退(soft 保留暂存与工作区;hard 丢弃全部,慎用) | -| `git revert ` | 新增提交来撤销某次提交(不改写历史,推荐) | -| `git stash` / `git stash pop` | 临时保存 / 恢复修改 | - -> **reset vs revert**:`reset` 改写历史,适合本地未推送的提交;`revert` 保留历史、新增撤销提交,适合已推送的提交。 - ---- - -## 十一、Fork + PR 开源协作流程 - -夏令营使用典型的 **Fork + Pull Request** 协作模式: - -```text -RT-Thread 官方仓库 (upstream) - │ Fork(Gitee 平台操作) - ▼ -个人 Gitee Fork (origin) - │ Clone - ▼ -本地仓库 - │ git add → git commit → git push - ▼ -个人 Fork 功能分支 - │ Pull Request - ▼ -官方仓库 → Review → Merge -``` - -### 11.1 origin 与 upstream - -| 名称 | 含义 | -| ---------- | ---------------- | -| `origin` | 自己的 Fork 仓库 | -| `upstream` | 官方原始仓库 | - -平时 `push → origin`,官方更新时 `git fetch upstream` 获取上游最新状态。 - -### 11.2 标准 PR 工作流(8 步) - -1. Gitee 上 Fork 官方仓库到个人账号,并同步上游; -2. `git switch master` + `git pull origin master`:切回主分支并拉取最新; -3. `git checkout -b day1`:新建当天分支(严禁直接改 master); -4. 修改 / 新增文件(建 `笔记/`、`作业/` 目录,写 Markdown); -5. `git add .`:加入暂存区; -6. `git commit -m "说明"`:本地提交; -7. `git push origin day1`:推送到个人 Fork 的 day1 分支; -8. Gitee 网页创建 PR(`你的fork:day1 → 上游:master`),指定 Reviewer。 - -- **审核职责**:组员 PR 由小组长审核合并,组长 PR 由导师复核。 -- **PR 拆分建议**:一次 PR 尽量只解决一个明确问题。Day1 通常拆两次——第一次只建个人目录模板,第二次再交笔记 + 作业。 - -### 11.3 冲突解决(Merge 与 Rebase) - -多人修改同一文件的同一位置时产生冲突,冲突标记如下: - -```text -<<<<<<< HEAD -当前分支内容 -======= -另一侧内容 ->>>>>>> branch -``` - -解决流程:阅读两侧修改 → 决定最终内容 → 删除冲突标记 → 保存 → `git add` → `git commit`(merge)或 `git rebase --continue`(rebase)。 - -- **Merge**(`git merge master`):保留分支合并关系; -- **Rebase**(`git rebase master`):把当前分支提交"重放"到新基线上,历史更线性;因为改写历史,已共享分支慎用。 - -```text -原来: A---B---C - \ - D---E -Rebase 后: A---B---C---D'---E' -``` - ---- - -## 十二、易混概念辨析 - -| 易混点 | 区别 | -| --------------------------- | ----------------------------------------- | -| `Commit` vs `Push` | Commit 是 本地保存;Push 才是 上传远程 | -| `Fetch` vs `Pull` | Fetch 只取不合并;Pull = fetch + merge | -| `Fork` vs `Clone` | Fork 是 远程→远程;Clone 是 远程→本地 | -| `origin` vs `upstream` | origin = 自己的 Fork;upstream = 官方仓库 | -| `add` vs `commit` vs `push` | 暂存 ≠ 提交 ≠ 推送,三个阶段 | - ---- - -## 十三、常见问题与踩坑记录 - -| 问题 | 处理 | -| ----------------------------------------------- | ------------------------------------------------------------ | -| ST-Link 固件版本过低、烧录报错 | 用 ST 官方固件升级工具更新固件(可多换几个工具尝试) | -| 调试结束后串口出现 `hard fault on thread: main` | 冷启动恢复;若复现,用 `pc`/`lr` 结合 `.map`/调用栈定位,检查栈余量、大数组、空指针、越界、复位方式 | -| Git 不跟踪空文件夹 | 空目录放 `.gitkeep` 或真实文件(如 README)才能提交 | -| `git push` 提示 `Everything up-to-date` | 没有新提交,先 `git add` 再 `git commit` | -| 命令报 `command not found` | 所有命令以 `git` 开头(`git commit` 不是 `commit`),注意 `checkout` 拼写 | -| Windows 带空格路径 `cd` 报 `too many arguments` | 盘符 `D:\` 写成 `/d/`,路径加双引号 `cd "/d/xxx"` | -| Gitee 禁止命令行暴露邮箱 | 设置里关闭「禁止命令行推送暴露个人邮箱」,或跳过邮箱配置 | -| 私有仓库 401/认证失败 | Gitee 私人令牌过期,重新生成;或改用 openssl 后端 + 令牌 | -| LF/CRLF 换行警告 | 仓库根目录放 `.gitattributes`(内容 `* text=auto`) | -| `fatal: not a git repository` | 当前目录不是仓库,进入项目根目录或 `git init` | -| `remote origin already exists` | 已绑定,用 `git remote set-url origin ` 修改 | - ---- - -## 十四、Day1 总结与待办 - -### 今日收获 - -1. 理解了 RTOS 为什么需要线程和调度,以及 Thread/Scheduler/Tick/IPC 等基本概念; -2. 掌握了 Nano / Standard / Smart 三条产品线的区别; -3. 建立了「Application → Component → Kernel → Driver → Hardware」的分层认识; -4. 完成 STM32F407 (spark) 和 QEMU 工程从创建、编译、下载到调试的完整流程; -5. 理解了 RT-Thread 启动流程与 `main()` 在线程体系中的位置; -6. 掌握了 FinSH / MSH 在 RTOS 调试中的作用; -7. 掌握了 Markdown 基础语法与相对路径管理资源的方法; -8. 理清了 Git 四大区域数据流与 Fork-PR 协作两条主线。 - -### 待办 / 后续重点 - -- [ ] 对照源码继续梳理启动流程与线程调度; -- [ ] 深入理解 `INIT_*_EXPORT` 宏背后的链接段机制; -- [ ] 结合 `.map` 与调用栈练习 HardFault 定位; -- [ ] 保持每日「笔记 + 作业」独立分支、独立 PR 的提交习惯。 - ---- - -*本文档为 Day1 个人学习笔记,内容基于当日课程与实操整理。* \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/README.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/README.md" deleted file mode 100644 index 908ec27974976f5cad62421c9f1564b40261d1ca..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,3 +0,0 @@ -# 作业 - -本目录保存每日作业。 diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_board_powered_green.jpg" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_board_powered_green.jpg" deleted file mode 100644 index 25e3abae3022ccf53f6eb4ef81a2eec472afd3ba..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_board_powered_green.jpg" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_board_powered_green_alt.jpg" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_board_powered_green_alt.jpg" deleted file mode 100644 index 92649e55df921bd398ce32ed34b8227490938818..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_board_powered_green_alt.jpg" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_board_usb_overview.jpg" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_board_usb_overview.jpg" deleted file mode 100644 index 84fca7828c07e516cb542c366732dcbb02d1f5ec..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_board_usb_overview.jpg" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_build_success.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_build_success.png" deleted file mode 100644 index f82e489466c9e1d70a5c787887e77dd52a705f25..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_build_success.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_com21_enumeration.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_com21_enumeration.png" deleted file mode 100644 index 7ec16380a8f9fee6e44ddc3b8a5ee09686f175cb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_com21_enumeration.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_download_success.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_download_success.png" deleted file mode 100644 index e55b2d40913f2f137f42905f53c16139aa6ad8a2..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_download_success.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_led1_manual_suspend.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_led1_manual_suspend.png" deleted file mode 100644 index 10e56059205f58b621798af0ef0bc811e5cea49e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_led1_manual_suspend.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_terminal_startup_and_threads.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_terminal_startup_and_threads.png" deleted file mode 100644 index d9fecdb7b437321854ecf847baa7c4c5432e5e22..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day2_terminal_startup_and_threads.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_package_terminal.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_package_terminal.png" deleted file mode 100644 index 0af7f13442bdf04317ae39b7901eda02cabaa715..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_package_terminal.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_parking_terminal.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_parking_terminal.png" deleted file mode 100644 index ecafff48fcff9d9050f69171ab4291928bdd5def..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_parking_terminal.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ps_cleanup.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ps_cleanup.png" deleted file mode 100644 index d750eb5b88591db1f42e7367329effd2c99f6c91..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ps_cleanup.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_startup_terminal.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_startup_terminal.png" deleted file mode 100644 index 63c1840e841bf140546c0775c67a5964c260465c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_startup_terminal.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ticket_terminal.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ticket_terminal.png" deleted file mode 100644 index 5c31ea3a23bea8ca81a7e0052f2662dee19de655..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ticket_terminal.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/package_demo.c" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/package_demo.c" deleted file mode 100644 index 2207040e9db0d28c67ca6c9c2593606ce2cb46ed..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/package_demo.c" +++ /dev/null @@ -1,270 +0,0 @@ -/* Day3 exercise: two producers and one message-queue sorting consumer. */ -#include - -#define PACKAGE_PRODUCER_COUNT 2 -#define PACKAGE_COUNT_PER_THREAD 5 -#define PACKAGE_QUEUE_CAPACITY 5 -#define PACKAGE_STACK_SIZE 1024 - -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; - rt_uint8_t end; -}; - -#define PACKAGE_MSG_SIZE RT_ALIGN(sizeof(struct package_msg), RT_ALIGN_SIZE) - -struct package_producer -{ - rt_uint32_t base_id; - rt_uint8_t region; -}; - -static struct rt_messagequeue package_queue; -static struct rt_mutex package_stat_lock; -static ALIGN(RT_ALIGN_SIZE) rt_uint8_t package_pool[PACKAGE_MSG_SIZE * PACKAGE_QUEUE_CAPACITY]; -static struct rt_thread package_threads[PACKAGE_PRODUCER_COUNT + 1]; -static ALIGN(RT_ALIGN_SIZE) rt_uint8_t package_stacks[PACKAGE_PRODUCER_COUNT + 1][PACKAGE_STACK_SIZE]; -static struct package_producer package_producers[PACKAGE_PRODUCER_COUNT] = { - {1001, 1}, - {2001, 2}, -}; -static rt_uint32_t package_end_count; -static rt_uint32_t package_total; -static rt_uint32_t package_region_count[4]; -static rt_uint32_t package_total_weight; -static rt_uint32_t package_send_failed; -static rt_uint32_t package_finished; -static rt_uint32_t package_expected; -static rt_bool_t package_abort; -static rt_bool_t package_running; - -static const char *package_region_name(rt_uint8_t region) -{ - switch (region) - { - case 1: return "east"; - case 2: return "south"; - case 3: return "north"; - default: return "unknown"; - } -} - -static void package_cleanup(void) -{ - rt_mq_detach(&package_queue); - rt_mutex_detach(&package_stat_lock); - package_running = RT_FALSE; -} - -static void package_detach_threads(rt_uint32_t first, rt_uint32_t count) -{ - rt_uint32_t index; - - for (index = first; index < count; index++) - rt_thread_detach(&package_threads[index]); -} - -static void package_finish(void) -{ - rt_bool_t last = RT_FALSE; - - rt_mutex_take(&package_stat_lock, RT_WAITING_FOREVER); - package_finished++; - if (package_finished == package_expected) - last = RT_TRUE; - rt_mutex_release(&package_stat_lock); - - if (last) - { - package_cleanup(); - rt_kprintf("package_demo finished\n"); - } -} - -static rt_err_t package_send_retry(const struct package_msg *message) -{ - rt_err_t result; - - while (!package_abort) - { - result = rt_mq_send(&package_queue, message, sizeof(*message)); - if (result == RT_EOK) - return RT_EOK; - - rt_mutex_take(&package_stat_lock, RT_WAITING_FOREVER); - package_send_failed++; - rt_mutex_release(&package_stat_lock); - rt_kprintf("package send failed, retrying\n"); - rt_thread_mdelay(80); - } - - return -RT_ERROR; -} - -static void package_producer_entry(void *parameter) -{ - struct package_producer *producer = (struct package_producer *)parameter; - struct package_msg message; - rt_uint32_t index; - - for (index = 0; index < PACKAGE_COUNT_PER_THREAD; index++) - { - message.id = producer->base_id + index; - message.region = producer->region; - message.weight = (rt_uint16_t)(700 + producer->region * 100 + index * 30); - message.end = 0; - if (package_send_retry(&message) != RT_EOK) - break; - rt_kprintf("producer %u sent package %u\n", - producer->base_id, message.id); - rt_thread_mdelay(5); - } - - message.id = 0; - message.region = 0; - message.weight = 0; - message.end = 1; - package_send_retry(&message); - package_finish(); -} - -static void package_consumer_entry(void *parameter) -{ - struct package_msg message; - rt_err_t result; - - (void)parameter; - while (!package_abort && package_end_count < PACKAGE_PRODUCER_COUNT) - { - result = rt_mq_recv(&package_queue, &message, sizeof(message), - RT_TICK_PER_SECOND); - if (result != RT_EOK) - { - if (result != -RT_ETIMEOUT) - rt_kprintf("package receive failed: %d\n", result); - continue; - } - - if (message.end) - { - package_end_count++; - rt_kprintf("producer end received: %u/%u\n", - package_end_count, PACKAGE_PRODUCER_COUNT); - continue; - } - - package_total++; - package_region_count[message.region]++; - package_total_weight += message.weight; - rt_kprintf("package %u -> %s, weight: %u g\n", - message.id, package_region_name(message.region), - message.weight); - rt_thread_mdelay(50); - } - - rt_kprintf("total packages: %u\n", package_total); - rt_kprintf("east: %u, south: %u, north: %u\n", - package_region_count[1], package_region_count[2], - package_region_count[3]); - rt_kprintf("total weight: %u g\n", package_total_weight); - rt_mutex_take(&package_stat_lock, RT_WAITING_FOREVER); - rt_kprintf("send failed: %u\n", package_send_failed); - rt_mutex_release(&package_stat_lock); - package_finish(); -} - -int package_demo(void) -{ - rt_err_t result; - rt_uint32_t index; - - if (package_running) - { - rt_kprintf("package_demo is already running\n"); - return -RT_EBUSY; - } - - result = rt_mq_init(&package_queue, "pkgmq", package_pool, - PACKAGE_MSG_SIZE, sizeof(package_pool), - RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - rt_kprintf("package queue init failed: %d\n", result); - return result; - } - - result = rt_mutex_init(&package_stat_lock, "pkglk", RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - rt_mq_detach(&package_queue); - rt_kprintf("package mutex init failed: %d\n", result); - return result; - } - - package_end_count = 0; - package_total = 0; - package_region_count[1] = 0; - package_region_count[2] = 0; - package_region_count[3] = 0; - package_total_weight = 0; - package_send_failed = 0; - package_finished = 0; - package_expected = PACKAGE_PRODUCER_COUNT + 1; - package_abort = RT_FALSE; - package_running = RT_TRUE; - - for (index = 0; index < PACKAGE_PRODUCER_COUNT; index++) - { - result = rt_thread_init(&package_threads[index], "producer", - package_producer_entry, &package_producers[index], - package_stacks[index], PACKAGE_STACK_SIZE, - 18, 5); - if (result != RT_EOK) - { - package_detach_threads(0, index); - package_cleanup(); - rt_kprintf("producer %u init failed: %d\n", index + 1, result); - return result; - } - } - - result = rt_thread_init(&package_threads[PACKAGE_PRODUCER_COUNT], - "sorter", package_consumer_entry, RT_NULL, - package_stacks[PACKAGE_PRODUCER_COUNT], - PACKAGE_STACK_SIZE, 16, 5); - if (result != RT_EOK) - { - package_detach_threads(0, PACKAGE_PRODUCER_COUNT); - package_cleanup(); - rt_kprintf("sorter init failed: %d\n", result); - return result; - } - - for (index = 0; index < PACKAGE_PRODUCER_COUNT + 1; index++) - { - result = rt_thread_startup(&package_threads[index]); - if (result != RT_EOK) - { - rt_bool_t cleanup_now; - - rt_mutex_take(&package_stat_lock, RT_WAITING_FOREVER); - package_abort = RT_TRUE; - package_expected = index; - cleanup_now = (package_finished == package_expected); - rt_mutex_release(&package_stat_lock); - package_detach_threads(index, PACKAGE_PRODUCER_COUNT + 1); - if (cleanup_now) - package_cleanup(); - rt_kprintf("package thread %u startup failed: %d\n", index, result); - return result; - } - } - - rt_kprintf("package_demo started: queue=%u, producers=%u\n", - PACKAGE_QUEUE_CAPACITY, PACKAGE_PRODUCER_COUNT); - return RT_EOK; -} -MSH_CMD_EXPORT(package_demo, message queue package demo); diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/parking_demo.c" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/parking_demo.c" deleted file mode 100644 index cda1f876e3f01e56bf3934dc3825076bdbb44baf..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/parking_demo.c" +++ /dev/null @@ -1,175 +0,0 @@ -/* Day3 exercise: counting semaphore as a parking-space resource limit. */ -#include - -#define PARKING_CAR_COUNT 5 -#define PARKING_SPACE_COUNT 3 -#define PARKING_TIMEOUT_TICKS (2 * RT_TICK_PER_SECOND) -#define PARKING_STACK_SIZE 1024 - -struct parking_car -{ - rt_uint32_t id; - rt_uint32_t stay_ms; -}; - -static struct rt_semaphore parking_spaces; -static struct rt_mutex parking_stat_lock; -static struct rt_thread parking_threads[PARKING_CAR_COUNT]; -static ALIGN(RT_ALIGN_SIZE) rt_uint8_t parking_stacks[PARKING_CAR_COUNT][PARKING_STACK_SIZE]; -static struct parking_car parking_cars[PARKING_CAR_COUNT]; -static rt_uint32_t parking_used; -static rt_uint32_t parking_finished; -static rt_uint32_t parking_expected; -static rt_bool_t parking_abort; -static rt_bool_t parking_running; - -static void parking_cleanup(void) -{ - rt_sem_detach(&parking_spaces); - rt_mutex_detach(&parking_stat_lock); - parking_running = RT_FALSE; -} - -static void parking_detach_threads(rt_uint32_t first, rt_uint32_t count) -{ - rt_uint32_t index; - - for (index = first; index < count; index++) - rt_thread_detach(&parking_threads[index]); -} - -static void parking_finish(void) -{ - rt_bool_t last = RT_FALSE; - - rt_mutex_take(&parking_stat_lock, RT_WAITING_FOREVER); - parking_finished++; - if (parking_finished == parking_expected) - last = RT_TRUE; - rt_mutex_release(&parking_stat_lock); - - if (last) - { - parking_cleanup(); - rt_kprintf("parking_demo finished\n"); - } -} - -static void parking_car_entry(void *parameter) -{ - struct parking_car *car = (struct parking_car *)parameter; - rt_err_t result; - - while (!parking_abort) - { - result = rt_sem_take(&parking_spaces, PARKING_TIMEOUT_TICKS); - if (result == RT_EOK) - { - if (parking_abort) - { - rt_sem_release(&parking_spaces); - break; - } - - rt_mutex_take(&parking_stat_lock, RT_WAITING_FOREVER); - parking_used++; - rt_kprintf("car%u entered, used spaces: %u\n", - car->id, parking_used); - rt_mutex_release(&parking_stat_lock); - - rt_thread_mdelay(car->stay_ms); - - rt_mutex_take(&parking_stat_lock, RT_WAITING_FOREVER); - parking_used--; - rt_kprintf("car%u left, used spaces: %u\n", - car->id, parking_used); - rt_mutex_release(&parking_stat_lock); - rt_sem_release(&parking_spaces); - break; - } - - if (parking_abort) - break; - - rt_kprintf("car%u waiting for a space\n", car->id); - rt_thread_mdelay(300); - } - - parking_finish(); -} - -int parking_demo(void) -{ - rt_err_t result; - rt_uint32_t index; - - if (parking_running) - { - rt_kprintf("parking_demo is already running\n"); - return -RT_EBUSY; - } - - result = rt_sem_init(&parking_spaces, "parksem", - PARKING_SPACE_COUNT, RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - rt_kprintf("parking semaphore init failed: %d\n", result); - return result; - } - - result = rt_mutex_init(&parking_stat_lock, "parklock", RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - rt_sem_detach(&parking_spaces); - rt_kprintf("parking mutex init failed: %d\n", result); - return result; - } - - parking_used = 0; - parking_finished = 0; - parking_expected = PARKING_CAR_COUNT; - parking_abort = RT_FALSE; - parking_running = RT_TRUE; - - for (index = 0; index < PARKING_CAR_COUNT; index++) - { - parking_cars[index].id = index + 1; - parking_cars[index].stay_ms = 700 + index * 180; - result = rt_thread_init(&parking_threads[index], "car", - parking_car_entry, &parking_cars[index], - parking_stacks[index], PARKING_STACK_SIZE, - 18, 5); - if (result != RT_EOK) - { - parking_detach_threads(0, index); - parking_cleanup(); - rt_kprintf("car%u thread init failed: %d\n", index + 1, result); - return result; - } - } - - for (index = 0; index < PARKING_CAR_COUNT; index++) - { - result = rt_thread_startup(&parking_threads[index]); - if (result != RT_EOK) - { - rt_bool_t cleanup_now; - - rt_mutex_take(&parking_stat_lock, RT_WAITING_FOREVER); - parking_abort = RT_TRUE; - parking_expected = index; - cleanup_now = (parking_finished == parking_expected); - rt_mutex_release(&parking_stat_lock); - parking_detach_threads(index, PARKING_CAR_COUNT); - if (cleanup_now) - parking_cleanup(); - rt_kprintf("car%u thread startup failed: %d\n", index + 1, result); - return result; - } - } - - rt_kprintf("parking_demo started: spaces=%u, cars=%u\n", - PARKING_SPACE_COUNT, PARKING_CAR_COUNT); - return RT_EOK; -} -MSH_CMD_EXPORT(parking_demo, counting semaphore parking demo); diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/startup_demo.c" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/startup_demo.c" deleted file mode 100644 index e9dddd03c0a3a4e6fa3e34119148cc265ccf4509..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/startup_demo.c" +++ /dev/null @@ -1,224 +0,0 @@ -/* Day3 exercise: event-set startup barrier with a bounded first wait. */ -#include - -#define EVENT_NET_READY (1u << 0) -#define EVENT_SENSOR_READY (1u << 1) -#define EVENT_STORAGE_READY (1u << 2) -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) -#define STARTUP_THREAD_COUNT 4 -#define STARTUP_STACK_SIZE 1024 - -struct startup_module -{ - const char *name; - rt_uint32_t event_bit; - rt_uint32_t delay_ms; -}; - -static struct rt_event startup_event; -static struct rt_mutex startup_lock; -static struct rt_thread startup_threads[STARTUP_THREAD_COUNT]; -static ALIGN(RT_ALIGN_SIZE) rt_uint8_t startup_stacks[STARTUP_THREAD_COUNT][STARTUP_STACK_SIZE]; -static struct startup_module startup_modules[3] = { - {"network", EVENT_NET_READY, 700}, - {"sensor", EVENT_SENSOR_READY, 1400}, - {"storage", EVENT_STORAGE_READY, 5000}, -}; -static rt_uint32_t startup_ready_mask; -static rt_uint32_t startup_finished; -static rt_uint32_t startup_expected; -static rt_bool_t startup_abort; -static rt_bool_t startup_running; - -static const char *startup_missing_name(rt_uint32_t bit) -{ - if ((bit & EVENT_NET_READY) != 0) - return "network"; - if ((bit & EVENT_SENSOR_READY) != 0) - return "sensor"; - if ((bit & EVENT_STORAGE_READY) != 0) - return "storage"; - return "none"; -} - -static void startup_cleanup(void) -{ - rt_event_detach(&startup_event); - rt_mutex_detach(&startup_lock); - startup_running = RT_FALSE; -} - -static void startup_detach_threads(rt_uint32_t first, rt_uint32_t count) -{ - rt_uint32_t index; - - for (index = first; index < count; index++) - rt_thread_detach(&startup_threads[index]); -} - -static void startup_finish(void) -{ - rt_bool_t last = RT_FALSE; - - rt_mutex_take(&startup_lock, RT_WAITING_FOREVER); - startup_finished++; - if (startup_finished == startup_expected) - last = RT_TRUE; - rt_mutex_release(&startup_lock); - - if (last) - { - startup_cleanup(); - rt_kprintf("startup_demo finished\n"); - } -} - -static void startup_module_entry(void *parameter) -{ - struct startup_module *module = (struct startup_module *)parameter; - - rt_thread_mdelay(module->delay_ms); - if (startup_abort) - { - startup_finish(); - return; - } - - rt_mutex_take(&startup_lock, RT_WAITING_FOREVER); - startup_ready_mask |= module->event_bit; - rt_mutex_release(&startup_lock); - rt_kprintf("%s ready\n", module->name); - rt_event_send(&startup_event, module->event_bit); - startup_finish(); -} - -static void startup_business_entry(void *parameter) -{ - rt_uint32_t recved = 0; - rt_uint32_t ready_mask; - rt_err_t result; - - (void)parameter; - if (startup_abort) - goto finish; - - result = rt_event_recv(&startup_event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - 3 * RT_TICK_PER_SECOND, &recved); - if (result == -RT_ETIMEOUT) - { - rt_mutex_take(&startup_lock, RT_WAITING_FOREVER); - ready_mask = startup_ready_mask; - rt_mutex_release(&startup_lock); - rt_kprintf("wait modules timeout, %s is not ready\n", - startup_missing_name(EVENT_ALL_READY & ~ready_mask)); - } - else if (result != RT_EOK) - { - rt_kprintf("startup event wait failed: %d\n", result); - } - - if (startup_abort) - goto finish; - - result = rt_event_recv(&startup_event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_WAITING_FOREVER, &recved); - if (result == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - } - else - { - rt_kprintf("startup final wait failed: %d\n", result); - } - -finish: - startup_finish(); -} - -int startup_demo(void) -{ - rt_err_t result; - rt_uint32_t index; - - if (startup_running) - { - rt_kprintf("startup_demo is already running\n"); - return -RT_EBUSY; - } - - result = rt_event_init(&startup_event, "stup_evt", RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - rt_kprintf("startup event init failed: %d\n", result); - return result; - } - result = rt_mutex_init(&startup_lock, "stup_lk", RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - rt_event_detach(&startup_event); - rt_kprintf("startup mutex init failed: %d\n", result); - return result; - } - - startup_ready_mask = 0; - startup_finished = 0; - startup_expected = STARTUP_THREAD_COUNT; - startup_abort = RT_FALSE; - startup_running = RT_TRUE; - - for (index = 0; index < 3; index++) - { - result = rt_thread_init(&startup_threads[index], "initmod", - startup_module_entry, &startup_modules[index], - startup_stacks[index], STARTUP_STACK_SIZE, - 17, 5); - if (result != RT_EOK) - { - startup_detach_threads(0, index); - startup_cleanup(); - rt_kprintf("startup module thread init failed: %d\n", result); - return result; - } - } - - result = rt_thread_init(&startup_threads[3], "business", - startup_business_entry, RT_NULL, - startup_stacks[3], STARTUP_STACK_SIZE, - 16, 5); - if (result != RT_EOK) - { - startup_detach_threads(0, 3); - startup_cleanup(); - rt_kprintf("business thread init failed: %d\n", result); - return result; - } - - for (index = 0; index < STARTUP_THREAD_COUNT; index++) - { - result = rt_thread_startup(&startup_threads[index]); - if (result != RT_EOK) - { - rt_bool_t cleanup_now; - - rt_mutex_take(&startup_lock, RT_WAITING_FOREVER); - startup_abort = RT_TRUE; - startup_expected = index; - cleanup_now = (startup_finished == startup_expected); - rt_mutex_release(&startup_lock); - startup_detach_threads(index, STARTUP_THREAD_COUNT); - if (cleanup_now) - startup_cleanup(); - else - rt_event_send(&startup_event, EVENT_ALL_READY); - rt_kprintf("startup thread %u startup failed: %d\n", index, result); - return result; - } - } - - rt_kprintf("startup_demo started\n"); - return RT_EOK; -} -MSH_CMD_EXPORT(startup_demo, event startup demo); diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 1ff498b3c92e35dcd8b0b7969c3d1edef513ce2c..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Day2 RT-Thread thread study. - * - * Board mapping from the Spark-1 schematic: - * GPIO_LED_R -> PF12 - * GPIO_LED_B -> PF11 - * KEY0 -> PC0, active low - */ - -#include -#include -#include -#include - -#define DBG_TAG "thread_study" -#define DBG_LVL DBG_LOG -#include - -#define DAY2_LED_R_PIN GET_PIN(F, 12) -#define DAY2_LED_B_PIN GET_PIN(F, 11) -#define DAY2_KEY_PIN GET_PIN(C, 0) -#define DAY2_KEY_ACTIVE PIN_LOW - -#define LED_THREAD_STACK_SIZE 1024 -#define LED_THREAD_PRIORITY 16 -#define LED_THREAD_TIMESLICE 5 -#define MONITOR_THREAD_PRIORITY 10 -#define MONITOR_THREAD_STACK_SIZE 1024 -#define MONITOR_THREAD_TIMESLICE 5 -#define KEY_SCAN_PERIOD_MS 20 -#define MONITOR_PERIOD_TICKS (2 * RT_TICK_PER_SECOND) - -static rt_thread_t led1_thread; -static rt_thread_t monitor_thread; -static struct rt_thread led2_thread; -static ALIGN(RT_ALIGN_SIZE) rt_uint8_t led2_stack[LED_THREAD_STACK_SIZE]; -static struct rt_semaphore led1_resume_sem; -static volatile rt_bool_t led1_paused; - -static const char *thread_state_name(rt_thread_t thread) -{ - switch (thread->stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: - return "init"; - case RT_THREAD_READY: - return "ready"; - case RT_THREAD_RUNNING: - return "running"; - case RT_THREAD_SUSPEND: - return "suspend"; - case RT_THREAD_CLOSE: - return "close"; - default: - return "unknown"; - } -} - -static rt_uint32_t thread_stack_remaining(rt_thread_t thread) -{ - rt_uint8_t *base = (rt_uint8_t *)thread->stack_addr; - rt_uint8_t *ptr = base; - rt_uint8_t *end = base + thread->stack_size; - - while ((ptr < end) && (*ptr == '#')) - { - ptr++; - } - - return (rt_uint32_t)(ptr - base); -} - -static const char *led1_control_state(void) -{ - return led1_paused ? "manual_suspend" : "periodic_running"; -} - -static void led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - rt_err_t result; - - (void)parameter; - - while (1) - { - while (led1_paused) - { - result = rt_sem_take(&led1_resume_sem, RT_WAITING_FOREVER); - if (result != RT_EOK) - { - LOG_E("wait led1 resume semaphore failed: %d", result); - } - } - - rt_pin_write(DAY2_LED_R_PIN, !rt_pin_read(DAY2_LED_R_PIN)); - /* LOG_I("th_led1 count=%u tick=%u", count++, rt_tick_get()); */ - count++; - rt_thread_mdelay(500); - } -} - -static void led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - - (void)parameter; - - while (1) - { - rt_pin_write(DAY2_LED_B_PIN, !rt_pin_read(DAY2_LED_B_PIN)); - /* LOG_I("th_led2 count=%u tick=%u", count++, rt_tick_get()); */ - count++; - rt_thread_mdelay(700); - } -} - -static void print_thread_info(const char *label, rt_thread_t thread) -{ - if (thread == RT_NULL) - { - rt_kprintf("%s: unavailable\n", label); - return; - } - - rt_kprintf("%s name=%s state=%s priority=%u remaining_stack=%u/%u\n", - label, - thread->name, - thread_state_name(thread), - thread->current_priority, - thread_stack_remaining(thread), - thread->stack_size); -} - -static void thread_dump(void) -{ - print_thread_info("led1", led1_thread); - print_thread_info("led2", &led2_thread); - print_thread_info("monitor", monitor_thread); - rt_kprintf("led1 control=%s\n", led1_control_state()); -} -MSH_CMD_EXPORT(thread_dump, show Day2 thread status); - -static void monitor_entry(void *parameter) -{ - rt_tick_t last_report = rt_tick_get(); - rt_bool_t key_armed = RT_TRUE; - - (void)parameter; - - while (1) - { - rt_tick_t now = rt_tick_get(); - - if ((now - last_report) >= MONITOR_PERIOD_TICKS) - { - last_report = now; - /* 保留 thread_dump 命令输出,关闭监控线程自动滚屏。 */ - } - - if (rt_pin_read(DAY2_KEY_PIN) == DAY2_KEY_ACTIVE) - { - if (key_armed) - { - rt_thread_mdelay(KEY_SCAN_PERIOD_MS); - if (rt_pin_read(DAY2_KEY_PIN) == DAY2_KEY_ACTIVE) - { - rt_err_t result; - - if (led1_paused) - { - result = rt_sem_release(&led1_resume_sem); - if (result == RT_EOK) - { - led1_paused = RT_FALSE; - rt_kprintf("led1 control=%s\n", led1_control_state()); - } - } - else - { - /* 清理可能残留的唤醒令牌,避免旧令牌误放行。 */ - while (rt_sem_trytake(&led1_resume_sem) == RT_EOK) - { - } - - led1_paused = RT_TRUE; - result = RT_EOK; - rt_kprintf("led1 control=%s\n", led1_control_state()); - } - - if (result != RT_EOK) - { - LOG_E("th_led1 control failed: %d", result); - } - key_armed = RT_FALSE; - } - } - } - else - { - key_armed = RT_TRUE; - } - - rt_thread_mdelay(KEY_SCAN_PERIOD_MS); - } -} - -static int thread_study_init(void) -{ - rt_err_t result; - - rt_pin_mode(DAY2_LED_R_PIN, PIN_MODE_OUTPUT); - rt_pin_mode(DAY2_LED_B_PIN, PIN_MODE_OUTPUT); - rt_pin_write(DAY2_LED_R_PIN, PIN_HIGH); - rt_pin_write(DAY2_LED_B_PIN, PIN_HIGH); - rt_pin_mode(DAY2_KEY_PIN, PIN_MODE_INPUT_PULLUP); - - result = rt_sem_init(&led1_resume_sem, "led1sem", 0, RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - LOG_E("init led1 semaphore failed: %d", result); - return result; - } - - led1_thread = rt_thread_create("th_led1", led1_entry, RT_NULL, - LED_THREAD_STACK_SIZE, - LED_THREAD_PRIORITY, - LED_THREAD_TIMESLICE); - if (led1_thread == RT_NULL) - { - LOG_E("create th_led1 failed"); - return -RT_ENOMEM; - } - rt_thread_startup(led1_thread); - - result = rt_thread_init(&led2_thread, "th_led2", led2_entry, RT_NULL, - led2_stack, sizeof(led2_stack), - LED_THREAD_PRIORITY, LED_THREAD_TIMESLICE); - if (result != RT_EOK) - { - LOG_E("init th_led2 failed: %d", result); - return result; - } - rt_thread_startup(&led2_thread); - - monitor_thread = rt_thread_create("th_monitor", monitor_entry, RT_NULL, - MONITOR_THREAD_STACK_SIZE, - MONITOR_THREAD_PRIORITY, - MONITOR_THREAD_TIMESLICE); - if (monitor_thread == RT_NULL) - { - LOG_E("create th_monitor failed"); - return -RT_ENOMEM; - } - rt_thread_startup(monitor_thread); - - /* LOG_I("Day2 thread study initialized"); */ - return RT_EOK; -} -INIT_APP_EXPORT(thread_study_init); diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/ticket_demo.c" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/ticket_demo.c" deleted file mode 100644 index edc5708c048c078a1f999762c33677e706bdc2f7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/ticket_demo.c" +++ /dev/null @@ -1,153 +0,0 @@ -/* Day3 exercise: mutex-protected ticket transactions. */ -#include - -#define TICKET_WINDOW_COUNT 2 -#define TICKET_TOTAL 20 -#define TICKET_STACK_SIZE 1024 - -struct ticket_window -{ - char name; -}; - -static struct rt_mutex ticket_lock; -static struct rt_thread ticket_threads[TICKET_WINDOW_COUNT]; -static ALIGN(RT_ALIGN_SIZE) rt_uint8_t ticket_stacks[TICKET_WINDOW_COUNT][TICKET_STACK_SIZE]; -static struct ticket_window ticket_windows[TICKET_WINDOW_COUNT] = {{'A'}, {'B'}}; -static rt_uint8_t ticket_sold[TICKET_TOTAL + 1]; -static rt_int32_t ticket_count; -static rt_uint32_t ticket_finished; -static rt_uint32_t ticket_expected; -static rt_bool_t ticket_abort; -static rt_bool_t ticket_running; - -static void ticket_cleanup(void) -{ - rt_mutex_detach(&ticket_lock); - ticket_running = RT_FALSE; -} - -static void ticket_detach_threads(rt_uint32_t first, rt_uint32_t count) -{ - rt_uint32_t index; - - for (index = first; index < count; index++) - rt_thread_detach(&ticket_threads[index]); -} - -static void ticket_finish(void) -{ - rt_bool_t last = RT_FALSE; - - rt_mutex_take(&ticket_lock, RT_WAITING_FOREVER); - ticket_finished++; - if (ticket_finished == ticket_expected) - last = RT_TRUE; - rt_mutex_release(&ticket_lock); - - if (last) - { - ticket_cleanup(); - rt_kprintf("ticket_demo finished: remaining=%d\n", ticket_count); - } -} - -static void ticket_window_entry(void *parameter) -{ - struct ticket_window *window = (struct ticket_window *)parameter; - rt_int32_t ticket_no; - - while (!ticket_abort) - { - rt_mutex_take(&ticket_lock, RT_WAITING_FOREVER); - if (ticket_abort) - { - rt_mutex_release(&ticket_lock); - break; - } - - if (ticket_count <= 0) - { - rt_mutex_release(&ticket_lock); - break; - } - - /* Check, allocate, record, and decrement form one transaction. */ - ticket_no = ticket_count; - ticket_sold[ticket_no]++; - ticket_count--; - rt_kprintf("window %c sold ticket %d\n", window->name, ticket_no); - rt_mutex_release(&ticket_lock); - rt_thread_mdelay(30); - } - - ticket_finish(); -} - -int ticket_demo(void) -{ - rt_err_t result; - rt_uint32_t index; - - if (ticket_running) - { - rt_kprintf("ticket_demo is already running\n"); - return -RT_EBUSY; - } - - result = rt_mutex_init(&ticket_lock, "ticketlk", RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - rt_kprintf("ticket mutex init failed: %d\n", result); - return result; - } - - rt_memset(ticket_sold, 0, sizeof(ticket_sold)); - ticket_count = TICKET_TOTAL; - ticket_finished = 0; - ticket_expected = TICKET_WINDOW_COUNT; - ticket_abort = RT_FALSE; - ticket_running = RT_TRUE; - - for (index = 0; index < TICKET_WINDOW_COUNT; index++) - { - result = rt_thread_init(&ticket_threads[index], "window", - ticket_window_entry, &ticket_windows[index], - ticket_stacks[index], TICKET_STACK_SIZE, - 19, 5); - if (result != RT_EOK) - { - ticket_detach_threads(0, index); - ticket_cleanup(); - rt_kprintf("window %c thread init failed: %d\n", - ticket_windows[index].name, result); - return result; - } - } - - for (index = 0; index < TICKET_WINDOW_COUNT; index++) - { - result = rt_thread_startup(&ticket_threads[index]); - if (result != RT_EOK) - { - rt_bool_t cleanup_now; - - rt_mutex_take(&ticket_lock, RT_WAITING_FOREVER); - ticket_abort = RT_TRUE; - ticket_expected = index; - cleanup_now = (ticket_finished == ticket_expected); - rt_mutex_release(&ticket_lock); - ticket_detach_threads(index, TICKET_WINDOW_COUNT); - if (cleanup_now) - ticket_cleanup(); - rt_kprintf("window %c thread startup failed: %d\n", - ticket_windows[index].name, result); - return result; - } - } - - rt_kprintf("ticket_demo started: total=%d, windows=%u\n", - TICKET_TOTAL, TICKET_WINDOW_COUNT); - return RT_EOK; -} -MSH_CMD_EXPORT(ticket_demo, mutex ticket demo); diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 76a76dd2aaf489c487837f2869814fe84618c758..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,20 +0,0 @@ -# 第一天作业 - -## 完成内容 - -- 使用个人 Fork 并完成与上游仓库的 SSH 连接。 -- 建立 `master`、`init-template-li-tingrong` 和 `day1-content-li-tingrong` 分支协作流程。 -- 通过 Pull Request 初始化个人笔记与作业目录模板。 -- 整理第一天学习笔记和作业内容,并推送到 Day1 功能分支。 - -## 提交结果 - -- 笔记路径:`2026/第4组/李庭荣/笔记/第一天笔记.md`。 -- 作业路径:`2026/第4组/李庭荣/作业/第一天作业.md`。 -- 目录模板初始化 PR 已合并到上游 `master`。 -- Day1 内容已提交到 `day1-content-li-tingrong` 分支,提交为 `8dfc26a`;截至当前记录,Day1 内容 PR 的创建或合并状态仍需在 Gitee 确认。 - -## 学习收获 - -- 理解了 Git 的本地提交、Fork 远程推送、分支开发和 Pull Request 流程。 -- 掌握了使用 Markdown 记录学习内容并按统一目录管理每日笔记和作业的方法。 diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index ade8b08f75781e1acee9e24a9b77324a5123b2f0..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,224 +0,0 @@ -# 第三天作业:线程间同步与通信 - -## 一、作业目标 - -本日使用 RT-Thread V4.1.1 的四类 IPC 完成四个独立 MSH 实验:计数信号量、互斥量、事件集和消息队列。每个实验至少创建两个线程,检查 IPC 对象和线程初始化/启动返回值,并在结束后清理静态对象。 - -邮箱属于课程扩展内容,本次不作为必做实验提交。 - -## 二、提交源码 - -```text -parking_demo.c -> parking_demo -ticket_demo.c -> ticket_demo -startup_demo.c -> startup_demo -package_demo.c -> package_demo -``` - -工程权威源码位于: - -```text -D:\RT-ThreadStudio\workspace\RSOC2026_IPC\applications\ -``` - -本目录中的四个 C 文件是提交副本,保持与工程源码同步;构建产物不提交。 - -## 三、实验设计 - -### 1. `parking_demo` - -初始化值为 3 的计数信号量表示停车位,创建 5 个车辆线程。车辆以 2 秒 Tick 超时申请车位,获得资源后停留并释放。`parking_used` 由互斥量保护,保证显示的进入数量不会超过 3。 - -### 2. `ticket_demo` - -创建 A、B 两个售票线程,共售 20 张票。互斥量保护“检查余票、分配票号、记录票号、减少余票”的完整事务,而不是只保护减法操作。最终要求剩余票数为 0,且每个票号只出现一次。 - -### 3. `startup_demo` - -网络、传感器和存储线程分别发送三个事件位。业务线程先用 `RT_EVENT_FLAG_AND` 等待 3 秒,存储线程因延时 5 秒而触发一次超时;随后业务线程再次等待全部事件,输出业务启动信息。`startup_ready_mask` 用于解释超时原因,事件集本身只承担同步功能。 - -### 4. `package_demo` - -消息结构包含编号、区域、重量和结束标志。两个生产线程各发送 5 条快递消息,并各发送一条结束消息;分拣线程收到两条结束消息后统计总数、区域数量、总重量和发送失败次数。消息池按 `RT_ALIGN_SIZE` 对齐,队列容量为 5 条消息。 - -## 四、MSH 操作 - -固件启动后,在串口 `msh >` 中逐项执行: - -```text -help -parking_demo -ticket_demo -startup_demo -package_demo -``` - -每个命令应单独运行。上一个实验结束并完成对象脱离后再重复执行同一命令,避免同名线程或 IPC 对象残留。当前代码通过运行标志拒绝运行中的重复启动。 - -## 五、构建修改 - -`RSOC2026_IPC` 原工程只收集了 `main.c`,因此新增: - -```text -applications/SConscript -drivers/SConscript -libraries/SConscript -``` - -同时补充 `rtconfig.py` 的 Cortex-M4 编译参数、汇编参数、链接脚本和 BIN 后处理命令。`Debug/*` 是自动生成目录,不手工修改。 - -## 六、验证结果 - -已完成:四个 Day3 C 文件被 SCons 收集并编译,BSP、HAL、CMSIS、RT-Thread 内核和示例包编译通过,链接成功并生成 `rt-thread.elf`,同时生成 `rtthread.bin`。 - -已完成:用户在 RT-Thread Studio 中确认最新固件已经通过 ST-Link 下载并启动;串口终端已依次验证 `parking_demo`、`ticket_demo`、`startup_demo` 和 `package_demo`,并在最后使用 `ps` 检查实验线程已经退出。 - -本节以下内容只记录实际截图中出现的串口证据。构建成功、下载成功、串口命令成功和实体硬件行为属于不同证据层级,不能相互替代。 - -## 七、源码与运行逻辑对应 - -### 1. 停车场:计数信号量限制资源数量 - -源码:[`parking_demo.c`](parking_demo.c) - -初始化时使用静态信号量,初始计数为 3;另用互斥量保护 `parking_used`,因为信号量负责限制资源数量,不应该直接读取其内部计数作为业务统计: - -~~~ -result = rt_sem_init(&parking_spaces, "parksem", - PARKING_SPACE_COUNT, RT_IPC_FLAG_FIFO); -result = rt_sem_take(&parking_spaces, PARKING_TIMEOUT_TICKS); -~~~ - -每辆车最多等待 `2 * RT_TICK_PER_SECOND`。获取成功后先在互斥量保护下增加 `parking_used`,停车延时结束后再减少统计并释放信号量;获取超时则打印等待信息,延时 300 ms 后重试。5 个车辆线程全部调用 `parking_finish()`,最后一个线程负责脱离信号量和互斥量,并输出 `parking_demo finished`。 - -运行逻辑为:3 个车位先被占满,后续车辆等待;已有车辆离开并释放信号量后,等待车辆获得唤醒机会进入。截图中最大占用数为 3,5 辆车全部离场,最终占用数为 0。 - -### 2. 售票系统:互斥量保护完整事务 - -源码:[`ticket_demo.c`](ticket_demo.c) - -售票线程持有互斥量时,同时完成检查余票、取得票号、记录票号、减少余票和打印结果,避免两个窗口在检查和修改之间交错: - -~~~ -rt_mutex_take(&ticket_lock, RT_WAITING_FOREVER); -if (ticket_count <= 0) -{ - rt_mutex_release(&ticket_lock); - break; -} - -ticket_no = ticket_count; -ticket_sold[ticket_no]++; -ticket_count--; -rt_kprintf("window %c sold ticket %d\n", window->name, ticket_no); -rt_mutex_release(&ticket_lock); -~~~ - -A、B 两个窗口分别运行同一入口函数,共享 `ticket_count` 和 `ticket_sold[]`。票数变为 0 后两个线程退出,完成计数达到预期值的最后一个线程脱离互斥量并输出剩余票数。截图显示票号从 20 到 1,未出现重复号码,最终 `remaining=0`。 - -### 3. 启动条件:事件集实现 AND 屏障 - -源码:[`startup_demo.c`](startup_demo.c) - -网络、传感器和存储线程分别发送三个事件位,业务线程先进行 3 秒有界等待,再进行永久等待: - -~~~ -result = rt_event_recv(&startup_event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - 3 * RT_TICK_PER_SECOND, &recved); -~~~ - -模块延时分别为 700 ms、1400 ms 和 5000 ms,因此业务线程第一次等待会因为存储模块尚未完成而超时。超时后,代码读取受互斥量保护的 `startup_ready_mask`,明确打印未就绪模块;之后再次使用 `RT_EVENT_FLAG_AND` 永久等待全部事件。三个模块都发送事件后,业务线程输出 `all modules are ready` 和 `business task started`。 - -### 4. 快递分拣:消息队列传递结构化消息 - -源码:[`package_demo.c`](package_demo.c) - -消息结构同时携带编号、区域、重量和结束标志,消息池按照对齐后的单条消息大小分配 5 条容量: - -~~~ -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; - rt_uint8_t end; -}; - -#define PACKAGE_MSG_SIZE RT_ALIGN(sizeof(struct package_msg), RT_ALIGN_SIZE) -~~~ - -两个生产者分别生成 `1001~1005` 和 `2001~2005`。消息队列满时,`rt_mq_send()` 返回错误,代码在互斥量保护下增加 `package_send_failed`,打印重试信息并延时 80 ms,直到消息成功发送。每个生产者最后发送一条 `end=1` 的结束消息,分拣线程收到两条结束消息后才输出汇总并退出,因此不会遗漏另一个生产者的消息。 - -截图中队列容量为 5,出现 `package send failed, retrying` 说明生产速度暂时超过消费速度;重试最终成功,统计结果为 10 条消息、东部 5 条、南部 5 条、北部 0 条、总重量 9100 g。 - -## 八、串口运行截图相关讲解 - -### 1. 停车场运行 - -![parking_demo 串口运行结果](assets/day3_parking_terminal.png) - -图中先执行 `ps` 确认系统线程状态,再运行 `parking_demo`。`car1`、`car2`、`car3` 先占满 3 个车位;车辆离场释放信号量后,`car4`、`car5` 依次进入。整个过程的 `used spaces` 没有超过 3,最后输出 `parking_demo finished`。 - -### 2. 售票运行 - -![ticket_demo 串口运行结果](assets/day3_ticket_terminal.png) - -图中两个窗口交替取得互斥量,票号从 20 递减到 1,最终输出: - -~~~ -ticket_demo finished: remaining=0 -~~~ - -`ticket_demo started` 出现在前两条售票日志之后,是线程启动后立即获得调度造成的输出顺序差异,不影响互斥量保护的售票事务。 - -### 3. 启动条件运行 - -![startup_demo 串口运行结果](assets/day3_startup_terminal.png) - -图中先出现 `network ready` 和 `sensor ready`,3 秒有界等待到期后打印 `storage is not ready`;5 秒延时结束后存储线程发送事件,业务线程收到全部事件并输出 `all modules are ready`、`business task started`,最后实验完成。 - -### 4. 消息队列运行 - -![package_demo 串口运行结果](assets/day3_package_terminal.png) - -图中可以看到两个生产者发送 10 条消息,分拣线程按区域输出消息内容。由于队列容量只有 5,运行中出现 9 次发送失败重试;这不是消息丢失,而是非阻塞 `rt_mq_send()` 在队列满时返回错误后的应用层重试。最终输出: - -~~~ -producer end received: 2/2 -total packages: 10 -east: 5, south: 5, north: 0 -total weight: 9100 g -send failed: 9 -package_demo finished -~~~ - -### 5. 实验线程清理 - -![四个实验完成后的 ps](assets/day3_ps_cleanup.png) - -最后一次 `ps` 只剩 `tshell`、`tidle0`、`timer` 和 `main` 等系统线程,没有残留 `car`、`window`、`initmod`、`business`、`producer` 或 `sorter`。这说明截图中已运行的实验线程均已退出,并完成了对应静态 IPC 对象的脱离。 - -## 九、分层验证结论 - -| 证据层级 | 本次结果 | 结论边界 | -| --- | --- | --- | -| 源码 | 四个提交副本与工程源码同步,分别导出四个 MSH 命令 | 证明实现了对应 IPC 逻辑,不能单独证明板上运行 | -| 构建 | SCons 构建成功,生成 `rt-thread.elf` 和 `rtthread.bin` | 证明编译、链接和固件产物成功 | -| 下载 | RT-Thread Studio 中已确认最新固件下载成功 | 证明固件已写入并启动,不能替代每条串口命令证据 | -| 串口 | 四个 MSH 命令均输出 `finished`,统计值符合设计;最终 `ps` 无实验线程残留 | 证明本次截图中的线程、IPC 和清理路径实际执行 | -| 实体硬件 | 本次材料是串口终端截图 | 未单独证明 LED、按键或其他外设的物理现象 | - -## 十、提交资源 - -本次补充的截图资源位于 `assets/`: - -~~~ -day3_parking_terminal.png -day3_ticket_terminal.png -day3_startup_terminal.png -day3_package_terminal.png -day3_ps_cleanup.png -~~~ - -截图均由用户提供的原始终端截图按 80% 比例缩小后保存,Markdown 使用相对路径引用,便于在仓库页面和本地预览中查看。 diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 85d27d765e5ba2396e58222c969bc915805faad9..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,113 +0,0 @@ -# 第二天作业 - -## 一、作业目标 - -在星火 1 号 STM32F407 的 RT-Thread Studio 工程中,实现动态创建和静态创建两种 LED 线程,增加按键监控线程,并用自动初始化与 MSH 命令完成线程状态观察和按键暂停控制。 - -## 二、实现内容 - -| 项目 | 实现 | -| --- | --- | -| 动态 LED 线程 | `th_led1` 由 `rt_thread_create()` 创建,栈 1024 B,优先级 16,时间片 5 Tick;周期翻转 PF12。 | -| 静态 LED 线程 | `th_led2` 由 `rt_thread_init()` 初始化,使用静态 TCB 和 1024 B 静态栈,优先级 16,时间片 5 Tick;周期翻转 PF11。 | -| 监控线程 | `th_monitor` 动态创建,栈 1024 B,优先级 10,时间片 5 Tick;以 20 ms 周期扫描 PC0 低电平有效按键。 | -| 自动初始化 | `INIT_APP_EXPORT(thread_study_init)` 初始化 GPIO、信号量并启动三个线程。 | -| MSH 命令 | `MSH_CMD_EXPORT(thread_dump, ...)` 导出 `thread_dump`,显示线程状态、优先级、剩余栈和 LED1 控制标志。 | -| 按键暂停 | 首次有效按键经 20 ms 消抖后设置 `led1_paused`,`th_led1` 等待 `led1_resume_sem`,控制标志输出为 `manual_suspend`。 | - -源码使用静态信号量 `led1_resume_sem` 表示恢复事件,未直接调用 `rt_thread_suspend()` 或 `rt_thread_resume()`。暂停前会清理可能残留的信号量令牌,避免旧唤醒事件影响下一次暂停。 - -## 三、操作步骤 - -1. 使用 USB 连接星火 1 号的 ST-Link 调试接口并上电。 -2. 在设备管理器确认 `STMicroelectronics STLink Virtual COM Port`。本次测试时端口为 COM21;实际端口号应以当前枚举结果为准。 -3. 在 RT-Thread Studio 打开 `thread_sample1` 工程,确认 `applications/thread_study.c` 参与构建。 -4. 执行构建,确认控制台为零错误和零警告。 -5. 使用 ST-Link 下载并启动程序。 -6. 打开串口终端,选择当前 ST-Link VCP 端口,等待 RT-Thread 启动横幅和 `msh >` 提示。 -7. 输入 `thread_dump`,确认 `th_led1`、`th_led2`、`th_monitor` 的名称、优先级和栈信息,并观察 `led1 control=periodic_running`。 -8. 第一次有效按下 KEY0,等待消抖后再次输入 `thread_dump`,确认 `led1 control=manual_suspend`。 - -本次按键记录在首次人工暂停处截止,不追加第二次按键后的恢复验证或结论。 - -## 四、实际验证结果 - -| 验证项目 | 结果 | -| --- | --- | -| 串口枚举 | 已看到 ST-Link 虚拟串口 COM21。 | -| Studio 构建 | 成功,`Build Finished. 0 errors, 0 warnings`;Flash 约 60.83 KB、RAM 约 6.22 KB。 | -| ST-Link 下载 | 成功,控制台显示下载完成、硬件复位和应用开始运行。 | -| 调试会话 | 成功,Studio 中 ST-LINK GDB Server 与 GDB 会话处于运行状态。 | -| 串口启动 | 成功,已看到 RT-Thread 4.1.1 横幅和 `msh >`。 | -| 三个作业线程 | 成功,`thread_dump` 显示 `th_led1`、`th_led2`、`th_monitor`;截图中的剩余栈依次为 852/1024、848/1024、844/1024。 | -| 按键暂停 | 成功,首次有效按键前控制标志为 `periodic_running`,按键后为 `manual_suspend`。 | - -日志中的内核 `state=suspend` 也可能由 `rt_thread_mdelay()` 的周期等待产生,不能单独作为人工暂停的证据;本作业以 `led1 control=manual_suspend` 作为首次按键控制成功的判定依据。 - -## 五、完成演示 - -### 1. 板卡连接与上电现场 - -以下实物图显示本次使用的 RT-Spark 星火 1 号开发板、STM32F407 主控、USB 连接和板上电源/状态灯。照片证明板卡已上电、连接现场存在且板上灯光可见;单张照片不能单独证明两个 LED 线程的颜色、周期,或按键暂停的时序,这些功能结论以后的构建、终端和控制状态证据为准。 - -RT-Spark 星火 1 号开发板上电与绿色状态灯 - -RT-Spark 星火 1 号开发板上电现场另一视角 - -连接 USB 的 RT-Spark 星火 1 号开发板全貌 - -### 2. 软件和终端验证截图 - -以下为本次作业的缩略版验证截图,原图已按最大宽度 720 px 缩小后随作业提交。 - -#### 2.1 ST-Link 虚拟串口 - -设备管理器显示 `STMicroelectronics STLink Virtual COM Port (COM21)`;当前使用端口应以重新枚举结果为准。 - -设备管理器中的 ST-Link 虚拟串口 COM21 - -#### 2.2 Studio 构建成功 - -构建控制台显示 `Build Finished. 0 errors, 0 warnings`,并显示 Flash 约 60.83 KB、RAM 约 6.22 KB。 - -RT-Thread Studio Day2 工程构建成功 - -#### 2.3 ST-Link 下载并启动 - -下载控制台显示 `File download complete`、硬件复位和应用运行成功。 - -ST-Link 下载完成并启动应用 - -#### 2.4 启动横幅与三个线程 - -串口终端显示 RT-Thread 4.1.1 启动信息及 `thread_dump` 结果,`th_led1`、`th_led2` 和 `th_monitor` 均已创建。 - -串口启动横幅与三个作业线程 - -#### 2.5 首次按键后的人工暂停 - -首次有效按键后,`thread_dump` 的控制标志由 `periodic_running` 变为 `manual_suspend`。按键验证在此处截止。 - -首次按键后 LED1 人工暂停 - -## 六、提交内容 - -```text -2026/第4组/李庭荣/ -├── 笔记/ -│ └── 第二天笔记.md -└── 作业/ - ├── 第二天作业.md - ├── thread_study.c - └── assets/ - ├── day2_com21_enumeration.png - ├── day2_build_success.png - ├── day2_download_success.png - ├── day2_terminal_startup_and_threads.png - ├── day2_led1_manual_suspend.png - ├── day2_board_powered_green.jpg - ├── day2_board_powered_green_alt.jpg - └── day2_board_usb_overview.jpg -``` - -本次提交学习笔记、作业说明、实际测试源码及缩略版验证图片;不提交构建产物、个人环境文件、密钥或无关工程文件。 diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/README.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/README.md" deleted file mode 100644 index c482c7ae10495628818e4ba5f1c3eee9767f44ce..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/README.md" +++ /dev/null @@ -1,3 +0,0 @@ -# 笔记 - -本目录保存每日学习笔记。 diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_cortex_m_stack_frame.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_cortex_m_stack_frame.png" deleted file mode 100644 index 5f6adf26af9536d2982c16b2489d4936a63b9e38..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_cortex_m_stack_frame.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_kernel_object_hierarchy.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_kernel_object_hierarchy.png" deleted file mode 100644 index 697be3d0e6eb4a1d90d069b3161e8d3ea5df8dad..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_kernel_object_hierarchy.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_round_robin_timeslice.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_round_robin_timeslice.png" deleted file mode 100644 index 93df21793eec6315dc5037f2191652b7ab78c73e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_round_robin_timeslice.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_startup_flow.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_startup_flow.png" deleted file mode 100644 index ec5a1069ac131fa20b4650355a3e0f22e188ce72..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_startup_flow.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_thread_states.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_thread_states.png" deleted file mode 100644 index 6acbca338bb94e537e0579a6ab1e0f650bd73b43..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/day2_thread_states.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/kernel_manual_auto_init.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/kernel_manual_auto_init.png" deleted file mode 100644 index 47396cd1c726f4c41f8a3de4ebf73411eb6a3694..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/kernel_manual_auto_init.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/kernel_manual_idle.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/kernel_manual_idle.png" deleted file mode 100644 index 66f90deae3e9c470de3a25fe1d51e553a9fb5808..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/kernel_manual_idle.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/kernel_manual_msh_commands.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/kernel_manual_msh_commands.png" deleted file mode 100644 index e55523e2fac6085ad721dab1f0675413b2579eb9..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/kernel_manual_msh_commands.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/spark1_onboard_devices_schematic.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/spark1_onboard_devices_schematic.png" deleted file mode 100644 index f51fe83633e930825515f01f16d8e5cc6b1174c0..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/spark1_onboard_devices_schematic.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/spark1_stlink_vcp_schematic.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/spark1_stlink_vcp_schematic.png" deleted file mode 100644 index be0ad4cec718a4b1bee8c051ac1b88876b92530b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/assets/spark1_stlink_vcp_schematic.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index abdbd5725187be81cd6f8c0e2d0336e25c5e199e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,144 +0,0 @@ -# RT-Thread Day1 学习笔记 - -## 一、RT-Thread 产品线 - -RT-Thread 的内核与组件可按设备资源和应用复杂度选择: - -| 版本 | 定位 | 主要能力 | -| --- | --- | --- | -| Nano | 资源受限设备 | 实时调度、线程同步通信、时钟、中断和内存管理等基础能力 | -| 标准版 | 常规嵌入式应用 | POSIX API、日志、网络协议栈、IoT 协议和可选中间件 | -| Smart | 带 MMU 的复杂设备 | 内核态与用户态隔离,并支持更复杂的应用运行环境 | - -本次学习以 RT-Thread 4.1.1 工程为基础,重点理解应用代码、线程调度和日志输出之间的关系。 - -## 二、QEMU A9 最小应用 - -QEMU A9 示例可用于观察 RT-Thread 应用入口与终端输出。最小示例代码如下: - -```c -#include -#include -#include - -int main(void) -{ - printf("hello rt-thread\n"); - return 0; -} -``` - -`printf()` 输出用于验证应用入口已经被调用。应用 `main()` 返回后,RT-Thread 内核和其他系统线程仍会继续运行。 - -## 三、STM32F407ZG 周期日志示例 - -在 STM32F407ZG 的 RT-Thread 工程中,可以使用日志组件和线程延时实现周期输出: - -```c -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -int main(void) -{ - int count = 1; - - while (count++) - { - LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - - return RT_EOK; -} -``` - -代码要点: - -- `rtthread.h` 提供 RT-Thread 内核 API。 -- `DBG_TAG` 用于标记日志模块,终端日志中的 `[D/main]` 与该标签对应。 -- `LOG_D()` 输出调试等级日志。 -- `rt_thread_mdelay(1000)` 让当前线程延时约 1 秒。 - -终端中已观察到 RT-Thread 启动信息、MSH 命令行和连续的 `[D/main] Hello RT-Thread!` 输出。编译、下载、串口输出和实体开发板运行属于不同层级的验证,应分别记录。 - -## 四、RT-Thread 启动流程 - -从芯片复位到应用线程运行的核心流程如下: - -```text -复位处理函数 - -> 初始化栈、.data 和 .bss - -> SystemInit() - -> rtthread_startup() - -> 板级初始化与内核对象初始化 - -> 创建 main 线程和 idle 线程 - -> 启动调度器 - -> main 线程执行应用 main() -``` - -启动文件负责建立 C 运行环境。随后 `rtthread_startup()` 初始化板级资源、内核对象、软件定时器和组件。 - -应用中的 `main()` 并不是复位后的最早裸机入口,而是作为 `main` 线程的入口函数,在调度器启动后运行。`idle` 线程优先级最低,当没有其他就绪线程时运行。 - -## 五、线程延时、SysTick 和调度 - -周期日志的运行过程如下: - -```text -main 线程运行 - -> 输出日志 - -> 调用 rt_thread_mdelay(1000) - -> 当前线程进入延时挂起态 - -> 调度其他就绪线程或 idle 线程 - -> 系统节拍到期 - -> 当前线程恢复就绪并再次输出日志 -``` - -`rt_thread_mdelay(1000)` 不是空循环等待。当前线程挂起后,CPU 可以运行其他线程。 - -系统节拍通常由 SysTick 产生。每次节拍中断到来时,内核会更新 tick 计数、处理到期的延时线程、推进软件定时器,并在需要时触发调度。 - -RT-Thread 使用基于优先级的抢占式调度:数值越小表示优先级越高;高优先级线程就绪时可以抢占低优先级线程。 - -## 六、FinSH/MSH 命令行 - -FinSH/MSH 组件提供交互式命令行。常用命令包括: - -| 命令 | 用途 | -| --- | --- | -| `help` | 查看命令帮助 | -| `ps` | 查看线程状态 | -| `free` | 查看动态内存使用情况 | -| `reboot` | 触发系统复位 | - -FinSH/MSH 线程与应用 `main` 线程由同一个调度器管理,因此系统可以在周期输出日志时响应终端命令。 - -## 七、Git 分支与 Pull Request 流程 - -开始新内容前,先从真正的上游仓库同步本地 `master`,再创建独立开发分支: - -```bash -git fetch upstream -git switch master -git merge --ff-only upstream/master -git push origin master -git switch -c day1-content-li-tingrong -``` - -完成修改并确认变更范围后,执行: - -```bash -git add 2026/第4组/李庭荣/笔记/第一天笔记.md -git add 2026/第4组/李庭荣/作业/第一天作业.md -git commit -m "Add Li Tingrong Day1 notes and homework" -git push -u origin day1-content-li-tingrong -``` - -推送后,从个人 Fork 的 `day1-content-li-tingrong` 分支向上游仓库的 `master` 创建 Pull Request。分支推送、PR 创建、审核通过和合并完成是不同状态,不能混为一谈;合并完成后再同步本地 `master`。 - -## 八、小结 - -Day1 的核心收获是理解 RT-Thread 应用代码在线程上下文中的运行方式,以及日志、线程延时、SysTick、调度器和 FinSH/MSH 组件之间的关系。后续将继续学习线程创建、线程同步、软件定时器、消息队列和常用设备框架。 diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 7bcf98fcbfe3b205833b87014566854fc8ee875f..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,312 +0,0 @@ -# 第三天笔记:RT-Thread 内核 IPC - -日期:2026-08-19 - -## 一、课程主线 - -IPC(Inter-Process Communication)用于线程之间的同步与通信。Day3 重点理解:资源不可用时线程如何阻塞,资源就绪后如何恢复;同步对象和通信对象的区别;永久等待、非阻塞尝试、超时等待、紧急发送和对象销毁的行为。 - -| 类型 | 对象 | 主要用途 | -| --- | --- | --- | -| 线程同步 | 信号量 | 事件通知、资源计数 | -| 线程同步 | 互斥量 | 保护临界区和共享资源 | -| 线程同步 | 事件集 | 表达一个或多个同步条件 | -| 线程通信 | 邮箱 | 传递固定宽度的值或指针 | -| 线程通信 | 消息队列 | 传递固定长度的结构化消息 | - -![RT-Thread 内核对象层级](assets/day2_kernel_object_hierarchy.png) - -该图用于理解线程、定时器和 IPC 对象都属于 RT-Thread 内核对象体系,不是某个 IPC 命令已经运行的证据。 - -## 二、临界区与阻塞模型 - -临界区是访问共享资源的一段代码,同一时刻只能由一个执行流进入。共享变量、设备寄存器、环形缓冲区和串口输出状态都可能成为临界资源。锁的范围应覆盖完整事务,例如“检查条件、修改状态、记录结果”;持锁期间不应进行长时间延时或永久等待。 - -| 等待方式 | 资源不可用时的行为 | 适用场景 | -| --- | --- | --- | -| 永久阻塞 | 线程挂起,直到资源可用 | 必须等待的工作线程 | -| 非阻塞尝试 | 立即返回成功或失败 | 状态探测、不能等待的路径 | -| 超时等待 | 等待指定 Tick,超时返回 | 需要有界响应时间的任务 | - -```text -调用 take/recv - -> 资源不可用 - -> 当前线程进入挂起态 - -> 调度器运行其他就绪线程 - -> 资源到达或等待超时 - -> 当前线程重新进入就绪队列 -``` - -`state=suspend` 只能说明线程当前没有参与调度,不能单独说明它是在延时、等待 IPC,还是被显式挂起。 - -## 三、信号量 - -信号量通过计数值表示可用令牌数量,没有线程所有权,适合线程同步和资源计数,不适合代替互斥量保护复杂临界区。二值信号量通常表示事件未到达或已到达;计数信号量表示多个同类资源。 - -```text -初始值为 0 - -> rt_sem_take() 没有令牌 - -> 当前线程阻塞 - -> 其他线程 rt_sem_release() - -> 等待线程被唤醒并获取令牌 -``` - -主要 API: - -```c -rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag); -rt_err_t rt_sem_delete(rt_sem_t sem); -rt_err_t rt_sem_init(rt_sem_t sem, const char *name, - rt_uint32_t value, rt_uint8_t flag); -rt_err_t rt_sem_detach(rt_sem_t sem); -rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout); -rt_err_t rt_sem_trytake(rt_sem_t sem); -rt_err_t rt_sem_release(rt_sem_t sem); -``` - -`RT_WAITING_FOREVER` 表示永久等待;`rt_sem_trytake()` 或超时参数为 0 表示非阻塞尝试。静态对象使用 `init -> detach`,动态对象使用 `create -> delete`。 - -```c -static struct rt_semaphore sem; - -rt_sem_init(&sem, "sem", 0, RT_IPC_FLAG_FIFO); -if (rt_sem_take(&sem, RT_WAITING_FOREVER) == RT_EOK) -{ - rt_kprintf("event received\\n"); -} -rt_sem_release(&sem); -rt_sem_detach(&sem); -``` - -信号量释放不要求释放者就是获取者,这一点与互斥量不同。信号量内部计数不能直接当作业务统计变量使用。 - -## 四、互斥量 - -互斥量是带所有权的锁,主要用于保护临界区。成功获取互斥量的线程成为持有者,通常只有持有者才能释放。RT-Thread 的互斥量记录持有者和持有次数,支持同一线程递归获取,并通过优先级继承降低优先级翻转的影响。 - -| 对比项 | 信号量 | 互斥量 | -| --- | --- | --- | -| 所有权 | 无 | 有 | -| 主要用途 | 同步、资源计数 | 临界区保护 | -| 释放限制 | 不依赖获取者身份 | 通常由持有者释放 | -| 优先级继承 | 无互斥所有权语义 | 支持 | - -优先级翻转过程是:低优先级线程持锁,高优先级线程等待,中优先级线程持续抢占低优先级线程。互斥量会临时提升锁持有者的优先级,但不能替代合理的锁粒度、锁顺序和有界临界区设计。 - -主要 API: - -```c -rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag); -rt_err_t rt_mutex_delete(rt_mutex_t mutex); -rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag); -rt_err_t rt_mutex_detach(rt_mutex_t mutex); -rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout); -rt_err_t rt_mutex_trytake(rt_mutex_t mutex); -rt_err_t rt_mutex_release(rt_mutex_t mutex); -``` - -```c -static struct rt_mutex lock; -static rt_int32_t value; - -rt_mutex_init(&lock, "lock", RT_IPC_FLAG_PRIO); -if (rt_mutex_take(&lock, RT_WAITING_FOREVER) == RT_EOK) -{ - value++; - rt_mutex_release(&lock); -} -rt_mutex_detach(&lock); -``` - -“检查条件、修改数据、记录结果”应放在同一个临界区中。只给某一条赋值语句加锁,仍可能在检查和修改之间产生竞态。 - -## 五、事件集 - -事件集只表达同步条件,不携带业务数据。当前 RT-Thread 使用 32 位事件集合,每一位表示一个独立事件。事件位是状态,不是消息队列;重复发送同一位不会按次数排队。 - -```c -#define EVENT_NET_READY (1u << 0) -#define EVENT_SENSOR_READY (1u << 1) -#define EVENT_STORAGE_READY (1u << 2) -``` - -| 选项 | 含义 | -| --- | --- | -| `RT_EVENT_FLAG_OR` | 指定事件中任意一位满足即可返回 | -| `RT_EVENT_FLAG_AND` | 指定的所有事件位都满足后才返回 | -| `RT_EVENT_FLAG_CLEAR` | 成功接收后清除匹配的事件位 | - -主要 API: - -```c -rt_event_t rt_event_create(const char *name, rt_uint8_t flag); -rt_err_t rt_event_delete(rt_event_t event); -rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag); -rt_err_t rt_event_detach(rt_event_t event); -rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set); -rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, - rt_uint8_t option, rt_int32_t timeout, - rt_uint32_t *recved); -``` - -```c -static struct rt_event event; -rt_uint32_t recved; - -rt_event_init(&event, "event", RT_IPC_FLAG_FIFO); -rt_event_send(&event, EVENT_NET_READY); -rt_event_recv(&event, - EVENT_NET_READY | EVENT_SENSOR_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_WAITING_FOREVER, &recved); -rt_event_detach(&event); -``` - -事件集只负责同步条件,不负责保存模块名称、数据内容或事件发生次数。超时后若要说明未完成条件,应由应用层维护状态掩码并设置同步边界。 - -## 六、邮箱 - -邮箱是轻量的线程通信对象,每个槽位保存一个 `rt_ubase_t` 宽度的值。在 32 位 MCU 上通常为 4 字节,可以传递整数或指针。通过指针传递结构体时,指向的对象必须在接收线程使用期间保持有效。 - -主要 API: - -```c -rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag); -rt_err_t rt_mb_delete(rt_mailbox_t mb); -rt_err_t rt_mb_init(rt_mailbox_t mb, const char *name, - void *msg_pool, rt_size_t size, rt_uint8_t flag); -rt_err_t rt_mb_detach(rt_mailbox_t mb); -rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value); -rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_ubase_t value, rt_int32_t timeout); -rt_err_t rt_mb_urgent(rt_mailbox_t mb, rt_ubase_t value); -rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout); -``` - -```c -static struct rt_mailbox mailbox; -static rt_ubase_t pool[4]; -rt_ubase_t value; - -rt_mb_init(&mailbox, "mail", pool, 4, RT_IPC_FLAG_FIFO); -rt_mb_send(&mailbox, 0x1234); -rt_mb_recv(&mailbox, &value, RT_WAITING_FOREVER); -rt_mb_detach(&mailbox); -``` - -普通发送在邮箱满时立即返回;`rt_mb_send_wait()` 可以等待空位;`rt_mb_urgent()` 将邮件插入队首。带等待的接口不能在中断上下文中使用。不能发送线程栈上即将失效的临时对象地址。 - -## 七、消息队列 - -消息队列用于传递固定最大长度的消息。发送时复制消息内容,接收时复制到接收缓冲区,适合传递结构体、串口帧和传感器数据。消息池必须容纳指定数量的、按 `RT_ALIGN_SIZE` 对齐后的消息块。 - -主要 API: - -```c -rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, - rt_size_t max_msgs, rt_uint8_t flag); -rt_err_t rt_mq_delete(rt_mq_t mq); -rt_err_t rt_mq_init(rt_mq_t mq, const char *name, void *msgpool, - rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag); -rt_err_t rt_mq_detach(rt_mq_t mq); -rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size); -rt_err_t rt_mq_send_wait(rt_mq_t mq, const void *buffer, - rt_size_t size, rt_int32_t timeout); -rt_err_t rt_mq_urgent(rt_mq_t mq, const void *buffer, rt_size_t size); -rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, - rt_size_t size, rt_int32_t timeout); -``` - -```c -struct sensor_msg -{ - rt_uint32_t id; - rt_uint16_t value; -}; - -#define MSG_SIZE RT_ALIGN(sizeof(struct sensor_msg), RT_ALIGN_SIZE) -static struct rt_messagequeue queue; -static rt_uint8_t pool[MSG_SIZE * 5]; -struct sensor_msg tx = {1001, 250}; -struct sensor_msg rx; - -rt_mq_init(&queue, "sensorq", pool, MSG_SIZE, - sizeof(pool), RT_IPC_FLAG_FIFO); -rt_mq_send(&queue, &tx, sizeof(tx)); -rt_mq_recv(&queue, &rx, sizeof(rx), RT_WAITING_FOREVER); -rt_mq_detach(&queue); -``` - -`rt_mq_send()` 在队列满时立即返回错误;`rt_mq_send_wait()` 可以在限定时间内等待空间;`rt_mq_urgent()` 将消息插入队首。生产者必须检查发送结果。 - -## 八、五类 IPC 的区别 - -| 对象 | 是否携带业务数据 | 是否有所有权 | 典型等待条件 | 典型应用 | -| --- | --- | --- | --- | --- | -| 信号量 | 否 | 否 | 令牌数量大于 0 | 事件通知、资源计数 | -| 互斥量 | 否 | 有 | 锁未被其他线程持有 | 共享资源保护 | -| 事件集 | 否 | 否 | OR 或 AND 条件满足 | 多条件同步 | -| 邮箱 | 一个值或指针 | 否 | 邮箱中存在邮件 | 轻量通知、指针传递 | -| 消息队列 | 固定长度数据 | 否 | 队列中存在消息 | 生产者消费者、结构体传递 | - -选择原则:通知或限制资源数量使用信号量;保护共享数据一致性使用互斥量;等待多个条件使用事件集;传递一个值或指针使用邮箱;复制结构化数据使用消息队列。 - -## 九、动态与静态对象生命周期 - -```text -动态对象:create -> 使用 -> delete -静态对象:init -> 使用 -> detach -``` - -动态对象由系统堆分配控制块和相关内存,适合数量或生命周期在运行时确定的场景,但必须处理内存不足和删除时的等待线程唤醒问题。 - -静态对象由应用提供控制块和缓冲区,边界更明确。执行 `detach` 或 `delete` 前,必须确保没有线程继续等待或访问对象。动态对象不能使用 `detach`,静态对象不能使用 `delete`。 - -## 十、RT-Thread 工程中的 IPC - -本地工程: - -```text -D:\RT-ThreadStudio\workspace\RSOC2026_IPC -``` - -`rtconfig.h` 已启用: - -```text -RT_USING_SEMAPHORE -RT_USING_MUTEX -RT_USING_EVENT -RT_USING_MAILBOX -RT_USING_MESSAGEQUEUE -RT_USING_MSH -FINSH_USING_MSH -``` - -工程中的 `kernel_samples-latest` 提供信号量、互斥量、事件集、邮箱、邮箱紧急发送和消息队列示例,并通过 MSH 导出对应命令。重复运行示例前,应确认上一次示例已经结束并完成对象清理,避免固定名称对象残留。 - -## 十一、验证边界 - -| 证据层级 | 可以说明什么 | 不能替代什么 | -| --- | --- | --- | -| 配置 | IPC 相关宏已启用 | 不能证明对象已经运行 | -| 源码 | API 调用和对象关系存在 | 不能证明固件已下载 | -| 构建 | 源码已编译、链接产物已生成 | 不能证明串口输出 | -| 下载 | 固件已写入并复位启动 | 不能证明每个命令已执行 | -| 串口 | MSH 回显、线程日志和 IPC 输出 | 不能单独证明实体硬件现象 | -| 硬件 | 可重复的板卡现象 | 不能替代源码和串口证据 | - -当前本地工程已完成 IPC 源码收集、编译、链接以及 `rt-thread.elf` 和 `rtthread.bin` 生成;下载、串口命令和实体运行需要单独记录,不能由构建结果推断。 - -## 十二、官方资料与工程源码 - -- [RT-Thread IPC:信号量](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1?id=%e4%bf%a1%e5%8f%b7%e9%87%8f) -- [RT-Thread IPC:互斥量](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1?id=%e4%ba%92%e6%96%a5%e9%87%8f) -- [RT-Thread IPC:事件集](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1?id=%e4%ba%8b%e4%bb%b6%e9%9b%86) -- [RT-Thread IPC:邮箱和消息队列](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc2/ipc2) - -本地对应源码: - -```text -D:\RT-ThreadStudio\workspace\RSOC2026_IPC\rt-thread\include\rtthread.h -D:\RT-ThreadStudio\workspace\RSOC2026_IPC\rt-thread\src\ipc.c -D:\RT-ThreadStudio\workspace\RSOC2026_IPC\packages\kernel_samples-latest\zh\ -``` diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index fe3309b4ba0a2b2e2b8173b8d8c132f17b9e3532..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,337 +0,0 @@ -# RT-Thread Day2 学习笔记 - -## 一、课程主题 - -Day2 从 RT-Thread 的内核启动讲到线程创建、调度与状态观察,再讲 SCons 构建系统和 RT-Thread Studio 的编译、下载、串口调试。实验以星火 1 号 STM32F407 工程为载体,实现一个动态 LED 线程、一个静态 LED 线程、一个按键监控线程,以及用于查看状态的 MSH 命令。 - -本笔记结合课程课件、RT-Thread 官方 SCons/Studio 文档、星火 1 号原理图、RT-Thread V4.1.1 内核学习手册和实际 `thread_sample1` 工程整理。通用课件用于理解机制;引脚、电平、Tick、线程优先级范围及启用的软件组件,以当前 BSP 的 `rtconfig.h`、源码和终端输出为最终依据。 - -## 二、开发板、调试链路与 Studio 工作流 - -本次平台为 RT-Spark 星火 1 号,主控为 `STM32F407ZGT6`。板卡包含板载 ST-Link/V2.1、USB 调试接口、SWD/JTAG、虚拟串口(VCP)、方向按键和 RGB LED 等设备。原理图的价值是解释硬件连接关系,而驱动能否被应用调用,还取决于 BSP 是否实现并启用了对应接口。 - -星火 1 号 ST-Link 与虚拟串口连接关系 - -上图用于确认“电脑 USB -> 板载 ST-Link -> SWD 下载/调试与 VCP 串口”的链路。设备管理器中出现 ST-Link 虚拟串口只说明 VCP 在当时已枚举;它不等同于固件已经下载或应用已经输出日志。 - -星火 1 号板载按键和 LED 相关原理图 - -板载设备页标有 `BTN_UP`、`BTN_DOWN`、`BTN_LEFT`、`BTN_RIGHT` 以及 LED 相关网络。RGB 灯器件为 `SK6805` 串行 LED 链,不能仅凭网络名称推断为“每种颜色对应一个普通 GPIO”。本实验源码中的 PF12、PF11、PC0 是当前工程采用并已运行的配置;将代码迁移到其他星火 BSP 时,应先从原理图追踪网络到 MCU 引脚,再核对 board/driver 源码和有效电平。 - -在 RT-Thread Studio 中,建议将操作拆为独立闭环:选择正确的板卡、芯片和调试器,确认 `applications/thread_study.c` 参与构建,零错误构建,使用 ST-Link 下载,打开当前 VCP 串口终端,最后再观察 `msh >`、线程信息和实体行为。这样能够避免把“USB 已连接”误判为“作业已运行”。 - -## 三、从上电到用户程序 - -```text -芯片复位 - -> 启动汇编设置栈和 C 运行环境 - -> 初始化 .data、清零 .bss - -> SystemInit()、板级时钟和外设初始化 - -> rtthread_startup() - -> 初始化内核对象、时钟、定时器和组件 - -> 创建 main、idle 等系统线程 - -> 启动调度器 - -> main 线程运行用户 main() -``` - -RT-Thread 从复位到调度器启动的流程图 - -用户 `main()` 并不是芯片复位后的第一个 C 函数。RT-Thread 先完成内核和板级准备,再让调度器选中 main 线程执行。idle 线程优先级最低,不能被挂起;它在没有其他就绪线程时运行,并负责回收已退出的动态线程资源,也可以承载低功耗或看门狗钩子。 - -从裸机视角看,传统前后台程序通常把多个任务放在 `while (1)` 中顺序轮询,并用中断处理异步事件。RTOS 则把职责拆为多个线程:线程描述“做什么、何时等待”,调度器决定“谁先使用 CPU”。STM32F407 是单核 MCU,多线程是通过快速上下文切换形成的并发效果,同一时刻只有一个线程真正处于运行态。 - -## 四、自动初始化 - -`INIT_APP_EXPORT(function)` 会把函数登记到链接期的初始化表。系统进入应用初始化阶段时自动遍历该表并调用函数,因此应用无需在 `main.c` 手动调用初始化入口。 - -```text -源码中的 INIT_APP_EXPORT(thread_study_init) - -> 链接到自动初始化表 - -> 系统启动时遍历应用初始化表 - -> thread_study_init() 初始化 GPIO、信号量和三个线程 - -> rt_thread_startup() 将线程加入就绪队列 -``` - -RT-Thread 自动初始化阶段与链接器收集机制 - -自动初始化函数应只做一次性工作,例如设备配置、IPC 对象初始化和创建线程。无限循环必须放在线程入口函数中,否则会阻塞后续初始化。 - -自动初始化并非只有 `INIT_APP_EXPORT()` 一种阶段。与 Day2 最相关的阶段可理解为:`INIT_BOARD_EXPORT()` 用于调度器启动前的早期板级初始化;`INIT_DEVICE_EXPORT()`、`INIT_COMPONENT_EXPORT()`、`INIT_ENV_EXPORT()` 依次准备设备、组件和环境;`INIT_APP_EXPORT()` 适合登记应用模块。宏会将函数指针收集到 RTI 链接段,main 线程执行 `rt_components_init()` 时按阶段遍历。选择初始化阶段时,关键是判断“该函数是否依赖调度器、线程、设备或堆已经可用”。 - -## 五、线程、栈与生命周期 - -线程控制块 `struct rt_thread` 记录线程名称、优先级、状态、入口函数、栈地址和栈指针等信息。调度切换时,CPU 寄存器现场压入当前线程栈;重新获得 CPU 时再从该栈恢复。因此递归过深、大型局部数组或调用层级过多都可能造成栈溢出。 - -RT-Thread 内核对象层次结构 - -`rt_thread` 同时是 RT-Thread 统一内核对象模型的一部分。线程、定时器、设备、内存池等对象通过 `rt_object` 被统一管理;信号量、互斥量、事件、邮箱和消息队列等 IPC 对象通过 `rt_ipc_object` 维护等待队列。这样当线程等待一个信号量时,内核不仅记录“线程暂停”,还知道它正等待哪个 IPC 对象,以便资源可用时恢复相应线程。 - -```text -创建/初始化 -> 初始态 -> startup -> 就绪态 -> 运行态 - ^ | - | | 延时、等待信号量 - | v - +----- 挂起/阻塞态 - -线程入口返回或线程删除 -> 关闭态 -> idle 线程回收动态资源 -``` - -RT-Thread 线程状态示意图 - -动态线程由 `rt_thread_create()` 从系统堆申请线程控制块和栈,调用方便但依赖堆空间;静态线程使用应用预先定义的线程控制块和栈,再通过 `rt_thread_init()` 初始化,内存边界明确且不会引入运行期堆碎片。两者创建后都必须调用 `rt_thread_startup()` 才能调度执行。 - -对 Cortex-M 而言,首次运行一个新线程前,内核会预先构造一帧符合异常返回格式的初始栈内容,包括入口地址、入口参数、退出处理位置和待恢复的寄存器值。第一次切换时 CPU 恢复这帧上下文,线程便从入口函数开始执行。 - -Cortex-M 新线程初始栈帧示意图 - -栈使用的观察不能只看“创建时分配了多少”。应结合 `ps`/`list_thread` 或本实验的 `thread_dump` 观察剩余空间,并在日志量最大、调用最深的路径复查。深递归、大数组、`rt_kprintf`/格式化输出和层级过深的驱动调用,都可能令单次看似充足的栈在极端路径下溢出。 - -## 六、调度器与时间片 - -RT-Thread 使用抢占式优先级调度:优先级数值越小,优先级越高;更高优先级线程进入就绪态时,可以抢占低优先级线程。同优先级线程按时间片轮转,时间片单位是 Tick。 - -本工程中 `RT_TICK_PER_SECOND` 为 1000,故 1 Tick 约为 1 ms。两个 LED 线程都使用优先级 16、时间片 5 Tick;监控线程优先级 10,数值更小,因此优先级更高。线程若调用 `rt_thread_mdelay()`,在等待期间会暂时处于 `suspend` 状态,这不等同于按键触发的人工暂停。 - -同优先级线程按时间片轮转示意图 - -时间片只处理“同优先级且同时就绪”的线程:当前线程时间片耗尽后,会让同一优先级就绪队列中的下一线程获得 CPU。更高优先级线程一旦就绪,抢占规则优先于时间片轮转。`RT_TICK_PER_SECOND`、Timer 线程是否启用和各系统线程优先级均可能因 BSP 配置不同而变化,课堂中的数值不应直接套用于全部工程。 - -idle 线程始终就绪且优先级最低,通常负责回收已结束动态线程的资源。它不是普通业务线程:idle 钩子中不能调用延时、等待信号量等可能挂起的 API,也应避免调用内部可能依赖同步保护的动态内存操作。 - -RT-Thread idle 线程职责与限制 - -## 七、本次线程设计 - -| 线程 | 创建方式 | 优先级/栈/时间片 | 职责 | -| --- | --- | --- | --- | -| `th_led1` | 动态 `rt_thread_create()` | 16 / 1024 B / 5 Tick | 周期翻转当前代码配置的 PF12;按键暂停时等待恢复信号量。 | -| `th_led2` | 静态 `rt_thread_init()` | 16 / 1024 B / 5 Tick | 周期翻转当前代码配置的 PF11。 | -| `th_monitor` | 动态 `rt_thread_create()` | 10 / 1024 B / 5 Tick | 每 20 ms 扫描 PC0 按键,做消抖并控制 LED1。 | - -当前 `thread_study.c` 定义 PF12、PF11 和 PC0(KEY0,低电平有效),初始化时将前两者设为输出高电平、按键设为上拉输入。PF12/PF11 的“红/蓝”命名来自本实验代码注释和运行目标,不能替代对 SK6805 驱动方式、实际 LED 颜色与有效电平的板级核对;实体行为应以当前 BSP 和连续硬件观察为准。 - -## 八、按键暂停的信号量实现 - -本实验没有让 monitor 线程直接调用 `rt_thread_suspend()` 和 `rt_thread_resume()`。它使用 `led1_paused` 表示控制意图,使用初值为 0 的 `led1_resume_sem` 作为恢复事件: - -```text -第一次有效按键 - -> monitor 线程延时 20 ms 消抖 - -> led1_paused = RT_TRUE - -> 输出 led1 control=manual_suspend - -> th_led1 在下一轮检查中 rt_sem_take(..., RT_WAITING_FOREVER) - -> LED1 周期翻转停止 - -后续恢复逻辑已写入源码:再次有效按键释放信号量,唤醒 th_led1。 -本次实验记录按用户要求截止在第一次按键后的人工暂停,不记录第二次按键恢复结果。 -``` - -暂停前通过 `rt_sem_trytake()` 清理残留令牌,避免旧的释放信号使下一次暂停立刻失效。`volatile` 只保证状态变量每次从内存读取,不能替代互斥、原子操作或完整的状态机;本实验中 monitor 是该标志的唯一写入者,适合演示线程等待事件。更复杂的产品逻辑应优先使用事件、消息队列或受保护的状态机。 - -`MSH_CMD_EXPORT(thread_dump, ...)` 导出 `thread_dump` 命令,输出三个线程的名称、内核状态、优先级和剩余栈,并额外输出: - -```text -led1 control=periodic_running -``` - -或: - -```text -led1 control=manual_suspend -``` - -这个控制标志用于区分周期延时造成的普通 `suspend` 与按键造成的人工暂停。 - -## 九、SCons 构建系统 - -RT-Thread 的构建配置链路如下: - -```text -Kconfig - -> menuconfig / Studio 配置 - -> rtconfig.h 中的配置宏 - -> SConstruct 调用各目录 SConscript - -> DefineGroup() 按 depend 条件收集源码 - -> rtconfig.py 提供工具链、编译和链接参数 - -> 编译、链接并生成 rtthread.elf / rtthread.bin -``` - -- `SConstruct`:工程构建入口。 -- `SConscript`:目录级构建脚本,通常用 `Glob()` 搜集源文件并用 `DefineGroup()` 注册源码组。 -- `Kconfig`:配置菜单的定义源;配置结果最终影响 `rtconfig.h` 宏。 -- `rtconfig.py`:GCC、Keil、IAR 等工具链参数和链接相关设置。 - -`Glob('*.c')` 常用于收集目录下 C 源文件;`DefineGroup(name, src, depend)` 将源文件注册为一个构建组,`depend` 则是配置开关。例如 `depend=['RT_USING_PIN']` 时,只有 `rtconfig.h` 中启用该宏,组内文件才会参与编译。源码文件放在 `applications` 目录中不保证参与构建,还要确认其被当前目录 `SConscript` 收集,并在编译日志中找到对应的编译命令。 - -常用操作与目的如下: - -| 命令 | 用途 | -| --- | --- | -| `scons` | 使用当前配置编译工程。 | -| `scons -j8` | 以 8 个并行任务编译;任务数应按 CPU 与内存情况调整。 | -| `scons -c` | 清理构建生成物后重新构建。 | -| `scons --menuconfig` | 进入配置菜单,修改 Kconfig 选项。 | -| `scons --target=mdk5` / `scons --target=iar` | 生成对应 IDE 工程,前提是 BSP 提供模板。 | -| `scons --target=cmake` | 生成 CMake 相关脚本。 | -| `scons --dist` | 导出裁剪后的独立 BSP 框架。 | -| `scons --verbose` | 显示真实编译、汇编和链接命令,便于定位参数问题。 | - -修改 menuconfig 后,应重新生成 IDE 工程或刷新 Studio 工程配置,避免 IDE 的文件列表、宏定义与 `rtconfig.h` 不一致。 - -## 十、实际构建与运行验证 - -本次验证基于同一个 `thread_sample1` RT-Thread Studio 工程,结论分层记录: - -| 验证项 | 已取得的证据 | 结论 | -| --- | --- | --- | -| 板卡连接和串口 | 设备管理器枚举 `STMicroelectronics STLink Virtual COM Port (COM21)` | 当时 USB 与虚拟串口可用;重新插拔后端口号可能变化。 | -| 工程构建 | Studio 显示 `Build Finished. 0 errors, 0 warnings`;Flash 约 60.83 KB、RAM 约 6.22 KB | 已构建成功。 | -| 固件下载 | 显示 `File download complete`、硬件复位、应用运行成功 | 已通过 ST-Link 下载并启动。 | -| 调试会话 | Studio 显示运行中的 ST-LINK GDB Server 和 GDB | 调试会话已建立。 | -| 串口运行 | RT-Thread 4.1.1 启动横幅、`msh >` 和 `thread_dump` | 固件已在板上运行并可交互。 | -| 三个线程 | `th_led1`、`th_led2`、`th_monitor` 均出现在 `thread_dump` 中 | 已确认创建并被调度。 | -| 首次按键 | 从 `led1 control=periodic_running` 变为 `led1 control=manual_suspend` | 已确认首次有效按键触发人工暂停控制。 | - -截图中的线程剩余栈为 `th_led1` 852/1024、`th_led2` 848/1024、`th_monitor` 844/1024。按键验证到首次人工暂停为止,未将后续恢复行为写为已验证事实。 - -除了自定义 `thread_dump`,启用 FinSH/MSH 的工程通常还可使用 `ps` 或 `list_thread` 查看内核线程表。不同 RT-Thread 版本的列名和统计方式可能不同,常见字段包括线程名、优先级、状态、栈指针、栈大小、最大栈使用量、剩余 Tick 和错误码。它们用于发现异常优先级、持续运行、栈接近耗尽或 IPC 等待;单次命令输出只是一个时刻的快照,应结合线程代码中的延时和等待点解释。 - -RT-Thread FinSH 或 MSH 线程查询字段示例 - -## 十一、从代码到运行现象的完整链路 - -完成代码不等于功能已经在开发板上运行。Day2 的实际操作可以分为五层,每一层都回答不同的问题: - -```text -源码层 - thread_study.c 中的 GPIO、线程和自动初始化代码是否正确 - | - v -构建层 - 源文件是否参与编译,能否生成 rtthread.elf / rtthread.bin - | - v -下载层 - 固件是否经 ST-Link 写入目标芯片并在复位后启动 - | - v -串口与调试层 - 系统启动后是否能输出日志、进入 msh,以及是否观察到目标线程 - | - v -实体行为层 - LED 和按键的可见、可重复行为是否与线程控制逻辑一一对应 -``` - -这五层不能相互替代。例如,`Build Finished. 0 errors, 0 warnings` 只证明构建成功,不能证明下载成功;下载完成也不能证明串口已经连通;串口中看到线程存在,也不能单独证明红、蓝 LED 的颜色和闪烁节奏均正确。将证据拆开记录,能够在问题出现时快速判断故障位于代码、构建、下载、连接还是硬件映射。 - -### 1. 串口枚举与终端连接 - -设备管理器出现 `STMicroelectronics STLink Virtual COM Port (COM21)`,说明该时刻 USB 连接和 ST-Link 虚拟串口驱动可用。它是打开终端的前提,但 COM 编号由 Windows 动态分配,重新插拔后可能不再是 COM21。 - -连接终端时应先确认当前端口,再等待 RT-Thread 横幅和 `msh >`: - -```text -RT-Thread 4.1.1 ... -msh > -``` - -横幅证明内核已经走过启动过程;`msh >` 说明 FinSH/MSH 已工作,可以输入 `thread_dump` 观察应用线程。若只看到端口却没有横幅,应分别检查波特率、复位时机、控制台 UART 配置和下载的固件是否为当前工程。 - -### 2. Studio 构建、下载和调试的判定 - -构建完成时,Studio 控制台显示 `Build Finished. 0 errors, 0 warnings`。本次输出还给出 Flash 约 60.83 KB、RAM 约 6.22 KB,它们是链接后的资源占用,而不是线程栈本身的瞬时使用量。后续增加线程、日志或软件包时,应持续观察 RAM 是否接近板卡可用内存。 - -下载成功要以下载日志为准。本次 ST-Link 日志包含: - -```text -File download complete -Hardware reset is performed -Application is running -Start operation achieved successfully -``` - -这组信息形成“写入完成 -> 芯片复位 -> 应用启动”的顺序证据。Studio 调试视图中 `Thread #1 [core:0] (Running)`,以及 `ST-LINK_gdbserver.exe` 和 `arm-none-eabi-gdb.exe` 同时存在,则说明调试服务器和 GDB 客户端已建立运行中的会话。 - -### 3. 使用 thread_dump 观察线程 - -`thread_dump` 不只是显示“线程是否存在”,还可以用于解释调度和栈使用。例如输出中的字段可以这样理解: - -| 字段 | 含义 | 本次观察时的解释 | -| --- | --- | --- | -| `name` | 线程对象名 | 用于确认自动初始化创建的目标线程确实存在。 | -| `state` | 读取命令瞬间的内核状态 | LED 线程在 `rt_thread_mdelay()` 期间常显示 `suspend`,属于正常周期等待。 | -| `priority` | 当前调度优先级 | `th_monitor` 为 10,两个 LED 线程为 16;数值小表示优先级高。 | -| `remaining_stack` | 从栈底填充标记估算的未使用栈空间 | 例如 852/1024 表示当前观察点尚有较多余量,但仍应在最深调用路径下复查。 | -| `led1 control` | 实验自行维护的控制状态 | 用于区分“正常延时等待”与“按键要求 LED1 人工暂停”。 | - -截图中 `th_led1`、`th_led2` 的优先级均为 16,`th_monitor` 为 10;这与源码配置相符。两个 LED 线程同优先级,调度器允许它们依时间片公平竞争 CPU;不过它们大部分时间主动延时,因此不会持续占用处理器。 - -### 4. 首次按键暂停的判定过程 - -按键是低电平有效,monitor 线程每 20 ms 扫描一次。检测到低电平后,它先等待一个消抖周期,再读取一次按键;只有两次都为有效电平才接受本次按下。`key_armed` 在按键释放后重新置位,避免持续按住按键时反复切换状态。 - -本次的验证顺序如下: - -```text -上电并进入 msh - -> 输入 thread_dump - -> 看到 led1 control=periodic_running - -> 第一次有效按下 KEY0 - -> 20 ms 消抖通过 - -> monitor 设置 led1_paused = RT_TRUE - -> 输出 led1 control=manual_suspend - -> LED1 在下一次循环检查到暂停标志后等待 led1_resume_sem -``` - -这里的关键判断不是三个线程的 `state=suspend`。因为 LED 线程每次调用 `rt_thread_mdelay()` 也会进入挂起态,单看 `state` 无法辨别是正常延时还是按键控制。`led1 control=manual_suspend` 是本实验专门增加的可观测状态,因此可作为首次按键暂停已触发的可靠软件证据。 - -源码中还包含后续恢复分支:再次有效按键会释放 `led1_resume_sem` 并清除暂停标志。该分支属于已实现的代码逻辑,但本次采集的验证记录按要求停在第一次按键后的 `manual_suspend`,不能将恢复写为已验证结果。 - -### 5. LED 现象与源码的对应边界 - -`th_led1` 和 `th_led2` 的 Tick 日志、线程状态和板卡灯光照片共同说明两个 LED 线程已被调度执行。源代码设置的周期分别为 500 ms 和 700 ms,因此理论上会表现为不同节奏。 - -但单张照片无法稳定证明“PF12 一定对应红色、PF11 一定对应蓝色”以及两个周期的逐一可见对应。板卡原理图、BSP 定义、有效电平和外设驱动方式都可能影响最终灯光。因此,当前结论是“线程已运行、板上灯光可见、首次按键的软件暂停状态已确认”;若要证明颜色与节奏的完整映射,应补充一段连续视频或完整连续串口记录。 - -## 十二、建议的增量实现与排错顺序 - -将作业拆成小步验证,定位效率最高: - -```text -1. 仅保留 thread_study_init() - -> 确认 INIT_APP_EXPORT 已执行、GPIO 初始化无误 - -2. 加入 th_led1(动态) - -> 构建、下载,确认单线程周期行为 - -3. 加入 th_led2(静态) - -> 用 thread_dump 确认两个线程名称、优先级和栈 - -4. 加入 th_monitor 与 MSH 命令 - -> 确认终端可查看状态,不开启高频自动打印 - -5. 加入按键消抖与信号量等待 - -> 先验证 periodic_running,再验证首次 manual_suspend -``` - -若构建失败,先检查 `thread_study.c` 是否被所在目录的 `SConscript` 收集、相关 Kconfig 宏是否启用,再看编译日志中的第一个真实错误。若下载失败,检查 ST-Link 连接、目标芯片和下载配置;若没有串口输出,检查当前 VCP 编号、串口参数、控制台 UART 和固件版本。不要把多个层级的问题混在一起修改。 - -## 十三、实验记录模板 - -一次可复核的 Day2 验证记录应至少按下列顺序保存: - -```text -1. 板卡上电和 ST-Link VCP 枚举 -2. Studio 零错误构建结果及 Flash/RAM 占用 -3. 下载完成、复位和应用启动日志 -4. RT-Thread 启动横幅与 msh > -5. thread_dump 的初始输出:三个线程和 periodic_running -6. 第一次按键后的 thread_dump:manual_suspend -``` - -使用该顺序可以让其他人从证据中复现“工程编译成功、程序已实际运行、线程已创建、按键控制已触发”的完整路径。无截图或日志支持的内容,应明确标记为设计说明、待验证项或后续建议,而不是写成测试结论。 - -## 十四、小结 - -Day2 把“线程如何创建”延伸到“线程何时得到 CPU、何时等待、如何观察和如何被事件控制”。动态与静态线程的区别在内存来源,抢占和时间片决定运行次序,自动初始化把应用准备工作接入系统启动阶段。构建成功、下载成功、串口运行成功和实体按键现象属于不同证据层级,需要分别记录。 diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/Day2\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/Day2\344\275\234\344\270\232.md" deleted file mode 100644 index 63c07a94f6cd10d1e5cdd326be465a35131c36f3..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/Day2\344\275\234\344\270\232.md" +++ /dev/null @@ -1,139 +0,0 @@ -# 【DAY2】作业 - -## Part 1:理论知识梳理文档 - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -- **启动路径**:详细画出/写出从上电复位 `Reset_Handler` 到进入 `main()` 的完整调用链。 - - ```mermaid - flowchart TD - A[上电/复位] --> B[硬件读向量表] - B --> C[SP = _estack] - B --> D[PC = Reset_Handler] - - D --> E[拷贝 .data] - E --> F[清零 .bss] - F --> G[SystemInit] - G --> H[entry] - - H --> I[rtthread_startup] - - I --> J[rt_hw_board_init] - J --> J1[rt_system_heap_init] - J1 --> J2[hw_board_init] - J2 --> J2a[HAL_Init] - J2a --> J2b[clk_init 配置系统时钟] - J2b --> J2c[rt_hw_systick_init] - J2c --> J2d[rt_hw_pin/usart_init] - J2d --> J3[rt_console_set_device] - J3 --> J4[rt_components_board_init] - - I --> K[rt_system_timer_init] - K --> L[rt_system_scheduler_init] - L --> M[rt_application_init] - M --> M1[rt_thread_create main] - M1 --> M2[main_thread_entry 作为线程入口] - I --> N[rt_system_timer_thread_init] - N --> O[rt_thread_idle_init] - O --> P[rt_system_scheduler_start] - P --> Q[rt_hw_context_switch_to] - Q --> R[PendSV 上下文切换] - R --> S[main_thread_entry 在线程上下文执行] - - S --> T[rt_components_init] - T --> U[main 用户代码] - ``` - - --- - -- **自动初始化原理**:解释 `INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` 的底层实现原理。 - - 把函数指针放进 Flash 里一个专用段(`.rti_fn.x`),链接器按段名排序拼成一张“初始化函数表”,启动时 C 代码遍历这张表依次调用。 - - ![image-20260819014705480](fig/image-20260819014705480.png) - - - -### 2. 内核基础与对象模型 - -- **对象容器与控制块**:简述 RT-Thread 面向对象的设计思想,线程控制块(`struct rt_thread`)中关键成员(如 `stat`、`current_priority`、`init_tick`、`sp`)的作用。 - - RT-Thread 的内核并不是 C++ 意义上的类继承,而是用 C 语言结构体 + 对象容器 模拟面向对象: - - - 统一基类:所有内核对象都有 `struct rt_object` 公共头部 - - - “继承”:子对象把 `rt_object` 放在结构体最前面,共享同一套管理机制 - - - 对象容器:每类对象有一个全局链表,用于注册、查找、枚举 - - | 成员 | 作用 | - | :----------------- | :----------------------------------------------------------- | - | `stat` | 线程生命周期状态:调度器依据它决定能否被切换、挂起或销毁 | - | `current_priority` | 当前有效优先级。数值越小优先级越高。 | - | `init_tick` | 创建时设定的时间片(单位:系统 tick)。同优先级线程轮转时使用 | - | `sp` | 线程栈顶指针。`PendSV_Handler` 上下文切换时读写的就是它; | - -- **内存分配方式**:对比静态对象与动态对象的区别(分配位置、生命周期、释放机制),分析为什么某些高可靠场景强制使用静态线程。 - - | 静态对象 | 动态对象 | | - | :---------- | :------------------------------------------ | ----------------------------------------------------------- | - | 创建 API | `rt_xxx_init()` + 用户提供的内存 | `rt_xxx_create()` 内部 `malloc` | - | 控制块位置 | 全局变量 / 栈 / 用户指定 BSS 区 | 堆(heap) | - | 栈/资源位置 | 用户静态数组,如 `main_stack[]` | `RT_KERNEL_MALLOC(stack_size)` | - | 释放 API | `rt_xxx_detach()`:从容器摘除,不 free 内存 | `rt_xxx_delete()`:挂到 defunct 队列,idle 线程里 free 内存 | - | 前提条件 | 无堆也能用 | 必须 `RT_USING_HEAP` | - -为什么高可靠场景倾向静态线程? -1. 内存确定性(无堆碎片、无分配失败) -2. 生命周期可控,静态对象 不会误 free,detach 只注销不释放内存 - -### 3. 线程状态机与调度算法 - -- **五态模型**:绘制或描述 RT-Thread 线程状态(初始态、就绪态、运行态、挂起态、关闭态)及触发状态转换的典型 API。 - - | 状态 | 含义 | 是否在就绪队列 | - | :------ | :------------------------------------------- | :-------------------------------------- | - | INIT | 已创建/初始化,尚未启动 | 否 | - | READY | 具备运行条件,等待 CPU | 是(挂在 `rt_thread_priority_table[]`) | - | RUNNING | 正在占用 CPU(单核下即 `rt_current_thread`) | 否(已从就绪队列移除) | - | SUSPEND | 阻塞/睡眠,不参与调度 | 否(可能在 IPC 等待链或挂起链) | - | CLOSE | 逻辑死亡,等待 idle 回收 | 否 | - - ![线程状态转换图](fig/04thread_sta.png) - - **调度机制剖析**: - - - **抢占式调度**:一旦就绪队列中出现 比当前线程优先级更高 的线程,内核会 立即(在 rt_schedule() 调用点)剥夺当前 CPU,切换到高优先级线程。 - - - **时间片轮转**:仅当 多个 READY 线程处于同一优先级 时,在它们之间轮流分配 CPU - - RT-Thread 是 基于优先级的抢占式调度,同优先级之间再辅以 时间片轮转。 - - - -## Part 2:多线程实战编程(C 语言实现) - -### 代码思路 - -1个任务一个线程,线程间通过消息通信 - -** th_led1 动态创建,翻转 LED_R,打印计数与 tick* - - ** th_led2 静态初始化,翻转 LED_B,打印计数* - - ** th_monitor 监控两线程状态/栈,按键切换 th_led1 挂起与恢复* - - ** th_key 轮询 GPIO_BTN_LEFT,释放信号量通知 monitor* - -详细代码见代码文件 - - ### 实验结果 - -按下按键1次 - -![image-20260819040748843](fig/image-20260819040748843.png) - -第2次按下恢复 - -![image-20260819040932552](fig/image-20260819040932552.png) \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/04thread_sta.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/04thread_sta.png" deleted file mode 100644 index fe2cc8141fd5c220db0fca600c75bd9b8ea66a92..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/04thread_sta.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818223913304.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818223913304.png" deleted file mode 100644 index 0762716ecde3e35008840cb4795c558959e056d9..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818223913304.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818225546819.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818225546819.png" deleted file mode 100644 index 09b0eba96d4cfbff10c475d03d151eb4c3af9676..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818225546819.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818230329653.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818230329653.png" deleted file mode 100644 index 2aab668cd93d15cc5ffdf6c29492ba1e7e560891..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818230329653.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233336472.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233336472.png" deleted file mode 100644 index a8d0de7e836b2add91bad08afc78555d99c98a2d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233336472.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233557188.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233557188.png" deleted file mode 100644 index dc0d27f3b2aae9141b6dbd18edea85722584958b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233557188.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233611744.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233611744.png" deleted file mode 100644 index 8c89bd4b48e47777e5fb7acf7ebd8d1b00bedf0f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233611744.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233810253.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233810253.png" deleted file mode 100644 index 95c4c19102d992a81346ab9fa8f9dbc238be54f8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233810253.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233832060.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233832060.png" deleted file mode 100644 index 7849e4aea55f2bd926f39b3e7bfb4c313718268d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260818233832060.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819001927320.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819001927320.png" deleted file mode 100644 index c9fc4277066dc8adc2c9ee8edae06ca2c161d4bb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819001927320.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819001950725.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819001950725.png" deleted file mode 100644 index 3c1bdb0adaf8dd409457f5a0828af8610fc36967..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819001950725.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819010945365.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819010945365.png" deleted file mode 100644 index c635a6df9c6f27dcffdea35061f8b4a43a57a224..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819010945365.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012000651.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012000651.png" deleted file mode 100644 index a6a8afd1db23571bfb52cfda19a2103d240ac00e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012000651.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012047296.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012047296.png" deleted file mode 100644 index c5497f41d6d8050ebeee13608d41f443f46c0d69..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012047296.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012422113.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012422113.png" deleted file mode 100644 index 034076fb67cd8992557210ca2ea9fb2306e66d63..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012422113.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012756955.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012756955.png" deleted file mode 100644 index 5b5d47c4c3a71e3b18c4ab32d3bf9affb05f0ee9..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819012756955.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819013529094.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819013529094.png" deleted file mode 100644 index 30171919ac8241a977c4454d72e80aefac94b887..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819013529094.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819014605381.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819014605381.png" deleted file mode 100644 index a495da1f94ba7cf5d4995dd9bb81bb9afabdfdc6..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819014605381.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819014705480.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819014705480.png" deleted file mode 100644 index 9d4450ecf614d8a729dd65290edb6342a8b9822b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819014705480.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819040748843.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819040748843.png" deleted file mode 100644 index 3a611eaf555ad3ec23ba89d4e1f19f4f01807a3e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819040748843.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819040932552.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819040932552.png" deleted file mode 100644 index 36d743a18d3ab2a1a72f236633226d64440e2b92..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260819040932552.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025750400.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025750400.png" deleted file mode 100644 index c4abf840140f989a691df49870d9eab610e412ac..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025750400.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025821424.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025821424.png" deleted file mode 100644 index 1702e3f8a097794363bd1055ae5ad9b1248757c2..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025821424.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025900537.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025900537.png" deleted file mode 100644 index 72d12c96c808e48aa1edb024621fac856ce0a30a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025900537.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025926758.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025926758.png" deleted file mode 100644 index 78469e3b38c01728185b9443b21acc98dc36a8a4..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/image-20260820025926758.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/rtt_startup.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/rtt_startup.png" deleted file mode 100644 index 04dbb79fed05b7463c5ceea179f5dd75c6553724..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/fig/rtt_startup.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/thread_led.c" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/thread_led.c" deleted file mode 100644 index 555aa170fb8818808dc3fb4987bd01a5ef3260b4..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/thread_led.c" +++ /dev/null @@ -1,592 +0,0 @@ -/* - * 多线程实战:LED 工作线程 + 监控控制线程 - * - * th_led1 动态创建,翻转 LED_R,打印计数与 tick - * th_led2 静态初始化,翻转 LED_B,打印计数 - * th_monitor 监控两线程状态/栈,按键切换 th_led1 挂起与恢复 - * th_key 轮询 GPIO_BTN_LEFT,释放信号量通知 monitor - */ -#include -#include -#include - -#ifndef RT_USING_DM -#define GPIO_LED_B GET_PIN(F, 11) -#define GPIO_LED_R GET_PIN(F, 12) -#define GPIO_BTN_LEFT GET_PIN(C, 0) /* 板载 KEY_LEFT,按下接 GND 为低电平 */ -#endif - -/* LED 工作线程配置 */ -#define LED_THREAD_PRIORITY 16U -#define LED_THREAD_STACK_SIZE 1024U -#define LED_THREAD_TIMESLICE 5U -#define LED_BLINK_INTERVAL_MS 500U - -/* 监控线程配置 */ -#define MONITOR_THREAD_PRIORITY 10U -#define MONITOR_THREAD_STACK_SIZE 1024U -#define MONITOR_THREAD_TIMESLICE 5U -#define MONITOR_PERIOD_MS 2000U - -/* 按键扫描线程配置 */ -#define KEY_THREAD_PRIORITY 15U -#define KEY_THREAD_STACK_SIZE 512U -#define KEY_THREAD_TIMESLICE 5U -#define KEY_DEBOUNCE_MS 100U -#define KEY_SCAN_INTERVAL_MS 10U - -/* 按键信号量配置 */ -#define KEY_SEM_NAME "key_sem" -#define KEY_SEM_INIT_VALUE 0U -#define STACK_FILL_MARK '#' - -static rt_thread_t g_pThLed1 = RT_NULL; -static rt_thread_t g_pThMonitor = RT_NULL; -static rt_thread_t g_pThKey = RT_NULL; -static rt_sem_t g_pKeySem = RT_NULL; -static rt_bool_t g_led1Suspended = RT_FALSE; - -static char g_thLed2Stack[LED_THREAD_STACK_SIZE]; -static struct rt_thread g_thLed2; - -/************************************************************************ -函数名 : getThreadSchedStatStr -功能描述: 获取线程调度状态字符串 -输入参数: stat -- 线程状态值 -输出参数: 无 -返回值 : 状态字符串指针 -************************************************************************/ -static const char *getThreadSchedStatStr(rt_uint8_t stat) -{ - const char *pResult = "unknown"; - rt_uint8_t schedStat = (rt_uint8_t)(stat & RT_THREAD_STAT_MASK); - - if (RT_THREAD_READY == schedStat) - { - pResult = "ready"; - } - else if (RT_THREAD_SUSPEND_MASK == (schedStat & RT_THREAD_SUSPEND_MASK)) - { - pResult = "blocked"; - } - else if (RT_THREAD_INIT == schedStat) - { - pResult = "init"; - } - else if (RT_THREAD_CLOSE == schedStat) - { - pResult = "close"; - } - else if (RT_THREAD_RUNNING == schedStat) - { - pResult = "running"; - } - else - { - pResult = "unknown"; - } - - return pResult; -} - -/************************************************************************ -函数名 : getThreadStackRemaining -功能描述: 计算线程剩余栈空间 -输入参数: pThread -- 线程句柄 -输出参数: 无 -返回值 : 剩余栈字节数 -************************************************************************/ -static rt_uint32_t getThreadStackRemaining(struct rt_thread *pThread) -{ - rt_uint8_t *pPtr = RT_NULL; - rt_uint32_t remainBytes = 0U; - rt_bool_t isValid = RT_FALSE; - - if (RT_NULL != pThread) - { - if (RT_NULL != pThread->stack_addr) - { - if (0U < pThread->stack_size) - { - isValid = RT_TRUE; - } - else - { - isValid = RT_FALSE; - } - } - else - { - isValid = RT_FALSE; - } - } - else - { - isValid = RT_FALSE; - } - - if (RT_TRUE == isValid) - { -#if defined(ARCH_CPU_STACK_GROWS_UPWARD) - pPtr = (rt_uint8_t *)pThread->stack_addr + pThread->stack_size - 1U; - while (STACK_FILL_MARK == *pPtr) - { - pPtr--; - } - remainBytes = (rt_uint32_t)(pPtr - (rt_uint8_t *)pThread->stack_addr); -#else - pPtr = (rt_uint8_t *)pThread->stack_addr; - while (STACK_FILL_MARK == *pPtr) - { - pPtr++; - } - remainBytes = pThread->stack_size - - (rt_uint32_t)(pPtr - (rt_uint8_t *)pThread->stack_addr); -#endif - } - else - { - remainBytes = 0U; - } - - return remainBytes; -} - -/************************************************************************ -函数名 : monitorPrintThread -功能描述: 打印线程监控信息 -输入参数: pThread -- 线程句柄 -输出参数: 无 -返回值 : 无 -************************************************************************/ -static void monitorPrintThread(struct rt_thread *pThread) -{ - rt_uint8_t stat = 0U; - const char *pCtrlStr = "n/a"; - rt_bool_t isLed1Thread = RT_FALSE; - - if (RT_NULL == pThread) - { - rt_kprintf("[monitor] thread is NULL\n"); - } - else - { - stat = RT_SCHED_CTX(pThread).stat; - if ((RT_NULL != g_pThLed1) && (pThread == g_pThLed1)) - { - isLed1Thread = RT_TRUE; - } - else - { - isLed1Thread = RT_FALSE; - } - - if (RT_TRUE == isLed1Thread) - { - if (RT_TRUE == g_led1Suspended) - { - pCtrlStr = "suspended"; - } - else - { - pCtrlStr = "running"; - } - } - else - { - pCtrlStr = "n/a"; - } - - rt_kprintf("[monitor] %s: ctrl=%s, sched=%s, stack_remain=%u\n", - pThread->parent.name, - pCtrlStr, - getThreadSchedStatStr(stat), - getThreadStackRemaining(pThread)); - } -} - -/************************************************************************ -函数名 : monitorToggleLed1 -功能描述: 切换 th_led1 挂起与恢复状态 -输入参数: 无 -输出参数: 无 -返回值 : 无 -************************************************************************/ -static void monitorToggleLed1(void) -{ - if (RT_NULL == g_pThLed1) - { - rt_kprintf("[monitor] th_led1 is NULL\n"); - } - else - { - if (RT_FALSE == g_led1Suspended) - { - rt_thread_suspend(g_pThLed1); - g_led1Suspended = RT_TRUE; - rt_kprintf("[monitor] th_led1 suspended\n"); - } - else - { - rt_thread_resume(g_pThLed1); - g_led1Suspended = RT_FALSE; - rt_kprintf("[monitor] th_led1 resumed\n"); - } - } -} - -/************************************************************************ -函数名 : monitorHandleKey -功能描述: 处理按键信号量,切换 th_led1 挂起与恢复 -输入参数: 无 -输出参数: 无 -返回值 : 无 -************************************************************************/ -static void monitorHandleKey(void) -{ - rt_err_t takeResult = RT_EOK; - - if (RT_NULL == g_pKeySem) - { - rt_kprintf("[monitor] key semaphore is NULL\n"); - } - else - { - takeResult = rt_sem_trytake(g_pKeySem); - while (RT_EOK == takeResult) - { - monitorToggleLed1(); - takeResult = rt_sem_trytake(g_pKeySem); - } - } -} - -/************************************************************************ -函数名 : thKeyEntry -功能描述: 按键扫描线程入口,检测 GPIO_BTN_LEFT 并释放信号量 -输入参数: pParameter -- 线程参数 -输出参数: 无 -返回值 : 无 -************************************************************************/ -static void thKeyEntry(void *pParameter) -{ - rt_base_t pinLevel = PIN_HIGH; - - RT_UNUSED(pParameter); - - while (1) - { - pinLevel = rt_pin_read(GPIO_BTN_LEFT); - if (PIN_LOW == pinLevel) - { - rt_thread_mdelay(KEY_DEBOUNCE_MS); - pinLevel = rt_pin_read(GPIO_BTN_LEFT); - if (PIN_LOW == pinLevel) - { - if (RT_NULL != g_pKeySem) - { - rt_sem_release(g_pKeySem); - rt_kprintf("[key] GPIO_BTN_LEFT pressed\n"); - } - else - { - rt_kprintf("[key] key semaphore is NULL\n"); - } - - pinLevel = rt_pin_read(GPIO_BTN_LEFT); - while (PIN_LOW == pinLevel) - { - rt_thread_mdelay(KEY_SCAN_INTERVAL_MS); - pinLevel = rt_pin_read(GPIO_BTN_LEFT); - } - } - else - { - /* 消抖失败,忽略本次抖动 */ - } - } - else - { - /* 按键未按下,继续扫描 */ - } - - rt_thread_mdelay(KEY_SCAN_INTERVAL_MS); - } -} - -/************************************************************************ -函数名 : thLed1Entry -功能描述: 工作线程1入口,翻转 LED_R 并打印计数与 tick -输入参数: pParameter -- 线程参数 -输出参数: 无 -返回值 : 无 -************************************************************************/ -static void thLed1Entry(void *pParameter) -{ - rt_uint32_t count = 0U; - rt_base_t ledLevel = PIN_LOW; - - RT_UNUSED(pParameter); - - while (1) - { - if (PIN_LOW == ledLevel) - { - ledLevel = PIN_HIGH; - } - else - { - ledLevel = PIN_LOW; - } - - rt_pin_write(GPIO_LED_R, ledLevel); - rt_kprintf("th_led1 count: %u, tick: %u\n", count, rt_tick_get()); - count++; - rt_thread_mdelay(LED_BLINK_INTERVAL_MS); - } -} - -/************************************************************************ -函数名 : thLed2Entry -功能描述: 工作线程2入口,翻转 LED_B 并打印计数 -输入参数: pParameter -- 线程参数 -输出参数: 无 -返回值 : 无 -************************************************************************/ -static void thLed2Entry(void *pParameter) -{ - rt_uint32_t count = 0U; - rt_base_t ledLevel = PIN_LOW; - - RT_UNUSED(pParameter); - - while (1) - { - if (PIN_LOW == ledLevel) - { - ledLevel = PIN_HIGH; - } - else - { - ledLevel = PIN_LOW; - } - - rt_pin_write(GPIO_LED_B, ledLevel); - rt_kprintf("th_led2 count: %u\n", count); - count++; - rt_thread_mdelay(LED_BLINK_INTERVAL_MS); - } -} - -/************************************************************************ -函数名 : thMonitorEntry -功能描述: 监控线程入口,周期打印线程状态并处理按键 -输入参数: pParameter -- 线程参数 -输出参数: 无 -返回值 : 无 -************************************************************************/ -static void thMonitorEntry(void *pParameter) -{ - rt_err_t takeResult = RT_EOK; - - RT_UNUSED(pParameter); - - while (1) - { - if (RT_NULL == g_pKeySem) - { - rt_thread_mdelay(MONITOR_PERIOD_MS); - } - else - { - takeResult = rt_sem_take(g_pKeySem, rt_tick_from_millisecond(MONITOR_PERIOD_MS)); - if (RT_EOK == takeResult) - { - monitorToggleLed1(); - monitorHandleKey(); - } - else - { - /* 超时未收到按键,继续周期监控 */ - } - } - - rt_kprintf("[monitor] ---- thread status ----\n"); - monitorPrintThread(g_pThLed1); - monitorPrintThread(&g_thLed2); - } -} - -/************************************************************************ -函数名 : createLed1Thread -功能描述: 动态创建并启动 th_led1 -输入参数: 无 -输出参数: 无 -返回值 : RT_EOK 成功,-RT_ERROR 失败 -************************************************************************/ -static int createLed1Thread(void) -{ - int result = RT_EOK; - - g_pThLed1 = rt_thread_create("th_led1", - thLed1Entry, - RT_NULL, - LED_THREAD_STACK_SIZE, - LED_THREAD_PRIORITY, - LED_THREAD_TIMESLICE); - if (RT_NULL == g_pThLed1) - { - rt_kprintf("create th_led1 failed.\n"); - result = -RT_ERROR; - } - else - { - rt_thread_startup(g_pThLed1); - } - - return result; -} - -/************************************************************************ -函数名 : createLed2Thread -功能描述: 静态初始化并启动 th_led2 -输入参数: 无 -输出参数: 无 -返回值 : RT_EOK 成功,-RT_ERROR 失败 -************************************************************************/ -static int createLed2Thread(void) -{ - int result = RT_EOK; - - rt_thread_init(&g_thLed2, - "th_led2", - thLed2Entry, - RT_NULL, - &g_thLed2Stack[0], - sizeof(g_thLed2Stack), - LED_THREAD_PRIORITY, - LED_THREAD_TIMESLICE); - rt_thread_startup(&g_thLed2); - - return result; -} - -/************************************************************************ -函数名 : createKeyThread -功能描述: 动态创建并启动 th_key -输入参数: 无 -输出参数: 无 -返回值 : RT_EOK 成功,-RT_ERROR 失败 -************************************************************************/ -static int createKeyThread(void) -{ - int result = RT_EOK; - - g_pThKey = rt_thread_create("th_key", - thKeyEntry, - RT_NULL, - KEY_THREAD_STACK_SIZE, - KEY_THREAD_PRIORITY, - KEY_THREAD_TIMESLICE); - if (RT_NULL == g_pThKey) - { - rt_kprintf("create th_key failed.\n"); - result = -RT_ERROR; - } - else - { - rt_thread_startup(g_pThKey); - } - - return result; -} - -/************************************************************************ -函数名 : createMonitorThread -功能描述: 动态创建并启动 th_monitor -输入参数: 无 -输出参数: 无 -返回值 : RT_EOK 成功,-RT_ERROR 失败 -************************************************************************/ -static int createMonitorThread(void) -{ - int result = RT_EOK; - - g_pThMonitor = rt_thread_create("th_monitor", - thMonitorEntry, - RT_NULL, - MONITOR_THREAD_STACK_SIZE, - MONITOR_THREAD_PRIORITY, - MONITOR_THREAD_TIMESLICE); - if (RT_NULL == g_pThMonitor) - { - rt_kprintf("create th_monitor failed.\n"); - result = -RT_ERROR; - } - else - { - rt_thread_startup(g_pThMonitor); - } - - return result; -} - -/************************************************************************ -函数名 : threadLedStart -功能描述: 初始化引脚、信号量并创建启动各工作线程 -输入参数: 无 -输出参数: 无 -返回值 : RT_EOK 成功,-RT_ERROR 失败 -************************************************************************/ -static int threadLedStart(void) -{ - int result = RT_EOK; - -#ifndef RT_USING_DM - rt_pin_mode(GPIO_LED_R, PIN_MODE_OUTPUT); - rt_pin_mode(GPIO_LED_B, PIN_MODE_OUTPUT); - rt_pin_mode(GPIO_BTN_LEFT, PIN_MODE_INPUT_PULLUP); -#endif - - g_pKeySem = rt_sem_create(KEY_SEM_NAME, KEY_SEM_INIT_VALUE, RT_IPC_FLAG_PRIO); - if (RT_NULL == g_pKeySem) - { - rt_kprintf("create key semaphore failed.\n"); - result = -RT_ERROR; - } - else - { - result = createLed1Thread(); - } - - if (RT_EOK == result) - { - result = createLed2Thread(); - } - else - { - /* 前置步骤失败,跳过后续创建 */ - } - - if (RT_EOK == result) - { - result = createKeyThread(); - } - else - { - /* 前置步骤失败,跳过后续创建 */ - } - - if (RT_EOK == result) - { - result = createMonitorThread(); - } - else - { - /* 前置步骤失败,跳过后续创建 */ - } - - return result; -} -INIT_APP_EXPORT(threadLedStart); -MSH_CMD_EXPORT(threadLedStart, thread led sample); diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/\347\254\2543\345\244\251\344\275\234\344\270\232\345\220\214\346\255\245\344\270\216\351\200\232\344\277\241.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/\347\254\2543\345\244\251\344\275\234\344\270\232\345\220\214\346\255\245\344\270\216\351\200\232\344\277\241.md" deleted file mode 100644 index 80f6bbc1eece85ead56ac7b1dcac037b084a5e34..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/\347\254\2543\345\244\251\344\275\234\344\270\232\345\220\214\346\255\245\344\270\216\351\200\232\344\277\241.md" +++ /dev/null @@ -1,52 +0,0 @@ -# RT-Thread 线程间同步与通信课后作业 - ---- - -## 练习一:停车场车位管理 - -![image-20260820025750400](fig/image-20260820025750400.png) - -### 思考题 - -为什么这里使用计数信号量,而不是互斥量? - -互斥量解决的是同一时刻谁在用这一个资源;计数信号量解决的是还有几个同类资源可用。 - ---- - -## 练习二:多窗口售票系统 - -![image-20260820025821424](fig/image-20260820025821424.png) - -### 对比实验 - -先暂时不使用互斥量,并在“读取余票”和“修改余票”之间加入短暂延时,观察是否会出现重复售票或余票错误。 - -随后加入互斥量并再次运行,对比两次结果。 - -检查余票和减 1不是原子操作;中间延时让两个线程都能通过“还有票”的判断,再各自减 1,造成丢失更新。 - -### 思考题 - -为什么只给 `ticket_count--` 加锁仍可能不安全? - -锁的范围太小,没有保护“检查 + 减 1”这个组合。 - ---- - -## 练习三:系统启动条件检查 - -### ![image-20260820025900537](fig/image-20260820025900537.png) - -如果用三个独立信号量实现这个功能,与使用事件集相比有什么区别? - -事件集,一个对象、多个标志位、支持 AND/OR 组合等待,实现方便。 - -三个独立信号量,三个独立「通知」,各管各的,实现复杂。 - ---- - -## 练习四:快递分拣中心 - -![image-20260820025926758](fig/image-20260820025926758.png) - diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/day2\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/day2\347\254\224\350\256\260.md" deleted file mode 100644 index 81be08344b6beeb30f54eb0d56751f21a51e6feb..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/day2\347\254\224\350\256\260.md" +++ /dev/null @@ -1,40 +0,0 @@ -# day2笔记 - -## 1.env环境 - -```bash -# 1.生成vscode工程 - scons --target=vsc -#2.vscode打开 -code . -#3.vscode编译 -scons -j20 -#4.打包工程 -scons --dist -menuconfig #设置配置 -``` - -![image-20260818223913304](fig/image-20260818223913304.png) - -![image-20260818225546819](fig/image-20260818225546819.png) - -## 2.RT-Thread 启动流程 - -![启动流程](fig/rtt_startup.png) - -stm32f407汇编文件 - -![image-20260818230329653](fig/image-20260818230329653.png) - -上电或复位后,CPU 最终会执行到 `Reset_Handler` - - - -![image-20260818233336472](fig/image-20260818233336472.png) - -![image-20260818233611744](fig/image-20260818233611744.png) - -## 3.线程 - -静态和动态创建 - diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/04thread_sta.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/04thread_sta.png" deleted file mode 100644 index fe2cc8141fd5c220db0fca600c75bd9b8ea66a92..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/04thread_sta.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260817214624931.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260817214624931.png" deleted file mode 100644 index 13016fe99251a3908f4157fe2fde1bc2b4598849..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260817214624931.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260817215420407.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260817215420407.png" deleted file mode 100644 index c3cffcaf0b087068df49fa1eefeecc7729ba44f2..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260817215420407.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260817220126643.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260817220126643.png" deleted file mode 100644 index 0101249e39720bf348a6185cc5322f8452f26a55..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260817220126643.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818002017158.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818002017158.png" deleted file mode 100644 index c66f22ace1eca3b6c1bedafeae362c5e9205cfbb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818002017158.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818003229547.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818003229547.png" deleted file mode 100644 index ceb6ed7b3cba3a3a4cb6a5c893e62b46156604dd..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818003229547.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818003519803.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818003519803.png" deleted file mode 100644 index 260ce07416d706d59358d314a893d616fd40aebe..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818003519803.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818005557910.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818005557910.png" deleted file mode 100644 index f8c994dd0a4f2a126de3833fb72d12266b6e1909..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818005557910.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818223913304.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818223913304.png" deleted file mode 100644 index 0762716ecde3e35008840cb4795c558959e056d9..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818223913304.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818225546819.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818225546819.png" deleted file mode 100644 index 09b0eba96d4cfbff10c475d03d151eb4c3af9676..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818225546819.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818230329653.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818230329653.png" deleted file mode 100644 index 2aab668cd93d15cc5ffdf6c29492ba1e7e560891..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818230329653.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233336472.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233336472.png" deleted file mode 100644 index a8d0de7e836b2add91bad08afc78555d99c98a2d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233336472.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233557188.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233557188.png" deleted file mode 100644 index dc0d27f3b2aae9141b6dbd18edea85722584958b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233557188.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233611744.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233611744.png" deleted file mode 100644 index 8c89bd4b48e47777e5fb7acf7ebd8d1b00bedf0f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233611744.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233810253.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233810253.png" deleted file mode 100644 index 95c4c19102d992a81346ab9fa8f9dbc238be54f8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233810253.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233832060.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233832060.png" deleted file mode 100644 index 7849e4aea55f2bd926f39b3e7bfb4c313718268d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260818233832060.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819001927320.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819001927320.png" deleted file mode 100644 index c9fc4277066dc8adc2c9ee8edae06ca2c161d4bb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819001927320.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819001950725.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819001950725.png" deleted file mode 100644 index 3c1bdb0adaf8dd409457f5a0828af8610fc36967..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819001950725.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819010945365.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819010945365.png" deleted file mode 100644 index c635a6df9c6f27dcffdea35061f8b4a43a57a224..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819010945365.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012000651.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012000651.png" deleted file mode 100644 index a6a8afd1db23571bfb52cfda19a2103d240ac00e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012000651.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012047296.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012047296.png" deleted file mode 100644 index c5497f41d6d8050ebeee13608d41f443f46c0d69..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012047296.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012422113.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012422113.png" deleted file mode 100644 index 034076fb67cd8992557210ca2ea9fb2306e66d63..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012422113.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012756955.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012756955.png" deleted file mode 100644 index 5b5d47c4c3a71e3b18c4ab32d3bf9affb05f0ee9..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819012756955.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819013529094.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819013529094.png" deleted file mode 100644 index 30171919ac8241a977c4454d72e80aefac94b887..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819013529094.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819014605381.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819014605381.png" deleted file mode 100644 index a495da1f94ba7cf5d4995dd9bb81bb9afabdfdc6..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819014605381.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819014705480.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819014705480.png" deleted file mode 100644 index 9d4450ecf614d8a729dd65290edb6342a8b9822b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819014705480.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819040748843.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819040748843.png" deleted file mode 100644 index 3a611eaf555ad3ec23ba89d4e1f19f4f01807a3e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819040748843.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819040932552.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819040932552.png" deleted file mode 100644 index 36d743a18d3ab2a1a72f236633226d64440e2b92..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/image-20260819040932552.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/rtt_startup.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/rtt_startup.png" deleted file mode 100644 index 04dbb79fed05b7463c5ceea179f5dd75c6553724..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/fig/rtt_startup.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 714df275a0871e7a9362d0da87ff307059ea05e8..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\346\235\216\346\224\277/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,78 +0,0 @@ - - -# day1 - -## 1.辅助工具 - -### 辅助软件 - -![image-20260817212709431](C:\Users\rxm\AppData\Roaming\Typora\typora-user-images\image-20260817212709431.png) - -## 2文档语法 - -代码 - -``` -``` - -```c - -``` - -表格 - -| | | -| ---- | ---- | -| | | - -特殊 - -`q` - -**B** - -## 3代码架构 - -内核源码 对象思想 **4.1.1版本** - -![image-20260817215420407](fig/image-20260817215420407.png) - -## 4.配置git - -### 1用户名 - -设置用户名:`git config --global user.name "你的名字"` - -设置邮箱:`git config --global user.email "你的邮箱` - -![image-20260818002017158](fig/image-20260818002017158.png) - -### 2.密钥 - -ssh-keygen -t rsa -b 4096 -C "your_email@example.com" - -```bash -cat /c/Users/rxm/.ssh/id_rsa.pub -``` - -![image-20260818003229547](fig/image-20260818003229547.png) - -![image-20260818003519803](fig/image-20260818003519803.png) - -验证 - -```bash - ssh -T git@gitee.com -``` - -PR - -![image-20260818005557910](fig/image-20260818005557910.png) - -## 其他 - -任务点 - -![image-20260817220126643](fig/image-20260817220126643.png) - -## \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 401c69ee199357e2ab83c7aea22e91875fe1562e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,273 +0,0 @@ -# RT-Thread 线程间同步与通信课后作业 - -## 一、作业要求 - -要求: - -1. 不得直接复制 `kernel_samples` 示例后只修改变量名和打印内容。 -2. 每个练习至少创建两个线程。 -3. 所有线程和 IPC 对象都要检查创建结果。 -4. 程序需要通过 MSH 命令启动。 -5. 提交理论总结与实验报告(`README.md`)+ 源代码(`*.c`)。 - ---- - -## 练习一:停车场车位管理 - -### 场景 - -一个停车场只有 3 个车位,同时有 5 辆车尝试进入。车位属于有限资源,车辆只有获得车位后才能进入。 - -### 功能要求 - -1. 创建一个初始值为 3 的计数信号量,表示剩余车位。 -2. 创建 5 个车辆线程。 -3. 每辆车到达后申请一个车位。 -4. 获得车位后打印车辆编号和进入信息。 -5. 车辆停留一段时间后离开,并释放车位。 -6. 未在 2 秒内获得车位的车辆打印等待超时信息,稍后重新申请。 -7. 任意时刻进入停车场的车辆数不能超过 3。 - -### 实验分析 - -最开始三个车位(信号量)很快就被申请占满,car2、car1就都只能等待申请,等到其中一辆走了之后,car2就立即申请车位(信号量),car1同理,最后所有车位都清空。 - -![image-20260820144226421](../../笔记/figures/image-20260820144226421.png) - -### 思考题 - -为什么这里使用计数信号量,而不是互斥量? -停车场有 **3 个车位**,这是多个相同类型的资源。当一辆车进入时,占用一个车位(信号量值减 1);离开时释放一个车位(信号量值加 1)。 -计数信号量恰好能表达这种“剩余车位数量”的概念:初始值为 3,每辆车申请成功就减 1,当值变为 0 时,后续车辆必须等待。 -如果使用互斥量,则相当于停车场只有一个车位(互斥量只能表示“有/无”资源),无法同时容纳 3 辆车,不符合需求。 - ---- - -## 练习二:多窗口售票系统 - -### 场景 - -两个售票窗口同时出售同一批车票。系统初始有 20 张票,每次只能由一个窗口修改剩余票数。 - -### 功能要求 - -1. 创建两个售票线程,分别代表窗口 A 和窗口 B。 -2. 使用一个全局变量保存剩余票数。 -3. 每个窗口循环完成以下操作: - - 检查是否还有余票; - - 将余票减 1; - - 记录售出的票号; - - 打印售票结果。 -4. 使用互斥量保护完整的售票过程。 -5. 售完后两个线程自动退出。 -6. 最终必须满足: - - 20 张票全部售出; - - 不出现相同票号; - - 剩余票数不能小于 0。 - -### 实验分析 - -```text -window A sold ticket 20 -window B sold ticket 19 -window B sold ticket 18 -... -all tickets sold -remaining tickets: 0 -``` - -![image-20260820142203309](../../笔记/figures/image-20260820142203309-17872071368765.png) - -### 对比实验 - -先暂时不使用互斥量,并在“读取余票”和“修改余票”之间加入短暂延时,观察是否会出现重复售票或余票错误。随后加入互斥量并再次运行,对比两次结果。 - -不知道是什么原因,我不使用互斥量和使用互斥量的程序两次结果一模一样,但是如果变成“读取余票”和“修改余票”是两个不同的线程,而且不使用互斥量时就会出现重复售票或余票错误。 - -### 思考题 - -为什么只给 `ticket_count--` 加锁仍可能不安全? - -实际上,即使 `ticket_count--` 本身是原子操作(例如通过关中断或原子指令实现),也不能保证售票过程的正确性,因为**检查余票和减票是两个独立的操作**。 - -**竞态场景分析**: - -假设当前 `ticket_count = 1`,两个线程 A 和 B 几乎同时执行: - -1. **线程 A** 检查 `if (ticket_count > 0)`,条件为真,准备减票。 -2. **线程 B** 也检查 `if (ticket_count > 0)`,因为此时 A 还没有执行减票,`ticket_count` 仍为 1,条件也为真。 -3. **线程 A** 执行 `ticket_count--`,`ticket_count` 变为 0,成功售出一张票。 -4. **线程 B** 执行 `ticket_count--`(原子操作),`ticket_count` 变为 -1,超卖了一张票。 - -如果只给 `ticket_count--` 加锁,锁住的只是减一这个单一操作,并不能阻止两个线程都通过检查。必须将 **“检查余票 > 0”** 和 **“减票”** 作为一个整体临界区保护起来,才能避免这种“检查-修改”竞态。 - ---- - -## 练习三:系统启动条件检查 - -### 场景 - -设备启动前需要完成三个独立模块的初始化: - -- 网络模块初始化; -- 传感器模块初始化; -- 存储模块初始化。 - -只有三个模块全部准备完成,主业务线程才能启动。 - -PS:只是举例,模拟即可。 - -### 功能要求 - -定义三个事件位: - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -``` - -创建以下线程: - -- 网络初始化线程; -- 传感器初始化线程; -- 存储初始化线程; -- 主业务线程。 - -每个初始化线程经过不同时间的延时后,发送对应的就绪事件。 - -主业务线程必须使用 AND 模式等待三个事件全部到达,然后打印: - -```text -all modules are ready -business task started -``` - -另外增加一次超时测试: - -- 将存储模块初始化时间设置为 5 秒; -- 主业务线程最多等待 3 秒; -- 超时后打印尚未完成的模块,并再次等待。 - -### 实验分析 - -运行结果显示如果在程序运行中再次插入程序命令,会检测程序是否仍在运行并拒绝重复启动。 - -![image-20260820150431172](../../笔记/figures/image-20260820150431172.png) - -### 思考题 - -如果用三个独立信号量实现这个功能,与使用事件集相比有什么区别? - -**1. 等待语义的复杂性** - -- **事件集**:可以原子地等待**多个事件的组合**,支持 AND/OR 逻辑。主业务线程只需一次调用即可等待三个模块全部就绪,且可以设置超时并查询哪些事件未到达。 -- **三个信号量**:主业务线程需要分别获取三个信号量,无法在一次调用中判断“全部就绪”。通常需要额外的逻辑(如一个计数器或复合信号量)来实现“全部到达”的条件,代码复杂度高且容易出错。 - -**2. 状态查询能力** - -- **事件集**:通过 `rt_event_recv` 可以随时查询当前事件集合,便于打印未完成的模块(如超时后检查)。 -- **信号量**:只能获取计数值,无法直接得知哪个模块尚未初始化。 - -**3. 资源语义的匹配** - -- **事件集**:专门用于事件通知,表达“某事件是否发生”,适合表示模块初始化的完成状态。 -- **信号量**:本质是“资源计数”,更适用于管理可用资源的数量(如车位数)。若用来表示“初始化完成”,语义上并不直观。 - -**4. 清除与重入** - -- **事件集**:支持 `RT_EVENT_FLAG_CLEAR` 标志,在满足条件后自动清除事件位,方便重新运行演示。 -- **信号量**:获取后值会减一,若多个线程同时等待信号量,可能产生竞争;事件集则允许多个线程同时等待同一组事件。 - -**结论** - -事件集更适合“多个条件全部满足才继续”的场景,它提供了原子性的多事件等待、条件组合判断和状态查询能力,而信号量则更擅长资源计数和互斥访问。因此,系统启动条件检查应优先使用事件集。 - ---- - -## 练习四:快递分拣中心 - -### 场景 - -两个收件线程不断产生快递信息,一个分拣线程从消息队列中取出快递并按照目的地区域分类。 - -### 消息结构 - -```c -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -``` - -其中: - -```text -region = 1:华东 -region = 2:华南 -region = 3:华北 -``` - -### 功能要求 - -1. 创建一个消息队列,队列容量为 5 条消息。 -2. 创建两个发件线程,每个线程产生 5 条快递消息。 -3. 快递编号不得重复。 -4. 分拣线程接收消息,并打印快递编号、区域和重量。 -5. 分拣完成后统计: - - 总快递数量; - - 各区域快递数量; - - 所有快递的总重量。 -6. 当队列已满时,发送线程不得崩溃,应记录发送失败次数。 -7. 两个发送线程完成后,向分拣线程发送一条结束消息。 -8. 分拣线程收到结束消息后打印统计结果并退出。 - -### 实验分析 - -在分拣线程函数中,结束消息的接收要选择阻塞接收,可以用下面的代码,。 - -```c - while (rt_mq_send(pkg_mq, &msg, sizeof(msg)) != RT_EOK) - { - rt_thread_mdelay(rt_tick_from_millisecond(10)); - } -``` - -如果不使用阻塞接收而是,直接使用`rt_mq_send(pkg_mq, &msg, sizeof(msg));`的话会导致**分拣线程等不到第二个结束消息**。因为两个发送线程最后都会发送一条 `region == REGION_END` 的结束消息,但若其中任何一条(甚至两条都)因队列满而发送失败,分拣线程就永远等不到 `end_received >= 2`,于是卡死在 `rt_mq_recv()`,统计代码无法执行,只能重启系统。 - -![image-20260820141456076](../../笔记/figures/image-20260820141456076-17872071141291.png) - -这里我测试两种发送快递的延时,前一个是2ms,后一个是20ms,接收快递分类延时都是40ms,可以看到第二个程序比第一个要多接收一次快递消息。 - -首先讨论一下20ms发送消息的线程出现失败的原因 - -- 发送线程优先级高于分拣线程(数值更小,`29` vs `30`),所以两个发送线程更容易获得 CPU。 -- 发送线程每发送一条后只延时 `20 ms`;两个发送线程同时运行,平均约 `10 ms` 产生一条快递消息。 -- 分拣线程每收到一条快递要延时 `40 ms` 模拟分拣耗时,消费速度明显慢于生产速度。 - -生产快、消费慢,队列很快就会被填满。当队列已满时,`rt_mq_send()` 是非阻塞发送,会立即返回 `-RT_EFULL`,发送线程不会等待,于是这条快递消息被丢弃,失败计数加一。 -而加快发送消息之后,队列满的就更快了,所以会多一次发送失败的计数。 - -![image-20260820135936211](../../笔记/figures/image-20260820135936211-17872071184313.png) - ---- - -## 二、MSH 命令要求 - -每个练习导出一个独立命令: - -```text -parking_demo -ticket_demo -startup_demo -package_demo -``` - -重复执行命令时,不得因同名线程或 IPC 对象残留而发生异常。可以在任务结束后主动释放资源,也可以检测任务是否仍在运行并拒绝重复启动。 - -1、重复执行命令时不会发生异常; - -2、任务结束后主动释放资源,不会出现线程残留。 -![image-20260820151822129](../../笔记/figures/image-20260820151822129.png) \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" deleted file mode 100644 index 1e05baf1a531a38c3c34e6a1ce32742f543c8c3a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 godbear 练习四:快递分拣中心(消息队列) - *1. 创建一个消息队列,队列容量为 5 条消息。 - *2. 创建两个发件线程,每个线程产生 5 条快递消息。 - *3. 快递编号不得重复。 - *4. 创建一个分拣线程接收消息,并打印快递编号、区域和重量。 - *5. 分拣完成后统计: - - 总快递数量; - - 各区域快递数量; - - 所有快递的总重量。 - *6. 当队列已满时,发送线程不得崩溃,应记录发送失败次数。 - *7. 两个发送线程完成后,向分拣线程发送一条结束消息。 - *8. 分拣线程收到结束消息后打印统计结果并退出。 - */ - -#include -#include - -#define DBG_TAG "package" -#define DBG_LVL DBG_LOG -#include - -#define PKG_PER_SENDER 5 /* 每个发件线程产生 5 条 */ -#define PKG_QUEUE_SIZE 5 /* 消息队列容量 5 条 */ -#define REGION_END 0 /* 结束消息标志 */ - -struct package_msg -{ - rt_uint32_t id; /* 快递编号 */ - rt_uint8_t region; /* 目的区域 1=华东 2=华南 3=华北 */ - rt_uint16_t weight; /* 重量(g) */ -}; - -static rt_mq_t pkg_mq = RT_NULL; /* 消息队列控制块 */ -static volatile rt_uint32_t send_failed = 0; /* 队列满导致的发送失败次数 */ -static volatile rt_uint8_t pkg_running = 0; - -/* 发件线程:产生 5 条快递消息,最后发送一条结束消息 */ -static void sender_entry(void *param) -{ - rt_uint32_t base = (rt_uint32_t)(rt_size_t)param; /* 1000 或 2000 */ - struct package_msg msg; - rt_uint32_t i; - static const rt_uint8_t region_tab[PKG_PER_SENDER] = {1, 2, 3, 1, 2}; - static const rt_uint16_t weight_tab[PKG_PER_SENDER] = {1200, 850, 960, 1500, 1000}; - - for (i = 0; i < PKG_PER_SENDER; i++) - { - msg.id = base + i; - msg.region = region_tab[i]; - msg.weight = weight_tab[i]; - - /* 队列满时发送失败不崩溃,记录失败次数 */ - if (rt_mq_send(pkg_mq, &msg, sizeof(msg)) != RT_EOK) - { - rt_enter_critical(); - send_failed++; - rt_exit_critical(); - } - rt_thread_mdelay(rt_tick_from_millisecond(20)); - } - - /* 发送结束消息 */ - msg.id = 0; - msg.region = REGION_END; - msg.weight = 0; - /* 发送结束消息:失败则让出 CPU 重试,直到成功 */ - while (rt_mq_send(pkg_mq, &msg, sizeof(msg)) != RT_EOK) - { - rt_thread_mdelay(rt_tick_from_millisecond(10)); - } -} - -/* 分拣线程:接收并统计,收到两发件线程的结束消息后输出统计并退出 */ -static void sorter_entry(void *param) -{ - (void)param; - struct package_msg msg; - rt_uint32_t total = 0, total_weight = 0; - rt_uint32_t east = 0, south = 0, north = 0; - rt_uint32_t end_received = 0; - const char *region_name; - - while (1) - { - rt_err_t res = rt_mq_recv(pkg_mq, &msg, sizeof(msg), RT_WAITING_FOREVER); - if (res != RT_EOK) - { - rt_kprintf("mq recv failed\n"); - break; - } - - if (msg.region == REGION_END) - { - end_received++; - if (end_received >= 2) - break; - continue; - } - - switch (msg.region) - { - case 1: region_name = "east"; east++; break; - case 2: region_name = "south"; south++; break; - case 3: region_name = "north"; north++; break; - default: region_name = "unknown"; break; - } - total++; - total_weight += msg.weight; - - /* 模拟分拣耗时,使队列产生“满”的场景 */ - rt_thread_mdelay(rt_tick_from_millisecond(40)); - rt_kprintf("package %d -> %s, weight: %d g\n", msg.id, region_name, msg.weight); - } - - rt_kprintf("total packages: %d\n", total); - rt_kprintf("east: %d, south: %d, north: %d\n", east, south, north); - rt_kprintf("total weight: %d g\n", total_weight); - rt_kprintf("send failed: %d\n", send_failed); - - /* 清理资源,支持命令重复执行 */ - pkg_running = 0; - if (pkg_mq) - { - rt_mq_delete(pkg_mq); - pkg_mq = RT_NULL; - } -} - -static int package_demo(int argc, char *argv[]) -{ - rt_thread_t tid; - - if (pkg_running) - { - rt_kprintf("package_demo is already running, please wait...\n"); - return -RT_EBUSY; - } - - /* 创建容量为 5 条的消息队列 */ - pkg_mq = rt_mq_create("pkg_mq", sizeof(struct package_msg), PKG_QUEUE_SIZE, RT_IPC_FLAG_PRIO); - if (pkg_mq == RT_NULL) - { - rt_kprintf("create package message queue failed\n"); - return -RT_ENOMEM; - } - - send_failed = 0; - pkg_running = 1; - - /* 发件线程 A(编号 1001~1005) */ - tid = rt_thread_create("sndA", sender_entry, (void *)(rt_size_t)1000, - 1024, RT_THREAD_PRIORITY_MAX - 3, 10); - if (tid) - { - rt_thread_startup(tid); - } - else - { - rt_kprintf("create sender A thread failed\n"); - } - - /* 发件线程 B(编号 2001~2005) */ - tid = rt_thread_create("sndB", sender_entry, (void *)(rt_size_t)2000, - 1024, RT_THREAD_PRIORITY_MAX - 3, 10); - if (tid) - { - rt_thread_startup(tid); - } - else - { - rt_kprintf("create sender B thread failed\n"); - } - - /* 分拣线程 */ - tid = rt_thread_create("sorter", sorter_entry, RT_NULL, - 1024, RT_THREAD_PRIORITY_MAX - 2, 10); - if (tid) - { - rt_thread_startup(tid); - } - else - { - rt_kprintf("create sorter thread failed\n"); - pkg_running = 0; - rt_mq_delete(pkg_mq); - pkg_mq = RT_NULL; - return -RT_ERROR; - } - - return RT_EOK; -} -MSH_CMD_EXPORT(package_demo, express sorting demo with message queue); diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" deleted file mode 100644 index 1f4692daef3f35f36f2b4d95d2e3e521b07cb2d0..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 godbear 练习一:停车场车位管理(计数信号量) - */ - -#include -#include - -#define DBG_TAG "parking" -#define DBG_LVL DBG_LOG -#include - -#define PARK_SPACES 3 /* 车位总数 */ -#define PARK_CAR_NUM 5 /* 车辆数 */ -#define PARK_WAIT_MS 2000 /* 申请车位超时时间(ms) */ -#define PARK_STAY_MS 2500 /* 车辆停留时间(ms) */ - -static rt_sem_t park_sem = RT_NULL; -static volatile rt_uint8_t park_running = 0; /* 任务是否运行中 */ -static volatile rt_uint8_t park_entered = 0; /* 当前在场车辆数 */ -static volatile rt_uint8_t park_finished = 0; /* 已离场车辆数 */ -static volatile rt_uint8_t park_created = 0; /* 实际创建的车辆线程数 */ - -/* 车辆线程:申请车位 -> 停留 -> 离开并释放车位 */ -static void parking_car_entry(void *param) -{ - rt_uint8_t id = (rt_uint8_t)(rt_size_t)param; - rt_err_t result; - - while (1) - { - /* 带 2 秒超时申请车位 */ - result = rt_sem_take(park_sem, rt_tick_from_millisecond(PARK_WAIT_MS)); - if (result == RT_EOK) - { - /* 获得车位,进入停车场 */ - rt_enter_critical(); - park_entered++; - rt_uint8_t used = park_entered; - rt_exit_critical(); - rt_kprintf("car%d entered, used spaces: %d\n", id, used); - - /* 停留一段时间 */ - rt_thread_mdelay(rt_tick_from_millisecond(PARK_STAY_MS)); - - /* 离开并释放车位 */ - rt_enter_critical(); - park_entered--; - rt_exit_critical(); - rt_kprintf("car%d left\n", id); - rt_sem_release(park_sem); - break; - } - else - { - /* 2 秒内未获得车位,打印等待超时信息,稍后重新申请 */ - rt_kprintf("car%d waiting for a space\n", id); - rt_thread_mdelay(rt_tick_from_millisecond(100)); - } - } - - /* 所有车辆离场后清理资源,保证命令可重复执行 */ - rt_enter_critical(); - park_finished++; - if (park_finished >= park_created) - { - park_finished = 0; - park_entered = 0; - park_created = 0; - park_running = 0; - rt_exit_critical(); - - rt_kprintf("all cars have parked, used spaces: 0\n"); - if (park_sem) - { - rt_sem_delete(park_sem); - park_sem = RT_NULL; - } - } - else - { - rt_exit_critical(); - } -} - -static int parking_demo(int argc, char *argv[]) -{ - rt_uint8_t i; - - if (park_running) - { - rt_kprintf("parking_demo is already running, please wait...\n"); - return -RT_EBUSY; - } - - /* 创建初始值为 3 的计数信号量 */ - park_sem = rt_sem_create("park_sem", PARK_SPACES, RT_IPC_FLAG_PRIO); - if (park_sem == RT_NULL) - { - rt_kprintf("create parking semaphore failed\n"); - return -RT_ENOMEM; - } - - park_running = 1; - park_entered = 0; - park_finished = 0; - park_created = 0; - - /* 创建 5 个车辆线程 */ - for (i = 1; i <= PARK_CAR_NUM; i++) - { - char name[RT_NAME_MAX]; - rt_thread_t tid; - - rt_snprintf(name, sizeof(name), "pcar%d", i); - tid = rt_thread_create(name, parking_car_entry, (void *)(rt_size_t)i, - 1024, RT_THREAD_PRIORITY_MAX - 3, 10); - if (tid == RT_NULL) - { - rt_kprintf("create car thread %s failed\n", name); - continue; - } - rt_thread_startup(tid); - park_created++; - } - - if (park_created == 0) - { - park_running = 0; - rt_sem_delete(park_sem); - park_sem = RT_NULL; - return -RT_ERROR; - } - - return RT_EOK; -} -MSH_CMD_EXPORT(parking_demo, parking lot demo with counting semaphore); diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" deleted file mode 100644 index 9203aa07cf93775af3b64088764a27005b8bf2f8..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 godbear 练习三:系统启动条件检查(事件集 AND+超时) - */ - -#include -#include - -#define DBG_TAG "startup" -#define DBG_LVL DBG_LOG -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -static rt_event_t startup_evt = RT_NULL; -static volatile rt_uint32_t ready_flags = 0; /* 各模块就绪状态位 */ -static volatile rt_uint8_t startup_running = 0; - -/* 网络初始化线程:1 秒后就绪 */ -static void net_init_entry(void *param) -{ - (void)param; - rt_thread_mdelay(rt_tick_from_millisecond(1000)); - ready_flags |= EVENT_NET_READY; - rt_event_send(startup_evt, EVENT_NET_READY); - rt_kprintf("network ready\n"); -} - -/* 传感器初始化线程:2 秒后就绪 */ -static void sensor_init_entry(void *param) -{ - (void)param; - rt_thread_mdelay(rt_tick_from_millisecond(2000)); - ready_flags |= EVENT_SENSOR_READY; - rt_event_send(startup_evt, EVENT_SENSOR_READY); - rt_kprintf("sensor ready\n"); -} - -/* 存储初始化线程:5 秒后就绪(用于触发主线程 3 秒超时) */ -static void storage_init_entry(void *param) -{ - (void)param; - rt_thread_mdelay(rt_tick_from_millisecond(5000)); - ready_flags |= EVENT_STORAGE_READY; - rt_event_send(startup_evt, EVENT_STORAGE_READY); - rt_kprintf("storage ready\n"); -} - -/* 主业务线程:AND 模式等待三个事件全部到达,最多等 3 秒 */ -static void business_entry(void *param) -{ - (void)param; - - while (1) - { - rt_err_t res = rt_event_recv(startup_evt, EVENT_ALL, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - rt_tick_from_millisecond(3000), RT_NULL); - if (res == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - break; - } - else - { - /* 3 秒超时:打印尚未完成的模块,并再次等待 */ - if (!(ready_flags & EVENT_NET_READY)) - rt_kprintf("wait modules timeout, network is not ready\n"); - if (!(ready_flags & EVENT_SENSOR_READY)) - rt_kprintf("wait modules timeout, sensor is not ready\n"); - if (!(ready_flags & EVENT_STORAGE_READY)) - rt_kprintf("wait modules timeout, storage is not ready\n"); - } - } - - /* 此时三个初始化线程均已结束,可安全清理资源,支持命令重复执行 */ - rt_thread_mdelay(rt_tick_from_millisecond(100)); - startup_running = 0; - ready_flags = 0; - if (startup_evt) - { - rt_event_delete(startup_evt); - startup_evt = RT_NULL; - } -} - -static int startup_demo(int argc, char *argv[]) -{ - rt_thread_t tid; - - if (startup_running) - { - rt_kprintf("startup_demo is already running, please wait...\n"); - return -RT_EBUSY; - } - - /* 创建事件集 */ - startup_evt = rt_event_create("start_evt", RT_IPC_FLAG_PRIO); - if (startup_evt == RT_NULL) - { - rt_kprintf("create startup event failed\n"); - return -RT_ENOMEM; - } - - ready_flags = 0; - startup_running = 1; - - /* 网络初始化线程 */ - tid = rt_thread_create("netinit", net_init_entry, RT_NULL, - 1024, RT_THREAD_PRIORITY_MAX - 3, 10); - if (tid) - { - rt_thread_startup(tid); - } - else - { - rt_kprintf("create network init thread failed\n"); - startup_running = 0; - rt_event_delete(startup_evt); - startup_evt = RT_NULL; - return -RT_ERROR; - } - - /* 传感器初始化线程 */ - tid = rt_thread_create("seninit", sensor_init_entry, RT_NULL, - 1024, RT_THREAD_PRIORITY_MAX - 3, 10); - if (tid) - { - rt_thread_startup(tid); - } - else - { - rt_kprintf("create sensor init thread failed\n"); - } - - /* 存储初始化线程 */ - tid = rt_thread_create("stoinit", storage_init_entry, RT_NULL, - 1024, RT_THREAD_PRIORITY_MAX - 3, 10); - if (tid) - { - rt_thread_startup(tid); - } - else - { - rt_kprintf("create storage init thread failed\n"); - } - - /* 主业务线程 */ - tid = rt_thread_create("bizmain", business_entry, RT_NULL, - 1024, RT_THREAD_PRIORITY_MAX - 3, 10); - if (tid) - { - rt_thread_startup(tid); - } - else - { - rt_kprintf("create business thread failed\n"); - } - - return RT_EOK; -} -MSH_CMD_EXPORT(startup_demo, system startup condition check demo with event); diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" deleted file mode 100644 index c09d6d1f4174fcdedb9f09b69960b4e5b57da731..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 godbear 练习二:多窗口售票系统(互斥量) - */ - -#include -#include - -#define DBG_TAG "ticket" -#define DBG_LVL DBG_LOG -#include - -#define TICKET_TOTAL 20 /* 初始余票 */ - -static rt_mutex_t ticket_mutex = RT_NULL; -static volatile rt_int16_t ticket_count = TICKET_TOTAL; -static volatile rt_uint8_t ticket_running = 0; -static volatile rt_uint8_t ticket_finished = 0; -static volatile rt_uint8_t ticket_created = 0; - -/* 售票窗口线程:互斥量保护完整的“检查余票-减一-记录-打印”流程 */ -static void ticket_window_entry(void *param) -{ - char name = (char)(rt_size_t)param; /* 'A' 或 'B' */ - - while (1) - { - rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); - - /* 检查余票,售完退出 */ - if (ticket_count <= 0) - { - rt_mutex_release(ticket_mutex); - break; - } - - /* 记录票号并减一(票号取自当前余票,互斥下不会重复) */ - rt_int16_t no = ticket_count; - ticket_count--; - rt_kprintf("window %c sold ticket %d\n", name, no); - - rt_mutex_release(ticket_mutex); - - /* 短暂让出,模拟不同窗口交替售票 */ - rt_thread_mdelay(rt_tick_from_millisecond(20)); - } - - /* 两个窗口都结束后清理资源 */ - rt_enter_critical(); - ticket_finished++; - if (ticket_finished >= ticket_created) - { - ticket_finished = 0; - ticket_created = 0; - ticket_running = 0; - rt_exit_critical(); - - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: %d\n", ticket_count); - if (ticket_mutex) - { - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - } - } - else - { - rt_exit_critical(); - } -} - -static int ticket_demo(int argc, char *argv[]) -{ - rt_thread_t tid; - - if (ticket_running) - { - rt_kprintf("ticket_demo is already running, please wait...\n"); - return -RT_EBUSY; - } - - /* 创建互斥量保护售票流程 */ - ticket_mutex = rt_mutex_create("tk_mutex", RT_IPC_FLAG_PRIO); - if (ticket_mutex == RT_NULL) - { - rt_kprintf("create ticket mutex failed\n"); - return -RT_ENOMEM; - } - - ticket_count = TICKET_TOTAL; - ticket_running = 1; - ticket_finished = 0; - ticket_created = 0; - - /* 窗口 A */ - tid = rt_thread_create("tickA", ticket_window_entry, (void *)(rt_size_t)'A', - 1024, RT_THREAD_PRIORITY_MAX - 3, 10); - if (tid) - { - rt_thread_startup(tid); - ticket_created++; - } - else - { - rt_kprintf("create window A thread failed\n"); - } - - /* 窗口 B */ - tid = rt_thread_create("tickB", ticket_window_entry, (void *)(rt_size_t)'B', - 1024, RT_THREAD_PRIORITY_MAX - 3, 10); - if (tid) - { - rt_thread_startup(tid); - ticket_created++; - } - else - { - rt_kprintf("create window B thread failed\n"); - } - - if (ticket_created == 0) - { - ticket_running = 0; - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - return -RT_ERROR; - } - - return RT_EOK; -} -MSH_CMD_EXPORT(ticket_demo, multi-window ticket selling demo with mutex); diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index a28edcb5811ed3754788630eebcdd6ab48c368c0..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,409 +0,0 @@ -## Part 1:理论知识梳理文档(写入作业目录 `README.md`) - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -- **启动路径**:从上电复位 `Reset_Handler` 到进入 `main()` 的完整调用链。 - -##### 第一阶段:启动文件入口 - -系统复位后的第一个执行入口,负责初始化堆栈、中断向量等基础环境,并根据不同编译工具链进入对应的启动入口。 - -| 步骤 | 内容 | 功能作用 | -| ---- | --------------------------------------- | ------------------------------------------------------------ | -| 1 | `startup_stm32f407xx.S`(汇编启动文件) | 系统复位后的第一个执行入口,负责初始化堆栈、中断向量等基础环境 | -| 2 | MDK → `$Sub$$main()` | Keil 工具链的启动入口,在进入 main 前完成系统初始化 | -| 3 | IAR → `__low_level_init()` | IAR 工具链的启动入口,完成底层初始化 | -| 4 | GCC → `entry()` | GCC 工具链的启动入口,进入系统初始化流程 | -| 5 | 三个工具链分支汇入 `rtthread_startup()` | 实现跨编译器兼容,统一进入系统启动主流程 | - -##### 第二阶段:内核核心初始化(rt-thread_startup 主流程) - -`rtthread_startup()` 是系统启动主入口函数,串起整个初始化流程,依次完成中断、板级硬件、版本显示、定时器、调度器、信号等内核核心初始化。 - -| 步骤 | 内容 | 功能作用 | -| ---- | ----------------------------------------------------- | ------------------------------------------------------------ | -| 1 | `rtthread_startup()` | 系统启动主入口函数,串起整个初始化流程 | -| 2 | `rt_hw_interrupt_disable()` | 禁用硬件中断,保证初始化过程不被中断打扰 | -| 3 | `rt_hw_board_init()`→4 | 板级硬件初始化,配置时钟、串口、内存等基础硬件 | -| 4 | `rt_components_board_init()` → `board init functions` | 调用板级初始化函数列表,完成各外设的板级配置 | -| 5 | `rt_show_version()` | 显示系统版本号,串口输出 RT-Thread 版本横幅 | -| 6 | `rt_system_timer_init()` | 初始化系统定时器,为系统提供时间基准 | -| 7 | `rt_system_scheduler_init()` | 初始化调度器,建立调度器数据结构 | -| 8 | `rt_system_signal_init()`* | 初始化信号处理机制,支持线程间信号通信(如果不使用信号相关内容,可以在menuconfig里面关掉) | - -##### 第三阶段:线程创建 - -完成内核核心初始化后,系统开始创建各类系统线程与用户主线程,包括 main 主线程、定时器线程和空闲线程。 - -| 步骤 | 内容 | 功能作用 | -| ---- | --------------------------------------- | ------------------------------------------------------------ | -| 1 | `rt_application_init()`→2 | 初始化应用程序框架,创建应用入口线程 | -| 2 | `rt_thread_init(main_thread_entry)` | 创建 main 主线程(用户代码入口) | -| 3 | `rt_system_timer_thread_init()`*→4 | 初始化系统定时器线程(如果不使用,可以在menuconfig里面关掉) | -| 4 | `rt_thread_init(rt_thread_timer_entry)` | 创建定时器线程,负责软件定时器管理 | -| 5 | **`rt_thread_idle_init()`**→6 | 初始化空闲线程 | -| 6 | `rt_thread_init(rt_thread_idle_entry)` | 创建 idle 空闲线程(优先级最低,回收资源) | - -##### 第四阶段:启动调度器 - -| 步骤 | 内容 | 功能作用 | -| ---- | ----------------------------- | -------------------------------------------- | -| 1 | `rt_system_scheduler_start()` | 启动调度器,开始按优先级和时间片调度就绪线程 | -| 2 | 根据调度规则执行不同线程 | 线程创建后不会立即执行,由调度器决定何时运行 | - -##### 第五阶段:组件初始化与用户代码 - -调度器启动后,系统继续完成组件初始化,最终进入用户 `main()` 函数执行用户代码。 - -| 步骤 | 内容 | 功能作用 | -| ---- | ------------------------------------------------------------ | -------------------------------------------------- | -| 1 | `rt_components_init()`→2-6 | 组件初始化,按序执行各类初始化函数,系统自动初始化 | -| 2 | pre-initialization functions | 预初始化函数(最早执行) | -| 3 | device init functions | 设备驱动初始化 | -| 4 | components init functions | 组件初始化(文件系统、网络等) | -| 5 | environment init functions | 环境初始化 | -| 6 | applications init functions | 应用层初始化 | -| 7 | `main_thread_entry()`(包含`rt_components_init()`和MDK编译的`$Super$$mian()$`) → `main()` | 用户主函数入口,用户代码从这里开始执行 | - -**整体流程**:汇编启动文件 → 内核核心初始化(中断/板级/定时器/调度器/信号)→ 创建 main/定时器/空闲线程 → 启动调度器 → 组件初始化 → 进入用户 main() - -- **自动初始化原理**:解释 `INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` 的底层实现原理。 - -基本原理是: - -```tex -宏展开生成函数指针 - ↓ -函数指针放入特殊 section - ↓ -链接器收集成初始化函数表 - ↓ -RT-Thread 启动时遍历表 - ↓ -依次调用函数 -``` - -RT-Thread 的自动初始化机制本质上是通过 **宏 + 编译器 section + 链接脚本 + 启动遍历** 实现的,即宏定义写入函数指针,宏定义把函数指针放入指定的代码段(section),系统启动时 `rt_components_init()` 按段顺序遍历并逐个调用,实现「声明即自动执行」。 - -**1.宏展开:生成函数指针** - -以 `INIT_APP_EXPORT()` 为例,源码: - -```c -INIT_APP_EXPORT(my_thread_create); -可以被简化理解为展开成: -typedef int (*init_fn_t)(void); \\ 定义一个函数指针变量; - -const init_fn_t __used \\ 这个变量保存 my_thread_create 的函数地址; -__attribute__((section(".rti_fn.6"))) \\ 并把这个函数指针放到特殊段 .rti_fn.6 中。 - -``` - -也就是说,`INIT_APP_EXPORT()` 并不是直接调用函数,而是把函数地址登记到一张“自动初始化函数表”里。 - -**2、`INIT_BOARD_EXPORT` 与` INIT_APP_EXPORT` 的区别**: - -两者底层原理相同,区别主要是 **section 不同**,因此被调用的阶段不同。 - -| 宏 | 常见 section | 用途 | -| --------------------- | ----------------- | -------------- | -| `INIT_BOARD_EXPORT()` | `.rti_fn.1.board` | 板级硬件初始化 | -| `INIT_APP_EXPORT()` | `.rti_fn.6` | 应用层初始化 | - -**3. 链接器收集函数指针表** - -编译器只是把函数指针放到特殊 section,真正形成“表”的是链接脚本。 - -典型链接脚本可能类似: - -```tex -.rti_fn.board : -{ - . = ALIGN(4); - __rt_init_rti_board_start = .; - KEEP(*(SORT(.rti_fn.1.board))) - __rt_init_rti_board_end = .; -} > FLASH - -.rti_fn : -{ - . = ALIGN(4); - __rt_init_rti_start = .; - KEEP(*(SORT(.rti_fn.*))) - __rt_init_rti_end = .; -} > FLASH -``` - -关键点: -```tex -KEEP:防止初始化函数表被链接器优化删除 -SORT:按 section 名称排序,保证初始化顺序 -__rt_init_rti_board_start / __rt_init_rti_board_end:板级初始化表的起止地址 -__rt_init_rti_start / __rt_init_rti_end:普通初始化表的起止地址 -``` - -**4. 链接后的内存示意图** - -如果有如下初始化函数: - -```c -INIT_BOARD_EXPORT(board_init); -INIT_DEVICE_EXPORT(device_init); -INIT_COMPONENT_EXPORT -``` - -链接后 Flash 中可能形成类似结构: - -```tex -Flash - -.rti_fn.1.board -┌────────────────────────────┐ -│ board_init 函数地址 │ -└────────────────────────────┘ - -.rti_fn.3 -┌────────────────────────────┐ -│ device_init 函数地址 │ -└────────────────────────────┘ - -.rti_fn.4 -┌────────────────────────────┐ -│ component_init 函数地址 │ -└────────────────────────────┘ - -.rti_fn.6 -┌────────────────────────────┐ -│ my_thread_create 函数地址 │ -└────────────────────────────┘ -``` - -但这些项的本质都是:**函数指针** - -**5. 启动时遍历调用** - -RT-Thread 启动时会遍历这些函数指针表。 - -**底层完整流程**: - -```tex -源码阶段 - ↓ -用户写: -INIT_APP_EXPORT(my_thread_create); - ↓ -预处理器展开宏 - ↓ -生成一个函数指针变量: -const init_fn_t xxx = my_thread_create; - ↓ -编译器把这个变量放到: -.rti_fn.6 段 - ↓ -链接器收集所有 .rti_fn.* 段 - ↓ -形成初始化函数表 - ↓ -链接脚本定义起始/结束符号: -__rt_init_rti_start -__rt_init_rti_end - ↓ -RT-Thread 启动时调用 rt_components_init() - ↓ -遍历函数指针表 - ↓ -依次调用表中的函数 - ↓ -my_thread_create() 被执行 - ↓ -main() 被执行 -``` - -### 2. 内核基础与对象模型 - -- **对象容器与控制块**:简述 RT-Thread 面向对象的设计思想,线程控制块(`struct rt_thread`)中关键成员(如 `stat`、`current_priority`、`init_tick`、`sp`)的作用。 - -RT-Thread 面向对象的设计思想: - -将逻辑抽象化成一个对象,将用结构体表示对象、用函数表示对象行为、用对象类型区分对象种类、用链表/容器统一管理系统对象。 - -**线程控制块中关键成员作用:** - -| 成员 | 功能作用 | -| ------------------------------------ | --------------------------------------------------------- | -| `parent` | 继承 rt_object 基类,使线程成为内核对象,可被对象容器管理 | -| `sp` | 栈指针,保存线程当前运行上下文(寄存器状态) | -| `entry` | 入口函数指针,线程启动后执行的函数 | -| `parameter` | 传给入口函数的参数 | -| `stack_addr` / `stack_size` | 线程栈的地址和大小,用于保存线程局部变量和调用栈 | -| `error` | 线程错误代码 | -| `stat` | 表示线程当前状态(初始化/就绪/运行/挂起/关闭态) | -| `current_priority` / `init_priority` | 当前优先级 / 初始优先级(支持优先级继承) | -| `number_mask` | 优先级位图掩码,用于快速调度 | -| `init_tick` / `remaining_tick` | 时间片初始值 / 剩余值,用于时间片轮转调度 | -| `thread_timer` | 内置线程定时器,用于线程延时、超时等 | -| `cleanup` | 线程退出时的清除函数 | -| `user_data` | 用户自定义数据 | - -- **内存分配方式**:对比静态对象与动态对象的区别(分配位置、生命周期、释放机制),分析为什么某些高可靠场景强制使用静态线程。 - -线程对象可以通过动态线程`rt_thread_create()`和静态线程`rt_thread_init()`创建动态对象和静态对象,二者通过静态分配和动态分配内存。 - -**1、分配位置** - -| 项目 | 静态线程 | 动态线程 | -| ---------------- | --------------- | ------------------ | -| 内存来源 | 全局/静态区 | 堆区 | -| 分配时机 | 编译/链接时确定 | 运行时申请 | -| 内存位置 | 固定 | 不固定 | -| 是否依赖堆 | 不依赖 | 通常依赖 | -| 内存大小是否明确 | 明确 | 取决于运行时堆状态 | - -**2、生命周期** - -| 项目 | 静态线程 | 动态线程 | -| ---------------- | ---------------- | ---------------------- | -| 创建时机 | 通常开机初始化 | 运行时按需创建 | -| 生命周期 | 通常和系统一样长 | 可短可长 | -| 是否常驻 | 通常常驻 | 可临时存在 | -| 是否频繁创建销毁 | 一般不销毁 | 可以创建销毁 | -| 适合任务 | 长期固定任务 | 临时任务、数量不定任务 | - -**3、释放机制** - -| 项目 | 静态线程 | 动态线程 | -| ---------------- | ---------- | --------------------------- | -| 是否需要手动释放 | 通常不需要 | 需要关注释放 | -| 内存来源 | 静态区 | 堆 | -| 是否可能内存泄漏 | 一般不会 | 可能 | -| 是否可能内存碎片 | 基本不会 | 可能 | -| 资源回收方式 | 固定占用 | 线程退出/删除/detach 后释放 | - -而高可靠场景通常要求:行为确定、内存可控、启动可靠、不能随机死机、不能运行时分配失败。 - -1. 静态线程内存在编译时就可以确定。 - -2. 动态线程创建依赖堆,但是堆可能不足;静态线程开机前就把内存准备好,不依赖运行时堆状态。 - -3. 动态内存长时间运行后可能出现碎片,静态线程不会造成这种问题,因为静态线程的内存位置固定。 - -4. 动态内存系统常见风险是内存泄漏,而静态线程不存在“用完释放”的概念,它的内存固定占用,不会因为泄漏导致系统运行越久越危险。 - -5. 静态线程通常在系统启动早期就可以初始化,只要静态数组存在,线程对象就存在。动态线程则可能受各种因素影响。 - -6. 静态线程不需要运行时分配内存,所以行为更确定,实时性更可预测。 - - 总之:静态线程把内存问题从“运行时不确定问题”变成了“编译期可检查问题”。 - -### 3. 线程状态机与调度算法 - -- **五态模型**:绘制或描述 RT-Thread 线程状态(初始态、就绪态、运行态、挂起态、关闭态)及触发状态转换的典型 API。 - - **状态转换关系:** - - | 起始状态 | 目标状态 | 触发函数 / 条件 | - | -------- | -------- | ------------------------------------------------------------ | - | 无 | 初始状态 | 通过动态线程创建`rt_thread_create()`和静态线程创建`rt_thread_init()` | - | 初始状态 | 就绪状态 | `rt_thread_startup()` | - | 就绪状态 | 运行状态 | 被调度器选中执行(调度器调度) | - | 运行状态 | 挂起状态 | `rt_thread_suspend()` 及①组阻塞函数 | - | 挂起状态 | 就绪状态 | ②组唤醒函数 | - | 运行状态 | 关闭状态 | `rt_thread_exit()` | - | 挂起状态 | 关闭状态 | `rt_thread_delete()` / `rt_thread_detach()` | - - **①组阻塞函数(运行 → 挂起)** - - 运行中的线程调用以下函数会主动让出 CPU 或阻塞等待资源,从而转入挂起状态: - - ```c - rt_thread_delay(); /* 延时等待 */ - rt_sem_take(); /* 获取信号量(不可用则阻塞) */ - rt_mutex_take(); /* 获取互斥锁(被占用则阻塞) */ - rt_mp_alloc(); /* 分配内存池块(无可用块则阻塞) */ - rt_event_recv(); /* 接收事件标志(无匹配事件则阻塞) */ - rt_mb_recv(); /* 接收邮箱消息(为空则阻塞) */ - rt_mq_recv(); /* 接收消息队列(为空则阻塞) */ - ``` - - **②组唤醒函数(挂起 → 就绪)** - - 由其他线程或中断调用,用于唤醒挂起中的线程,使其回到就绪状态: - - ```c - rt_thread_resume(); /* 显式恢复线程 */ - rt_sem_release(); /* 释放信号量(唤醒等待者) */ - rt_mutex_release(); /* 释放互斥锁(唤醒等待者) */ - rt_mp_release(); /* 释放内存池块(唤醒等待分配者) */ - rt_event_send(); /* 发送事件标志(唤醒等待者) */ - rt_mb_send(); /* 发送邮箱消息(唤醒等待接收者) */ - rt_mq_send(); /* 发送消息队列(唤醒等待接收者) */ - ``` - -- **调度机制剖析**: - - - **抢占式调度** - - 核心思想:如果有更高优先级的线程进入就绪态,那么当前低优先级线程会被立即暂停,CPU 切换给高优先级线程。 - - 其中优先级数值越小,优先级越高。 - - 如果当前正在运行线程 B,而线程 A 突然变成就绪态,那么调度器会切换到线程 A。通过线程控制块中的`stat、current_priority、sp`来判断线程是否可以被调用。 - - `statu:`判断线程是否可以被调度。 - - ```c - READY 可以被调度 - RUNNING 正在运行 - SUSPEND 不能运行 - ``` - - `current_priority:`调度器根据当前优先级判断谁更重要。优先级数值越小,越优先运行。 - - `sp:`切换线程时保存和恢复现场。 - - ```tex - 切走线程:保存 sp - 切入线程:恢复 sp - ``` - - - **时间片轮转** - - 通过时间片轮转判断多个相同优先级线程如何公平使用 CPU,同优先级线程轮流使用 CPU。如果没有时间片机制,可能一个线程一直运行,另一个线程很难得到机会。 - - 时间片轮转就是:每个同优先级线程运行一段时间,时间片用完后切换到下一个同优先级线程。 - -## Part 2:多线程实战编程(C 语言实现) - -### 任务说明 - -1. **工作线程 1(动态创建 `th_led1`)**: - - 优先级 `16`,时间片 `5 ticks`,栈 `1024 Bytes`。 - - 循环翻转板载 LED_R,每次执行打印计数与 `rt_tick_get()`。 - -2. **工作线程 2(静态初始化 `th_led2`)**: - - 优先级 `16`,时间片 `5 ticks`,静态定义全局栈与 TCB。 - - 循环翻转板载 LED_B,每次执行打印计数。 - -3. **监控与控制线程(动态创建 `th_monitor`)**: - - 优先级 `10`(高于工作线程),周期延时 `rt_thread_mdelay(2000)`。 - - 唤醒后获取并打印 `th_led1` 与 `th_led2` 的运行状态与剩余栈空间大小。 - - 结合板载按键检测(或按键中断):单次触发时调用 `rt_thread_suspend()` 挂起 `th_led1`;再次触发时调用 `rt_thread_resume()` 恢复。 - - **实验结果:** - - 先执行监控线程,后续再依次执行工作线程1、2,并打印线程信息 - - ![image-20260819150759771](../../笔记/figures/image-20260819150759771-17871244340711.png) - - 实物图![线程截图1](../../笔记/figures/线程截图1-17871244518903.jpg) - - ![线程截图2](../../笔记/figures/线程截图2-17871244569335.jpg) - - 串口打印消息截图 - - ![image-20260819150540298](../../笔记/figures/image-20260819150540298-17871244603327.png) - -4. **系统集成与调试命令**: - - 使用 `INIT_APP_EXPORT()` 自动加载测试模块,禁止在 `main.c` 中硬编码调用。 - - 使用 `MSH_CMD_EXPORT()` 导出自定义查看命令(如 `thread_dump`)。 - - \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index 87278b76bd59e644308dbce4dee3f3ca6797efda..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-18 godbear the first version - */ - -/* - * thread_study.c - * - * 作业内容: - * 1. 动态创建工作线程 th_led1 - * 2. 静态初始化工作线程 th_led2 - * 3. 动态创建监控线程 th_monitor - * 4. 使用 INIT_APP_EXPORT 自动初始化 - * 5. 使用 MSH_CMD_EXPORT 导出 thread_dump 命令 - * - * 硬件环境: - * STM32F407ZG - * LED_R = PF12 - * LED_B = PF11 - * KEY = PC5 - */ - - -#include -#include -#include -#include -#include - -/* 引脚定义 */ - -#define LED_B_PIN GET_PIN(F,11) -#define LED_R_PIN GET_PIN(F,12) -#define KEY_PIN GET_PIN(C,5) - -/* - * LED 极性: - * 如果板子 LED 是低电平点亮,请改成: - * #define LED_ON_LEVEL PIN_LOW - * #define LED_OFF_LEVEL PIN_HIGH - */ -#define LED_ON_LEVEL PIN_HIGH -#define LED_OFF_LEVEL PIN_LOW - -/* - * 按键极性: - * 当前假设 PC5 使用内部上拉,按键按下接地,即低电平有效。 - * - * 如果按键是高电平有效,改: - * KEY_PIN_MODE -> PIN_MODE_INPUT_PULLDOWN - * KEY_IRQ_MODE -> PIN_IRQ_MODE_RISING - * KEY_PRESSED_LEVEL -> PIN_HIGH - */ -#define KEY_PIN_MODE PIN_MODE_INPUT_PULLUP -#define KEY_IRQ_MODE PIN_IRQ_MODE_FALLING -#define KEY_PRESSED_LEVEL PIN_LOW - - -/* 线程优先级 */ -#define LED_THREAD_PRIORITY 16 -#define MONITOR_THREAD_PRIORITY 16 - -/* ===================== 线程对象 ===================== */ - -/* 指向led线程的状态信号量的指针 */ -static rt_sem_t led_sem = RT_NULL; - -/* 工作线程 1:动态创建 */ -static rt_thread_t th_led1 = RT_NULL; - -/* 工作线程 2:静态初始化 */ -/* 使用静态线程时,线程的栈需要设置字节对齐 */ -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t th_led2_stack[2048]; -static struct rt_thread th_led2; - -/* 监控线程:动态创建 */ -static rt_thread_t th_monitor = RT_NULL; - - -/* 按键中断与线程通信的全局变量 */ -static volatile rt_uint32_t key_event_count = 0; /* 按键按下次数计数器 */ -static volatile rt_tick_t last_isr_tick = 0; /* 上次中断触发的系统时间 */ - - - - -/* ===================== 工具函数 ===================== */ - -static const char *thread_state_str(rt_uint8_t stat) -{ - stat &= RT_THREAD_STAT_MASK; - - switch (stat) - { - case RT_THREAD_INIT: - return "init"; - - case RT_THREAD_READY: - return "ready"; - - case RT_THREAD_RUNNING: - return "running"; - - case RT_THREAD_SUSPEND: - return "suspend"; - - case RT_THREAD_CLOSE: - return "close"; - - default: - return "unknown"; - } -} - -/* - * 获取线程剩余栈空间。 - * - * RT-Thread 初始化线程栈时,通常会把栈填充为 '#'。 - * 栈从高位向低位增长,未使用区域一般仍保持 '#'。 - * 因此可以从 stack_addr 开始统计连续 '#' 的数量, - * 作为剩余栈空间的近似值。 - * - * 如果栈底不是 '#',则退化为用 sp 粗略估算。 - */ -static rt_uint32_t thread_stack_free(struct rt_thread *thread) -{ - rt_uint8_t *p; - rt_uint8_t *end; - rt_uint32_t free_size = 0; - - if (thread == RT_NULL || thread->stack_addr == RT_NULL) - { - return 0; - } - - p = (rt_uint8_t *)thread->stack_addr; - end = p + thread->stack_size; - - if (*p == '#') - { - while (p < end && *p == '#') - { - p++; - free_size++; - } - return free_size; - } - - if ((rt_uint32_t)thread->sp > (rt_uint32_t)thread->stack_addr) - { - return (rt_uint32_t)thread->sp - (rt_uint32_t)thread->stack_addr; - } - - return 0; -} - -static void print_thread_info(const char *tag, struct rt_thread *thread) -{ - if (thread == RT_NULL) - { - rt_kprintf("%-8s: thread not created\n", tag); - return; - } - - rt_kprintf("%-8s name=%.*s state=%-7s prio=%d tick=%u stack_free=%u/%u\n", - tag, - RT_NAME_MAX, - thread->name, - thread_state_str(thread->stat), - thread->current_priority, - (unsigned int)thread->init_tick, - (unsigned int)thread_stack_free(thread), - (unsigned int)thread->stack_size); -} - -static void print_all_status(void) -{ - rt_kprintf("--------- thread status --------\n"); - print_thread_info("led1", th_led1); - print_thread_info("led2", &th_led2); - print_thread_info("monitor", th_monitor); -} - - -/* ===================== 按键中断服务函数 (ISR) ===================== */ -/* - * 注意:中断函数必须快进快出! - * 绝对不能在这里使用 rt_thread_mdelay、rt_kprintf 或挂起线程! - */ -static void key_isr_handler(void *args) -{ - rt_tick_t current_tick = rt_tick_get(); - - /* 硬件级/ISR级软件消抖: - * 如果距离上次触发时间不到 20ms,认为是机械抖动,直接丢弃。 - */ - if ((current_tick - last_isr_tick) >= rt_tick_from_millisecond(20)) - { - last_isr_tick = current_tick; - key_event_count++; /* 记账:有效按键次数 +1 */ - } -} -/* - * 就绪态延时。 - * - * 与 rt_thread_mdelay() 不同: - * rt_thread_mdelay() 会让线程进入 SUSPEND 状态, - * 而这里通过不断判断 tick 并调用 rt_thread_yield(), - * 使线程仍然处于 READY/RUNNING 状态。 - * - * 这样监控线程调用 rt_thread_suspend(th_led1) 更容易成功。 - */ -static void ready_delay_ms(rt_uint32_t ms) -{ - rt_tick_t start = rt_tick_get(); - rt_tick_t ticks = rt_tick_from_millisecond(ms); - - while ((rt_tick_get() - start) < ticks) - { - rt_thread_yield(); - } -} - -/* 线程th_led1的入口函数 */ -static void th_led1_entry(void *parameter) -{ - - static rt_uint8_t count = 0; - while(1) - { - if (rt_sem_take(led_sem, 0) == RT_EOK) - { - rt_kprintf("th_led1 suspend.\n"); - rt_thread_suspend(th_led1); //挂起th_led1 - } - else - { - count++; - rt_pin_write(LED_R_PIN, !rt_pin_read(LED_R_PIN)); - rt_kprintf("th_led1 count=%u tick=%u\n", - (unsigned int)count, - (unsigned int)rt_tick_get()); - ready_delay_ms(1000); - } - } -} - -/* 线程th_led2的入口函数 */ -static void th_led2_entry(void *parameter) -{ - - static rt_uint8_t count = 0; - while(1) - { - count++; - rt_pin_write(LED_B_PIN, !rt_pin_read(LED_B_PIN)); - rt_kprintf("th_led2 count=%u\n",(unsigned int)count); - ready_delay_ms(1000); - } -} - -/* 线程th_monitor的入口函数 */ -static void th_monitor_entry(void *parameter) -{ - rt_uint32_t key_cnt; - rt_base_t level; - while(1) - { - /* 关中断,安全地读取并清零计数器 (原子操作) */ - level = rt_hw_interrupt_disable(); - key_cnt = key_event_count; - key_event_count = 0; /* 读完就清零,防止重复处理 */ - rt_hw_interrupt_enable(level); - - /* 如果计数器大于 0,说明睡觉期间有人按了按键 */ - - if (key_cnt > 0) - { - rt_thread_mdelay(20); - if(rt_pin_read(KEY_PIN)==0) //按键消抖20ms - { - - //每次按下按键翻转一下线程th_led1的状态 - if((th_led1->stat &= RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND) - { - rt_kprintf("th_led1 resume.\n"); - rt_thread_resume(th_led1); //使线程恢复运行 - } - else - { - rt_sem_release(led_sem); //释放信号量 - } - rt_kprintf("[KEY] key event \n"); - } - - } - - // 打印状态 - print_all_status(); - rt_thread_mdelay(2000); - - } -} - - -static void gpio_init(void) -{ - - /* 初始化 LED 引脚 */ - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); - - rt_pin_write(LED_R_PIN, LED_ON_LEVEL); - rt_pin_write(LED_B_PIN, LED_ON_LEVEL); - - /* 初始化按键中断 */ - rt_pin_mode(KEY_PIN, KEY_PIN_MODE); - rt_pin_attach_irq(KEY_PIN, KEY_IRQ_MODE, key_isr_handler, RT_NULL); - rt_pin_irq_enable(KEY_PIN, PIN_IRQ_ENABLE); - -} - - -/* ===================== 模块初始化 ===================== */ - -static int thread_study_init(void) -{ - /* 初始化GPIO*/ - gpio_init(); - /* 创建一个动态信号量,初始值是0 */ - led_sem = rt_sem_create("led_sem", 0, RT_IPC_FLAG_PRIO); - /* - * 工作线程 1:动态创建 - * 名称:th_led1 - * 优先级:16 - * 时间片:5 ticks - * 栈大小:1024 Bytes - */ - th_led1 = rt_thread_create("th_led1", - th_led1_entry, - RT_NULL, - 2048, - 16, - 5); - rt_thread_startup(th_led1);//动态线程不能用&符号 - /* - * 工作线程 2:静态初始化 - * 名称:th_led2 - * 优先级:16 - * 时间片:5 ticks - * 栈大小:1024 Bytes - */ - rt_thread_init(&th_led2, - "th_led2", - th_led2_entry, - RT_NULL, - th_led2_stack, - sizeof(th_led2_stack), - 16, - 5); - - rt_thread_startup(&th_led2); - - /* 监控线程:动态创建 */ - th_monitor = rt_thread_create("th_mon", - th_monitor_entry, - RT_NULL, - 2048, - 10, - 5); - rt_thread_startup(th_monitor); - rt_kprintf("thread_study init done\n"); - rt_kprintf("LED_R=PF12, LED_B=PF11, KEY=PC5\n"); - rt_kprintf("Press KEY to suspend/resume th_led1\n"); - - return 0; -} -INIT_APP_EXPORT(thread_study_init); - -/* ===================== MSH 调试命令 ===================== */ - -void thread_dump(void) -{ - print_all_status(); -} -MSH_CMD_EXPORT(thread_dump, dump thread study status); - - - - - - - - - diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/idle_thread.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/idle_thread.png" deleted file mode 100644 index 06c4224c9324386764f3ca5b9ce6d53c13cae289..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/idle_thread.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817105111097-17870467140921.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817105111097-17870467140921.png" deleted file mode 100644 index 29a4b7c745e8119c95e49e15d97d167af0150d72..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817105111097-17870467140921.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817105111097.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817105111097.png" deleted file mode 100644 index 29a4b7c745e8119c95e49e15d97d167af0150d72..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817105111097.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110606463-17870467140923.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110606463-17870467140923.png" deleted file mode 100644 index e43ffe69a1bc2c5c560b9bb58d8ae1162fca2c70..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110606463-17870467140923.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110606463.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110606463.png" deleted file mode 100644 index e43ffe69a1bc2c5c560b9bb58d8ae1162fca2c70..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110606463.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110644743-17870467140925.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110644743-17870467140925.png" deleted file mode 100644 index 228fbc3c7a4170bf786fe2b5c6d45856ca33e5bb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110644743-17870467140925.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110644743.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110644743.png" deleted file mode 100644 index 228fbc3c7a4170bf786fe2b5c6d45856ca33e5bb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817110644743.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111229110-17870467140927.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111229110-17870467140927.png" deleted file mode 100644 index 438444823fd968fbcf3fe40d56b446994d066dde..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111229110-17870467140927.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111229110.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111229110.png" deleted file mode 100644 index 438444823fd968fbcf3fe40d56b446994d066dde..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111229110.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111657041-17870467140928.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111657041-17870467140928.png" deleted file mode 100644 index 51248454bd2036b19031d4cf3f8a6036e8feef13..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111657041-17870467140928.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111657041.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111657041.png" deleted file mode 100644 index 51248454bd2036b19031d4cf3f8a6036e8feef13..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111657041.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111817303-17870467140926.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111817303-17870467140926.png" deleted file mode 100644 index 00825151dde0bede8b67bd81b390eccb999bdb2b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111817303-17870467140926.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111817303.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111817303.png" deleted file mode 100644 index 00825151dde0bede8b67bd81b390eccb999bdb2b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817111817303.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112240678-17870467140929.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112240678-17870467140929.png" deleted file mode 100644 index aa17a0a54e83b712f5d024cd5c8b87f8536a5672..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112240678-17870467140929.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112240678.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112240678.png" deleted file mode 100644 index aa17a0a54e83b712f5d024cd5c8b87f8536a5672..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112240678.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112509368-178704671409211.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112509368-178704671409211.png" deleted file mode 100644 index 19e691a910c41b804c8f7b4dd910637b0382c161..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112509368-178704671409211.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112509368.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112509368.png" deleted file mode 100644 index 19e691a910c41b804c8f7b4dd910637b0382c161..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817112509368.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115139177-178704671409219.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115139177-178704671409219.png" deleted file mode 100644 index 55e8dc4bc5b47cfedb7fe28dd05c7e5dadf69673..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115139177-178704671409219.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115139177.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115139177.png" deleted file mode 100644 index 55e8dc4bc5b47cfedb7fe28dd05c7e5dadf69673..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115139177.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115320960-178704671409220.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115320960-178704671409220.png" deleted file mode 100644 index 0666e838782e0a9ec652243702351ead3c4b1b3f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115320960-178704671409220.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115320960.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115320960.png" deleted file mode 100644 index 0666e838782e0a9ec652243702351ead3c4b1b3f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817115320960.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817121917695-178704671409212.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817121917695-178704671409212.png" deleted file mode 100644 index 36d729723b4fcea42639e979baea5235cb1b6a59..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817121917695-178704671409212.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817121917695.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817121917695.png" deleted file mode 100644 index 36d729723b4fcea42639e979baea5235cb1b6a59..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817121917695.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122428925-178704671409213.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122428925-178704671409213.png" deleted file mode 100644 index ee6eb8f77f0cdec989c59c47bf97d328b26ab09b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122428925-178704671409213.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122428925.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122428925.png" deleted file mode 100644 index ee6eb8f77f0cdec989c59c47bf97d328b26ab09b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122428925.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122605054-178704671409214.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122605054-178704671409214.png" deleted file mode 100644 index ae78c4c16786ced18382fdd74a5ab3d7a5bc1209..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122605054-178704671409214.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122605054.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122605054.png" deleted file mode 100644 index ae78c4c16786ced18382fdd74a5ab3d7a5bc1209..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817122605054.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123026731-178704671409210.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123026731-178704671409210.png" deleted file mode 100644 index ae78c4c16786ced18382fdd74a5ab3d7a5bc1209..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123026731-178704671409210.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123026731.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123026731.png" deleted file mode 100644 index ae78c4c16786ced18382fdd74a5ab3d7a5bc1209..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123026731.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123106106-178704671409218.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123106106-178704671409218.png" deleted file mode 100644 index a42db77e1152c2e66b2c728a8672be3b35222159..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123106106-178704671409218.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123106106.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123106106.png" deleted file mode 100644 index a42db77e1152c2e66b2c728a8672be3b35222159..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123106106.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123229229-178704671409215.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123229229-178704671409215.png" deleted file mode 100644 index 779b7686fc9889800ac08f3f6d864bf37e951e9f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123229229-178704671409215.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123229229.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123229229.png" deleted file mode 100644 index 779b7686fc9889800ac08f3f6d864bf37e951e9f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123229229.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123403869-178704671409216.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123403869-178704671409216.png" deleted file mode 100644 index c83ed09719ad29ad532fa0cb16ea8976decb6faa..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123403869-178704671409216.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123403869.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123403869.png" deleted file mode 100644 index c83ed09719ad29ad532fa0cb16ea8976decb6faa..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817123403869.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817130200062-178704671409217.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817130200062-178704671409217.png" deleted file mode 100644 index 2d2c96d2d67d44dc57c0c213acb4ba1675b09568..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817130200062-178704671409217.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817130200062.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817130200062.png" deleted file mode 100644 index 2d2c96d2d67d44dc57c0c213acb4ba1675b09568..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817130200062.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817185327034-178704671409221.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817185327034-178704671409221.png" deleted file mode 100644 index 939d465241ea9f2c1ea799b51aece944b30e43ff..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817185327034-178704671409221.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817185327034.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817185327034.png" deleted file mode 100644 index 939d465241ea9f2c1ea799b51aece944b30e43ff..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817185327034.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190141688-178704671409222.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190141688-178704671409222.png" deleted file mode 100644 index 5c483f3790093b694a3638d063fb8ad3826aaee1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190141688-178704671409222.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190141688.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190141688.png" deleted file mode 100644 index 5c483f3790093b694a3638d063fb8ad3826aaee1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190141688.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190602919-178704671409223.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190602919-178704671409223.png" deleted file mode 100644 index b00f2487f0b87cf986f71bc74a5f42abd320a86e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190602919-178704671409223.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190602919.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190602919.png" deleted file mode 100644 index b00f2487f0b87cf986f71bc74a5f42abd320a86e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817190602919.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817191804794-178704671409224.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817191804794-178704671409224.png" deleted file mode 100644 index edb6a9c777abcdb1123aa4ce1dc7f619fc6f8753..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817191804794-178704671409224.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817191804794.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817191804794.png" deleted file mode 100644 index edb6a9c777abcdb1123aa4ce1dc7f619fc6f8753..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817191804794.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194649248-178704671409225.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194649248-178704671409225.png" deleted file mode 100644 index e6d759fec6193168eaf0fd89e5a3b1aeb648adc7..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194649248-178704671409225.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194649248.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194649248.png" deleted file mode 100644 index e6d759fec6193168eaf0fd89e5a3b1aeb648adc7..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194649248.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194932150-178704671409327.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194932150-178704671409327.png" deleted file mode 100644 index 93c452a7df9c7d5da93d87dfde62609439571d2f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194932150-178704671409327.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194932150.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194932150.png" deleted file mode 100644 index 93c452a7df9c7d5da93d87dfde62609439571d2f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817194932150.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817222814464-178704671409326.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817222814464-178704671409326.png" deleted file mode 100644 index e6ac59f5891dfbc228088b8eea10ad9e74d65816..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817222814464-178704671409326.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817222814464.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817222814464.png" deleted file mode 100644 index e6ac59f5891dfbc228088b8eea10ad9e74d65816..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260817222814464.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818091412456.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818091412456.png" deleted file mode 100644 index d0ede4e131d55ee114cd181f95f44b6ef1c45d69..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818091412456.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184322876.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184322876.png" deleted file mode 100644 index 5e7dc776e5b5399b6139e7f0eb4f57d589dd275a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184322876.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184335534.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184335534.png" deleted file mode 100644 index 5e7dc776e5b5399b6139e7f0eb4f57d589dd275a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184335534.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184427643.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184427643.png" deleted file mode 100644 index 40784af0da8dec012035bbf366c53fcd58234628..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184427643.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184435201.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184435201.png" deleted file mode 100644 index 40784af0da8dec012035bbf366c53fcd58234628..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260818184435201.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150540298-17871244603327.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150540298-17871244603327.png" deleted file mode 100644 index 309a811e87d2808ea48e1bfd0bc8dbd6a8f16077..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150540298-17871244603327.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150540298.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150540298.png" deleted file mode 100644 index 309a811e87d2808ea48e1bfd0bc8dbd6a8f16077..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150540298.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150759771-17871244340711.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150759771-17871244340711.png" deleted file mode 100644 index 62e6d7685c275ed64c701de2196ab7b8a3d93e2e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150759771-17871244340711.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150759771.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150759771.png" deleted file mode 100644 index 62e6d7685c275ed64c701de2196ab7b8a3d93e2e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260819150759771.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820135936211-17872071184313.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820135936211-17872071184313.png" deleted file mode 100644 index 1dfa4bfe9332e0ac0e8ffd335ce2ace94013e18b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820135936211-17872071184313.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820135936211.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820135936211.png" deleted file mode 100644 index 1dfa4bfe9332e0ac0e8ffd335ce2ace94013e18b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820135936211.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820141456076-17872071141291.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820141456076-17872071141291.png" deleted file mode 100644 index c24b37f9256683debd778d99626958bb087004c9..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820141456076-17872071141291.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820141456076.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820141456076.png" deleted file mode 100644 index c24b37f9256683debd778d99626958bb087004c9..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820141456076.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820142203309-17872071368765.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820142203309-17872071368765.png" deleted file mode 100644 index 2d1212c1183830bcf748de3632c239010f64b66f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820142203309-17872071368765.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820142203309.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820142203309.png" deleted file mode 100644 index 2d1212c1183830bcf748de3632c239010f64b66f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820142203309.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820144226421.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820144226421.png" deleted file mode 100644 index 3e6c6b57d216d9ef36e229013e7a51bc545eefaf..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820144226421.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820150216025.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820150216025.png" deleted file mode 100644 index 6ba16a1d3af3945388a54cc8bbeead9fcce95310..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820150216025.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820150431172.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820150431172.png" deleted file mode 100644 index d31f1f0b2f28bbc4b4789f1bb06fc04a797d7cdb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820150431172.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820151822129.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820151822129.png" deleted file mode 100644 index 739d965c476bed57939619498e3e67ff24079f79..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/image-20260820151822129.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/main_thread.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/main_thread.png" deleted file mode 100644 index 7ea870a94731ba6a06732a4ff18eefcb32a8669b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/main_thread.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930049718_0580c19a82d6b693a765de35f73e906d-17870467140922.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930049718_0580c19a82d6b693a765de35f73e906d-17870467140922.png" deleted file mode 100644 index 2b1e7bee316128457c0abcda3e5a4bc01ad01f8c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930049718_0580c19a82d6b693a765de35f73e906d-17870467140922.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930049718_0580c19a82d6b693a765de35f73e906d.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930049718_0580c19a82d6b693a765de35f73e906d.png" deleted file mode 100644 index 2b1e7bee316128457c0abcda3e5a4bc01ad01f8c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930049718_0580c19a82d6b693a765de35f73e906d.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930192180_b6eb301b65dd500da6882856d803b31f-17870467140924.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930192180_b6eb301b65dd500da6882856d803b31f-17870467140924.png" deleted file mode 100644 index 7e93a112f38e64cd53a12073daa476130bc152b0..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930192180_b6eb301b65dd500da6882856d803b31f-17870467140924.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930192180_b6eb301b65dd500da6882856d803b31f.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930192180_b6eb301b65dd500da6882856d803b31f.png" deleted file mode 100644 index 7e93a112f38e64cd53a12073daa476130bc152b0..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786930192180_b6eb301b65dd500da6882856d803b31f.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950856866_b5d48893754028deb32af06076440e5d-178704671409329.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950856866_b5d48893754028deb32af06076440e5d-178704671409329.png" deleted file mode 100644 index 43030a788e129c29b494a7a37d9246b2c44cfaa1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950856866_b5d48893754028deb32af06076440e5d-178704671409329.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950856866_b5d48893754028deb32af06076440e5d.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950856866_b5d48893754028deb32af06076440e5d.png" deleted file mode 100644 index 43030a788e129c29b494a7a37d9246b2c44cfaa1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950856866_b5d48893754028deb32af06076440e5d.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950943042_60404a3330f350a0caac7611102451c4-178704671409331.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950943042_60404a3330f350a0caac7611102451c4-178704671409331.png" deleted file mode 100644 index 6136206595940cc94111c8d53ea80b33c6170b00..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950943042_60404a3330f350a0caac7611102451c4-178704671409331.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950943042_60404a3330f350a0caac7611102451c4.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950943042_60404a3330f350a0caac7611102451c4.png" deleted file mode 100644 index 6136206595940cc94111c8d53ea80b33c6170b00..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786950943042_60404a3330f350a0caac7611102451c4.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786953074055_7132e3201c0bf72483bb3827262fc273-178704671409328.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786953074055_7132e3201c0bf72483bb3827262fc273-178704671409328.png" deleted file mode 100644 index ddbe67a8b5958da0a4090268605d9aecccd84803..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786953074055_7132e3201c0bf72483bb3827262fc273-178704671409328.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786953074055_7132e3201c0bf72483bb3827262fc273.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786953074055_7132e3201c0bf72483bb3827262fc273.png" deleted file mode 100644 index ddbe67a8b5958da0a4090268605d9aecccd84803..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786953074055_7132e3201c0bf72483bb3827262fc273.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786954747878_c999a45c5ade5aff578685dc607d5b0b-178704671409330.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786954747878_c999a45c5ade5aff578685dc607d5b0b-178704671409330.png" deleted file mode 100644 index 7f4b3b1c0ea7c67814b10c80693dc41248e48c08..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786954747878_c999a45c5ade5aff578685dc607d5b0b-178704671409330.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786954747878_c999a45c5ade5aff578685dc607d5b0b.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786954747878_c999a45c5ade5aff578685dc607d5b0b.png" deleted file mode 100644 index 7f4b3b1c0ea7c67814b10c80693dc41248e48c08..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/original_1786954747878_c999a45c5ade5aff578685dc607d5b0b.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_create.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_create.png" deleted file mode 100644 index 8db2d1b26be8092e044337190980884a959e0733..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_create.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_delay.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_delay.png" deleted file mode 100644 index ec3f821f17efb6b879d02822fc33d7ed2a73fa5d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_delay.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_entry.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_entry.png" deleted file mode 100644 index 64f02bac85459fdee4fd22e9fda03f02e1edf231..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_entry.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_init.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_init.png" deleted file mode 100644 index ea66d3eda71e52e2d748a68da96f8b6bba9219d6..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_init.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_init_example.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_init_example.png" deleted file mode 100644 index 513075ea5f21e4e2b758827995eabbc1e50df9d6..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_init_example.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_priority.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_priority.png" deleted file mode 100644 index 16df0af941a077dadae5f9ec7d78cddda37dd14a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_priority.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_stack.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_stack.png" deleted file mode 100644 index 4166d96ce56c0e55629fd092a42252f1f991ce83..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_stack.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_state_transition.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_state_transition.png" deleted file mode 100644 index 57b264dd8a31b868b05613fe97a3e9e122f61b8f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_state_transition.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_timeslice.png" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_timeslice.png" deleted file mode 100644 index 69e68875b3d30dae20b895198e79009026546ea2..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/thread_timeslice.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2761-17871244518903.jpg" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2761-17871244518903.jpg" deleted file mode 100644 index f76db55e426224a140ea3fc521dfeb4faeee3f8b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2761-17871244518903.jpg" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2761.jpg" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2761.jpg" deleted file mode 100644 index f76db55e426224a140ea3fc521dfeb4faeee3f8b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2761.jpg" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2762-17871244569335.jpg" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2762-17871244569335.jpg" deleted file mode 100644 index 5f004a237526d19a974b5fd536a53bbd999005ae..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2762-17871244569335.jpg" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2762.jpg" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2762.jpg" deleted file mode 100644 index 5f004a237526d19a974b5fd536a53bbd999005ae..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/figures/\347\272\277\347\250\213\346\210\252\345\233\2762.jpg" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 287786961f8dcc89ad08c382ade004a173bcf756..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,525 +0,0 @@ -# RT-Thread 夏令营 Day1 学习记录 - -## 学习目标 - -**Day1 学习目标**: - -1. 完成 RT-Thread Studio 开发环境搭建 -2. 掌握 Markdown 笔记规范 -3. 创建并调试 STM32F407 工程 -4. 掌握 Git 基础操作与 PR 开源协作流程 - -## 一、开发环境搭建 - -### 1. 安装 RT-Thread Studio - -- 下载地址:https://www.rt-thread.org/document/site/#/development-tools/rtthread-studio/um/studio-user-begin -- 第一次启动需要注册一个 RT-Thread 社区账号(手机号和邮箱都可以)。 -- 打开应用后点击恢复窗口布局:![image-20260817105111097](figures/image-20260817105111097-17870467140921.png) - -### 2. 更新软件包 - -- 勾选4.1.1版本: - - ![img](figures/original_1786930049718_0580c19a82d6b693a765de35f73e906d-17870467140922.png) - -- 并勾选芯片包 STM32F4 系列的 0.2.4 最新版进行更新:![](figures/original_1786930192180_b6eb301b65dd500da6882856d803b31f-17870467140924.png) - -- 确认设备管理器识别到 **ST-Link 驱动**,确保硬件连接正常。 - ---- - -## 二、工程创建与运行 - -### task1:新建一个 qemu-a9 仿真工程,并运行 RT-Thread - -1. 创建 rt-thread 仿真项目: - - ![image-20260817110606463](figures/image-20260817110606463-17870467140923.png) - - 或者 - - ![image-20260817110644743](figures/image-20260817110644743-17870467140925.png) - -2. 选择项目基于开发板: - - ![image-20260817111229110](figures/image-20260817111229110-17870467140927.png) - -3. 打开 main.c:![image-20260817111657041](figures/image-20260817111657041-17870467140928.png) - -4. 点击编译,出现控制台的内容即可: - - ![image-20260817111817303](figures/image-20260817111817303-17870467140926.png) - -5. 完成编译后进行 debug 验证,终止 debug:![image-20260817112240678](figures/image-20260817112240678-17870467140929.png) - -6. 点击下载按钮:![image-20260817123026731](figures/image-20260817123026731-178704671409210.png)终端输出hello rt-thread。![image-20260817112509368](figures/image-20260817112509368-178704671409211.png) - -任务 1 仿真 RT-Thread 运行完成。 - -### task3:基于 RT-Thread 4.1.1 版本新建 stm32f407zg 芯片工程 - -1. 创建一个 RT-Thread 项目,输入项目名,**选择 RT-Thread 的版本为 4.1.1**,按照开发板的板载芯片选择内容进行创建: - - ![image-20260817121917695](figures/image-20260817121917695-178704671409212.png) - -2. 打开 main.c 文件: - - ![image-20260817122428925](figures/image-20260817122428925-178704671409213.png) - -3. 在最后一个定义声明后输入代码: - -```c -rt_kprintf("hello RSOC2026!\n"); -``` - -4. 点击编译,控制台输出:![image-20260817122605054](figures/image-20260817122605054-178704671409214.png) -5. 用数据线连接板子 USB-DBG 接口,按下板子上的 PWR-ON 按键打开电源。 -6. 点击下载,点击![image-20260817123106106](figures/image-20260817123106106-178704671409218.png)启动终端,串口选择查看设备管理器,查看STLink是哪个端口,选择对应端口![image-20260817123229229](figures/image-20260817123229229-178704671409215.png) -7. 确认自动打开终端查看串口发送信息:![image-20260817123403869](figures/image-20260817123403869-178704671409216.png) - -工程创建完成。此时在终端中输入 `ps` 并回车可以看到 msh(**Module Shell**)相关信息: - -![image-20260817130200062](figures/image-20260817130200062-178704671409217.png) - -嵌入式工程的完整编译流程为:**编辑 → 编译 → 汇编 → 烧录(下载)**。 - -- **编辑**:编写源代码(如 main.c)。 -- **编译**:将 C 源码编译为汇编代码,再汇编为机器码,最终链接生成 `.elf` 和 `.bin` 文件。 -- **烧录**:通过 ST-Link 等工具将 `.bin` 文件下载至开发板 Flash。 - -### 断点调试的作用 - -- 在代码中设置**断点**后,程序运行到断点处会**暂停**,此时可以查看变量值、寄存器状态、调用栈等信息。 -- 通过单步执行(Step Over / Step Into)逐行观察程序行为,帮助**定位逻辑错误**和**验证程序运行流程**。 -- 调试完成后需**终止 debug**,再点击下载按钮将程序固化到开发板。 - -### RT-Thread 启动与线程调度理解 - -RT-Thread 系统启动过程中会完成硬件初始化、内核对象初始化和组件初始化等工作,随后启动调度器。线程创建后不会立即执行: - -- 使用 `rt_thread_create()` 动态创建线程,或使用 `rt_thread_init()` 初始化静态线程; -- 在系统进入 `rt_thread_startup()` 后,线程进入就绪状态; -- 而调用 `rt_system_scheduler_start()` 后,调度器开始根据优先级和时间片调度就绪线程。 - -系统启动时的版本横幅由 `rt_show_version()` 输出。串口出现如下信息,说明程序已运行至 RT-Thread 的系统启动阶段: - -```text -\ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 17 2026 11:02:01 - 2006 - 2022 Copyright by RT-Thread team -``` - ---- - -#### 1. 系统启动流程 - -RT-Thread 系统从复位开始,依次完成硬件初始化、内核初始化和调度器启动,主要流程如下: - -| 阶段 | 关键函数 | 作用 | -| ------------ | --------------------------- | -------------------------------- | -| 硬件初始化 | rt_hw_board_init() | 初始化时钟、串口、内存等基础硬件 | -| 版本信息 | rt_show_version() | 输出系统版本横幅 | -| 定时器初始化 | rt_system_timer_init() | 初始化系统定时器 | -| 调度器初始化 | rt_system_scheduler_init() | 初始化调度器数据结构 | -| 应用初始化 | rt_application_init() | 创建 main 线程(应用入口) | -| 空闲线程 | rt_thread_idle_init() | 创建 idle 空闲线程 | -| 启动调度器 | rt_system_scheduler_start() | 启动调度器,开始调度就绪线程 | - -#### 2. 线程创建与状态转换 - -线程创建有两种方式: - -- rt_thread_create():**动态创建**,从系统堆中自动分配线程控制块(TCB)和栈空间,使用简单,但需注意堆内存消耗; -- rt_thread_init():**静态初始化**,由用户提供线程控制块和栈空间,不依赖堆,适合资源受限场景。 - -线程创建后不会立即执行,需经历状态转换: - -``` -初始态(INITIAL) → 就绪态(READY) → 运行态(RUNNING) → 挂起态(SUSPEND) → 关闭态(CLOSE) -``` - -- rt_thread_startup():将线程从**初始态**转为**就绪态**,进入调度队列等待调度; -- rt_system_scheduler_start():启动调度器后,调度器根据**优先级**和**时间片**调度就绪线程。 - -#### 3. 调度机制 - -- **优先级抢占式调度**:高优先级线程就绪时,立即抢占正在运行的低优先级线程; -- **时间片轮转**:同优先级的多个线程按时间片轮流执行,避免单个线程独占 CPU; -- **位图调度算法**:RT-Thread 使用位图方式快速查找当前最高优先级的就绪线程,调度效率高。 - -#### 4. main 线程与 idle 线程 - -- **main 线程**:应用入口线程,用户代码从 main() 开始执行,优先级较低; -- **idle 线程**:空闲线程,优先级最低,当系统无其他就绪线程时运行,负责回收被删除线程的资源。 - -#### 5. 启动验证 - -系统启动时的版本横幅由 rt_show_version() 输出。串口出现如下信息,说明程序已运行至 RT-Thread 的系统启动阶段: - -```text -\ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 17 2026 11:02:01 - 2006 - 2022 Copyright by RT-Thread team -``` - -## 三、Markdown 笔记规范 - -### task2:Markdown 语法学习 - -> 所有开头的符号都是英文符号。 - -#### 标题 - -- 一级标题:开头输入 `#` 加上一个空格,输入想要的标题,按下 Enter 即可。 -- 二级标题:开头输入 `##` 加上一个空格,输入想要的标题,按下 Enter 即可。 -- 三级标题:开头输入 `###` 加上一个空格,输入想要的标题,按下 Enter 即可。 -- 几级标题几个 `#` 开头。 - -#### 代码段 - -- 用 ``` 开头加带语言类型,或者先输入三个反引号后续再选择代码语言。 -- 段落中间写代码段可以输入两遍反引号再在中间输入内容就会自动生成代码段(不需要选择代码语言)。 -- 反引号是英文输入法数字 1 左边的下的符号(就是打出波浪号 `~` 的那个键)。 - -**2 个反引号包括起来**:用于段落中间的**短代码**(行内代码),如段落中代码 `printf()`。 - -**3 个反引号 ```**:用于单独占一行的**大段代码**(代码块),例如: - -```c -#include - -int main() -{ - return 0; -} -``` - -#### 表格 - -- **竖线 `|`**:相当于表格的**墙**,用来分隔每一列。 -- **横线 `-`**:相当于表格的**地板**,用来把表头和下面的内容分开。 - -##### 1. 基础表格 - -只需要这样敲(注意:竖线之间要有空格):![image-20260817115139177](figures/image-20260817115139177-178704671409219.png) - -显示内容: - -| 姓名 | 年龄 | 城市 | -| ---- | ---- | ---- | -| 张三 | 25 | 北京 | -| 李四 | 30 | 上海 | - -敲完第一行表头,按一下回车,会自动补全第二行的 `| ---- |`。 - -| t1 | t2 | -| ---- | ---- | -| 1 | 2 | - -##### 2. 表格文字对齐 - -如果想让某一列的文字靠左、居中或靠右,只需要在第二行的横线旁边加上**冒号 `:`**。 - -- **左对齐**:冒号在左边 `:---`(默认就是左对齐) -- **居中对齐**:冒号在两边 `:---:` -- **右对齐**:冒号在右边 `---:` - -**敲代码:**![image-20260817115320960](figures/image-20260817115320960-178704671409220.png) - -显示内容: - -| 姓名 | 分数 | 排名 | -| :--- | :--: | ---: | -| 张三 | 95 | 1 | -| 李四 | 88 | 2 | - -快捷键插入表格:Ctrl + T - -#### 高亮语法 - -用 `` 包裹文字即可高亮文字,或者用 `` 包裹文字,使得文字变成黄底;用 `` 包裹文字,使得文字变成粉底。 - -- `高亮语法` -- 高亮 -- 高亮 - -#### 加粗 - -使用两个 `**` 将文字包裹即可加粗文字,如 **加粗字体**。 - -#### 图片管理 - -- 建议将图片保存至 `./figures` 目录,防止项目迁移时资源丢失。 - ---- - -## 四、Git 基础操作与开源协作 - -### task4:Git 基础操作与开源协作 - -### 1. Git 简介与四大区域数据流 - -Git 是**分布式版本控制系统**,用于保存历史、跟踪修改、多人协作、分支开发、代码回退与开源贡献。 - -``` -工作区 --git add--> 暂存区 --git commit--> 本地仓库 --git push--> 远程仓库 -``` - -| 区域 | 含义 | -| -------- | ------------------------------ | -| 工作区 | 正在编辑的文件 | -| 暂存区 | `git add` 选择本次提交的修改 | -| 本地仓库 | `git commit` 生成版本记录 | -| 远程仓库 | `git push` 上传到 Gitee 等平台 | - -### 2. 安装 Git 并新建仓库 - -- 下载 Git 程序和 tortoisegit 程序(可安装汉化包)进行安装。 -- 安装完成后新建一个文件夹 `RT-Thread-summer-camp`,最好文件路径上全是英文。 -- 注册一个 gitee 账号,新建一个仓库 `RT-Thread-summer-camp`:![image-20260817185327034](figures/image-20260817185327034-178704671409221.png) -- 在仓库右上角点击克隆/下载按钮,在这个界面下复制第三行代码命令:![image-20260817190141688](figures/image-20260817190141688-178704671409222.png) -- 在之前新建的文件夹下右键鼠标选择 Open Git Bash here,进入 Git 终端:![image-20260817190602919](figures/image-20260817190602919-178704671409223.png) - -> **注意**:在 Git 终端里面复制粘贴内容不能用快捷键,要右键点击 Paste、Copy。下面所有出现 `your` 的相关内容都是需要填个人的信息。 - -### 3. Git 信息配置 - -将这两行代码分两次输入进去并按下回车键: - -```bash -git config --global user.name "your_name" -git config --global user.email "your_email@example.com" -``` - -- `--global` 表示全局配置,适用于所有仓库。 -- 如果只想针对某个仓库设置,去掉 `--global` 即可。 -- 可输入 `git config --list` 查看配置信息:![image-20260817191804794](figures/image-20260817191804794-178704671409224.png) - -### 4. 生成 SSH 公钥 - -在终端输入: - -```bash -ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -``` - -连续按回车键,出现问题时输入 `y` 或者 `yes` 等按回车即可,然后输入 `cat /c/Users/Your_User/.ssh/id_rsa.pub` 就生成密钥,复制该密钥,在 gitee 界面点击右上角的头像,选择设置,点击 SSH 公钥,添加公钥,输入名称,输入刚刚生成的公钥,点击保存:![image-20260817194649248](figures/image-20260817194649248-178704671409225.png) - -然后在终端输入 `ssh -T git@gitee.com` 按下回车,看见 successfully 代表密钥测试成功:![image-20260817194932150](figures/image-20260817194932150-178704671409327.png) - -在之前获取 git 信息配置的界面获取 `git remote set-url origin git@gitee.com:your.git`(根据自己的修改):设置当前 git 仓库 URL,将该代码输入到终端中。 - -### 5. 常用命令速查 - -| 序号 | 命令 | 作用 | -| ---- | ------------------------------------------ | ------------------------------------------------------------ | -| 1 | `git init` | 初始化当前目录为 git 仓库 | -| 2 | `git clone ` | 克隆远程仓库到本地 | -| 3 | `git pull origin master` | 拉取远程 master 分支 | -| 4 | `git add file/path` | 添加 file/path 到 git 暂存区 | -| 5 | `git commit -m "message"` | 本次 git 提交日志为 "message" | -| 6 | `git commit -am "msg"` | 暂存并提交(跳过 add) | -| 7 | `git push origin master` | 将本地 git 分支最新内容同步/推送到远端 | -| 8 | `git fetch origin master` | 只把远程的最新代码"下载"到本地的隐藏缓存区,但不会修改你当前正在写的文件 | -| 9 | `git remote -v` | 查看本地仓库连接到了哪个远程服务器(比如 Gitee、GitHub),以及连接的网址是什么 | -| 10 | `git remote add origin ` | 添加远程仓库 | -| 11 | `git remote add upstream ` | 添加上游(官方)仓库 | -| 12 | `git remote set-url origin ` | 修改远程仓库地址 | -| 13 | `git checkout -b test` | 基于当前分支切出一个名为 test 的新分支 | -| 14 | `git switch branch_name` | 切换分支 | -| 15 | `git switch -c ` | 新建并切换分支 | -| 16 | `git stash` | 保存修改。如果你正在开发功能,但突然需要切换分支修 bug,可以先暂存当前修改 | -| 17 | `git stash pop` | 恢复暂存的工作区修改 | -| 18 | `git branch -d test` | 删除分支(`-d` 是 `--delete` 的缩写。如果分支还有没合并的代码,Git 会阻止你删除,防止代码丢失) | -| 19 | `git branch -D test` | 强制删除分支(大写的 `-D` 是强制删除,不管有没有合并都会直接删掉,**慎用**!) | -| 20 | `git push origin --delete first_day` | 删除远端仓库分支(执行后,去 Gitee 网页上刷新,就会发现那个分支不见了) | -| 21 | `git fetch -p` | 清理本地的远程分支缓存(`-p` 是 prune 的缩写,意思是修剪、清理过期的远程分支记录) | -| 22 | `git log --oneline` | 查看简洁提交历史 | -| 23 | `git diff` / `git diff --cached` | 查看工作区 / 暂存区差异 | -| 24 | `git restore ` | 撤销工作区修改 | -| 25 | `git restore --staged ` | 撤销暂存 | -| 26 | `git reset --soft/--mixed/--hard ` | 回退到指定提交 | -| 27 | `git revert ` | 新增提交撤销指定提交 | - -> **补充**:git 暂存区代表你本地的 git 管理库,将指定的文件/文件夹加入 git 暂存库后,这些文件可以得到一个保护。 -> -> **同步方向**:`git pull`:远端 -> 本地;`git push`:本地 -> 远端。 -> -> 有时候你删了远程分支,但在本地用 `git branch -a` 查看时,发现远程分支还在(其实是个残留的影子),可用 `git fetch -p` 清理。 - -**补充:reset vs revert** - -- `reset`:**改写历史**,适合本地未推送的提交。 -- `revert`:**保留历史**,新增一个撤销提交,适合已推送的提交。 - -### 6. 创建本地仓库与同步 - -1. 在终端中分别输入 `git init` 和 `git pull origin master` 将远端 master 仓库拉到本地; -2. 在文件夹下新建 README.md(如果没有的话),输入内容或者修改内容; -3. 输入 `git add README.md`,添加文件到本地仓库中; -4. 输入 `git commit -m "修改说明信息"` 进行暂存操作并提交修改说明; -5. 输入 `git push origin master` 将暂存的文件将本地 git 分支最新内容同步/推送到远端。 - -### 7. 分支管理 - -**创建与切换分支**: - -1. 通过 `git checkout -b test` 或者 `git branch test` 命令创建一个名为 test 的分支,并自动跳转到分支 test; -2. 进行任何修改操作后,输入 `git status` 查看有无显示有红色或绿色文字,有代表存在修改; -3. 输入 `git add ./` 把**当前目录**下面的文件变化添加到 Git 的暂存区,准备提交; -4. 存在修改时,输入 `git commit -m "修改说明信息"` 进行暂存操作并提交修改说明; -5. 输入 `git push`,把暂存的内容推送到当前分支 test 命名相同的文字,如果没有远端这个分支则会报错,此时应该输入 `git push origin test`(意思:推送到名叫 `origin` 的仓库,分支名叫 `test`),或者 `git push -u origin test`(`-u` 意思是 upstream,设置上游关联,加上 `-u` 之后,Git 就记住了:"本地的 `test` 对应远程的 `test`"),之后都可以直接使用 `git push`。 - -**删除分支**: - -1. 切换到主分支:`git checkout master` -2. 删除本地分支:`git branch -d test` -3. 删除远程分支:`git push origin --delete test` - -### 8. 分支同步与合并 - -1. `git branch`:查看本地的"版本列表";`git branch -r`(查看远程分支);`git branch -a`(查看所有分支:本地 + 远程),其中后面带 `remotes/origin/` 开头的,就是**远程仓库里实际存在的分支**。带 `*` 的表示本地仓库当前正停留的分支。 -2. `git fetch origin master`:只把远程的最新代码"下载"到本地的隐藏缓存区,但不会修改你当前正在写的文件;`git pull origin main`:先下载远程最新代码,然后**自动尝试合并**到你当前的分支。如果多人同时修改了同一行代码,可能会产生冲突(Conflict);`git pull --rebase origin main`:先下载远程最新代码,然后把你本地还没推送的提交"挪"到远程最新代码的后面。 -3. `git merge test`:把 `test` 分支里的代码,合并到你"当前所在"的分支里;`git rebase first_day`:把我当前的分支,重新"嫁接"到 `first_day` 的最新状态后面。 -4. `git fetch --all`:这会同步你在 `git remote -v` 中看到的所有远程仓库(比如 origin、upstream 等)的最新状态。 -5. `git fetch origin`:只更新指定远程仓库 -6. `git fetch --prune或者git fetch -p`:如果队友在远程删了某个分支,但你本地用 `git branch -a` 还能看到它的影子(残留记录),执行这个可以“大扫除”。 - -| 特性 | `git merge`(合并) | `git rebase`(变基) | -| ------------ | ---------------------------------------------- | ---------------------------------------- | -| **历史记录** | 会产生一个新的"合并节点",历史像树枝分叉又合拢 | 把你的提交"挪"到主干后面,历史像一条直线 | -| **适用场景** | 团队协作、保留完整的开发痕迹 | 个人分支整理、强迫症想要干净的直线历史 | -| **风险** | 低,比较安全 | 高,如果操作不当容易丢代码 | - -**注意**:合并时进入了 **Vim 编辑器**,这是 Git 默认的文本编辑器。它正在让你确认这次合并(Merge)的说明信息。 - -![image-20260817222814464](figures/image-20260817222814464-178704671409326.png) - -不需要修改任何内容,只需要**保存并退出**即可完成合并。请严格按照以下步骤操作(不要乱按其他键): - -**快速解决步骤(3步走)** - -1. 按一下键盘左上角的 `Esc` 键。 - - *作用:确保你退出了编辑模式,回到了命令模式。(虽然看起来没变化,但这步很关键)* -2. 输入英文冒号 `:`。 - - *注意:必须是英文输入法下的冒号。此时你会看到光标跳到了屏幕的最左下角。* -3. 输入 `wq` 然后按 `回车键 (Enter)`。 - - *解释:`w` 代表 write (保存),`q` 代表 quit (退出)。* - -### 9. Fork + PR 开源协作流程 - -**Fork 仓库**: - -- fork 仓库是为了保证原有的 master 分支不变,只修改自己需要修改的那一部分代码。 -- 选择要 fork 的仓库,点击右上方的 Fork 即可完成 fork:![img](figures/original_1786953074055_7132e3201c0bf72483bb3827262fc273-178704671409328.png) - -**协作流程示意**: - -``` -官方仓库 upstream → Fork → 个人 Gitee Fork origin → Clone → 本地仓库 -→ add/commit/push → 个人 Fork 功能分支 → Pull Request → 官方仓库 Review → Merge -``` - -**origin 与 upstream**: - -- `origin`:自己的 Fork 仓库,平时 `push` 的目标。 -- `upstream`:官方原始仓库,官方更新时用 `git fetch upstream` 同步。 - -**标准 PR 工作流(8 步)**: - -1. 在 Gitee 上 Fork 官方仓库,并同步上游。 -2. `git switch master` + `git pull origin master`:切回主分支并拉取最新。 -3. `git checkout -b day1`:新建当天分支(**严禁直接改 master**)。 -4. 修改/新增文件(建 `笔记/`、`作业/` 目录写 Markdown)。 -5. `git add .` -6. `git commit -m "说明"` -7. `git push origin day1` -8. 在 Gitee 网页创建 PR(你的 fork:day1 → 上游:master),指定 Reviewer。 - -**提交 PR**: - -1. 在修改完成分支后切换到要合并的分支,在贡献代码下点击创建 PR:![img](figures/original_1786950856866_b5d48893754028deb32af06076440e5d-178704671409329.png) -2. 选择源分支和要合并过去的分支,输入 PR 标题和 PR 信息,@需要审核的对象,创建 PR,等待审核和合并:![img](figures/original_1786950943042_60404a3330f350a0caac7611102451c4-178704671409331.png) - -**审核职责**:组员 PR 由小组长审核合并,组长 PR 由导师复核。 - -**PR 拆分建议**:一次 PR 尽量只解决一个明确问题。Day1 通常拆两次——第一次建个人目录模板,第二次交笔记+作业。 - -### 10. 冲突解决 - -**注意**:fork 合并 PR 时可能会产生 git 冲突,原因如下:![img](figures/original_1786954747878_c999a45c5ade5aff578685dc607d5b0b-178704671409330.png) - -**冲突产生原因**:多个 fork 仓库基于不同时间点的主仓库内容进行修改,当主仓库已更新而自己的提交仍基于旧版本时,创建 PR 就会产生冲突。 - -**冲突标记**: - -``` -<<<<<<< HEAD -(当前分支内容) -======= -(另一分支内容) ->>>>>>> branch -``` - -**如何解决 git 冲突**: - -1. fork repo:同步当前上游(在 fork 仓库页面点击"同步"按钮,将上游最新更改同步到自己的 fork); -2. 切到产生冲突的分支,执行 `git merge` / `git rebase`,将最新远端的修改同步到产生冲突的分支; -3. 此时会让你解决冲突(自行选择旧的时间线还是新的时间线的修改)。 - -**解决流程**:阅读两侧修改 → 决定最终内容 → 删除冲突标记 → 保存 → `git add` → `git commit`(merge)或 `git rebase --continue`(rebase)。 - -**Merge vs Rebase 对比**: - -| 方式 | 特点 | 适用场景 | -| ------ | ------------------------------ | ------------------------------ | -| Merge | 保留分支合并关系,历史有分叉 | 团队协作、保留完整开发痕迹 | -| Rebase | 把提交重放到新基线,历史更线性 | 个人分支整理(已共享分支慎用) | - -``` -Rebase 前: Rebase 后: -A - B - C A - B - C - D' - E' - \ \ - D - E -``` - -### 11. 易混概念辨析 - -| 概念对 | 区别 | -| --------------------- | -------------------------------------------- | -| Commit vs Push | Commit 是**本地保存**;Push 才是**上传远程** | -| Fetch vs Pull | Fetch **只取不合并**;Pull = fetch + merge | -| Fork vs Clone | Fork 是**远程→远程**;Clone 是**远程→本地** | -| origin vs upstream | origin = 自己的 Fork;upstream = 官方仓库 | -| add vs commit vs push | **暂存 ≠ 提交 ≠ 推送**,三个阶段 | - -### 12. 常见问题与踩坑记录 - -| 问题 | 处理 | -| ----------------------------------------------- | ------------------------------------------------------------ | -| ST-Link 固件版本过低、烧录报错 | 用 ST 官方固件升级工具更新固件(可多换几个工具尝试) | -| 调试结束后串口出现 `hard fault on thread: main` | 冷启动恢复;若复现,用 pc/lr 结合 .map/调用栈定位,检查栈余量、大数组、空指针、越界、复位方式 | -| Git 不跟踪空文件夹 | 空目录放 `.gitkeep` 或真实文件(如 README)才能提交 | -| `git push` 提示 Everything up-to-date | 没有新提交,先 `git add` 再 `git commit` | -| 命令报 command not found | 所有命令以 `git` 开头(`git commit` 不是 `commit`),注意 checkout 拼写 | -| Windows 带空格路径 `cd` 报 too many arguments | 盘符 `D:\` 写成 `/d/`,路径加双引号 `cd "/d/xxx"` | -| Gitee 禁止命令行暴露邮箱 | 设置里关闭「禁止命令行推送暴露个人邮箱」,或跳过邮箱配置 | -| 私有仓库 401/认证失败 | Gitee 私人令牌过期,重新生成;或改用 openssl 后端 + 令牌 | -| LF/CRLF 换行警告 | 仓库根目录放 `.gitattributes`(内容 `* text=auto`) | -| `fatal: not a git repository` | 当前目录不是仓库,进入项目根目录或 `git init` | -| `remote origin already exists` | 已绑定,用 `git remote set-url origin ` 修改 | - -### 13. 作业提交规范 - -- **目录结构**:在 fork 仓库中按 **"组别/学员姓名/日期"** 的层级创建目录,分别存放笔记和作业。 -- **分支命名**:使用 `day1`、`day2` 等命名分支,避免覆盖模板文件,确保提交结构清晰。 -- **PR 审核**:学员提交 PR 时需指定对应的小组长作为审查人员(Reviewer),审查通过后方可合并。 -- **职责分工**: - - **小组长**:拥有合并 PR 的权限,负责审核组员提交的代码结构、命名规范及内容质量。 - - **导师**:负责审核小组长的 PR,并处理跨组的复杂问题。 -- **每日任务**:每天至少提交 2 个 PR(第一个用于初始化个人目录模板,第二个用于提交当天的作业与笔记内容)。 - ---- diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 877aced72002db8f68f51127ca0b24d0059634f5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,784 +0,0 @@ -# RT-Thread 夏令营 Day3 学习记录 - -## 学习目标 - -**Day3 学习目标**: - -1. 理解多线程同步与通信(IPC)的必要性,掌握临界区、阻塞/非阻塞、线程挂起等基础概念。 -2. 掌握 RT-Thread 五大 IPC 机制:信号量、互斥量、事件集(线程同步)+ 消息邮箱、消息队列(线程间通信)的原理、控制块与核心 API。 -3. 理解优先级反转现象及基于「优先级继承协议」的解决方案。 -4. 完成 Day3 课后四项实战编程(停车场、售票、启动条件检查、快递分拣),将 IPC 理论落地到代码。 - -## 一、IPC 基础概念与总览 - -### 1. 为什么需要 IPC(线程间同步与通信) - -经过前两天的学习,我们已经掌握环境搭建、芯片启动流程与 RT-Thread 启动流程。在实际的 RTOS 系统中会**频繁使用多线程编程**,而多个线程之间需要解决两类问题: - -- **线程同步(Synchronization)**:多个线程访问同一共享资源或协调执行顺序,需要互斥与先后约束。 -- **线程间通信(Communication)**:线程之间需要传递数据,如按键状态、传感器采样值、字符串消息等。 - -RT-Thread 提供的 IPC(Inter-Process Communication,进程间通信)机制正是解决这两类问题的工具。裸机编程中我们常靠**全局变量**在不同功能模块之间通信(一个功能改值、另一个功能读值),但这种方式在多线程下存在竞争风险,RT-Thread 提供了更完善、更安全的机制。 - -### 2. 基础概念:临界区、阻塞与非阻塞、线程挂起 - -#### 2.1 临界区(Critical Section) - -**临界区**:一个资源在同一时间**只能被一个线程使用**,不能同时被多个线程占用,这样的资源区域称为临界区。 - -> 类比:一个房间里有一台饮水机、一套健身器材和一个厕所这三个资源,房间里有四位住户。这三个资源在同一时刻只能被一位住户使用,不能被多个人同时占用——这就是临界区的概念。 - -#### 2.2 阻塞与非阻塞 - -- **阻塞(Blocking)**:一个线程占用了临界区资源,此时其他线程想使用该资源就必须**等待**,这个等待现象称为线程的阻塞。 - > 类比:拿杯子去饮水机接水,必须站在饮水机前等水接完,期间不能干别的事。 -- **非阻塞(Non-blocking)**:把杯子放到饮水机下面后自动装水,装满后会发出通知(如声音提示),等待期间可以去做其他事情。 - > 类比:放好杯子后自动装水,装完通知,期间可以自由活动。 - -#### 2.3 线程挂起(Suspend) - -结合 Day2 学过的线程状态:**当一个线程被阻塞时,它会转入挂起(SUSPEND)状态**;直到阻塞解除之后,才会进入就绪(READY)状态,等待调度器调度运行。 - -### 3. IPC 机制总览 - -RT-Thread 的 IPC 机制分为两大类、共五种: - -| 分类 | 机制 | 一句话用途 | -| --- | --- | --- | -| 线程同步 | 信号量(Semaphore) | 资源计数与线程同步,二值/计数两种形态 | -| 线程同步 | 互斥量(Mutex) | 临界区互斥访问,有所有权、可防优先级反转 | -| 线程同步 | 事件集(Event) | 一对多/多对多的事件组合同步(逻辑与/或) | -| 线程间通信 | 消息邮箱(Mailbox) | 传递固定 4 字节消息,开销低、效率高 | -| 线程间通信 | 消息队列(Message Queue) | 传递不定长消息,支持紧急消息插队 | - -> 同步机制解决「谁先谁后、能不能用」的问题;通信机制解决「数据怎么传」的问题。 - -## 二、线程同步机制(一):信号量与互斥量 - -### 1. 信号量(Semaphore) - -#### 1.1 概念与分类 - -信号量是 RT-Thread 中最基础的同步工具,可以类比为**交通信号灯**:绿灯放行、红灯等待;也可以理解为裸机中的全局标志变量(flag),由 RTOS 内核统一管理。 - -信号量分为三类: - -| 类型 | 取值 | 初始值 | 典型用途 | -| --- | --- | --- | --- | -| 二值信号量 | 0 / 1 | 通常为 0 | **同步**:一个线程完成任务后通知另一个线程 | -| 计数信号量 | 0 ~ N | 资源数量 N | **资源管理**:管理多个同类资源实例 | -| 互斥信号量 | — | — | 归入互斥量章节讲解(具有所有权) | - -- **二值信号量用于同步**:初始值通常为 0,表示「事件尚未发生」。线程 A 完成某任务后释放(值 0→1),线程 B 获取到信号量(值 1→0)后被唤醒执行后续操作。 -- **计数信号量用于资源管理**:初始值为资源数量(如 3 个车位 → 3)。线程获取信号量相当于占用一个资源(值 -1),释放相当于归还资源(值 +1)。 - -#### 1.2 信号量的工作原理(take / release) - -- **获取信号量 `rt_sem_take()`**:当信号量值 > 0 时,获取成功并执行值 -1;当信号量值为 0 时无法获取,线程会根据 `timeout` 参数进行等待: - - 若 `timeout = 10`(tick),则等待 10 个 tick,期间仍无法获取就返回超时错误; - - 若 `timeout = 0`(无等待),获取不到立即返回超时,**不会阻塞线程**,继续执行后续逻辑。 -- **释放信号量 `rt_sem_release()`**:信号量值 +1,并唤醒正在等待该信号量的线程,使其能够获取。 - -> **注意区分**:释放(`rt_sem_release`)与脱离(`rt_sem_detach`)不是一回事。释放是归还信号量资源;脱离是把通过 `rt_sem_init` 静态初始化的信号量对象从内核对象管理器中移除(相当于删除)。 - -#### 1.3 信号量控制块 - -信号量对象继承自 IPC 对象基类 `rt_ipc_object`,其主要结构如下: - -```c -struct rt_semaphore -{ - struct rt_ipc_object parent; /* 继承 IPC 基类:含等待队列 suspend_thread、自旋锁等 */ - rt_uint16_t value; /* 信号量的当前计数值 */ -}; -``` - -- `parent`(`rt_ipc_object`):包含挂起线程等待队列(存放因获取不到信号量而阻塞的线程链表)与用于保护的自旋锁; -- `value`:当前信号量计数值,体现剩余可用资源数量。 - -#### 1.4 信号量核心 API - -| API | 作用 | -| --- | --- | -| `rt_sem_create(name, value, flag)` | **动态创建**信号量,返回句柄;失败返回 `RT_NULL` | -| `rt_sem_init(sem, name, value, flag)` | **静态初始化**信号量,对象与内存由用户提供 | -| `rt_sem_take(sem, time)` | 获取信号量,`time` 为等待超时(tick),`0` 表示不等待 | -| `rt_sem_trytake(sem)` | 无等待获取,等价于 `rt_sem_take(sem, 0)` | -| `rt_sem_release(sem)` | 释放信号量,值 +1,唤醒等待线程 | -| `rt_sem_delete(sem)` | 删除动态创建的信号量 | -| `rt_sem_detach(sem)` | 脱离静态初始化的信号量 | - -**`flag` 参数的选择**(信号量/互斥量/事件集/邮箱/队列通用): - -| flag 取值 | 含义 | -| --- | --- | -| `RT_IPC_FLAG_FIFO` | 先进先出等待:按阻塞先后顺序唤醒 | -| `RT_IPC_FLAG_PRIO` | 优先级等待:按线程优先级高低唤醒 | - -> 课程强调:在**实时性要求高**的场景下,应优先选择**优先级等待(PRIO)**模式,保证高优先级线程先获得资源。 - -#### 1.5 演示示例(信号量线程同步) - -课程在 ART-Pi Studio 中演示了信号量示例的完整流程: - -1. **创建工程**:基于芯片创建工程,RT-Thread 版本选择 4.1.1,芯片选择 STM32F4 系列(星火一号为 F407ZEG),调试器选择 ST-Link。 -2. **添加软件包**:通过 settings 配置工具添加官方**内核示例软件包(kernel_samples)**,输入关键词搜索并添加。 -3. **开启示例**:在内核示例中开启信号量示例。 -4. **分析代码逻辑**:示例中线程一循环计数并**释放信号量**,线程二**等待信号量**后执行 LED 点亮/熄灭操作,实现两个线程之间的**同步**——线程一通知,线程二响应。 - -> 该示例体现的是「二值信号量做同步」的典型用法:释放方与获取方互相配合,形成「生产-消费」式的事件通知关系。 - -### 2. 实战编程一:停车场车位管理(计数信号量) - -> 对应作业 `parking_demo.c`,MSH 命令:`parking_demo` - -#### 2.1 实验要求 - -1. 创建一个**初始值为 3 的计数信号量**,表示剩余车位; -2. 创建 5 个车辆线程; -3. 每辆车到达后申请一个车位; -4. 获得车位后打印车辆编号和进入信息; -5. 车辆停留一段时间后离开,并释放车位; -6. 未在 2 秒内获得车位的车辆打印等待超时信息,稍后重新申请; -7. 任意时刻进入停车场的车辆数不能超过 3。 - -#### 2.2 核心代码解析(要点提炼) - -```c -static rt_sem_t park_sem = RT_NULL; /* 计数信号量,初始值 3 */ -static volatile rt_uint32_t park_entered = 0; /* 当前在场车辆数 */ -static volatile rt_uint8_t park_running = 0; /* 运行标志,支持 MSH 重复执行 */ - -park_sem = rt_sem_create("park_sem", 3, RT_IPC_FLAG_PRIO); /* 创建初始值=3 的计数信号量 */ - -static void car_entry(void *param) -{ - while (1) - { - /* 申请车位:2 秒超时,取不到则打印等待并延时 100ms 重新申请 */ - if (rt_sem_take(park_sem, rt_tick_from_millisecond(2000)) != RT_EOK) - { - rt_kprintf("car%d waiting for a space\n", no); - rt_thread_mdelay(rt_tick_from_millisecond(100)); - continue; - } - rt_enter_critical(); /* 进入临界区保护共享计数 */ - park_entered++; - rt_exit_critical(); - rt_kprintf("car%d entered, used spaces: %d\n", no, park_entered); - rt_thread_mdelay(rt_tick_from_millisecond(2500)); /* 停留 2.5s */ - rt_enter_critical(); - park_entered--; - rt_exit_critical(); - rt_kprintf("car%d left\n", no); - rt_sem_release(park_sem); /* 离开后释放车位 */ - break; - } - /* 全部离场后:删除信号量、复位标志,使命令可重复执行 */ -} -``` - -代码要点: - -- **`rt_sem_create` 初始值 3**:计数信号量天然限制「最多 3 辆车同时在停车场」,即使 5 个线程并发申请,`rt_sem_take` 也只会让 3 个成功; -- **超时重试**:`rt_sem_take(park_sem, 2000ms)` 取不到信号量时返回非 `RT_EOK`,车辆打印等待信息后 `mdelay(100ms)` 再进入 while 循环重新申请,而非直接退出; -- **临界区保护共享计数**:`park_entered` 是跨线程共享变量,用 `rt_enter_critical/rt_exit_critical`(全局关中断)保护自加/自减,避免并发竞争; -- **重复执行设计**:所有车辆线程结束后(`park_finished == park_created`)删除信号量并复位运行标志,保证再次执行 `parking_demo` 不会因同名 IPC 对象残留而出错。 - -#### 2.3 实验结果与现象 - -![停车场运行输出](figures/image-20260820144226421.png) - -- 前 3 辆车(car1/car2/car3)立即获得车位进入,`used spaces` 依次为 1/2/3; -- 第 4、5 辆车因车位已满,打印 `car4 waiting for a space` 等等待信息; -- 有车离开释放车位后,等待中的车辆重新申请成功进入,`used spaces` 始终 ≤ 3; -- 计数信号量保证了「任意时刻进入停车场的车辆数不超过 3」的资源上限约束。 - -#### 2.4 思考题解答:为什么用计数信号量而不是互斥量? - -> 计数信号量表示「可用的同类资源数量」(这里是 3 个车位),允许多个线程**同时占用**、数量受控——3 辆车可以同时在停车场内;而互斥量本质是二值的「排他锁」,同一时刻**只允许一个线程**访问临界区,无法表达"允许 3 个并行"的资源池语义。管理「多个可并行使用的资源实例」正是计数信号量的典型场景,互斥量则适用于"同一时刻只能一人使用"的互斥访问。 - -### 3. 互斥量(Mutex) - -#### 3.1 概念与特性 - -互斥量可以理解为一种**特殊的二值信号量**,但比信号量多出三个重要特性: - -| 特性 | 说明 | -| --- | --- | -| **所有权(Ownership)** | 只有**持有互斥量(拥有所有权)的线程**才能释放它,其他线程不能越权释放 | -| **递归访问(Recursive)** | 同一线程可以重复获取同一互斥量(内部计数 `hold` 递增),不会造成自我死锁 | -| **防止优先级反转** | 基于优先级继承协议,避免高优先级线程被无关的中优先级线程间接阻塞 | - -> 类比:互斥量就像一把带钥匙的门锁(如停车场门禁)——只有拿着钥匙(所有权)的人才能开锁/闭锁;一个人可以反复进出(递归),但别人无法替他锁门。 - -#### 3.2 优先级反转与优先级继承协议 - -**优先级反转现象**(经典三线程场景): - -1. 线程 C(低优先级)先获得互斥量 M,正在执行; -2. 线程 A(高优先级)到来,尝试获取 M 失败,被阻塞等待; -3. 此时线程 B(中优先级)就绪,因为 B 优先级高于 C,**B 抢占 CPU 运行**; -4. 由于 C 被 B 抢占,迟迟无法执行完并释放 M,导致高优先级的 A 被中优先级的 B **间接阻塞**——这就是「优先级反转」。 - -**解决方案:优先级继承协议**: - -- 当高优先级线程 A 尝试获取线程 C 持有的互斥量 M 时,内核会**临时把 C 的优先级提升到与 A 同级**; -- 此时 C 的优先级高于 B,B **无法抢占 C**,C 得以继续运行并释放 M; -- C 释放 M 后,A 获得 M 继续执行;A 执行完后,B 才正常运行。这样 B 就无法"插队"抢占 A 的资源。 - -**使用互斥量的注意事项**: - -1. 获得互斥量后,**持有时间必须尽可能短**,不能长时间占用(避免阻塞其他线程太久); -2. 持有互斥量期间**不得更改线程优先级**,否则可能人为引入无界的优先级反转问题。 - -#### 3.3 互斥量控制块 - -```c -struct rt_mutex -{ - struct rt_ipc_object parent; /* 继承 IPC 基类 */ - rt_uint16_t value; /* 互斥量值(开锁/闭锁状态) */ - rt_uint8_t original_priority; /* 持有线程的原始优先级(用于优先级继承后恢复) */ - rt_uint8_t hold; /* 持有次数(支持递归获取) */ - struct rt_thread *owner; /* 当前持有互斥量的线程 */ - rt_list_t taken_list; /* 该线程持有的对象链表 */ -}; -``` - -关键成员说明: - -- **`owner`**:当前持有互斥量的线程,体现所有权属性; -- **`hold`**:持有次数,体现递归访问特性(同线程多次 take 时递增); -- **`original_priority` / 优先级相关成员**:用于实现优先级继承——临时提升后需记录原始优先级以便恢复; -- **等待队列**:存放等待获取互斥量的线程(按优先级排序,保证实时性); -- **自旋锁**:并发环境下保护互斥量对象内部成员。 - -#### 3.4 互斥量核心 API - -| API | 作用 | -| --- | --- | -| `rt_mutex_create(name, flag)` | **动态创建**互斥量 | -| `rt_mutex_init(mutex, name, flag)` | **静态初始化**互斥量 | -| `rt_mutex_take(mutex, time)` | 获取互斥量(带超时) | -| `rt_mutex_trytake(mutex)` | 无等待获取互斥量 | -| `rt_mutex_release(mutex)` | 释放互斥量(**仅持有者可释放**),开锁并使持有线程失去控制权 | -| `rt_mutex_delete(mutex)` / `rt_mutex_detach(mutex)` | 删除动态 / 脱离静态互斥量 | - -> 课程强调:互斥量的 `flag` 无论填「先进先出」还是「优先级等待」,**内核都会按优先级等待方式处理**,因为互斥量必须保证系统实时性(配合优先级继承)。 - -#### 3.5 演示示例(临界区数据保护) - -课程在软件包中打开互斥量示例: - -- 示例有两个线程,线程一、线程二分别对 `number1` 和 `number2` 两个变量执行加 1; -- 通过互斥量保护,保证两个 number 的值**保持同步一致**; -- 在调试模式下对比了**有无互斥量保护**时 `number1` 与 `number2` 的数值变化:不加互斥量时,两个线程同时读写共享数据会出现数值错乱;加上互斥量后,临界区的读写被串行化,数值始终保持一致。直观展示了互斥量对临界区数据的保护作用。 - -### 4. 实战编程二:多窗口售票系统(互斥量) - -> 对应作业 `ticket_demo.c`,MSH 命令:`ticket_demo` - -#### 4.1 实验要求 - -1. 创建两个售票线程,分别代表窗口 A 和窗口 B; -2. 使用一个全局变量保存剩余票数; -3. 每个窗口循环完成:检查是否还有余票 → 将余票减 1 → 记录售出的票号 → 打印售票结果; -4. 使用互斥量保护完整的售票过程; -5. 售完后两个线程自动退出; -6. 最终必须满足:20 张票全部售出、不出现相同票号、剩余票数不能小于 0; -7. 对比实验:先不加互斥量并在「读取余票」与「修改余票」之间加延时,观察重复售票/余票错误。 - -#### 4.2 核心代码解析(要点提炼) - -```c -static rt_mutex_t ticket_mutex = RT_NULL; -static volatile rt_int32_t ticket_count = 20; /* 全局余票 */ - -ticket_mutex = rt_mutex_create("tk_mutex", RT_IPC_FLAG_PRIO); - -static void window_entry(void *param) -{ - while (1) - { - rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); /* 加锁:进入临界区 */ - if (ticket_count <= 0) /* 检查余票 */ - { - rt_mutex_release(ticket_mutex); /* 无票则解锁退出 */ - break; - } - int no = ticket_count; /* 记录票号(=当前余票) */ - ticket_count--; /* 余票减 1 */ - rt_mutex_release(ticket_mutex); /* 解锁:离开临界区 */ - rt_kprintf("window %s sold ticket %d\n", name, no); - rt_thread_mdelay(rt_tick_from_millisecond(20)); /* 让出 CPU */ - } - /* 两窗口都结束后:删除互斥量、复位标志 */ -} -``` - -代码要点: - -- **互斥量保护完整临界区**:`take → 检查余票 → 记录票号 → 减 1 → release` 是一个不可分割的整体,任意时刻只有一个窗口能执行这一段,因此票号不会重复、余票不会为负; -- **票号取自当前余票**:在锁内先把 `ticket_count` 存为 `no` 再自减,保证每张票号唯一、递减输出(20 → 1); -- **无锁时的问题**:若不加互斥量,两个窗口可能在「检查余票」和「减 1」之间被打断——A 读到余票 1 后切走,B 也读到余票 1,两人都卖 1 号票,出现**重复票号**;若在「读」与「改」之间加延时,这个竞争窗口被放大,问题更明显; -- **重复执行设计**:两个窗口线程均退出后删除互斥量并复位标志,`ticket_demo` 可重复执行。 - -#### 4.3 实验结果与现象 - -![售票系统运行输出](figures/image-20260820142203309-17872071368765.png) - -- **有互斥量**:输出严格递减且不重复(`window A sold ticket 20` / `window B sold ticket 19` / …),最终打印 `all tickets sold`、`remaining tickets: 0`,20 张票全部售出; -- **对比实验(无互斥量 + 读改间延时)**:出现**重复票号**(同一票号被两个窗口售出)或余票计数错误,验证了临界区不加锁的竞争问题; -- **结论**:互斥量把「读-改-写」多步操作串行化,是保证共享数据一致性的关键。 - -#### 4.4 思考题解答:为什么只给 `ticket_count--` 加锁仍可能不安全? - -> 售票流程是「检查余票 → 记录票号 → 减 1」的**多步操作**,即使只对减 1 加锁,在「检查是否还有余票」和「修改余票」之间仍然存在竞争窗口:两个窗口可能同时读到余票为 1,都判定"有余票",各自卖出一张重复票号。安全的关键在于**用互斥量保护从检查到修改的完整临界区**(读-改-写作为一个原子整体),而不是只锁住其中单个语句。 - -## 三、线程同步机制(二):事件集(Event) - -### 1. 事件集的概念与特点 - -事件集用于实现**一对多、多对多**的线程同步:一个线程等待多个事件,或多个线程等待多个事件。一个事件集可以包含多种事件。 - -> 类比:公交站等车有三种场景—— -> -> 1. 去某地只有**一种公交**能到,等到这班公交就能出发(**特定事件**唤醒线程); -> 2. 去某地有**三种公交**都能到,等到**任意一辆**即可出发(**任意事件**唤醒线程); -> 3. 与同伴 p2 约好一起去,必须**公交到达且同伴也到达**,两个条件都满足才能出发(**多个事件同时发生**才唤醒线程)。 - -**事件集的实现方式**:用**一个 32 位无符号整型变量**表示事件集合,**每一位(bit)代表一个事件**;线程通过**逻辑与 / 逻辑或**将一个或多个事件关联成组合。 - -**事件集的特点**: - -| 特点 | 说明 | -| --- | --- | -| 位映射 | 32 位无符号整型记录事件,每 bit 一个事件,每个线程最多拥有 32 个事件标志 | -| 只用于同步 | 事件仅用于**同步**,**不提供数据传输**功能 | -| 无排队性 | 多次向线程发送同一事件,若线程未读取,效果等同于只发生一次(如发两次事件2 ≈ 发一次事件2) | -| 事件独立 | 事件与线程相关,事件与事件之间相互独立 | - -### 2. 逻辑与 / 逻辑或 / 清除标记 - -线程在接收事件时,通过 `opt` 参数组合以下三种属性: - -| 属性 | 宏定义 | 含义 | -| --- | --- | --- | -| 逻辑或 | `RT_EVENT_FLAG_OR` | 多个事件中**任意一个**发生即可唤醒线程 | -| 逻辑与 | `RT_EVENT_FLAG_AND` | 多个事件**全部发生**才唤醒线程 | -| 清除标记 | `RT_EVENT_FLAG_CLEAR` | 事件被接收后,将对应的 1 位**清零**(类似读取即清除) | - -> 对应公交站场景:逻辑或 = "任意一辆公交到站就走";逻辑与 = "公交和同伴都到了才走";清除标记 = "事件处理完后把标志位清零"。 - -### 3. 事件集控制块与核心 API - -**控制块**:继承 IPC 对象基类,增加事件集合成员 `set`(32 位无符号整型,记录 32 个事件的标记),并用自旋锁保护内部成员。 - -```c -struct rt_event -{ - struct rt_ipc_object parent; /* 继承 IPC 基类 */ - rt_uint32_t set; /* 事件集合:每 bit 代表一个事件 */ -}; -``` - -**核心 API**: - -| API | 作用 | -| --- | --- | -| `rt_event_create(name, flag)` | **动态创建**事件集 | -| `rt_event_init(event, name, flag)` | **静态初始化**事件集 | -| `rt_event_send(event, set)` | 发送事件(将 `set` 中的位置 1),可唤醒匹配的等待线程 | -| `rt_event_recv(event, set, opt, timeout, recved)` | 接收事件:按 `opt`(AND/OR/CLEAR)匹配,`recved` 输出实际收到的事件 | -| `rt_event_delete(event)` / `rt_event_detach(event)` | 删除动态 / 脱离静态事件集 | - -- `flag` 同样可取值 `RT_IPC_FLAG_FIFO` 或 `RT_IPC_FLAG_PRIO`; -- `rt_event_recv` 中 `timeout` 为等待超时,超时后若事件未满足则返回超时错误。 - -### 4. 演示示例(逻辑或 + 逻辑与) - -课程演示了事件集的典型执行流程: - -- **线程一**:第一次以**逻辑或**模式等待事件 3 或事件 5(任一发生即唤醒);随后第二次以**逻辑与**模式等待事件 3 和事件 5 同时发生; -- **线程二**:依次发送事件 3、事件 5; -- 运行现象: - - 第一次等待时,线程二只要发出事件 3(或 5),线程一即被唤醒(**逻辑或:任意一个即可**); - - 第二次等待时,必须事件 3 和事件 5 **都**被发出,线程一才会被唤醒(**逻辑与:全部满足才唤醒**)。 - -> 结论:接收时靠 `opt` 标志位区分——逻辑或只要任一事件发生即可唤醒;逻辑与必须所有关联事件全部发生才会唤醒。 - -### 5. 实战编程三:系统启动条件检查(事件集) - -> 对应作业 `startup_demo.c`,MSH 命令:`startup_demo` - -#### 5.1 实验要求 - -设备启动前需要完成三个独立模块初始化(网络、传感器、存储),只有三个模块全部准备完成,主业务线程才能启动。 - -1. 定义三个事件位:`EVENT_NET_READY (1U<<0)`、`EVENT_SENSOR_READY (1U<<1)`、`EVENT_STORAGE_READY (1U<<2)`; -2. 创建三个初始化线程,各自经过不同时间的延时后发送对应的就绪事件; -3. 主业务线程必须用 **AND 模式**等待三个事件全部到达,然后打印 `all modules are ready` / `business task started`; -4. **超时测试**:存储模块初始化设为 5 秒,主业务线程最多等待 3 秒;超时后打印尚未完成的模块,并再次等待。 - -#### 5.2 核心代码解析(要点提炼) - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -static rt_event_t startup_evt = RT_NULL; -static volatile rt_uint32_t ready_flags = 0; /* 各模块就绪状态位,用于超时诊断 */ - -/* 三个初始化线程:延时后置位 ready_flags 并 rt_event_send 对应事件 */ -static void net_init_entry(void *param) -{ - rt_thread_mdelay(rt_tick_from_millisecond(1000)); /* 网络 1 秒就绪 */ - ready_flags |= EVENT_NET_READY; - rt_event_send(startup_evt, EVENT_NET_READY); - rt_kprintf("network ready\n"); -} -/* sensor_init_entry:2 秒;storage_init_entry:5 秒(触发超时) */ - -static void business_entry(void *param) -{ - while (1) - { - /* AND + CLEAR:三个事件全部到达才唤醒,唤醒后清除事件位 */ - rt_err_t res = rt_event_recv(startup_evt, EVENT_ALL, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - rt_tick_from_millisecond(3000), RT_NULL); - if (res == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - break; - } - /* 3 秒超时:逐个检查 ready_flags,打印尚未完成的模块,再次等待 */ - if (!(ready_flags & EVENT_NET_READY)) - rt_kprintf("wait modules timeout, network is not ready\n"); - if (!(ready_flags & EVENT_SENSOR_READY)) - rt_kprintf("wait modules timeout, sensor is not ready\n"); - if (!(ready_flags & EVENT_STORAGE_READY)) - rt_kprintf("wait modules timeout, storage is not ready\n"); - } - /* 清理:等初始化线程结束后删除事件集、复位标志 */ -} -``` - -代码要点: - -- **位定义**:三个模块各占一个事件位,`EVENT_ALL` 组合成"全部就绪"的掩码; -- **发送**:每个初始化线程 `mdelay` 模拟初始化耗时后,置位 `ready_flags` 并 `rt_event_send` 发送对应事件; -- **AND + CLEAR 接收**:`rt_event_recv(startup_evt, EVENT_ALL, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, 3000ms)` 表示"三个事件全部到位才唤醒,唤醒后清除标志位";3 秒超时返回非 `RT_EOK`; -- **超时诊断**:超时后利用 `ready_flags`(普通变量记录就绪状态)逐个判断哪个模块未就绪并打印——这里 `ready_flags` 是为了输出"尚未完成的模块"而额外维护的镜像,`rt_event_recv` 本身不提供逐个查询; -- **重复执行设计**:主业务线程退出后延时 100ms 等待初始化线程全部结束,再删除事件集并复位标志。 - -#### 5.3 实验结果与现象 - -![启动条件检查运行输出](figures/image-20260820150431172.png) - -- 网络(1s)、传感器(2s)先就绪,打印 `network ready` / `sensor ready`; -- 存储模块需 5 秒,主业务线程 3 秒超时,打印 `wait modules timeout, storage is not ready`,随后再次进入等待; -- 存储 5 秒就绪后打印 `storage ready`,三个事件全部到位,主业务线程被唤醒,打印 `all modules are ready` / `business task started`; -- 验证了事件集 **AND 模式 + 超时重试**在"多条件聚合判断"场景下的有效性。 - -#### 5.4 思考题解答:用三个独立信号量实现 vs 使用事件集的区别? - -> 事件集可以**一次等待、批量检查多个事件**,并直接用 AND 逻辑表达"全部就绪"的聚合条件,唤醒条件是"事件集合"而非单个计数;若用三个独立信号量,主线程要么嵌套等待多次、要么难以在一个等待中同时判断三个条件,逻辑更繁琐、表达能力更弱。此外事件集的**无排队性**(重复发送等同一次)也天然契合"就绪状态"这类只关心"是否发生"的同步语义。事件集天然适合"多条件聚合判断"的同步场景。 - -## 四、线程间通信机制:消息邮箱与消息队列 - -### 1. 消息邮箱(Mailbox) - -#### 1.1 概念与特点 - -裸机编程中常用全局变量在不同功能之间通信(一方改值、一方读值);在 RT-Thread 中,**消息邮箱**是线程间传递消息的轻量级工具。 - -> 场景类比:线程一检测按键状态,将按键状态作为**邮件**发送到邮箱;线程二从邮箱读取邮件,根据按键状态控制 LED 亮灭。线程一也可以扩展为多个发送方:如三个线程分别发送按键状态、ADC 采样信息、传感器数据到邮箱,LCD 显示线程统一从邮箱获取并显示。 - -**特点**: - -| 特点 | 说明 | -| --- | --- | -| 固定 4 字节 | 每封邮件固定容纳 **4 字节**(32 位系统中一个指针的大小),可存放指针或整型数据 | -| 开销低、效率高 | 相比队列更轻量,适合高频小数据传递 | -| 发送非阻塞 | 发送通常**非阻塞**,可安全用于**中断服务程序 / 定时器**向线程发送消息 | -| 接收可阻塞 | 接收通常在线程中执行,邮箱无邮件时按超时时间阻塞等待 | - -> 结论:邮箱是「中断服务和定时器向线程发送消息」的有效手段;接收一般放在线程中,不在中断里接收邮件。 - -#### 1.2 邮箱控制块 - -```c -struct rt_mailbox -{ - struct rt_ipc_object parent; /* 继承 IPC 基类 */ - rt_ubase_t *msg_pool; /* 邮箱缓冲区起始地址(注意:这是地址) */ - rt_uint16_t size; /* 邮箱容量(邮件个数) */ - rt_uint16_t entry; /* 邮箱中当前邮件数量 */ - rt_uint16_t in_offset; /* 邮件缓冲区进指针(写位置) */ - rt_uint16_t out_offset; /* 邮件缓冲区出指针(读位置) */ - rt_list_t send_suspend_thread; /* 发送线程挂起等待队列 */ - rt_ubase_t resource_lock; /* 资源锁 */ -}; -``` - -关键成员:缓冲区起始地址与大小、当前邮件数量、进出指针(环形管理)、发送线程挂起队列、资源锁。 - -#### 1.3 邮箱核心 API - -| API | 作用 | -| --- | --- | -| `rt_mb_create(name, size, flag)` | **动态创建**邮箱,`size` 为邮件容量 | -| `rt_mb_init(mb, name, msgpool, size, flag)` | **静态初始化**邮箱(传入缓冲区指针) | -| `rt_mb_send(mb, value)` | 发送邮件(非阻塞) | -| `rt_mb_send_wait(mb, value, timeout)` | 带超时的发送 | -| `rt_mb_recv(mb, value, timeout)` | 接收邮件(可阻塞,按超时等待) | -| `rt_mb_delete(mb)` / `rt_mb_detach(mb)` | 删除动态 / 脱离静态邮箱 | - -#### 1.4 演示示例(邮件收发) - -课程演示邮箱示例: - -- **线程一**:以**永久等待**方式从邮箱接收邮件,收到后打印邮件内容; -- **线程二**:循环发送字符串一、字符串二各 5 次,最后发送字符串三,用于结束线程一; -- 运行现象:线程一依次收到线程二发出的邮件;线程二发送完结束消息后,线程一退出。 - -> 邮箱每封邮件固定 4 字节,示例中传递的"字符串"本质是把**字符串指针**作为 4 字节邮件传递,接收方再按指针取值打印。 - -### 2. 消息队列(Message Queue) - -#### 2.1 概念与特点 - -**消息队列是消息邮箱的扩展**:邮箱只能传固定 4 字节,而消息队列支持**不定长消息**,常用于线程间交换任意长度的消息、串口接收不定长数据等场景。 - -**发送流程**: - -1. 消息队列对象先从**空闲消息块列表**上取下一个空闲消息块; -2. 把发送方(线程或中断服务程序)要发送的消息内容**复制**到这个消息块上; -3. 再把这个消息块挂到**消息队列的尾部**。 - -**满队列处理**:当且仅当空闲消息列表上有可用空闲块时,发送才能成功。若空闲块不足(队列已满),发送方会收到错误码提示「消息队列已满」;也可以使用带超时的 `send_wait` 等待空闲块。 - -**紧急消息插队**:`rt_mq_urgent()` 将消息**插队到队列首部**,接收线程**第一个就能接收到紧急消息**,保证高优先级消息被优先处理。 - -#### 2.2 消息队列控制块 - -消息队列基于**链表**管理消息块,控制块结构大致包含: - -```c -struct rt_messagequeue -{ - struct rt_ipc_object parent; /* 继承 IPC 基类 */ - void *msg_pool; /* 消息池起始地址 */ - rt_uint16_t msg_size; /* 每条消息大小 */ - rt_uint16_t max_msgs; /* 最大消息条数 */ - rt_uint16_t entry; /* 当前消息条数 */ - void *msg_queue_head; /* 消息队列链表头 */ - void *msg_queue_tail; /* 消息队列链表尾 */ - void *msg_queue_free; /* 空闲消息块链表头 */ - rt_list_t receive_suspend_thread;/* 接收线程挂起等待队列 */ -}; -``` - -关键成员:消息池指针与大小、每条消息大小、最大消息条数、空闲消息块链表、消息队列链表、挂起接收线程队列等。 - -#### 2.3 消息队列核心 API - -| API | 作用 | -| --- | --- | -| `rt_mq_create(name, msg_size, max_msgs, flag)` | **动态创建**消息队列(指定消息大小与最大条数) | -| `rt_mq_init(mq, name, msgpool, msg_size, pool_size, flag)` | **静态初始化**消息队列 | -| `rt_mq_send(mq, buffer, size)` | 发送消息(复制内容到队列尾部) | -| `rt_mq_send_wait(mq, buffer, size, timeout)` | 带超时发送(队列满时按超时等待空闲块) | -| `rt_mq_urgent(mq, buffer, size)` | **紧急发送**:插队到队列首部 | -| `rt_mq_recv(mq, buffer, size, timeout)` | 接收消息(复制到缓冲区,可阻塞) | -| `rt_mq_delete(mq)` / `rt_mq_detach(mq)` | 删除动态 / 脱离静态消息队列 | - -> `rt_mq_recv` 需要指定存放消息的缓冲区;接收到的消息内容会被复制到该缓冲区中。 - -#### 2.4 演示示例(紧急消息插队) - -课程演示消息队列示例: - -- 使用**静态方式**创建消息队列:传入内存池缓冲区地址,设定每条消息大小 1 字节、内存池大小,以优先级等待方式; -- **线程一**:以永久等待方式接收消息,收到后打印消息内容;接收满 19 次后退出线程并脱离队列; -- **线程二**:定义要发送的消息(字符),当 `count == 8` 时发送**紧急消息**,`count >= 20` 时发送 20 次后退出,其余情况不断发送普通消息到队列; -- 运行/调试现象: - 1. 线程二先发送 `a`,线程一收到并打印 `a`; - 2. 线程二陆续发送 `b c d e f g h`,并在第 8 次发送**紧急消息 `i`**(插队到队列首部); - 3. 线程一被唤醒后取到的**第一个消息是紧急消息 `i`**(而不是按顺序的 `b`),第二个才取到 `b`; - 4. 之后的消息按发送顺序依次接收,直到 19 次后线程一退出。 - -> 结论:紧急消息 `i` 通过插队到队列头部,被接收线程**优先读取**,验证了 `rt_mq_urgent()` 的紧急插队机制。 - -### 3. 实战编程四:快递分拣中心(消息队列) - -> 对应作业 `package_demo.c`,MSH 命令:`package_demo` - -#### 3.1 实验要求 - -两个收件线程不断产生快递信息,一个分拣线程从消息队列中取出快递并按目的地区域分类。 - -1. 创建**容量为 5 条消息**的消息队列; -2. 创建两个发件线程,每个线程产生 5 条快递消息; -3. 快递编号不得重复; -4. 分拣线程接收消息,并打印快递编号、区域和重量; -5. 分拣完成后统计:总快递数量、各区域快递数量、所有快递总重量; -6. 当队列已满时,发送线程不得崩溃,应记录发送失败次数; -7. 两个发送线程完成后,向分拣线程发送一条结束消息; -8. 分拣线程收到结束消息后打印统计结果并退出。 - -消息结构: - -```c -struct package_msg -{ - rt_uint32_t id; /* 快递编号 */ - rt_uint8_t region; /* 目的区域:1=华东 2=华南 3=华北 */ - rt_uint16_t weight; /* 重量(g) */ -}; -``` - -#### 3.2 核心代码解析(要点提炼) - -```c -#define PKG_PER_SENDER 5 /* 每个发件线程产生 5 条 */ -#define PKG_QUEUE_SIZE 5 /* 消息队列容量 5 条 */ -#define REGION_END 0 /* 结束消息标志 */ - -static rt_mq_t pkg_mq = RT_NULL; -static volatile rt_uint32_t send_failed = 0; /* 队列满导致的发送失败次数 */ - -pkg_mq = rt_mq_create("pkg_mq", sizeof(struct package_msg), PKG_QUEUE_SIZE, RT_IPC_FLAG_PRIO); - -/* 发件线程:base 为 1000 或 2000,产生 5 条快递消息,最后发送结束消息 */ -static void sender_entry(void *param) -{ - for (i = 0; i < PKG_PER_SENDER; i++) - { - msg.id = base + i; /* 1001~1005 或 2001~2005,编号不重复 */ - msg.region = region_tab[i]; /* 区域表 {1,2,3,1,2} */ - msg.weight = weight_tab[i]; /* 重量表 */ - /* 队列满时发送失败不崩溃,记录失败次数 */ - if (rt_mq_send(pkg_mq, &msg, sizeof(msg)) != RT_EOK) - { - rt_enter_critical(); - send_failed++; - rt_exit_critical(); - } - rt_thread_mdelay(rt_tick_from_millisecond(20)); - } - /* 结束消息:region=REGION_END;阻塞重试直到发送成功(见 3.3) */ - msg.region = REGION_END; - while (rt_mq_send(pkg_mq, &msg, sizeof(msg)) != RT_EOK) - rt_thread_mdelay(rt_tick_from_millisecond(10)); -} - -/* 分拣线程:永久等待接收,收到 2 条结束消息后统计并退出 */ -static void sorter_entry(void *param) -{ - while (1) - { - rt_err_t res = rt_mq_recv(pkg_mq, &msg, sizeof(msg), RT_WAITING_FOREVER); - if (msg.region == REGION_END) - { - if (++end_received >= 2) break; /* 两个发件线程的结束消息都收到才退出 */ - continue; - } - switch (msg.region) { case 1: east++; ... } - total++; total_weight += msg.weight; - rt_thread_mdelay(rt_tick_from_millisecond(40)); /* 模拟分拣耗时,制造队列满 */ - rt_kprintf("package %d -> %s, weight: %d g\n", ...); - } - rt_kprintf("total packages / east,south,north / total weight / send failed\n"); - /* 清理:删除消息队列、复位标志 */ -} -``` - -代码要点: - -- **变长消息**:队列中传递的是 `struct package_msg` 结构体(`sizeof` 可自定义),体现消息队列"不定长消息"的优势——邮箱只能传 4 字节,装不下 id+region+weight 三个字段; -- **队列满不崩溃**:生产速率(每 20ms 一条)快于消费速率(每 40ms 一条),5 条容量很容易填满;`rt_mq_send` 返回非 `RT_EOK` 时在临界区内 `send_failed++`,线程继续运行不崩溃; -- **结束消息必须阻塞重试**:若结束消息也用普通非阻塞 `rt_mq_send`,在队列满时会发送失败,分拣线程将永远等不到 2 条结束消息而卡死——因此用 `while (rt_mq_send(...) != RT_EOK) mdelay(10ms)` 保证最终发送成功(详见 3.3); -- **重复执行设计**:分拣线程退出后删除消息队列并复位标志。 - -#### 3.3 实验结果与现象 - -![分拣输出与统计结果](figures/image-20260820141456076-17872071141291.png) - -- 分拣线程依次打印 `package 1001 -> east, weight: 1200 g` 等 10 条快递信息; -- 结束统计:`total packages: 10`、`east/south/north` 各区域数量、`total weight` 总重量; -- `send failed: N`(N>0):生产快于消费,消息队列被填满时部分 `rt_mq_send` 失败并计数,验证了「队列满不崩溃」的要求; -- 若把结束消息改为**非阻塞发送**,在队列满时结束消息丢失,分拣线程等不到 2 条结束消息而**永久阻塞**——这是实验中的一个关键坑点。 - -![生产消费速率与结束消息对比](figures/image-20260820135936211-17872071184313.png) - -> 实验调试结论:消息队列是"生产者-消费者"模型的天然载体;当生产速率 > 消费速率时队列必然变满,必须对"发送失败"做处理(计数或阻塞重试),尤其要保证**控制类消息(结束消息)不能因队列满而丢失**。 - -#### 3.4 实战要点总结 - -- 消息队列承载**结构体等不定长数据**,比邮箱(固定 4 字节)更灵活; -- 队列满 ≠ 崩溃:`rt_mq_send` 返回错误码即可优雅处理; -- **控制消息与数据消息分离对待**:数据可丢弃计数(`send_failed++`),控制消息(结束信号)必须可靠送达(阻塞重试); -- 分拣线程优先级略高于发送线程(`RT_THREAD_PRIORITY_MAX-2` vs `-3`),保证队列不堆积时能及时消费。 - -### 4. 五大 IPC 机制综合对比 - -| 机制 | 分类 | 数据传递 | 核心特性 | 典型场景 | -| --- | --- | --- | --- | --- | -| 信号量 | 同步 | 不传数据 | 二值/计数,take 减 / release 加 | 资源计数(停车场)、事件通知 | -| 互斥量 | 同步 | 不传数据 | 所有权、递归访问、防优先级反转 | 临界区互斥(售票)、共享数据保护 | -| 事件集 | 同步 | 不传数据 | 32 位事件位,逻辑与/或,无排队性 | 多条件聚合同步(启动条件检查) | -| 消息邮箱 | 通信 | 固定 4 字节 | 开销低、发送非阻塞可入中断 | 按键/传感器数据 → 显示线程 | -| 消息队列 | 通信 | 不定长 | 链表管理、支持紧急插队 | 变长消息、串口不定长数据、快递分拣 | - -**选择建议**: - -- 只需要**互斥访问共享资源**(同一时刻一人用)→ 互斥量; -- 需要**管理多个同类资源 / 做事件通知** → 计数信号量 / 二值信号量; -- 需要**同时等待多个条件(且/或组合)** → 事件集; -- 需要**传递固定 4 字节的轻量数据**(如指针、状态值)→ 消息邮箱; -- 需要**传递不定长数据**或**消息插队优先处理** → 消息队列。 - -### 5. 四道实战练习总览(多练习收尾) - -> 四道练习分别对应五大 IPC 机制中的四种(信号量/互斥量/事件集/消息队列),组成一条"从资源管理到消息传递"的完整实践链。练习一、二解决**共享资源的并发访问**(同步),练习三解决**多条件聚合等待**(同步),练习四解决**线程间批量传递数据**(通信)。 - -#### 5.1 MSH 命令与重复执行设计 - -每个练习都通过 `MSH_CMD_EXPORT` 导出独立命令: - -| 练习 | MSH 命令 | 说明 | -| --- | --- | --- | -| 练习一 | `parking_demo` | 停车场车位管理(计数信号量) | -| 练习二 | `ticket_demo` | 多窗口售票系统(互斥量) | -| 练习三 | `startup_demo` | 系统启动条件检查(事件集) | -| 练习四 | `package_demo` | 快递分拣中心(消息队列) | - -**四道练习统一的工程化设计**(保证重复执行不异常): - -1. **运行标志防重入**:每个 demo 用 `volatile rt_uint8_t xxx_running` 记录运行状态,命令入口先检查,若已在运行则打印提示并返回 `-RT_EBUSY`,拒绝重复启动; -2. **全部资源检查**:IPC 对象(信号量/互斥量/事件集/队列)创建后判断返回值是否为 `RT_NULL`,失败打印错误并返回错误码; -3. **线程创建检查**:`rt_thread_create` 返回 `RT_NULL` 时打印失败信息,并做资源回滚(删除已创建的 IPC 对象); -4. **任务结束主动清理**:所有子线程结束后删除 IPC 对象并复位运行标志,使命令可再次执行,不残留同名线程或对象。 - -#### 5.2 四练习一览表 - -| 练习 | 场景 | 机制 | 核心 API | 关键设计点 | 实验验证点 | -| --- | --- | --- | --- | --- | --- | -| 一 | 停车场 3 车位 5 车 | 计数信号量 | `rt_sem_create/take/release` | 初始值=3 限流、2s 超时重试、临界区保护计数 | 任意时刻进场 ≤ 3 | -| 二 | 两窗口售 20 张票 | 互斥量 | `rt_mutex_create/take/release` | 锁住"检查-记录-减 1"完整临界区 | 票号不重复、余票不为负 | -| 三 | 三模块启动检查 | 事件集 | `rt_event_create/send/recv` | AND+CLEAR 聚合等待、超时诊断镜像 | 全部就绪才启动、超时提示缺模块 | -| 四 | 快递分拣 | 消息队列 | `rt_mq_create/send/recv` | 队列满计数不崩溃、结束消息阻塞重试 | 统计准确、满队列失败计数、不卡死 | - -**四练习的工程要点沉淀**: - -- 计数信号量的初始值就是"资源上限";互斥量保护的是**完整临界区**而非单条语句; -- 事件集用 **AND+CLEAR** 表达"全部就绪且消费一次",配合镜像状态变量做超时诊断; -- 消息队列的生产速率应受控(`mdelay` 节流),控制类消息必须可靠送达(阻塞重试),数据类消息可计数丢弃。 - -> 课程总结:IPC 的五大机制各有适用场景,实际开发中应结合需求选择;学习中可多查阅 RT-Thread 文档中心 [线程间同步](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1),其中包含各种内核与组件非常详细的介绍。 diff --git "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index ca606a13754597b2a8df231fc54b52d387376fbc..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\206\212\350\207\252\345\212\233/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,851 +0,0 @@ -# RT-Thread 夏令营 Day2 学习记录 - -## 学习目标 - -**Day2 学习目标**: - -1. 了解实时操作系统(RTOS)的基本概念,理解其与裸机开发的本质区别。 -2. 掌握 RT-Thread 系统启动流程的完整步骤,理解每个阶段的功能作用。 -3. 理解线程、调度器等 RTOS 核心概念。 - -## 一、实时操作系统与构建工具 - -### 1. 实时操作系统 - -**什么是实时操作系统(RTOS)**:实时操作系统是一种能够在**确定的时间**内对外部事件做出响应的操作系统,其核心特征是**实时性**——响应时间具有确定性、可预测性,而非"越快越好"。 - -**裸机开发(前后台系统)的局限**: - -前后台系统由**前台 + 后台**两部分组成:前台是**中断服务程序(ISR)**,接收异步中断并通知后台;后台是**主循环大循环**,循环调用各功能处理函数完成相应操作。主循环代码可视为后台行为,中断服务程序为前台。 - -```c -void main(void) -{ - init(); - while (1) /* 后台:主循环大循环 */ - { - ADC_Read(); - SPI_Read(); - USB_Packet(); - LCD_Update(); - Audio_Decode(); - File_Write(); - } -} - -void USB_ISR(void) /* 前台:中断服务程序 */ -{ - Clear interrupt; - Read packet; -} -``` - -裸机模式的四大缺陷: - -| 缺陷 | 说明 | -| --- | --- | -| 并发性 | 程序并发工作效率低,任务只能串行排队执行 | -| 实时性 | 功能复杂时实时性无法保证,隐性的延时会影响整个后台系统 | -| 可维护性 | 代码/维护困难,改变部分功能可能影响整个系统,牵一发而动全身 | -| 可重用性 | 模块化程度低,软件可重用性差,总是重复造轮子 | - -**RTOS 多任务精髓对比**:芯片视角下,多个任务(线程)按调度规则**交替切换执行**,同一时刻只有一个任务占用 CPU;人眼视角下,多个任务看起来"同时"进行、像在并行。中断(ISR)处理在裸机与 RTOS 中相同。 - -**RTOS 的核心优势**: - -- **多任务并发**:多个任务看似同时运行,由调度器分配 CPU。 -- **任务调度**:按优先级抢占式调度 + 时间片轮转,保证高优先级任务及时响应。 -- **实时响应**:任务可被及时调度,满足确定性响应要求。 -- **资源管理**:提供信号量、互斥量、消息队列、事件等机制,实现任务间同步与通信。 - -**任务(线程)的概念**:RTOS 中**任务/线程是调度的基本单位**,任务的实体是线程。每个线程拥有独立的栈空间与入口函数,由内核统一管理调度。 - -**RT-Thread 简介**:国产开源实时操作系统,提供三条产品线: - -- **Nano**:极简版,仅内核,适合资源受限场景。 -- **标准版**:完整内核 + 组件(文件系统、网络、设备驱动框架等)。 -- **Smart**:混合微内核,支持带 MMU 的处理器,可运行 Linux 应用。 - -### 2. 构建工具简介 - -SCons学习链接:[SCons](https://www.rt-thread.org/document/site/#/development-tools/build-config-system/SCons) - -构建工具的核心作用:**按照规则将源代码编译成可执行程序**,是嵌入式开发中连接源码与目标固件的关键环节。 - -- **Make / Makefile**:经典构建工具,通过 Makefile 描述编译规则与依赖关系,自动完成编译、链接等步骤。 -- **CMake**:跨平台构建系统,通过 CMakeLists.txt 描述工程,可生成 Makefile 或 IDE 工程文件。 -- **SCons**:基于 Python 的开源构建系统,使用 Python 脚本描述构建规则,配置更简洁灵活。 -- **RT-Thread 的演进**:RT-Thread 早期使用 Make/Makefile 构建,后转向 SCons 构建系统,简化工程配置与跨平台支持。 -- **IDE 与构建工具的关系**:IDE 提供图形化操作界面,底层实际调用构建工具(如 Make/SCons)完成编译构建。 - -#### 2.1 为什么 RT-Thread 用 SCons - -- **Make 的痛点**:Makefile 语法比较混乱,不利于初学者学习;在 Windows 平台上使用 Make 不方便,需要安装 Cygwin 环境。 -- **RT-Thread 的选择**:从 RT-Thread 0.3.0 开始引入 SCons 构建系统,唯一目的就是**让大家从复杂的 Makefile 配置、IDE 配置中脱离出来,把精力集中在 RT-Thread 功能开发上**。 - -#### 2.2 SCons 是什么 - -- SCons 是一套由 **Python 语言编写**的开源构建系统,功能类似 GNU Make。 -- 它不使用 Makefile,而是用 **SConstruct 和 SConscript 文件**来组织构建,这些文件本身就是 **Python 脚本**,可以使用标准 Python 语法编写。 -- 因此在构建脚本中可以调用 Python 标准库做各种复杂处理,这是 Makefile 做不到的。 - -#### 2.3 核心文件(必知) - -一个 RT-Thread BSP 工程目录下,构建系统由这几个文件组成: - -| 文件 | 作用 | -| --- | --- | -| `SConstruct` | SCons 的**入口脚本**,一个 BSP 只有一个,初始化构建所需环境 | -| `SConscript` | 分散在各级源码子目录的**构建子脚本**,一个 BSP 有多个,是组织源码的主力军 | -| `rtconfig.py` | **编译器配置文件**,存放工具链、编译器路径、编译参数等 | -| `Kconfig` | menuconfig 配置入口,配合 Env 工具生成配置界面,最终把配置宏写入 `rtconfig.h` | - -> 新手理解:`SConstruct` 是"总指挥",`SConscript` 是"各小组长",`rtconfig.py` 是"工具清单",`Kconfig` 是"配置菜单"。 - -#### 2.4 常用命令速查 - -在 BSP 目录打开命令行(Env 工具)执行: - -| 命令 | 作用 | -| --- | --- | -| `scons` | 编译工程(默认使用 ARM GCC 编译器) | -| `scons -j4` | 多线程编译,加快速度(双核机器用 `-j4`) | -| `scons -c` | 清除编译生成的临时文件和目标文件 | -| `scons --target=mdk5` | 生成 MDK 工程文件 `project.uvprojx` | -| `scons --target=iar` | 生成 IAR 工程文件 `project.eww` | -| `scons --target=cmake` / `--target=makefile` | 生成 CMake / Makefile 脚本 | -| `scons --dist` | 搭建项目框架,生成可独立拷贝的 dist 目录 | -| `scons -s` | 不打印具体内部命令 | - -> 注意:修改了 `rtconfig.h` 打开/关闭组件后,需要重新执行 `scons --target=xxx` 生成对应工程,再在 MDK/IAR 中编译下载。 - -#### 2.5 SConscript 常用函数 - -```python -from building import * # 导入 RT-Thread 自定义构建函数(几乎每个 SConscript 都要写) - -cwd = GetCurrentDir() # 获取当前脚本所在路径 -src = Glob('*.c') # 匹配当前目录下所有 .c 文件 -src = Split('''shell.c -msh.c''') # 把字符串拆成文件列表 - -if GetDepend('RT_USING_FINSH'): # 判断宏是否被定义(是否启用某组件) - src += ['cmd.c'] - -group = DefineGroup('Finsh', src, depend = ['RT_USING_FINSH'], CPPPATH = [cwd]) - # 定义一个构建组(对应 MDK/IAR 中的分组) -Return('group') # 把构建对象返回给上级脚本 -``` - -- `DefineGroup` 的 `depend` 参数:依赖的宏在 `rtconfig.h` 中定义了,该组才会参与编译。 -- `CPPPATH`:头文件搜索路径,把当前目录加入后,其他源码就能引用这里的头文件。 - -#### 2.6 常见疑问 - -- **SCons 和 IDE 什么关系?** IDE 通过图形界面操作,底层实际调用 SCons 等构建工具完成编译;IDE 会根据你添加的源码生成类似 SConscript 的脚本。 -- **想换编译器怎么办?** 在 Env 命令行用 `set RTT_CC=keil`、`set RTT_EXEC_PATH=C:/Keilv5` 临时指定;或直接修改 `rtconfig.py` 里的 `CROSS_TOOL` 和 `EXEC_PATH` 永久生效。 -- **编译器路径注意**:不要安装到带中文或空格的路径(如 `C:\Program Files`),否则解析路径会出错;路径中反斜杠 `\` 建议写成 `/` 或加 `r` 前缀。 -- **`scons` 报 Visual Studio 警告?** 提示找不到 VS 编译器,但嵌入式开发与 Windows 本地无关,直接忽略即可。 - -## 二、RT-Thread 启动流程分析 - -RT-Thread 的启动流程从汇编启动文件开始,经过内核核心初始化、线程创建、调度器启动、组件初始化,最终进入用户 `main()` 函数。整体流程如下图所示: - -![image-20260818091412456](figures/image-20260818091412456.png) - -**整体流程一句话总结**:汇编启动文件 → 内核核心初始化(中断/板级/定时器/调度器/信号)→ 创建 main/定时器/空闲线程 → 启动调度器 → 组件初始化 → 进入用户 main()。 - -### 第一阶段:启动文件入口 - -系统复位后的第一个执行入口,负责初始化堆栈、中断向量等基础环境,并根据不同编译工具链进入对应的启动入口。 - -| 步骤 | 内容 | 功能作用 | -| --- | --- | --- | -| 1 | `startup_xx.S`(汇编启动文件) | 系统复位后的第一个执行入口,负责初始化堆栈、中断向量等基础环境 | -| 2 | MDK → `$Sub$$main()` | Keil 工具链的启动入口,在进入 main 前完成系统初始化 | -| 3 | IAR → `__low_level_init()` | IAR 工具链的启动入口,完成底层初始化 | -| 4 | GCC → `entry()` | GCC 工具链的启动入口,进入系统初始化流程 | -| 5 | 三个工具链分支汇入 `rtthread_startup()` | 实现跨编译器兼容,统一进入系统启动主流程 | - -### 第二阶段:内核核心初始化(rtthread_startup 主流程) - -`rtthread_startup()` 是系统启动主入口函数,串起整个初始化流程,依次完成中断、板级硬件、版本显示、定时器、调度器、信号等内核核心初始化。 - -| 步骤 | 内容 | 功能作用 | -| ---- | --------------------------------------------------- | ------------------------------------------------------------ | -| 1 | `rtthread_startup()` | 系统启动主入口函数,串起整个初始化流程 | -| 2 | `rt_hw_interrupt_disable()` | 禁用硬件中断,保证初始化过程不被中断打扰 | -| 3 | `rt_hw_board_init()`→4 | 板级硬件初始化,配置时钟、串口、内存等基础硬件 | -| 4 | `rt_components_board_init()` → board init functions | 调用板级初始化函数列表,完成各外设的板级配置 | -| 5 | `rt_show_version()` | 显示系统版本号,串口输出 RT-Thread 版本横幅 | -| 6 | `rt_system_timer_init()` | 初始化系统定时器,为系统提供时间基准 | -| 7 | `rt_system_scheduler_init()` | 初始化调度器,建立调度器数据结构 | -| 8 | `rt_system_signal_init()`* | 初始化信号处理机制,支持线程间信号通信(如果不使用信号相关内容,可以在menuconfig里面关掉) | - -### 第三阶段:线程创建 - -完成内核核心初始化后,系统开始创建各类系统线程与用户主线程,包括 main 主线程、定时器线程和空闲线程。 - -| 步骤 | 内容 | 功能作用 | -| --- | --- | --- | -| 1 | `rt_application_init()` | 初始化应用程序框架,创建应用入口线程 | -| 2 | `rt_thread_init(main_thread_entry)` | 创建 main 主线程(用户代码入口) | -| 3 | `rt_system_timer_thread_init()` | 初始化系统定时器线程 | -| 4 | `rt_thread_init(rt_thread_timer_entry)` | 创建定时器线程,负责软件定时器管理 | -| 5 | `rt_thread_idle_init()` | 初始化空闲线程 | -| 6 | `rt_thread_init(rt_thread_idle_entry)` | 创建 idle 空闲线程(优先级最低,回收资源) | - -### 第四阶段:启动调度器 - -| 步骤 | 内容 | 功能作用 | -| --- | --- | --- | -| 1 | `rt_system_scheduler_start()` | 启动调度器,开始按优先级和时间片调度就绪线程 | -| 2 | 根据调度规则执行不同线程 | 线程创建后不会立即执行,由调度器决定何时运行 | - -### 第五阶段:组件初始化与用户代码 - -调度器启动后,系统继续完成组件初始化,最终进入用户 `main()` 函数执行用户代码。 - -| 步骤 | 内容 | 功能作用 | -| --- | --- | --- | -| 1 | `rt_components_init()` | 组件初始化,按序执行各类初始化函数 | -| 2 | pre-initialization functions | 预初始化函数(最早执行) | -| 3 | device init functions | 设备驱动初始化 | -| 4 | components init functions | 组件初始化(文件系统、网络等) | -| 5 | environment init functions | 环境初始化 | -| 6 | applications init functions | 应用层初始化 | -| 7 | `main_thread_entry()` → `main()` | 用户主函数入口,用户代码从这里开始执行 | - -## 三、线程管理 - -### 1. 线程控制块(TCB) - -#### 1.1 什么是线程控制块 - -线程控制块(Thread Control Block, TCB)是 RT-Thread 操作系统中用于管理线程的一个数据结构。它存放线程的相关信息,如优先级、线程名称、线程状态等,也包含线程与线程之间连接用的链表结构、线程等待事件集合等。 - -线程控制块由结构体 `struct rt_thread` 定义并形成**线程内核对象**,再链接到**内核对象容器**中进行管理。 - -![image-20260818184335534](figures/image-20260818184335534.png) - -每个线程对象包含三部分: - -- 线程控制块(TCB):管理线程的核心数据结构 -- 线程栈(Stack):保存线程局部变量和调用栈 -- 入口函数(Entry):线程启动后执行的函数 - -#### 1.2 线程控制块继承关系 - -`struct rt_thread` 继承自基类 `rt_object`,在基类对象基础上进行扩展,增加了线程状态、优先级等属性。 - -![image-20260818184435201](figures/image-20260818184435201.png) - -**基类 rt_object 成员**: - -- `name`:对象名称 -- `type`:对象类型 -- `flag`:对象标志 -- `list`:对象链表节点 - -**rt_object 派生类**:`rt_thread`、`rt_mempool`、`rt_ipc_object`、`rt_timer`、`rt_device` - -**rt_ipc_object(IPC 对象基类)**:成员 `suspend_thread`(挂起线程链表),派生 `rt_sem`(信号量)、`rt_mutex`(互斥量)、`rt_event`(事件)、`rt_mb`(邮箱)、`rt_mq`(消息队列) - -#### 1.3 线程控制块结构体定义 - -```c -struct rt_thread -{ - struct rt_object parent; /* 继承自 rt_object 基类 */ - - /* 栈指针与入口指针 */ - void *sp; /* 栈指针 */ - void *entry; /* 入口函数指针 */ - void *parameter; /* 线程入口函数参数 */ - void *stack_addr; /* 栈地址指针 */ - rt_uint32_t stack_size; /* 栈大小 */ - - /* 错误代码与状态 */ - rt_err_t error; /* 线程错误代码 */ - rt_uint8_t stat; /* 线程状态 */ - - /* 优先级 */ - rt_uint8_t current_priority; /* 当前优先级 */ - rt_uint8_t init_priority; /* 初始优先级 */ - rt_uint32_t number_mask; /* 优先级位图掩码 */ - - /* 时间片 */ - rt_ubase_t init_tick; /* 线程初始化计数值(时间片) */ - rt_ubase_t remaining_tick; /* 线程剩余计数值 */ - - struct rt_timer thread_timer; /* 内置线程定时器 */ - - void (*cleanup)(struct rt_thread *tid); /* 线程退出清除函数 */ - rt_uint32_t user_data; /* 用户数据 */ -}; -``` - -#### 1.4 关键字段功能说明 - -| 字段 | 功能作用 | -| --- | --- | -| `parent` | 继承 rt_object 基类,使线程成为内核对象,可被对象容器管理 | -| `sp` | 栈指针,保存线程当前运行上下文(寄存器状态) | -| `entry` | 入口函数指针,线程启动后执行的函数 | -| `parameter` | 传给入口函数的参数 | -| `stack_addr` / `stack_size` | 线程栈的地址和大小,用于保存线程局部变量和调用栈 | -| `error` | 线程错误代码 | -| `stat` | 线程状态(就绪/运行/挂起/关闭等) | -| `current_priority` / `init_priority` | 当前优先级 / 初始优先级(支持优先级继承) | -| `number_mask` | 优先级位图掩码,用于快速调度 | -| `init_tick` / `remaining_tick` | 时间片初始值 / 剩余值,用于时间片轮转调度 | -| `thread_timer` | 内置线程定时器,用于线程延时、超时等 | -| `cleanup` | 线程退出时的清除函数 | -| `user_data` | 用户自定义数据 | - -### 2. 线程属性 - -#### 2.1 线程属性-线程栈 - -![线程栈](figures/thread_stack.png) - -每个线程都拥有**独立的栈空间**,用于保存线程的上下文(寄存器状态)和局部变量。线程栈是线程运行时的私有存储区域,互不干扰。 - -##### 2.1.1 线程栈的作用 - -- **保存上下文**:发生上下文切换时,当前线程的寄存器状态会被压入其线程栈保存,待再次被调度时恢复。 -- **存放局部变量**:线程内定义的局部变量从线程栈中申请空间,函数调用栈也建立在线程栈上。 - -##### 2.1.2 栈的增长方向 - -Cortex-M 处理器的栈**自顶向下增长**(从高地址向低地址方向),即栈指针 SP 初始指向栈顶(高地址),压栈时 SP 递减。 - -##### 2.1.3 硬件自动压栈顺序 - -Cortex-M 在进入中断/异常时由硬件自动压栈,寄存器入栈顺序如下: - -``` -PSR → PC → LR → R12 → R3 → R2 → R1 → R0 → R11 ... R4 -``` - -其中 `PSR`(程序状态寄存器)、`PC`(程序计数器)、`LR`(链接寄存器)、`R12`、`R3~R0` 由硬件自动压栈,其余通用寄存器 `R11...R4` 由软件(上下文切换代码)负责保存。 - -#### 2.2 线程属性-优先级 - -![线程属性-优先级](figures/thread_priority.png) - -线程的优先级表示线程被调度的优先程度。每个线程都具有优先级,越重要的线程,赋予的优先级就越高,线程被调度的可能性才会越大。 - -- **优先级范围**:RT-Thread 最大支持 256 个线程优先级(0~255),数值越小优先级越高,0 为最高优先级。 -- **可配置**:在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置。 -- **空闲线程**:最低优先级默认分配给空闲线程使用,用户一般不使用。 -- **抢占式调度**:在系统中,如果有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程会优先抢占处理器运行。 - -##### 2.2.1 调度机制剖析:调度器如何完成抢占 - -抢占式调度的核心思想:如果有更高优先级的线程进入就绪态,当前低优先级线程会被**立即暂停**,CPU 切换给高优先级线程(优先级数值越小,优先级越高)。调度器正是通过线程控制块(TCB)中的字段来参与抢占判断: - -| TCB 字段 | 在抢占判断中的作用 | -| --- | --- | -| `stat` | 判断线程是否可被调度:`RT_THREAD_READY` 可调度、`RT_THREAD_RUNNING` 正在运行、`RT_THREAD_SUSPEND` 不能运行 | -| `current_priority` | 调度器据此判断谁更优先:数值越小优先级越高,越优先运行 | -| `sp` | 切换线程时保存 / 恢复现场 | - -- **切换现场的过程**:切走线程时保存其 `sp`(把当前运行现场压栈并记录栈指针位置);切入线程时恢复其 `sp`(从栈中弹出上下文,CPU 从原断点接着执行)。 -- 举例:当前运行线程 B,若线程 A 突然变为就绪态且优先级更高,调度器判断 `current_priority` 后立即切换到线程 A,实现抢占。 - -#### 2.3 线程属性-时间片 - -![thread_timeslice](figures/thread_timeslice.png) - -- **适用场景**:时间片仅对**优先级相同**的就绪态线程有效,用于时间片轮转调度。 -- **作用**:约束线程单次运行时长,单位为系统节拍(OS Tick)。 -- **示例**:A 线程时间片 10、B 线程时间片 5,则 A 每次运行 10 节拍、B 每次运行 5 节拍,两者交替执行。 - -#### 2.4 线程属性-入口函数 - -![线程属性-入口函数](figures/thread_entry.png) - -- **作用**:线程所实现的功能由入口函数实现。 -- **函数格式**:`void (*entry)(void* parameter)` -- **关键要求**:入口函数内必须有**让出 CPU** 的动作(如 `rt_thread_mdelay()`),否则会独占 CPU,导致其他线程无法运行。 - -```c -/* 线程1的入口函数 */ -static void thread1_entry(void *parameter) -{ - rt_uint32_t count = 0; - while (1) - { - /* 线程1采用低优先级运行,一直打印计数值 */ - rt_kprintf("thread1 count:%d\n", count++); - rt_thread_mdelay(500); /* 让出 CPU,延时 500ms */ - } -} -``` - -#### 2.5 线程属性-线程延时 - -![thread_delay](figures/thread_delay.png) - -在**抢占式调度**系统中,线程不运行时应当**主动让出 CPU**,以保证其他线程能够获得运行机会。 - -- **延时 / 睡眠**:让出 CPU 的常用方法,线程调用延时函数后进入挂起状态,由调度器调度其他就绪线程运行。 -- `rt_thread_delay()`:以**系统 Tick** 为单位让当前线程睡眠。 -- `rt_thread_mdelay()`:以**毫秒(ms)** 为单位让当前线程睡眠,使用更直观。 - -### 3. 线程状态分类 - -RT-Thread 中线程共有 **5 种状态**,由线程控制块中的 `stat` 字段标识: - -| 状态 | 宏定义 | 含义 | -| --- | --- | --- | -| 初始状态 | `RT_THREAD_INIT` | 线程刚创建(初始化)完成,尚未启动,未加入就绪队列 | -| 就绪状态 | `RT_THREAD_READY` | 线程已具备运行条件,等待调度器分配 CPU | -| 运行状态 | `RT_THREAD_RUNNING` | 线程正在占用 CPU 执行 | -| 挂起状态 | `RT_THREAD_SUSPEND` | 线程因等待资源/事件/延时等被阻塞,暂停执行 | -| 关闭状态 | `RT_THREAD_CLOSE` | 线程已结束/被删除,不再参与调度(终态) | - -### 4. 线程状态转化 - -![线程状态转化](figures/thread_state_transition.png) - -线程在生命周期中会在上述状态之间转换,状态转换关系如下图所示(结合"线程状态转化"PPT): - -``` -初始状态 → 就绪状态 → 运行状态 ↔ 挂起状态 → 关闭状态 -``` - -#### 4.1 状态转换关系 - -| 起始状态 | 目标状态 | 触发函数 / 条件 | -| --- | --- | --- | -| 初始状态 | 就绪状态 | `rt_thread_startup()` | -| 就绪状态 | 运行状态 | 被调度器选中执行(调度器调度) | -| 运行状态 | 挂起状态 | `rt_thread_suspend()` 及①组阻塞函数 | -| 挂起状态 | 就绪状态 | ②组唤醒函数 | -| 运行状态 | 关闭状态 | `rt_thread_exit()` | -| 挂起状态 | 关闭状态 | `rt_thread_delete()` / `rt_thread_detach()` | - -#### 4.2 ①组阻塞函数(运行 → 挂起) - -运行中的线程调用以下函数会主动让出 CPU 或阻塞等待资源,从而转入挂起状态: - -```c -rt_thread_delay(); /* 延时等待 */ -rt_sem_take(); /* 获取信号量(不可用则阻塞) */ -rt_mutex_take(); /* 获取互斥锁(被占用则阻塞) */ -rt_mp_alloc(); /* 分配内存池块(无可用块则阻塞) */ -rt_event_recv(); /* 接收事件标志(无匹配事件则阻塞) */ -rt_mb_recv(); /* 接收邮箱消息(为空则阻塞) */ -rt_mq_recv(); /* 接收消息队列(为空则阻塞) */ -``` - -#### 4.3 ②组唤醒函数(挂起 → 就绪) - -由其他线程或中断调用,用于唤醒挂起中的线程,使其回到就绪状态: - -```c -rt_thread_resume(); /* 显式恢复线程 */ -rt_sem_release(); /* 释放信号量(唤醒等待者) */ -rt_mutex_release(); /* 释放互斥锁(唤醒等待者) */ -rt_mp_release(); /* 释放内存池块(唤醒等待分配者) */ -rt_event_send(); /* 发送事件标志(唤醒等待者) */ -rt_mb_send(); /* 发送邮箱消息(唤醒等待接收者) */ -rt_mq_send(); /* 发送消息队列(唤醒等待接收者) */ -``` - -#### 4.4 状态转化要点 - -- **挂起 → 运行需经就绪态**:挂起线程被唤醒后先进入就绪状态,再由调度器调度进入运行状态,不能直接由挂起态进入运行态。 -- **关闭状态为终态**:线程一旦进入关闭状态即不可逆,不再参与调度,其资源由系统回收。 -- 就绪态与运行态之间的切换由**调度器**完成(按优先级、时间片轮转等策略)。 - -### 5. 系统线程 - -#### 5.1 主线程 - -![系统线程-主线程](figures/main_thread.png) - -- 系统启动时自动创建,入口为 `main_thread_entry()`,用户代码从 `main()` 开始执行。 -- 启动流程:`$Sub$$main()` → `rtthread_startup()` → `rt_application_init()` → `main_thread_entry()` → `main()` -- 用户可在 `main()` 中添加应用初始化代码。 - -`main()` 本质上是主线程的入口函数,经 `main_thread_entry()` 被调度器调度运行。在 `main()` 中打印字符串,可通过串口观察输出: - -```c -int main(void) -{ - rt_kprintf("hello world!\n"); /* 串口输出 */ - return 0; -} -``` - -#### 5.2 空闲线程 - -![系统线程-空闲线程](figures/idle_thread.png) - -- 系统创建的最低优先级线程,状态永远为就绪态,无其他就绪线程时被调度。 -- 通常为死循环,且永不被挂起。 -- 特殊用途: - 1. **自动回收线程资源**:运行完毕线程执行 `rt_thread_exit()` → 从就绪队列删除 → 状态改为关闭 → 挂入僵尸队列 `rt_thread_defunct` → 由空闲线程回收资源。 - 2. **提供钩子函数接口**:适合功耗管理、看门狗喂狗等任务。 - -### 6. 线程创建与初始化 - -#### 6.1 静态初始化线程 rt_thread_init - -![线程初始化](figures/thread_init.png) - -- 静态线程初始化使用 `rt_thread_init()`,线程句柄和栈由用户自行分配(通常为全局变量),内核不负责动态内存分配。 - -```c -rt_err_t rt_thread_init(struct rt_thread *thread, - const char *name, - void *(*entry)(void *parameter), - void *parameter, - void *stack_start, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick) -``` - -**参数说明**: - -| 参数 | 含义与要点 | -| --- | --- | -| `thread` | 线程句柄,由用户提供,指向对应的线程控制块内存地址 | -| `name` | 线程名称;最大长度受 `rtconfig.h` 中 `RT_NAME_MAX` 宏限制,多余部分自动截掉 | -| `entry` | 线程入口函数,线程启动后执行的函数 | -| `parameter` | 传给线程入口函数的参数 | -| `stack_start` | 线程栈起始地址;多数系统需做地址对齐(如 ARM 需 4 字节对齐) | -| `stack_size` | 线程栈大小,单位字节 | -| `priority` | 线程优先级,范围由 `RT_THREAD_PRIORITY_MAX` 宏决定;数值越小优先级越高,0 为最高 | -| `tick` | 时间片大小,单位是系统时钟节拍(OS Tick);存在同优先级线程时,指定一次调度最大运行时长 | - -**返回值**:`RT_EOK` 线程创建成功;`-RT_ERROR` 线程创建失败。 - -- **要点**:栈首地址需按架构对齐(如 ARM 需 4 字节对齐)。 - -#### 6.2 动态创建线程 rt_thread_create - -![线程创建](figures/thread_create.png) - -线程要成为可执行对象,**必须由操作系统内核创建**。动态创建线程使用 `rt_thread_create()`: - -- **内存分配**:从**动态堆内存**中为线程分配句柄(TCB)和栈空间,无需用户预先定义。 -- **栈对齐**:分配的栈空间按 `rtconfig.h` 中配置的 `RT_ALIGN_SIZE` 进行对齐。 -- **创建参数**:需指定线程名称、入口函数、入口参数、栈大小等关键信息。 - -```c -rt_thread_t rt_thread_create(const char *name, - void (*entry)(void *parameter), - void *parameter, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -#### 6.3 线程初始化完整示例 - -![线程初始化示例](figures/thread_init_example.png) - -- **资源提供**:线程栈和句柄由用户提供,并需确保对齐(`ALIGN(RT_ALIGN_SIZE)`)。 -- **完整流程**:准备资源 → `rt_thread_init()` 初始化 → `rt_thread_startup()` 启动线程。 -- **初始化参数**:需指定线程名称、入口函数、起始地址(栈首地址)和栈大小。 -- **示例**:自定义一个优先级为 20、时间片为 10 的线程。 - -```c -/* 定义线程栈与线程控制块 */ -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t thread_stack[1024]; -static struct rt_thread thread; - -/* 线程入口函数 */ -static void thread_entry(void *parameter) -{ - while (1) - { - rt_kprintf("thread running\n"); - rt_thread_mdelay(1000); /* 让出 CPU */ - } -} - -/* 初始化并启动线程:优先级 20,时间片 10 */ -rt_thread_init(&thread, "thread", thread_entry, RT_NULL, - thread_stack, sizeof(thread_stack), 20, 10); -rt_thread_startup(&thread); -``` - -#### 6.4 静态线程 vs 动态线程对比 - -| 对比项 | 静态线程(`rt_thread_init`) | 动态线程(`rt_thread_create`) | -| --- | --- | --- | -| 分配位置 | 全局/静态区 | 堆区 | -| 分配时机 | 编译/链接时确定 | 运行时申请 | -| 是否依赖堆 | 不依赖 | 通常依赖 | -| 生命周期 | 通常与系统一样长、常驻 | 可短可长、按需创建销毁 | -| 释放机制 | 固定占用,通常无需手动释放 | 线程退出/`delete`/`detach` 后释放,需关注 | -| 内存泄漏风险 | 一般不会 | 可能 | -| 内存碎片风险 | 基本不会 | 可能 | - -**高可靠场景强制使用静态线程的原因**: - -1. 内存在**编译期即可确定**,内存可控、行为确定。 -2. **不依赖运行时堆**,开机前就把内存准备好,不会因堆不足导致分配失败。 -3. 内存位置固定,长时间运行**不产生碎片**。 -4. 不存在「用完释放」概念,内存固定占用,**不会因泄漏**导致系统越跑越危险。 -5. **启动早期即可初始化**,只要静态数组存在,线程对象就存在。 -6. 无需运行时分配内存,**行为确定、实时性可预测**。 - -> **一句话概括**:静态线程把内存问题从「运行时不确定」变成「编译期可检查」。 - -### 7. 线程启动与自动初始化 - -#### 7.1 如何真正启动线程运行(而非纯粹调用) - -线程创建后**并不会自动运行**,它只是一个静态对象。要让线程真正跑起来,需要三步: - -``` -创建(init/create) → 启动(startup) → 调度器调度执行 -``` - -- **创建**:`rt_thread_init()`(静态)或 `rt_thread_create()`(动态)完成线程对象和栈的初始化,此时线程处于初始态(`RT_THREAD_INIT`),未加入就绪队列。 -- **启动**:`rt_thread_startup()` 将线程从初始态转入就绪态(`RT_THREAD_READY`),挂入就绪队列。 -- **调度**:调度器(`rt_system_scheduler_start()` 启动后)按优先级和时间片从就绪队列中选中该线程,分配 CPU 执行入口函数。 - -> **常见误区**:直接调用线程入口函数(如 `thread1_entry(param);`)**并不是启动线程**,那只是普通函数调用——不会建立独立线程栈上下文、不经过调度器,无法体现多任务并发特性。 - -#### 7.2 rt_thread_startup() 启动线程 - -```c -rt_err_t rt_thread_startup(rt_thread_t thread); -``` - -- **作用**:将线程状态从 `RT_THREAD_INIT` 变为 `RT_THREAD_READY`,加入就绪队列,等待调度器调度。 -- **适用对象**:静态(`rt_thread_init`)和动态(`rt_thread_create`)创建的线程,都需要调用它才能真正运行。 -- **内部动作**:设置好线程栈上下文(sp 指向栈起始位置),调度器切换时从栈中恢复寄存器,跳转到入口函数执行。 - -#### 7.3 INIT_APP_EXPORT() 自动初始化机制 - -RT-Thread 提供一套「自动初始化」机制:**不需要手动调用**,系统启动时按优先级自动执行初始化函数。 - -```c -INIT_APP_EXPORT(fn); /* 声明 fn 为应用层自动初始化函数 */ -``` - -**自动初始化宏层级**(按执行先后顺序): - -| 宏 | 执行阶段 | -| --- | --- | -| `INIT_BOARD_EXPORT(fn)` | 板级初始化(最早) | -| `INIT_PREV_EXPORT(fn)` | 预初始化 | -| `INIT_DEVICE_EXPORT(fn)` | 设备驱动初始化 | -| `INIT_COMPONENT_EXPORT(fn)` | 组件初始化 | -| `INIT_ENV_EXPORT(fn)` | 环境初始化 | -| `INIT_APP_EXPORT(fn)` | 应用层初始化(最晚) | - -**原理**:这些宏把函数指针放入指定的代码段(section),系统启动时 `rt_components_init()` 按段顺序遍历并逐个调用,实现「声明即自动执行」。 - -##### 7.3.1 底层原理深入剖析 - -**完整链路**:宏展开生成函数指针 → 放入特殊 section → 链接器收集成初始化函数表 → 启动时 `rt_components_init()` 遍历调用。 - -1. **宏展开:生成函数指针**。`INIT_APP_EXPORT(fn)` 并不是直接调用函数,而是展开为:定义函数指针类型 `typedef int (*init_fn_t)(void);`,生成一个保存 `fn` 函数地址的 const 函数指针变量,并用 `__attribute__((section(".rti_fn.6")))` 把它放入指定段。本质是把函数地址「登记」到一张自动初始化函数表里。 - -2. **INIT_BOARD_EXPORT 与 INIT_APP_EXPORT 的区别**:两者底层原理相同,区别在于 **section 不同**,因此被调用的阶段不同: - - | 宏 | section | 用途 | - | --- | --- | --- | - | `INIT_BOARD_EXPORT()` | `.rti_fn.1.board` | 板级硬件初始化(最早) | - | `INIT_APP_EXPORT()` | `.rti_fn.6` | 应用层初始化(最晚) | - -3. **链接器收集函数指针表**:编译器只是把函数指针放进特殊 section,真正形成「表」的是链接脚本。链接脚本的关键机制: - - ```text - .rti_fn : { - . = ALIGN(4); - __rt_init_rti_start = .; - KEEP(*(SORT(.rti_fn.*))) /* 收集所有 .rti_fn.x 段 */ - __rt_init_rti_end = .; - } > FLASH - ``` - - - **KEEP**:防止初始化函数表被链接器优化删除; - - **SORT**:按 section 名称排序,保证初始化顺序; - - **`__rt_init_rti_start` / `__rt_init_rti_end`**:初始化函数表的起止地址符号,供启动时遍历使用。 - -4. **Flash 内存示意图**:链接后各 `.rti_fn.x` 段依次存放对应初始化函数的地址(本质都是函数指针): - - ```text - Flash - .rti_fn.1.board ┌────────────────────────────┐ board_init 函数地址 - .rti_fn.3 ┌────────────────────────────┐ device_init 函数地址 - .rti_fn.4 ┌────────────────────────────┐ component_init 函数地址 - .rti_fn.6 ┌────────────────────────────┐ my_thread_create 函数地址 - ``` - -5. **启动时遍历调用**:RT-Thread 启动时调用 `rt_components_init()`,利用 `__rt_init_rti_start` / `__rt_init_rti_end` 遍历初始化函数表,依次调用表中的函数,实现「声明即自动执行」,最终进入用户 `main()`。 - -#### 7.4 两者的区别 - -| 对比项 | `INIT_APP_EXPORT()` | `rt_thread_startup()` | -| --- | --- | --- | -| 本质 | **初始化函数**的自动注册/自动调用机制 | **线程**的启动函数 | -| 作用对象 | 普通 C 函数(初始化回调) | 线程对象 `rt_thread_t` | -| 触发时机 | 系统启动阶段,由 `rt_components_init()` 自动按序调用 | 代码中手动调用 | -| 是否启动线程 | 否,只把函数挂入初始化段,不产生线程 | 是,把线程投入就绪队列,由调度器调度运行 | -| 执行环境 | 在系统启动流程中同步执行 | 在线程栈上下文中异步调度执行 | -| 典型用途 | 注册模块/驱动/组件的初始化代码 | 启动一个用户线程开始运行 | - -> **总结**:`INIT_APP_EXPORT()` 是「让某个**初始化函数**在系统启动时被自动调用」;`rt_thread_startup()` 是「让某个**线程**进入就绪队列被调度运行」。前者初始化的是代码/模块,后者启动的是线程。 - -## 四、多线程实战编程(Day2 作业) - -> 本大章基于作业 `thread_study.c` 与 README Part 2 提炼整理,完整源码见作业目录。 - -### 1. 任务需求概述 - -基于 STM32F407ZG(LED_R=PF12、LED_B=PF11、KEY=PC5)实现多线程实战,共 3 个工作线程 + 按键交互 + 调试命令: - -| 线程 | 创建方式 | 优先级 | 时间片 | 栈大小 | 职责 | -| --- | --- | --- | --- | --- | --- | -| `th_led1` | 动态创建 | 16 | 5 | 2048 | 循环翻转板载 LED_R,打印计数与 `rt_tick_get()` | -| `th_led2` | 静态初始化 | 16 | 5 | 2048(全局栈) | 循环翻转板载 LED_B,打印计数 | -| `th_monitor` | 动态创建 | 10 | 5 | 2048 | 每 2000ms 读取按键计数、打印线程状态、挂起/恢复 th_led1 | - -其他集成要求: - -- **按键交互**:单次触发挂起 `th_led1`,再次触发恢复; -- **自动加载**:使用 `INIT_APP_EXPORT()` 自动加载测试模块,禁止在 `main.c` 中硬编码调用; -- **调试命令**:使用 `MSH_CMD_EXPORT()` 导出 `thread_dump` 查看命令。 - -### 2. 核心代码解析 - -#### 2.1 动态线程创建(th_led1) - -动态线程由内核从堆中分配 TCB 与栈,返回线程句柄 `rt_thread_t`: - -```c -th_led1 = rt_thread_create("th_led1", th_led1_entry, RT_NULL, 2048, 16, 5); -rt_thread_startup(th_led1); /* 动态线程传句柄(指针)本身,不加 & */ -``` - -#### 2.2 静态线程初始化(th_led2) - -静态线程的 TCB 与栈由用户提供,全局定义并做字节对齐: - -```c -ALIGN(RT_ALIGN_SIZE) /* 全局栈做字节对齐 */ -static rt_uint8_t th_led2_stack[2048]; -static struct rt_thread th_led2; - -rt_thread_init(&th_led2, "th_led2", th_led2_entry, RT_NULL, - th_led2_stack, sizeof(th_led2_stack), 16, 5); -rt_thread_startup(&th_led2); /* 静态线程传 &th_led2 */ -``` - -> **注意传参区别**:动态线程传 `th_led1`(句柄本身即指针),静态线程传 `&th_led2`(取结构体地址)。 - -#### 2.3 监控线程挂起 / 恢复 th_led1 - -监控线程优先级 10(高于工作线程 16),周期 `rt_thread_mdelay(2000)`。读取按键计数后,通过 TCB 的 `stat` 判断 th_led1 状态: - -```c -if ((th_led1->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND) - rt_thread_resume(th_led1); /* 已挂起 → 恢复运行 */ -else - rt_sem_release(led_sem); /* 运行中 → 释放信号量,由 th_led1 自行挂起 */ -``` - -#### 2.4 按键 ISR:快进快出 + tick 消抖 - -- **ISR 必须快进快出**:中断函数里严禁使用 `rt_thread_mdelay`、`rt_kprintf` 或挂起线程等耗时 / 阻塞操作; -- **tick 消抖(20ms)**:记录上次中断触发时刻,与本次相差不足 20ms 视为机械抖动直接丢弃,兼顾防重复触发。 - -```c -static void key_isr_handler(void *args) -{ - rt_tick_t current_tick = rt_tick_get(); - if ((current_tick - last_isr_tick) >= rt_tick_from_millisecond(20)) - { - last_isr_tick = current_tick; - key_event_count++; /* 有效按键计数 +1 */ - } -} -``` - -监控线程侧还会**关中断原子读取**计数(`rt_hw_interrupt_disable()/enable()`),读后清零防止重复处理,再 `rt_thread_mdelay(20)` 二次软件消抖确认按键仍按下。 - -#### 2.5 信号量 led_sem 通知挂起 - -- 创建动态信号量 `led_sem`(初值 0); -- 监控线程按键事件中 `rt_sem_release(led_sem)` 通知 th_led1; -- th_led1 每次循环 `rt_sem_take(led_sem, 0)` 非阻塞尝试获取,拿到即调用 `rt_thread_suspend(th_led1)` 自行挂起: - -```c -if (rt_sem_take(led_sem, 0) == RT_EOK) -{ - rt_kprintf("th_led1 suspend.\n"); - rt_thread_suspend(th_led1); -} -``` - -#### 2.6 ready_delay_ms():保持 READY 态延时 - -- `rt_thread_mdelay()` 会让线程进入 **SUSPEND** 状态,此时 `rt_thread_suspend()` 将无法成功挂起该线程; -- 自定义 `ready_delay_ms()` 通过 `rt_tick_get()` 计时 + `rt_thread_yield()` 让出 CPU,使线程始终保持 **READY/RUNNING** 状态,保证监控线程能顺利挂起 th_led1: - -```c -static void ready_delay_ms(rt_uint32_t ms) -{ - rt_tick_t start = rt_tick_get(); - rt_tick_t ticks = rt_tick_from_millisecond(ms); - while ((rt_tick_get() - start) < ticks) - rt_thread_yield(); /* 让出 CPU 但保持就绪态 */ -} -``` - -#### 2.7 INIT_APP_EXPORT 自动初始化 - -- 模块初始化函数 `thread_study_init()` 负责 GPIO 初始化、信号量创建、三个线程的创建与启动; -- 用 `INIT_APP_EXPORT(thread_study_init)` 注册后,系统启动到应用层初始化阶段自动调用,**无需在 `main.c` 中硬编码调用**: - -```c -static int thread_study_init(void) -{ - gpio_init(); - led_sem = rt_sem_create("led_sem", 0, RT_IPC_FLAG_PRIO); - /* 创建并启动 th_led1 / th_led2 / th_monitor ... */ - return 0; -} -INIT_APP_EXPORT(thread_study_init); -``` - -#### 2.8 MSH_CMD_EXPORT 导出调试命令 - -```c -void thread_dump(void) -{ - print_all_status(); /* 打印各线程名称/状态/优先级/剩余栈 */ -} -MSH_CMD_EXPORT(thread_dump, dump thread study status); -``` - -MSH 命令行中可直接输入 `thread_dump` 查看线程状态,便于运行调试。 - -### 3. 实验结果 - -先执行监控线程,再依次执行工作线程 1、2,并打印线程信息: - -![串口运行截图](figures/image-20260819150759771.png) - -实物图: - -![线程截图1](figures/线程截图1.jpg) - -![线程截图2](figures/线程截图2.jpg) - -串口打印消息截图: - -![串口打印消息截图](figures/image-20260819150540298.png) diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817191820776.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817191820776.png" deleted file mode 100644 index 5d70a07a461d5c998efa78f4a254ebc4450d4c06..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817191820776.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817192501362.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817192501362.png" deleted file mode 100644 index 4ef3d4c38203902182629d99560024982a7a5c0d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817192501362.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817193731528.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817193731528.png" deleted file mode 100644 index 511078f76a817ce099c5436ab0513430e3bf9f62..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817193731528.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817194935176.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817194935176.png" deleted file mode 100644 index 675af572591f862319f4055c466ca2a81014bf4e..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817194935176.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817195352915.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817195352915.png" deleted file mode 100644 index 1ab59ee5f3d8e375b11589ca2fb6bcca398724b1..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817195352915.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817200358090.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817200358090.png" deleted file mode 100644 index c593bee6ccb853d080b721c452a5f3a9ab0ef202..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817200358090.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817201145613.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817201145613.png" deleted file mode 100644 index 8136b628e0aad66197339e5147725aa2a218532d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817201145613.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817201344437.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817201344437.png" deleted file mode 100644 index d5dd42b5b62b5882e84b2ebc602ce4be84e89d77..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817201344437.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817201535828.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817201535828.png" deleted file mode 100644 index cf9ffbfb20f12eec978dd974fa20d953cdbf3988..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817201535828.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817202603640.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817202603640.png" deleted file mode 100644 index 2b7a8b067e17e30a1eeb428870ab1527b6ceed14..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817202603640.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817211849102.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817211849102.png" deleted file mode 100644 index 631a3c7d1618961a2efbe0aab7408c7673168a0b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817211849102.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817211947151.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817211947151.png" deleted file mode 100644 index dd000f8a79b456fbad1a8700cf1629783c20dffd..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817211947151.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817212018549.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817212018549.png" deleted file mode 100644 index 64a74dddec5a9939025a33dae133fc9766800482..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817212018549.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817213710662.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817213710662.png" deleted file mode 100644 index 80da36acee0df8ab093b7194d08dd51f46069720..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/FIGURES/image-20260817213710662.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index abbd7269eca3f3c8c5815855339907f1965ea3e7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,118 +0,0 @@ - - -# 一级标题 - -## 二级标题 - -### 三级标题 - - - -```c -#include - -int main() -{ - return 0; -} -``` - - - -| t1 | t2 | -| :--: | :--: | -| 1 | 2 | - - - -**加粗** - -`高亮` - -*斜体* - - - -![](FIGURES/image-20260817191820776.png) - - - - - -1. - -![](FIGURES/image-20260817192501362.png) - - - -2.点击小锤子进行编译 - -![](FIGURES/image-20260817193731528.png) - - - - - -3.点击下载,弹出终端,显示日志和打印出内容 - -![image-20260817213710662](FIGURES/image-20260817213710662.png) - - - - - - - -1.连接星火1号type-C供电和Debug接口,并按下蓝色电源开关 - -![](FIGURES/image-20260817195352915.png) - - - -2.查看设备管理器>通用串行总线设备 - -![](FIGURES/image-20260817194935176.png) - - - -3.基于芯片创建RTT项目 - -![](FIGURES/image-20260817200358090.png) - - - -4.输入打印信息,进行编译 - -![](FIGURES/image-20260817201145613.png) - - - -5.下载 -![](FIGURES/image-20260817201344437.png) - - - -6.启动串行终端。注意波特率:115200 - -![](FIGURES/image-20260817201535828.png) - - - -7.终端结果打印,配合复位按键。 - -![image-20260817202603640](FIGURES/image-20260817202603640.png) - -8.Debug,升级固件 - -![image-20260817211849102](FIGURES/image-20260817211849102.png) - -![image-20260817211947151](FIGURES/image-20260817211947151.png) - -![image-20260817212018549](FIGURES/image-20260817212018549.png) - - - - - - - diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index eacaf13edffcd3716338270171f126394247d349..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\252/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,23 +0,0 @@ -## Git 提交与 PR 操作流程 - -```bash -# 1. 初始化本地仓库 -git init - -# 2. 克隆远程仓库 -git clone xxx/xx.git - -# 3. 创建并切换到新分支 -git checkout -b day1 - -# 4. 添加文件到暂存区 -git add . - -# 5. 提交到本地仓库 -git commit -m "提交说明" - -# 6. 推送新分支到远程仓库 -git push -u origin day1 - -# 7. 前往 Gitee 网页提交 PR -``` \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 12411ef495c6315612e941fda59868be73da4960..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,186 +0,0 @@ -# 【DAY2】RT-Thread 启动流程、内核基础与多线程编程 - -- **目标平台**:STM32F407 星火号(`stm32f407-rtspark`) -- **提交仓库**:`https://gitee.com/rtthread/rsoc-rtt` -- **作业交付物**:理论总结与实验报告(`README.md`)+ 可编译源码(`*.c`)通过 Gitee PR 提交。 - -## Part 1:理论知识梳理文档(写入作业目录 `README.md`) - -### 1. STM32F407 启动流程与 RT-Thread 引导机制 - -#### **启动路径** -详细画出/写出从上电复位 `Reset_Handler` 到进入 `main()` 的完整调用链。 - -![alt text](RTT.png) - -概念解释: - -Rest_Handler是系统上电复位后第一个执行入口,是一段汇编代码,位于芯片启动文件startup_stm32f407xx.s中。是stm32复位后第一个软件执行点,负责完成从硬件复位状态到C语言运行环境的最小必要初始化,最终把控制权交给C代码 - -启动文件执行后,**在入口函数中调用rtthread_setup()** - -系统的线程由调度器分配 rt_system_scheduler_init()初始化线程调度器 - -信号量不需要时可以关掉 - -application_init 创建一个线程, main thread是系统中的一个线程,不是裸机中的 - -系统线程定时器的线程也是可选的,如果打开可以创建一个叫timer的线程 - -idle初始化,idle比较重要,空闲线程,系统空转时 - -启动调度器之后上面的调度器开始调度。RTT时抢占式优先级的调度策略,在整个调度链表中找优先级最高的(小号高)执行优先级最高的线程 - -组件初始化设计RTT的自动初始化机制,通过宏实现(下一道题,自动化初始化原理)。rt_components_init(),进行组件的初始化。在main函数实际调用之前,发现有函数就去调用(自动初始化机制,不需要人手动初始化,是系统初始化的) - -然后就进入main函数了 - -这里的C代码是__main,C库,是系统代码。从上电到用户代码的链如下: - ```plain -Reset_Handler (汇编) - └── 直接跳转 ──► __main (C 库,系统代码) - └── 初始化 C 环境后 ──► 调用main()或$Sub$$main() - └── 最终 ──► 用户函数的main() - ``` - - -#### **自动初始化原理** -解释 `INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` 的底层实现原理。 - -将函数指针放入特定的数据段,启动时遍历调用 - -补充: -两个宏分别作用于板级初始化和应用初始化。板级初始化阶段内核尚未完全就绪,进行硬件驱动的初始化(UART、GPIO、Timer)应用初始化阶段内核已经完成初始化,调度器即将启动,用于应用系统、文件系统和网络协议栈的初始化。 - - - -### 2. 内核基础与对象模型 - -#### **对象容器与控制块** -简述 RT-Thread 面向对象的设计思想,线程控制块(`struct rt_thread`)中关键成员(如 `stat`、`current_priority`、`init_tick`、`sp`)的作用。 -1. 面向对象设计思想 - RTT采用C语言模拟面向对象设计 - - 基类: - ```c - struct rt_object //所有内核对象的公共属性 - ``` - - 派生:其他内核对象均集成自rt_object - - 容器管理:所有对象按类型分类挂入对象容器链表(rt_object_container),支持遍历、查找和统计 - - 线程控制块由结构体`struct rt_thread`定义并形成**线程内核对象**,在基类的基础上增加了线程状态、优先级等组件,在链接(通过**双向链表**)到内核对象容器中进行管理 - ![alt text]({E29355FB-A696-469A-B788-46A6F6B290B2}.png) - - 每个线程中有线程控制块,线程栈和每个线程的入口函数。 - - ![text]({9E05C824-346A-4509-9270-2FE1EC2DCE46}.png) - - RTT线程控制块`struct rt_thread`定义和属性如下: - ```c - struct rt_thread{ - struct rt_object parent; //继承rt_object - - void *sp; //栈指针,上下文切换时保存/恢复 CPU 寄存器到线程私有栈 - void *entry; //入口函数指针 - void *paramter; - void *stact_addr; //栈起始地址,线程私有栈底地址,用于栈溢出检测。每个线程有自己的线程栈 - rt_uint32_t stack_size; //栈大小 - - rt_err_t error; - rt_uint8_t stat; //线程状态,决定调度器行为(就绪/运行/挂起/关闭/初始) - - rt_uint8_t current_priority; - rt_uint8_t init_priority; - rt_uint32_t number_mask; - - rt_ubase_t init_tick; //线程初始化计数值(初始时间片) - rt_ubase_t remaining_tick; //线程剩余计数值(剩余时间片) - - struct rt_timer thread_timer; //内置线程定时器,用于延时 - - void(*cleanup)(struct rt_thread *tid); //线程退出清除函数 - rt_uint32_t user_data; //用户数据 - } - ``` - - -#### **内存分配方式** -对比静态对象与动态对象的区别(分配位置、生命周期、释放机制),分析为什么某些高可靠场景强制使用静态线程。 -| 对比维度 | 静态对象 | 动态对象| -| --- | -------- | -- | -| **分配位置** | 编译期确定,存储在 `.data`/`.bss` 段或用户指定的全局数组 | 运行时从 **内存堆(Heap)** 或 **内存池(Memory Pool)** 分配 | -| **生命周期** | 与程序同生命周期,除非显式删除 | 创建时分配,删除时释放 | -| **释放机制** | `rt_thread_delete()` / `rt_sem_delete()` 仅从调度器/容器中移除,**内存不回收** | `rt_thread_delete()` / `rt_sem_delete()` 释放内存回堆/池 | -| **API 前缀** | `rt_thread_init()` / `rt_sem_init()` | `rt_thread_create()` / `rt_sem_create() | -| **内存确定性** | **编译期可知**,无运行时分配失败风险 | 依赖堆状态,存在**内存碎片**和**分配失败**风险 | -| **适用场景** | 高可靠、资源受限、硬实时系统 | 功能复杂、对象数量动态变化的应 | - - -### 3. 线程状态机与调度算法 - -#### **五态模型** -绘制或描述 RT-Thread 线程状态(初始态、就绪态、运行态、挂起态、关闭态)及触发状态转换的典型 API。 - - | 起始状态 | 目标状态 | 触发函数 / 条件 | - | -------- | -------- | ------------------------------------------------------------ | - | 无 | 初始状态 | 动态`rt_thread_create()`静态`rt_thread_init()` | - | 初始状态 | 就绪状态 | `rt_thread_startup()` | - | 就绪状态 | 运行状态 | 被调度器选中执行(调度器调度) | - | 运行状态 | 挂起状态 | 阻塞函数 | - | 挂起状态 | 就绪状态 | 唤醒函数 | - | 就绪状态 | 挂起状态 | `rt_thread_suspend()` | - | 运行状态 | 关闭状态 | `rt_thread_exit()` | - | 挂起状态 | 关闭状态 | `rt_thread_delete()` / `rt_thread_detach()` | - -#### **调度机制剖析** - - - **抢占式调度**:通过优先级调度 - - **时间片轮转**:在优先级相同时,按时间片决定进程的执行时间 - -## Part 2:多线程实战编程(C 语言实现) -串口截图 -![alt text](image.png) -硬件运行图片 - -1. 未按下,两个线程运行,两灯闪烁 - ![alt text](image-2.png) -2. 按下按键,线程1挂起,只有LED_B闪烁 - ![alt text](image-1.png) -3. 再次按下按键,两灯恢复闪烁 -### 任务说明 - -1. **工作线程 1(动态创建 `th_led1`)**: - - 优先级 `16`,时间片 `5 ticks`,栈 `1024 Bytes`。 - - 循环翻转板载 LED_R,每次执行打印计数与 `rt_tick_get()`。 -2. **工作线程 2(静态初始化 `th_led2`)**: - - 优先级 `16`,时间片 `5 ticks`,静态定义全局栈与 TCB。 - - 循环翻转板载 LED_B,每次执行打印计数。 -3. **监控与控制线程(动态创建 `th_monitor`)**: - - 优先级 `10`(高于工作线程),周期延时 `rt_thread_mdelay(2000)`。 - - 唤醒后获取并打印 `th_led1` 与 `th_led2` 的运行状态与剩余栈空间大小。 - - 结合板载按键检测(或按键中断):单次触发时调用 `rt_thread_suspend()` 挂起 `th_led1`;再次触发时调用 `rt_thread_resume()` 恢复。 -4. **系统集成与调试命令**: - - 使用 `INIT_APP_EXPORT()` 自动加载测试模块,禁止在 `main.c` 中硬编码调用。 - - 使用 `MSH_CMD_EXPORT()` 导出自定义查看命令(如 `thread_dump`)。 - -## Part 3:Gitee PR 提交与规范 - -### 1. 提交目录规范 - -``` -2026/<组别>/<姓名>/第二天作业 -├── README.md # 包含:Part 1 理论整理 + Part 2 实验结果分析与串口截图 -└── thread_study.c # 作业源码(含动/静态线程、自动初始化、MSH导出) -``` - -### 2. PR 提交流程与规范 - -1. Fork 官方仓库 [https://gitee.com/rtthread/rsoc-rtt](https://gitee.com/rtthread/rsoc-rtt) 到个人账号。 -2. 基于 `master` 新建分支:`day2-姓名拼音`。 -3. 编写代码、文档并进行本地 `scons` 编译验证与星火1号烧录测试。 -4. 提交 Commit。 -5. 向主仓库提交 Pull Request: - - **PR 标题**:`[作业提交] 内核与多线程培训 - <姓名>` - - **PR 内容**:附带理论文档简述、硬件运行现象照片及 FinSH 串口日志输出。 \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/RTT.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/RTT.png" deleted file mode 100644 index e2ab12c744149d5af12e31c7b94232f1f9b4046d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/RTT.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/image-1.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/image-1.png" deleted file mode 100644 index e8fc47ae73318b547d7479eb9fa527676839b8ef..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/image-1.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/image-2.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/image-2.png" deleted file mode 100644 index d721b69624e07bb27e8c5fe8ef2890dd8c299102..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/image-2.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/image.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/image.png" deleted file mode 100644 index a67cde9e243bca362733b8ae0ac215d5db780a9a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/image.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index f0f17e4c93b3a1bf165dda795981fbd3b6c741d1..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 d0ubleU0x00 the first version - */ - -/* - * 程序清单:多线程管理程序,创建动态、静态线程、监控、按键挂起和恢复 - * 命令调用格式:导出thread_dump到终端 - * 程序功能:创建两个LED工作线程和一个监控线程,支持按键挂起/恢复工作线程1 -*/ - - -#include -#include -#include -#include -#include - - - -#define LED_B_PIN GET_PIN(F,11) -#define LED_R_PIN GET_PIN(F,12) -#define KEY_PIN GET_PIN(C,0) - - -#define LED_ON_LEVEL PIN_LOW -#define LED_OFF_LEVEL PIN_HIGH - -/* - * 按键极性: - * 当前假设 PC5 使用内部上拉,按键按下接地,即低电平有效。 - * - * 如果按键是高电平有效,改: - * KEY_PIN_MODE -> PIN_MODE_INPUT_PULLDOWN - * KEY_IRQ_MODE -> PIN_IRQ_MODE_RISING - * KEY_PRESSED_LEVEL -> PIN_HIGH - */ -#define KEY_PIN_MODE PIN_MODE_INPUT_PULLUP -#define KEY_IRQ_MODE PIN_IRQ_MODE_FALLING -#define KEY_PRESSED_LEVEL PIN_LOW - - -/* 线程优先级 */ -#define LED_THREAD_PRIORITY 16 -#define MONITOR_THREAD_PRIORITY 16 - -/* ===================== 线程对象 ===================== */ - -/* 指向led线程的状态信号量的指针 */ -static rt_sem_t led_sem = RT_NULL; - -/* 工作线程 1:动态创建 */ -static rt_thread_t th_led1 = RT_NULL; - -/* 工作线程 2:静态初始化 */ -/* 使用静态线程时,线程的栈需要设置字节对齐 */ -ALIGN(RT_ALIGN_SIZE) -static rt_uint8_t th_led2_stack[1024]; -static struct rt_thread th_led2; - -/* 监控线程:动态创建 */ -static rt_thread_t th_monitor = RT_NULL; - - -/* 按键中断与线程通信的全局变量 */ -static volatile rt_uint32_t key_event_count = 0; /* 按键按下次数计数器 */ -static volatile rt_tick_t last_isr_tick = 0; /* 上次中断触发的系统时间 */ - - - - -/* ===================== 工具函数 ===================== */ - -static const char *thread_state_str(rt_uint8_t stat) -{ - stat &= RT_THREAD_STAT_MASK; - - switch (stat) - { - case RT_THREAD_INIT: - return "init"; - - case RT_THREAD_READY: - return "ready"; - - case RT_THREAD_RUNNING: - return "running"; - - case RT_THREAD_SUSPEND: - return "suspend"; - - case RT_THREAD_CLOSE: - return "close"; - - default: - return "unknown"; - } -} - -//当前-sp -static rt_uint32_t thread_stack_free(struct rt_thread *thread) -{ - if (thread == RT_NULL || thread->stack_addr == RT_NULL) - { - return 0; - } - - /* 使用当前栈指针 sp 计算剩余空间:sp 高于 stack_addr 的部分即为未使用区域 */ - if ((rt_uint32_t)thread->sp > (rt_uint32_t)thread->stack_addr) - { - return (rt_uint32_t)thread->sp - (rt_uint32_t)thread->stack_addr; - } - - return 0; -} - -static void print_thread_info(const char *tag, struct rt_thread *thread) -{ - if (thread == RT_NULL) - { - rt_kprintf("%-8s: thread not created\n", tag); - return; - } - - rt_kprintf("%-8s name=%.*s state=%-7s prio=%d tick=%u stack_free=%u/%u\n", - tag, - RT_NAME_MAX, - thread->name, - thread_state_str(thread->stat), - thread->current_priority, - (unsigned int)thread->init_tick, - (unsigned int)thread_stack_free(thread), - (unsigned int)thread->stack_size); -} - -static void print_all_status(void) -{ - rt_kprintf("[thread status]\n"); - print_thread_info("led1", th_led1); - print_thread_info("led2", &th_led2); - print_thread_info("monitor", th_monitor); -} - - -/* ===================== 按键中断服务函数 (ISR) ===================== */ -static void key_isr_handler(void *args) -{ - rt_tick_t current_tick = rt_tick_get(); - - /* 硬件级/ISR级软件消抖: - * 如果距离上次触发时间不到 20ms,认为是机械抖动,直接丢弃。 - */ - if ((current_tick - last_isr_tick) >= rt_tick_from_millisecond(20)) - { - last_isr_tick = current_tick; - key_event_count++; /* 有效按键次数 +1 */ - } -} -/* - * 就绪态延时。 - * - * 与 rt_thread_mdelay() 不同: - * rt_thread_mdelay() 会让线程进入 SUSPEND 状态, - * 而这里通过不断判断 tick 并调用 rt_thread_yield(), - * 使线程仍然处于 READY/RUNNING 状态。 - * - * 这样监控线程调用 rt_thread_suspend(th_led1) 更容易成功。 - */ -static void ready_delay_ms(rt_uint32_t ms) -{ - rt_tick_t start = rt_tick_get(); - rt_tick_t ticks = rt_tick_from_millisecond(ms); - - while ((rt_tick_get() - start) < ticks) - { - rt_thread_yield(); - } -} - -/* 线程th_led1的入口函数 */ -static void th_led1_entry(void *parameter) -{ - - static rt_uint8_t count = 0; - while(1) - { - if (rt_sem_take(led_sem, 0) == RT_EOK) - { - rt_kprintf("th_led1 suspend.\n"); - rt_thread_suspend(th_led1); //挂起th_led1 - } - else - { - count++; - rt_pin_write(LED_R_PIN, !rt_pin_read(LED_R_PIN)); - rt_kprintf("th_led1 count=%u tick=%u\n", - (unsigned int)count, - (unsigned int)rt_tick_get()); - ready_delay_ms(1000); - } - } -} - -/* 线程th_led2的入口函数 */ -static void th_led2_entry(void *parameter) -{ - - static rt_uint8_t count = 0; - while(1) - { - count++; - rt_pin_write(LED_B_PIN, !rt_pin_read(LED_B_PIN)); - rt_kprintf("th_led2 count=%u\n",(unsigned int)count); - ready_delay_ms(1000); - } -} - -/* 线程th_monitor的入口函数 */ -static void th_monitor_entry(void *parameter) -{ - rt_uint32_t key_cnt; - rt_base_t level; - while(1) - { - /* 关中断,安全地读取并清零计数器 (原子操作) */ - level = rt_hw_interrupt_disable(); - key_cnt = key_event_count; - key_event_count = 0; /* 读完就清零,防止重复处理 */ - rt_hw_interrupt_enable(level); - - /* 如果计数器大于 0,说明睡觉期间有人按了按键 */ - - if (key_cnt > 0) - { - rt_thread_mdelay(20); - if(rt_pin_read(KEY_PIN)==0) //按键消抖20ms - { - - //每次按下按键翻转一下线程th_led1的状态 - if((th_led1->stat &= RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND) - { - rt_kprintf("th_led1 resume.\n"); - rt_thread_resume(th_led1); //使线程恢复运行 - } - else - { - rt_sem_release(led_sem); //释放信号量 - } - rt_kprintf("[KEY] key event \n"); - } - - } - - // 打印状态 - print_all_status(); - rt_thread_mdelay(2000); - - } -} - - -static void gpio_init(void) -{ - - /* 初始化 LED 引脚 */ - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); - - rt_pin_write(LED_R_PIN, LED_ON_LEVEL); - rt_pin_write(LED_B_PIN, LED_ON_LEVEL); - - /* 初始化按键中断 */ - rt_pin_mode(KEY_PIN, KEY_PIN_MODE); - rt_pin_attach_irq(KEY_PIN, KEY_IRQ_MODE, key_isr_handler, RT_NULL); - rt_pin_irq_enable(KEY_PIN, PIN_IRQ_ENABLE); - -} - - -/* ===================== 模块初始化 ===================== */ - -static int thread_study_init(void) -{ - /* 初始化GPIO*/ - gpio_init(); - /* 创建一个动态信号量,初始值是0 */ - led_sem = rt_sem_create("led_sem", 0, RT_IPC_FLAG_PRIO); - /* - * 工作线程 1:动态创建 - * 名称:th_led1 - * 优先级:16 - * 时间片:5 ticks - * 栈大小:1024 Bytes - */ - th_led1 = rt_thread_create("th_led1", - th_led1_entry, - RT_NULL, - 2048, - 16, - 5); - rt_thread_startup(th_led1);//动态线程不能用&符号 - /* - * 工作线程 2:静态初始化 - * 名称:th_led2 - * 优先级:16 - * 时间片:5 ticks - * 栈大小:1024 Bytes - */ - rt_thread_init(&th_led2, - "th_led2", - th_led2_entry, - RT_NULL, - th_led2_stack, - sizeof(th_led2_stack), - 16, - 5); - - rt_thread_startup(&th_led2); - - /* 监控线程:动态创建 */ - th_monitor = rt_thread_create("th_mon", - th_monitor_entry, - RT_NULL, - 2048, - 10, - 5); - rt_thread_startup(th_monitor); - rt_kprintf("thread_study init done\n"); - rt_kprintf("LED_R=PF12, LED_B=PF11, KEY=PC5\n"); - rt_kprintf("Press KEY to suspend/resume th_led1\n"); - - return 0; -} -INIT_APP_EXPORT(thread_study_init); - -/* ===================== MSH 调试命令 ===================== */ - -void thread_dump(void) -{ - print_all_status(); -} -MSH_CMD_EXPORT(thread_dump, dump thread study status); - - diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/{9E05C824-346A-4509-9270-2FE1EC2DCE46}.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/{9E05C824-346A-4509-9270-2FE1EC2DCE46}.png" deleted file mode 100644 index 17d796cbd1e8b8b5edf75c23b74692350b90a529..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/{9E05C824-346A-4509-9270-2FE1EC2DCE46}.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/{E29355FB-A696-469A-B788-46A6F6B290B2}.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/{E29355FB-A696-469A-B788-46A6F6B290B2}.png" deleted file mode 100644 index 2f46656eda1ccce63b1dd4ca03880e7f65a516be..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/{E29355FB-A696-469A-B788-46A6F6B290B2}.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-1.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-1.png" deleted file mode 100644 index 156a8dfcd87b0c26ed6551003c12fa6d43808d75..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-1.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-2.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-2.png" deleted file mode 100644 index 0ef7eeff63e8416ef6c4238cc2230f96cd425ba7..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-2.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-3.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-3.png" deleted file mode 100644 index 0fcf8e6e340c228a1e9a03b5005a7dd938f43c2d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-3.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-4.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-4.png" deleted file mode 100644 index b4cf007bacd5c7bbf6dd9f8a18d6da4374be6999..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-4.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-5.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-5.png" deleted file mode 100644 index 5712c15369fdc2b1ec6bec64d231a50fae604f92..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image-5.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image.png" deleted file mode 100644 index 1f990184102715531aa5cc74eae039cce5d73fa4..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/image.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/pic1.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/pic1.png" deleted file mode 100644 index 986a4db6051273ceab3eae54d6a101f82773f29d..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/pic1.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{6ABE2BB1-6288-4CF2-8744-544E15FB5AD7}.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{6ABE2BB1-6288-4CF2-8744-544E15FB5AD7}.png" deleted file mode 100644 index d24a0de7a113e5299274d564ab3e425c99e26dcf..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{6ABE2BB1-6288-4CF2-8744-544E15FB5AD7}.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{96D9EC29-A393-4F21-B4E5-7ED0EEB5EC6D}.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{96D9EC29-A393-4F21-B4E5-7ED0EEB5EC6D}.png" deleted file mode 100644 index 73820bea336b8c04a934b15dca9382e25fddb38c..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{96D9EC29-A393-4F21-B4E5-7ED0EEB5EC6D}.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{A40AD466-BFCB-4C9D-BA52-92B2C9EE33E5}.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{A40AD466-BFCB-4C9D-BA52-92B2C9EE33E5}.png" deleted file mode 100644 index 5acabee3d5837d604b6af478e87be306a28b2245..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{A40AD466-BFCB-4C9D-BA52-92B2C9EE33E5}.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{EE5B4AE8-0853-4784-B9B0-B7313B4470CE}.png" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{EE5B4AE8-0853-4784-B9B0-B7313B4470CE}.png" deleted file mode 100644 index c4c96d125f93dc758165a96988883129bd13f49f..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/{EE5B4AE8-0853-4784-B9B0-B7313B4470CE}.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/\346\236\204\345\273\272\347\263\273\347\273\237.md" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/\346\236\204\345\273\272\347\263\273\347\273\237.md" deleted file mode 100644 index a9c53b4a87beb173de4eceeae3c227486d427549..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/\346\236\204\345\273\272\347\263\273\347\273\237.md" +++ /dev/null @@ -1,38 +0,0 @@ -# SCons -RTT使用scons命令编译和构建系统 - -文档地址:[text](https://www.rt-thread.org/document/site/#/development-tools/build-config-system/SCons) - -构建工具:根据一定的规则或指令,将源代码编译成可执行的二进制程序 - -SCons 由python编写的构建工具 - -脚本作用范围:当前src目录下的.c、.h文件,是分散式写的 - -## rtconfig.py 配置文件 -写了编译器和构建参数相关的内容 - -工具链的区分 -![alt text](image-2.png) - -工具链参数 -![alt text]({A40AD466-BFCB-4C9D-BA52-92B2C9EE33E5}.png) - -# SCons基本命令 - -使用`scons`命令编译BSP -可以加后缀 -j 多线程编译 - -![alt text]({96D9EC29-A393-4F21-B4E5-7ED0EEB5EC6D}.png) - -`scons -c` 清除编译目标 包括临时文件和目标文件 - -`scons --target=makefile(或者什么其他的内容)` scons根据当前的目录结构生成一个makefile或c脚本,也可以生成一些mdk的工程 - -`scons --dist`打包,变成一个比较精简的BSP,不相关的BSP文件都会被移除,并且可以拷贝此工程到任意目录下使用 - -`scons --verbose` 打印详细参数 - -# config文件 - - diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 4b62c79dd3744bd3c102cba0de404a093a610ed9..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,92 +0,0 @@ -因为git和markdown是之前掌握的内容,不再重复记录 -这里有一点不理解的是为什么会出现不同的编译器环境 - -# RTT 启动流程 - -图片显示了从芯片上电到用户 `main()` 函数执行的整个初始化过程。 - -![图片](./pic1.png) - -## 一、启动入口(左侧) - -系统上电后,首先执行 **启动文件** `startup_xx.S`,根据不同的编译器环境,进入不同的入口函数:(为什么会出现不同的编译期环境) - - -| 编译器 | 入口函数 | 说明 | -|--------|----------|------| -| **MDK (Keil)** | `$Sub$$main()` | 使用 MDK 的符号扩展机制,拦截 main 函数 | -| **IAR** | `__low_level_init()` | IAR 编译器的底层初始化入口 | -| **GCC** | `entry()` | GCC 工具链的标准入口 | - -这些入口最终都会汇聚到 **`rtthread_startup()`**,(RT-Thread 统一的启动函数)。 - - - -## 二、内核初始化阶段(中间主干) - -`rtthread_startup()` 按顺序执行以下初始化步骤: - -1. **`rt_hw_interrupt_disable()`** — 关闭全局中断,确保初始化过程不被打断。 -2. **`rt_hw_board_init()`** → **`rt_components_board_init()`** → **board init functions** - 进行板级硬件初始化(时钟、GPIO、串口等),执行通过自动初始化机制注册的板级初始化函数。 -3. **`rt_show_version()`** — 打印 RT-Thread 版本信息(通常通过串口输出)。 -4. **`rt_system_timer_init()`** — 初始化系统定时器(用于提供时钟 tick)。 -5. **`rt_system_scheduler_init()`** — 初始化线程调度器。 -6. **`rt_system_signal_init()`** — 初始化信号机制(如果使能)。 -7. **`rt_application_init()`** → **`rt_thread_init(main_thread_entry)`** - 创建**主线程**(main thread),入口函数是 `main_thread_entry()`。 -8. **`rt_system_timer_thread_init()`** → **`rt_thread_init(rt_thread_timer_entry)`** - 创建**系统定时器线程**,用于处理软件定时器。 -9. **`rt_thread_idle_init()`** → **`rt_thread_init(rt_thread_idle_entry)`** - 创建**空闲线程**(idle thread),当系统无其他线程运行时执行。 -10. **`rt_system_scheduler_start()`** — **启动调度器**,从此操作系统开始多线程调度。 - ---- - -## 三、线程运行与组件初始化(右侧) - -调度器启动后,按照调度规则执行各个线程: - -### 1. 主线程 `main_thread_entry()` -这是最重要的线程,它负责: -- 调用 **`rt_components_init()`** — 执行通过自动初始化机制注册的各种组件初始化函数,按层级包括: - - **pre-initialization functions** — 预初始化 - - **device init functions** — 设备初始化 - - **components init functions** — 组件初始化 - - **environment init functions** — 环境初始化 - - **applications init functions** — 应用初始化 -- 最后调用 **`$Super$$main()`**(MDK 环境下)→ 进入用户的 **`main()`** 函数。 - -### 2. 定时器线程 `rt_thread_timer_entry()` -处理系统中所有的软件定时器超时回调。 - -### 3. 空闲线程 `rt_thread_idle_entry()` -系统空闲时运行,通常用于执行低优先级任务或进入低功耗模式。 - -### 4. 其他线程 -用户自定义的其他应用线程。 - ---- - -## 总结 - -这张图清晰地展示了 RT-Thread 的**分层启动架构**: - -``` -启动文件 → rtthread_startup() [内核初始化] → 调度器启动 → 多线程运行 - ↓ - main_thread_entry() - ↓ - rt_components_init() [组件自动初始化] - ↓ - main() -``` - -**核心特点**: -- **自动初始化机制**:通过宏(如 `INIT_BOARD_EXPORT`、`INIT_APP_EXPORT` 等)将初始化函数分散在各个文件中,由 `rt_components_init()` 统一按优先级调用。 -- **主线程包裹 main()**:用户的 `main()`是RT-Thread的主线程上下文中执行的,因此可以直接使用RT-Thread的API(创建线程、信号量等)。 -- **编译器兼容**:通过不同的入口适配MDK、IAR、GCC三种主流嵌入式编译工具链。(但是不知道为什么要这么做) - -注:与linux系统的区别 - - Linux为通用操作系统,其初始化止于内核边界、用户空间依赖外部配置,main()是拥有独立地址空间的用户进程,必须通过系统调用访问内核,且构建体系深度绑定 GCC。 \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/\347\272\277\347\250\213\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/\347\272\277\347\250\213\347\254\224\350\256\260.md" deleted file mode 100644 index edb4552bd7c527365f06e507be8cb6f1b080c6de..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\347\216\213\347\220\263/\347\254\224\350\256\260/\347\272\277\347\250\213\347\254\224\350\256\260.md" +++ /dev/null @@ -1,91 +0,0 @@ -## 线程初始化 -有静态初始化和动态初始化 - -静态线程对象初始化,线程句柄(线程控制块指针)、线程栈由用户提供 -> 静态线程是指 线程控制块、线程运行栈一般都设置为全局变量,在编译时就被确定、分配处理,内核不负责动态分配内存空间 -> -> 用户提供的栈首地址要做系统对齐(ARM 上为4字节对齐)、 - -函数接口: -```c -rt_err rt_thread_init(struct rt_thread *thread, //线程控制块指针 - const char *name, //线程名称 - void(*entry)(void *parameter), //可以调用的参数 - void *parameter, - void *stack_start, //线程起始地址 - rt_uint32_t stack_size, - rt_uint8_t priority, //线程优先级,小数高 - rt_uint32_t tick); //调度方式,优先级相同时的时间片 - -``` -线程初始化接口参数: -![alt text](image-1.png) - -注意线程是由返回值的,编码时注意接收。如果没有足够分配线程的空间可能会分配失败 - -与linux的不同:**因为RTT没有MMU,直接使用物理地址,所以这里会有线程起始地址这个说法**,栈大小、位置由开发者显式指定,链接器在编译期就把地址写死了 - -静态编译的好处:有确定性,在编译时就知道系统资源的分配情况。动态初始化不知道申请空间的时机和次数等(尽量不要用递归,会分配很多空间) - -动态初始化线程,调用到这个函数时,系统会从动态堆内存中分配一个线程句柄以及相应栈大小参数的空间。分配出的栈空间按rtconfig.h中配置的RT_ALIGN_SIZE方式对齐 - -```c -rt_thread_t rt_thread_create( - const char* name, - void (*entry)(void* parameter), - void *parameter, - rt_uint32_t stack_size, - rt_uint8_t priority, - rt_uint32_t tick); -``` - -## 线程启动 -线程怎么让他工作 - -创建(初始化)的线程处于初始状态,没有变成就绪态,就未进入就绪线程的调度队列 - -创建成功后调用函数接口`rt_thread_startup`让该线程进入就绪状态。调用这个函数时,立即将线程状态改为就绪态,并放到相应优先级队列中调度。如果启动的这个线程优先级是最高的,将立即切换到这个线程。 - -```c -rt_err_t rt_thread_startup(rt_thread_t thread) //让线程起来 -``` -线程初始化示例 - -![]({6ABE2BB1-6288-4CF2-8744-544E15FB5AD7}.png) - -## 线程延时 -让出CPU使用权 -抢占式系统线程在不适用CPU时需要让出CPU使用权,让其他线程运行。这通过线程延时或睡眠实现 - -通过使用让线程延时/休眠的API实现,在指定的时间到达后重新运行 - -```c -rt_err_t rt_thread_delay(rt_tick_t tick); //以一个OS tick为单位(理解为时钟周期?) -rt_err_t rt_thread_delay(rt_int32_t ms);//以ms为单位,用32位整数表示 -``` - -# 代码实现:基于星火1号芯片的内核实例 -所有的启动流程都在/rt-thread/src/components.c文件中 - -自动初始化 -![]({EE5B4AE8-0853-4784-B9B0-B7313B4470CE}.png) - -# 作业:多线程实战编程 -使用C语言写函数实现创建线程 -点LED看标准版本中的设备驱动IO设备模型,根据[PIN设备使用示例](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/pin/pin?id=pin-%e8%ae%be%e5%a4%87%e4%bd%bf%e7%94%a8%e7%a4%ba%e4%be%8b)编写 - -## 任务说明 - -1. **工作线程 1(动态创建 `th_led1`)**: - - 优先级 `16`,时间片 `5 ticks`,栈 `1024 Bytes`。 - - 循环翻转板载 LED_R,每次执行打印计数与 `rt_tick_get()`。 -2. **工作线程 2(静态初始化 `th_led2`)**: - - 优先级 `16`,时间片 `5 ticks`,静态定义全局栈与 TCB。 - - 循环翻转板载 LED_B,每次执行打印计数。 -3. **监控与控制线程(动态创建 `th_monitor`)**: - - 优先级 `10`(高于工作线程),周期延时 `rt_thread_mdelay(2000)`。 - - 唤醒后获取并打印 `th_led1` 与 `th_led2` 的运行状态与剩余栈空间大小。 - - 结合板载按键检测(或按键中断):单次触发时调用 `rt_thread_suspend()` 挂起 `th_led1`;再次触发时调用 `rt_thread_resume()` 恢复。 -4. **系统集成与调试命令**: - - 使用 `INIT_APP_EXPORT()` (自动初始化)自动加载测试模块,禁止在 `main.c` 中硬编码调用。 - - 使用 `MSH_CMD_EXPORT()` 导出自定义查看命令(如 `thread_dump`)。 \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/README.md" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/README.md" deleted file mode 100644 index c52fdc2a40c96bd674464b4d6b720f66571a6961..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,360 +0,0 @@ -## Part1:STM32F407 启动流程与 RT-Thread 引导机制 - -### 1.1 从上电复位 Reset_Handler 到 main() 完整调用链 - -STM32F407 在裸机与 RT-Thread 系统下启动流程不同,RT-Thread 会接管启动流程,在调度器启动后再进入用户 main 函数,完整调用链如下: - -```Plain -芯片上电复位 -↓ -硬件自动读取 0x00000004 复位向量,跳转执行 Reset_Handler(汇编启动文件) -↓ -Reset_Handler - 1. 初始化栈指针 SP - 2. 调用 SystemInit() 配置系统时钟、中断向量表、FPU - 3. 跳转至 rt_thread_startup()(RT-Thread 内核入口) -↓ -rt_thread_startup() - 1. rt_hw_board_init() // 板级硬件初始化 - 2. rt_system_heap_init() // 系统堆内存初始化 - 3. rt_system_scheduler_init()// 调度器初始化 - 4. 内核对象、定时器、空闲线程初始化 - 5. 执行 INIT_BOARD_EXPORT 注册的板级初始化函数 - 6. rt_system_scheduler_start() // 开启调度器 -↓ -调度器切换至主线程 -↓ -进入 main() 函数 -↓ -自动执行 INIT_APP_EXPORT 注册的应用初始化函数 -``` - -### 1.2 自动初始化原理:INIT_BOARD_EXPORT 与 INIT_APP_EXPORT - -RT-Thread 自动初始化基于 **GCC 自定义段属性**实现,无需用户手动调用初始化函数,内核开机自动遍历执行。 - -#### 底层实现原理 - -两个宏本质是对 `__attribute__((section()))` 的封装,将自定义初始化函数的函数指针存放至链接脚本定义的专属内存段中: - -- **INIT_BOARD_EXPORT(fn)**:将函数指针放入 `.rti_board` 段 -- **INIT_APP_EXPORT(fn)**:将函数指针放入 `.rti_app` 段 - -链接阶段,编译器将所有同等级初始化函数指针连续排列,形成函数指针数组;内核启动时遍历该段内存,依次执行所有注册函数,实现自动初始化。 - -#### 执行时机与区别 - -- **INIT_BOARD_EXPORT**:调度器启动前执行,无线程环境,仅可做硬件底层初始化,禁止阻塞 API。 -- **INIT_APP_EXPORT**:进入 main 函数、调度器正常运行后执行,具备完整线程环境,可使用系统各类 API。 - -## 2 内核基础与对象模型 - -### 2.1 对象容器与线程控制块 - -RT-Thread 采用 **面向对象设计思想**,将线程、信号量、消息队列、互斥锁等所有内核资源统一抽象为内核对象,通过统一的对象容器管理,支持对象遍历、统计、调试与资源管理,提升内核通用性与可维护性。 - -线程控制块 `struct rt_thread` 是描述线程的核心数据结构,保存线程全部属性与运行上下文,关键成员作用如下: - -- **stat(线程状态)**:标记线程当前状态(初始、就绪、运行、挂起、关闭),是调度器判断线程是否参与调度的核心依据。 -- **current_priority(当前优先级)**:决定线程抢占优先级,数值越小优先级越高,支持运行时动态修改,是抢占式调度的核心参数。 -- **init_tick(时间片)**:用于同优先级时间片轮转调度,系统滴答中断中递减,计数归零时线程主动让出 CPU。 -- **sp(栈指针)**:保存线程私有栈的栈顶地址,线程切换时用于保存/恢复寄存器上下文,保证线程切换后继续断点执行。 - -### 2.2 静态对象与动态对象区别 - -| 对比维度 | 静态对象 | 动态对象 | -| :----------- | :----------------------------- | :----------------------------------- | -| 内存分配位置 | 编译期分配,位于 BSS/DATA 段 | 运行期分配,取自系统 Heap 堆内存 | -| 生命周期 | 贯穿整个系统运行周期 | 创建后生效,删除后失效 | -| 释放机制 | 无需手动释放,仅可脱离内核管理 | 调用 rt_thread_delete 主动释放堆内存 | -| 运行开销 | 无运行时分配开销,无内存碎片 | 存在分配开销,长期运行易产生碎片 | - -#### 高可靠场景强制使用静态线程的原因 - -1. **杜绝内存分配失败风险**:动态线程依赖堆内存,长期运行会产生内存碎片,极端情况下 malloc 分配失败导致任务创建失败;静态线程编译期固定分配内存,运行零风险。 -2. **运行时序可确定**:静态内存地址、大小固定,系统资源可量化、可预估,满足工业实时系统的确定性要求。 -3. **无内存泄漏问题**:无需手动释放内存,避免动态对象误删、漏删导致的内存泄漏故障。 - -## 3 线程状态机与调度算法 - -### 3.1 RT-Thread 线程五态模型 - -RT-Thread 标准五态:初始态、就绪态、运行态、挂起态、关闭态,各状态转换规则与对应 API 如下: - -- **初始态(RT_THREAD_INIT)**:线程初始化完成,未加入就绪队列。创建 API:`rt_thread_init` / `rt_thread_create`;调用 `rt_thread_startup` 转为就绪态。 -- **就绪态(RT_THREAD_READY)**:线程资源就绪,等待 CPU 时间片。可被高优先级任务抢占,或时间片轮转获取 CPU;调用`rt_thread_suspend` 进入挂起态。 -- **运行态(RT_THREAD_RUNNING)**:当前占用 CPU 正在执行的线程。高优先级任务就绪则被抢占回就绪态;时间片耗尽回归就绪态;执行阻塞 API 进入挂起态。 -- **挂起态(RT_THREAD_SUSPEND)**:线程阻塞休眠,不参与调度。触发方式:延时、等待信号量、主动挂起;资源到位/延时结束/主动唤醒(`rt_thread_resume`)转为就绪态。 -- **关闭态(RT_THREAD_CLOSE)**:线程执行结束,退出调度队列。触发方式:线程函数 return、`rt_thread_exit`。 - -### 3.2 调度机制剖析 - -#### 3.2.1 抢占式调度 - -RT-Thread 核心实时机制,规则:**只要更高优先级线程变为就绪态,立即抢占当前低优先级线程的 CPU 使用权**。 - -抢占行为多发生在中断退出阶段,中断内唤醒高优先级任务,退出中断时触发上下文切换,保证紧急任务优先执行,保障系统实时性。不同优先级线程完全遵循高优先执行原则。 - -#### 3.2.2 时间片轮转调度 - -时间片轮转仅针对 **同一优先级** 的多个就绪线程。 - -系统每个线程拥有固定时间片`init_tick`,SysTick 滴答中断驱动计数递减;当前线程时间片耗尽后,主动让出 CPU,同优先级下一个就绪线程获得执行权并重置时间片。若线程提前阻塞,剩余时间片直接作废。 - -### 总结 - -RT-Thread 调度核心规则:**不同优先级抢占优先,同优先级时间片轮转**,兼顾系统实时性与多任务公平调度。 - - - -## Part2: 多线程实战编程 - -基于 STM32F407 平台,完成动态线程、静态线程、线程状态监控、线程挂起/恢复、自动初始化、控制台命令导出全套实战功能。 - -### 1 工程配置与全局定义 - -提前完成板载 LED、按键 GPIO 初始化。 - -```c -#include - -/*************************引脚定义**************************/ -#define LED_Port GPIOF -#define LED1_Pin GPIO_PIN_11 -#define LED2_Pin GPIO_PIN_12 -//使用按键btn_down -#define Key_Port GPIOC -#define Key_Pin GPIO_PIN_1 - -/********************* 全局静态线程定义 *********************/ -// 静态线程2栈大小、TCB控制块定义 -#define TH_LED2_STACK_SIZE 2048 -rt_uint8_t th_led2_stack[TH_LED2_STACK_SIZE]; -struct rt_thread th_led2_tcb; - -/********************* 线程句柄定义 *********************/ -rt_thread_t th_led1_handle = RT_NULL; -rt_thread_t th_monitor_handle = RT_NULL; - -/********************* 计数变量 *********************/ -static rt_uint32_t led1_cnt = 0; -static rt_uint32_t led2_cnt = 0; -static rt_uint8_t key_flag = 0; -static rt_uint8_t task_flag = 0; -``` - -### 2 板载硬件底层驱动 - -适配 STM32F407 板载 LED、按键基础驱动 - -```c -void gpio_init(void) -{ - GPIO_InitTypeDef GPIO_InitStruct = {0}; - - // 使能GPIO时钟 - __HAL_RCC_GPIOF_CLK_ENABLE(); - __HAL_RCC_GPIOC_CLK_ENABLE(); - - // 初始化PF11、PF12为推挽输出(LED) - GPIO_InitStruct.Pin = LED1_Pin | LED2_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - HAL_GPIO_Init(LED_Port, &GPIO_InitStruct); - - HAL_GPIO_WritePin(LED_Port, LED1_Pin | LED2_Pin, GPIO_PIN_RESET); - - // 初始化PC1为上拉输入(按键) - GPIO_InitStruct.Pin = Key_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_PULLUP; - HAL_GPIO_Init(Key_Port, &GPIO_InitStruct); -} -INIT_BOARD_EXPORT(gpio_init); - -// LED 翻转 -void led_toggle(uint16_t GPIO_Pin) -{ - HAL_GPIO_TogglePin(LED_Port, GPIO_Pin); -} - -rt_bool_t key_scan(void) -{ - if ((HAL_GPIO_ReadPin(Key_Port, Key_Pin) == GPIO_PIN_RESET)) - { - rt_thread_mdelay(20); - if(HAL_GPIO_ReadPin(Key_Port, Key_Pin) == GPIO_PIN_RESET) - return RT_TRUE; - } - - return RT_FALSE; -} -``` - -### 3 线程入口函数实现 - -#### 3.1 动态线程1:LED_R 闪烁线程 - -优先级16、时间片5ticks、栈1024Byte,循环翻转LED并打印计数与系统时钟节拍 - -```c -void th_led1_entry(void *parameter) -{ - while(1) - { - if(task_flag) { - rt_kprintf("Suspend th_led1 success!\n"); - rt_thread_suspend(th_led1_handle); - rt_schedule(); - } - - led_toggle(LED1_Pin); - led1_cnt++; - rt_kprintf("[LED1] Count: %d, SysTick: %d\n", led1_cnt, rt_tick_get()); - rt_thread_mdelay(500); - } -} -``` - -#### 3.2 静态线程2:LED_B 闪烁线程 - -静态TCB+静态栈,同优先级、同时间片配置,循环翻转LED并打印计数 - -```c -void th_led2_entry(void *parameter) -{ - while(1) - { - led_toggle(LED2_Pin); - led2_cnt++; - rt_kprintf("[LED2] Count: %d\n", led2_cnt); - rt_thread_mdelay(500); - } -} -``` - -#### 3.3 监控控制线程(高优先级) - -优先级10,2秒周期执行,打印线程状态、剩余栈空间,响应按键挂起/恢复LED1线程 - -```c -void th_monitor_entry(void *parameter) -{ - while(1) - { - // 按键单次触发:切换LED1线程挂起/恢复状态 - if(key_scan()) - { - if (key_flag) - { - task_flag = 0; - rt_thread_resume(th_led1_handle); - rt_kprintf("Resume th_led1 success!\n"); - } - else - task_flag = 1; - - key_flag = !key_flag; - } - - // 打印线程运行状态与剩余栈空间 - rt_kprintf("\n========== Thread Status Info ==========\n"); - rt_kprintf("[LED1 Thread] State: %d\n", th_led1_handle->stat); - - // 静态线程直接读取TCB - rt_kprintf("[LED2 Thread] State: %d\n", th_led2_tcb.stat); - rt_kprintf("========================================\n\n"); - - rt_thread_mdelay(2000); - } -} -``` - -### 4 线程初始化与自动加载 - -通过 `INIT_APP_EXPORT` 自动初始化,无需main函数硬编码调用,区分动态/静态线程创建方式 - -```c -void thread_test_init(void) -{ - /***** 动态创建 LED1 线程 *****/ - th_led1_handle = rt_thread_create("th_led1",th_led1_entry,RT_NULL,2048,16,5); - if(th_led1_handle != RT_NULL) - { - rt_thread_startup(th_led1_handle); - rt_kprintf("[Init] th_led1 create success!\n"); - } - - /***** 静态初始化 LED2 线程 *****/ - rt_thread_init(&th_led2_tcb,"th_led2",th_led2_entry,RT_NULL,th_led2_stack,TH_LED2_STACK_SIZE,16,5); - rt_thread_startup(&th_led2_tcb); - rt_kprintf("[Init] th_led2 static init success!\n"); - - /***** 动态创建监控线程 *****/ - th_monitor_handle = rt_thread_create("th_monitor",th_monitor_entry,RT_NULL,2048,10,5); - if(th_monitor_handle != RT_NULL) - { - rt_thread_startup(th_monitor_handle); - rt_kprintf("[Init] th_monitor create success!\n"); - } -} -INIT_APP_EXPORT(thread_test_init); -``` - -### 5 自定义控制台调试命令 - -通过 `MSH_CMD_EXPORT` 导出 thread_dump 命令,手动查看线程详细信息 - -```c -void thread_dump_cmd(void) -{ - rt_kprintf("\n===== Custom Thread Dump Info =====\n"); - rt_kprintf("\nLED1 Count: %d\n", led1_cnt); - rt_kprintf("LED2 Count: %d\n", led2_cnt); - rt_kprintf("LED1 Suspend State: %s\n", th_led1_handle->stat); -} -MSH_CMD_EXPORT(thread_dump_cmd, dump custom thread info); -``` - -### 6 功能说明与核心要点 - -#### 6.1 线程参数校验 - -- **th_led1(动态)**:优先级16、时间片5ticks、栈大小1024Byte,同优先级与LED2线程时间片轮转调度 -- **th_led2(静态)**:全局静态栈+静态TCB,无堆内存分配,高可靠运行 -- **th_monitor(监控)**:优先级10,高于工作线程,可抢占工作线程,保证监控任务实时执行 - -#### 6.2 核心功能逻辑 - -- 双LED线程同优先级轮转运行,分别打印独立计数与系统节拍 -- 按键单次触发切换LED1线程挂起/恢复状态,线程状态实时生效 -- 监控线程2秒周期打印双线程状态、剩余栈空间,实现线程运行监控 -- 所有业务自动初始化,不占用main函数资源,符合RT-Thread开发规范 -- 自定义控制台命令,支持手动查询线程状态与运行计数 - -### 7 实验现象总结 - -#### 7.1串口数据分析 - -![image-20260818223455183](.\source\day02\image-20260818223455183.png) - -**可以看到当按键按下之前,led1任务和led2任务都在执行;按下后led1任务被挂起,之后就只有led2任务的输出** - - - -![image-20260818223700152](.\source\day02\image-20260818223700152.png) - -**可以看到当按键再次按下,led1任务被恢复,led1任务和led2任务都有输出** - - - -#### 7.2 实验结果总结 - -- 系统上电后,红蓝LED交替闪烁,串口持续打印两个线程的运行计数 -- 每2秒打印一次线程状态 -- 按下板载按键,LED1线程挂起,红灯停止闪烁;再次按下恢复运行 -- 控制台输入 `thread_dump`,可打印系统所有线程信息与自定义运行参数 - - - diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/day3/day03.md" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/day3/day03.md" deleted file mode 100644 index 5029ec0b51a23d40ae6377f6fd0a152e36f36159..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/day3/day03.md" +++ /dev/null @@ -1,559 +0,0 @@ - -# RT‑Thread 线程间同步与通信实验报告 -## 练习一:停车场车位管理 -### 设计说明 -1. 使用**计数信号量**,初始值3,代表3个车位。 -2. 创建5个车辆线程,每个线程`rt_sem_take`申请车位,2秒超时;超时打印等待,稍后重试。 -3. 获取成功打印进入;延时模拟停车,之后释放信号量离开。 -4. 信号量最大计数3,保证同时在场车辆不超过3。 -5. 全部线程结束后删除线程与信号量,防止重复运行异常。 - -### 代码 - -```c -#define PARK_CAR_NUM 5 -#define PARK_SPACE_NUM 3 -#define PARK_WAIT_TICK RT_TICK_PER_SECOND * 2 /* 2秒超时 */ - -static rt_sem_t g_park_sem = RT_NULL; -static rt_thread_t g_car_thread[PARK_CAR_NUM] = {RT_NULL}; - -static void car_thread_entry(void *parameter) -{ - rt_uint32_t car_id = (rt_uint32_t)parameter; - rt_err_t ret; - - while(1) - { - ret = rt_sem_take(g_park_sem, PARK_WAIT_TICK); - if(ret == RT_EOK) - { - rt_uint32_t used = PARK_SPACE_NUM - g_park_sem->value; - rt_kprintf("car%d entered, used spaces: %d\n", car_id, used); - - rt_thread_delay(5000); - - rt_kprintf("car%d left\n", car_id); - rt_sem_release(g_park_sem); - break; - } - else - { - rt_kprintf("car%d waiting for a space\n", car_id); - } - } - rt_thread_self()->user_data = (void *)0; -} - -static int parking_demo(int argc, char** argv) -{ - int i; - rt_err_t err; - - /* 创建计数信号量,初始3个车位 */ - g_park_sem = rt_sem_create("park_sem", PARK_SPACE_NUM, RT_IPC_FLAG_FIFO); - if(g_park_sem == RT_NULL) - { - rt_kprintf("create park sem failed!\n"); - return -RT_ENOMEM; - } - - for(i = 0; i < PARK_CAR_NUM; i++) - { - g_car_thread[i] = rt_thread_create("car", car_thread_entry, (void*)(i+1), - 1024, 10, 20); - if(g_car_thread[i] == RT_NULL) - { - rt_kprintf("create car%d thread failed\n", i+1); - goto parking_clean; - } - rt_thread_startup(g_car_thread[i]); - } - - return RT_EOK; - -parking_clean: - for(i = 0; i < PARK_CAR_NUM; i++) - { - if(g_car_thread[i] != RT_NULL) - { - rt_thread_delete(g_car_thread[i]); - g_car_thread[i] = RT_NULL; - } - } - rt_sem_delete(g_park_sem); - g_park_sem = RT_NULL; - return -RT_ERROR; -} -MSH_CMD_EXPORT(parking_demo, parking lot management demo); - -``` - -### 串口输出 - -```plain -msh >parking_demo -car1 entered, used spaces: 1 -car2 entered, used spaces: 2 -car3 entered, used spaces: 3 -car4 waiting for a space -car5 waiting for a space -car4 waiting for a space -car5 waiting for a space -car1 left -car4 entered, used spaces: 3 -car2 left -car5 entered, used spaces: 3 -car3 left -car4 left -car5 left -``` - -### 思考题:为什么使用计数信号量而不是互斥量? -> 互斥量用于**单一资源独占**,计数只能0/1,用于保护临界区,还具备所有权、优先级继承。 -> 停车场是**多实例有限资源(3个相同车位)**,允许多个线程同时获取资源,最大同时3个。 -> 计数信号量支持大于1的初值,可以允许多个线程同时获取资源,适合这类资源池场景。互斥量只能一个线程持有,无法实现3个车位同时占用。 - -## 练习二:多窗口售票系统 -### 设计说明 -1. 全局变量`g_ticket_count=20`,两个售票线程A、B。 -2. **互斥量保护完整售票流程:判余票、延时模拟业务、票数自减、输出票号**,整个作为临界区。 -3. 票数为0时线程退出。 -4. 对比实验:注释掉互斥take/release,在判断票数和自减之间加入延时,会出现:票号重复、剩余票数小于0、卖出总票数大于20。开启互斥后现象消失。 - -### 代码 - -```c -#define TICKET_TOTAL 20 -static rt_mutex_t g_ticket_mtx = RT_NULL; -static rt_int32_t g_ticket_count = TICKET_TOTAL; -static rt_thread_t g_winA_thread = RT_NULL; -static rt_thread_t g_winB_thread = RT_NULL; - -static void window_thread_entry(void *parameter) -{ - char *win_name = (char *)parameter; - - while(1) - { - rt_err_t ret = rt_mutex_take(g_ticket_mtx, RT_WAITING_FOREVER); - if(ret != RT_EOK) break; - - if(g_ticket_count <= 0) - { - rt_mutex_release(g_ticket_mtx); - break; - } - - /* 模拟业务耗时 */ - rt_thread_delay(2); - - rt_int32_t curr_ticket = g_ticket_count; - g_ticket_count --; - rt_kprintf("%s sold ticket %d\n", win_name, curr_ticket); - - rt_mutex_release(g_ticket_mtx); - - rt_thread_delay(1); - } - - rt_thread_self()->user_data = (void *)0; -} - -static int ticket_demo(int argc, char** argv) -{ - if(g_ticket_mtx != RT_NULL) - { - rt_kprintf("ticket demo already running!\n"); - return -RT_EBUSY; - } - - g_ticket_count = TICKET_TOTAL; - - g_ticket_mtx = rt_mutex_create("ticket_mtx", RT_IPC_FLAG_PRIO); - if(g_ticket_mtx == RT_NULL) - { - rt_kprintf("create ticket mutex failed\n"); - return -RT_ENOMEM; - } - - g_winA_thread = rt_thread_create("winA", window_thread_entry, "window A", - 1024, 10, 20); - g_winB_thread = rt_thread_create("winB", window_thread_entry, "window B", - 1024, 10, 20); - - if(g_winA_thread == RT_NULL || g_winB_thread == RT_NULL) - { - rt_kprintf("create window thread failed\n"); - rt_mutex_delete(g_ticket_mtx); - g_ticket_mtx = RT_NULL; - return -RT_ENOMEM; - } - - rt_thread_startup(g_winA_thread); - rt_thread_startup(g_winB_thread); - - /* 等待两个售票线程执行完毕 */ - while(g_winA_thread->stat != RT_THREAD_CLOSE || - g_winB_thread->stat != RT_THREAD_CLOSE) - { - rt_thread_delay(RT_TICK_PER_SECOND / 10); - } - - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: %d\n", g_ticket_count); - - rt_mutex_delete(g_ticket_mtx); - g_ticket_mtx = RT_NULL; - - return RT_EOK; -} -MSH_CMD_EXPORT(ticket_demo, multi window ticket sale demo); - -``` - -### 串口输出 - -```plain -msh >ticket_demo -window A sold ticket 20 -window B sold ticket 19 -window A sold ticket 18 -window B sold ticket 17 -window A sold ticket 16 -window B sold ticket 15 -window A sold ticket 14 -window B sold ticket 13 -window A sold ticket 12 -window B sold ticket 11 -window A sold ticket 10 -window B sold ticket 9 -window A sold ticket 8 -window B sold ticket 7 -window A sold ticket 6 -window B sold ticket 5 -window A sold ticket 4 -window B sold ticket 3 -window A sold ticket 2 -window B sold ticket 1 -all tickets sold -remaining tickets: 0 -``` - -### 思考题:为什么只给 ticket_count-- 这一行加锁仍然不安全? - -> 售票逻辑是:读取票数 → 判断是否>0 → 票数减1。 -> 如果只对`ticket_count--`加锁,**读取票数、判断票数的代码没有保护**。 -> 线程A读取票数=1,还没执行自减;线程B也读到票数=1;两个线程都判断有票,都执行减1。最终会卖出2张票,票数变成‑1,出现超卖。 -> 临界区必须把**读取‑判断‑修改这一整套业务逻辑全部包裹**,而不是只保护赋值语句。 - -## 练习三:系统启动条件检查 -### 设计说明 -1. 事件集三个bit位分别代表网络、传感器、存储模块就绪。 -2. 三个初始化线程,不同延时,发送对应事件位。存储模块延时5s。 -3. 主业务线程使用`RT_EVENT_FLAG_AND`等待**全部3个事件同时置位**,最大等待3s。 -4. 超时打印哪些模块未就绪,继续循环等待;全部就绪打印业务启动。 - -### 代码 - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define ALL_MODULE_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -static rt_event_t g_sys_event = RT_NULL; -static rt_thread_t g_th_net = RT_NULL; -static rt_thread_t g_th_sensor = RT_NULL; -static rt_thread_t g_th_storage = RT_NULL; -static rt_thread_t g_th_business = RT_NULL; - -static void net_init_thread(void *param) -{ - RT_UNUSED(param); - rt_thread_delay(RT_TICK_PER_SECOND * 1); - rt_event_send(g_sys_event, EVENT_NET_READY); - rt_kprintf("network ready\n"); -} - -static void sensor_init_thread(void *param) -{ - RT_UNUSED(param); - rt_thread_delay(RT_TICK_PER_SECOND * 2); - rt_event_send(g_sys_event, EVENT_SENSOR_READY); - rt_kprintf("sensor ready\n"); -} - -static void storage_init_thread(void *param) -{ - RT_UNUSED(param); - rt_thread_delay(RT_TICK_PER_SECOND * 5); - rt_event_send(g_sys_event, EVENT_STORAGE_READY); - rt_kprintf("storage ready\n"); -} - -static void business_thread(void *param) -{ - RT_UNUSED(param); - rt_uint32_t recv; - rt_err_t ret; - - while(1) - { - ret = rt_event_recv(g_sys_event, ALL_MODULE_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_TICK_PER_SECOND * 3, &recv); - if(ret == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - break; - } - else - { - rt_kprintf("wait modules timeout, "); - if( !(recv & EVENT_NET_READY) ) rt_kprintf("network "); - if( !(recv & EVENT_SENSOR_READY) ) rt_kprintf("sensor "); - if( !(recv & EVENT_STORAGE_READY) ) rt_kprintf("storage "); - rt_kprintf("is not ready\n"); - } - } -} - -static int startup_demo(int argc, char** argv) -{ - if(g_sys_event != RT_NULL) - { - rt_kprintf("startup demo already running!\n"); - return -RT_EBUSY; - } - - g_sys_event = rt_event_create("sys_event", RT_IPC_FLAG_FIFO); - if(g_sys_event == RT_NULL) - { - rt_kprintf("create sys event failed\n"); - return -RT_ENOMEM; - } - - g_th_net = rt_thread_create("net_init", net_init_thread, RT_NULL, 1024, 10, 20); - g_th_sensor = rt_thread_create("sen_init", sensor_init_thread, RT_NULL, 1024, 10, 20); - g_th_storage = rt_thread_create("sto_init", storage_init_thread, RT_NULL, 1024, 10, 20); - g_th_business = rt_thread_create("business", business_thread, RT_NULL, 1024, 10, 20); - - if(!g_th_net || !g_th_sensor || !g_th_storage || !g_th_business) - { - rt_kprintf("create startup thread fail\n"); - rt_event_delete(g_sys_event); - g_sys_event = RT_NULL; - return -RT_ENOMEM; - } - - rt_thread_startup(g_th_net); - rt_thread_startup(g_th_sensor); - rt_thread_startup(g_th_storage); - rt_thread_startup(g_th_business); - - /* 等待业务线程结束 */ - while(g_th_business->stat != RT_THREAD_CLOSE) - { - rt_thread_delay(RT_TICK_PER_SECOND/10); - } - - rt_event_delete(g_sys_event); - g_sys_event = RT_NULL; - - return RT_EOK; -} -MSH_CMD_EXPORT(startup_demo, system startup event check demo); -``` - -### 串口输出 - -```plain -msh >startup_demo -network ready -sensor ready -wait modules timeout, network sensor storage is not ready -storage ready -all modules are ready -business task started -``` - -### 思考题:用三个独立信号量实现对比事件集,有什么区别? - -1. 事件集:**一个对象管理多个条件位**,支持AND(全部就绪)/OR(任意就绪);可以一次性等待多个条件;事件位可以被清除;不需要每个条件维护一个IPC对象。 -2. 三个独立信号量:需要依次take三个信号量,才能代表全部就绪;无法原子等待“三者同时就绪”;take顺序会带来逻辑麻烦;每个条件占用一个信号量对象;无法方便获知哪一个条件未就绪。 -3. 事件集非常适合多模块初始化、多条件就绪这种场景;信号量适合资源计数。 - -## 练习四:快递分拣中心 -### 设计说明 -1. 消息队列容量5,消息结构体包含id、region、weight。 -2. 2个发送线程,每个生成5条快递,id不重复;队列满时发送失败计数累加,重试。 -3. 分拣线程阻塞接收消息,解析区域打印信息;统计总件数、各区域数量、总重量。 -4. 两个发送线程全部结束,发送2条结束标记消息;分拣线程收到两个结束标记,输出统计并退出。 -5. 全部完成释放队列与线程资源。 - -### 代码 - -```c -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; /* 1华东 2华南 3华北 */ - rt_uint16_t weight; -}; - -#define PACKAGE_QUEUE_LEN 5 -#define PER_SENDER_CNT 5 -#define END_MSG_ID 0xFFFFFFFFU - -static rt_mq_t g_package_mq = RT_NULL; -static rt_thread_t g_sender1 = RT_NULL; -static rt_thread_t g_sender2 = RT_NULL; -static rt_thread_t g_sorter = RT_NULL; - -static rt_uint32_t g_send_fail_cnt = 0; - -static void sender_thread_entry(void *param) -{ - rt_uint32_t base_id = (rt_uint32_t)param; - struct package_msg msg; - int i; - - for(i = 0; i < PER_SENDER_CNT; i++) - { - msg.id = base_id + i; - msg.region = 1 + rand() % 3; - msg.weight = 200 + rand() % 1500; - - rt_err_t ret = rt_mq_send(g_package_mq, &msg, sizeof(struct package_msg)); - if(ret != RT_EOK) - { - g_send_fail_cnt ++; - rt_thread_delay(10); - i --; /* 发送失败重试 */ - continue; - } - rt_thread_delay(10); - } -} - -static void sorter_thread_entry(void *param) -{ - RT_UNUSED(param); - struct package_msg msg; - rt_err_t ret; - - rt_uint32_t total = 0; - rt_uint32_t east_cnt = 0, south_cnt = 0, north_cnt = 0; - rt_uint32_t total_weight = 0; - int end_flag = 0; - int end_recv = 0; - - while(1) - { - ret = rt_mq_recv(g_package_mq, &msg, sizeof(struct package_msg), RT_WAITING_FOREVER); - if(ret != RT_EOK) continue; - - if(msg.id == END_MSG_ID) - { - end_recv ++; - if(end_recv >= 2) break; - continue; - } - - total ++; - total_weight += msg.weight; - const char *area; - switch(msg.region) - { - case 1: east_cnt++; area = "east"; break; - case 2: south_cnt++; area = "south"; break; - case 3: north_cnt++; area = "north"; break; - default: area = "unknown"; break; - } - rt_kprintf("package %d -> %s, weight: %d g\n", msg.id, area, msg.weight); - } - - rt_kprintf("total packages: %d\n", total); - rt_kprintf("east: %d, south: %d, north: %d\n", east_cnt, south_cnt, north_cnt); - rt_kprintf("total weight: %d g\n", total_weight); - rt_kprintf("send failed: %d\n", g_send_fail_cnt); -} - -static int package_demo(int argc, char** argv) -{ - if(g_package_mq != RT_NULL) - { - rt_kprintf("package demo already running!\n"); - return -RT_EBUSY; - } - g_send_fail_cnt = 0; - - g_package_mq = rt_mq_create("pkg_mq", sizeof(struct package_msg), PACKAGE_QUEUE_LEN, RT_IPC_FLAG_FIFO); - if(g_package_mq == RT_NULL) - { - rt_kprintf("create package mq failed\n"); - return -RT_ENOMEM; - } - - g_sender1 = rt_thread_create("send1", sender_thread_entry, (void*)1001, 1024, 10, 20); - g_sender2 = rt_thread_create("send2", sender_thread_entry, (void*)2001, 1024, 10, 20); - g_sorter = rt_thread_create("sorter", sorter_thread_entry, RT_NULL, 2048, 10, 20); - - if(!g_sender1 || !g_sender2 || !g_sorter) - { - rt_kprintf("create package thread failed\n"); - rt_mq_delete(g_package_mq); - g_package_mq = RT_NULL; - return -RT_ENOMEM; - } - - rt_thread_startup(g_sender1); - rt_thread_startup(g_sender2); - rt_thread_startup(g_sorter); - - /* 等待两个发送线程执行完毕 */ - while(g_sender1->stat != RT_THREAD_CLOSE || - g_sender2->stat != RT_THREAD_CLOSE) - { - rt_thread_delay(RT_TICK_PER_SECOND / 10); - } - - /* 两个发送线程完成,发送结束标记消息 */ - struct package_msg end_msg = {END_MSG_ID,0,0}; - rt_mq_send(g_package_mq, &end_msg, sizeof(end_msg)); - rt_mq_send(g_package_mq, &end_msg, sizeof(end_msg)); - - /* 等待分拣线程结束 */ - while(g_sorter->stat != RT_THREAD_CLOSE) - { - rt_thread_delay(RT_TICK_PER_SECOND / 10); - } - - rt_mq_delete(g_package_mq); - g_package_mq = RT_NULL; - return RT_EOK; -} -MSH_CMD_EXPORT(package_demo, express package sort demo); -``` - -### 串口输出 - -```plain -msh >package_demo -package 1001 -> east, weight: 960 g -package 2001 -> north, weight: 820 g -package 1002 -> south, weight: 1132 g -package 2002 -> south, weight: 313 g -package 1003 -> south, weight: 709 g -package 2003 -> north, weight: 605 g -package 1004 -> east, weight: 1249 g -package 2004 -> east, weight: 1419 g -package 1005 -> north, weight: 1024 g -package 2005 -> east, weight: 1349 g -total packages: 10 -east: 4, south: 3, north: 3 -total weight: 9580 g -send failed: 0 -``` diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/day3/main.c" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/day3/main.c" deleted file mode 100644 index 1e08f6475169e10d060ae4e11dcc7e867f060215..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/day3/main.c" +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 RT-Thread first version - */ - -#include -#include -#define DBG_TAG "main" -#define LOG_LVL LOG_LVL_DBG -#include -#include -#include - -/* ===================== 练习一:停车场车位管理 parking_demo ===================== */ -#define PARK_CAR_NUM 5 -#define PARK_SPACE_NUM 3 -#define PARK_WAIT_TICK RT_TICK_PER_SECOND * 2 /* 2秒超时 */ - -static rt_sem_t g_park_sem = RT_NULL; -static rt_thread_t g_car_thread[PARK_CAR_NUM] = {RT_NULL}; - -static void car_thread_entry(void *parameter) -{ - rt_uint32_t car_id = (rt_uint32_t)parameter; - rt_err_t ret; - - while(1) - { - ret = rt_sem_take(g_park_sem, PARK_WAIT_TICK); - if(ret == RT_EOK) - { - rt_uint32_t used = PARK_SPACE_NUM - g_park_sem->value; - rt_kprintf("car%d entered, used spaces: %d\n", car_id, used); - -// rt_tick_t stay_tick = RT_TICK_PER_SECOND / 2 + rt_rand() % RT_TICK_PER_SECOND; - rt_thread_delay(5000); - - rt_kprintf("car%d left\n", car_id); - rt_sem_release(g_park_sem); - break; - } - else - { - rt_kprintf("car%d waiting for a space\n", car_id); - } - } - rt_thread_self()->user_data = (void *)0; -} - -static int parking_demo(int argc, char** argv) -{ - int i; - rt_err_t err; - - /* 创建计数信号量,初始3个车位 */ - g_park_sem = rt_sem_create("park_sem", PARK_SPACE_NUM, RT_IPC_FLAG_FIFO); - if(g_park_sem == RT_NULL) - { - rt_kprintf("create park sem failed!\n"); - return -RT_ENOMEM; - } - - for(i = 0; i < PARK_CAR_NUM; i++) - { - g_car_thread[i] = rt_thread_create("car", car_thread_entry, (void*)(i+1), - 1024, 10, 20); - if(g_car_thread[i] == RT_NULL) - { - rt_kprintf("create car%d thread failed\n", i+1); - goto parking_clean; - } - rt_thread_startup(g_car_thread[i]); - } - - return RT_EOK; - -parking_clean: - for(i = 0; i < PARK_CAR_NUM; i++) - { - if(g_car_thread[i] != RT_NULL) - { - rt_thread_delete(g_car_thread[i]); - g_car_thread[i] = RT_NULL; - } - } - rt_sem_delete(g_park_sem); - g_park_sem = RT_NULL; - return -RT_ERROR; -} -MSH_CMD_EXPORT(parking_demo, parking lot management demo); - -/* ===================== 练习二:多窗口售票系统 ticket_demo ===================== */ -#define TICKET_TOTAL 20 -static rt_mutex_t g_ticket_mtx = RT_NULL; -static rt_int32_t g_ticket_count = TICKET_TOTAL; -static rt_thread_t g_winA_thread = RT_NULL; -static rt_thread_t g_winB_thread = RT_NULL; - -static void window_thread_entry(void *parameter) -{ - char *win_name = (char *)parameter; - - while(1) - { - rt_err_t ret = rt_mutex_take(g_ticket_mtx, RT_WAITING_FOREVER); - if(ret != RT_EOK) break; - - if(g_ticket_count <= 0) - { - rt_mutex_release(g_ticket_mtx); - break; - } - - /* 模拟业务耗时 */ - rt_thread_delay(2); - - rt_int32_t curr_ticket = g_ticket_count; - g_ticket_count --; - rt_kprintf("%s sold ticket %d\n", win_name, curr_ticket); - - rt_mutex_release(g_ticket_mtx); - - rt_thread_delay(1); - } - - rt_thread_self()->user_data = (void *)0; -} - -static int ticket_demo(int argc, char** argv) -{ - if(g_ticket_mtx != RT_NULL) - { - rt_kprintf("ticket demo already running!\n"); - return -RT_EBUSY; - } - - g_ticket_count = TICKET_TOTAL; - - g_ticket_mtx = rt_mutex_create("ticket_mtx", RT_IPC_FLAG_PRIO); - if(g_ticket_mtx == RT_NULL) - { - rt_kprintf("create ticket mutex failed\n"); - return -RT_ENOMEM; - } - - g_winA_thread = rt_thread_create("winA", window_thread_entry, "window A", - 1024, 10, 20); - g_winB_thread = rt_thread_create("winB", window_thread_entry, "window B", - 1024, 10, 20); - - if(g_winA_thread == RT_NULL || g_winB_thread == RT_NULL) - { - rt_kprintf("create window thread failed\n"); - rt_mutex_delete(g_ticket_mtx); - g_ticket_mtx = RT_NULL; - return -RT_ENOMEM; - } - - rt_thread_startup(g_winA_thread); - rt_thread_startup(g_winB_thread); - - /* 等待两个售票线程执行完毕 */ - while(g_winA_thread->stat != RT_THREAD_CLOSE || - g_winB_thread->stat != RT_THREAD_CLOSE) - { - rt_thread_delay(RT_TICK_PER_SECOND / 10); - } - - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: %d\n", g_ticket_count); - - rt_mutex_delete(g_ticket_mtx); - g_ticket_mtx = RT_NULL; - - return RT_EOK; -} -MSH_CMD_EXPORT(ticket_demo, multi window ticket sale demo); - -/* ===================== 练习三:系统启动条件检查 startup_demo ===================== */ -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define ALL_MODULE_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -static rt_event_t g_sys_event = RT_NULL; -static rt_thread_t g_th_net = RT_NULL; -static rt_thread_t g_th_sensor = RT_NULL; -static rt_thread_t g_th_storage = RT_NULL; -static rt_thread_t g_th_business = RT_NULL; - -static void net_init_thread(void *param) -{ - RT_UNUSED(param); - rt_thread_delay(RT_TICK_PER_SECOND * 1); - rt_event_send(g_sys_event, EVENT_NET_READY); - rt_kprintf("network ready\n"); -} - -static void sensor_init_thread(void *param) -{ - RT_UNUSED(param); - rt_thread_delay(RT_TICK_PER_SECOND * 2); - rt_event_send(g_sys_event, EVENT_SENSOR_READY); - rt_kprintf("sensor ready\n"); -} - -static void storage_init_thread(void *param) -{ - RT_UNUSED(param); - rt_thread_delay(RT_TICK_PER_SECOND * 5); - rt_event_send(g_sys_event, EVENT_STORAGE_READY); - rt_kprintf("storage ready\n"); -} - -static void business_thread(void *param) -{ - RT_UNUSED(param); - rt_uint32_t recv; - rt_err_t ret; - - while(1) - { - ret = rt_event_recv(g_sys_event, ALL_MODULE_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_TICK_PER_SECOND * 3, &recv); - if(ret == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - break; - } - else - { - rt_kprintf("wait modules timeout, "); - if( !(recv & EVENT_NET_READY) ) rt_kprintf("network "); - if( !(recv & EVENT_SENSOR_READY) ) rt_kprintf("sensor "); - if( !(recv & EVENT_STORAGE_READY) ) rt_kprintf("storage "); - rt_kprintf("is not ready\n"); - } - } -} - -static int startup_demo(int argc, char** argv) -{ - if(g_sys_event != RT_NULL) - { - rt_kprintf("startup demo already running!\n"); - return -RT_EBUSY; - } - - g_sys_event = rt_event_create("sys_event", RT_IPC_FLAG_FIFO); - if(g_sys_event == RT_NULL) - { - rt_kprintf("create sys event failed\n"); - return -RT_ENOMEM; - } - - g_th_net = rt_thread_create("net_init", net_init_thread, RT_NULL, 1024, 10, 20); - g_th_sensor = rt_thread_create("sen_init", sensor_init_thread, RT_NULL, 1024, 10, 20); - g_th_storage = rt_thread_create("sto_init", storage_init_thread, RT_NULL, 1024, 10, 20); - g_th_business = rt_thread_create("business", business_thread, RT_NULL, 1024, 10, 20); - - if(!g_th_net || !g_th_sensor || !g_th_storage || !g_th_business) - { - rt_kprintf("create startup thread fail\n"); - rt_event_delete(g_sys_event); - g_sys_event = RT_NULL; - return -RT_ENOMEM; - } - - rt_thread_startup(g_th_net); - rt_thread_startup(g_th_sensor); - rt_thread_startup(g_th_storage); - rt_thread_startup(g_th_business); - - /* 等待业务线程结束 */ - while(g_th_business->stat != RT_THREAD_CLOSE) - { - rt_thread_delay(RT_TICK_PER_SECOND/10); - } - - rt_event_delete(g_sys_event); - g_sys_event = RT_NULL; - - return RT_EOK; -} -MSH_CMD_EXPORT(startup_demo, system startup event check demo); - -/* ===================== 练习四:快递分拣中心 package_demo ===================== */ -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; /* 1华东 2华南 3华北 */ - rt_uint16_t weight; -}; - -#define PACKAGE_QUEUE_LEN 5 -#define PER_SENDER_CNT 5 -#define END_MSG_ID 0xFFFFFFFFU - -static rt_mq_t g_package_mq = RT_NULL; -static rt_thread_t g_sender1 = RT_NULL; -static rt_thread_t g_sender2 = RT_NULL; -static rt_thread_t g_sorter = RT_NULL; - -static rt_uint32_t g_send_fail_cnt = 0; - -static void sender_thread_entry(void *param) -{ - rt_uint32_t base_id = (rt_uint32_t)param; - struct package_msg msg; - int i; - - for(i = 0; i < PER_SENDER_CNT; i++) - { - msg.id = base_id + i; - msg.region = 1 + rand() % 3; - msg.weight = 200 + rand() % 1500; - - rt_err_t ret = rt_mq_send(g_package_mq, &msg, sizeof(struct package_msg)); - if(ret != RT_EOK) - { - g_send_fail_cnt ++; - rt_thread_delay(10); - i --; /* 发送失败重试 */ - continue; - } - rt_thread_delay(10); - } -} - -static void sorter_thread_entry(void *param) -{ - RT_UNUSED(param); - struct package_msg msg; - rt_err_t ret; - - rt_uint32_t total = 0; - rt_uint32_t east_cnt = 0, south_cnt = 0, north_cnt = 0; - rt_uint32_t total_weight = 0; - int end_flag = 0; - int end_recv = 0; - - while(1) - { - ret = rt_mq_recv(g_package_mq, &msg, sizeof(struct package_msg), RT_WAITING_FOREVER); - if(ret != RT_EOK) continue; - - if(msg.id == END_MSG_ID) - { - end_recv ++; - if(end_recv >= 2) break; - continue; - } - - total ++; - total_weight += msg.weight; - const char *area; - switch(msg.region) - { - case 1: east_cnt++; area = "east"; break; - case 2: south_cnt++; area = "south"; break; - case 3: north_cnt++; area = "north"; break; - default: area = "unknown"; break; - } - rt_kprintf("package %d -> %s, weight: %d g\n", msg.id, area, msg.weight); - } - - rt_kprintf("total packages: %d\n", total); - rt_kprintf("east: %d, south: %d, north: %d\n", east_cnt, south_cnt, north_cnt); - rt_kprintf("total weight: %d g\n", total_weight); - rt_kprintf("send failed: %d\n", g_send_fail_cnt); -} - -static int package_demo(int argc, char** argv) -{ - if(g_package_mq != RT_NULL) - { - rt_kprintf("package demo already running!\n"); - return -RT_EBUSY; - } - g_send_fail_cnt = 0; - - g_package_mq = rt_mq_create("pkg_mq", sizeof(struct package_msg), PACKAGE_QUEUE_LEN, RT_IPC_FLAG_FIFO); - if(g_package_mq == RT_NULL) - { - rt_kprintf("create package mq failed\n"); - return -RT_ENOMEM; - } - - g_sender1 = rt_thread_create("send1", sender_thread_entry, (void*)1001, 1024, 10, 20); - g_sender2 = rt_thread_create("send2", sender_thread_entry, (void*)2001, 1024, 10, 20); - g_sorter = rt_thread_create("sorter", sorter_thread_entry, RT_NULL, 2048, 10, 20); - - if(!g_sender1 || !g_sender2 || !g_sorter) - { - rt_kprintf("create package thread failed\n"); - rt_mq_delete(g_package_mq); - g_package_mq = RT_NULL; - return -RT_ENOMEM; - } - - rt_thread_startup(g_sender1); - rt_thread_startup(g_sender2); - rt_thread_startup(g_sorter); - - /* 等待两个发送线程执行完毕 */ - while(g_sender1->stat != RT_THREAD_CLOSE || - g_sender2->stat != RT_THREAD_CLOSE) - { - rt_thread_delay(RT_TICK_PER_SECOND / 10); - } - - /* 两个发送线程完成,发送结束标记消息 */ - struct package_msg end_msg = {END_MSG_ID,0,0}; - rt_mq_send(g_package_mq, &end_msg, sizeof(end_msg)); - rt_mq_send(g_package_mq, &end_msg, sizeof(end_msg)); - - /* 等待分拣线程结束 */ - while(g_sorter->stat != RT_THREAD_CLOSE) - { - rt_thread_delay(RT_TICK_PER_SECOND / 10); - } - - rt_mq_delete(g_package_mq); - g_package_mq = RT_NULL; - return RT_EOK; -} -MSH_CMD_EXPORT(package_demo, express package sort demo); - -int main(void) -{ - int count = 1; - while (count++) - { -// LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - - return RT_EOK; -} diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/desktop.ini" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/desktop.ini" deleted file mode 100644 index d957fd188dbc10a2a74cd31f9982e1cfcc0c4a28..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/desktop.ini" +++ /dev/null @@ -1,4 +0,0 @@ -[ViewState] -Mode= -Vid= -FolderType=Generic diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/source/day02/image-20260818223455183.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/source/day02/image-20260818223455183.png" deleted file mode 100644 index 523cfede6aedb5270ef34743cf318be2111f3abc..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/source/day02/image-20260818223455183.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/source/day02/image-20260818223700152.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/source/day02/image-20260818223700152.png" deleted file mode 100644 index fa775d04b5d11559a7a3a7cd0b9877204687860a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/source/day02/image-20260818223700152.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index bdcfaa09b971352e0b454526674471c1de1ff154..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-17 RT-Thread first version - */ - -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -#include - -/*************************引脚定义**************************/ -#define LED_Port GPIOF -#define LED1_Pin GPIO_PIN_11 -#define LED2_Pin GPIO_PIN_12 -//使用按键btn_down -#define Key_Port GPIOC -#define Key_Pin GPIO_PIN_1 - -/********************* 全局静态线程定义 *********************/ -// 静态线程2栈大小、TCB控制块定义 -#define TH_LED2_STACK_SIZE 2048 -rt_uint8_t th_led2_stack[TH_LED2_STACK_SIZE]; -struct rt_thread th_led2_tcb; - -/********************* 线程句柄定义 *********************/ -rt_thread_t th_led1_handle = RT_NULL; -rt_thread_t th_monitor_handle = RT_NULL; - -/********************* 计数变量 *********************/ -static rt_uint32_t led1_cnt = 0; -static rt_uint32_t led2_cnt = 0; -static rt_uint8_t key_flag = 0; -static rt_uint8_t task_flag = 0; - -void gpio_init(void) -{ - GPIO_InitTypeDef GPIO_InitStruct = {0}; - - // 使能GPIO时钟 - __HAL_RCC_GPIOF_CLK_ENABLE(); - __HAL_RCC_GPIOC_CLK_ENABLE(); - - // 初始化PF11、PF12为推挽输出(LED) - GPIO_InitStruct.Pin = LED1_Pin | LED2_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - HAL_GPIO_Init(LED_Port, &GPIO_InitStruct); - - HAL_GPIO_WritePin(LED_Port, LED1_Pin | LED2_Pin, GPIO_PIN_RESET); - - // 初始化PC1为上拉输入(按键) - GPIO_InitStruct.Pin = Key_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_PULLUP; - HAL_GPIO_Init(Key_Port, &GPIO_InitStruct); -} -INIT_BOARD_EXPORT(gpio_init); - -// LED 翻转 -void led_toggle(uint16_t GPIO_Pin) -{ - HAL_GPIO_TogglePin(LED_Port, GPIO_Pin); -} - -rt_bool_t key_scan(void) -{ - if ((HAL_GPIO_ReadPin(Key_Port, Key_Pin) == GPIO_PIN_RESET)) - { - rt_thread_mdelay(20); - if(HAL_GPIO_ReadPin(Key_Port, Key_Pin) == GPIO_PIN_RESET) - return RT_TRUE; - } - - return RT_FALSE; -} - -void th_led1_entry(void *parameter) -{ - while(1) - { - if(task_flag) { - rt_kprintf("Suspend th_led1 success!\n"); - rt_thread_suspend(th_led1_handle); - rt_schedule(); - } - - led_toggle(LED1_Pin); - led1_cnt++; - rt_kprintf("[LED1] Count: %d, SysTick: %d\n", led1_cnt, rt_tick_get()); - rt_thread_mdelay(500); - } -} - - -void th_led2_entry(void *parameter) -{ - while(1) - { - led_toggle(LED2_Pin); - led2_cnt++; - rt_kprintf("[LED2] Count: %d\n", led2_cnt); - rt_thread_mdelay(500); - } -} - -void th_monitor_entry(void *parameter) -{ - while(1) - { - // 按键单次触发:切换LED1线程挂起/恢复状态 - if(key_scan()) - { - if (key_flag) - { - task_flag = 0; - rt_thread_resume(th_led1_handle); - rt_kprintf("Resume th_led1 success!\n"); - } - else - task_flag = 1; - - key_flag = !key_flag; - } - - // 打印线程运行状态与剩余栈空间 - rt_kprintf("\n========== Thread Status Info ==========\n"); - rt_kprintf("[LED1 Thread] State: %d\n", th_led1_handle->stat); - - // 静态线程直接读取TCB - rt_kprintf("[LED2 Thread] State: %d\n", th_led2_tcb.stat); - rt_kprintf("========================================\n\n"); - - rt_thread_mdelay(2000); - } -} - -void thread_test_init(void) -{ - /***** 动态创建 LED1 线程 *****/ - th_led1_handle = rt_thread_create("th_led1",th_led1_entry,RT_NULL,2048,16,5); - if(th_led1_handle != RT_NULL) - { - rt_thread_startup(th_led1_handle); - rt_kprintf("[Init] th_led1 create success!\n"); - } - - /***** 静态初始化 LED2 线程 *****/ - rt_thread_init(&th_led2_tcb,"th_led2",th_led2_entry,RT_NULL,th_led2_stack,TH_LED2_STACK_SIZE,16,5); - rt_thread_startup(&th_led2_tcb); - rt_kprintf("[Init] th_led2 static init success!\n"); - - /***** 动态创建监控线程 *****/ - th_monitor_handle = rt_thread_create("th_monitor", - th_monitor_entry, - RT_NULL, - 2048, - 10, - 5); - if(th_monitor_handle != RT_NULL) - { - rt_thread_startup(th_monitor_handle); - rt_kprintf("[Init] th_monitor create success!\n"); - } -} -INIT_APP_EXPORT(thread_test_init); - -void thread_dump_cmd(void) -{ - rt_kprintf("\n===== Custom Thread Dump Info =====\n"); - rt_kprintf("\nLED1 Count: %d\n", led1_cnt); - rt_kprintf("LED2 Count: %d\n", led2_cnt); - rt_kprintf("LED1 Suspend State: %s\n", th_led1_handle->stat); -} -// 导出控制台命令 -MSH_CMD_EXPORT(thread_dump_cmd, dump custom thread info); - -int main(void) -{ - while (1) - { - rt_thread_mdelay(1000); - } - - return RT_EOK; -} diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817111304917.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817111304917.png" deleted file mode 100644 index 43bbc79946f6a713b86bf1d66f958b86a7c1184b..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817111304917.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817111632842.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817111632842.png" deleted file mode 100644 index 31f4617328cec94b2d57230ffc7faf7193ecbfd4..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817111632842.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817115330973.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817115330973.png" deleted file mode 100644 index b6f9829f0eb4e89e3e67893b304164d0ddb47cd8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817115330973.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817120500547.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817120500547.png" deleted file mode 100644 index c0dfab66fb5309add87c4f88059dd081db9d3a81..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817120500547.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817120630039.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817120630039.png" deleted file mode 100644 index 1bd35fe3441351e0bb077a3e245c3bb8611c2aef..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817120630039.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817200757618.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817200757618.png" deleted file mode 100644 index e91888be90453e6573bd34b94fa37eb7026267fb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817200757618.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817202523379.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817202523379.png" deleted file mode 100644 index d83b5058c6691e57b97f010438438a1312c68318..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817202523379.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817203845637.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817203845637.png" deleted file mode 100644 index 4c420fb9a1c62be58c7b858721d96628102bdd0a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817203845637.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817204505435.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817204505435.png" deleted file mode 100644 index 93aa1c9afe3fa4e37b69bf768b7ea641d5e8b9f2..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817204505435.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817204528180.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817204528180.png" deleted file mode 100644 index ff857ec7130465c7a4d12a20600a176bdac650c8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817204528180.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817204933439.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817204933439.png" deleted file mode 100644 index 5149a0c541433e47ccb287c1f7746a6f26b23dce..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817204933439.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817205702325.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817205702325.png" deleted file mode 100644 index c938fb66c5fb36c49441bdb8f58fca646b4bd3c8..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817205702325.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817210037911.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817210037911.png" deleted file mode 100644 index 485279f1e0bd8986c14a1dd97fdbb2968a62df74..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817210037911.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817210452856.png" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817210452856.png" deleted file mode 100644 index 175fc57bc9e884f8dde7b6356ac3acc068fdc401..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/source/day01/image-20260817210452856.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index fb0388809edcd3cd8de9159633873a161cfcebd3..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\350\265\265\345\211\215\346\260\221/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,167 +0,0 @@ -# Day01 - - - -## task1:新建一个qemu-a9工程,并运行RT-Thread - -​ **1. 创建项目** - -image-20260817111304917 - - - -**2. 编译后,下载,终端中显示运行信息** - -![image-20260817111632842](C:\Users\loong dragen\桌面\RTT学习\source\day01\image-20260817111632842.png) - - - -## task2:markdown语法学习 - -代码文本 -```c -#include - -int main(void) -{ - printf("Hello World\n"); - return 0; -} -``` - - - -表格 - -| item1 | item2 | item3 | -| :---: | :---: | :---: | -| 1 | 2 | 3 | - - - -`高亮语法` - -**加粗** - - - -## task3:基于RT-Thread 4.1.1版本新建stm32f407zg芯片工程 - -​ **1. 创建工程** - -image-20260817115330973 - -​ **2. 修改main函数代码** - -```c -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -int main(void) -{ - int count = 1; - - rt_kprintf("Hello World\n"); - - while (count++) - { -// LOG_D("Hello RT-Thread!"); - rt_thread_mdelay(1000); - } - - return RT_EOK; -} - -``` - - - -**3. 设置串口后编译,下载,终端中可见如下信息** - -![image-20260817120500547](C:\Users\loong dragen\桌面\RTT学习\source\day01\image-20260817120500547.png) - - - -**4. 可通过PS指令查看当前任务详情** - -![image-20260817120630039](C:\Users\loong dragen\桌面\RTT学习\source\day01\image-20260817120630039.png) - - - -## task4:git的使用 - -**1. 下载git和登录gitee后,先配置自己的全局身份,再配置一下公钥,方便后续上传代码** - -​ 全局身份的命令如下: - -```sh -git config --global user.name 'YourName' -git config --global user.email 'YourEmail' -git config --list //可查看是否修改成功 -``` - -image-20260817200757618 - -生成 RSA 密钥 - -```shell -ssh-keygen -t rsa -``` - -获取 RSA 公钥内容,并配置到ssh中 - -```shell -cat ~/.ssh/id_rsa.pub -``` - -![image-20260817202523379](C:\Users\loong dragen\桌面\RTT学习\source\day01\image-20260817202523379.png) - -**2. 接下来可以去拉取项目了,直接去克隆/下载处复制git命令即可** - -![image-20260817203845637](C:\Users\loong dragen\桌面\RTT学习\source\day01\image-20260817203845637.png) - - - - - -### Q:如何贡献代码在他人仓库? - -**1. 先把原作者的仓库fork一份在自己的仓库** - -![image-20260817204528180](C:\Users\loong dragen\桌面\RTT学习\source\day01\image-20260817204528180.png) - - - -**2. 然后拉取到自己的文件夹中,并且添加分支** - -对应的命令如下 - -```she -git branch "Name" //创建分支,Name为分支的名字 -git checkout "Name" //切换到对应分支中 -``` - -![image-20260817204933439](C:\Users\loong dragen\桌面\RTT学习\source\day01\image-20260817204933439.png) - - - -**3. 代码修改后,按如下命令提交** - -```she -git add . //添加修改 -git commit -m "xxx" //xxx是一下描述信息 -git push origin Name //Name是分支的名字 -``` - -![image-20260817210037911](C:\Users\loong dragen\桌面\RTT学习\source\day01\image-20260817210037911.png) - - - -**4. 提交代码** - -![image-20260817210452856](C:\Users\loong dragen\桌面\RTT学习\source\day01\image-20260817210452856.png) - -最后点击`创建Pull Request`,即可提交 diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 41eabc1d4f7734205855dbc40772eb645311ee1d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,300 +0,0 @@ -# RT-Thread 线程间同步与通信课后作业 - -## 一、作业要求 - -要求: - -1. 不得直接复制 `kernel_samples` 示例后只修改变量名和打印内容。 -2. 每个练习至少创建两个线程。 -3. 所有线程和 IPC 对象都要检查创建结果。 -4. 程序需要通过 MSH 命令启动。 - ---- - -## 练习一:停车场车位管理 - -### 场景 - -一个停车场只有 3 个车位,同时有 5 辆车尝试进入。车位属于有限资源,车辆只有获得车位后才能进入。 - -### 功能要求 - -1. 创建一个初始值为 3 的计数信号量,表示剩余车位。 -2. 创建 5 个车辆线程。 -3. 每辆车到达后申请一个车位。 -4. 获得车位后打印车辆编号和进入信息。 -5. 车辆停留一段时间后离开,并释放车位。 -6. 未在 2 秒内获得车位的车辆打印等待超时信息,稍后重新申请。 -7. 任意时刻进入停车场的车辆数不能超过 3。 - -### 示例输出 - -```text -car1 entered, used spaces: 1 -car2 entered, used spaces: 2 -car3 entered, used spaces: 3 -car4 waiting for a space -car2 left -car4 entered, used spaces: 3 -``` - -### 思考题 - -为什么这里使用计数信号量,而不是互斥量? - -**互斥量本质上是一个 0/1 的二值信号量**,只能表达"占用/空闲"两种状态,用于解决多个任务互斥访问**同一个**临界资源的问题:同一时刻最多允许一个任务进入临界区,并且它带有优先级继承机制,用于缓解优先级反转问题。 - -而停车场有 **3 个等价车位**,属于"数量大于 1 的可复用资源"。计数信号量的计数值恰好可以表达"剩余资源数量": - -- 创建时初值为 3,表示 3 个空车位; -- 每辆车进入时 `take` 一次,计数减 1;离开时 `release` 一次,计数加 1; -- 计数减到 0 后,后来的车辆自动挂起等待,**内核保证任意时刻场内车辆数不超过 3**。 - -如果改用互斥量: - -1. **并发度退化为 1**——互斥量只能放行一辆车,其余 4 辆车全部排队,3 个车位无法同时利用,浪费资源; -2. **语义不符**——互斥量要求"谁获取谁释放",通常用于同一任务进入/退出临界区;而车位的获取者(进场的车)与"释放后可进入的下一辆车"不是同一主体,这是典型的资源池模型,用计数信号量表达才自然。 - -**互斥量管"能不能进临界区",计数信号量管"还剩多少资源"**。资源只有 1 份时两者等价,资源多于 1 份时必须用计数信号量。 - ---- - -## 练习二:多窗口售票系统 - -### 场景 - -两个售票窗口同时出售同一批车票。系统初始有 20 张票,每次只能由一个窗口修改剩余票数。 - -### 功能要求 - -1. 创建两个售票线程,分别代表窗口 A 和窗口 B。 -2. 使用一个全局变量保存剩余票数。 -3. 每个窗口循环完成以下操作: - - 检查是否还有余票; - - 将余票减 1; - - 记录售出的票号; - - 打印售票结果。 -4. 使用互斥量保护完整的售票过程。 -5. 售完后两个线程自动退出。 -6. 最终必须满足: - - 20 张票全部售出; - - 不出现相同票号; - - 剩余票数不能小于 0。 - -### 示例输出 - -```text -window A sold ticket 20 -window B sold ticket 19 -window B sold ticket 18 -... -all tickets sold -remaining tickets: 0 -``` - -### 对比实验 - -先暂时不使用互斥量,并在“读取余票”和“修改余票”之间加入短暂延时,观察是否会出现重复售票或余票错误。 - -随后加入互斥量并再次运行,对比两次结果。 - -### 思考题 - -为什么只给 `ticket_count--` 加锁仍可能不安全? - -售票由三步组成: - -1. 读取 `remaining_tickets`,判断是否大于 0(还有没有票); -2. 将余票减 1; -3. 把售出的票号记录下来。 - -如果只给第 2 步(`ticket_count--`)加锁,第 1 步的判断和第 2 步的修改之间就存在竞态窗口: - -```text -窗口 A: 读取 remaining == 1 → 判断有余票 -窗口 B: 读取 remaining == 1 → 判断有余票 ← A 还没来得及减 1 -窗口 A: remaining-- → 卖出 1 号票 -窗口 B: remaining-- → 也卖出 1 号票 ← 重复售票! -``` - -两个窗口都认为"还剩 1 张票",各卖出一张、各减一次,结果: - -- 同一个票号被卖出两次(**重复售票**,违反"不出现相同票号"); -- 计数被减了两次,可能变成负数(**超卖**,违反"剩余票数不能小于 0")。 - -对比实验正是复现这一现象:去掉互斥量后,在"读取余票"与"修改余票"之间插入 1ms 延时,把竞态窗口拉大,两个窗口就会同时读到相同的余票值,卖出重复票号,最终 `data verification: FAILED!`。 - -因此**必须把"检查是否有票 → 减 1 → 记录票号"整个事务放进互斥量保护的临界区**,让复合操作成为原子操作,才能保证售票正确性。这也说明:互斥量保护的范围(临界区划分)比"哪一行加锁"更重要。 - ---- - -## 练习三:系统启动条件检查 - -### 场景 - -设备启动前需要完成三个独立模块的初始化: - -- 网络模块初始化; -- 传感器模块初始化; -- 存储模块初始化。 - -只有三个模块全部准备完成,主业务线程才能启动。 - -PS:只是举例,模拟即可。 - -### 功能要求 - -定义三个事件位: - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -``` - -创建以下线程: - -- 网络初始化线程; -- 传感器初始化线程; -- 存储初始化线程; -- 主业务线程。 - -每个初始化线程经过不同时间的延时后,发送对应的就绪事件。 - -主业务线程必须使用 AND 模式等待三个事件全部到达,然后打印: - -```text -all modules are ready -business task started -``` - -另外增加一次超时测试: - -- 将存储模块初始化时间设置为 5 秒; -- 主业务线程最多等待 3 秒; -- 超时后打印尚未完成的模块,并再次等待。 - -### 示例输出 - -```text -network ready -sensor ready -wait modules timeout, storage is not ready -storage ready -all modules are ready -business task started -``` - -### 思考题 - -如果用三个独立信号量实现这个功能,与使用事件集相比有什么区别? - -事件集(event set)是"状态型"的**,多个条件被编码到 32 个位中: - -1. **一次等待多个条件**:主业务线程一条 `rt_event_recv(AND)` 就能原子地等待"三个模块全部就绪",与模块就绪的先后顺序无关,代码简洁。 -2. **超时后状态不丢失**:超时返回时已就绪的事件位仍然保留(不带 CLEAR 标志时),可以继续等待或随时查询,天然适合"启动条件检查"这种场景。 -3. **支持多种组合**:AND(全部满足)、OR(任一满足)、CLEAR(取走即清除),还能广播给多个等待者,表达力强。 - -**信号量是"消耗型"的**,take 一次就少一次。若用三个独立信号量实现: - -1. **必须依次 take 三次**:如果先拿到了网络、传感器两个信号量,而存储信号量还没就绪,那么**已经拿到的两个信号量被本任务消耗掉了**,状态被破坏——下次重试时它们"不见了",必须小心翼翼地归还,逻辑复杂且容易出错。 -2. **难以判断"谁没就绪"**:超时后要定位是哪个模块没完成,需要额外的全局状态记录(如本实现中 `g_ready_state` 所做的事),事件集按位查询天然就能做到。 -3. **无法被多个任务共享观察**:一个任务取走信号量后,其他任务就感知不到"模块已就绪"这一事实;而事件集的状态可以同时被多个等待者看到。 - ---- - -## 练习四:快递分拣中心 - -### 场景 - -两个收件线程不断产生快递信息,一个分拣线程从消息队列中取出快递并按照目的地区域分类。 - -### 消息结构 - -```c -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -``` - -其中: - -```text -region = 1:华东 -region = 2:华南 -region = 3:华北 -``` - -### 功能要求 - -1. 创建一个消息队列,队列容量为 5 条消息。 -2. 创建两个发件线程,每个线程产生 5 条快递消息。 -3. 快递编号不得重复。 -4. 分拣线程接收消息,并打印快递编号、区域和重量。 -5. 分拣完成后统计: - - 总快递数量; - - 各区域快递数量; - - 所有快递的总重量。 -6. 当队列已满时,发送线程不得崩溃,应记录发送失败次数。 -7. 两个发送线程完成后,向分拣线程发送一条结束消息。 -8. 分拣线程收到结束消息后打印统计结果并退出。 - -### 示例输出 - -```text -package 1001 -> east, weight: 1200 g -package 2001 -> south, weight: 850 g -package 1002 -> north, weight: 960 g -... -total packages: 10 -east: 4, south: 3, north: 3 -total weight: 10250 g -send failed: 0 -``` - ---- - -## 二、MSH 命令要求 - -每个练习导出一个独立命令: - -```text -parking_demo -ticket_demo -startup_demo -package_demo -``` - -重复执行命令时,不得因同名线程或 IPC 对象残留而发生异常。可以在任务结束后主动释放资源,也可以检测任务是否仍在运行并拒绝重复启动。 - -练习一对应文件中 task1 工程,练习二对应文件中 task2 工程,练习三对应文件中 task3 工程,练习四对应文件中 task4 工程。根据要求修改对应工程代码,各练习问题的回答见下文"四、思考题解答"。 - ---- - -## 三、运行说明 - -| 练习 | 工程 | MSH 命令 | 实现要点 | -|------|------|----------|----------| -| 练习一 | task1 | `parking_demo` | 计数信号量(初值 3)+ 5 个车辆线程,2 秒超时后重试 | -| 练习二 | task2 | `ticket_demo` | 互斥量保护"检查 → 减 1 → 记录票号"完整事务 | -| 练习二对比实验 | task2 | `ticket_demo nomutex` | 不加互斥量,读取余票与修改余票之间插入 1ms 延时 | -| 练习三 | task3 | `startup_demo` | 事件集 AND 模式等待 3 个模块就绪,3 秒超时后打印未就绪模块并再次等待 | -| 练习四 | task4 | `package_demo` | 容量 5 的消息队列 + 2 个发件线程 + 1 个分拣线程,队列满时记录失败并重试 | - -### 重复执行保护 - -- 各练习均使用运行标志检测任务是否仍在运行,运行中再次执行命令会被拒绝(返回 EBUSY 并提示)。 -- 任务结束后主动删除 IPC 对象(信号量/互斥量/事件集/消息队列)。 -- 线程退出后由 RT-Thread 空闲线程自动回收线程对象与栈空间,不会因同名线程残留产生异常。 -- task1 在启动新练习前会额外检查并清理残留的车辆线程。 - -### 对比实验操作步骤(练习二) - -1. 输入 `ticket_demo nomutex`,观察输出:两个窗口会多次卖出**相同票号**(重复售票),总售票数与剩余票数对不上,`data verification: FAILED!`。 -2. 等待监控线程打印汇总后,再输入 `ticket_demo`,观察输出:20 张票全部售出、无重复票号、剩余票数为 0,`data verification: PASSED`。 -3. 对比两次结果即可得出结论:互斥量把"检查 → 减 1 → 记录"变成原子事务,是售票正确性的保证。 - -总结:**信号量适合"计数/互斥"类同步(如练习一、二),事件集适合"多条件状态组合"类同步(如练习三)**。要求三个条件同时满足且超时后要继续等待,事件集是更合适的工具。 \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task1.c" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task1.c" deleted file mode 100644 index bbad8432cac92e30ac4d8c6e3b284042ac8e6789..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task1.c" +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 RT-Thread first version - */ - -#include - -#define DBG_TAG "main" -#define DBG_LVL DBG_LOG -#include - -/* ============ 停车场配置 ============ */ -#define PARKING_SPACES 3 /* 车位总数 */ -#define CAR_COUNT 5 /* 车辆总数 */ -#define WAIT_TIMEOUT_MS 2000 /* 等待车位超时时间(ms) */ -#define PARKING_TIME_MS 3000 /* 每辆车停车时间(ms) */ -#define RETRY_INTERVAL_MS 500 /* 超时后重试间隔(ms) */ - -/* ============ 全局变量 ============ */ -static rt_sem_t parking_sem = RT_NULL; /* 车位计数信号量 */ -static volatile int exercise_running = 0; /* 练习运行标志 */ -static int total_entered = 0; /* 累计进入车辆数 */ -static int total_left = 0; /* 累计离开车辆数 */ - -/* ============ 车辆线程参数 ============ */ -typedef struct -{ - int car_id; /* 车辆编号 (1~5) */ - rt_sem_t sem; /* 车位信号量句柄 */ -} car_arg_t; - -/* - * 车辆线程入口 - * 每辆车尝试获取车位,成功后停车一段时间再释放 - * 超时则打印等待信息后重试 - */ -static void car_thread_entry(void *parameter) -{ - car_arg_t *arg = (car_arg_t *)parameter; - int car_id = arg->car_id; - rt_sem_t sem = arg->sem; - rt_err_t result; - char name[RT_NAME_MAX]; - - rt_snprintf(name, sizeof(name), "car%d", car_id); - LOG_I("%s arriving, looking for space...", name); - - while (1) - { - result = rt_sem_take(sem, rt_tick_from_millisecond(WAIT_TIMEOUT_MS)); - - if (result == RT_EOK) - { - rt_enter_critical(); - total_entered++; - int used = total_entered - total_left; - rt_exit_critical(); - - LOG_I("%s entered, used spaces: %d", name, used); - rt_thread_mdelay(PARKING_TIME_MS); - - result = rt_sem_release(sem); - if (result != RT_EOK) - LOG_E("%s sem_release failed! err=%d", name, result); - - rt_enter_critical(); - total_left++; - int all_done = (total_left >= CAR_COUNT); - rt_exit_critical(); - - LOG_I("%s left", name); - - if (all_done) - { - LOG_I("=== All %d cars processed. Exercise complete! ===", CAR_COUNT); - if (parking_sem != RT_NULL) - { - rt_sem_delete(parking_sem); - parking_sem = RT_NULL; - LOG_I("Semaphore released."); - } - exercise_running = 0; - } - - rt_free(arg); - return; - } - else if (result == -RT_ETIMEOUT) - { - LOG_I("%s waiting for a space...", name); - rt_thread_mdelay(RETRY_INTERVAL_MS); - } - else - { - LOG_E("%s sem_take error: %d", name, result); - rt_free(arg); - return; - } - } -} - -/* - * 检查并清理残留的练习资源 - */ -static int cleanup_residuals(void) -{ - int i; - char tname[RT_NAME_MAX]; - rt_thread_t tid; - - for (i = 1; i <= CAR_COUNT; i++) - { - rt_snprintf(tname, sizeof(tname), "car%d", i); - tid = rt_thread_find(tname); - if (tid != RT_NULL) - { - LOG_W("Found residual thread '%s', deleting...", tname); - rt_thread_delete(tid); - } - } - - if (parking_sem != RT_NULL) - { - LOG_W("Found residual semaphore, deleting..."); - rt_sem_delete(parking_sem); - parking_sem = RT_NULL; - } - - total_entered = 0; - total_left = 0; - return RT_EOK; -} - -/* - * 停车场练习主函数 - MSH命令: parking_demo - */ -static int parking_demo(int argc, char *argv[]) -{ - int i, j; - rt_thread_t thread; - car_arg_t *arg; - char tname[RT_NAME_MAX]; - - if (exercise_running) - { - LOG_W("Parking exercise already running! Please wait."); - return RT_EBUSY; - } - - /* 先置运行标志,避免车辆线程先于标志完成 */ - exercise_running = 1; - - cleanup_residuals(); - - parking_sem = rt_sem_create("parking", PARKING_SPACES, RT_IPC_FLAG_FIFO); - if (parking_sem == RT_NULL) - { - LOG_E("Failed to create parking semaphore!"); - return RT_ERROR; - } - LOG_I("Created semaphore: %d spaces available", PARKING_SPACES); - - for (i = 1; i <= CAR_COUNT; i++) - { - arg = (car_arg_t *)rt_malloc(sizeof(car_arg_t)); - if (arg == RT_NULL) - { - LOG_E("No memory for car %d!", i); - for (j = 1; j < i; j++) - { - rt_snprintf(tname, sizeof(tname), "car%d", j); - thread = rt_thread_find(tname); - if (thread) rt_thread_delete(thread); - } - rt_sem_delete(parking_sem); - parking_sem = RT_NULL; - return RT_ERROR; - } - arg->car_id = i; - arg->sem = parking_sem; - - rt_snprintf(tname, sizeof(tname), "car%d", i); - thread = rt_thread_create(tname, car_thread_entry, arg, 512, 15, 10); - if (thread == RT_NULL) - { - LOG_E("Failed to create thread for car%d!", i); - rt_free(arg); - for (j = 1; j < i; j++) - { - rt_snprintf(tname, sizeof(tname), "car%d", j); - thread = rt_thread_find(tname); - if (thread) rt_thread_delete(thread); - } - rt_sem_delete(parking_sem); - parking_sem = RT_NULL; - return RT_ERROR; - } - - if (rt_thread_startup(thread) != RT_EOK) - { - LOG_E("Failed to start thread for car%d!", i); - rt_thread_delete(thread); - rt_free(arg); - for (j = 1; j < i; j++) - { - rt_snprintf(tname, sizeof(tname), "car%d", j); - thread = rt_thread_find(tname); - if (thread) rt_thread_delete(thread); - } - rt_sem_delete(parking_sem); - parking_sem = RT_NULL; - return RT_ERROR; - } - LOG_I("Car %d ready ('%s' started)", i, tname); - } - - LOG_I("All %d cars competing for %d spaces...", CAR_COUNT, PARKING_SPACES); - return RT_EOK; -} - -MSH_CMD_EXPORT(parking_demo, Parking lot semaphore exercise); - -int main(void) -{ - LOG_I("Parking Lot Exercise - type 'parking_demo' in MSH"); - return RT_EOK; -} diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task2.c" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task2.c" deleted file mode 100644 index bf66bd74aba8d6f5938b74e9af3973dd59662d17..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task2.c" +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 Student Multi-window ticket sale system - * - * 练习二:多窗口售票系统 - * - * MSH 命令: - * ticket_demo 使用互斥量保护售票过程(正常模式) - * ticket_demo nomutex 对比实验:不加互斥量,且在读取余票与修改余票 - * 之间插入延时,观察重复售票 / 余票统计错误 - */ - -#include - -#define DBG_TAG "ticket" -#define DBG_LVL DBG_LOG -#include - -/* ============ 售票系统配置 ============ */ -#define TOTAL_TICKETS 20 /* 总票数 */ -#define THREAD_PRIORITY 15 /* 线程优先级 */ -#define THREAD_STACK_SIZE 512 /* 线程栈大小 */ -#define TICKET_INTERVAL_MS 20 /* 售票间隔(ms) */ -#define RACE_DELAY_MS 1 /* 无互斥量模式下"读-改"之间的延时(ms) */ - -/* 售出记录数组容量:正常模式最多 20 条;无互斥量模式可能出现重复售票, - * 记录条数可能超过总票数,容量翻倍并做越界保护 */ -#define MAX_SOLD_RECORDS (TOTAL_TICKETS * 2) - -/* ============ 事件位定义 ============ */ -#define EVENT_WIN_A_DONE (1U << 0) -#define EVENT_WIN_B_DONE (1U << 1) -#define EVENT_ALL_DONE (EVENT_WIN_A_DONE | EVENT_WIN_B_DONE) - -/* ============ 全局变量 ============ */ -static rt_mutex_t g_mutex = RT_NULL; /* 互斥量,保护完整售票过程 */ -static rt_event_t g_event = RT_NULL; /* 事件集,同步两窗口完成 */ -static volatile int g_running = 0; /* 售票系统运行标志 */ -static volatile int g_with_mutex = 1; /* 是否使用互斥量(1: 是, 0: 对比实验) */ -static volatile int g_remaining = 0; /* 剩余票数 */ -static volatile int g_sold[MAX_SOLD_RECORDS]; /* 售出票号记录 */ -static volatile int g_sold_count = 0; /* 已售票数 */ - -/* ============ 售票窗口线程参数 ============ */ -typedef struct -{ - const char *name; /* 窗口名 */ - rt_uint32_t done_flag; /* 本窗口完成事件位 */ -} window_arg_t; - -static window_arg_t g_window_a = { "window A", EVENT_WIN_A_DONE }; -static window_arg_t g_window_b = { "window B", EVENT_WIN_B_DONE }; - -/* - * 售票窗口线程入口 - * - * 使用互斥量模式:互斥量保护"检查余票 -> 减1 -> 记录票号"完整事务, - * 保证不重复售票、余票数不会小于 0。 - * - * 对比实验模式(无互斥量):在读取余票与修改余票之间插入人为延时, - * 放大竞态窗口,复现重复售票与余票统计错误。 - */ -static void window_entry(void *parameter) -{ - window_arg_t *arg = (window_arg_t *)parameter; - - if (g_with_mutex) - { - /* ---- 正常模式:互斥量保护完整售票过程 ---- */ - while (1) - { - /* 获取互斥量,保护完整售票事务 */ - if (rt_mutex_take(g_mutex, RT_WAITING_FOREVER) != RT_EOK) - { - LOG_E("%s: failed to take mutex", arg->name); - break; - } - - /* 临界区开始:检查 -> 减1 -> 记录 */ - if (g_remaining > 0) - { - int ticket_no; - - g_remaining--; /* 将余票减 1 */ - ticket_no = g_remaining + 1; /* 本次售出的票号 */ - - g_sold[g_sold_count] = ticket_no; /* 记录售出的票号 */ - g_sold_count++; - - /* 临界区结束 */ - rt_mutex_release(g_mutex); - - /* 打印售票结果(打印在临界区外) */ - rt_kprintf("%s sold ticket %d\n", arg->name, ticket_no); - - rt_thread_mdelay(TICKET_INTERVAL_MS); - } - else - { - /* 没有余票,释放互斥量并退出 */ - rt_mutex_release(g_mutex); - break; - } - } - } - else - { - /* ---- 对比实验:无互斥量,读-改之间插入延时 ---- */ - while (1) - { - int cur = g_remaining; /* 1. 读取余票 */ - - if (cur > 0) - { - /* 2. 人为延时,放大竞态窗口: - * 两个窗口可能同时读到相同的余票值 */ - rt_thread_mdelay(RACE_DELAY_MS); - - g_remaining = cur - 1; /* 3. 修改余票 */ - - /* 4. 记录票号(带越界保护,防止实验模式写坏内存) */ - if (g_sold_count < MAX_SOLD_RECORDS) - { - g_sold[g_sold_count] = cur; - g_sold_count++; - } - - rt_kprintf("%s sold ticket %d\n", arg->name, cur); - - rt_thread_mdelay(TICKET_INTERVAL_MS); - } - else - { - /* 读不到余票,退出 */ - break; - } - } - } - - /* 通知监控线程:本窗口售完 */ - rt_event_send(g_event, arg->done_flag); -} - -/* - * 打印售票结果汇总,并校验数据一致性 - */ -static void print_summary(void) -{ - int i, j; - int dup = 0; - - rt_kprintf("\nall tickets sold\n"); - rt_kprintf("remaining tickets: %d\n", g_remaining); - rt_kprintf("total sold: %d\n", g_sold_count); - - /* 校验:不出现相同票号 */ - for (i = 0; i < g_sold_count; i++) - { - for (j = i + 1; j < g_sold_count; j++) - { - if (g_sold[i] == g_sold[j]) - { - dup++; - break; - } - } - } - - if (dup > 0) - { - rt_kprintf("duplicate tickets: %d\n", dup); - rt_kprintf("data verification: FAILED!\n"); - } - else if (g_remaining == 0 && g_sold_count == TOTAL_TICKETS) - { - rt_kprintf("no duplicate tickets\n"); - rt_kprintf("data verification: PASSED\n"); - } - else - { - rt_kprintf("data verification: FAILED!\n"); - } -} - -/* - * 监控线程:等待两个窗口全部售完,打印结果并清理资源 - */ -static void monitor_entry(void *parameter) -{ - rt_uint32_t recved = 0; - (void)parameter; - - /* AND 模式等待两个窗口的完成事件(CLEAR 防止残留) */ - if (g_event != RT_NULL) - { - rt_event_recv(g_event, EVENT_ALL_DONE, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_WAITING_FOREVER, &recved); - } - - print_summary(); - - /* 售票结束,释放 IPC 资源 */ - if (g_mutex != RT_NULL) - { - rt_mutex_delete(g_mutex); - g_mutex = RT_NULL; - } - if (g_event != RT_NULL) - { - rt_event_delete(g_event); - g_event = RT_NULL; - } - g_running = 0; -} - -/* - * 初始化售票系统状态 - */ -static void ticket_init_state(void) -{ - int i; - - g_remaining = TOTAL_TICKETS; - g_sold_count = 0; - for (i = 0; i < MAX_SOLD_RECORDS; i++) - { - g_sold[i] = 0; - } -} - -/* - * MSH 命令:ticket_demo [nomutex] - */ -static int ticket_demo(int argc, char *argv[]) -{ - rt_thread_t tid_a = RT_NULL; - rt_thread_t tid_b = RT_NULL; - rt_thread_t tid_mon = RT_NULL; - rt_err_t result; - - /* 可选参数 nomutex:对比实验模式 */ - if (argc >= 2 && rt_strcmp(argv[1], "nomutex") == 0) - g_with_mutex = 0; - else - g_with_mutex = 1; - - /* 已在运行则拒绝重复启动 */ - if (g_running) - { - rt_kprintf("ticket demo is already running, please wait...\n"); - return RT_EBUSY; - } - - ticket_init_state(); - - /* 创建互斥量(对比实验模式不需要) */ - if (g_with_mutex) - { - g_mutex = rt_mutex_create("tick_mx", RT_IPC_FLAG_PRIO); - if (g_mutex == RT_NULL) - { - LOG_E("Failed to create mutex"); - rt_kprintf("Error: failed to create mutex!\n"); - return RT_ERROR; - } - } - - /* 创建事件集,用于同步两个窗口完成 */ - g_event = rt_event_create("tick_ev", RT_IPC_FLAG_PRIO); - if (g_event == RT_NULL) - { - LOG_E("Failed to create event"); - rt_kprintf("Error: failed to create event!\n"); - if (g_mutex != RT_NULL) - { - rt_mutex_delete(g_mutex); - g_mutex = RT_NULL; - } - return RT_ERROR; - } - - /* 创建窗口 A / B 线程 */ - tid_a = rt_thread_create("win_a", window_entry, &g_window_a, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid_a == RT_NULL) - { - rt_kprintf("Error: failed to create window A thread!\n"); - goto cleanup_fail; - } - tid_b = rt_thread_create("win_b", window_entry, &g_window_b, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid_b == RT_NULL) - { - rt_kprintf("Error: failed to create window B thread!\n"); - goto cleanup_fail; - } - - /* 创建监控线程 */ - tid_mon = rt_thread_create("tick_mon", monitor_entry, RT_NULL, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid_mon == RT_NULL) - { - rt_kprintf("Error: failed to create monitor thread!\n"); - goto cleanup_fail; - } - - /* 启动线程 */ - result = rt_thread_startup(tid_a); - if (result != RT_EOK) - { - rt_kprintf("Error: failed to start window A thread!\n"); - goto cleanup_fail; - } - result = rt_thread_startup(tid_b); - if (result != RT_EOK) - { - rt_kprintf("Error: failed to start window B thread!\n"); - goto cleanup_fail; - } - result = rt_thread_startup(tid_mon); - if (result != RT_EOK) - { - rt_kprintf("Error: failed to start monitor thread!\n"); - goto cleanup_fail; - } - - g_running = 1; - - if (g_with_mutex) - { - rt_kprintf("ticket demo started: %d tickets, windows A/B selling (with mutex)\n", - TOTAL_TICKETS); - } - else - { - rt_kprintf("ticket demo started: %d tickets, windows A/B selling (NO mutex, " - "delay between read and write)\n", TOTAL_TICKETS); - rt_kprintf("comparison experiment: expect duplicate tickets or wrong count\n"); - } - return RT_EOK; - -cleanup_fail: - /* 创建/启动失败:清理已创建的对象。 - * 未启动的线程处于 INIT 状态,rt_thread_delete 会将其交给空闲线程回收; - * 已退出的线程为 CLOSE 状态,rt_thread_delete 为空操作,可安全重复调用。 */ - if (tid_a != RT_NULL) - rt_thread_delete(tid_a); - if (tid_b != RT_NULL) - rt_thread_delete(tid_b); - if (tid_mon != RT_NULL) - rt_thread_delete(tid_mon); - if (g_mutex != RT_NULL) - { - rt_mutex_delete(g_mutex); - g_mutex = RT_NULL; - } - if (g_event != RT_NULL) - { - rt_event_delete(g_event); - g_event = RT_NULL; - } - return RT_ERROR; -} -MSH_CMD_EXPORT(ticket_demo, ticket demo: ticket_demo [nomutex]); - -/* - * 主函数 - */ -int main(void) -{ - rt_kprintf("Ticket sale system ready.\n"); - rt_kprintf("Commands: ticket_demo (with mutex), ticket_demo nomutex (comparison)\n"); - return RT_EOK; -} diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task3.c" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task3.c" deleted file mode 100644 index 4c4b725fac2c1d230796ff977533388ac9c2f5fd..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task3.c" +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 Student System startup condition check - * - * 练习三:系统启动条件检查(事件集) - * - * 设备启动前需要完成三个独立模块的初始化: - * 网络模块、传感器模块、存储模块。 - * 只有三个模块全部就绪,主业务线程才能启动。 - * - * MSH 命令: startup_demo - */ - -#include - -#define DBG_TAG "startup" -#define DBG_LVL DBG_LOG -#include - -/* ============ 事件位定义 ============ */ -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -/* ============ 启动时序配置 ============ */ -#define NET_INIT_TIME_MS 1000 /* 网络模块初始化耗时 */ -#define SENSOR_INIT_TIME_MS 2000 /* 传感器模块初始化耗时 */ -#define STORAGE_INIT_TIME_MS 5000 /* 存储模块初始化耗时(超过等待超时) */ -#define WAIT_TIMEOUT_MS 3000 /* 主业务线程等待超时 */ - -#define THREAD_PRIORITY 16 -#define THREAD_STACK_SIZE 512 - -/* ============ 全局变量 ============ */ -static rt_event_t g_startup_event = RT_NULL; /* 启动事件集 */ -static volatile rt_uint32_t g_ready_state = 0; /* 模块就绪状态(供超时打印使用) */ -static volatile int g_running = 0; /* 演示运行标志 */ - -/* ============ 模块初始化线程参数 ============ */ -typedef struct -{ - const char *name; /* 模块名 */ - rt_uint32_t event_bit; /* 对应的就绪事件位 */ - rt_int32_t init_time_ms; /* 模拟初始化耗时 */ -} module_arg_t; - -static module_arg_t g_net_arg = { "network", EVENT_NET_READY, NET_INIT_TIME_MS }; -static module_arg_t g_sensor_arg = { "sensor", EVENT_SENSOR_READY, SENSOR_INIT_TIME_MS }; -static module_arg_t g_storage_arg = { "storage", EVENT_STORAGE_READY, STORAGE_INIT_TIME_MS }; - -/* - * 模块初始化线程入口 - * 延时模拟初始化过程,完成后发送就绪事件并记录就绪状态 - */ -static void module_init_entry(void *parameter) -{ - module_arg_t *arg = (module_arg_t *)parameter; - - /* 模拟初始化耗时 */ - rt_thread_mdelay(arg->init_time_ms); - - /* 发送就绪事件 */ - rt_event_send(g_startup_event, arg->event_bit); - - /* 记录就绪状态(供主业务线程超时时打印未就绪模块) */ - rt_enter_critical(); - g_ready_state |= arg->event_bit; - rt_exit_critical(); - - rt_kprintf("%s ready\n", arg->name); -} - -/* - * 打印尚未就绪的模块 - */ -static void print_missing_modules(void) -{ - rt_base_t level; - rt_uint32_t state; - int first = 1; - - /* 关中断读取共享状态,保证读取一致性 */ - level = rt_hw_interrupt_disable(); - state = g_ready_state; - rt_hw_interrupt_enable(level); - - if (!(state & EVENT_NET_READY)) - { - rt_kprintf("network is not ready"); - first = 0; - } - if (!(state & EVENT_SENSOR_READY)) - { - rt_kprintf("%ssensor is not ready", first ? "" : ", "); - first = 0; - } - if (!(state & EVENT_STORAGE_READY)) - { - rt_kprintf("%sstorage is not ready", first ? "" : ", "); - } -} - -/* - * 主业务线程入口 - * AND 模式等待三个模块全部就绪;超时则打印未就绪模块并再次等待 - */ -static void business_entry(void *parameter) -{ - rt_err_t result; - rt_uint32_t recved = 0; - (void)parameter; - - while (1) - { - /* AND 模式:等待三个事件位全部置位,最多等 3 秒。 - * 超时不带 CLEAR,已就绪的事件位不会被清除,可直接再次等待。 */ - result = rt_event_recv(g_startup_event, EVENT_ALL_READY, - RT_EVENT_FLAG_AND, - rt_tick_from_millisecond(WAIT_TIMEOUT_MS), - &recved); - if (result == RT_EOK) - { - break; - } - - /* 超时:打印尚未完成的模块,然后继续等待 */ - rt_kprintf("wait modules timeout, "); - print_missing_modules(); - rt_kprintf("\n"); - } - - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - - /* 演示结束:释放事件集并复位运行标志 */ - rt_event_delete(g_startup_event); - g_startup_event = RT_NULL; - g_running = 0; -} - -/* - * MSH 命令:startup_demo - */ -static int startup_demo(int argc, char *argv[]) -{ - rt_thread_t tid_net = RT_NULL; - rt_thread_t tid_sensor = RT_NULL; - rt_thread_t tid_storage = RT_NULL; - rt_thread_t tid_business = RT_NULL; - - /* 已在运行则拒绝重复启动 */ - if (g_running) - { - rt_kprintf("startup demo is already running, please wait...\n"); - return RT_EBUSY; - } - - /* 创建事件集 */ - g_startup_event = rt_event_create("start_ev", RT_IPC_FLAG_PRIO); - if (g_startup_event == RT_NULL) - { - rt_kprintf("Error: failed to create event!\n"); - return RT_ERROR; - } - - g_ready_state = 0; - - /* 创建三个模块初始化线程 */ - tid_net = rt_thread_create("net_init", module_init_entry, &g_net_arg, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid_net == RT_NULL) - { - rt_kprintf("Error: failed to create network init thread!\n"); - goto cleanup_fail; - } - tid_sensor = rt_thread_create("sen_init", module_init_entry, &g_sensor_arg, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid_sensor == RT_NULL) - { - rt_kprintf("Error: failed to create sensor init thread!\n"); - goto cleanup_fail; - } - tid_storage = rt_thread_create("sto_init", module_init_entry, &g_storage_arg, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid_storage == RT_NULL) - { - rt_kprintf("Error: failed to create storage init thread!\n"); - goto cleanup_fail; - } - - /* 创建主业务线程 */ - tid_business = rt_thread_create("business", business_entry, RT_NULL, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid_business == RT_NULL) - { - rt_kprintf("Error: failed to create business thread!\n"); - goto cleanup_fail; - } - - /* 启动所有线程 */ - if (rt_thread_startup(tid_net) != RT_EOK || - rt_thread_startup(tid_sensor) != RT_EOK || - rt_thread_startup(tid_storage) != RT_EOK || - rt_thread_startup(tid_business) != RT_EOK) - { - rt_kprintf("Error: failed to start threads!\n"); - goto cleanup_fail; - } - - g_running = 1; - rt_kprintf("startup demo: waiting for all modules ready (timeout %d ms)\n", - WAIT_TIMEOUT_MS); - return RT_EOK; - -cleanup_fail: - /* 创建/启动失败:清理已创建的对象。 - * 已退出的线程为 CLOSE 状态,rt_thread_delete 为空操作,可安全重复调用。 */ - if (tid_net != RT_NULL) - rt_thread_delete(tid_net); - if (tid_sensor != RT_NULL) - rt_thread_delete(tid_sensor); - if (tid_storage != RT_NULL) - rt_thread_delete(tid_storage); - if (tid_business != RT_NULL) - rt_thread_delete(tid_business); - if (g_startup_event != RT_NULL) - { - rt_event_delete(g_startup_event); - g_startup_event = RT_NULL; - } - return RT_ERROR; -} -MSH_CMD_EXPORT(startup_demo, system startup condition check demo); - -/* - * 主函数 - */ -int main(void) -{ - rt_kprintf("System startup check demo ready.\n"); - rt_kprintf("Command: startup_demo\n"); - return RT_EOK; -} diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task4.c" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task4.c" deleted file mode 100644 index 32e4467c68667134fbb3efd907f4adf216997ced..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/task4.c" +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 Student Package sorting center - * - * 练习四:快递分拣中心(消息队列) - * - * 两个收件线程不断产生快递信息,一个分拣线程从消息队列中取出快递 - * 并按目的地区域分类统计。 - * - * MSH 命令: package_demo - */ - -#include - -#define DBG_TAG "package" -#define DBG_LVL DBG_LOG -#include - -/* ============ 快递消息结构 ============ */ -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -/* ============ 分拣中心配置 ============ */ -#define MQ_CAPACITY 5 /* 消息队列容量(条消息) */ -#define PKG_PER_SENDER 5 /* 每个发件线程产生的快递数 */ -#define SENDER_COUNT 2 /* 发件线程数量 */ -#define REGION_END 0 /* 结束消息标志: region = 0 */ -#define SEND_TIMEOUT_MS 10 /* 队列满时发送等待超时(ms) */ -#define SORT_DELAY_MS 50 /* 模拟单件分拣耗时(ms),使队列容易满 */ - -#define THREAD_PRIORITY 15 -#define THREAD_STACK_SIZE 512 - -/* ============ 全局变量 ============ */ -static rt_mq_t g_pkg_mq = RT_NULL; /* 快递消息队列 */ -static volatile int g_running = 0; /* 演示运行标志 */ -static volatile int g_send_failed = 0; /* 发送失败次数(队列满) */ - -/* ============ 发件线程参数 ============ */ -typedef struct -{ - rt_uint32_t base_id; /* 快递编号基准 */ - const rt_uint8_t *regions; /* 各快递目的区域表 */ - const rt_uint16_t *weights; /* 各快递重量表 */ -} sender_arg_t; - -/* 发件线程 A:编号 1001~1005 */ -static const rt_uint8_t snd1_regions[PKG_PER_SENDER] = {1, 2, 1, 3, 1}; -static const rt_uint16_t snd1_weights[PKG_PER_SENDER] = {1200, 850, 960, 1100, 700}; -static sender_arg_t g_sender_a = {1001, snd1_regions, snd1_weights}; - -/* 发件线程 B:编号 2001~2005,与 A 不重复 */ -static const rt_uint8_t snd2_regions[PKG_PER_SENDER] = {2, 3, 3, 1, 2}; -static const rt_uint16_t snd2_weights[PKG_PER_SENDER] = {900, 1300, 800, 950, 1050}; -static sender_arg_t g_sender_b = {2001, snd2_regions, snd2_weights}; - -/* - * 记录一次发送失败(队列满导致的超时) - */ -static void record_send_failed(void) -{ - rt_enter_critical(); - g_send_failed++; - rt_exit_critical(); -} - -/* - * 发送一条消息:队列满时记录失败次数并重试,保证线程不崩溃 - */ -static void send_with_retry(struct package_msg *msg) -{ - while (rt_mq_send_wait(g_pkg_mq, msg, sizeof(*msg), - rt_tick_from_millisecond(SEND_TIMEOUT_MS)) != RT_EOK) - { - record_send_failed(); - } -} - -/* - * 发件线程入口 - * 产生 5 条快递消息,全部发出后再发一条结束消息(region = 0) - */ -static void sender_entry(void *parameter) -{ - sender_arg_t *arg = (sender_arg_t *)parameter; - struct package_msg msg; - int i; - - for (i = 0; i < PKG_PER_SENDER; i++) - { - msg.id = arg->base_id + i; - msg.region = arg->regions[i]; - msg.weight = arg->weights[i]; - - send_with_retry(&msg); - } - - /* 发送结束消息,通知分拣线程本发件线程已完成 */ - msg.id = 0; - msg.region = REGION_END; - msg.weight = 0; - send_with_retry(&msg); -} - -/* - * 区域名映射 - */ -static const char *region_name(rt_uint8_t region) -{ - switch (region) - { - case 1: return "east"; - case 2: return "south"; - case 3: return "north"; - default: return "unknown"; - } -} - -/* - * 分拣线程入口 - * 从消息队列取出快递并分类;收到两个结束消息后打印统计结果并退出 - */ -static void sorter_entry(void *parameter) -{ - struct package_msg msg; - rt_base_t level; - int failed; - int total = 0; /* 总快递数量 */ - int end_count = 0; /* 收到的结束消息数 */ - int region_cnt[4] = {0, 0, 0, 0}; /* 下标 1/2/3 对应华东/华南/华北 */ - rt_uint32_t total_weight = 0; /* 总重量 */ - - (void)parameter; - - while (1) - { - if (rt_mq_recv(g_pkg_mq, &msg, sizeof(msg), RT_WAITING_FOREVER) != RT_EOK) - { - LOG_E("sorter: failed to receive message"); - continue; - } - - /* 结束消息:两个发件线程都结束时打印统计并退出 */ - if (msg.region == REGION_END) - { - end_count++; - if (end_count >= SENDER_COUNT) - { - break; - } - continue; - } - - /* 分类统计 */ - total++; - region_cnt[msg.region]++; - total_weight += msg.weight; - - rt_kprintf("package %u -> %s, weight: %u g\n", - msg.id, region_name(msg.region), msg.weight); - - /* 模拟分拣耗时,让发送线程有机会遇到队列满 */ - rt_thread_mdelay(SORT_DELAY_MS); - } - - /* 打印统计结果 */ - rt_kprintf("total packages: %d\n", total); - rt_kprintf("east: %d, south: %d, north: %d\n", - region_cnt[1], region_cnt[2], region_cnt[3]); - rt_kprintf("total weight: %u g\n", total_weight); - - level = rt_hw_interrupt_disable(); - failed = g_send_failed; - rt_hw_interrupt_enable(level); - rt_kprintf("send failed: %d\n", failed); - - /* 分拣结束:释放消息队列并复位运行标志 */ - rt_mq_delete(g_pkg_mq); - g_pkg_mq = RT_NULL; - g_running = 0; -} - -/* - * MSH 命令:package_demo - */ -static int package_demo(int argc, char *argv[]) -{ - rt_thread_t tid_a = RT_NULL; - rt_thread_t tid_b = RT_NULL; - rt_thread_t tid_sorter = RT_NULL; - - /* 已在运行则拒绝重复启动 */ - if (g_running) - { - rt_kprintf("package demo is already running, please wait...\n"); - return RT_EBUSY; - } - - g_send_failed = 0; - - /* 创建消息队列:容量 5 条消息 */ - g_pkg_mq = rt_mq_create("pkg_q", sizeof(struct package_msg), - MQ_CAPACITY, RT_IPC_FLAG_FIFO); - if (g_pkg_mq == RT_NULL) - { - rt_kprintf("Error: failed to create message queue!\n"); - return RT_ERROR; - } - - /* 创建两个发件线程 */ - tid_a = rt_thread_create("send_a", sender_entry, &g_sender_a, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid_a == RT_NULL) - { - rt_kprintf("Error: failed to create sender A thread!\n"); - goto cleanup_fail; - } - tid_b = rt_thread_create("send_b", sender_entry, &g_sender_b, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid_b == RT_NULL) - { - rt_kprintf("Error: failed to create sender B thread!\n"); - goto cleanup_fail; - } - - /* 创建分拣线程 */ - tid_sorter = rt_thread_create("sorter", sorter_entry, RT_NULL, - THREAD_STACK_SIZE, THREAD_PRIORITY, 10); - if (tid_sorter == RT_NULL) - { - rt_kprintf("Error: failed to create sorter thread!\n"); - goto cleanup_fail; - } - - /* 启动所有线程 */ - if (rt_thread_startup(tid_a) != RT_EOK || - rt_thread_startup(tid_b) != RT_EOK || - rt_thread_startup(tid_sorter) != RT_EOK) - { - rt_kprintf("Error: failed to start threads!\n"); - goto cleanup_fail; - } - - g_running = 1; - rt_kprintf("package demo: 2 senders, %d packages each, queue capacity %d\n", - PKG_PER_SENDER, MQ_CAPACITY); - return RT_EOK; - -cleanup_fail: - /* 创建/启动失败:清理已创建的对象。 - * 已退出的线程为 CLOSE 状态,rt_thread_delete 为空操作,可安全重复调用。 */ - if (tid_a != RT_NULL) - rt_thread_delete(tid_a); - if (tid_b != RT_NULL) - rt_thread_delete(tid_b); - if (tid_sorter != RT_NULL) - rt_thread_delete(tid_sorter); - if (g_pkg_mq != RT_NULL) - { - rt_mq_delete(g_pkg_mq); - g_pkg_mq = RT_NULL; - } - return RT_ERROR; -} -MSH_CMD_EXPORT(package_demo, package sorting center demo); - -/* - * 主函数 - */ -int main(void) -{ - rt_kprintf("Package sorting center demo ready.\n"); - rt_kprintf("Command: package_demo\n"); - return RT_EOK; -} diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index 6f69838b55af1c490f848699951d2a6fe613d903..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,225 +0,0 @@ -# Part 1 理论整理 - -## 1. STM32F407 启动流程与 RT-Thread 引导机制 - -### 1.1 从上电复位到用户 `main()` - -STM32F407 复位后,Cortex-M4 先从中断向量表读取两个值: - -- `vector[0]`:主栈顶地址,装入 MSP; -- `vector[1]`:复位入口地址,装入 PC,开始执行 `Reset_Handler`。 - -常见的 MDK 工程为例,完整调用链如下: - -```mermaid -%%{init: {"theme": "base", "themeVariables": {"fontSize": "18px"}}}%% -flowchart LR - A["1. 复位入口
向量表 -> Reset_Handler
-> SystemInit() -> __main"] - B["2. RT-Thread 接管
$Sub$$main() -> rtthread_startup()
关中断 -> rt_hw_board_init()"] - C["3. 内核准备
rt_show_version()
-> rt_system_timer_init()
-> rt_system_scheduler_init()
-> [rt_system_signal_init()]"] - D["4. 创建系统线程
rt_application_init()
-> [rt_system_timer_thread_init()]
-> rt_thread_idle_init()"] - E["5. 进入应用
rt_system_scheduler_start()
-> 首次上下文切换
-> main_thread_entry()
-> rt_components_init()
-> $Super$$main()"] - A --> B --> C --> D --> E -``` - -> **关键点**:`rt_system_scheduler_start()` 启动后不再返回;不同工具链(MDK/GCC/IAR)仅在 C 库到 `rtthread_startup()` 的入口方式上有差异。 - ------- - -### 1.2 自动初始化原理(`INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT`) - -**底层核心机制**: -利用**编译链接器的段(Section)收集功能**,将函数指针在编译阶段放置在特定的内存段中,启动时统一遍历调用。 - -1. **宏展开**:`INIT_EXPORT(fn, level)` 定义了一个带有 `rt_section(".rti_fn." level)` 属性的常量函数指针 `__rt_init_##fn`,该指针指向用户函数 `fn`。 -2. **链接器收集**:链接脚本将不同 `level`(1~6)的 `.rti_fn.` 段按顺序排列,形成连续的函数指针数组。通过 `rti_board_start`、`rti_board_end`、`rti_end` 等哨兵变量确定数组边界。 -3. **遍历调用**: - - **`rt_components_board_init()`**:仅遍历 **1级**(`.rti_fn.1`)函数。在调度器启动前(`rt_hw_board_init()` 内部)执行,用于 GPIO、串口等最底层硬件初始化,**严禁阻塞**。 - - **`rt_components_init()`**:遍历 **2~6级** 函数。在 main 线程中(用户 `main` 之前)执行。 - -| 级别 | 导出宏 | 执行上下文 | 典型用途 | -| :--- | :---------------------- | :----------- | :--------------------------- | -| 1 | `INIT_BOARD_EXPORT` | 调度器启动前 | GPIO、串口等板级硬件初始化 | -| 2 | `INIT_PREV_EXPORT` | main 线程 | 依赖较少的纯软件初始化 | -| 3 | `INIT_DEVICE_EXPORT` | main 线程 | 设备驱动注册 | -| 4 | `INIT_COMPONENT_EXPORT` | main 线程 | DFS、网络等组件初始化 | -| 5 | `INIT_ENV_EXPORT` | main 线程 | 文件系统挂载等运行环境初始化 | -| 6 | `INIT_APP_EXPORT` | main 线程 | 用户应用初始化 | - ------- - -## 2. 内核基础与对象模型 - -### 2.1 面向对象设计思想与 TCB 关键成员 - -- **设计思想**:RT-Thread 用 C 模拟面向对象,通过**继承**机制——将 `struct rt_object parent` 作为结构体的**首成员**,使子类(线程、设备、IPC等)复用父类的名称、类型、对象链表节点等公共属性。内核通过 `rt_object_container[]` 容器统一管理所有内核对象。 -- **线程控制块关键成员**(`struct rt_thread`): - -| 成员 | 作用 | -| :----------------- | :----------------------------------------------------------- | -| `sp` | 当前线程栈指针。保存上下文时记录现场,恢复时加载;不同架构(如 Cortex-M 栈向下增长)决定其具体存储方式。 | -| `stat` | 线程当前状态及附加标志(如挂起、就绪、运行、关闭等),调度器通过它决定是否将线程入队或忽略。 | -| `current_priority` | 当前有效优先级。当发生互斥量**优先级继承**时会被临时提高,避免优先级翻转。 | -| `init_priority` | 线程基础(原始)优先级,用于优先级继承结束后恢复。 | -| `init_tick` | 每轮时间片的初始长度(单位:OS Tick),轮转时重置给 `remaining_tick`。 | -| `remaining_tick` | 本轮剩余时间片。每个 OS Tick 递减,归零时触发同优先级轮转。 | - -### 2.2 静态线程 vs 动态线程(内存分配与高可靠场景分析) - -| 维度 | 静态线程 (`rt_thread_init`) | 动态线程 (`rt_thread_create`) | -| :----------- | :----------------------------------------------------------- | :----------------------------------------------------------- | -| **分配位置** | TCB 与栈空间**由调用者分配**,通常位于全局/静态存储区(`.bss/.data`)或栈上。 | TCB 与栈空间从**系统堆(Heap)**动态申请。 | -| **生命周期** | 由调用者显式控制,存储空间在编译期已确定,可长期保留并复用。 | 随线程创建而分配,随线程退出而回收。 | -| **释放机制** | `rt_thread_detach()` 仅从内核对象容器中脱离,**不释放**用户提供的 TCB 和栈内存。 | `rt_thread_delete()` 或线程入口返回后进入关闭态,由 **idle 线程**进行延迟资源回收。 | -| **主要风险** | 栈尺寸配置过小导致溢出,或对象被过早复用(生命周期管理混乱)。 | 堆分配失败、内存碎片、分配耗时不确定(实时性抖动)。 | - -**高可靠项目强制使用静态线程的原因**: - -1. **内存上界可预估**:启动前即可精确计算 TCB 与栈所需的总内存容量,不存在运行期堆溢出风险。 -2. **运行时确定性**:无动态内存分配(`malloc`)带来的耗时抖动和失败分支,路径耗时稳定。 -3. **规避碎片化**:没有 `free` 操作,系统长期运行不存在堆内存碎片问题。 -4. **所有权清晰**:生命周期与模块绑定,便于代码审查与安全认证(如 DO-178C、IEC 61508)。 - ------- - -## 3. 线程状态机与调度算法 - -### 3.1 五态模型及转换 - -RT-Thread 线程严格遵循以下状态流转,核心转换逻辑如下: - -### - -```mermaid -stateDiagram-v2 - [*] --> 初始态: rt_thread_init / rt_thread_create - 初始态 --> 就绪态: rt_thread_startup - 就绪态 --> 运行态: 被调度器选中 - 运行态 --> 就绪态: 高优先级抢占 / 时间片到 / yield - 运行态 --> 挂起态: delay / IPC 等待 / suspend - 挂起态 --> 就绪态: 超时 / 资源到达 / resume - 初始态 --> 关闭态: detach / delete - 就绪态 --> 关闭态: detach / delete - 挂起态 --> 关闭态: detach / delete - 运行态 --> 关闭态: 入口返回 / rt_thread_exit - 关闭态 --> [*]: 动态资源由 idle 回收;静态存储由用户管理 -``` - -| 状态 | 含义 | 典型进入方式 | -| -------------------------- | -------------------------------------- | ------------------------------------------------------------ | -| 初始态 `RT_THREAD_INIT` | 已创建但未进入就绪队列,不参与调度 | `rt_thread_init()`、`rt_thread_create()` | -| 就绪态 `RT_THREAD_READY` | 运行条件满足,按优先级等待 CPU | `rt_thread_startup()`、超时或资源到达 | -| 运行态 `RT_THREAD_RUNNING` | 当前占用 CPU;单核系统同一时刻只有一个 | 调度器选中 | -| 挂起态 `RT_THREAD_SUSPEND` | 等待时间或资源,不参与调度 | `rt_thread_delay()`、`rt_sem_take()`、`rt_mutex_take()`、`rt_mq_recv()` 等 | -| 关闭态 `RT_THREAD_CLOSE` | 不再参与调度,等待清理 | 入口返回、`rt_thread_exit()`、删除或脱离 | - -### 3.2 调度机制剖析 - -**(1)抢占式优先级调度** - -- **规则**:固定优先级,**数值越小优先级越高**。最高就绪优先级线程必定抢占当前线程。 -- **实现**:通过**优先级位图(O(1) 查找)**定位最高就绪优先级。≤32 级用单 32 位位图;>32 级采用二级位图(组+组内)。 -- **触发点**: - 1. 新线程就绪(如 `rt_thread_startup`、IPC 释放/超时)。 - 2. 当前线程阻塞(延时、等待 IPC、主动 yield 或退出)。 - 3. SysTick 中断导致时间片耗尽。 - 4. 中断唤醒高优先级线程(中断中仅标记,退出时通过 PendSV 切换)。 - -**(2)时间片轮转** - -- **作用范围**:**仅作用于相同优先级**的就绪线程。高优先级线程就绪仍会立即抢占,不受时间片限制。 -- **运行逻辑**: - - 线程运行时,每个 OS Tick 递减 `remaining_tick`。 - - 归零时:将 `remaining_tick` 重置为 `init_tick`,并将当前线程移到同优先级就绪队列尾部。 - - 调度器取该队列的下一个线程运行。 -- **特殊场景**:若同优先级队列只有一个线程,时间片耗尽后它仍继续运行(无轮转对象)。 - -> **工程建议**:应使线程阻塞于事件(如信号量、队列),避免高优先级线程持续空转,否则会导致低优先级线程及 idle 线程饥饿,影响动态资源回收与低功耗。 - - - -# Part 2 实验结果分析与串口截图 - -## 实验结果分析 - -### 1. `thread_dump` 全局视图 - -| 线程 | 优先级 | 状态 | 栈深(sp) | max used | 剩余时间片 | 错误 | -| -------- | ------ | ------- | -------- | -------- | ---------- | ---- | -| th_monit | 10 | suspend | 168B | 16% | 5 | OK | -| th_led2 | 16 | suspend | 124B | 16% | 4 | OK | -| th_led1 | 16 | suspend | 128B | 17% | 5 | OK | -| tshell | 20 | running | 344B | 15% | 3 | OK | -| tidle0 | 31 | ready | 144B | 56% | 28 | OK | -| timer | 4 | suspend | 128B | 25% | 9 | OK | -| main | 10 | suspend | 172B | 14% | 1 | OK | - -结论: - -- **优先级符合设计**:th_monitor(10)> 工作线程(16);系统 timer 线程 4 最高,tshell 20、tidle0 31 垫底。数值越小优先级越高的规则得到直观验证; -- **状态解读**:dump 时三个模块线程全部 suspend,是因为命令执行时它们被协作式暂停/正睡在 `mdelay` 里;`tshell running` 正是执行 `thread_dump` 命令的 shell 线程本身; -- **`th_monit` 名字截断**:RT-Thread 线程名上限 `RT_NAME_MAX=8`,"th_monitor" 被截为 8 字符,属正常现象; -- **两种栈测量方法互相印证**:内核按 `'#'` 扫描算出的 max used 17%/16%,与我们 `thread_stack_free()` 报出的 848/1024(已用 176B=17.2%)、852/1024(已用 172B=16.8%)**完全一致**——说明剩余栈统计方法是可靠的; -- **时间片验证**:left tick 列显示 5/4/5,工作线程的 5 ticks 时间片正在生效(4 表示被打断时已耗掉 1 个 tick); -- **error 全 OK**:各线程运行至今无异常错误码。 - -### 2. `thread_log on` 后的周期日志 - -- th_led1 的 tick 每次递增约 **505**(500ms 周期 + 5 ticks 调度抖动,1 tick=1ms,`RT_TICK_PER_SECOND=1000` 验证成立); -- th_led1/th_led2 各自独立计数、互不干扰; -- **观察到一个典型现象**:`th_led1 count=401, tick=200` 后面紧跟着孤立的 `123le` —— 这是日志刷屏时你在终端敲的字符与输出流交错显示,视觉上被拆散嵌进了日志行尾。msh 没有行编辑隔离,刷屏时输入显示不可靠(命令本身仍能被接收)。**建议输入命令前先 `thread_log off`**。 - -### 3. KEY1 挂起/恢复实验(核心结果) - -日志给出了教科书级的完整证据链: - -**第一次按键(tick≈209722)**: - -``` -th_led1 count=420, tick=209722 -KEY1: suspend requested for th_led1 ← 监控线程处理按键事件 -th_led2 count=421 / 422 / 423 ... ← th_led2 不受影响继续跑 -(th_led1 计数冻结在 420, LED_R 停闪) -``` - -**第二次按键(tick≈216159)**: - -``` -KEY1: th_led1 resumed -th_led1: state=ready ← 刚恢复:在就绪队列中,尚未运行 -th_led1 count=421, tick=216159 ← 从 421 继续,而不是从 0 重来 -``` - -三个关键结论: - -1. **挂起只冻结目标线程**:th_led1 停住期间 th_led2 正常计数 421→432,系统其它部分完全不受影响; -2. **挂起是"现场冻结"而非"重新开始"**:恢复后 th_led1 从 count=421 接着跑,证明 CPU 上下文(寄存器+PC)被完整保存; -3. **两套时钟互相印证**:tick 从 209722 跳到 216159,间隔 **6437ms**;同期 th_led2 计数 420→432 = 12 次 × 500ms = **6s**,完全吻合; -4. **ready ≠ running**:恢复瞬间监控报 `state=ready`(已放回就绪队列),下一个报告又变回 `suspend`(进入 mdelay 睡眠)——三种状态(ready/suspend/running)的转换在日志里都能看到。 - -### 4. 栈空间分析 - -- 稳态运行:th_led1 **776/1024(24%)**、th_led2 780/1024、th_monitor 856/1024,长时间运行 + 挂起/恢复循环后**数字稳定不变 → 无栈泄漏**; -- dump 时 848/852 与稳态 776/780 的差异**不是泄漏**,是采样点不同:dump 把线程停在循环顶部(浅栈 128B),稳态监控采样在 `mdelay` 睡眠点(深栈 248B)——开启 LOG 后 `LOG_I→rt_kprintf` 调用链使最深栈点增加了约 70B; -- 使用率均 <25%,1024B 配置余量充足,距溢出很远。 - -### 5. 与任务要求的逐项对照 - -| 任务要求 | 实验验证 | -| ------------------------------------------------------------ | -------------------------------------------- | -| th_led1 动态创建,优先级 16、时间片 5、栈 1024B,翻转 LED_R 并打印 count+tick | ✓ left tick=5,max used 17%,tick 步进 505ms | -| th_led2 静态初始化(全局栈+TCB),参数同上,翻转 LED_B 打印 count | ✓ 列表可见且与动态线程行为一致 | -| th_monitor 优先级 10、周期 2s、打印状态与剩余栈;KEY1 单次挂起/再次恢复 | ✓ 报告周期 2s,挂起/恢复证据链完整 | -| INIT_APP_EXPORT 自动加载、main.c 无硬编码;MSH_CMD_EXPORT 调试命令 | ✓ `thread_dump`/`thread_log` 均可从 msh 调用 | - -### 6.建议的进一步实验 - -1. 挂起期间执行 `thread_dump`,确认 th_led1 保持 suspend 不被命令误恢复; -2. 快速连按 KEY1,观察 50ms 去抖与"挂起请求未完成时按键被忽略"的日志(`KEY1: suspend pending, press ignored`); -3. 在 `thread_log off` 状态下观察:LED 仍闪烁、按键日志仍输出——证明静默的只是周期打印,线程本身照常运行。 - -## 串口截图 - -![](C:\Users\lenovo\Desktop\学习\100.png) - -![](C:\Users\lenovo\Desktop\学习\102.png) \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index e9cfe240d34ffdb0e892e22b0566865000371004..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,487 +0,0 @@ -/* - * 多线程实战测试模块(通过 INIT_APP_EXPORT 自动加载, main.c 中无任何硬编码调用) - * - * th_led1 : 动态创建, 优先级 16, 时间片 5 ticks, 栈 1024 Bytes - * 循环翻转 LED_R(PF12), 每次执行打印计数与 rt_tick_get() - * th_led2 : 静态初始化(全局栈 + 全局 TCB), 优先级 16, 时间片 5 ticks - * 循环翻转 LED_B(PF11), 每次执行打印计数 - * th_monitor : 动态创建, 优先级 10(高于工作线程), 周期 rt_thread_mdelay(2000), - * 唤醒后打印 th_led1/th_led2 的运行状态与剩余栈空间; - * KEY1(PC5) 单次触发调用 rt_thread_suspend() 挂起 th_led1, - * 再次触发调用 rt_thread_resume() 恢复 th_led1 - * - * 调试命令: - * MSH_CMD_EXPORT(thread_dump, ...) -> msh 中执行 thread_dump 查看线程状态 - * 执行时工作线程的周期打印会被临时暂停(协作式挂起), 命令输出完毕后再恢复, - * 避免工作线程日志刷屏覆盖本命令的输出。 - * MSH_CMD_EXPORT(thread_log, ...) -> msh 中执行 thread_log on/off 开关周期 - * 日志输出; 周期日志上电默认关闭, 串口保持安静, 便于在 msh 中输入命令。 - */ - -#include -#include -#include -#include - -#define DBG_TAG "thread_study" -#define DBG_LVL DBG_LOG -#include - -/* 板载 LED 与按键引脚 */ -#define LED_B_PIN GET_PIN(F, 11) -#define LED_R_PIN GET_PIN(F, 12) -#define KEY1_PIN GET_PIN(C, 5) -#define LED_OFF_LEVEL PIN_HIGH - -/* 工作线程参数:优先级 16, 时间片 5 ticks, 栈 1024 Bytes */ -#define LED_THREAD_STACK_SIZE 1024 -#define LED_THREAD_PRIORITY 16 -#define LED_THREAD_TIMESLICE 5 - -/* 监控线程参数:优先级 10(数值越小优先级越高, 高于工作线程的 16) */ -#define MONITOR_STACK_SIZE 1024 -#define MONITOR_PRIORITY 10 -#define MONITOR_TIMESLICE 5 - -#define MONITOR_PERIOD_MS 2000 -#define LED_PERIOD_MS 500 -#define KEY_DEBOUNCE_MS 50 - -/* 动态创建的两个线程句柄 */ -static rt_thread_t th_led1; -static rt_thread_t th_monitor; - -/* th_led2 静态初始化:全局 TCB 与全局栈 */ -static struct rt_thread th_led2; -ALIGN(8) static rt_uint8_t th_led2_stack[LED_THREAD_STACK_SIZE]; - -/* KEY1 按键事件信号量:中断里只释放信号量, 挂起/恢复在监控线程中执行 */ -static struct rt_semaphore key1_sem; - -/* th_led1 挂起/恢复标记, 详见 monitor_key_events() 中的说明 */ -static volatile rt_bool_t led1_suspend_request; -static volatile rt_bool_t led1_suspended; - -/* thread_dump 命令的临时暂停标记:暂停期间工作线程不打印, 便于查看命令输出 */ -static volatile rt_bool_t led1_pause_request; /* 请求 th_led1 暂停 */ -static volatile rt_bool_t led2_pause_request; /* 请求 th_led2 暂停 */ -static volatile rt_bool_t led1_dump_paused; /* th_led1 已暂停 */ -static volatile rt_bool_t led2_dump_paused; /* th_led2 已暂停 */ -static volatile rt_bool_t quiet_dump; /* 监控线程暂停打印与按键处理 */ - -/* 周期日志开关:thread_log 命令运行时控制。 - * 上电默认关闭, 串口保持安静、msh 可以正常输入命令; - * 执行 thread_log on 后打开演示日志。 */ -static volatile rt_bool_t worker_log_enabled = RT_FALSE; - -/* KEY1 中断去抖 */ -static volatile rt_bool_t key1_irq_seen; -static volatile rt_tick_t key1_last_irq_tick; - -/* 翻转一个引脚的电平 */ -static void led_toggle(rt_base_t pin) -{ - int level = rt_pin_read(pin); - - rt_pin_write(pin, level == PIN_LOW ? PIN_HIGH : PIN_LOW); -} - -/* 线程运行状态名称, 供打印使用 */ -static const char *thread_state_name(rt_thread_t thread) -{ - switch (thread->stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: - return "init"; - case RT_THREAD_READY: - return "ready"; - case RT_THREAD_SUSPEND: - return "suspend"; - case RT_THREAD_RUNNING: - return "running"; - case RT_THREAD_CLOSE: - return "close"; - default: - return "unknown"; - } -} - -/* - * 剩余栈空间统计: - * RT-Thread 初始化线程栈时用 '#' 填充(rt-thread/src/thread.c), - * Cortex-M 栈向下生长, 因此从栈底向上数 '#' 即为未被使用的字节数。 - */ -static rt_size_t thread_stack_free(rt_thread_t thread) -{ - rt_uint8_t *start = (rt_uint8_t *)thread->stack_addr; - rt_uint8_t *end = start + thread->stack_size; - rt_uint8_t *cursor = start; - - while (cursor < end && *cursor == '#') - { - cursor++; - } - - return (rt_size_t)(cursor - start); -} - -/* th_led1 线程入口:翻转 LED_R, 打印计数与系统滴答 */ -static void led1_entry(void *parameter) -{ - rt_uint32_t count = 0; - - RT_UNUSED(parameter); - - while (1) - { - /* - * 监控线程只能"请求"挂起:本内核版本的 rt_thread_suspend() 带有 - * RT_ASSERT(thread == rt_thread_self()) 断言(RT_DEBUG 开启), 且 - * 无法挂起处于睡眠态的线程, 因此由 th_led1 自己执行挂起操作, - * 之后再由监控线程调用 rt_thread_resume() 恢复。 - */ - if (led1_suspend_request) - { - led1_suspend_request = RT_FALSE; - led1_suspended = RT_TRUE; - - /* 挂起自己并主动让出 CPU, 直到被 rt_thread_resume() 恢复 */ - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - - /* 恢复后从此处继续执行 */ - led1_suspended = RT_FALSE; - } - - /* thread_dump 命令请求:暂停本线程打印, 待命令输出完毕后恢复 */ - if (led1_pause_request) - { - led1_pause_request = RT_FALSE; - led1_dump_paused = RT_TRUE; - - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - - led1_dump_paused = RT_FALSE; - } - - led_toggle(LED_R_PIN); - count++; - if (worker_log_enabled) - { - LOG_I("th_led1 count=%lu, tick=%lu", - (unsigned long)count, - (unsigned long)rt_tick_get()); - } - rt_thread_mdelay(LED_PERIOD_MS); - } -} - -/* th_led2 线程入口:翻转 LED_B, 打印计数 */ -static void led2_entry(void *parameter) -{ - rt_uint32_t count = 0; - - RT_UNUSED(parameter); - - while (1) - { - /* thread_dump 命令请求:暂停本线程打印, 待命令输出完毕后恢复 */ - if (led2_pause_request) - { - led2_pause_request = RT_FALSE; - led2_dump_paused = RT_TRUE; - - rt_thread_suspend(rt_thread_self()); - rt_schedule(); - - led2_dump_paused = RT_FALSE; - } - - led_toggle(LED_B_PIN); - count++; - if (worker_log_enabled) - { - LOG_I("th_led2 count=%lu", (unsigned long)count); - } - rt_thread_mdelay(LED_PERIOD_MS); - } -} - -/* KEY1 中断回调:只做去抖并释放信号量, 不直接操作线程 */ -static void key1_irq_callback(void *parameter) -{ - rt_tick_t now = rt_tick_get(); - - RT_UNUSED(parameter); - - if (!key1_irq_seen || - (now - key1_last_irq_tick) >= rt_tick_from_millisecond(KEY_DEBOUNCE_MS)) - { - key1_irq_seen = RT_TRUE; - key1_last_irq_tick = now; - rt_sem_release(&key1_sem); - } -} - -/* 处理按键事件:单次触发挂起 th_led1, 再次触发恢复 th_led1 */ -static void monitor_key_events(void) -{ - while (rt_sem_trytake(&key1_sem) == RT_EOK) - { - if (led1_suspended) - { - /* 确认 th_led1 确实处于挂起态后再恢复 */ - if ((th_led1->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND) - { - if (rt_thread_resume(th_led1) == RT_EOK) - { - LOG_I("KEY1: th_led1 resumed"); - } - } - } - else if (!led1_suspend_request) - { - /* th_led1 在下一次循环检查到该请求后自行调用 - * rt_thread_suspend() 挂起, 挂起完成后进入 suspend 状态 */ - led1_suspend_request = RT_TRUE; - LOG_I("KEY1: suspend requested for th_led1"); - } - else - { - LOG_W("KEY1: suspend pending, press ignored"); - } - } -} - -/* 打印 th_led1 与 th_led2 的运行状态与剩余栈空间 */ -static void monitor_report(void) -{ - if (!worker_log_enabled) - { - return; - } - - LOG_I("th_led1: state=%s, free_stack=%lu/%lu bytes", - thread_state_name(th_led1), - (unsigned long)thread_stack_free(th_led1), - (unsigned long)th_led1->stack_size); - LOG_I("th_led2: state=%s, free_stack=%lu/%lu bytes", - thread_state_name(&th_led2), - (unsigned long)thread_stack_free(&th_led2), - (unsigned long)th_led2.stack_size); -} - -/* 监控线程入口:周期唤醒, 处理按键事件并打印线程状态 */ -static void monitor_entry(void *parameter) -{ - RT_UNUSED(parameter); - - while (1) - { - rt_thread_mdelay(MONITOR_PERIOD_MS); - - /* thread_dump 命令执行期间暂停打印与按键处理, 避免刷屏覆盖命令输出 */ - if (!quiet_dump) - { - monitor_key_events(); - monitor_report(); - } - } -} - -/* 板载 LED 与按键初始化 */ -static void board_io_init(void) -{ - rt_pin_mode(LED_B_PIN, PIN_MODE_OUTPUT); - rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT); - rt_pin_write(LED_B_PIN, LED_OFF_LEVEL); - rt_pin_write(LED_R_PIN, LED_OFF_LEVEL); - - rt_pin_mode(KEY1_PIN, PIN_MODE_INPUT_PULLUP); -} - -/* 本模块自动加载入口:由 INIT_APP_EXPORT 注册, main.c 中不做任何调用 */ -static int thread_study_init(void) -{ - rt_err_t result; - - board_io_init(); - - result = rt_sem_init(&key1_sem, "key1", 0, RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - LOG_E("init key1 semaphore failed: %d", result); - return result; - } - - result = rt_pin_attach_irq(KEY1_PIN, - PIN_IRQ_MODE_FALLING, - key1_irq_callback, - RT_NULL); - if (result != RT_EOK) - { - LOG_E("attach KEY1 irq failed: %d", result); - return result; - } - - result = rt_pin_irq_enable(KEY1_PIN, PIN_IRQ_ENABLE); - if (result != RT_EOK) - { - LOG_E("enable KEY1 irq failed: %d", result); - return result; - } - - /* 工作线程 1:动态创建 th_led1 */ - th_led1 = rt_thread_create("th_led1", - led1_entry, - RT_NULL, - LED_THREAD_STACK_SIZE, - LED_THREAD_PRIORITY, - LED_THREAD_TIMESLICE); - if (th_led1 == RT_NULL) - { - LOG_E("create th_led1 failed"); - return -RT_ENOMEM; - } - rt_thread_startup(th_led1); - - /* 工作线程 2:静态初始化 th_led2(全局栈 + 全局 TCB) */ - result = rt_thread_init(&th_led2, - "th_led2", - led2_entry, - RT_NULL, - th_led2_stack, - sizeof(th_led2_stack), - LED_THREAD_PRIORITY, - LED_THREAD_TIMESLICE); - if (result != RT_EOK) - { - LOG_E("init th_led2 failed: %d", result); - return result; - } - rt_thread_startup(&th_led2); - - /* 监控与控制线程:动态创建 th_monitor */ - th_monitor = rt_thread_create("th_monitor", - monitor_entry, - RT_NULL, - MONITOR_STACK_SIZE, - MONITOR_PRIORITY, - MONITOR_TIMESLICE); - if (th_monitor == RT_NULL) - { - LOG_E("create th_monitor failed"); - return -RT_ENOMEM; - } - rt_thread_startup(th_monitor); - - LOG_I("thread study module loaded: th_led1(dynamic), th_led2(static), th_monitor"); - LOG_I("period log is off by default, run 'thread_log on' in msh to enable"); - return RT_EOK; -} -INIT_APP_EXPORT(thread_study_init); - -#ifdef RT_USING_FINSH -/* 打印单个线程的详细状态(供 thread_dump 命令使用) */ -static void thread_detail(rt_thread_t thread) -{ - rt_kprintf("%-10s prio=%-3d state=%-8s stack_free=%lu/%lu bytes\n", - thread->name, - thread->current_priority, - thread_state_name(thread), - (unsigned long)thread_stack_free(thread), - (unsigned long)thread->stack_size); -} - -/* finsh 内置的系统线程列表命令 */ -extern long list_thread(void); - -/* MSH 命令:thread_dump 查看本模块线程的运行状态与剩余栈空间。 - * 执行时先请求工作线程暂停打印, 输出完毕后恢复, 避免日志刷屏覆盖输出。 */ -static int thread_dump(int argc, char **argv) -{ - rt_bool_t paused_led1 = RT_FALSE; - rt_bool_t paused_led2 = RT_FALSE; - rt_tick_t deadline; - - RT_UNUSED(argc); - RT_UNUSED(argv); - - /* 命令执行期间, 监控线程暂停打印与按键处理 */ - quiet_dump = RT_TRUE; - - /* 请求正在运行的工作线程暂停(已被按键挂起的 th_led1 无需再暂停) */ - if (!led1_suspended && !led1_suspend_request) - { - paused_led1 = RT_TRUE; - led1_pause_request = RT_TRUE; - } - paused_led2 = RT_TRUE; - led2_pause_request = RT_TRUE; - - /* 等待工作线程确认暂停(最多等待一个工作周期, 留 2s 余量) */ - deadline = rt_tick_get() + rt_tick_from_millisecond(2000); - while (rt_tick_get() < deadline && - ((paused_led1 && !led1_dump_paused) || - (paused_led2 && !led2_dump_paused))) - { - rt_thread_mdelay(20); - } - - /* 先打印系统全部线程概览 */ - list_thread(); - - /* 再打印本模块线程的详细状态 */ - rt_kprintf("\n[thread_study] worker/monitor threads:\n"); - thread_detail(th_led1); - thread_detail(&th_led2); - thread_detail(th_monitor); - - /* 恢复被本命令暂停的线程(已被按键挂起的 th_led1 保持挂起不动) */ - if (paused_led1 && led1_dump_paused && - (th_led1->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND) - { - rt_thread_resume(th_led1); - } - if (paused_led2 && led2_dump_paused && - (th_led2.stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND) - { - rt_thread_resume(&th_led2); - } - - quiet_dump = RT_FALSE; - - return 0; -} -MSH_CMD_EXPORT(thread_dump, "dump thread_study threads status and stack usage"); - -/* MSH 命令:thread_log on/off 开关周期日志, 关闭后串口安静, 便于操作 msh */ -static int thread_log(int argc, char **argv) -{ - if (argc == 1) - { - rt_kprintf("thread_study log output: %s\n", - worker_log_enabled ? "on" : "off"); - return 0; - } - - if (strcmp(argv[1], "on") == 0) - { - worker_log_enabled = RT_TRUE; - rt_kprintf("thread_study log output: on\n"); - } - else if (strcmp(argv[1], "off") == 0) - { - worker_log_enabled = RT_FALSE; - rt_kprintf("thread_study log output: off\n"); - } - else - { - rt_kprintf("usage: thread_log [on|off]\n"); - } - - return 0; -} -MSH_CMD_EXPORT(thread_log, "turn on/off thread_study period log output"); -#endif diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/SConscript" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/SConscript" deleted file mode 100644 index f526323834923a1b47b9c10e8e5addf4b2a08e98..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/SConscript" +++ /dev/null @@ -1,11 +0,0 @@ -Import('RTT_ROOT') -Import('rtconfig') -from building import * - -cwd = GetCurrentDir() -src = Glob('*.c') -CPPPATH = [cwd] - -group = DefineGroup('test_dev', src, depend = [''], CPPPATH = CPPPATH) - -Return('group') diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/test_dev.c" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/test_dev.c" deleted file mode 100644 index 6acfc29b3be0c2ab1c427144a1fdc719b6c00657..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/test_dev.c" +++ /dev/null @@ -1,310 +0,0 @@ -/* - * 虚拟设备驱动框架层 test_dev -- 框架实现 - * - * 本文件实现了一个完整的 RT-Thread 字符设备驱动框架: - * 1. 通过 rt_device_create 申请内核设备对象,附加空间存放驱动私有数据; - * 2. 统一实现 init/open/close/read/write/control 六个 ops 并挂接到设备; - * 3. 通过 rt_device_register 注册进内核 I/O 设备模型; - * 4. 内部使用互斥锁保证多线程访问安全; - * 5. 用一块 RAM 模拟真实硬件的存储(相当于一个虚拟 EEPROM)。 - * - * 应用层无需知道内部实现,只通过 rt_device_* 标准接口访问设备。 - */ - -#include "test_dev.h" - -#define DBG_TAG "test_dev.drv" -#define DBG_LVL DBG_LOG -#include - -/* 驱动私有数据结构,保存在 rt_device 对象之后的附加空间里, - * 通过 dev->user_data 指向它。真实驱动里这里会放寄存器基址、DMA 句柄等 */ -struct test_dev -{ - rt_uint8_t *buf; /* 模拟存储(虚拟硬件) */ - rt_uint32_t buf_size; /* 存储容量 */ - rt_uint8_t version; /* 框架版本号 */ - rt_uint8_t mode; /* 工作模式 */ - rt_uint32_t open_count; /* 累计打开次数 */ - rt_uint32_t read_count; /* 累计读次数 */ - rt_uint32_t write_count; /* 累计写次数 */ - rt_mutex_t lock; /* 保护 buf 的互斥锁 */ -}; -typedef struct test_dev *test_dev_t; - -#define _TEST_DEV(t) ((test_dev_t)(t)->user_data) - -/* ============================ ops 实现 ============================ */ - -static rt_err_t test_dev_init(rt_device_t dev) -{ - test_dev_t tdev = _TEST_DEV(dev); - - /* 复位设备统计信息,等价于真实驱动里的"上电初始化" */ - tdev->open_count = 0; - tdev->read_count = 0; - tdev->write_count = 0; - - LOG_I("init: buf=%p size=%u", tdev->buf, tdev->buf_size); - - return RT_EOK; -} - -static rt_err_t test_dev_open(rt_device_t dev, rt_uint16_t oflag) -{ - test_dev_t tdev = _TEST_DEV(dev); - - /* 本虚拟设备只支持读写方式打开 */ - if (!(oflag & RT_DEVICE_OFLAG_RDWR)) - { - LOG_W("open: only RDWR oflag supported"); - return -RT_EINVAL; - } - - /* 注册时带 RT_DEVICE_FLAG_STANDALONE,内核已拒绝重复打开, - * 这里再检查一次作为驱动自身的防御 */ - if (dev->open_flag & RT_DEVICE_OFLAG_OPEN) - { - LOG_W("open: device already opened"); - return -RT_EBUSY; - } - - tdev->open_count++; - LOG_I("open: oflag=%04x, open times=%u", oflag, tdev->open_count); - - return RT_EOK; -} - -static rt_err_t test_dev_close(rt_device_t dev) -{ - LOG_I("close: %s", dev->parent.name); - - return RT_EOK; -} - -static rt_size_t test_dev_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) -{ - test_dev_t tdev = _TEST_DEV(dev); - rt_size_t len = size; - - /* 参数检查 */ - if (buffer == RT_NULL || size == 0) - { - LOG_W("read: invalid param, buffer=%p size=%u", buffer, size); - return 0; - } - - if (pos < 0 || pos >= (rt_off_t)tdev->buf_size) - { - LOG_W("read: pos %d out of range [0, %u)", pos, tdev->buf_size); - return 0; - } - - /* 越界部分裁剪,模拟真实硬件的容量限制 */ - if ((rt_uint32_t)pos + size > tdev->buf_size) - len = tdev->buf_size - pos; - - rt_mutex_take(tdev->lock, RT_WAITING_FOREVER); - rt_memcpy(buffer, tdev->buf + pos, len); - tdev->read_count++; - rt_mutex_release(tdev->lock); - - LOG_D("read: pos=%d size=%u -> %u (total %u)", pos, size, len, tdev->read_count); - - return len; -} - -static rt_size_t test_dev_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) -{ - test_dev_t tdev = _TEST_DEV(dev); - rt_size_t len = size; - - /* 参数检查 */ - if (buffer == RT_NULL || size == 0) - { - LOG_W("write: invalid param, buffer=%p size=%u", buffer, size); - return 0; - } - - if (pos < 0 || pos >= (rt_off_t)tdev->buf_size) - { - LOG_W("write: pos %d out of range [0, %u)", pos, tdev->buf_size); - return 0; - } - - if ((rt_uint32_t)pos + size > tdev->buf_size) - len = tdev->buf_size - pos; - - rt_mutex_take(tdev->lock, RT_WAITING_FOREVER); - - /* 静默模式:模拟"设备被禁用",数据直接丢弃 */ - if (tdev->mode == TEST_DEV_MODE_SILENT) - { - LOG_W("write: silent mode, %u bytes dropped", size); - rt_mutex_release(tdev->lock); - return 0; - } - - rt_memcpy(tdev->buf + pos, buffer, len); - tdev->write_count++; - rt_mutex_release(tdev->lock); - - LOG_D("write: pos=%d size=%u -> %u (total %u)", pos, size, len, tdev->write_count); - - return len; -} - -static rt_err_t test_dev_control(rt_device_t dev, int cmd, void *args) -{ - test_dev_t tdev = _TEST_DEV(dev); - - switch (cmd) - { - case TEST_DEV_CMD_CLEAR: - rt_mutex_take(tdev->lock, RT_WAITING_FOREVER); - rt_memset(tdev->buf, 0, tdev->buf_size); - rt_mutex_release(tdev->lock); - LOG_I("control: CLEAR, %u bytes erased", tdev->buf_size); - break; - - case TEST_DEV_CMD_GET_INFO: - if (args == RT_NULL) - return -RT_EINVAL; - { - struct test_dev_info *info = (struct test_dev_info *)args; - info->version = tdev->version; - info->mode = tdev->mode; - info->buf_size = tdev->buf_size; - info->open_count = tdev->open_count; - info->read_count = tdev->read_count; - info->write_count = tdev->write_count; - } - break; - - case TEST_DEV_CMD_SET_MODE: - tdev->mode = (rt_uint8_t)((rt_uint32_t)args & 0xff); - LOG_I("control: SET_MODE -> %u", tdev->mode); - break; - - case TEST_DEV_CMD_GET_VERSION: - if (args == RT_NULL) - return -RT_EINVAL; - *(rt_uint8_t *)args = tdev->version; - break; - - case RT_DEVICE_CTRL_CONFIG: - /* 内核通用命令:演示驱动可以处理通用命令 */ - LOG_I("control: RT_DEVICE_CTRL_CONFIG, args=%p", args); - break; - - default: - LOG_W("control: unsupported cmd 0x%02x", cmd); - return -RT_EINVAL; - } - - return RT_EOK; -} - -/* ============================ 框架层接口 ============================ */ - -/** - * @brief 注册一个 test_dev 虚拟设备实例 - * - * @param name 设备名(rt_device_find 使用,最长 RT_NAME_MAX) - * - * @return RT_EOK 成功,其它值失败 - */ -rt_err_t rt_test_dev_register(const char *name) -{ - rt_device_t dev; - test_dev_t tdev; - - RT_ASSERT(name != RT_NULL); - - /* 创建内核设备对象,附加空间存放驱动私有数据 */ - dev = rt_device_create(RT_Device_Class_Char, sizeof(struct test_dev)); - if (dev == RT_NULL) - { - LOG_E("create failed: no memory"); - return -RT_ENOMEM; - } - - /* 初始化私有数据(模拟"探测硬件"的过程) */ - tdev = (test_dev_t)(dev + 1); - rt_memset(tdev, 0, sizeof(struct test_dev)); - dev->user_data = tdev; - - tdev->buf = rt_malloc(TEST_DEV_BUF_SIZE); - if (tdev->buf == RT_NULL) - { - LOG_E("alloc buf failed: no memory"); - rt_device_destroy(dev); - return -RT_ENOMEM; - } - rt_memset(tdev->buf, 0, TEST_DEV_BUF_SIZE); - tdev->buf_size = TEST_DEV_BUF_SIZE; - tdev->version = 0x01; - tdev->mode = TEST_DEV_MODE_NORMAL; - - tdev->lock = rt_mutex_create(name, RT_IPC_FLAG_PRIO); - if (tdev->lock == RT_NULL) - { - LOG_E("create mutex failed"); - rt_free(tdev->buf); - rt_device_destroy(dev); - return -RT_ERROR; - } - - /* 挂接统一 ops */ - dev->init = test_dev_init; - dev->open = test_dev_open; - dev->close = test_dev_close; - dev->read = test_dev_read; - dev->write = test_dev_write; - dev->control = test_dev_control; - - /* 注册进内核设备模型。 - * STANDALONE:同一时刻只允许一个应用打开 */ - if (rt_device_register(dev, name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE) != RT_EOK) - { - LOG_E("register failed"); - rt_mutex_delete(tdev->lock); - rt_free(tdev->buf); - rt_device_destroy(dev); - return -RT_ERROR; - } - - LOG_I("register: name=%s buf=%u bytes", name, tdev->buf_size); - - return RT_EOK; -} - -/** - * @brief 注销一个 test_dev 虚拟设备实例 - * - * @param dev rt_device_find 得到的设备句柄 - */ -void rt_test_dev_unregister(rt_device_t dev) -{ - test_dev_t tdev; - - RT_ASSERT(dev != RT_NULL); - tdev = _TEST_DEV(dev); - - rt_device_unregister(dev); - - rt_mutex_delete(tdev->lock); - rt_free(tdev->buf); - rt_device_destroy(dev); - - LOG_I("unregister done"); -} - -/* ============================ 设备实例 ============================ */ - -/* 自动注册一个名为 "test_dev" 的设备实例(设备初始化阶段执行) */ -static int test_dev_auto_register(void) -{ - return rt_test_dev_register(TEST_DEV_NAME); -} -INIT_DEVICE_EXPORT(test_dev_auto_register); diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/test_dev.h" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/test_dev.h" deleted file mode 100644 index 624b1a001dd926a705de1c6ca9ecc9686dec6b57..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/test_dev.h" +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 虚拟设备驱动框架层 test_dev -- 对外接口 - * - * 分层结构: - * ---------------------------------------------------------- - * 应用层 test_dev_sample.c - * rt_device_find/open/read/write/control/close - * ---------------------------------------------------------- - * 框架层 test_dev.c(本文件定义接口) | - * 统一 ops 挂接 + 注册/注销接口 + 私有数据 + 线程安全 | - * ---------------------------------------------------------- - * 内核层 rt_device I/O 设备模型(rt_device_create/register)| - * ---------------------------------------------------------- - */ - -#ifndef __TEST_DEV_H__ -#define __TEST_DEV_H__ - -#include -#include - -#define TEST_DEV_NAME "test_dev" /* 设备名,rt_device_find 使用 */ -#define TEST_DEV_BUF_SIZE 256 /* 模拟存储容量(字节) */ - -/* 自定义控制命令:内核占用 0x01~0x12,自定义命令参考内核做法, - * 以设备类型为基地址偏移,避免与内核命令冲突 */ -#define TEST_DEV_CMD_BASE (RT_DEVICE_CTRL_BASE(Char) + 0x10) -#define TEST_DEV_CMD_CLEAR (TEST_DEV_CMD_BASE + 0) /* 清空模拟存储 */ -#define TEST_DEV_CMD_GET_INFO (TEST_DEV_CMD_BASE + 1) /* 获取设备信息 */ -#define TEST_DEV_CMD_SET_MODE (TEST_DEV_CMD_BASE + 2) /* 设置工作模式 */ -#define TEST_DEV_CMD_GET_VERSION (TEST_DEV_CMD_BASE + 3) /* 获取框架版本号 */ - -/* 设备工作模式 */ -#define TEST_DEV_MODE_NORMAL 0 /* 普通模式:读写正常 */ -#define TEST_DEV_MODE_SILENT 1 /* 静默模式:write 直接丢弃数据 */ - -/* 设备信息,TEST_DEV_CMD_GET_INFO 命令的输出 */ -struct test_dev_info -{ - rt_uint8_t version; /* 框架版本号 */ - rt_uint8_t mode; /* 当前工作模式 */ - rt_uint32_t buf_size; /* 模拟存储容量 */ - rt_uint32_t open_count; /* 累计打开次数 */ - rt_uint32_t read_count; /* 累计读次数 */ - rt_uint32_t write_count; /* 累计写次数 */ -}; - -/* 框架层导出接口:注册/注销一个 test_dev 虚拟设备实例,支持多实例 */ -rt_err_t rt_test_dev_register(const char *name); -void rt_test_dev_unregister(rt_device_t dev); - -#endif /* __TEST_DEV_H__ */ diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/test_dev_sample.c" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/test_dev_sample.c" deleted file mode 100644 index 2bb7172d04125ce143dbaee7ce2924245f00b2c7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev/test_dev_sample.c" +++ /dev/null @@ -1,241 +0,0 @@ -/* - * 虚拟设备驱动框架层 test_dev -- 应用层示例 - * - * 应用层只使用内核标准设备接口访问 test_dev: - * rt_device_find -> rt_device_open -> rt_device_control/read/write - * -> rt_device_close - * - * msh 命令: - * test_dev_demo 跑一遍完整的 I/O 流程 - * test_dev_read 读取并打印存储内容 - * test_dev_write 向设备写入字符串 - * test_dev_info 打印设备信息 - * test_dev_thread 启动一个周期读设备的后台线程 - */ - -#include -#include -#include "test_dev.h" - -#define DBG_TAG "test_dev.app" -#define DBG_LVL DBG_LOG -#include - -/* 打印一段内存的十六进制内容,方便观察设备存储 */ -static void _hex_dump(const char *title, const rt_uint8_t *buf, rt_size_t len) -{ - rt_size_t i; - - LOG_I("%s (%u bytes):", title, len); - for (i = 0; i < len; i++) - { - rt_kprintf("%02x ", buf[i]); - if ((i + 1) % 16 == 0) - rt_kprintf("\n"); - } - if (len % 16) - rt_kprintf("\n"); -} - -/* 完整 I/O 流程演示:open -> control -> write -> read -> control -> close */ -static void test_dev_demo(void) -{ - rt_device_t dev; - struct test_dev_info info; - rt_uint8_t version = 0; - rt_uint8_t wbuf[32], rbuf[32]; - int i, ret; - - /* 1. 查找设备:对象容器里按名字找,拿到的是驱动框架注册的设备句柄 */ - dev = rt_device_find(TEST_DEV_NAME); - if (dev == RT_NULL) - { - LOG_E("can not find device: %s", TEST_DEV_NAME); - return; - } - LOG_I("[1/6] find device: %s", TEST_DEV_NAME); - - /* 2. 打开设备:框架 open 接口被调用,统计 open 次数 */ - ret = rt_device_open(dev, RT_DEVICE_OFLAG_RDWR); - if (ret != RT_EOK) - { - LOG_E("open failed: %d", ret); - return; - } - LOG_I("[2/6] open device ok"); - - /* 3. control:读取设备信息与版本号 */ - rt_device_control(dev, TEST_DEV_CMD_GET_INFO, &info); - rt_device_control(dev, TEST_DEV_CMD_GET_VERSION, &version); - LOG_I("[3/6] control: version=%u mode=%u size=%u open=%u read=%u write=%u", - info.version, info.mode, info.buf_size, - info.open_count, info.read_count, info.write_count); - - /* 4. write:写入一段递增数据 */ - for (i = 0; i < 16; i++) - wbuf[i] = 0xA0 + i; - ret = rt_device_write(dev, 0, wbuf, 16); - LOG_I("[4/6] write %d bytes @ pos 0", ret); - - /* 5. read:读回来并校验 */ - ret = rt_device_read(dev, 0, rbuf, 16); - LOG_I("[5/6] read %d bytes @ pos 0", ret); - if (ret == 16 && rt_memcmp(wbuf, rbuf, 16) == 0) - LOG_I(" data check PASS"); - else - LOG_E(" data check FAIL"); - _hex_dump("content", rbuf, 16); - - /* 演示控制命令:清空后读出的数据应全为 0 */ - rt_device_control(dev, TEST_DEV_CMD_CLEAR, RT_NULL); - rt_device_read(dev, 0, rbuf, 8); - _hex_dump("after CLEAR", rbuf, 8); - - /* 演示静默模式:write 被丢弃 */ - rt_device_control(dev, TEST_DEV_CMD_SET_MODE, (void *)TEST_DEV_MODE_SILENT); - ret = rt_device_write(dev, 0, wbuf, 16); - LOG_I(" silent mode write -> %d (dropped)", ret); - rt_device_control(dev, TEST_DEV_CMD_SET_MODE, (void *)TEST_DEV_MODE_NORMAL); - - /* 6. 关闭设备:框架 close 接口被调用 */ - rt_device_close(dev); - LOG_I("[6/6] close device ok"); -} -MSH_CMD_EXPORT(test_dev_demo, test_dev full io flow demo); - -/* 读指定位置的数据并打印 */ -static void test_dev_read(rt_uint32_t pos, rt_uint32_t size) -{ - rt_device_t dev; - rt_uint8_t buf[64]; - int ret; - - dev = rt_device_find(TEST_DEV_NAME); - if (dev == RT_NULL) - { - LOG_E("can not find device: %s", TEST_DEV_NAME); - return; - } - - if (size > sizeof(buf)) - size = sizeof(buf); - - if (rt_device_open(dev, RT_DEVICE_OFLAG_RDWR) != RT_EOK) - { - LOG_E("open failed"); - return; - } - - ret = rt_device_read(dev, pos, buf, size); - if (ret > 0) - _hex_dump("read", buf, ret); - else - LOG_W("read pos %u failed", pos); - - rt_device_close(dev); -} -MSH_CMD_EXPORT_ALIAS(test_dev_read, dev_read, test_dev_read pos size); - -/* 向设备写入一个字符串 */ -static void test_dev_write(const char *text) -{ - rt_device_t dev; - int ret; - - dev = rt_device_find(TEST_DEV_NAME); - if (dev == RT_NULL) - { - LOG_E("can not find device: %s", TEST_DEV_NAME); - return; - } - - if (rt_device_open(dev, RT_DEVICE_OFLAG_RDWR) != RT_EOK) - { - LOG_E("open failed"); - return; - } - - ret = rt_device_write(dev, 0, text, rt_strlen(text)); - LOG_I("write %d bytes", ret); - - rt_device_close(dev); -} -MSH_CMD_EXPORT(test_dev_write, test_dev_write string); - -/* 打印设备信息 */ -static void test_dev_info(void) -{ - rt_device_t dev; - struct test_dev_info info; - - dev = rt_device_find(TEST_DEV_NAME); - if (dev == RT_NULL) - { - LOG_E("can not find device: %s", TEST_DEV_NAME); - return; - } - - if (rt_device_open(dev, RT_DEVICE_OFLAG_RDWR) != RT_EOK) - { - LOG_E("open failed"); - return; - } - - rt_device_control(dev, TEST_DEV_CMD_GET_INFO, &info); - rt_kprintf("name=%s version=%u mode=%u size=%u open=%u read=%u write=%u\n", - TEST_DEV_NAME, info.version, info.mode, info.buf_size, - info.open_count, info.read_count, info.write_count); - - rt_device_close(dev); -} -MSH_CMD_EXPORT(test_dev_info, show test_dev info); - -/* 后台线程:周期读设备,模拟真实应用(如传感器轮询) */ -static void test_dev_poll_thread(void *parameter) -{ - rt_device_t dev; - rt_uint8_t buf[8]; - int count = 0; - - dev = (rt_device_t)parameter; - - while (count++ < 10) - { - rt_device_read(dev, 0, buf, sizeof(buf)); - rt_thread_mdelay(1000); - } - - LOG_I("poll thread exit"); - rt_device_close(dev); -} - -static void test_dev_thread(void) -{ - rt_device_t dev; - rt_thread_t tid; - - dev = rt_device_find(TEST_DEV_NAME); - if (dev == RT_NULL) - { - LOG_E("can not find device: %s", TEST_DEV_NAME); - return; - } - - if (rt_device_open(dev, RT_DEVICE_OFLAG_RDWR) != RT_EOK) - { - LOG_E("open failed"); - return; - } - - tid = rt_thread_create("devpoll", test_dev_poll_thread, dev, 1024, 20, 10); - if (tid == RT_NULL) - { - LOG_E("create thread failed"); - rt_device_close(dev); - return; - } - rt_thread_startup(tid); - - LOG_I("poll thread started, read device every 1s (10 times)"); -} -MSH_CMD_EXPORT(test_dev_thread, start a poll thread on test_dev); diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 5639c0e1666b7fe0838dea74837b18eaab3f72e5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,236 +0,0 @@ -## 一、RT-Thread 实时操作系统概述 - -### 1.发展历程 - -RT-Thread 是一个开源的实时操作系统,经过多年发展,已形成完整的生态体系。 - -### 2.整体架构 - -RT-Thread 采用分层设计,自底向上分为: - -- **组件RT-Thread软件包:**运行于RT-Thread操作系统上,面向不同应用领域的通用软件,由描述信息、源代码或库文件组成。RT-Thread提供。了开放的软件包平台,这里存放了官方提供或开。发者提供的软件包,该平台为开发者提供了众多可重用软件包的选择。因为这些软件包具有很强的可重用性,模块化程度很高,极大的方便应用开发者在最短时间内,打造出自己想要的系统。 -- **组件与服务层:**组件是基于RT-Thread内核之上的上层软件,例如虚拟文件系统、FinSH命令行界面、网络框架、设备框架等。采用模块化设计,做到组件内部高内聚,组件之间低耦合。 -- **内核层:**是RT-Thread的核心部分,包括了内核系统中对象的实现,例如多线程及其调度、信号量、邮箱、消息队列、内存管理、定时器等;libcpu/BSP(芯片移植相关文件/板级支持包)与硬件密切相关,由外设驱动和CPU移植构成。 - -> 官网:https://www.rt-thread.org/ - - - -## 二、工程创建与运行 - -### 1.工程创建 - -1、在RT-Thread Studio中,找到“文件->新建->RT-Thread项目”,单击“RT-Thread项目”,就可以打开图b所示“新建RT-Thread项目”选项卡。 - -2、如图,设置项目名称为“hello_rtt”、项目保存位置、选择项目所用芯片的厂商和型号等信息,单击“完成”,RT-Thread Studio就会帮我们新建一个名字为“hello_rtt”的RT-Thread项目。 - -![](C:\Users\lenovo\Desktop\学习\56.png) - -新建完项目后,在RT-Thread Studio的“项目资源管理器”中,可以看到项目的目录树 - -![](C:\Users\lenovo\Desktop\学习\57.png) - -项目树有多个分支,每个分支都有各自的作用,下表对项目树的各个分支进行说明: - -| 目录 | 描述 | -| ------------------ | ------------------------------------------------------------ | -| RT-Thread Settings | 对其双击可以打开RT-Thread的图形化配置工具 | -| CubeMX Settings | 对其双击可以打开STM32CubeMX图形化配置工具,对STM32芯片的硬件外设进行配置 | -| applications | 用户应用程序目录,所有应用程序都可以放到这里,其中包括main.c | -| Debug | 项目编译过程文件目录,如编译过程产生的.o文件等 | -| drivers | 和硬件平台相关的设备驱动文件目录 | -| libraries | 和平台相关的底层库。对于STM32平台,目前版本使用的STM32官方的HAL库作为平台底层驱动库 | -| linkscripts | 项目的链接脚本 | -| rt-thread | rt-thread内核代码 | -| rtconfig.h | rt-thread的配置文件,在RT-Thread Settings中所做的修改,都会改变这个文件,这个文件不能手动修改 | - -下载程序(调试器下载) - -![](C:\Users\lenovo\Desktop\学习\59.png) - -### 2.工程运行 - -#### (1)系统启动流程(关键函数) - -| 阶段 | 函数 | 作用 | -| :----------- | :---------------------------- | :--------------------------------- | -| 硬件初始化 | `rt_hw_board_init()` | 初始化时钟、串口、内存等 | -| 版本信息 | `rt_show_version()` | 输出系统横幅(RT-Thread 版本信息) | -| 定时器初始化 | `rt_system_timer_init()` | 初始化系统定时器 | -| 调度器初始化 | `rt_system_scheduler_init()` | 初始化调度器数据结构 | -| 应用初始化 | `rt_application_init()` | 创建 main 线程(应用入口) | -| 空闲线程 | `rt_thread_idle_init()` | 创建 idle 空闲线程 | -| 启动调度器 | `rt_system_scheduler_start()` | 开始调度就绪线程 | - -#### (2)线程状态转换 - -- 创建方式: - - `rt_thread_create()`:动态创建(自动分配 TCB 和栈) - - `rt_thread_init()`:静态初始化(用户提供 TCB 和栈) -- 状态流转:**初始态 → 就绪态 → 运行态 → 挂起态 → 关闭态** -- `rt_thread_startup()` 将线程从初始态转为就绪态。 -- 调度器启动后,根据优先级和时间片进行调度 - -#### (3)嵌入式工程编译流程 - -- **编辑** → **编译** → **汇编** → **烧录(下载)** -- 编译生成 `.elf` 和 `.bin` 文件,通过 ST-Link 烧录至 Flash。 - -#### (4)断点调试 - -- 设置断点后,程序运行至断点暂停,可查看变量、寄存器、调用栈。 -- 单步执行(Step Over / Step Into)帮助定位逻辑错误。 -- 调试完成后终止调试,再点击下载固化程序。 - -## 三、Markdown 语法规范 - -### 1.标题 - -- 一级:`# 标题` -- 二级:`## 标题` -- 三级:`### 标题` - (几级标题就几个 `#`,注意空格) - -### 2.代码段 - -- **行内代码**:用两个反引号包裹(``代码``),例如 `printf()`。 -- **代码块**:用三个反引号包裹并指定语言(如 `c ` ```),或直接使用三个反引号。 - -示例: - -``` - #include -int main() { - return 0; -}``` -``` - -### 3.表格 - -- 使用竖线 `|` 分隔列,横线 `---` 分隔表头与内容。 -- 对齐方式:左对齐 `:---`,居中 `:---:`,右对齐 `---:`。 - -示例: - -| t1 | t2 | -| :--: | :--: | -| DAY1 | DAY2 | - -快捷插入表格:`Ctrl + T` - -### 4.高亮与加粗 - -- **高亮**:使用反引号包裹(`高亮`)或特定语法(根据平台)。 -- **加粗**:使用两个星号包裹(**加粗**)。 - -### 5.图片管理 - -建议将图片保存至 `./figures` 目录,引用相对路径,防止迁移丢失。 - -## 四、Git 基础操作与开源协作 - -### 1. 安装与配置 - -- 安装 Git 和 TortoiseGit(可选汉化包)。 -- 注册 Gitee 账号,新建仓库。 -- 在本地文件夹右键 `Open Git Bash here` 打开终端。 - -#### (1)配置用户信息 - -bash - -``` -git config --global user.name "你的姓名" -git config --global user.email"你的邮箱" -git config --list #查看配置 -``` - -(去掉 `--global` 仅对当前仓库生效) - -查看配置:`git config --list` - -#### (2)生成 SSH 公钥(免密推送) - -bash - -``` -ssh-keygen -t rsa -b 4096 -C "your_email@example.com"#生成密钥 -cat ~/.ssh/id_ras.pub #查看公钥 -``` - -将公钥复制到 Gitee → 设置 → SSH 公钥 → 添加。 - -验证:`ssh -T git@gitee.com` 显示 `successfully` 即成功。 - -#### (3)设置远程仓库 URL(SSH 方式) - -bash - -``` -git remote set-url origin git@gitee.com:your_username/your_repo.git -``` - -### 2. 常用 Git 命令速查 - -| 命令 | 作用 | -| :---------------------------------- | :------------------------- | -| `git init` | 初始化当前目录为仓库 | -| `git pull origin master` | 拉取远程 master 分支 | -| `git add ` | 添加文件到暂存区 | -| `git commit -m "message"` | 提交暂存内容并添加日志 | -| `git push origin master` | 推送本地 master 到远程 | -| `git fetch origin master` | 仅下载远程更新(不合并) | -| `git remote -v` | 查看远程仓库地址 | -| `git checkout -b ` | 创建并切换到新分支 | -| `git switch ` | 切换分支 | -| `git stash` | 暂存当前未提交修改 | -| `git branch -d ` | 删除本地分支(安全) | -| `git branch -D ` | 强制删除本地分支 | -| `git push origin --delete ` | 删除远程分支 | -| `git fetch -p` | 清理本地残留的远程分支记录 | - -### 3. 分支操作与同步 - -#### (1)创建并推送新分支 - -bash - -``` -git checkout -b test # 创建并切换 -git add . -git commit -m "说明" -git push origin test # 首次推送需指定远程分支 -# 或 git push -u origin test # 关联上游,之后可直接 git push -``` - -#### (2)查看分支 - -bash - -``` -git branch # 本地 -git branch -r # 远程 -git branch -a # 所有(本地+远程) -``` - -#### (3)合并与变基 - -- **merge**:产生合并节点,保留分叉历史,较安全。 -- **rebase**:将提交“嫁接”到目标分支,使历史线性,但风险较高。 - -#### (4)解决合并冲突 - - 冲突原因:多个分支基于不同时间点修改同一文件。 - -1. 同步上游(fork 仓库点击“同步”)。 -2. 在冲突分支执行 `git merge` 或 `git rebase`。 -3. 手动解决冲突,然后 `git add` 和 `git commit`。 -4. 推送后 PR 自动更新。 - -### 4. Fork 与 PR 流程 - -- **Fork** 上游仓库(保持原仓库不变)。 -- 在本地克隆自己的 fork,创建功能分支(如 `day1`)。 -- 修改并推送到自己的远程分支。 -- 在 Gitee 上点击“创建 PR”,选择源分支与目标分支(上游 master),填写标题与描述,指定审核人(如小组长)。 -- 审核通过后合并。 \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 6ba8185240b5658fe234b93f931c027027f65c35..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,319 +0,0 @@ -# RT-Thread 线程 IPC 理论 - -> 核心原则:**同步解决“何时运行、谁能访问”,通信解决“传递什么数据”。** - -## 1. 概念 - -### 1.1 同步、互斥与通信 - -- **临界区**:多个线程都会访问的共享数据或代码区域。 -- **互斥**:同一时刻只允许一个线程进入临界区,重点是保护资源。 -- **同步**:按照约定的先后顺序运行,重点是协调事件或工作阶段。 -- **通信**:在线程之间传递数值、指针或数据块。 - -没有同步保护时,线程可能在另一个线程尚未完成写入时读取共享数据,产生竞态和数据不一致。阻塞对象在资源不可用时可使线程挂起;资源到达、对象释放或超时后,线程再被唤醒。 - -### 1.2 RT-Thread IPC 的共同模型 - -```mermaid -flowchart LR - A[线程 / 中断产生事件] --> B{IPC 对象} - B -->|同步| C[等待条件满足] - B -->|通信| D[缓存数据] - C --> E[线程被唤醒] - D --> E - E --> F[处理临界区或消息] -``` - -所有 IPC 对象都包含控制块、等待队列和对象生命周期。等待队列通常可选 `RT_IPC_FLAG_PRIO`(按优先级)或 `RT_IPC_FLAG_FIFO`(先进先出);实时任务通常优先使用 `PRIO`。 - -## 2. 五类 IPC 如何选 - -| 对象 | 解决的问题 | 数据能力 | 关键语义 | 典型场景 | -| --- | --- | --- | --- | --- | -| 信号量 Semaphore | 通知或资源计数 | 不传数据 | 计数可累加,无所有权 | 中断通知线程、生产者/消费者资源数 | -| 互斥量 Mutex | 保护共享资源 | 不传数据 | 有所有者、可递归、支持优先级继承 | 保护共享变量、设备或临界区 | -| 事件集 Event | 等待多个条件 | 仅事件位 | 32 个标志位,支持 AND/OR;事件不排队 | “任一条件”或“全部条件”满足后唤醒 | -| 消息邮箱 Mailbox | 低开销传递单值 | 一个机器字(32 位目标通常为 4 字节) | 邮箱有容量;可发送紧急邮件 | 传整数、状态码或缓冲区指针 | -| 消息队列 Message Queue | 传递结构化数据 | 单条大小由创建时 `msg_size` 限制 | 普通消息入队尾,紧急消息入队首 | 串口帧、命令、结构体或数据块 | - -快速判断:**只需要唤醒,用信号量;需要保护资源,用互斥量;要等多个条件,用事件集;只传一个机器字,用邮箱;要传结构体或缓冲区,用消息队列。** - -## 3. 线程间同步 - -### 3.1 信号量 - -信号量的值表示可用实例数。`take` 成功时值减一;值为零时,线程按等待策略阻塞或按超时返回;`release` 会唤醒等待线程或使值加一。 - -| 类型 | 初始值 | 用途 | -| --- | ---: | --- | -| 二值信号量 | 常用 `0` | 事件通知;例如中断释放,线程处理 | -| 计数信号量 | 资源数量 | 表示可用缓冲区、空位或设备实例数 | - -常用 API: - -| 动态对象 | 静态对象 | 运行操作 | -| --- | --- | --- | -| `rt_sem_create` / `rt_sem_delete` | `rt_sem_init` / `rt_sem_detach` | `rt_sem_take`、`rt_sem_trytake`、`rt_sem_release` | - -要点:信号量没有“谁持有、谁释放”的所有权约束;它可用于同步,也可用于计数,但不应替代需要所有权和优先级继承的互斥量。 - -### 3.2 互斥量 - -互斥量是带所有权语义的二值锁:成功获取后只有持有者可以释放;同一线程可递归获取,递归次数需匹配释放次数。RT-Thread 的优先级继承会在高优先级线程等待低优先级持有的互斥量时临时提升持有者优先级,从而缓解优先级反转。 - -常用 API: - -| 动态对象 | 静态对象 | 运行操作 | -| --- | --- | --- | -| `rt_mutex_create` / `rt_mutex_delete` | `rt_mutex_init` / `rt_mutex_detach` | `rt_mutex_take`、`rt_mutex_trytake`、`rt_mutex_release` | - -使用原则:只在临界区内持有锁,统一多个锁的获取顺序;互斥量能缓解优先级反转,但不能自动消除死锁。 - -### 3.3 事件集 - -事件集用一个 32 位无符号数表示 32 个独立事件位,只用于同步,不携带数据,也没有计数排队能力。 - -- `RT_EVENT_FLAG_OR`:关注的事件中任意一个到达即可唤醒。 -- `RT_EVENT_FLAG_AND`:关注的事件全部到达才唤醒。 -- `RT_EVENT_FLAG_CLEAR`:成功接收后清除对应事件位;不设置则事件位继续保留。 - -常用 API:`rt_event_create` / `rt_event_delete`、`rt_event_init` / `rt_event_detach`、`rt_event_send`、`rt_event_recv`。 - -同一事件在清除前重复发送,效果等同于发送一次;需要累计次数时应使用信号量。 - -## 4. 线程间通信 - -### 4.1 消息邮箱 - -邮箱每次传递一个 `rt_ubase_t` 机器字。4 字节只适用于 32 位目标,不能把它当作所有平台都固定为 4 字节。邮箱容量在创建或初始化时确定,邮件满时普通发送会立即返回;需要等待空间时使用 `rt_mb_send_wait`。 - -常用 API: - -| 动态对象 | 静态对象 | 发送与接收 | -| --- | --- | --- | -| `rt_mb_create` / `rt_mb_delete` | `rt_mb_init` / `rt_mb_detach` | `rt_mb_send`、`rt_mb_send_wait`、`rt_mb_urgent`、`rt_mb_recv` | - -邮箱适合传整数、状态码或指针。传指针时,指向的缓冲区必须在线程接收并处理完成前保持有效;中断服务例程可用邮箱发送轻量通知,数据本体应放在安全的共享缓冲区中。 - -### 4.2 消息队列 - -消息队列在创建时指定 `msg_size`(单条消息最大长度)和队列容量,消息块由内核管理。它不是无限长或任意长度队列:实际发送长度不能超过 `msg_size`,队列满时应选择立即返回或带超时等待。 - -常用 API: - -| 动态对象 | 静态对象 | 发送与接收 | -| --- | --- | --- | -| `rt_mq_create` / `rt_mq_delete` | `rt_mq_init` / `rt_mq_detach` | `rt_mq_send`、`rt_mq_send_wait`、`rt_mq_urgent`、`rt_mq_recv` | - -普通消息进入队尾;`rt_mq_urgent` 将消息插入队首,适合退出、故障或高优先级控制命令。消息队列会把消息内容复制到队列中,发送方可在发送返回后复用源缓冲区;若消息本身是指针,仍需管理指针指向对象的生命周期。 - -## 5. API 生命周期与等待 - -### 5.1 动态与静态对象 - -| 方式 | 内存来源 | 结束操作 | 适用情况 | -| --- | --- | --- | --- | -| 动态创建 | 内核动态分配 | `delete` | 对象数量或生命周期运行时决定 | -| 静态初始化 | 用户提供控制块/缓冲区 | `detach` | 启动时固定、对内存可控或不使用堆 | - -创建和初始化必须与删除和脱离匹配。对象仍有线程等待时,不要随意销毁;删除或脱离会唤醒等待者并返回错误,业务代码应检查返回值。 - -### 5.2 超时参数 - -- `RT_WAITING_NO`:不等待,条件不满足立即返回。 -- `RT_WAITING_FOREVER`:永久等待,直到条件满足或对象被删除/脱离。 -- 其他非负值:等待指定的 OS Tick 数,超时返回 `-RT_ETIMEOUT`。 - -阻塞调用应根据业务实时性设置超时,并对 `RT_EOK`、超时和对象错误分别处理;不要用永久等待掩盖线程无法推进的问题。 - -## 6. 常见问题 - -1. **把信号量当互斥锁**:信号量没有所有权,可能被错误线程释放;保护共享资源应使用互斥量。 -2. **忽略优先级反转**:共享资源采用互斥量,并缩短持锁时间;多个锁统一获取顺序。 -3. **误解事件集**:事件位不是计数器,重复事件不会累加;需要次数语义时改用信号量或消息队列。 -4. **邮箱传递失效指针**:邮箱只传机器字,不复制指针指向的数据;接收方使用期间必须保证缓冲区生命周期。 -5. **消息队列长度未规划**:创建时根据最大消息大小和峰值数量配置 `msg_size` 与容量,队列满时明确丢弃、等待或降级策略。 -6. **FIFO 影响实时性**:除非确实需要先来后到,否则 IPC 等待队列优先选择 `RT_IPC_FLAG_PRIO`。 -7. **只依赖调试现象**:线程调度具有时序性,数据“看起来正常”不等于没有竞态;共享数据必须有明确的同步协议。 - -## 7. SCons 构建配置速查 - -SCons 是 RT-Thread 使用的 Python 构建系统。它把源码组织、配置裁剪和工具链参数分开管理: - -```mermaid -flowchart LR - K[Kconfig / menuconfig] --> R[rtconfig.h] - P[rtconfig.py] --> S[SCons] - C[SConstruct] --> S - D[各目录 SConscript] --> S - R --> D - S --> O[目标文件 / 工程 / 固件] -``` - -- `SConstruct`:BSP 的构建入口,一个 BSP 通常只有一个。 -- `SConscript`:各目录的子构建脚本,组织源文件、头文件路径和依赖组。 -- `rtconfig.py`:选择 `CROSS_TOOL`、工具链路径以及编译/汇编/链接参数。 -- `Kconfig` 与 `menuconfig`:配置功能并生成 `rtconfig.h`,SCons 据此裁剪构建内容。 - -常用命令: - -| 命令 | 作用 | -| --- | --- | -| `scons` | 在 BSP 目录执行增量编译 | -| `scons -c` | 清除生成的目标文件 | -| `scons -jN` | 使用 N 路并行编译 | -| `scons --verbose` | 显示完整编译参数 | -| `scons --target=mdk5` / `--target=iar` | 生成对应 IDE 工程 | -| `scons --dist` | 生成可独立分发的 BSP 工程 | - -模块的最小 `SConscript` 结构如下,`depend` 用于让 Kconfig 宏控制该模块是否参与构建: - -```python -from building import * - -cwd = GetCurrentDir() -src = Glob('*.c') -group = DefineGroup('App', src, - depend=['RT_USING_APP'], - CPPPATH=[cwd]) -Return('group') -``` - -配置 IPC 功能时,先通过 BSP 的 `menuconfig` 使能对应内核组件,再重新执行 `scons`;若需要更新 MDK/IAR 等工程文件,继续执行相应的 `--target` 命令。 - -## 8. 工程化延伸:线程参数也有生命周期 - -四个练习在创建线程时都向 `rt_thread_create()` 传入了 `parameter`。这个参数看起来不起眼,却很容易成为并发程序中的隐藏错误:**内核只保存指针,不会自动复制指针指向的数据。** - -参考代码采用了两种安全做法: - -- `task1.c` 为每辆车动态分配一份独立参数,线程使用完后释放; -- `task2.c`、`task3.c`、`task4.c` 使用静态参数,参数的生命周期覆盖线程运行期。 - -### 8.1 一个容易写出的错误实例 - -下面把局部变量的地址交给线程,看似每次都传入了不同的通道号,实际并不安全: - -```c -static void start_sensor_threads(void) -{ - int i; - - for (i = 0; i < 2; i++) - { - sensor_arg_t arg; - - arg.channel = i; - arg.output = g_sample_mq; - - /* 错误:arg 是循环中的局部变量 */ - create_and_start_sensor(&arg); - } -} -``` - -问题不在于线程有没有立即启动,而在于 `arg` 的存储时间太短: - -1. 下一次循环可能覆盖同一块栈内存,两个线程便可能读到相同的参数; -2. 函数返回后,`arg` 已经失效,线程再次访问属于悬空指针; -3. 加延时只能改变错误出现的概率,不能修复生命周期问题。 - -> 判断标准:传给线程的不是“一个值”,而是“一个地址”。这个地址指向的对象,必须至少存活到线程最后一次访问它。 - -### 8.2 固定配置:使用静态参数 - -若线程参数在整个程序运行期间都固定不变,可以使用静态存储: - -```c -typedef struct -{ - rt_uint8_t channel; - rt_mq_t output; -} sensor_arg_t; - -static sensor_arg_t g_sensor_args[2]; - -static void prepare_sensor_args(void) -{ - g_sensor_args[0].channel = 0; - g_sensor_args[0].output = g_sample_mq; - g_sensor_args[1].channel = 1; - g_sensor_args[1].output = g_sample_mq; -} -``` - -创建线程时分别传入 `&g_sensor_args[0]` 和 `&g_sensor_args[1]`。静态对象不会随启动函数返回而失效,但线程启动后不要在没有同步保护的情况下继续修改这些参数。 - -### 8.3 动态配置:明确参数所有权 - -如果每次启动的参数都不同,可以为每个线程单独分配内存。下面只展示参数交接的核心写法,采集业务函数省略: - -```c -static void sensor_entry(void *parameter) -{ - sensor_arg_t local = *(sensor_arg_t *)parameter; - - /* 参数已复制到本线程栈中,尽早释放堆内存 */ - rt_free(parameter); - - collect_sensor_data(local.channel, local.output); -} - -static rt_err_t start_one_sensor(rt_uint8_t channel, rt_mq_t output) -{ - sensor_arg_t *arg; - rt_thread_t tid; - rt_err_t result; - char name[RT_NAME_MAX]; - - arg = (sensor_arg_t *)rt_malloc(sizeof(*arg)); - if (arg == RT_NULL) - return -RT_ENOMEM; - - arg->channel = channel; - arg->output = output; - rt_snprintf(name, sizeof(name), "sensor%u", (unsigned int)channel); - - tid = rt_thread_create(name, sensor_entry, arg, 512, 15, 10); - if (tid == RT_NULL) - { - rt_free(arg); /* 创建失败,仍由启动者释放 */ - return -RT_ERROR; - } - - result = rt_thread_startup(tid); - if (result != RT_EOK) - { - rt_thread_delete(tid); - rt_free(arg); /* 启动失败,所有权尚未移交 */ - return result; - } - - /* 启动成功后,arg 归工作线程所有,启动者不能再访问 */ - return RT_EOK; -} -``` - -参数所有权的变化可以画成一条清晰的路径: - -```mermaid -flowchart LR - A[启动者分配参数] --> B[填充参数并创建线程] - B --> C{线程启动成功?} - C -->|否| D[启动者删除线程并释放参数] - C -->|是| E[所有权交给工作线程] - E --> F[线程复制参数] - F --> G[线程释放原参数] - G --> H[使用局部副本工作] -``` - -### 8.4 三种需求如何选 - -| 参数需求 | 推荐做法 | 原因 | -| --- | --- | --- | -| 配置固定,且整个运行期都有效 | 静态或全局参数 | 无需动态分配,生命周期容易保证 | -| 每个线程有一份独立的启动配置 | 动态分配并移交所有权 | 各线程互不覆盖,退出或复制后可释放 | -| 线程运行期间还要持续接收新数据 | 消息队列或邮箱 | `parameter` 适合初始配置,不适合充当持续通信通道 | - -最后分清三个互不相同的生命周期:**线程对象何时退出、参数内存何时释放、参数中引用的 IPC 对象何时删除。** 参数结构体已经安全释放,并不代表其中保存的消息队列或信号量句柄也可以提前删除。 diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 2de9cb30959c337ebc1093c9ebe6420d204ed0b5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,328 +0,0 @@ -## 1. STM32F407 启动流程与 RT-Thread 引导机制 - -### 1.1 从上电复位到用户 `main()` - -STM32F407 复位后,Cortex-M4 先从中断向量表读取两个值: - -- `vector[0]`:主栈顶地址,装入 MSP; -- `vector[1]`:复位入口地址,装入 PC,开始执行 `Reset_Handler`。 - -常见的 MDK 工程为例,完整调用链如下: - -```mermaid -%%{init: {"theme": "base", "themeVariables": {"fontSize": "18px"}}}%% -flowchart LR - A["1. 复位入口
向量表 -> Reset_Handler
-> SystemInit() -> __main"] - B["2. RT-Thread 接管
$Sub$$main() -> rtthread_startup()
关中断 -> rt_hw_board_init()"] - C["3. 内核准备
rt_show_version()
-> rt_system_timer_init()
-> rt_system_scheduler_init()
-> [rt_system_signal_init()]"] - D["4. 创建系统线程
rt_application_init()
-> [rt_system_timer_thread_init()]
-> rt_thread_idle_init()"] - E["5. 进入应用
rt_system_scheduler_start()
-> 首次上下文切换
-> main_thread_entry()
-> rt_components_init()
-> $Super$$main()"] - A --> B --> C --> D --> E -``` - -> 图中“可选”步骤由配置项决定是否启用。`rt_system_scheduler_start()` 正常情况下不会返回。 - -#### 三个容易混淆的点 - -| 问题 | 结论 | -| ------------------------------------------- | ------------------------------------------------------------ | -| `SystemInit()` 是否完成 168 MHz 主频配置? | 不一定。它负责芯片早期底层配置;具体 PLL 和总线时钟通常由 BSP 的 `SystemClock_Config()` 在 `rt_hw_board_init()` 中完成。以实际 BSP 为准。 | -| `rt_system_timer_init()` 是否配置 SysTick? | 不是。它初始化内核定时器数据结构;硬件 Tick 通常在 `rt_hw_board_init()` 中配置。 | -| 调度器启动后谁先运行? | 最高优先级的就绪线程。若启用且软件定时器线程优先级更高,它可能先运行并随即阻塞;之后才轮到 main 线程。 | - -不同工具链只改变“C 运行库到 `rtthread_startup()`”这一小段: - -| 工具链 | RT-Thread 接管入口 | 用户 `main()` 的调用方式 | -| ------------------ | -------------------- | ------------------------------ | -| MDK / Arm Compiler | `$Sub$$main()` | main 线程调用 `$Super$$main()` | -| GCC | 链接入口 `entry()` | main 线程调用 `main()` | -| IAR | `__low_level_init()` | main 线程调用 `main()` | - -### 1.2 自动初始化:`INIT_BOARD_EXPORT` 与 `INIT_APP_EXPORT` - -自动初始化的本质是“**编译时登记函数指针,启动时按区间遍历**”。模块只需导出初始化函数,无需集中修改一个总初始化函数。 - -```c -typedef int (*init_fn_t)(void); - -#define INIT_EXPORT(fn, level) \ - rt_used const init_fn_t __rt_init_##fn \ - rt_section(".rti_fn." level) = fn - -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") -#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2") -#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3") -#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4") -#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") -``` - -> 只有启用 `RT_USING_COMPONENTS_INIT` 时,上述宏才会建立初始化表;关闭该配置时,导出宏为空。新版本可能在 1 级板级初始化中继续细分阶段,具体以工程的 `rtdef.h` 和链接脚本为准。 - -```mermaid -flowchart LR - A["INIT_xxx_EXPORT(fn)"] --> B["函数指针放入 .rti_fn.N 段"] - B --> C["链接器收集并按段名排列"] - C --> D["形成连续初始化函数表"] - D --> E["rt_components_board_init
或 rt_components_init 遍历调用"] -``` - -内核通过 `rti_board_start`、`rti_board_end`、`rti_end` 等哨兵确定遍历边界: - -- `rt_components_board_init()` 只执行 1 级函数,通常在 `rt_hw_board_init()` 中调用; -- `rt_components_init()` 执行 2~6 级函数,由 main 线程在用户 `main()` 之前调用。 - -| 级别 | 导出宏 | 执行上下文 | 典型用途 | -| :--: | ----------------------- | ------------ | ---------------------------- | -| 1 | `INIT_BOARD_EXPORT` | 调度器启动前 | GPIO、串口等板级硬件初始化 | -| 2 | `INIT_PREV_EXPORT` | main 线程 | 依赖较少的纯软件初始化 | -| 3 | `INIT_DEVICE_EXPORT` | main 线程 | 设备驱动注册 | -| 4 | `INIT_COMPONENT_EXPORT` | main 线程 | DFS、网络等组件初始化 | -| 5 | `INIT_ENV_EXPORT` | main 线程 | 文件系统挂载等运行环境初始化 | -| 6 | `INIT_APP_EXPORT` | main 线程 | 用户应用初始化 | - -关键差异: - -| `INIT_BOARD_EXPORT` | `INIT_APP_EXPORT` | -| ---------------------------- | ------------------------------- | -| 调度器尚未启动 | 已进入 main 线程上下文 | -| 不应阻塞,也不能依赖线程调度 | 可使用已初始化的线程与 IPC 服务 | -| 适合最早期硬件准备 | 适合业务模块启动 | - -> 链接器保证不同级别按段排列;同一级别内的先后次序不应作为模块依赖契约。存在依赖时,应调整初始化级别,或在同一初始化函数中显式控制顺序。 - ---- - -## 2. 内核基础与对象模型 - -### 2.1 对象容器与控制块 - -RT-Thread 用 C 语言模拟面向对象:派生对象把 `struct rt_object parent` 放在首成员,从而复用名称、类型、标志和对象链表等公共能力。 - -```text -rt_object -├── rt_thread 线程 -├── rt_timer 定时器 -├── rt_device 设备 -└── rt_ipc_object IPC 基类 - ├── rt_semaphore / rt_mutex - ├── rt_event - └── rt_mailbox / rt_messagequeue -``` - -`rt_object_container[]` 按对象类型维护全局链表。对象初始化或创建后挂入对应容器,可统一完成对象查找、类型检查和线程列表展示。静态对象使用 `rt_object_init()` 登记,动态对象使用 `rt_object_allocate()` 分配并登记。 - -### 2.2 `struct rt_thread` 关键成员 - -线程控制块(TCB)保存线程的身份、调度属性、栈和等待信息,是调度器管理线程的核心数据结构。 - -| 成员 | 作用 | -| --------------------------- | ------------------------------------------------------------ | -| `parent` | 继承的 `rt_object`,提供名称、类型、静态/动态属性和对象链表节点 | -| `sp` | 当前线程栈指针。切出时保存现场位置,切入时据此恢复上下文 | -| `entry` / `parameter` | 线程入口函数及其参数 | -| `stack_addr` / `stack_size` | 栈空间起始地址与大小,用于上下文、局部变量和栈使用检查 | -| `stat` | 当前状态及附加状态标志,是就绪、挂起、关闭等转换的判断依据 | -| `current_priority` | 当前有效优先级;发生互斥量优先级继承时可能临时改变 | -| `init_priority` | 创建时的基础优先级,优先级继承结束后用于恢复 | -| `init_tick` | 每轮时间片的初值,单位为 OS Tick | -| `remaining_tick` | 本轮剩余时间片,每个 Tick 递减 | -| `tlist` / 调度节点 | 挂入就绪队列或等待队列;具体字段组织随版本变化 | -| `thread_timer` | 线程内置单次定时器,用于延时和阻塞超时 | - -Cortex-M 栈通常从高地址向低地址增长。线程第一次运行前,`rt_hw_stack_init()` 会预先构造入口地址、参数、返回地址和寄存器初值;此后上下文切换只需保存或恢复该线程自己的栈。 - -### 2.3 静态线程与动态线程 - -| 维度 | 静态线程 | 动态线程 | -| -------- | -------------------------------------------------------- | ------------------------------------------------------------ | -| API | `rt_thread_init()` | `rt_thread_create()` | -| TCB 与栈 | 由调用者提供,通常位于全局/静态存储区 | 从系统堆分配 TCB 和栈 | -| 前提 | 不依赖系统堆 | 需启用并初始化动态堆 | -| 生命周期 | 由调用者控制,存储空间可长期保留并复用 | 通常随线程创建、退出和删除而变化 | -| 结束处理 | `rt_thread_detach()` 脱离内核对象;用户内存不会被 `free` | `rt_thread_delete()` 或入口返回后进入关闭态,由 idle 线程完成延迟回收 | -| 主要风险 | 栈尺寸配置不当、对象被过早复用 | 分配失败、碎片、泄漏和分配耗时波动 | - -某些高可靠项目规定线程必须静态创建,原因不是“静态一定更快”,而是它更容易证明系统行为: - -1. 启动前即可计算 TCB 与栈的总内存上界; -2. 运行期无堆分配失败和外部碎片; -3. 创建路径耗时稳定,故障分支更少; -4. 所有权和生命周期清晰,便于审查与认证。 - -静态创建仍需正确估算栈空间并监测栈余量,它不能自动避免栈溢出。 - ---- - -## 3. 线程状态机与调度算法 - -### 3.1 五态模型 - -```mermaid -stateDiagram-v2 - [*] --> 初始态: rt_thread_init / rt_thread_create - 初始态 --> 就绪态: rt_thread_startup - 就绪态 --> 运行态: 被调度器选中 - 运行态 --> 就绪态: 高优先级抢占 / 时间片到 / yield - 运行态 --> 挂起态: delay / IPC 等待 / suspend - 挂起态 --> 就绪态: 超时 / 资源到达 / resume - 初始态 --> 关闭态: detach / delete - 就绪态 --> 关闭态: detach / delete - 挂起态 --> 关闭态: detach / delete - 运行态 --> 关闭态: 入口返回 / rt_thread_exit - 关闭态 --> [*]: 动态资源由 idle 回收;静态存储由用户管理 -``` - -| 状态 | 含义 | 典型进入方式 | -| -------------------------- | -------------------------------------- | ------------------------------------------------------------ | -| 初始态 `RT_THREAD_INIT` | 已创建但未进入就绪队列,不参与调度 | `rt_thread_init()`、`rt_thread_create()` | -| 就绪态 `RT_THREAD_READY` | 运行条件满足,按优先级等待 CPU | `rt_thread_startup()`、超时或资源到达 | -| 运行态 `RT_THREAD_RUNNING` | 当前占用 CPU;单核系统同一时刻只有一个 | 调度器选中 | -| 挂起态 `RT_THREAD_SUSPEND` | 等待时间或资源,不参与调度 | `rt_thread_delay()`、`rt_sem_take()`、`rt_mutex_take()`、`rt_mq_recv()` 等 | -| 关闭态 `RT_THREAD_CLOSE` | 不再参与调度,等待清理 | 入口返回、`rt_thread_exit()`、删除或脱离 | - -`rt_thread_suspend()` / `rt_thread_resume()` 适合成对控制“显式挂起”的线程。因信号量、互斥量或消息队列而挂起的线程,应由对应 IPC 对象或超时机制唤醒,不能用 `rt_thread_resume()` 强行打断等待关系。 - -### 3.2 抢占式优先级调度 - -RT-Thread 采用固定优先级抢占调度:**优先级数值越小,优先级越高**。优先级数量由 `RT_THREAD_PRIORITY_MAX` 配置,常见为 32,最大可为 256。 - -```mermaid -flowchart LR - A["线程变为 READY"] --> B["加入 current_priority 对应就绪链表"] - B --> C["更新优先级位图"] - C --> D["位图查找最高就绪优先级,O(1)"] - D --> E["取该优先级链表头线程"] - E --> F["必要时触发上下文切换"] -``` - -- 不超过 32 个优先级时,一个 32 位位图即可定位最高就绪优先级; -- 超过 32 个优先级时,使用“组位图 + 组内位图”的两级查找; -- 每个优先级对应一条就绪链表,同优先级线程在链表内轮转。 - -典型调度触发点: - -1. `rt_thread_startup()`、IPC 释放或超时使更高优先级线程就绪; -2. 当前线程延时、等待 IPC、主动 `yield` 或退出; -3. SysTick 使当前线程时间片耗尽; -4. 中断唤醒更高优先级线程。中断中只登记切换请求,实际切换在中断退出时完成。 - -Cortex-M 通常借助 PendSV 完成上下文切换:硬件自动保存异常现场,移植层保存其余寄存器,并把栈位置写入当前线程的 `sp`;切入时执行相反过程。调度锁或临界区期间,切换会推迟到允许调度时。 - -### 3.3 时间片轮转 - -时间片只解决“**多个就绪线程优先级相同**”时谁先运行,不会削弱高优先级线程的抢占权。 - -```text -线程开始本轮运行 - -> 每个 OS Tick:remaining_tick-- - -> remaining_tick > 0:继续运行 - -> remaining_tick == 0:恢复为 init_tick,移到同优先级队列尾部 - -> 调度同优先级的下一个线程 -``` - -| 场景 | 调度结果 | -| -------------------------- | ------------------------------------ | -| 更高优先级线程就绪 | 立即抢占;当前线程保留未用完的时间片 | -| 同优先级且当前时间片耗尽 | 当前线程排到队尾,下一线程运行 | -| 同优先级但只有一个就绪线程 | 没有可轮转对象,仍由它运行 | -| 低优先级线程就绪 | 不会抢占当前线程 | - -工程上应优先让线程阻塞等待真实事件,如信号量、消息队列或周期定时,而不是持续空转。高优先级线程若长期不阻塞,会使低优先级线程和 idle 线程饥饿,动态线程资源回收与低功耗处理也会受影响。 - -### 3.4 工程实践补充 - -#### 从最小系统开始验证 - -面对“编译成功但无法运行”或启动后 HardFault,不要一次启用全部组件。更稳妥的顺序是: - -1. 用 Env 的 `menuconfig` 或 RT-Thread Studio 的 Settings 检查配置,暂时关闭无关组件和外设; -2. 先验证系统时钟、系统 Tick、控制台和一个简单 LED 线程; -3. 再逐项启用驱动、组件和软件包,每次修改后重新编译验证并保留可回退版本。 - -#### 线程参数检查表 - -| 检查项 | 建议 | 判断依据 | -| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| 优先级 | 按响应期限分配,同时检查 main、timer(启用软定时器时)等系统线程 | 只要高优先级线程就绪,它就能抢占低优先级线程 | -| 时间片 | 重点检查同优先级线程;没有同优先级竞争时无需反复调大 | 时间片只参与同优先级轮转 | -| 线程栈 | 先留合理余量,再通过 `list_thread` / `ps` 观察最大占用并调整 | 过小会溢出,过大则浪费 RAM;显示列名随版本而异 | -| 阻塞点 | 业务循环优先等待信号量、消息队列或事件;周期任务可使用延时 | 防止高优先级线程持续占用 CPU | -| idle | 可用 idle 钩子观察系统是否还有空闲时间,但钩子必须短且不能阻塞 | idle 能运行表示 CPU 有余量,不代表所有线程都满足实时期限 | -| 状态查看 | 把 `list_thread` / `ps` 当作瞬时快照 | shell 线程通常显示运行态,其他线程显示挂起态不等于从未被调度 | - -#### 三类“定时器”不要混淆 - -| 类型 | 本质 | 回调上下文 | 使用原则 | -| ---------------------- | ------------------------- | -------------- | -------------------------------------------------- | -| MCU 硬件定时器 | 芯片 TIM 等外设 | 外设中断 | 精度高;中断处理应短小 | -| RT-Thread `HARD_TIMER` | 基于 OS Tick 的软件定时器 | 系统 Tick 中断 | 默认模式;不能阻塞,避免耗时操作 | -| RT-Thread `SOFT_TIMER` | 基于 OS Tick 的软件定时器 | timer 线程 | 需启用 `RT_USING_TIMER_SOFT`;多个长回调会相互延迟 | - -定时器回调适合“触发动作”,耗时业务更适合通知专用线程处理。 - ---- - -## 4. SCons 构建要点 - -### 4.1 文件分工 - -| 文件 | 职责 | -| ------------------------ | --------------------------------------------- | -| `SConstruct` | BSP 的构建入口,装载环境并递归组织工程 | -| `SConscript` | 描述当前模块的源文件、头文件、依赖和编译参数 | -| `rtconfig.py` | 选择工具链并设置编译、汇编和链接参数 | -| `Kconfig` | 定义可配置选项及依赖关系 | -| `.config` / `rtconfig.h` | 保存配置结果,并向构建脚本和 C 代码提供配置值 | - -### 4.2 常用命令 - -| 命令 | 作用 | -| --------------------- | ------------------------ | -| `scons` | 增量编译当前 BSP | -| `scons -c` | 清理构建产物 | -| `scons -j4` | 使用 4 个并行任务编译 | -| `scons --target=mdk5` | 生成 Keil MDK5 工程 | -| `scons --target=iar` | 生成 IAR 工程 | -| `scons --dist` | 导出可独立发布的工程目录 | - -### 4.3 `SConscript` 最小示例 - -```python -from building import * - -cwd = GetCurrentDir() -src = Split(''' -shell.c -msh.c -''') - -if GetDepend('MSH_USING_BUILT_IN_COMMANDS'): - src += ['cmd.c'] - -group = DefineGroup( - 'Finsh', - src, - depend=['RT_USING_FINSH'], - CPPPATH=[cwd] -) - -Return('group') -``` - -- `depend`:控制整个 Group 是否加入构建; -- `GetDepend()`:在脚本中按配置选择源文件; -- `CPPPATH` / `CPPDEFINES`:添加头文件目录和宏; -- `CFLAGS` / `CXXFLAGS` / `LIBS` / `LIBPATH`:设置编译选项和库。 - -```mermaid -flowchart LR - A["Kconfig"] --> B["menuconfig"] - B --> C[".config / rtconfig.h"] - C --> D["GetDepend / depend"] - D --> E["SConscript 选择源码"] - E --> F["scons 编译"] - E --> G["--target 生成 IDE 工程"] -``` - -使用 Keil 或 IAR 工程时,修改配置或源码分组后应重新执行相应的 `scons --target=...`,使 IDE 工程与 SCons 描述保持一致。 \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index fbae950ef2f0ecd2ca44222dae39fee640cf3a8d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\207\221\351\230\263\351\230\263/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,424 +0,0 @@ -## 一、为什么需要设备驱动框架 - -**痛点:外设 API 碎片化** - -同样是 SPI 发送数据,不同厂商的函数各不相同: - -| 厂商 | SPI 发送 API | -| ---- | ------------------------------ | -| GD | `spi_i2s_data_transmit` | -| ST | `HAL_SPI_Transmit` | -| NXP | `LPSPI_MasterTransferBlocking` | -| LPC | `SPI_MasterTransferBlocking` | - -后果:项目换 MCU,驱动基本推倒重写;同事之间代码无法复用;每换一家厂商都要重新学习。 - -**方案:驱动与设备代码分离** - -``` -应用层(设备驱动代码:读写 W25Q128、驱动 RW007 …)—— 与硬件无关 - ↕ 一套统一 API -对接驱动(每家 MCU 一套)—— 只负责适配硬件 -``` - -**演进路径**:先为 SPI、I2C 等分别制定统一 API → 再抽象出 **I/O 设备管理层**,用 `open / write / read / control / close` 一套接口操作所有设备;复杂设备(PIN / I2C / SPI / 看门狗…)在其上保留**特定设备框架**接口。 - -## 二、三层架构 - -| 应用层 一套标准 API 访问设备(find / open / read / write / control / close) | -| ------------------------------------------------------------ | -| I/O 设备管理层 device.c —— 设备容器:注册 / 注销、按名查找、引用计数、open 时自动 init | -| 设备驱动框架层 components/drivers —— 同类设备抽象 pin / i2c / spi / serial / watchdog / rtc … | -| 设备驱动层 BSP 驱动 —— 具体芯片实现:drv_gpio.c / drv_usart.c / drv_spi.c … | - -| 层 | 职责 | 谁来写 | -| -------------- | -------------------------------------------- | --------------------- | -| I/O 设备管理层 | 封装驱动、提供标准接口;换驱动不影响应用 | RT-Thread 官方 | -| 设备驱动框架层 | 抽取同类驱动的**公共部分**,差异部分留出接口 | RT-Thread 官方 | -| 设备驱动层 | 驱使硬件工作,创建并注册设备实例 | 芯片厂商 / BSP 工程师 | - -**两个视角**: - -- **横看成层**:自下而上逐层抽象,屏蔽下层差异; -- **纵看成继承**:`rt_object` → `rt_device` → 各具体设备类,子类实现父类接口 —— 面向对象(面向接口)编程。无论底层是 STM32 还是 GD32,串口驱动都对接到同一个串口框架类。 - -> 结论:新增一个 BSP 时,通常**只需写设备驱动层**,上面两层官方已写好。 - -**两条注册路径**(设备进入系统的两种方式): - -| 路径 | 适用设备 | 流程 | -| ---------- | -------------------------------------- | ------------------------------------------------------------ | -| 直接注册 | 逻辑简单的设备(如课堂 test 字符设备) | create → `rt_device_register` → 应用 `find` | -| 经框架注册 | 复杂设备(看门狗 / 串口 / I2C 总线…) | 按设备模型创建 → `rt_hw_watchdog_register` 等注册到框架 → 框架再注册到 I/O 设备管理器 | - -## 三、I/O 设备模型 - -### 3.1 设备对象:面向对象的继承 - -设备是内核对象的一种,通过**继承**派生: - -``` -rt_object(内核对象基类:名字、类型、链表) - └── rt_device(设备基类) - ├── 字符设备(串口 / 按键…) - ├── 块设备(Flash / SD…) - └── 总线设备(I2C 总线 / SPI 总线…) -``` - -`rt_device` 关键字段: - -| 字段 | 含义 | -| ----------------------------- | ------------------------------------------- | -| `parent` | 继承的内核对象(含设备名,`find` 按名查找) | -| `type` | 设备类型(分类枚举) | -| `flag` / `open_flag` | 注册标志 / 打开标志 | -| `ref_count` | 引用计数(每 open 一次 +1) | -| `rx_indicate` / `tx_complete` | 收发回调函数指针 | -| `ops` | 操作方法集(6 个函数指针) | -| `user_data` | 驱动私有数据 | - -### 3.2 设备分类:字符设备 vs 块设备 - -| | 字符设备 | 块设备 | -| -------- | -------------------- | ------------------------------ | -| 典型设备 | 串口、键盘、按键 | Flash、SD 卡、硬盘 | -| 访问方式 | 字节流,**顺序**读写 | **随机**寻址,按块(512B)读写 | -| 本质区别 | 不可随机访问 | 可随机访问 | - -其他类型:`NetIf / MTD / RTC / Sound / Graphic / I2CBUS / SPIBUS / SPIDevice / SDIO / USBDevice / USBHost / Miscellaneous`(枚举见 `rtdef.h` 的 `rt_device_class_type`)。 - -**为什么要分类**:不同组件依赖不同类型的设备,分类后即可**对一类设备统一控制**—— - -- MSH 可重定向到任意**字符设备**(把 LCD 模拟成字符设备,打印就能输出到屏幕); -- FatFS 文件系统依赖**块设备**(SD 卡、RAM 都可实现为块设备)。 - -### 3.3 ops 操作集:上下层对接的核心 - -设备驱动实现 ops,完成「上层 API ↔ 底层硬件」的对接: - -| 方法 | 职责 / 触发时机 | -| --------- | ------------------------------------------------ | -| `init` | 初始化设备,成功后置激活标志,**不会重复初始化** | -| `open` | 打开设备,建议在此才真正使能硬件 | -| `close` | 关闭设备,`ref_count` 减到 0 才真正关闭 | -| `read` | 读数据,`pos` 为偏移量(字符设备通常忽略) | -| `write` | 写数据,`pos` 为偏移量 | -| `control` | 按 `cmd` 控制设备,命令多由驱动自定义 | - -**两条铁律**(课堂实验验证): - -1. `rt_device_open` 时若设备未初始化,会**自动调用 init**; -2. 设备未 open 时,`read / write` **不会执行**。 - -> 验证方法:单步调试跟踪调用栈,可见上层 API 逐层调用到具体厂商实现(如 STM32 HAL 库),ops 函数指针在此过程中逐层赋值。 - -## 四、设备访问 API - -### 4.1 生命周期 - -``` -驱动侧:rt_device_create ──▶ 挂接 ops ──▶ rt_device_register ──▶ 进入设备管理器链表 -应用侧:rt_device_find ──▶ rt_device_open ──▶ read / write / control ──▶ rt_device_close -``` - -| API | 作用 | 要点 | -| --------------------------------------------- | ---------------- | --------------------------------------- | -| `rt_device_create(type, attach_size)` | 从堆创建控制块 | `attach_size` 为附加私有数据大小 | -| `rt_device_register(dev, name, flags)` | 注册进设备管理器 | 名字不可重名,长度受 `RT_NAME_MAX` 限制 | -| `rt_device_unregister / destroy` | 注销 / 销毁 | 注销不释放内存,销毁才释放 | -| `rt_device_find(name)` | 按名查找 | 找不到返回 `RT_NULL` | -| `rt_device_init(dev)` | 初始化 | 已激活则直接返回 | -| `rt_device_open(dev, oflag)` | 打开 | 未初始化先自动 init | -| `rt_device_close(dev)` | 关闭 | 与 open 配对 | -| `rt_device_read / write(dev, pos, buf, size)` | 读写 | 返回实际字节数,0 需查 errno | -| `rt_device_control(dev, cmd, arg)` | 控制 | 通用命令 + 驱动自定义命令 | - -注册后可在 FinSH 执行 `list_device` 查看设备名、类型与引用计数。 - -### 4.2 标志位速查 - -- **注册 flags**:`RDONLY / WRONLY / RDWR`、`STANDALONE`(禁止重复打开)、`STREAM`(`\n` 自动补 `\r`)、`INT_RX / DMA_RX / INT_TX / DMA_TX`、`REMOVABLE`、`SUSPENDED` -- **打开 oflags**:`RDONLY / WRONLY / RDWR`,以及 `INT_RX`、`DMA_RX` 等模式 - -> ⚠️ 要使用接收回调,**必须以 `INT_RX` 或 `DMA_RX` 方式打开**设备,否则回调不生效。 - -### 4.3 数据收发回调 - -中断 / DMA 场景下避免应用轮询等待: - -| API | 触发时机 | -| ----------------------------------------- | ------------------------------------------------------------ | -| `rt_device_set_rx_indicate(dev, rx_ind)` | 硬件收到数据,通知上层「有数据到达」(回调携带长度),上层随后 `read` 取数 | -| `rt_device_set_tx_complete(dev, tx_done)` | 底层发送完成(DMA / FIFO 完成中断),回调携带发送 buffer 地址 | - -回调链:**硬件中断 → 底层驱动 → 框架层 → 上层应用回调**。 - -### 4.4 字符设备驱动实例(test_drv.c) - -```c -/* —— 驱动侧:创建字符设备并挂接 6 个 ops(各方法内部仅打印日志)—— */ -int my_char_dev_init(void) -{ - rt_device_t dev = rt_device_create(RT_Device_Class_Char, 0); - if (dev == RT_NULL) - return -RT_ERROR; - - dev->init = my_char_init; /* 6 个 ops 均为自实现函数 */ - dev->open = my_char_open; - dev->close = my_char_close; - dev->read = my_char_read; - dev->write = my_char_write; - dev->control = my_char_control; - - if (rt_device_register(dev, "my_char", RT_DEVICE_FLAG_RDWR) != RT_EOK) - return -RT_ERROR; - return RT_EOK; -} -INIT_BOARD_EXPORT(my_char_dev_init); /* 系统启动时自动完成设备注册 */ - -/* —— 应用侧:find → open → control → write → read → close —— */ -static int my_char_app(void) -{ - rt_device_t dev = rt_device_find("my_char"); /* 按注册名查找 */ - if (dev == RT_NULL) - return -RT_ERROR; - - rt_device_open(dev, RT_DEVICE_OFLAG_RDWR); /* 未初始化会先自动 init */ - rt_device_control(dev, RT_DEVICE_CTRL_CONFIG, RT_NULL); - rt_device_write(dev, 0, "hello", 5); /* pos 为偏移量,字符设备常忽略 */ - rt_device_read(dev, 0, rx_buf, 128); - rt_device_close(dev); - return RT_EOK; -} -MSH_CMD_EXPORT(my_char_app, run char device demo); -``` - -## 五、PIN 框架(GPIO) - -芯片引脚分 4 类:电源、时钟、控制、通用 I/O(GPIO,可复用为 SPI / I2C / UART 等功能)。 - -| API | 作用 | -| ----------------------------------------- | ------------------------------------------------------------ | -| `rt_pin_mode(pin, mode)` | 配置模式:`OUTPUT` / `INPUT` / `INPUT_PULLUP` / `INPUT_PULLDOWN` / `OUTPUT_OD` | -| `rt_pin_write(pin, value)` | 输出 `PIN_LOW` / `PIN_HIGH` | -| `rt_pin_read(pin)` | 读电平,返回 `PIN_LOW` / `PIN_HIGH` | -| `rt_pin_attach_irq(pin, mode, hdr, args)` | 绑定中断回调,5 种触发:上升沿 / 下降沿 / 双边沿 / 高电平 / 低电平 | -| `rt_pin_irq_enable(pin, enabled)` | 使能 / 失能中断 | -| `rt_pin_detach_irq(pin)` | 脱离中断回调 | - -**按键中断实例**(基于 dev_test.c ,4 个按键:上拉输入 + 下降沿触发): - -```c -#define KEY_0 GET_PIN(C, 5) /* 4 个可编程按键,映射到 PC5 / PC1 / PC0 / PC4 */ -#define KEY_1 GET_PIN(C, 1) -#define KEY_2 GET_PIN(C, 0) -#define KEY_3 GET_PIN(C, 4) - -static void key_0_handler(void *args) /* 中断回调 */ -{ - LOG_I("key 0 pressed, level = %d", rt_pin_read(KEY_0)); -} -/* key_1 / key_2 / key_3 回调同理 */ - -void key_irq_setup(void) -{ - rt_pin_mode(KEY_0, PIN_MODE_INPUT_PULLUP); /* 上拉输入 */ - rt_pin_mode(KEY_1, PIN_MODE_INPUT_PULLUP); - rt_pin_mode(KEY_2, PIN_MODE_INPUT_PULLUP); - rt_pin_mode(KEY_3, PIN_MODE_INPUT_PULLUP); - - rt_pin_attach_irq(KEY_0, PIN_IRQ_MODE_FALLING, key_0_handler, RT_NULL); - rt_pin_attach_irq(KEY_1, PIN_IRQ_MODE_FALLING, key_1_handler, RT_NULL); - rt_pin_attach_irq(KEY_2, PIN_IRQ_MODE_FALLING, key_2_handler, RT_NULL); - rt_pin_attach_irq(KEY_3, PIN_IRQ_MODE_FALLING, key_3_handler, RT_NULL); - - rt_pin_irq_enable(KEY_0, PIN_IRQ_ENABLE); /* 使能中断 */ - rt_pin_irq_enable(KEY_1, PIN_IRQ_ENABLE); - rt_pin_irq_enable(KEY_2, PIN_IRQ_ENABLE); - rt_pin_irq_enable(KEY_3, PIN_IRQ_ENABLE); -} -MSH_CMD_EXPORT(key_irq_setup, enable key irq); -``` - -**经验**: - -- 机械按键抖动会触发多次中断 → 延时消抖或使用 **FlexibleButton** 软件包; -- 中断「没反应」先检查 ulog 日志级别(`LOG_LVL`)是否把打印过滤掉了。 - -## 六、I2C 框架 - -**协议要点**: - -- 两线:SCL(时钟)+ SDA(数据),速率分 10K / 100K / 400K / 3.4M 四档; -- **起始位 S**:SCL 高电平时 SDA 由高变低;**结束位 P**:SCL 高电平时 SDA 由低变高; -- 每传输一个字节,接收方回 **ACK**(拉低 SDA)或 **NACK**; -- 从机地址 7 位,总线上以「7 位地址 + 1 位读写位(0 写 / 1 读)」的 8 位形式出现;也支持 10 位地址。 - -**使用 4 步**: - -1. `rt_device_find("i2c2")` 找到总线(一般名为 `i2c0 / i2c1 …`); -2. 构造消息 `struct rt_i2c_msg { addr; flags; buf; len; }`,一次可传多条; -3. `rt_i2c_transfer(bus, msgs, n)` 传输,返回成功传输的消息数; -4. 处理结果。 - -**四种常用模式**(i2c_sample.c 四个示例函数): - -| 模式 | 消息构造 | -| --------------------- | ---------------------------------- | -| 写 1 字节(写寄存器) | 1 条写消息 | -| 写多字节 | 1 条写消息,len 更长 | -| 读 1 字节 | 2 条消息:先写寄存器地址,再读数据 | -| 读多字节 | 同上,读消息 len 更长 | - -**读单字节示例**(最能体现「一次传输多条消息」的用法): - -```c -struct rt_i2c_bus_device *bus; -struct rt_i2c_msg msg_list[2]; -rt_uint8_t reg_buf[1], data_buf[1]; - -bus = (struct rt_i2c_bus_device *)rt_device_find("i2c2"); - -reg_buf[0] = 0x75; /* 要读的寄存器地址(WHO_AM_I) */ - -msg_list[0].addr = 0x68; /* 从机 7 位地址 */ -msg_list[0].flags = RT_I2C_WR; /* 第一条:写寄存器地址 */ -msg_list[0].buf = reg_buf; -msg_list[0].len = 1; - -msg_list[1].addr = 0x68; -msg_list[1].flags = RT_I2C_RD; /* 第二条:读数据 */ -msg_list[1].buf = data_buf; -msg_list[1].len = 1; - -if (rt_i2c_transfer(bus, msg_list, 2) == 2) /* 一次传输 2 条消息 */ - rt_kprintf("who am i = 0x%02x\n", data_buf[0]); -``` - -**三大坑**: - -1. **总线死锁**:主机复位时从机正拉低 SDA 等待 SCL → 双方互等。解锁:传输前在 SCL 上发 **9 个时钟脉冲**,让从机完成挂起的传输: - -```c -static rt_err_t i2c_bus_recover(const struct stm32_soft_i2c_config *cfg) -{ - rt_int32_t cnt = 0; - if (PIN_LOW == rt_pin_read(cfg->sda)) { - while (cnt++ < 9) { /* 9 个时钟脉冲恢复总线 */ - rt_pin_write(cfg->scl, PIN_HIGH); - rt_hw_us_delay(100); - rt_pin_write(cfg->scl, PIN_LOW); - rt_hw_us_delay(100); - } - if (PIN_LOW == rt_pin_read(cfg->sda)) - return -RT_ERROR; - return RT_EOK; - } -} -``` - -2. **地址错误** → `rt_i2c_transfer` 返回 **-5**:7 位地址写成 8 位,或两条消息地址不一致: - -```c -msg_list[0].addr = 0x68; /* 写消息用 0x68 */ -msg_list[1].addr = 0x6D; /* 读消息却用 0x6D → transfer 返回 -5 */ -``` - -3. **禁止在中断里调用**:`rt_i2c_transfer` 内部会取互斥锁,中断服务函数中调用会 assert 报错。 - -> 配置:menuconfig 开启 I2C 驱动;无硬件 I2C 的芯片可用 **GPIO 模拟 I2C**(软件 I2C);调试可用 **i2c-tools** 软件包(`i2c scan i2c3` 扫描从机)。 - -## 七、SPI 框架 - -**特点**:高速、全双工、同步,4 线通信(SCLK / MOSI / MISO / CS),常用于 Flash、EEPROM、AD 等。 - -**总线-设备模型**: - -- **SPI 总线** = 控制器,需先注册; -- **SPI 设备** = 挂在总线上、由 CS 引脚区分的从设备,用 `rt_spi_bus_attach_device()` 挂载(STM32 BSP 提供 `rt_hw_spi_device_attach()`); -- **命名**:总线 `spix`,设备 `spixy`(x = 总线号,y = 设备序号)。如 `spi10` = spi1 总线上 0 号设备(示例见下)。 - -**配置** `struct rt_spi_configuration`: - -| 字段 | 取值 | -| ------------ | ------------------------------------------------------------ | -| `mode` | `RT_SPI_MASTER / SLAVE` | `RT_SPI_MODE_0~3`(CPOL/CPHA)| `RT_SPI_MSB / LSB` | `RT_SPI_CS_HIGH` 等 | -| `data_width` | 8 / 16 / 32 位 | -| `max_hz` | 时钟频率 | - -**收发 API**: - -| API | 说明 | -| ----------------------------- | -------------------------------------------------- | -| `rt_spi_transfer` | 同时收发一次数据 | -| `rt_spi_send` / `rt_spi_recv` | 只发送 / 只接收 | -| `rt_spi_send_then_send` | 连发两段,**片选不释放**(典型:先发地址再发数据) | -| `rt_spi_send_then_recv` | 先发后收,片选不释放;接收阶段主机发送 **0xFF** | -| `rt_spi_transfer_message` | 自定义消息传输 | - -**示例**(spi_sample.c ): - -```c -/* 挂载:spi1 总线上 0 号设备,CS = PA4(INIT_DEVICE_EXPORT 自动执行) */ -rt_hw_spi_device_attach("spi1", "spi10", GET_PIN(A, 4)); - -/* 查找 + 配置 */ -struct rt_spi_device *spi_dev = (struct rt_spi_device *)rt_device_find("spi10"); -struct rt_spi_configuration cfg; -cfg.data_width = 8; -cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; -cfg.max_hz = 1 * 1000 * 1000; /* 1M */ -rt_spi_configure(spi_dev, &cfg); - -/* 传输:同时收 / 发 1 字节 */ -rt_uint8_t tx_byte = 0x5A, rx_byte = 0; -rt_spi_transfer(spi_dev, &tx_byte, &rx_byte, 1); - -/* 连发两段(片选不释放):先发命令,再发数据 */ -rt_uint8_t cmd[2] = {0x1A, 0x99}, data[2] = {0x12, 0x39}; -rt_spi_send_then_send(spi_dev, cmd, 2, data, 2); - -/* 先发后收(片选不释放):发 cmd,收 data */ -rt_spi_send_then_recv(spi_dev, cmd, 2, data, 2); -``` - -## 八、驱动包与自动初始化 - -### 8.1 设备驱动包(Package) - -复杂传感器(如 ICM20608 六轴传感器)已有现成驱动包:包内部基于 I2C 框架完成驱动,应用层只需「初始化 → 校准 → 轮询读取」(基于 icm_20608_sample.c 改编): - -```c -sensor = icm20608_init("i2c2"); /* 基于 I2C 框架初始化传感器 */ -icm20608_calib_level(sensor, 10); /* 采样 10 次求零值校准 */ - -while (count++ < 100) /* 线程内轮询读取 */ -{ - icm20608_get_accel(sensor, &ax, &ay, &az); /* 三轴加速度 */ - icm20608_get_gyro(sensor, &gx, &gy, &gz); /* 三轴陀螺仪 */ - rt_thread_mdelay(1000); -} -``` - -### 8.2 自动初始化机制 - -驱动注册无需应用手动调用 —— 通过链接文件生成函数指针表,启动时按阶段自动执行: - -| 宏 | 阶段 | 典型用途 | -| ------------------------ | ------------------ | ---------------- | -| `INIT_BOARD_EXPORT(fn)` | 板级初始化(最早) | 芯片级外设初始化 | -| `INIT_DEVICE_EXPORT(fn)` | 设备初始化 | 设备驱动注册 | -| `INIT_APP_EXPORT(fn)` | 应用初始化(最后) | 应用自动启动 | - -```c -INIT_BOARD_EXPORT(my_char_dev_init); /* 板级初始化:最早执行 */ -INIT_DEVICE_EXPORT(key_dev_init); /* 设备初始化:驱动注册 */ -INIT_APP_EXPORT(app_auto_start); /* 应用初始化:最后执行 */ -``` - -## 九、开发模式与版本 - -| 模式 | 特点 | 适用 | -| ------------------ | ------------------------------------------------ | ---------------- | -| 基于芯片(BSP) | 手动在 `board.h / board.c` 配置引脚与时钟 | 多芯片适配 | -| 基于 SDK(CubeMX) | 直接使用 CubeMX 生成配置,通过**弱符号**自动链接 | 特定型号快速开发 | \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day1/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day1/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/README.md" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/README.md" deleted file mode 100644 index 2c05239e89d4a344f3078478b69c8c851818e075..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/README.md" +++ /dev/null @@ -1,77 +0,0 @@ -# Part 1: - -## 1. STM32F407 启动流程与 RT-Thread 引导机制 - -- **启动路径** - - 硬件自动行为:上电/复位 → 从向量表取 MSP 初始值 → 跳转到 Reset_Handler - - startup_xxx.S (汇编启动文件):Reset_Handler → 设置 MSP 主栈指针→ 拷贝 .data 段 (Flash → RAM) → 清零 .bss 段 → bl SystemInit → bl entry - - components.c:int entry(void) { rtthread_startup(); return 0;} - - 然后到了rtt的启动流程 - - ![](.\day2图1.png) - - - -- **自动初始化原理** - -上电 - │ - ├─ Reset_Handler - │ └─ entry() - │ └─ rtthread_startup() - │ ├─ rt_hw_board_init() - │ │ └─ rt_components_board_init() - │ │ **└─ INIT_BOARD_EXPORT ** - │ ├─ rt_system_scheduler_start() - │ │ └─ 启动调度器 - │ └─ main 线程 - │ └─ main_thread_entry() - │ └─ rt_components_init() - │ **└─ INIT_APP_EXPORT ** - │ └─ main() - -## 2.内核基础与对象模型 - -- **对象容器与控制块**:RT-Thread 用 C 语言通过“结构体嵌套基类 + 统一 API + 不暴露内部实现”的方式,实现了面向对象的内核设计,使内核对象具备继承、封装和统一管理的特性。 - -rt_uint8_t stat; - -| 宏定义 | 含义 | -| ----------------- | ------ | -| RT_THREAD_INIT | 初始态 | -| RT_THREAD_READY | 就绪态 | -| RT_THREAD_SUSPEND | 挂起态 | -| RT_THREAD_RUNNING | 运行态 | -| RT_THREAD_CLOSE | 关闭态 | - -current_priority — 当前优先级 - -init_tick — 时间片初始值 - -sp — 线程栈指针 - -- **内存分配方式**: **静态对象** 在编译期分配于 `.data/.bss`,生命周期贯穿系统运行,无分配失败和碎片风险;**动态对象**在运行期从堆中分配,灵活但不可靠。高可靠系统强制使用静态线程,是为了保证确定性、可验证性、避免内存碎片和运行时失败。 - -## 3. 线程状态机与调度算法 - -* **五态模型**: - -![](.\day2图2.png) - -* **调度机制剖析** :**抢占式调度**保证高优先级线程可随时打断低优先级线程,确保实时响应;**时间片轮转**则在同一优先级的多线程间,按固定时间片轮流分配 CPU,防止单一线程独占。 - -# Part2: - -线程th_led2 黄色LED等按照500ms间隔闪烁 - -因为在其他线程里面挂起线程会assert 报错,所以使用信号量,在线程th_monitor中扫描按键DOWN,会释放信号量,运行th_led1 红色LED灯闪烁,并且打印 `th_led1` 与 `th_led2` 的运行状态与剩余栈空间大小。运行状态和终端如下图所示 - -![](.\day2图3.jpg) - -![](.\day2图4.jpg) - -![](.\day2图5.png) diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2761.png" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2761.png" deleted file mode 100644 index a6f0b0af62c5149a3f30a8f85629ce09596e8afb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2761.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2762.png" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2762.png" deleted file mode 100644 index c6ec6fd61398e6ec4cafea3723eab0c3894bc8e4..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2762.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2763.jpg" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2763.jpg" deleted file mode 100644 index 314f6586444a4ffff8bf2c9f7ee2267ee4c931cb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2763.jpg" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2764.jpg" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2764.jpg" deleted file mode 100644 index 7d3f5f9878b207de1673fbc62b49c376331167e4..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2764.jpg" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2765.png" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2765.png" deleted file mode 100644 index 8eb4325a77a32a42c89dff7ce7d034a0cb1c3681..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day2/day2\345\233\2765.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/001_parking.c" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/001_parking.c" deleted file mode 100644 index 1db4ac647a0f03180f554beb28939c250d3bcac1..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/001_parking.c" +++ /dev/null @@ -1,91 +0,0 @@ -#include - -#define CAR_NUM 5 -#define MAX_SLOTS 3 -#define TIMEOUT_MS 2000 - -/* 信号量:真正控制车位 */ -static rt_sem_t parking_sem; - -/* 剩余车位计数器:专门用来显示 */ -static rt_uint32_t remaining_slots = MAX_SLOTS; - -/* 打印当前空位数 */ -static void print_parking_status(void) -{ - rt_kprintf("Remaining slots: %d\n", remaining_slots); -} - -/* 车辆线程入口 */ -static void car_thread_entry(void *param) -{ - rt_uint32_t car_id = (rt_uint32_t)param; - - while (1) - { - if (rt_sem_take(parking_sem, TIMEOUT_MS) == RT_EOK) - { - /* 进入成功,空位减 1 */ - remaining_slots--; - rt_kprintf("Car %d entered. ", car_id); - print_parking_status(); - - /* 模拟停车 2~4 秒 */ - rt_thread_mdelay(2000 + (car_id * 500)); - - /* 离开,空位加 1 */ - remaining_slots++; - rt_kprintf("Car %d left. ", car_id); - print_parking_status(); - - rt_sem_release(parking_sem); - break; - } - else - { - rt_kprintf("Car %d waiting timeout, retrying...\n", car_id); - } - } -} - -int parking_lot_sample(void) -{ - rt_thread_t tid[CAR_NUM]; - rt_uint32_t i; - - /* 创建信号量 */ - parking_sem = rt_sem_create("parking", MAX_SLOTS, RT_IPC_FLAG_FIFO); - if (parking_sem == RT_NULL) - { - rt_kprintf("Failed to create semaphore!\n"); - return -1; - } - - rt_kprintf("=== Parking Lot Opened, Total Slots: %d ===\n", MAX_SLOTS); - print_parking_status(); - - /* 创建 5 辆车 */ - for (i = 0; i < CAR_NUM; i++) - { - tid[i] = rt_thread_create("car", - car_thread_entry, - (void *)(i + 1), - 1024, - 10, - 10); - if (tid[i] != RT_NULL) - rt_thread_startup(tid[i]); - } - - return 0; -} - - -MSH_CMD_EXPORT(parking_demo, parking_demo); - -int main(void) -{ -// rt_kprintf("Hello RT-Thread!\n"); - return 0; -} - diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/002_ticket.c" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/002_ticket.c" deleted file mode 100644 index 8afbf028710ccdd148f97651311ba52e9f3b13d4..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/002_ticket.c" +++ /dev/null @@ -1,107 +0,0 @@ -#include - -#define TOTAL_TICKETS 20 -#define STACK_SIZE 512 -#define PRIORITY 10 - -/* 剩余票数 */ -static rt_int32_t remaining_tickets = TOTAL_TICKETS; - -/* 互斥锁 */ -static rt_mutex_t ticket_mutex = RT_NULL; - -/* 售票线程入口 */ -static void ticket_window_entry(void *param) -{ - const char *window_name = (const char *)param; - rt_int32_t ticket_no = 0; - - while (1) - { - /* 获取互斥锁 */ - rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); - - /* 临界区:检查并卖票 */ - if (remaining_tickets > 0) - { - ticket_no = remaining_tickets; - remaining_tickets--; - rt_kprintf("%s sold ticket %d\n", window_name, ticket_no); - } - else - { - /* 票卖完了,先释放锁再退出 */ - rt_mutex_release(ticket_mutex); - break; - } - - /* 释放互斥锁 */ - rt_mutex_release(ticket_mutex); - - /* 模拟售票间隔 */ - rt_thread_mdelay(10); - } - - rt_kprintf("%s closed.\n", window_name); -} - -int multi_window_ticket_system(void) -{ - rt_thread_t tid_a, tid_b; - - /* 防重复运行:如果锁已存在先清理 */ - if (ticket_mutex != RT_NULL) - { - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - } - - /* 1. 创建互斥锁 */ - ticket_mutex = rt_mutex_create("mtx", RT_IPC_FLAG_FIFO); - if (ticket_mutex == RT_NULL) - { - rt_kprintf("Failed to create mutex!\n"); - return -1; - } - - /* 2. 重置票数 */ - remaining_tickets = TOTAL_TICKETS; - - /* 3. 创建窗口 A */ - tid_a = rt_thread_create("winA", - ticket_window_entry, - (void *)"Window A", - STACK_SIZE, - PRIORITY, - 10); - if (tid_a != RT_NULL) - rt_thread_startup(tid_a); - - /* 4. 创建窗口 B */ - tid_b = rt_thread_create("winB", - ticket_window_entry, - (void *)"Window B", - STACK_SIZE, - PRIORITY, - 10); - if (tid_b != RT_NULL) - rt_thread_startup(tid_b); - - /* 5. 等待窗口把票卖完 */ - rt_thread_mdelay(500); - - /* 6. 清理资源(线程自然退出,无需手动 delete) */ - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - - rt_kprintf("All tickets sold out!\n"); - return 0; -} - - -MSH_CMD_EXPORT(ticket_demo, ticket_demo); -int main(void) -{ - return 0; -} - diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/003_startup.c" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/003_startup.c" deleted file mode 100644 index f2858cab4d99e6cfc89c9b5ce13d773418ae813e..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/003_startup.c" +++ /dev/null @@ -1,91 +0,0 @@ -#include - -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) - -static rt_event_t system_event = RT_NULL; - -static void net_init_thread(void *param) -{ - rt_thread_mdelay(1000); - rt_kprintf("network ready\n"); - rt_event_send(system_event, EVENT_NET_READY); -} - -static void sensor_init_thread(void *param) -{ - rt_thread_mdelay(2000); - rt_kprintf("sensor ready\n"); - rt_event_send(system_event, EVENT_SENSOR_READY); -} - -static void storage_init_thread(void *param) -{ - rt_thread_mdelay(5000); - rt_kprintf("storage ready\n"); - rt_event_send(system_event, EVENT_STORAGE_READY); -} - -static void main_business_thread(void *param) -{ - rt_uint32_t recved_events = 0; - rt_uint32_t wait_events = EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY; - - while (1) - { - rt_int32_t ret = rt_event_recv(system_event, - wait_events, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - 3000, - &recved_events); - - if (ret == -RT_ETIMEOUT) - { - rt_kprintf("wait modules timeout, "); - if (!(recved_events & EVENT_NET_READY)) rt_kprintf("network is not ready "); - if (!(recved_events & EVENT_SENSOR_READY)) rt_kprintf("sensor is not ready "); - if (!(recved_events & EVENT_STORAGE_READY)) rt_kprintf("storage is not ready "); - rt_kprintf("\n"); - } - else if (ret == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - break; - } - } -} - -int system_startup_check(void) -{ - system_event = rt_event_create("sys_evt", RT_IPC_FLAG_FIFO); - if (system_event == RT_NULL) - { - rt_kprintf("Failed to create event!\n"); - return -1; - } - - rt_thread_t tid_net = rt_thread_create("net", net_init_thread, RT_NULL, 512, 10, 10); - if (tid_net != RT_NULL) rt_thread_startup(tid_net); - - rt_thread_t tid_sensor = rt_thread_create("sensor", sensor_init_thread, RT_NULL, 512, 10, 10); - if (tid_sensor != RT_NULL) rt_thread_startup(tid_sensor); - - rt_thread_t tid_storage = rt_thread_create("storage", storage_init_thread, RT_NULL, 512, 10, 10); - if (tid_storage != RT_NULL) rt_thread_startup(tid_storage); - - rt_thread_t tid_main = rt_thread_create("main_biz", main_business_thread, RT_NULL, 512, 11, 10); - if (tid_main != RT_NULL) rt_thread_startup(tid_main); - - return 0; -} - -MSH_CMD_EXPORT(startup_demo,startup_demo); - -int main(void) -{ - - return 0; -} - diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/004_package.c" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/004_package.c" deleted file mode 100644 index 3f742b3bfa772110f261ea70095082e5eadbe104..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/004_package.c" +++ /dev/null @@ -1,227 +0,0 @@ -#include -#include /* 用于 rand() */ - -/* ==================== 宏定义与数据结构 ==================== */ - -#define MSG_QUEUE_CAPACITY (5) // 消息队列容量 -#define PACKAGES_PER_SENDER (5) // 每个发件线程发送的包裹数 -#define TOTAL_SENDERS (2) // 发件线程总数 -#define SENDER_STACK_SIZE (1024) // 发件线程栈大小 -#define SORTER_STACK_SIZE (2048) // 分拣线程栈大小 -#define SENDER_PRIORITY (25) // 发件优先级 -#define SORTER_PRIORITY (24) // 分拣优先级(略高,优先处理) - -// 快递消息结构 -struct package_msg { - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -// 结束信号结构 -struct end_msg { - rt_uint32_t type; // 固定为 0xFFFF -}; - -/* ==================== 全局变量 ==================== */ - -static rt_mq_t g_msg_queue; -static rt_mutex_t g_id_mutex; -static rt_uint32_t g_next_id = 1; -static rt_uint32_t g_send_failed_count = 0; - -/* ==================== 辅助函数 ==================== */ - -// 获取区域名称字符串 -static const char* get_region_name(rt_uint8_t region) { - switch (region) { - case 1: return "east"; - case 2: return "south"; - case 3: return "north"; - default: return "unknown"; - } -} - -/* ==================== 线程入口函数 ==================== */ - -/** - * @brief 发件线程入口 - */ -static void sender_thread_entry(void *parameter) { - struct package_msg msg; - rt_err_t result; - - for (int i = 0; i < PACKAGES_PER_SENDER; i++) { - // 1. 获取唯一 ID (互斥锁保护) - if (rt_mutex_take(g_id_mutex, RT_WAITING_FOREVER) == RT_EOK) { - msg.id = g_next_id++; - rt_mutex_release(g_id_mutex); - } else { - continue; // 获取失败则跳过 - } - - // 2. 生成随机数据 - // 使用 rand() % 3 + 1 生成 1, 2, 3 - msg.region = (rt_uint8_t)(rand() % 3 + 1); - msg.weight = (rt_uint16_t)(rand() % 900 + 100); // 100g - 1000g - - // 3. 发送到消息队列 - // 注意:这里使用 RT_WAITING_NO 模拟“不堵塞”,如果队列满了直接算发送失败 - result = rt_mq_send(g_msg_queue, &msg, sizeof(struct package_msg)); - - if (result != RT_EOK) { - g_send_failed_count++; - rt_kprintf("[SENDER] Send failed (Queue Full), ID: %d\n", msg.id); - } else { - rt_kprintf("[SENDER] Sent package ID: %d, Region: %s\n", - msg.id, get_region_name(msg.region)); - } - - // 模拟发件耗时 - rt_thread_mdelay(50); - } - - // 4. 发送完成,投递结束信号 - struct end_msg end_signal; - end_signal.type = 0xFFFF; - rt_mq_send(g_msg_queue, &end_signal, sizeof(struct end_msg)); - - rt_kprintf("[SENDER] Thread finished.\n"); -} - -/** - * @brief 分拣线程入口 - */ -static void sorter_thread_entry(void *parameter) { - rt_err_t result; - char recv_buf[64]; // 接收缓冲区,必须足够大以容纳最大的结构体 - int total_packages = 0; - int count_east = 0, count_south = 0, count_north = 0; - int total_weight = 0; - - rt_kprintf("\n[SYSTEM] Sorting center started...\n"); - - while (1) { - // 【修复点1】rt_mq_recv 需要4个参数:队列,缓冲,大小,等待时间 - result = rt_mq_recv(g_msg_queue, recv_buf, sizeof(recv_buf), RT_WAITING_FOREVER); - - if (result == RT_EOK) { - // 检查是否是结束信号 - // 简单判断:如果第一个字节是 0xFF (即 0xFFFF 的低位),视为结束信号 - // 严谨做法是判断 size 或者在结构体里加 type 字段,这里简化处理 - struct end_msg *p_end = (struct end_msg *)recv_buf; - if (p_end->type == 0xFFFF) { - break; // 收到结束信号,退出循环 - } - - // 正常处理快递 - struct package_msg *p_pkg = (struct package_msg *)recv_buf; - - total_packages++; - total_weight += p_pkg->weight; - - const char *region_str = get_region_name(p_pkg->region); - - // 【修复点2】if-else 语法修正:LOG/printf 必须在 {} 内部或作为独立语句 - if (p_pkg->region == 1) { - count_east++; - } else if (p_pkg->region == 2) { - count_south++; - } else if (p_pkg->region == 3) { - count_north++; - } - - // 打印分拣信息 - rt_kprintf("[SORTER] Package %-5u -> %-6s weight: %u g\n", - p_pkg->id, region_str, p_pkg->weight); - } - } - - // 5. 打印统计结果 - rt_kprintf("\n---------------- STATISTICS ----------------\n"); - rt_kprintf("Total packages : %d\n", total_packages); - rt_kprintf("East: %d, South: %d, North: %d\n", count_east, count_south, count_north); - rt_kprintf("Total weight : %d g\n", total_weight); - rt_kprintf("Send failed : %d\n", g_send_failed_count); - rt_kprintf("--------------------------------------------\n"); - - // 6. 清理资源 (可选,如果是单次运行Demo可以不删,但好习惯是删除) - // 注意:在实际工程中,通常由创建者删除,这里为了演示完整性放在最后 -} - -/* ==================== MSH 命令导出 ==================== */ - -/** - * @brief 启动快递分拣演示的命令函数 - */ -void package_sorting_demo(int argc, char **argv) { - rt_thread_t tid_sender1, tid_sender2, tid_sorter; - - rt_kprintf(">>> Starting Package Sorting Demo...\n"); - - // 重置全局状态 - g_next_id = 1; - g_send_failed_count = 0; - - // 1. 创建互斥量 - g_id_mutex = rt_mutex_create("id_mtx", RT_IPC_FLAG_FIFO); - if (g_id_mutex == RT_NULL) { - rt_kprintf("Create mutex failed!\n"); - return; - } - - // 2. 创建消息队列 - // 队列大小要能容纳 package_msg 和 end_msg,取较大者 - g_msg_queue = rt_mq_create("pkg_mq", - sizeof(struct package_msg), // 消息最大长度 - MSG_QUEUE_CAPACITY, // 队列容量 - RT_IPC_FLAG_FIFO); - if (g_msg_queue == RT_NULL) { - rt_kprintf("Create message queue failed!\n"); - rt_mutex_delete(g_id_mutex); - return; - } - - // 3. 创建分拣线程 - tid_sorter = rt_thread_create("sorter", - sorter_thread_entry, - RT_NULL, - SORTER_STACK_SIZE, - SORTER_PRIORITY, - 10); - if (tid_sorter != RT_NULL) { - rt_thread_startup(tid_sorter); - } - - // 4. 创建两个发件线程 - tid_sender1 = rt_thread_create("sender1", - sender_thread_entry, - RT_NULL, - SENDER_STACK_SIZE, - SENDER_PRIORITY, - 10); - if (tid_sender1 != RT_NULL) { - rt_thread_startup(tid_sender1); - } - - tid_sender2 = rt_thread_create("sender2", - sender_thread_entry, - RT_NULL, - SENDER_STACK_SIZE, - SENDER_PRIORITY, - 10); - if (tid_sender2 != RT_NULL) { - rt_thread_startup(tid_sender2); - } - - rt_kprintf(">>> Threads created successfully.\n"); -} - -// 导出命令到 MSH -MSH_CMD_EXPORT(package_sorting_demo, Start the package sorting center demo); - -int main(void) -{ - return 0; -} - diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/READM.md" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/READM.md" deleted file mode 100644 index 03c9c40b577bf09b55c7a37db39d9ebf1d7c9ed4..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/READM.md" +++ /dev/null @@ -1,24 +0,0 @@ -## 练习一:停车场车位管理 - -开启五个车辆线程进入同一个入口函数,用计数信号量限制进入函数的个数,每个线程结束后释放计数信号量。 - -**思考题 :** - -使用互斥量同时只能运行一个车辆线程,而计数信号量可以运行多个。 - -## 练习二:多窗口售票系统 - -使用互斥量检查票数并且售票,同一时刻只能由一个线程操控,不会造成读写异常。 - -**思考题 :** - -只给`ticket_count--`加锁,**未保护“读取剩余票数”和“记录票号”这两个关键步骤**,导致多个线程可能读取到相同的票号,从而出现**重复售票**的问题。因此,必须将**完整的售票流程**(检查、记录、减票)作为临界区,用互斥锁保护。 - -## 练习三:系统启动条件检查 - -使用事件集,确保每个事件完成后才进行主线程。 -**思考题 :** -使用信号量还会产生检查的先后顺序问题,增加耦合性。 - -## 练习四:快递分拣中心 -两个线程发送消息队列 一个线程接收消息队列 \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2761.png" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2761.png" deleted file mode 100644 index b326cb74213859b6d1f8cf7677d5ee3e422b6fcb..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2761.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2762.png" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2762.png" deleted file mode 100644 index da4780a313921122e2e6f73a99f1627d85ebb091..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2762.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2763.png" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2763.png" deleted file mode 100644 index 54751959d4f517f3a5f157a2f2203767ab732ba7..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2763.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2764.png" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2764.png" deleted file mode 100644 index d902e920cdcc36f9ec46d12587e475b60c44d95a..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\344\275\234\344\270\232/day3/day3_\345\233\2764.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index fcec290bb556486ac9c5851b697fb769c0ddcc73..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\232\213\346\226\207\346\235\260/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,71 +0,0 @@ -## 1.Markdown基本语法 - -### 1.简单 - - **我被加粗了** - -*我倾斜了* - -==我高亮了== - - - -#### 上标 - -2^2^ - -#### 下标 - -H~2~O - -### 2.代码 - -> hello markdown - -> > hello python - - - -```C -printf("hello world"); -``` - -`print('hello markdown')` - -## 2.新建QEMU仿真 - -创建新的rt -thread 项目 输入项目名称 点击完成 - -![](./day1_图1.png) - ------- - -点击1 编译,再点击2 下载,即可看到虚拟终端上的RTT 信息和“hello ...”字符串 - - -![](./day1_图2.png) - - -![](./day1_图3.png) - - - ------- - - - -## 3.git命令 - - - -```git -git init //初始化本地仓库 -git clone xxx/xx.git //克隆你需要的仓库 -git checkout -b day1 //创建并切换到新分支 -//添加你的文件后 -git add . //将所有修改添加到暂存区 -git commit -m "提交说明" //# 将暂存区的内容提交到本地仓库 -git push -u origin day1 //新分支push - -然后去网页上提交PR即可 -``` diff --git "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/assets/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 204047.png" "b/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/assets/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 204047.png" deleted file mode 100644 index 5b48924e31944f872988f67ed3ccca2abbdb01e4..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/assets/\345\261\217\345\271\225\346\210\252\345\233\276 2026-08-17 204047.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 3309b8611c6149d025eb812c8350efb6801675eb..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,18 +0,0 @@ -# STM32F407实操 - -## 配置完成后界面 - -![](C:\Users\huoyuchen\rsoc-rtt\2026\第4组\霍渝陈\作业\assets/屏幕截图 2026-08-17 204047.png) - -## 终端运行结果 - -``` - \ | / -- RT - Thread Operating System - / | \ 4.1.1 build Aug 17 2026 11:23:12 - 2006 - 2022 Copyright by RT-Thread team -hello RSOC2026! -[D/main] Hello RT-Thread! -msh /> -``` - diff --git "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" "b/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" deleted file mode 100644 index b37d6c117bd7cc0766a27e7487cdcdad6dcb9596..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/README.md" +++ /dev/null @@ -1,258 +0,0 @@ -# 第三天作业:线程间同步与通信 - -> 目标平台:STM32F407 星火号(stm32f407-rtspark) -> 内核版本:RT-Thread 4.1.1(Cortex-M4) -> 开发环境:RT-Thread Studio 2.3.1 + 板载 ST-LINK 下载,串口 COM7/115200 - ---- - -## 一、理论总结 - -### 1. 为什么要线程间同步与通信 - -多线程系统中,多个线程会**共享资源**(全局变量、外设、缓冲区)或**协作完成任务**。如果没有同步机制,就会出现数据错乱(比如两个窗口同时卖出同一张票);如果线程之间需要传递数据,就需要通信机制。RT-Thread 提供了四种经典的 IPC(Inter-Process Communication,进程间通信,这里指线程间)机制:**信号量、互斥量、事件集、消息队列**。 - -### 2. 四种机制对比 - -| 机制 | 本质 | 核心操作 | 适合场景 | 本次练习 | -|---|---|---|---|---| -| 计数信号量 | 计数器(资源数) | `take` 减 1 / `release` 加 1 | 多个同类资源的分配与回收 | 练习一:停车场 | -| 互斥量 | 0/1 锁(有所有权) | `take` / `release` | 保护临界区,共享数据互斥访问 | 练习二:售票 | -| 事件集 | 32 个事件位 | `send` 置位 / `recv` 等待 | 多个条件"全部/任一"满足后再行动 | 练习三:启动检查 | -| 消息队列 | 定长消息 FIFO | `send` 发消息 / `recv` 收消息 | 线程间传递数据(生产者-消费者) | 练习四:快递分拣 | - -### 3. 信号量 vs 互斥量(重点) - -- **信号量**:值可以大于 1,用来"数"有多少个可用资源(3 个车位 → 初始值 3)。它**没有所有权**概念,任何线程都可以 release。适合**资源计数与分配**。 -- **互斥量**:值只有 0/1,是"锁"。它有**所有权**(谁 take 谁 release),并且带**优先级继承**机制——低优先级线程持有锁时,会临时提升优先级,避免高优先级线程被"饿死"(优先级反转)。适合**保护临界区**。 -- 一句话记忆:**互斥量是锁,信号量是计数器**。 - -### 4. 事件集 vs 信号量(重点) - -- 信号量等待的是"资源被释放",`release` 一次只会唤醒**一个**等待的 `take`,而且资源计数会被消耗。 -- 事件集等待的是"条件满足",事件位**不会被消耗**(`send` 一次,所有等待该位的线程都能看到);还支持 **AND 模式**(所有位到齐才唤醒)和 **OR 模式**(任一到位即唤醒)。 -- 需要"三个模块**全部**就绪才能启动"这种组合条件时,事件集的 AND 模式天然匹配;用三个独立信号量则需要 take 三次,无法表达"同时满足"的语义。 - -### 5. 消息队列的特点 - -- 传递的是**数据本身**(内存拷贝),不是指针,生产者和消费者彻底解耦。 -- 容量固定:队列满时 `send` 失败(或阻塞),空时 `recv` 阻塞等待。 -- 适合"生产者-消费者"模型,天然带缓冲,能平滑双方的速率差。 - ---- - -## 二、实验报告 - -### 练习一:停车场车位管理(计数信号量) - -**需求**:3 个车位、5 辆车;每辆车先申请车位才能进入;2 秒内申请不到打印等待信息稍后重试;任意时刻场内车辆 ≤ 3。 - -**实现思路**: -1. 信号量初始值 3,表示 3 个空闲车位; -2. 5 个车辆线程 `rt_sem_take(sem, 2秒)` 申请车位,成功 → 打印进入信息 → 停车 3 秒 → `rt_sem_release` 释放; -3. 超时 → 打印 `waiting for a space`,睡 1 秒后重新申请(循环); -4. 用全局变量 `used_spaces` 记录当前占用数(在 take 成功后 +1,此时持有信号量,计数安全); -5. 控制线程负责创建/回收信号量与线程,任务结束后删除信号量,命令可重复执行。 - -**关键代码**: - -```c -/* 车辆线程:申请车位 → 停车 → 离开 */ -if (rt_sem_take(parking_sem, WAIT_TIMEOUT) == RT_EOK) /* 2 秒超时 */ -{ - used_spaces++; - rt_kprintf("car%d entered, used spaces: %d\n", id, used_spaces); - rt_thread_mdelay(CAR_STAY_TIME); /* 停车 3 秒 */ - used_spaces--; - rt_kprintf("car%d left\n", id); - rt_sem_release(parking_sem); - break; -} -else -{ - rt_kprintf("car%d waiting for a space\n", id); - rt_thread_mdelay(RETRY_DELAY); /* 稍后再申请 */ -} -``` - -**预期运行结果**: - -```text -car1 entered, used spaces: 1 -car2 entered, used spaces: 2 -car3 entered, used spaces: 3 -car4 waiting for a space -car5 waiting for a space -car1 left -car4 entered, used spaces: 3 -car2 left -car5 entered, used spaces: 3 -car3 left -parking: all cars done, resources released -``` - -(实际运行顺序可能因调度略有不同,但"任意时刻 used spaces ≤ 3"和"超时重试"两个特性必然体现。) - -**思考题:为什么使用计数信号量,而不是互斥量?** - -车位是 **3 个同类的有限资源**,需要"数出还剩几个"。计数信号量初始值 = 3 就表达了这个资源池,允许多个线程同时占用(最多 3 个)。而互斥量只有 0/1 两种状态,只能锁"一个"资源;它的所有权和优先级继承机制是为"保护临界区"设计的,不适合"分配多个资源"的场景。若用互斥量模拟,必须创建 3 个互斥量并管理它们的分配,逻辑复杂且无法表达"还剩几个车位"。 - ---- - -### 练习二:多窗口售票系统(互斥量) - -**需求**:两个窗口卖同一批 20 张票;每次只能一个窗口修改余票;最终 20 张全卖出、票号不重复、余票不为负。 - -**实现思路**: -1. 全局变量 `ticket_count` 保存余票(初始 20); -2. 互斥量保护**完整售票过程**:检查余票 → 读票号 → 减票 → 打印,全程在锁内; -3. 两个窗口线程卖完(余票 0)自动退出; -4. 提供两个命令做对比实验:`ticket_demo`(加锁,正确版)和 `ticket_demo_nolock`(不加锁 + 读写之间插入 2ms 延时,演示竞态)。 - -**关键代码**: - -```c -rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); /* 进入临界区 */ -if (ticket_count <= 0) /* 没票了,退出 */ -{ - rt_mutex_release(ticket_mutex); - break; -} -int n = ticket_count; -rt_thread_mdelay(1); /* 模拟售票耗时(锁内,安全) */ -ticket_count = n - 1; -rt_kprintf("window %c sold ticket %d\n", name, n); -rt_mutex_release(ticket_mutex); /* 退出临界区 */ -``` - -**预期运行结果(加锁版)**: - -```text -window A sold ticket 20 -window B sold ticket 19 -window B sold ticket 18 -... -all tickets sold -remaining tickets: 0 -``` - -**对比实验结果(无锁版)**:不加锁时,两个窗口会**同时读到相同的余票**,卖出重复票号(例如 `window A sold ticket 20` 和 `window B sold ticket 20` 同时出现),实际卖出的票数会少于 20,演示出明显的竞态错误;加锁后所有票号唯一、总数恰好 20。 - -**思考题:为什么只给 `ticket_count--` 加锁仍可能不安全?** - -"售票"是一连串操作:**检查余票 > 0 → 读出票号 → 修改余票**。如果只锁减票那一行,两个线程可能同时通过"余票 > 0"的检查(都读到 20),然后都卖出 20 号票 → **重复售票**;极端情况下还会**超卖成负数**。必须把"检查 + 读 + 改 + 记录"作为一个**完整的临界区**整体加锁,才能保证判断和修改之间不被打断。 - ---- - -### 练习三:系统启动条件检查(事件集) - -**需求**:网络/传感器/存储三个模块独立初始化,全部就绪后主业务线程才能启动;先做 3 秒超时测试(存储模块故意设 5 秒),超时打印未就绪模块,再继续等待。 - -**实现思路**: -1. 事件集 + 三个事件位:`NET=1<<0`、`SENSOR=1<<1`、`STORAGE=1<<2`; -2. 三个初始化线程延时 1s/2s/5s 后 `rt_event_send` 各自就绪事件; -3. 主业务线程 `rt_event_recv` 用 **AND + CLEAR** 模式等待三个位全部到齐; -4. 第一轮只等 3 秒 → 必然超时(存储要 5 秒)→ 通过 `recved` 参数(已到位的位)判断并打印"哪个模块没就绪"; -5. 第二轮不限时等待,三个位到齐后打印 `all modules are ready` 并启动业务。 - -**关键代码**: - -```c -/* 第一轮:AND 模式等全部事件,最多 3 秒 */ -ret = rt_event_recv(startup_evt, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - BUSINESS_WAIT_TIME, &recved); -if (ret == RT_ETIMEOUT) -{ - if (!(recved & EVENT_STORAGE_READY)) - rt_kprintf("wait modules timeout, storage is not ready\n"); -} - -/* 第二轮:不限时,继续等直到三个事件全部到达 */ -rt_event_recv(startup_evt, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_WAITING_FOREVER, &recved); -rt_kprintf("all modules are ready\n"); -rt_kprintf("business task started\n"); -``` - -**预期运行结果**: - -```text -network ready -sensor ready -wait modules timeout, storage is not ready -storage ready -all modules are ready -business task started -``` - -**思考题:如果用三个独立信号量实现这个功能,与使用事件集相比有什么区别?** - -1. **等待方式**:三个信号量只能依次 take 三次,无法表达"三个条件**同时**满足"的 AND 语义;事件集一次调用即可等待组合条件。 -2. **资源消耗**:信号量被 take 后计数会消耗,一个 `release` 只能唤醒一个 `take`;事件位**不消耗**,`send` 一次所有等待线程都能看到——如果以后有多个业务线程都要等"全部就绪",事件集依然有效,信号量则不行。 -3. **诊断能力**:事件集超时后能通过 `recved` 参数知道"已经到齐了哪些位",方便打印缺哪个模块(本练习的超时诊断正是靠这一点);信号量做不到。 - ---- - -### 练习四:快递分拣中心(消息队列) - -**需求**:两个收件线程各产生 5 条快递消息(编号不重复),分拣线程从容量为 5 的消息队列中取出并按区域分类统计;队列满时发送线程不得崩溃,需记录失败次数;结束后打印统计结果。 - -**实现思路**: -1. 消息队列容量 5,消息结构 `struct package_msg`(id/region/weight); -2. 两个发送线程编号前缀不同(1001~1005 / 2001~2005),天然不重复;区域轮流 华东/华南/华北; -3. 队列满时 `rt_mq_send` 返回 `-RT_EFULL` → 记录 `send_failed` 并延时重试,**不崩溃**; -4. 每个发送线程发完 5 条后,再发一条"结束消息"(`region=0` 哨兵); -5. 分拣线程(高优先级)收到 2 条结束消息后,打印统计结果并退出。 - -**关键代码**: - -```c -/* 发送一条消息:队列满就记录失败并延时重试(不崩溃) */ -static void send_package(const struct package_msg *msg) -{ - while (rt_mq_send(pkg_mq, msg, sizeof(*msg)) != RT_EOK) - { - send_failed++; /* 队列满,记一次失败 */ - rt_thread_mdelay(50); /* 等分拣线程腾出空间 */ - } -} - -/* 分拣线程:循环收消息,收到结束消息(region=0)则计数 */ -while (end_count < SENDER_COUNT) -{ - rt_mq_recv(pkg_mq, &msg, sizeof(msg), RT_WAITING_FOREVER); - if (msg.region == MSG_END) { end_count++; continue; } - /* 打印 + 统计区域和重量 */ -} -``` - -**预期运行结果**: - -```text -package 1001 -> east, weight: 500 g -package 2001 -> south, weight: 500 g -package 1002 -> north, weight: 637 g -... -total packages: 10 -east: 4, south: 3, north: 3 -total weight: 11410 g -send failed: N -``` - -(`send failed` 统计的是"队列满被拒后重试"的次数,并发运行时 N > 0 是正常的,恰好证明"满队列不崩溃、消息不丢失"这一功能在工作;10 条消息最终全部送达,`total packages` 恒为 10。) - ---- - -## 三、实验总结与踩坑 - -1. **IPC 对象创建必须检查返回值**:信号量/互斥量/事件集/消息队列/线程创建失败时返回 `RT_NULL`,不检查会导致空指针崩溃。 -2. **MSH 命令描述字符串不能带逗号**:`MSH_CMD_EXPORT` 宏按逗号分隔参数,描述里出现逗号会被当成第 3 个参数导致编译错误(改用空格分隔描述)。 -3. **MSH 命令可重复执行**:通过"任务运行标志"拒绝重复启动 + 任务结束后主动删除 IPC 对象并复位状态,避免同名对象残留导致 `rt_sem_create` 等返回失败。 -4. **临界区要锁"完整过程"**:判断 + 修改必须作为一个整体加锁(练习二对比实验直观展示了不锁的后果)。 -5. **事件集先打印再发事件**:初始化线程先 `rt_kprintf` 再 `rt_event_send`,避免高优先级业务线程抢占导致输出顺序错乱。 - -### 选型心得 - -- 要"数资源" → 信号量;要"锁数据" → 互斥量;要"等条件组合" → 事件集;要"传数据" → 消息队列。四种机制配合使用可以解决绝大多数线程协作问题。 diff --git "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" "b/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" deleted file mode 100644 index 1d92af92f34a352252ab10bcbe937f2b55bdbd2b..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/package_demo.c" +++ /dev/null @@ -1,208 +0,0 @@ -/* - * ============================================================ - * 练习四:快递分拣中心(消息队列) - * - * 场景:两个收件线程不断产生快递消息,一个分拣线程从消息 - * 队列中取出快递并按目的地区域分类统计。 - * - * 设计思路: - * 1. 消息队列容量 5 条(struct package_msg) - * 2. 两个发件线程各产生 5 条消息(编号 1001~1005 / 2001~2005,不重复) - * 3. 队列满时 rt_mq_send 返回失败 → 记录失败次数 + 延时重试,不崩溃 - * 4. 发完 5 条后各发一条"结束消息"(region=0 哨兵) - * 5. 分拣线程收消息、打印、统计;收到 2 条结束消息后打印统计并退出 - * - * MSH 命令:package_demo - * ============================================================ - */ - -#include -#include - -#define MQ_MSG_COUNT 5 /* 队列容量:5 条消息 */ -#define MSG_PER_SENDER 5 /* 每个发件线程产生 5 条 */ -#define SENDER_COUNT 2 /* 发件线程数量 */ - -#define REGION_EAST 1 /* 华东 */ -#define REGION_SOUTH 2 /* 华南 */ -#define REGION_NORTH 3 /* 华北 */ -#define MSG_END 0 /* region=0 表示结束消息 */ - -/* 快递消息结构(作业给定) */ -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -static rt_mq_t pkg_mq = RT_NULL; /* 快递消息队列 */ -static volatile rt_uint32_t send_failed = 0;/* 队列满导致的发送失败次数 */ -static volatile rt_uint8_t threads_done = 0;/* 已结束的线程数 */ -static volatile rt_uint8_t task_running = 0;/* 任务是否在运行(防重复启动) */ - -/* 区域编号 → 名称 */ -static const char *region_name(rt_uint8_t r) -{ - switch (r) - { - case REGION_EAST: return "east"; - case REGION_SOUTH: return "south"; - case REGION_NORTH: return "north"; - default: return "unknown"; - } -} - -/* 发送一条消息:队列满就记录失败并延时重试,直到发出去(不崩溃) */ -static void send_package(const struct package_msg *msg) -{ - while (rt_mq_send(pkg_mq, msg, sizeof(*msg)) != RT_EOK) - { - send_failed++; /* 队列满,记一次失败 */ - rt_thread_mdelay(50); /* 等分拣线程腾出空间 */ - } -} - -/* ------------------------------------------------------------ - * 发件线程:产生 5 条快递消息 + 1 条结束消息 - * param 传编号前缀(1000 或 2000),保证两线程编号不重复 - * ---------------------------------------------------------- */ -static void sender_entry(void *param) -{ - int base = (int)(rt_ubase_t)param; - struct package_msg msg; - int i; - - for (i = 0; i < MSG_PER_SENDER; i++) - { - msg.id = (rt_uint32_t)(base + i + 1); /* 1001..1005 / 2001..2005 */ - msg.region = (rt_uint8_t)((i % 3) + 1); /* 区域轮流 华东/华南/华北 */ - msg.weight = (rt_uint16_t)(500 + (i * 137) % 2000); /* 伪随机重量 */ - send_package(&msg); - } - - /* 发完 5 条后,发送一条结束消息(region=0 哨兵) */ - msg.id = 0; - msg.region = MSG_END; - msg.weight = 0; - send_package(&msg); - - threads_done++; -} - -/* ------------------------------------------------------------ - * 分拣线程:收消息 → 打印 + 统计;收到全部结束消息后汇总退出 - * ---------------------------------------------------------- */ -static void sorter_entry(void *param) -{ - struct package_msg msg; - rt_uint32_t total = 0, total_weight = 0; - rt_uint32_t east = 0, south = 0, north = 0; - rt_uint8_t end_count = 0; /* 收到的结束消息数 */ - - while (end_count < SENDER_COUNT) - { - if (rt_mq_recv(pkg_mq, &msg, sizeof(msg), RT_WAITING_FOREVER) != RT_EOK) - break; /* 异常兜底 */ - - if (msg.region == MSG_END) /* 结束消息:计数,不参与统计 */ - { - end_count++; - continue; - } - - /* 正常快递:打印 + 统计 */ - total++; - total_weight += msg.weight; - if (msg.region == REGION_EAST) - east++; - else if (msg.region == REGION_SOUTH) - south++; - else if (msg.region == REGION_NORTH) - north++; - - rt_kprintf("package %d -> %s, weight: %d g\n", - msg.id, region_name(msg.region), msg.weight); - } - - /* 打印统计结果 */ - rt_kprintf("total packages: %d\n", total); - rt_kprintf("east: %d, south: %d, north: %d\n", east, south, north); - rt_kprintf("total weight: %d g\n", total_weight); - rt_kprintf("send failed: %d\n", send_failed); - - threads_done++; -} - -/* ------------------------------------------------------------ - * 控制线程:创建队列 + 3 个线程,等全部结束后清理 - * ---------------------------------------------------------- */ -static void package_ctrl_entry(void *param) -{ - rt_thread_t sorter, s1, s2; - - /* 1. 创建消息队列(容量 5 条),检查创建结果 */ - pkg_mq = rt_mq_create("pkg_mq", sizeof(struct package_msg), MQ_MSG_COUNT, - RT_IPC_FLAG_PRIO); - if (pkg_mq == RT_NULL) - { - rt_kprintf("package: create message queue failed\n"); - task_running = 0; - return; - } - - /* 2. 创建分拣线程(优先级高,尽快消费)+ 两个发件线程 */ - sorter = rt_thread_create("sorter", sorter_entry, RT_NULL, 2048, 15, 10); - s1 = rt_thread_create("sender1", sender_entry, (void *)(rt_ubase_t)1000, 2048, 20, 10); - s2 = rt_thread_create("sender2", sender_entry, (void *)(rt_ubase_t)2000, 2048, 20, 10); - if (sorter == RT_NULL || s1 == RT_NULL || s2 == RT_NULL) - { - rt_kprintf("package: create thread failed\n"); - rt_mq_delete(pkg_mq); - pkg_mq = RT_NULL; - task_running = 0; - return; - } - rt_thread_startup(sorter); - rt_thread_startup(s1); - rt_thread_startup(s2); - - /* 3. 等 3 个线程全部结束 */ - while (threads_done < 3) - rt_thread_mdelay(50); - - /* 4. 收尾:删队列、复位状态 */ - rt_mq_delete(pkg_mq); - pkg_mq = RT_NULL; - send_failed = 0; - threads_done = 0; - task_running = 0; - rt_kprintf("package: demo finished, resources released\n"); -} - -/* ------------------------------------------------------------ - * MSH 命令入口:package_demo - * ---------------------------------------------------------- */ -static void package_demo(void) -{ - rt_thread_t ctrl; - - if (task_running) - { - rt_kprintf("package_demo is already running\n"); - return; - } - task_running = 1; - - ctrl = rt_thread_create("package_ctrl", package_ctrl_entry, RT_NULL, 2048, 12, 10); - if (ctrl == RT_NULL) - { - rt_kprintf("package_demo: create control thread failed\n"); - task_running = 0; - return; - } - rt_thread_startup(ctrl); -} - -/* 导出为 msh 命令 */ -MSH_CMD_EXPORT(package_demo, package demo 2 senders 1 sorter queue size 5); diff --git "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" "b/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" deleted file mode 100644 index 59cce63b8af8397c2438565ff34e9183ad6e9493..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/parking_demo.c" +++ /dev/null @@ -1,150 +0,0 @@ -/* - * ============================================================ - * 练习一:停车场车位管理(计数信号量) - * - * 场景:停车场只有 3 个车位,同时有 5 辆车尝试进入。 - * 车位是有限资源,车辆必须先申请到车位才能进入。 - * - * 设计思路: - * 1. 计数信号量初始值 = 3(剩余车位) - * 2. 每辆车 = 一个线程,进入前 rt_sem_take() 申请一个车位 - * 3. 申请成功 → 打印进入信息 → 停车 3 秒 → 离开时 rt_sem_release() 释放车位 - * 4. 2 秒内申请不到 → 打印 waiting 信息,等 1 秒后重新申请 - * 5. 用全局变量 used_spaces 记录当前已占用车位(take 成功才 +1, - * 此时持有信号量,其他车进不来,所以计数是安全的) - * - * MSH 命令:parking_demo (可重复执行,运行时重复输入会被拒绝) - * ============================================================ - */ - -#include -#include - -#define PARKING_SPACES 3 /* 车位总数 */ -#define CAR_COUNT 5 /* 车辆数量 */ -#define CAR_STAY_TIME (3 * RT_TICK_PER_SECOND) /* 每辆车停车 3 秒 */ -#define WAIT_TIMEOUT (2 * RT_TICK_PER_SECOND) /* 申请车位最多等 2 秒 */ -#define RETRY_DELAY (1 * RT_TICK_PER_SECOND) /* 超时后等 1 秒再申请 */ - -static rt_sem_t parking_sem = RT_NULL; /* 车位信号量(3 个资源) */ -static volatile rt_uint8_t used_spaces = 0; /* 当前已占用车位 */ -static volatile rt_uint8_t cars_finished = 0; /* 已离开的车辆数 */ -static volatile rt_uint8_t task_running = 0; /* 任务是否在运行(防重复启动) */ - -/* ------------------------------------------------------------ - * 车辆线程:申请车位 → 停车 → 离开 - * param 传的是车辆编号(1~5) - * ---------------------------------------------------------- */ -static void car_thread_entry(void *param) -{ - rt_uint8_t id = (rt_uint8_t)(rt_ubase_t)param; - - while (1) - { - /* 申请一个车位,最多等 2 秒 */ - if (rt_sem_take(parking_sem, WAIT_TIMEOUT) == RT_EOK) - { - /* 拿到车位:占用数 +1,打印进入信息 */ - used_spaces++; - rt_kprintf("car%d entered, used spaces: %d\n", id, used_spaces); - - /* 停车 3 秒(模拟停留) */ - rt_thread_mdelay(CAR_STAY_TIME); - - /* 离开:占用数 -1,释放车位 */ - used_spaces--; - rt_kprintf("car%d left\n", id); - rt_sem_release(parking_sem); - break; - } - else - { - /* 2 秒没等到车位:打印等待信息,稍后重新申请 */ - rt_kprintf("car%d waiting for a space\n", id); - rt_thread_mdelay(RETRY_DELAY); - } - } - - /* 记录一辆车完成,供控制线程判断何时收尾 */ - cars_finished++; -} - -/* ------------------------------------------------------------ - * 控制线程:创建信号量 + 5 辆车,等全部结束后清理资源 - * ---------------------------------------------------------- */ -static void parking_ctrl_entry(void *param) -{ - rt_thread_t cars[CAR_COUNT]; - rt_uint8_t created = 0; - int i; - - /* 1. 创建计数信号量,初始值 = 3 个车位 */ - parking_sem = rt_sem_create("parking", PARKING_SPACES, RT_IPC_FLAG_PRIO); - if (parking_sem == RT_NULL) - { - rt_kprintf("parking: create semaphore failed\n"); - task_running = 0; - return; - } - - /* 2. 创建 5 个车辆线程,逐个检查创建结果 */ - for (i = 0; i < CAR_COUNT; i++) - { - cars[i] = rt_thread_create("car", car_thread_entry, - (void *)(rt_ubase_t)(i + 1), /* 车辆编号 1~5 */ - 2048, 20, 10); /* 栈 2KB, 优先级 20 */ - if (cars[i] == RT_NULL) - { - rt_kprintf("parking: create car%d thread failed\n", i + 1); - break; /* 创建失败就只跑已创建的车 */ - } - rt_thread_startup(cars[i]); - created++; - } - - /* 3. 等待所有车辆线程跑完(每辆车离开后 cars_finished +1) */ - while (cars_finished < created) - { - rt_thread_mdelay(50); - } - - /* 4. 全部结束:删除信号量,复位状态,允许下次再执行 */ - rt_sem_delete(parking_sem); - parking_sem = RT_NULL; - used_spaces = 0; - cars_finished = 0; - task_running = 0; - rt_kprintf("parking: all cars done, resources released\n"); -} - -/* ------------------------------------------------------------ - * MSH 命令入口:parking_demo - * ---------------------------------------------------------- */ -static void parking_demo(void) -{ - rt_thread_t ctrl; - - /* 任务还在跑时拒绝重复启动 */ - if (task_running) - { - rt_kprintf("parking_demo is already running\n"); - return; - } - - task_running = 1; - used_spaces = 0; - cars_finished = 0; - - ctrl = rt_thread_create("parking_ctrl", parking_ctrl_entry, RT_NULL, - 2048, 15, 10); /* 控制线程优先级更高 */ - if (ctrl == RT_NULL) - { - rt_kprintf("parking_demo: create control thread failed\n"); - task_running = 0; - return; - } - rt_thread_startup(ctrl); -} - -/* 导出为 msh 命令 */ -MSH_CMD_EXPORT(parking_demo, parking lot demo: 5 cars compete for 3 spaces); diff --git "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" "b/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" deleted file mode 100644 index d6dfe0a7dd87afe7ce911c6f847bef40fa8803a7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/startup_demo.c" +++ /dev/null @@ -1,187 +0,0 @@ -/* - * ============================================================ - * 练习三:系统启动条件检查(事件集) - * - * 场景:设备启动前,网络/传感器/存储三个模块各自独立初始化, - * 全部就绪后主业务线程才能启动。 - * - * 设计思路: - * 1. 事件集 + 3 个事件位:NET=1<<0, SENSOR=1<<1, STORAGE=1<<2 - * 2. 三个初始化线程延时不同时间后 send 各自就绪事件 - * 3. 主业务线程用 AND 模式等全部事件(缺一不可) - * 4. 超时测试:业务先只等 3 秒,存储模块要 5 秒 → 超时打印 - * 未就绪模块,然后继续等待直到全部就绪 - * - * MSH 命令:startup_demo - * ============================================================ - */ - -#include -#include - -#define EVENT_NET_READY (1U << 0) /* 网络就绪事件位 */ -#define EVENT_SENSOR_READY (1U << 1) /* 传感器就绪事件位 */ -#define EVENT_STORAGE_READY (1U << 2) /* 存储就绪事件位 */ -#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) - -#define NET_INIT_TIME (1 * RT_TICK_PER_SECOND) /* 网络初始化 1 秒 */ -#define SENSOR_INIT_TIME (2 * RT_TICK_PER_SECOND) /* 传感器初始化 2 秒 */ -#define STORAGE_INIT_TIME (5 * RT_TICK_PER_SECOND) /* 存储初始化 5 秒(故意拖慢) */ -#define BUSINESS_WAIT_TIME (3 * RT_TICK_PER_SECOND) /* 业务线程第一轮只等 3 秒 */ - -static rt_event_t startup_evt = RT_NULL; /* 启动事件集 */ -static volatile rt_uint8_t threads_done = 0;/* 已结束的线程数 */ -static volatile rt_uint8_t task_running = 0;/* 任务是否在运行(防重复启动) */ - -/* 三个模块的初始化参数表 */ -struct module_def -{ - rt_uint32_t init_time; /* 初始化耗时 */ - rt_uint32_t ready_bit; /* 对应的事件位 */ - const char *name; /* 模块名 */ -}; - -static const struct module_def modules[] = -{ - { NET_INIT_TIME, EVENT_NET_READY, "network" }, - { SENSOR_INIT_TIME, EVENT_SENSOR_READY, "sensor" }, - { STORAGE_INIT_TIME, EVENT_STORAGE_READY, "storage" }, -}; - -/* ------------------------------------------------------------ - * 初始化线程:延时模拟初始化 → 打印 ready → 发送就绪事件 - * param 传模块下标(0=network, 1=sensor, 2=storage) - * ---------------------------------------------------------- */ -static void init_module_entry(void *param) -{ - int idx = (int)(rt_ubase_t)param; - - /* 模拟模块初始化耗时 */ - rt_thread_mdelay(modules[idx].init_time); - - /* 先打印再发事件,保证串口输出顺序稳定 */ - rt_kprintf("%s ready\n", modules[idx].name); - rt_event_send(startup_evt, modules[idx].ready_bit); - - threads_done++; -} - -/* ------------------------------------------------------------ - * 主业务线程:AND 模式等三个事件全部到达 - * 第一轮超时 3 秒 → 打印未就绪模块 → 第二轮不限时等齐 - * ---------------------------------------------------------- */ -static void business_entry(void *param) -{ - rt_uint32_t recved = 0; - rt_err_t ret; - - /* 第一轮:AND 模式等全部事件,最多 3 秒 */ - ret = rt_event_recv(startup_evt, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - BUSINESS_WAIT_TIME, &recved); - if (ret == RT_ETIMEOUT) - { - /* 超时:recved 里是已经到位的位,差谁就打印谁 */ - if (!(recved & EVENT_NET_READY)) - rt_kprintf("wait modules timeout, network is not ready\n"); - if (!(recved & EVENT_SENSOR_READY)) - rt_kprintf("wait modules timeout, sensor is not ready\n"); - if (!(recved & EVENT_STORAGE_READY)) - rt_kprintf("wait modules timeout, storage is not ready\n"); - } - - /* 第二轮:不限时,继续等直到三个事件全部到达 */ - ret = rt_event_recv(startup_evt, EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_WAITING_FOREVER, &recved); - if (ret == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - } - - threads_done++; -} - -/* ------------------------------------------------------------ - * 控制线程:创建事件集 + 4 个线程,等全部结束后清理 - * ---------------------------------------------------------- */ -static void startup_ctrl_entry(void *param) -{ - rt_thread_t th[4]; - rt_uint8_t created = 0; - int i; - - /* 1. 创建事件集,检查创建结果 */ - startup_evt = rt_event_create("startup_evt", RT_IPC_FLAG_PRIO); - if (startup_evt == RT_NULL) - { - rt_kprintf("startup: create event failed\n"); - task_running = 0; - return; - } - - /* 2. 创建三个初始化线程 */ - for (i = 0; i < 3; i++) - { - th[i] = rt_thread_create("init_mod", init_module_entry, - (void *)(rt_ubase_t)i, 1024, 20, 10); - if (th[i] == RT_NULL) - { - rt_kprintf("startup: create init thread %d failed\n", i); - break; - } - rt_thread_startup(th[i]); - created++; - } - - /* 3. 创建主业务线程(优先级更高) */ - th[3] = rt_thread_create("business", business_entry, RT_NULL, 1024, 15, 10); - if (th[3] != RT_NULL) - { - rt_thread_startup(th[3]); - created++; - } - else - { - rt_kprintf("startup: create business thread failed\n"); - } - - /* 4. 等所有线程结束(3 个初始化 + 1 个业务) */ - while (threads_done < created) - rt_thread_mdelay(50); - - /* 5. 收尾:删事件集、复位状态 */ - rt_event_delete(startup_evt); - startup_evt = RT_NULL; - threads_done = 0; - task_running = 0; - rt_kprintf("startup: demo finished, resources released\n"); -} - -/* ------------------------------------------------------------ - * MSH 命令入口:startup_demo - * ---------------------------------------------------------- */ -static void startup_demo(void) -{ - rt_thread_t ctrl; - - if (task_running) - { - rt_kprintf("startup_demo is already running\n"); - return; - } - task_running = 1; - - ctrl = rt_thread_create("startup_ctrl", startup_ctrl_entry, RT_NULL, 2048, 12, 10); - if (ctrl == RT_NULL) - { - rt_kprintf("startup_demo: create control thread failed\n"); - task_running = 0; - return; - } - rt_thread_startup(ctrl); -} - -/* 导出为 msh 命令 */ -MSH_CMD_EXPORT(startup_demo, startup demo 3 modules init business waits events); diff --git "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" "b/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" deleted file mode 100644 index f322605d598d2d04a691beb3f5d6077caad1cc00..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/ticket_demo.c" +++ /dev/null @@ -1,175 +0,0 @@ -/* - * ============================================================ - * 练习二:多窗口售票系统(互斥量) - * - * 场景:两个售票窗口(A/B)同时出售同一批 20 张票, - * 每次只能由一个窗口修改剩余票数。 - * - * 设计思路: - * 1. 全局变量 ticket_count 保存剩余票数(初始 20) - * 2. 互斥量保护"检查余票 + 读票号 + 修改余票 + 打印"完整过程 - * 3. 两个售票线程循环卖票,卖完(余票 0)自动退出 - * 4. 对比实验:ticket_demo_nolock 命令 = 不加锁 + 读写之间加延时, - * 用来演示不加锁会出现重复售票 - * - * MSH 命令: - * ticket_demo 带互斥量(正确版) - * ticket_demo_nolock 不带互斥量(对比实验,演示竞态) - * ============================================================ - */ - -#include -#include - -#define TOTAL_TICKETS 20 /* 总票数 */ - -static rt_mutex_t ticket_mutex = RT_NULL; /* 售票互斥量 */ -static volatile int ticket_count = 0; /* 剩余票数(全局共享) */ -static volatile rt_uint8_t sellers_done = 0;/* 已结束的售票线程数 */ -static volatile rt_uint8_t task_running = 0;/* 任务是否在运行(防重复启动) */ - -/* ------------------------------------------------------------ - * 加锁版售票线程:检查+读+改+打印 整个过程都在互斥量保护内 - * param 传窗口字母('A' / 'B') - * ---------------------------------------------------------- */ -static void seller_locked_entry(void *param) -{ - char name = (char)(rt_ubase_t)param; - - while (1) - { - /* 进入临界区 */ - rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); - - if (ticket_count <= 0) /* 没票了,退出 */ - { - rt_mutex_release(ticket_mutex); - break; - } - - int n = ticket_count; /* 读出当前票号 */ - rt_thread_mdelay(1); /* 模拟售票耗时(在锁内,安全) */ - ticket_count = n - 1; /* 修改余票 */ - rt_kprintf("window %c sold ticket %d\n", name, n); - - rt_mutex_release(ticket_mutex); /* 退出临界区 */ - } - - sellers_done++; -} - -/* ------------------------------------------------------------ - * 无锁版售票线程(对比实验): - * "读余票"和"修改余票"之间加延时 → 两个窗口可能读到同一张票号 - * ---------------------------------------------------------- */ -static void seller_nolock_entry(void *param) -{ - char name = (char)(rt_ubase_t)param; - - while (1) - { - int n = ticket_count; /* ① 读余票 */ - rt_thread_mdelay(2); /* ② 延时:此时另一个窗口可能也读到了同一个 n */ - if (n <= 0) /* ③ 检查余票 */ - break; - ticket_count = n - 1; /* ④ 修改余票 */ - rt_kprintf("window %c sold ticket %d\n", name, n); - } - - sellers_done++; -} - -/* ------------------------------------------------------------ - * 控制线程:创建互斥量 + 两个售票线程,等全部结束后清理 - * param = 1 加锁模式 / 0 无锁模式 - * ---------------------------------------------------------- */ -static void ticket_ctrl_entry(void *param) -{ - int locked = (int)(rt_ubase_t)param; - rt_thread_t win_a, win_b; - - ticket_count = TOTAL_TICKETS; - sellers_done = 0; - - /* 1. 创建互斥量(仅加锁模式需要) */ - if (locked) - { - ticket_mutex = rt_mutex_create("ticket_mx", RT_IPC_FLAG_PRIO); - if (ticket_mutex == RT_NULL) - { - rt_kprintf("ticket: create mutex failed\n"); - task_running = 0; - return; - } - } - - /* 2. 创建两个售票线程,检查创建结果 */ - win_a = rt_thread_create("winA", locked ? seller_locked_entry : seller_nolock_entry, - (void *)(rt_ubase_t)'A', 2048, 20, 10); - win_b = rt_thread_create("winB", locked ? seller_locked_entry : seller_nolock_entry, - (void *)(rt_ubase_t)'B', 2048, 20, 10); - if (win_a == RT_NULL || win_b == RT_NULL) - { - rt_kprintf("ticket: create seller thread failed\n"); - if (locked && ticket_mutex != RT_NULL) - rt_mutex_delete(ticket_mutex); - task_running = 0; - return; - } - rt_thread_startup(win_a); - rt_thread_startup(win_b); - - /* 3. 等两个窗口都卖完 */ - while (sellers_done < 2) - rt_thread_mdelay(50); - - /* 4. 收尾:删互斥量、复位状态,允许下次再执行 */ - if (locked && ticket_mutex != RT_NULL) - { - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - } - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: %d\n", ticket_count); - task_running = 0; -} - -/* ------------------------------------------------------------ - * 启动函数:locked=1 带互斥量 / locked=0 不带 - * ---------------------------------------------------------- */ -static void start_ticket_demo(int locked) -{ - rt_thread_t ctrl; - - if (task_running) - { - rt_kprintf("ticket_demo is already running\n"); - return; - } - task_running = 1; - - ctrl = rt_thread_create("ticket_ctrl", ticket_ctrl_entry, - (void *)(rt_ubase_t)locked, 2048, 15, 10); - if (ctrl == RT_NULL) - { - rt_kprintf("ticket_demo: create control thread failed\n"); - task_running = 0; - return; - } - rt_thread_startup(ctrl); -} - -/* 两个 MSH 命令入口:加锁版 / 无锁对比版 */ -static void ticket_demo(void) -{ - start_ticket_demo(1); -} - -static void ticket_demo_nolock(void) -{ - start_ticket_demo(0); -} - -/* 导出为 msh 命令 */ -MSH_CMD_EXPORT(ticket_demo, ticket demo: 2 windows sell 20 tickets with mutex); -MSH_CMD_EXPORT(ticket_demo_nolock, ticket demo WITHOUT mutex: show race); diff --git "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index 445b683b0241a78fe2fd0ace4097d776d9e02df5..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232.md" +++ /dev/null @@ -1,73 +0,0 @@ -# 【DAY2】RT-Thread 启动流程、内核基础与多线程编程 - -> 第4组 / 霍渝陈 | STM32F407 星火1号 | RT-Thread 4.1.1 - ---- - -## Part 1 理论知识 - -### 1. 启动流程 - -``` -Reset_Handler(设栈→拷贝.data→清.bss→SystemInit→bl entry) - → entry() → rtthread_startup() - → 关中断→rt_hw_board_init(①级INIT)→版本横幅→timer/scheduler初始化 - →创建"main"线程→idle线程→rt_system_scheduler_start()(永不返回) - → main线程: rt_components_init(②~⑥级INIT) → main() 用户代码 -``` - -`main()` 跑在 main 线程里,由调度器调度。 - -### 2. 自动初始化原理 - -`INIT_xxx_EXPORT(fn)` 把函数指针放入 `.rti_fn.<级别>` 段,启动时按段序遍历调用: - -```c -#define INIT_EXPORT(fn, level) \ - RT_USED const init_fn_t __rt_init_##fn RT_SECTION(".rti_fn." level) = fn -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") /* 板级 */ -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") /* 应用 */ -``` - -链接脚本 `KEEP(*(SORT(.rti_fn*)))` 聚成连续数组,`rt_components_board_init()`(①级) / `rt_components_init()`(②~⑥级) 遍历执行。 - -### 3. 内核对象与内存分配 - -- 对象模型:线程/信号量等均为对象,基类 `rt_object`,对象容器按类型管理 -- 线程控制块关键成员:`stat` / `current_priority` / `init_tick`+`remaining_tick` / `sp` -- 静态 vs 动态:静态=编译期分配、无失败风险、无碎片,高可靠场景强制静态;动态=堆分配、灵活但可能失败 - -### 4. 五态模型与调度 - -- 五态:初始→就绪→运行→挂起→关闭 -- 调度:优先级抢占(高优先级立即抢占)+ 时间片轮转(同优先级递减轮换) - ---- - -## Part 2 多线程实验 - -### 1. 实现 - -| 线程 | 方式 | 优先级/时间片/栈 | 功能 | -|---|---|---|---| -| th_led1 | 动态 create | 16/5/1024 | 翻转 LED_R,打印 count+tick | -| th_led2 | 静态 init | 16/5/1024 | 翻转 LED_B,打印 count | -| th_monitor | 动态 create | 10/5/1024 | 每2s打印状态+剩余栈;按键挂起/恢复 th_led1 | - -`INIT_APP_EXPORT` 自动加载;`MSH_CMD_EXPORT(thread_dump)` 导出命令。 - -### 2. 运行结果 - -``` -[th_led1] count=177 tick=88569 -[th_led2] count=177 -[th_led1] count=178 tick=89073 -[th_led2] count=178 -[th_monitor] tick=90567 - th_led1 : stat=suspend prio=16 剩余栈=780/1024 - th_led2 : stat=suspend prio=16 剩余栈=776/1024 -``` - -- 两工作线程计数同步交替,时间片轮转正常 -- monitor 每 2s 准点抢占打印,优先级正常 -- 按键可挂起/恢复 th_led1 diff --git "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 53828e8d2c884fce35618129b5281c1c136f57cb..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,53 +0,0 @@ -# 一.markdown基础语法 - - # “#”一级标题 - -## “##二级标题” - -## 代码块 - -``` - -``` - -## 表格 - -| | | | -|||| -|| - -## 图片 - -![]() - -更多教程网址: - -# 二.Git的使用 - -## 基础命令 - -| 命令 | 功效 | -| :-----------: | :----------------------: | -| git init | 初始化当前目录 | -| git add . | 添加文件到暂存区 | -| git commit | 将暂存区内容添加到仓库中 | -| git checkout | 切换分支 | -| git remote | 远程仓库操作 | -| git pull/push | 下载/上传代码并合并 | -| | | - -## 提交pr流程 - -​ 1.在自己的仓库添加一个fork - -​ 2.`git clone` 将原代码拷到电脑上 - -​ 3.对代码进行修改 - -​ 4.`git checkout -b <你的分支名> `创建分支 - -​ 5.`git add .` `git commit -m <描述>`,将文件添加到仓库中 - -​ 6.`git push`上传代码并合并 - -​ 7.去Gitee Pull Requests \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 326386dd63ab5ad8cde421609cfce4883a136cfc..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\234\215\346\270\235\351\231\210/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,228 +0,0 @@ -# 第二天笔记:RT-Thread 启动流程、内核基础与多线程编程 - -> 目标平台:STM32F407 星火号(stm32f407-rtspark) -> 本笔记基于工程实际代码整理(RT-Thread 4.1.1,Cortex-M4),关键结论均对照真实源码/符号地址,可自行验证。 - ---- - -## 1. STM32F407 启动流程与 RT-Thread 引导机制 - -### 1.1 从上电复位 Reset_Handler 到进入 main() 的完整调用链 - -``` -上电复位 - │ - ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ ① Reset_Handler(汇编) │ -│ 文件:libraries/CMSIS/Device/ST/STM32F4xx/Source/Templates/ │ -│ gcc/startup_stm32f407xx.S │ -│ · ldr sp, =_estack 设置栈指针 │ -│ · 循环拷贝 .data(Flash → RAM) │ -│ · 循环清零 .bss │ -│ · bl SystemInit 时钟等系统初始化(弱符号可覆盖) │ -│ · bl entry ★关键:调 entry 而不是 main │ -└─────────────────────────────────────────────────────────────────┘ - │ - ▼ -② entry() (rt-thread/src/components.c,GCC 分支) - │ int entry(void) { rtthread_startup(); return 0; } - │ (链接时用 -eentry 指定它为 ELF 入口符号) - ▼ -③ rtthread_startup() (rt-thread/src/components.c) - │ 按顺序执行: - │ 1. rt_hw_interrupt_disable() 关中断 - │ 2. rt_hw_board_init() 板级初始化(时钟/串口/堆) - │ └─ 内部调用 rt_components_board_init() → 执行 INIT_BOARD_EXPORT 的 ①级函数 - │ 3. rt_show_version() ★打印 RT-Thread 版本横幅 - │ 4. rt_system_timer_init() 系统时钟节拍(tick)初始化 - │ 5. rt_system_scheduler_init() 调度器初始化(就绪队列等) - │ 6. rt_application_init() 创建名为 "main" 的线程 ★ - │ 7. rt_system_timer_thread_init() 定时器线程 - │ 8. rt_thread_idle_init() 空闲线程(idle) - │ 9. rt_system_scheduler_start() ★启动调度器,此函数永不返回 - ▼ -④ 调度器切换到 main 线程 → main_thread_entry() (components.c) - │ 1. rt_components_init() → 执行 INIT_PREV/DEVICE/COMPONENT/ENV/APP 的 ②~⑥级函数 - │ 2. main() → 进入用户代码(applications/main.c) - ▼ -⑤ 用户 main() 里创建业务线程(如作业里的 th_led1/th_led2/th_monitor), - 之后所有线程由调度器按优先级/时间片调度 -``` - -**必须理解的三个点:** - -1. **你的 `main()` 不是程序起点**。起点是汇编 `Reset_Handler`,它做完整机初始化后调 `entry()` 进内核。 -2. **main() 跑在名为 "main" 的线程里**(`rt_application_init()` 创建),由调度器调度执行——RTOS 里没有"裸奔大循环",只有线程。 -3. `rt_system_scheduler_start()` 之后**不再返回**,所有代码都在线程/中断上下文里运行。 - -> 验证方法(对照自己工程):编译后对 `rtthread.elf` 执行 -> `arm-none-eabi-nm rtthread.elf | grep -E "Reset_Handler|entry|rtthread_startup|main"` -> 实际符号:`Reset_Handler`(弱) → `entry` 0x08000be0 → `rtthread_startup` 0x08000c54 → `main` 0x0800b34c - -### 1.2 自动初始化原理:INIT_BOARD_EXPORT 与 INIT_APP_EXPORT - -**一句话认知**:`INIT_xxx_EXPORT(函数)` 不是"调用"函数,而是把**函数指针**放到一个特殊段里;系统启动时按段顺序**自动遍历调用**,用户不用改 main。 - -**① 宏定义**(rt-thread/include/rtdef.h): - -```c -#define INIT_EXPORT(fn, level) \ - RT_USED const init_fn_t __rt_init_##fn RT_SECTION(".rti_fn." level) = fn - -#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") /* 板级 */ -#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2") /* 纯软件预初始化 */ -#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3") /* 设备 */ -#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4") /* 组件(dfs/lwip...) */ -#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") /* 环境(挂载...) */ -#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") /* 应用 */ -``` - -展开后等价于:`const init_fn_t __rt_init_my_func RT_SECTION(".rti_fn.6") = my_func;` -即:**一个放在 .rti_fn.6 段里的函数指针**。 - -**② 链接脚本收集**(linkscripts/STM32F407ZG/link.lds): - -```ld -__rt_init_start = .; -KEEP(*(SORT(.rti_fn*))) /* 按段名排序,把所有 init 函数指针聚成连续数组 */ -__rt_init_end = .; -``` - -**③ 执行者**(rt-thread/src/components.c): - -| 函数 | 遍历范围 | 谁调用 | 何时 | -|---|---|---|---| -| `rt_components_board_init()` | `__rt_init_rti_board_start` → `__rt_init_rti_board_end`(①级) | `rt_hw_board_init()` | 内核启动早期,中断仍关闭 | -| `rt_components_init()` | `__rt_init_rti_board_end` → `__rt_init_rti_end`(②~⑥级) | main 线程的 `main_thread_entry()` | 调度器启动后 | - -**④ 实际例子**(对 01_hello_rtt1 工程 elf 反查符号): - -``` -0x0800e4a0 __rt_init_rti_start -0x0800e4a4 __rt_init_rti_board_start -0x0800e4a8 __rt_init_clock_information ← 时钟初始化(INIT_BOARD_EXPORT) -0x0800e4ac __rt_init_rti_board_end -0x0800e4b0 __rt_init_finsh_system_init ← finsh 控制台(INIT_PREV_EXPORT) -0x0800e4b4 __rt_init_rti_end -``` - -**⑤ 设计意义**:组件自己"报名"、系统统一按序执行 → 模块解耦、增减组件不用改 main、初始化顺序由段名(数字)天然保证。 - ---- - -## 2. 内核基础与对象模型 - -### 2.1 对象容器与控制块 - -RT-Thread 用 **C 语言实现面向对象**:所有内核资源(线程、信号量、互斥量、消息队列、事件、定时器、设备…)都是"对象"。 - -- 基类 `struct rt_object`:`name` / `type` / `flags` / `list`(对象链表节点) -- 派生:`struct rt_thread`、`struct rt_semaphore`、`struct rt_mutex`…(首成员就是基类,可强转) -- 对象容器:`rt_object_container[]`(按 type 分类),`rt_object_find()` 按名字查对象 - -**线程控制块 `struct rt_thread` 关键成员**(rt-thread/include/rtdef.h): - -| 成员 | 作用 | -|---|---| -| `stat` | 线程状态:INIT/READY/RUNNING/SUSPEND/CLOSE(见第 3 节五态模型) | -| `current_priority` | 当前优先级(0 最高,越小越先调度) | -| `init_tick` | 创建时设定的 tick 数(时间片长度 / 延时基准) | -| `remaining_tick` | 剩余 tick:时间片轮转时每个 tick 递减,减到 0 让出 CPU | -| `sp` | ★栈指针:上下文切换时把寄存器现场压栈保存到这里,恢复时从这里弹栈 | -| `entry` / `parameter` | 线程入口函数及其参数 | -| `stack_addr` / `stack_size` | 线程栈地址与大小(栈溢出检测依据) | -| `tlist` | 就绪队列链表节点(同优先级线程串在一起轮转) | -| `error` | 线程错误码 | - -### 2.2 内存分配方式:静态对象 vs 动态对象 - -| 维度 | 静态(`rt_thread_init` + 全局变量) | 动态(`rt_thread_create`) | -|---|---|---| -| 分配位置 | 编译期定死,TCB 和栈在 `.bss/.data`(全局数组) | 运行时从堆 `rt_malloc` 分配 | -| 生命周期 | 与程序共存亡 | 可 `rt_thread_delete` 随时销毁 | -| 分配失败风险 | **不可能失败** | 堆不足时返回 NULL(要判空!) | -| 内存碎片 | 无 | 反复创建/销毁会产生碎片 | -| 可分析性 | 编译完内存布局完全确定 | 运行期才确定 | -| 使用 | `struct rt_thread t; rt_uint8_t stack[1024]; rt_thread_init(&t, ...)` | `tid = rt_thread_create("name", fn, ...)` | - -**为什么高可靠场景强制使用静态线程**:航空航天/汽车/医疗等场景要求**确定性**——不允许 malloc 失败、不允许碎片导致后期分配不出内存、内存使用量必须可静态审计。动态分配在长时间运行 + 频繁创建销毁时可能因碎片化而"悄悄失败",这在安全关键系统里不可接受。 - -> 作业对应:`th_led1`(动态,体验 create/delete)、`th_led2`(静态,定义全局 TCB + 栈 + rt_thread_init),正好两种都练到。 - ---- - -## 3. 线程状态机与调度算法 - -### 3.1 五态模型 - -``` - rt_thread_init / rt_thread_create - │ - ▼ - ┌────── 初始态 (INIT) ──────┐ - │ │ │ - │ rt_thread_startup │ - │ ▼ │ - │ ┌── 就绪态 (READY) ──┐ │ - │ │ │ │ │ - │ │ 调度器选中 │ │ 延时到期 / 信号量释放 / - │ │ ▼ │ │ rt_thread_resume - │ │ 运行态 (RUNNING) ──┼──┘ - │ │ │ ▲ │ - │ │ │ └────────────┘ 更高优先级线程抢占 - │ │ │ - │ │ ├─ rt_thread_delay/mdelay - │ │ ├─ rt_sem_take / rt_mutex_take / rt_mq_recv(资源不可用) - │ │ └─ rt_thread_suspend - │ │ ▼ - │ └── 挂起态 (SUSPEND) ────┐ - │ │ 被唤醒(见上) / resume - └── rt_thread_delete ────────┼──────────────┐ - / 线程函数返回 │ │ - ▼ ▼ - ┌── 关闭态 (CLOSE) ──┘ - │ 静态: rt_thread_detach - │ 动态: rt_thread_delete(回收内存) - └ -``` - -| 状态 | 说明 | 进入 API | 离开 API | -|---|---|---|---| -| 初始态 INIT | 已创建未启动,只占资源不参与调度 | `rt_thread_init/create` | `rt_thread_startup` | -| 就绪态 READY | 排队等 CPU | `startup` / `resume` / 阻塞条件满足 | 被调度选中 → 运行 | -| 运行态 RUNNING | 正在占用 CPU(全局同时只有一个) | 调度器选中 | 时间片耗尽 / 被抢占 / 主动阻塞 | -| 挂起态 SUSPEND | 阻塞等待某条件(延时/资源) | `rt_thread_delay`、`rt_sem_take`、`rt_thread_suspend` | 条件满足 / `rt_thread_resume` | -| 关闭态 CLOSE | 结束,资源待回收 | 线程函数 return、`rt_thread_delete` | `rt_thread_detach`(静态) | - -> 作业里 `rt_thread_suspend(th_led1)` 挂起 / `rt_thread_resume(th_led1)` 恢复,就是手动触发 运行态→挂起态→就绪态 的转换。 - -### 3.2 调度机制剖析 - -**① 抢占式调度(优先级驱动)** - -- 每个线程有优先级(0 ~ RT_THREAD_PRIORITY_MAX-1,默认 32 级,**0 最高**) -- 内核维护按优先级组织的**就绪队列**(位图 `number_mask` 加速查找最高优先级) -- 只要就绪队列里出现比当前线程**更高优先级**的线程,调度器立即切换(抢占点:创建线程、唤醒、释放信号量、延时到期等) -- 低优先级线程只有高优先级线程阻塞/结束时才能运行 - -> 作业中 `th_monitor` 优先级 10 < 工作线程 16,所以 monitor 一唤醒就**抢占**两个工作线程。 - -**② 时间片轮转(同优先级轮流)** - -- 同优先级的多个就绪线程按 `tlist` 链表排队 -- 每个线程拿到 `init_tick` 个 tick 的时间片,每过一个 tick `remaining_tick--` -- 减到 0 → 主动让出,切换到**同优先级下一个**线程,时间片重置为 `init_tick` -- 若该优先级只有 1 个线程,则时间片轮转退化为一直运行(直到被抢占/阻塞) - -**③ 调度触发与切换**:所有"可能改变就绪状态"的操作最终都会走到 `rt_schedule()`(src/scheduler.c),选择最高优先级就绪线程;真正切换由汇编 `rt_hw_context_switch`(Cortex-M4 在 libcpu/arm/cortex-m4/context_gcc.S)完成:**保存当前线程 sp 指向的寄存器现场 → 恢复目标线程现场 → 跳转**——这就是 `struct rt_thread.sp` 成员的用武之地。 - ---- - -## 附:作业进度自查 - -- [x] Part 1 理论整理(本文档) -- [ ] Part 2 多线程实验:`thread_study.c`(th_led1 动态 / th_led2 静态 / th_monitor 监控+按键挂起恢复 / INIT_APP_EXPORT 自动加载 / MSH_CMD_EXPORT 导出 thread_dump) -- [ ] 板级验证(编译 + 星火1号烧录 + FinSH 串口日志截图) -- [ ] 作业目录 README.md(Part1 + Part2 实验分析 + 截图) -- [ ] Gitee PR 提交(分支 day2-huoyuchen,标题《[作业提交] 内核与多线程培训 - 霍渝陈》) diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232.md" deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 9fd6c8d816c82d92252c72adefee03b32847acf7..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\200\345\244\251\344\275\234\344\270\232/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,272 +0,0 @@ -# RT-Thread夏令营学习笔记 - -## 一、Markdown基础学习 - -### 1.Markdown基本语法 - -Markdown是一种轻量级标记语言,常用于编写学习笔记、项目说明以及README文档。本次主要学习了标题、列表、粗体、代码块、链接和图片等基础语法。 - -常用写法如下: - -```markdown -# 一级标题 -## 二级标题 -### 三级标题 -#### ..... -- 无序列表 -1. 有序列表 - -**粗体** -`行内代码` -***斜体*** -``` - -Markdown语法简单,编写完成后也能够保持比较清晰的文档结构,非常适合用于整理后续RT-Thread学习内容。 - -### 2.代码块和文档编写 - -在嵌入式学习过程中,经常需要记录命令、程序代码以及操作步骤,因此代码块是Markdown中比较常用的功能。 - -例如记录Git命令: - -```bash -git status -git add . -git commit -m "完成学习任务" -``` - -使用代码块可以将程序、命令和普通说明文字区分开,使学习笔记更加清晰,也方便后续查找和复制命令。 - -### 3.Markdown的实际用途 - -在RT-Thread和开源项目的学习过程中,Markdown可以用于: - -- 编写每天的学习笔记; -- 编写Git仓库中的README.md; -- 记录程序编译、运行以及实验过程; -- 编写Pull Request中的说明内容。 - -因此,Markdown不仅可以用来整理笔记,也是参与开源项目开发需要掌握的一项基础技能。 - ------- - -## 二、RT-Thread基础环境配置 - -### 1.下载SDK和板级文件包 - -本次首先完成了RT-Thread基础开发环境的配置,包括下载SDK和对应的板级文件包。 - -SDK主要提供程序编译所需要的工具链、编译器以及相关开发工具。板级支持包BSP则与具体芯片和开发板相关,其中通常包含启动文件、驱动程序以及工程配置等内容。 - -整个环境可以简单理解为: - -```text -RT-Thread源码 - + -SDK开发工具 - + -BSP板级支持 - ↓ -RT-Thread开发环境 -``` - -只有正确配置SDK和BSP,后续才能正常创建、编译和运行RT-Thread工程。 - -### 2.创建工程并进行编译 - -完成基础环境配置后,需要创建RT-Thread工程,并通过编译来检查环境是否配置正确。 - -本次学习中使用了`qemu-vexpress-a9`工程。QEMU可以模拟ARM硬件环境,因此即使没有实际开发板,也可以运行RT-Thread进行学习和测试。 - -基本操作流程为: - -```text -创建qemu-vexpress-a9工程 - ↓ -检查工程配置 - ↓ -编译RT-Thread工程 - ↓ -检查编译结果 -``` - -如果工程能够顺利完成编译,并且没有出现工具链或文件缺失等错误,说明基础开发环境已经配置成功。 - -### 3.运行工程并检查msh - -完成工程编译后,可以通过QEMU运行RT-Thread。 - -程序正常启动后,会显示RT-Thread的启动信息,并进入msh命令行。 - -整体检查过程可以总结为: - -```text -创建工程 - ↓ -成功编译 - ↓ -QEMU运行 - ↓ -RT-Thread启动 - ↓ -进入msh -``` - -msh是RT-Thread提供的命令行Shell,可以通过输入命令查看系统状态和执行相关功能。 - -因此,当工程能够成功编译并进入msh时,就说明RT-Thread的基础开发环境已经基本搭建完成。 - ------- - -## 三、Git仓库和Pull Request - -### 1.Git基本操作 - -本次学习完成了Git环境配置,并创建了个人代码仓库,同时学习了常见的Git版本管理命令。 - -常用命令包括: - -```bash -git clone <仓库地址> -git status -git add . -git commit -m "提交信息" -git pull -git push -``` - -这些命令分别用于克隆仓库、查看修改、添加文件、提交修改、拉取代码以及推送代码。 - -基本工作流程可以理解为: - -```text -修改文件 - ↓ -git status - ↓ -git add . - ↓ -git commit - ↓ -git push - ↓ -远程仓库 -``` - -通过Git可以记录每一次代码和文档修改,方便后续查看历史版本以及进行多人协作。 - -### 2.分支和仓库同步 - -在进行新的学习任务之前,需要先保证本地仓库和远程主分支保持同步。 - -第一次PR提交,可以执行: - -```bash -git switch master -git pull origin master -git checkout -b day1 -``` - -其中: - -```bash -git switch master -``` - -用于切换回主分支。 - -```bash -git pull origin master -``` - -用于拉取远程主分支中的最新内容。 - -```bash -git checkout -b day1 -``` - -用于基于最新主分支创建一个新的`day1`分支。 - -因此比较规范的操作顺序为: - -```text -同步上游仓库 - ↓ -切换master - ↓ -拉取最新代码 - ↓ -创建新分支 - ↓ -开始新的任务 -``` - -这样可以避免不同学习任务之间的修改混在一起。 - -### 3.Pull Request基本流程 - -Pull Request通常简称PR,主要用于请求将自己分支中的代码或文档修改合并到目标仓库。 - -在开源项目中,一般先Fork目标仓库到自己的账号,再将仓库Clone到本地,然后创建新的开发分支。 - -基本流程为: - -```text -Fork上游仓库 - ↓ -Clone个人仓库 - ↓ -创建新分支 - ↓ -修改代码或文档 - ↓ -git add . - ↓ -git commit - ↓ -git push - ↓ -创建Pull Request -``` - -例如第二天完成学习任务后,可以执行: - -```bash -git add . -git commit -m "完成Day1学习任务" -git push origin day2 -``` - -随后进入远程仓库,在`day2`分支下创建新的PR。 - -如果后续只是修改当前PR中的问题,可以继续在原来的分支上修改并再次提交: - -```bash -git add . -git commit -m "修改PR内容" -git push origin day2 -``` - -新的提交会继续更新到当前PR中。 - -如果开始的是一个新的学习任务,则应该先同步主分支,再创建新的分支和新的PR。 - -通过本次学习,我基本掌握了从本地修改代码到提交PR的完整流程: - -```text -同步代码 - ↓ -创建分支 - ↓ -完成修改 - ↓ -git add - ↓ -git commit - ↓ -git push - ↓ -创建PR -``` - -这也是后续参与RT-Thread开源项目和管理个人学习代码时需要经常使用的基本工作流程。 \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113053514.png" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113053514.png" deleted file mode 100644 index 2488b3ee615c3e80a53ec7359fa876f232160a34..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113053514.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113312260.png" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113312260.png" deleted file mode 100644 index c4669e65b55037a99128124d24e64d60c7975f50..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113312260.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113426632.png" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113426632.png" deleted file mode 100644 index 5e2576c6549b4b67aaabd12e0dc94af04d6c5c98..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113426632.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113522585.png" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113522585.png" deleted file mode 100644 index 452dc11e95fc6b9a9d4c36f5c901f0ac9dfdd464..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113522585.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113553835.png" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113553835.png" deleted file mode 100644 index f720886981b17b1516a8dd4051dce7c505615863..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/image-20260820113553835.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/package_demo.c" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/package_demo.c" deleted file mode 100644 index fa3331eed863555b4ee753cacf9aa7f0cfffca54..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/package_demo.c" +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 24527 the first version - */ - -#include -#include - -#define SENDER_COUNT 2 -#define PACKAGE_PER_SENDER 5 -#define QUEUE_CAPACITY 5 - -#define THREAD_PRIORITY 25 -#define THREAD_STACK_SIZE 1024 -#define THREAD_TIMESLICE 5 - -#define END_PACKAGE_ID 0xFFFFFFFFU -#define SEND_RETRY_MS 50 - -/** - * @brief 快递消息结构。 - */ -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; - -/** - * @brief 发件线程参数。 - */ -struct sender_info -{ - const char *name; - rt_uint32_t base_id; - rt_uint8_t region_offset; -}; - -/* 消息队列 */ -static rt_mq_t package_mq = RT_NULL; - -/* 两个发件线程和一个分拣线程 */ -static rt_thread_t sender_threads[SENDER_COUNT] = {RT_NULL}; -static rt_thread_t sorter_thread = RT_NULL; - -/* 发件线程完成数量 */ -static rt_uint8_t sender_finished = 0; - -/* 消息发送失败次数 */ -static rt_uint32_t send_failed = 0; - -/* 防止重复启动 */ -static rt_bool_t demo_running = RT_FALSE; - -/* 两个发件线程参数 */ -static struct sender_info senders[SENDER_COUNT] = -{ - {"A", 1001, 0}, - {"B", 2001, 1} -}; - -/** - * @brief 获取区域名称。 - * - * @param region 区域编号。 - * - * @return 区域名称。 - */ -static const char *region_name(rt_uint8_t region) -{ - if (region == 1) - { - return "east"; - } - - if (region == 2) - { - return "south"; - } - - return "north"; -} - -/** - * @brief 发送一条快递消息。 - * - * 当消息队列已满时,记录一次发送失败, - * 延时后继续重试,直到消息成功进入队列。 - * - * @param msg 待发送的快递消息。 - */ -static void send_package(struct package_msg *msg) -{ - rt_err_t result; - - while (1) - { - result = rt_mq_send(package_mq, - msg, - sizeof(struct package_msg)); - - if (result == RT_EOK) - { - break; - } - - rt_enter_critical(); - send_failed++; - rt_exit_critical(); - - rt_kprintf("send package %d failed, retry later\n", msg->id); - - rt_thread_mdelay(SEND_RETRY_MS); - } -} - -/** - * @brief 发件线程入口。 - * - * 每个发件线程产生 5 条快递消息。 - * 两个线程使用不同编号段,因此快递编号不会重复。 - * - * @param parameter 指向 sender_info 的线程参数。 - */ -static void sender_entry(void *parameter) -{ - struct sender_info *sender; - struct package_msg msg; - struct package_msg end_msg; - rt_uint8_t i; - rt_bool_t last_sender = RT_FALSE; - - sender = (struct sender_info *)parameter; - - for (i = 0; i < PACKAGE_PER_SENDER; i++) - { - /* 生成唯一快递编号 */ - msg.id = sender->base_id + i; - - /* 模拟三个不同区域 */ - msg.region = ((i + sender->region_offset) % 3) + 1; - - /* 模拟快递重量 */ - msg.weight = 800 + ((msg.id + i) % 6) * 100; - - send_package(&msg); - - /* 模拟连续收件过程 */ - rt_thread_mdelay(20); - } - - /* - * 记录已经完成的发件线程数量。 - * 最后结束的发件线程负责发送结束消息。 - */ - rt_enter_critical(); - - sender_finished++; - - if (sender_finished == SENDER_COUNT) - { - last_sender = RT_TRUE; - } - - rt_exit_critical(); - - if (last_sender == RT_TRUE) - { - end_msg.id = END_PACKAGE_ID; - end_msg.region = 0; - end_msg.weight = 0; - - /* - * 结束消息必须成功送入队列, - * 如果此时队列已满,则等待后重试。 - */ - while (rt_mq_send(package_mq, - &end_msg, - sizeof(struct package_msg)) != RT_EOK) - { - rt_thread_mdelay(SEND_RETRY_MS); - } - } - - rt_kprintf("sender %s finished\n", sender->name); -} - -/** - * @brief 分拣线程入口。 - * - * 从消息队列中持续接收快递, - * 根据区域分类并统计数量和总重量。 - * - * 收到结束消息后打印最终统计结果并退出。 - * - * @param parameter 未使用。 - */ -static void sorter_entry(void *parameter) -{ - struct package_msg msg; - rt_uint32_t total = 0; - rt_uint32_t east_count = 0; - rt_uint32_t south_count = 0; - rt_uint32_t north_count = 0; - rt_uint32_t total_weight = 0; - rt_err_t result; - - while (1) - { - result = rt_mq_recv(package_mq, - &msg, - sizeof(struct package_msg), - RT_WAITING_FOREVER); - - if (result != RT_EOK) - { - rt_kprintf("receive package failed\n"); - continue; - } - - /* 收到结束消息,结束分拣 */ - if (msg.id == END_PACKAGE_ID) - { - break; - } - - total++; - total_weight += msg.weight; - - if (msg.region == 1) - { - east_count++; - } - else if (msg.region == 2) - { - south_count++; - } - else if (msg.region == 3) - { - north_count++; - } - - rt_kprintf( - "package %d -> %s, weight: %d g\n", - msg.id, - region_name(msg.region), - msg.weight); - - /* - * 模拟分拣处理时间。 - * 让生产速度可能快于消费速度, - * 从而更容易观察到队列已满。 - */ - rt_thread_mdelay(100); - } - - rt_kprintf("\n"); - rt_kprintf("total packages: %d\n", total); - rt_kprintf("east: %d, south: %d, north: %d\n", - east_count, - south_count, - north_count); - rt_kprintf("total weight: %d g\n", total_weight); - rt_kprintf("send failed: %d\n", send_failed); - - rt_mq_delete(package_mq); - package_mq = RT_NULL; - demo_running = RT_FALSE; -} - -/** - * @brief 启动快递分拣中心实验。 - * - * 创建容量为 5 条消息的消息队列, - * 创建两个发件线程和一个分拣线程。 - * - * @return RT_EOK 启动成功。 - * @return -RT_ERROR 消息队列或线程创建失败。 - */ -static rt_err_t package_demo(void) -{ - rt_err_t result; - int i; - - if (demo_running == RT_TRUE) - { - rt_kprintf("package demo is already running\n"); - return RT_EOK; - } - - sender_finished = 0; - send_failed = 0; - - /* 创建容量为 5 条消息的消息队列 */ - package_mq = rt_mq_create( - "pkgmq", - sizeof(struct package_msg), - QUEUE_CAPACITY, - RT_IPC_FLAG_FIFO); - - if (package_mq == RT_NULL) - { - rt_kprintf("create package message queue failed\n"); - return -RT_ERROR; - } - - demo_running = RT_TRUE; - - /* 创建分拣线程 */ - sorter_thread = rt_thread_create( - "sorter", - sorter_entry, - RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, - THREAD_TIMESLICE); - - if (sorter_thread == RT_NULL) - { - rt_kprintf("create sorter thread failed\n"); - rt_mq_delete(package_mq); - package_mq = RT_NULL; - demo_running = RT_FALSE; - return -RT_ERROR; - } - - /* 创建两个发件线程 */ - for (i = 0; i < SENDER_COUNT; i++) - { - sender_threads[i] = rt_thread_create( - i == 0 ? "sender_a" : "sender_b", - sender_entry, - &senders[i], - THREAD_STACK_SIZE, - THREAD_PRIORITY, - THREAD_TIMESLICE); - - if (sender_threads[i] == RT_NULL) - { - rt_kprintf("create sender %d thread failed\n", i + 1); - return -RT_ERROR; - } - } - - /* 启动分拣线程 */ - result = rt_thread_startup(sorter_thread); - - if (result != RT_EOK) - { - rt_kprintf("startup sorter thread failed\n"); - return -RT_ERROR; - } - - /* 启动两个发件线程 */ - for (i = 0; i < SENDER_COUNT; i++) - { - result = rt_thread_startup(sender_threads[i]); - - if (result != RT_EOK) - { - rt_kprintf("startup sender %d thread failed\n", i + 1); - return -RT_ERROR; - } - } - - return RT_EOK; -} - -/* 导出为 MSH 命令 */ -MSH_CMD_EXPORT(package_demo, package demo); diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/parking_demo.c" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/parking_demo.c" deleted file mode 100644 index 3e96031530287551bdb9b49fbce3d3c2be4dc90f..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/parking_demo.c" +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-19 RT-Thread first version - */ - -#include -#include - -/* 停车参数设置 */ -#define CAR_COUNT 5 -#define PARKING_SPACE_COUNT 3 - -/* 线程参数 */ -#define THREAD_PRIORITY 25 -#define THREAD_STACK_SIZE 1024 -#define THREAD_TIMESLICE 5 - -/* 等待时间与停车时间 */ -#define WAIT_TIME_MS 2000 -#define PARK_TIME_MS 3000 // 假设停车时间一样,随机停车时间也可以用rand() - -/* 停车位信号量 */ -static rt_sem_t parking_sem = RT_NULL; - -/* 用于等待所有车辆线程结束 */ -static rt_sem_t done_sem = RT_NULL; - -/* 当前剩余车位 */ -static rt_int8_t remaining_spaces = PARKING_SPACE_COUNT; - -/* 车辆线程句柄 */ -static rt_thread_t car_threads[CAR_COUNT]; - -/** - * @brief 修改剩余车位数量。 - * - * 车辆成功获得信号量后车位减 1, - * 车辆释放信号量后车位加 1。 - * - * @param change 车位变化量,-1 表示占用,+1 表示释放。 - * - * @return 修改后的剩余车位数量。 - */ -static rt_int8_t parking_space_change(rt_int8_t change) -{ - rt_int8_t spaces; - - rt_enter_critical(); - - remaining_spaces += change; - spaces = remaining_spaces; - - rt_exit_critical(); - - return spaces; -} - -/** - * @brief 车辆线程入口函数。 - * - * 5 个车辆线程使用相同优先级和时间片, - * 由 RT-Thread 调度器决定线程运行顺序。 - * 每辆车最多等待 2 秒申请车位,超时后重新申请。 - * - * @param parameter 车辆编号。 - */ -static void car_thread_entry(void *parameter) -{ - rt_uint32_t car_id = (rt_uint32_t)parameter; - rt_err_t result; - rt_int8_t spaces; - - while (1) - { - /* 申请一个停车位,成功后信号量数量减 1 */ - result = rt_sem_take( - parking_sem, - rt_tick_from_millisecond(WAIT_TIME_MS)); - - if (result == RT_EOK) - { - spaces = parking_space_change(-1); - - rt_kprintf( - "car%d entered, used spaces: %d \n", - car_id, - spaces); - - break; - } - - if (result == -RT_ETIMEOUT) - { - rt_kprintf( - "car%d wait timeout, retry later.\n", - car_id); - - rt_thread_mdelay(500); - continue; - } - - rt_kprintf( - "car%d take semaphore failed, error = %d.\n", - car_id, - result); - - rt_sem_release(done_sem); - return; - } - - /* 模拟车辆停车 */ - rt_thread_mdelay(PARK_TIME_MS); - - /* - * 车辆离开时释放停车位信号量。 - * rt_sem_release() 相当于归还一个停车资源。 - */ - result = rt_sem_release(parking_sem); - - if (result == RT_EOK) - { - spaces = parking_space_change(1); - - rt_kprintf( - "car%d left \n", - car_id); - } - else - { - rt_kprintf( - "car%d thread release semaphore failed, error = %d.\n", - car_id, - result); - } - - /* 通知主函数当前车辆线程已经结束 */ - rt_sem_release(done_sem); -} - -/** - * @brief 启动停车场车位管理实验。 - * - * 创建一个初始值为 3 的计数信号量和 5 个同优先级车辆线程。 - * 所有车辆线程以相同优先级参与调度竞争,获得信号量的车辆进入停车场。 - * - * @return RT_EOK 实验执行成功。 - * @return -RT_ERROR 创建 IPC 或线程失败。 - */ -int parking_demo(void) -{ - rt_err_t result; - int i; - int started_count = 0; - - remaining_spaces = PARKING_SPACE_COUNT; - - /* 创建停车位计数信号量,初始值为 3 */ - parking_sem = rt_sem_create( - "parksem", - PARKING_SPACE_COUNT, - RT_IPC_FLAG_FIFO); - - if (parking_sem == RT_NULL) - { - rt_kprintf("create parking semaphore failed.\n"); - return -RT_ERROR; - } - - /* 创建线程完成信号量 */ - done_sem = rt_sem_create( - "parkdone", - 0, - RT_IPC_FLAG_FIFO); - - if (done_sem == RT_NULL) - { - rt_kprintf("create done semaphore failed.\n"); - rt_sem_delete(parking_sem); - parking_sem = RT_NULL; - return -RT_ERROR; - } - - /* 创建 5 个相同优先级的车辆线程, 由RT-thread随机决定谁先进 */ - for (i = 0; i < CAR_COUNT; i++) - { - car_threads[i] = rt_thread_create( - "car", - car_thread_entry, - (void *)(rt_uint32_t)(i + 1), - THREAD_STACK_SIZE, - THREAD_PRIORITY, - THREAD_TIMESLICE); - - if (car_threads[i] == RT_NULL) - { - rt_kprintf("create car %d failed.\n", i + 1); - continue; - } - - result = rt_thread_startup(car_threads[i]); - - if (result != RT_EOK) - { - rt_kprintf("startup car %d failed.\n", i + 1); - rt_thread_delete(car_threads[i]); - car_threads[i] = RT_NULL; - continue; - } - - started_count++; - } - - /* 等待所有成功启动的车辆线程结束 */ - for (i = 0; i < started_count; i++) - { - rt_sem_take(done_sem, RT_WAITING_FOREVER); - } - - /* 删除 IPC 对象 */ - rt_sem_delete(done_sem); - rt_sem_delete(parking_sem); - - done_sem = RT_NULL; - parking_sem = RT_NULL; - - rt_kprintf("parking demo finished.\n"); - - return RT_EOK; -} - -/* 导出为 MSH 命令 */ -MSH_CMD_EXPORT(parking_demo, parking lot semaphore demo); diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/startup_demo.c" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/startup_demo.c" deleted file mode 100644 index 244b4f94b10e67b7d8a0e8e96457f0248e22cc69..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/startup_demo.c" +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 24527 the first version - */ - -#include -#include - -/* 三个模块对应的事件位 */ -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) - -#define EVENT_ALL_READY (EVENT_NET_READY | \ - EVENT_SENSOR_READY | \ - EVENT_STORAGE_READY) - -#define THREAD_PRIORITY 25 -#define THREAD_STACK_SIZE 1024 -#define THREAD_TIMESLICE 5 - -/* 模块初始化时间 */ -#define NET_INIT_MS 1000 -#define SENSOR_INIT_MS 2000 -#define STORAGE_INIT_MS 5000 - -/* 主业务线程第一次最多等待 3 秒 */ -#define BUSINESS_WAIT_MS 3000 - -/* 系统启动事件集 */ -static rt_event_t startup_event = RT_NULL; - -/* 线程句柄 */ -static rt_thread_t net_thread = RT_NULL; -static rt_thread_t sensor_thread = RT_NULL; -static rt_thread_t storage_thread = RT_NULL; -static rt_thread_t business_thread = RT_NULL; - -/* 防止重复启动 */ -static rt_bool_t demo_running = RT_FALSE; - - -/** - * @brief 网络模块初始化线程。 - * - * 模拟网络模块初始化 1 秒,完成后发送网络就绪事件。 - * - * @param parameter 未使用。 - */ -static void net_init_entry(void *parameter) -{ - rt_thread_mdelay(NET_INIT_MS); - - rt_event_send(startup_event, EVENT_NET_READY); - - rt_kprintf("network ready\n"); -} - - -/** - * @brief 传感器模块初始化线程。 - * - * 模拟传感器模块初始化 2 秒,完成后发送传感器就绪事件。 - * - * @param parameter 未使用。 - */ -static void sensor_init_entry(void *parameter) -{ - rt_thread_mdelay(SENSOR_INIT_MS); - - rt_event_send(startup_event, EVENT_SENSOR_READY); - - rt_kprintf("sensor ready\n"); -} - - -/** - * @brief 存储模块初始化线程。 - * - * 为测试超时,将存储模块初始化时间设置为 5 秒, - * 完成后发送存储就绪事件。 - * - * @param parameter 未使用。 - */ -static void storage_init_entry(void *parameter) -{ - rt_thread_mdelay(STORAGE_INIT_MS); - - rt_event_send(startup_event, EVENT_STORAGE_READY); - - rt_kprintf("storage ready\n"); -} - - -/** - * @brief 打印当前尚未完成的模块。 - * - * 通过事件集当前已经到达的事件位, - * 判断网络、传感器和存储模块中哪些还没有准备完成。 - */ -static void print_not_ready_modules(void) -{ - rt_uint32_t received = 0; - - /* 使用 OR 模式立即读取当前已经到达的事件。 */ - rt_event_recv(startup_event, - EVENT_ALL_READY, - RT_EVENT_FLAG_OR, - 0, - &received); - - rt_kprintf("wait modules timeout"); - - if ((received & EVENT_NET_READY) == 0) - { - rt_kprintf(", network is not ready"); - } - - if ((received & EVENT_SENSOR_READY) == 0) - { - rt_kprintf(", sensor is not ready"); - } - - if ((received & EVENT_STORAGE_READY) == 0) - { - rt_kprintf(", storage is not ready"); - } - - rt_kprintf("\n"); -} - - -/** - * @brief 主业务线程。 - * - * 第一次使用 AND 模式等待三个模块全部就绪, - * 最多等待 3 秒。 - * - * 如果发生超时,则打印尚未完成的模块, - * 随后再次使用 AND 模式等待,直到三个事件全部到达。 - * - * @param parameter 未使用。 - */ -static void business_entry(void *parameter) -{ - rt_err_t result; - rt_uint32_t received = 0; - - /* - * 第一次等待: - * 必须三个事件全部到达,最长等待 3 秒。 - */ - result = rt_event_recv( - startup_event, - EVENT_ALL_READY, - RT_EVENT_FLAG_AND, - rt_tick_from_millisecond(BUSINESS_WAIT_MS), - &received); - - if (result == -RT_ETIMEOUT) - { - /* 超时后打印尚未完成的模块 */ - print_not_ready_modules(); - - /* - * 第二次继续等待。 - * 使用 AND 模式,只有三个模块全部完成后才会返回。 - */ - result = rt_event_recv( - startup_event, - EVENT_ALL_READY, - RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, - RT_WAITING_FOREVER, - &received); - } - - if (result == RT_EOK) - { - rt_kprintf("all modules are ready\n"); - rt_kprintf("business task started\n"); - } - else - { - rt_kprintf("wait startup event failed, error = %d\n", result); - } - - /* 业务线程负责释放事件集。 */ - rt_event_delete(startup_event); - startup_event = RT_NULL; - demo_running = RT_FALSE; -} - - -/** - * @brief 创建并启动一个线程。 - * - * 用于减少四个线程重复的创建和启动代码。 - * - * @param thread 保存线程句柄的地址。 - * @param name 线程名称。 - * @param entry 线程入口函数。 - * - * @return RT_EOK 创建并启动成功。 - * @return -RT_ERROR 创建或启动失败。 - */ -static rt_err_t create_start_thread(rt_thread_t *thread, - const char *name, - void (*entry)(void *parameter)) -{ - rt_err_t result; - - *thread = rt_thread_create(name, - entry, - RT_NULL, - THREAD_STACK_SIZE, - THREAD_PRIORITY, - THREAD_TIMESLICE); - - if (*thread == RT_NULL) - { - rt_kprintf("create %s thread failed\n", name); - return -RT_ERROR; - } - - result = rt_thread_startup(*thread); - - if (result != RT_EOK) - { - rt_kprintf("startup %s thread failed\n", name); - - rt_thread_delete(*thread); - *thread = RT_NULL; - - return result; - } - - return RT_EOK; -} - - -/** - * @brief 启动系统启动条件检查实验。 - * - * 创建网络、传感器、存储和主业务四个线程。 - * 三个初始化线程分别发送对应事件位, - * 主业务线程必须使用 AND 模式等待所有模块完成。 - * - * @return RT_EOK 启动成功。 - * @return -RT_ERROR 事件集或线程创建失败。 - */ -static rt_err_t startup_demo(void) -{ - rt_err_t result; - - if (demo_running == RT_TRUE) - { - rt_kprintf("system start demo is already running\n"); - return RT_EOK; - } - - /* 创建动态事件集 */ - startup_event = rt_event_create("startup", RT_IPC_FLAG_FIFO); - - if (startup_event == RT_NULL) - { - rt_kprintf("create startup event failed\n"); - return -RT_ERROR; - } - - demo_running = RT_TRUE; - - /* 创建并启动三个初始化线程和一个业务线程 */ - result = create_start_thread(&net_thread, - "net_init", - net_init_entry); - if (result != RT_EOK) - { - goto __exit; - } - - result = create_start_thread(&sensor_thread, - "sensor_init", - sensor_init_entry); - if (result != RT_EOK) - { - goto __exit; - } - - result = create_start_thread(&storage_thread, - "storage_init", - storage_init_entry); - if (result != RT_EOK) - { - goto __exit; - } - - result = create_start_thread(&business_thread, - "business", - business_entry); - if (result != RT_EOK) - { - goto __exit; - } - - return RT_EOK; - -__exit: - - /* 创建失败时删除事件集。 */ - if (startup_event != RT_NULL) - { - rt_event_delete(startup_event); - startup_event = RT_NULL; - } - - demo_running = RT_FALSE; - - return -RT_ERROR; -} - - -/* 导出为 MSH 命令 */ -MSH_CMD_EXPORT(startup_demo, startup demo); diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/ticket_demo.c" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/ticket_demo.c" deleted file mode 100644 index 7a74328dafd8eaeb0efb579380bde49ead9d613d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/ticket_demo.c" +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 24527 the first version - */ - -#include -#include - -#define TOTAL_TICKETS 20 -#define THREAD_PRIORITY 25 -#define THREAD_STACK_SIZE 1024 -#define THREAD_TIMESLICE 5 - -/* 剩余票数 */ -static rt_int32_t remaining_tickets = TOTAL_TICKETS; - -/* 售票互斥量 */ -static rt_mutex_t ticket_mutex = RT_NULL; - -/* 两个售票线程 */ -static rt_thread_t window_a_thread = RT_NULL; -static rt_thread_t window_b_thread = RT_NULL; - -/* 已结束的窗口数量 */ -static rt_uint8_t finished_windows = 0; - -/* 实验运行标志 */ -static rt_bool_t ticket_running = RT_FALSE; - -/** - * @brief 售票窗口线程入口。 - * - * 每个窗口循环申请互斥量,在获得互斥量后完成 - * 检查余票、记录票号、余票减 1 和打印售票结果。 - * - * @param parameter 窗口名称。 - * - * @return 无。 - */ -static void ticket_window_entry(void *parameter) -{ - const char *window_name = (const char *)parameter; - rt_err_t result; - rt_int32_t ticket_no; - - while (1) - { - /* 获取互斥量,保护完整售票过程 */ - result = rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); - - if (result != RT_EOK) - { - rt_kprintf("window %s take mutex failed.\n", window_name); - return; - } - - /* 检查是否还有余票 */ - if (remaining_tickets <= 0) - { - rt_mutex_release(ticket_mutex); - break; - } - - /* 当前剩余票数作为本次售出的票号 */ - ticket_no = remaining_tickets; - - /* 售出一张票,余票减 1 */ - remaining_tickets--; - - rt_kprintf( - "window %s sold ticket %d, remaining: %d\n", - window_name, - ticket_no, - remaining_tickets); - - /* 售票完成后释放互斥量 */ - result = rt_mutex_release(ticket_mutex); - - if (result != RT_EOK) - { - rt_kprintf("window %s release mutex failed.\n", window_name); - return; - } - - /* - * 模拟窗口处理下一位乘客, - * 同时给另一个同优先级线程运行机会。 - */ - rt_thread_mdelay(100); - } - - /* - * 统计已经结束的窗口。 - * finished_windows 也是共享变量,因此同样使用互斥量保护。 - */ - result = rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); - - if (result == RT_EOK) - { - rt_bool_t last_window = RT_FALSE; - - finished_windows++; - - if (finished_windows == 2) - { - rt_kprintf("all tickets sold\n"); - rt_kprintf("remaining tickets: %d\n", remaining_tickets); - last_window = RT_TRUE; - } - - rt_mutex_release(ticket_mutex); - - /* - * 最后结束的窗口负责删除互斥量, - * 此时两个售票线程都已经完成售票循环。 - */ - if (last_window == RT_TRUE) - { - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - ticket_running = RT_FALSE; - } - } -} - -/** - * @brief 启动多窗口售票实验。 - * - * 创建一个互斥量和两个同优先级售票线程, - * 两个窗口共同出售 20 张票。 - * - * @return RT_EOK 启动成功。 - * @return -RT_ERROR 创建互斥量或线程失败。 - */ -static int ticket_demo(void) -{ - /* 防止实验尚未结束时重复启动 */ - if (ticket_running == RT_TRUE) - { - rt_kprintf("ticket demo is already running.\n"); - return RT_EOK; - } - - /* 重置实验数据 */ - remaining_tickets = TOTAL_TICKETS; - finished_windows = 0; - - /* 创建售票互斥量 */ - ticket_mutex = rt_mutex_create("ticket", RT_IPC_FLAG_FIFO); - - if (ticket_mutex == RT_NULL) - { - rt_kprintf("create ticket mutex failed.\n"); - return -RT_ERROR; - } - - ticket_running = RT_TRUE; - - /* 创建窗口 A 线程 */ - window_a_thread = rt_thread_create( - "window_a", - ticket_window_entry, - "A", - THREAD_STACK_SIZE, - THREAD_PRIORITY, - THREAD_TIMESLICE); - - if (window_a_thread == RT_NULL) - { - rt_kprintf("create window A thread failed.\n"); - rt_mutex_delete(ticket_mutex); - ticket_mutex = RT_NULL; - ticket_running = RT_FALSE; - return -RT_ERROR; - } - - /* 创建窗口 B 线程 */ - window_b_thread = rt_thread_create( - "window_b", - ticket_window_entry, - "B", - THREAD_STACK_SIZE, - THREAD_PRIORITY, - THREAD_TIMESLICE); - - if (window_b_thread == RT_NULL) - { - rt_kprintf("create window B thread failed.\n"); - rt_thread_delete(window_a_thread); - rt_mutex_delete(ticket_mutex); - - window_a_thread = RT_NULL; - ticket_mutex = RT_NULL; - ticket_running = RT_FALSE; - - return -RT_ERROR; - } - - /* 启动两个售票线程 */ - rt_thread_startup(window_a_thread); - rt_thread_startup(window_b_thread); - - return RT_EOK; -} - -/* 导出为 MSH 命令 */ -MSH_CMD_EXPORT(ticket_demo, ticket demo); - diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/ticket_demo_withoutmutex.c" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/ticket_demo_withoutmutex.c" deleted file mode 100644 index fbf3af43886e0216c06d9fcc8ff7a24ccc4f8679..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/ticket_demo_withoutmutex.c" +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-20 24527 the first version - */ - -#include -#include - -#define TOTAL_TICKETS 20 -#define THREAD_PRIORITY 25 -#define THREAD_STACK_SIZE 1024 -#define THREAD_TIMESLICE 5 - -/* 线程延时 */ -#define RACE_DELAY_MS 50 - -/* 全局剩余票数 */ -static rt_int32_t remaining_tickets = TOTAL_TICKETS; - -/* 两个售票线程 */ -static rt_thread_t window_a_thread = RT_NULL; -static rt_thread_t window_b_thread = RT_NULL; - -/** - * @brief 不使用互斥量的售票窗口线程。 - * - * 线程先读取全局剩余票数,再故意延时一小段时间, - * 最后才修改剩余票数。 - * - * 由于窗口 A 和窗口 B 没有互斥保护, - * 两个线程可能同时读取到相同的票号, - * 从而产生重复售票或剩余票数更新错误。 - * - * @param parameter 窗口名称。 - * - * @return 无。 - */ -static void ticket_window_entry(void *parameter) -{ - const char *window_name = (const char *)parameter; - rt_int32_t ticket_no; - - while (1) - { - /* 检查是否还有余票 */ - if (remaining_tickets <= 0) - { - break; - } - - /* 读取当前余票。 */ - ticket_no = remaining_tickets; - - /* 故意加入短暂延时。 */ - rt_thread_mdelay(RACE_DELAY_MS); - - /* - * 根据之前读取到的票号修改剩余票数。 */ - remaining_tickets = ticket_no - 1; - - rt_kprintf( - "window %s sold ticket %d, remaining: %d\n", - window_name, - ticket_no, - remaining_tickets); - - /* 给另一个同优先级线程运行机会 */ - rt_thread_mdelay(20); - } - - rt_kprintf("window %s finished.\n", window_name); -} - -/** - * @brief 启动不使用互斥量的售票实验。 - * - * 创建两个相同优先级的售票线程, - * 两个线程共同访问全局变量 remaining_tickets。 - * - * 不使用互斥量,并在读取和修改余票之间延时, - * 用于观察竞态条件造成的重复售票或数据错误。 - * - * @return RT_EOK 启动成功。 - * @return -RT_ERROR 线程创建或启动失败。 - */ -static int ticket_demo_withoutmutex(void) -{ - rt_err_t result; - - /* 每次运行前初始化为20张票 */ - remaining_tickets = TOTAL_TICKETS; - - rt_kprintf("\n"); - rt_kprintf("===== ticket demo WITHOUT mutex =====\n"); - rt_kprintf("initial tickets: %d\n", remaining_tickets); - - /* 创建窗口A线程 */ - window_a_thread = rt_thread_create( - "window_a", - ticket_window_entry, - "A", - THREAD_STACK_SIZE, - THREAD_PRIORITY, - THREAD_TIMESLICE); - - if (window_a_thread == RT_NULL) - { - rt_kprintf("create window A thread failed.\n"); - return -RT_ERROR; - } - - /* 创建窗口B线程 */ - window_b_thread = rt_thread_create( - "window_b", - ticket_window_entry, - "B", - THREAD_STACK_SIZE, - THREAD_PRIORITY, - THREAD_TIMESLICE); - - if (window_b_thread == RT_NULL) - { - rt_kprintf("create window B thread failed.\n"); - - rt_thread_delete(window_a_thread); - window_a_thread = RT_NULL; - - return -RT_ERROR; - } - - /* 启动窗口A线程 */ - result = rt_thread_startup(window_a_thread); - - // 创建线程A失败 - if (result != RT_EOK) - { - rt_kprintf("startup window A failed, error = %d.\n", result); - - rt_thread_delete(window_a_thread); - rt_thread_delete(window_b_thread); - - // 删除未初始化的线程 - window_a_thread = RT_NULL; - window_b_thread = RT_NULL; - - return -RT_ERROR; - } - - /* 启动窗口B线程 */ - result = rt_thread_startup(window_b_thread); - - if (result != RT_EOK) - { - rt_kprintf("startup window B failed, error = %d.\n", result); - - // a已经运行,仅删除b线程 - rt_thread_delete(window_b_thread); - window_b_thread = RT_NULL; - - return -RT_ERROR; - } - - return RT_EOK; -} - -/* 导出为 MSH 命令 */ -MSH_CMD_EXPORT(ticket_demo_withoutmutex, ticket demo without mutex); diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/\345\256\236\351\252\214\346\212\245\345\221\212.md" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/\345\256\236\351\252\214\346\212\245\345\221\212.md" deleted file mode 100644 index 881085e2f3be000e34a5c54fee4979ff8289e2e1..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/\345\256\236\351\252\214\346\212\245\345\221\212.md" +++ /dev/null @@ -1,752 +0,0 @@ -# RT-Thread 线程同步与 IPC 实验报告 - -## 一、实验目的 - -本次实验通过 RT-Thread 中的线程调度、信号量、互斥量、事件集和消息队列等机制,模拟多个典型的多线程应用场景,理解线程同步、共享资源保护以及线程间通信的基本方法。 - -实验主要包括以下四个练习: - -1. 停车场车位管理:使用计数信号量管理有限资源; -2. 多窗口售票系统:比较不使用互斥量和使用互斥量时共享数据访问的区别; -3. 系统启动条件检查:使用事件集等待多个模块全部初始化完成; -4. 快递分拣中心:使用消息队列完成生产者与消费者之间的数据传递。 - ---- - -## 二、实验环境 - -- 操作系统:RT-Thread -- 开发语言:C -- 线程创建:`rt_thread_create()` -- 线程启动:`rt_thread_startup()` -- MSH 命令导出:`MSH_CMD_EXPORT()` - -实验中主要使用以下 IPC 机制: - -- 计数信号量 `rt_sem_t` -- 互斥量 `rt_mutex_t` -- 事件集 `rt_event_t` -- 消息队列 `rt_mq_t` - ---- - -## 三、理论总结 - -### 3.1 线程调度 - -RT-Thread 中每个线程具有自己的优先级、线程栈和时间片。 - -当多个线程具有相同优先级时,可以在调度器控制下竞争 CPU。需要注意的是,相同优先级并不代表真正的随机执行,实际运行顺序还会受到就绪队列、时间片、线程阻塞和唤醒等因素影响。 - -因此,本实验中的车辆线程和售票线程虽然使用相同优先级,但其实际执行顺序主要由调度器决定。 - -### 3.2 信号量 - -信号量适合表示有限数量的共享资源。 - -例如停车场存在 3 个车位,可以创建初始值为 3 的计数信号量。 - -线程调用: - -```c -rt_sem_take(); -``` - -成功获取信号量后,相当于占用一个资源。 - -线程完成操作后调用: - -```c -rt_sem_release(); -``` - -相当于归还一个资源。 - -因此,计数信号量适合停车位、缓冲区等有限资源管理场景。 - -### 3.3 互斥量 - -互斥量主要用于保护共享资源。 - -一个线程获得互斥量后,其他需要同一个互斥量的线程必须等待,直到当前线程释放互斥量。 - -基本过程为: - -```c -rt_mutex_take(); - -访问和修改共享资源; - -rt_mutex_release(); -``` - -如果一个业务过程包含“读取、判断、修改”等多个步骤,就需要将整个过程放入临界区,而不是只保护其中一条语句。 - -### 3.4 事件集 - -事件集适合处理多个独立条件的同步。 - -例如设备启动时,可以分别使用不同事件位表示: - -```c -EVENT_NET_READY -EVENT_SENSOR_READY -EVENT_STORAGE_READY -``` - -主线程使用 `RT_EVENT_FLAG_AND` 等待时,只有所有指定事件位全部满足,线程才会继续运行。 - -因此事件集适合“多个条件全部完成后才能继续”的场景。 - -### 3.5 消息队列 - -消息队列用于不同线程之间传递数据。 - -生产线程通过: - -```c -rt_mq_send(); -``` - -发送消息。 - -消费线程通过: - -```c -rt_mq_recv(); -``` - -接收消息。 - -使用消息队列后,生产线程不需要直接操作消费线程,二者可以独立执行,因此能够降低线程之间的耦合。 - ---- - -# 四、练习一:停车场车位管理 - -## 4.1 实验目的 - -模拟一个只有 3 个车位的停车场,同时有 5 辆车尝试进入。 - -要求最多只有 3 辆车同时停车,当车位已满时,其他车辆等待停车位;等待超过 2 秒后输出超时信息并重新申请。 - -## 4.2 实现方法 - -程序创建初始值为 3 的计数信号量: - -```c -parking_sem = rt_sem_create( - "parksem", - PARKING_SPACE_COUNT, - RT_IPC_FLAG_FIFO); -``` - -同时创建 5 个相同优先级的车辆线程。 - -车辆通过: - -```c -rt_sem_take( - parking_sem, - rt_tick_from_millisecond(2000)); -``` - -申请停车位。 - -获得停车位成功后,全局变量: - -```c -remaining_spaces -``` - -减 1,用于记录当前剩余停车位。 - -车辆停车一段时间后执行: - -```c -rt_sem_release(parking_sem); -``` - -释放停车资源,并将剩余车位加 1。 - -## 4.3 实验现象 - -运行: - -```text -parking_demo -``` - -后可以观察到: - -- 5 个车辆线程参与停车位竞争; -- 同一时刻最多只有 3 辆车能够获得停车位; -- 当 3 个车位全部占用以后,其余车辆进入等待; -- 等待超过 2 秒的车辆输出超时信息; -- 超时车辆稍后再次申请停车位; -- 有车辆离开并释放信号量以后,等待车辆可以继续进入; -- 最终所有车辆完成停车过程。 - -由于车辆线程优先级和时间片相同,因此车辆的具体执行顺序由 RT-Thread 调度器决定,而不是程序人为固定。 - -![image-20260820113053514](figures/image-20260820113053514.png) - -## 4.4 结果分析 - -停车场初始存在: - -```text -3 个可用停车位 -``` - -一辆车成功执行 `rt_sem_take()` 后,可用停车资源减少一个。 - -车辆执行 `rt_sem_release()` 后,停车资源重新可用。 - -因此,即使存在 5 个车辆线程同时竞争,也只有最多 3 个线程能够同时获得停车资源。 - -说明计数信号量非常适合管理**数量有限的共享资源**。 - ---- - -# 五、练习二:多窗口售票系统 - -## 5.1 实验目的 - -使用窗口 A 和窗口 B 同时出售 20 张车票,通过“不使用互斥量”和“使用互斥量”两种情况进行对比,观察并发修改共享变量时可能发生的问题。 - -全局余票定义为: - -```c -static rt_int32_t remaining_tickets = TOTAL_TICKETS; -``` - ---- - -## 5.2 不使用互斥量 - -### 实现方法 - -在第一个版本中,窗口线程不使用互斥量。 - -线程首先读取: - -```c -ticket_no = remaining_tickets; -``` - -随后故意延时: - -```c -rt_thread_mdelay(50); -``` - -再执行: - -```c -remaining_tickets = ticket_no - 1; -``` - -通过这个延时增加线程在“读取”和“修改”之间发生切换的概率。 - -### 实验现象 - -运行: - -```text -ticket_demo_withoutmutex -``` - -时可能出现两个窗口出售相同票号的情况,例如: - -```text -window A sold ticket 20 -window B sold ticket 20 -``` - -假设初始余票为: - -```text -20 -``` - -窗口 A 首先读取 20,然后发生线程切换。 - -窗口 B 也读取到 20。 - -因此两个线程本地保存的: - -```c -ticket_no -``` - -都等于 20。 - -随后两个线程分别执行: - -```c -remaining_tickets = 19; -``` - -这样虽然发生了两次售票,但全局余票只减少了一次。 - -![image-20260820113312260](figures/image-20260820113312260.png) - -### 原因分析 - -问题的根本原因是: - -```text -读取余票 → 修改余票 -``` - -不是一个不可分割的操作。 - -两个线程同时访问共享变量时发生了竞态条件。 - -这会导致: - -- 重复售票; -- 数据覆盖; -- 实际售票数量与余票变化不一致。 - ---- - -## 5.3 使用互斥量 - -### 实现方法 - -加入互斥量以后,窗口线程首先执行: - -```c -rt_mutex_take(ticket_mutex, RT_WAITING_FOREVER); -``` - -然后完成: - -```text -检查余票 -读取票号 -余票减 1 -打印售票结果 -``` - -最后执行: - -```c -rt_mutex_release(ticket_mutex); -``` - -整个售票过程都处于互斥量保护之下。 - -### 实验现象 - -运行: - -```text -ticket_demo -``` - -后,两个窗口仍然可以交替工作,但是不会再同时修改余票。 - -票号能够从: - -```text -20 -19 -18 -... -2 -1 -``` - -依次售出。 - -最终结果为: - -```text -all tickets sold -remaining tickets: 0 -``` - -不会出现重复票号,也不会出现负数余票。 - -![image-20260820113426632](figures/image-20260820113426632.png) - -## 5.4 两种情况对比 - -| 项目 | 无互斥量 | 使用互斥量 | -|---|---|---| -| 两个窗口并发执行 | 是 | 是 | -| 共享变量有保护 | 否 | 是 | -| 可能出现重复票号 | 是 | 否 | -| 可能发生覆盖写入 | 是 | 否 | -| 最终余票可靠 | 不一定 | 是 | -| 售票过程完整互斥 | 否 | 是 | - -## 5.5 实验结论 - -多线程程序中,即使只是简单的变量加减,也不能默认其业务过程是线程安全的。 - -在售票实验中,必须把: - -```text -检查余票 → 获取票号 → 修改余票 → 输出结果 -``` - -作为完整临界区保护。 - -互斥量可以避免两个窗口同时操作同一份票务数据。 - ---- - -# 六、练习三:系统启动条件检查 - -## 6.1 实验目的 - -模拟设备启动过程中三个独立模块的初始化: - -- 网络模块; -- 传感器模块; -- 存储模块。 - -只有三个模块全部初始化完成以后,主业务线程才能启动。 - -## 6.2 实现方法 - -程序定义三个事件位: - -```c -#define EVENT_NET_READY (1U << 0) -#define EVENT_SENSOR_READY (1U << 1) -#define EVENT_STORAGE_READY (1U << 2) -``` - -三个初始化线程分别延时: - -```text -网络模块:1 秒 -传感器模块:2 秒 -存储模块:5 秒 -``` - -初始化完成后,通过: - -```c -rt_event_send(); -``` - -发送对应的就绪事件。 - -主业务线程第一次最多等待 3 秒,并使用: - -```c -RT_EVENT_FLAG_AND -``` - -等待三个事件全部到达。 - -## 6.3 实验现象 - -由于网络模块 1 秒完成,首先输出: - -```text -network ready -``` - -传感器 2 秒完成,随后输出: - -```text -sensor ready -``` - -但是存储模块需要 5 秒。 - -业务线程第一次只等待 3 秒,因此会发生超时,并检查当前尚未完成的事件: - -```text -wait modules timeout, storage is not ready -``` - -随后业务线程继续等待。 - -5 秒后: - -```text -storage ready -``` - -三个事件全部满足,业务线程输出: - -```text -all modules are ready -business task started -``` - -![image-20260820113522585](figures/image-20260820113522585.png) - -## 6.4 结果分析 - -事件集中的三个事件位分别表示三个模块的状态。 - -使用 AND 模式,相当于要求: - -```text -网络准备完成 -AND -传感器准备完成 -AND -存储准备完成 -``` - -三个条件全部成立以后,主业务线程才能运行。 - -事件等待还可以设置超时时间,因此程序能够在某个模块初始化时间过长时发现问题,而不是无限阻塞。 - -该实验说明,事件集适合多个独立任务完成后的统一同步。 - ---- - -# 七、练习四:快递分拣中心 - -## 7.1 实验目的 - -模拟两个发件线程不断生成快递数据,一个分拣线程从消息队列中接收快递并进行分类统计。 - -主要观察: - -- 消息队列的数据传递; -- 多生产者、单消费者模式; -- 队列满时的处理; -- 消息结束标志; -- 数据统计。 - -## 7.2 消息结构 - -快递消息定义为: - -```c -struct package_msg -{ - rt_uint32_t id; - rt_uint8_t region; - rt_uint16_t weight; -}; -``` - -其中: - -```text -region = 1:华东 -region = 2:华南 -region = 3:华北 -``` - -消息队列容量设置为: - -```text -5 条消息 -``` - -## 7.3 发件线程 - -程序创建两个发件线程,每个线程产生 5 条消息。 - -发送线程 A 使用: - -```text -1001 ~ 1005 -``` - -发送线程 B 使用: - -```text -2001 ~ 2005 -``` - -因此 10 条快递的 ID 不会重复。 - -按照当前源码中的区域生成方式,最终区域数量为: - -```text -华东:3 -华南:4 -华北:3 -``` - -按照当前源码中的重量计算方式,10 条快递的总重量为: - -```text -11200 g -``` - -## 7.4 队列满处理 - -发送线程调用: - -```c -rt_mq_send(); -``` - -向消息队列放入快递信息。 - -如果队列已经达到容量上限,程序不会崩溃,也不会直接丢弃快递,而是: - -```c -send_failed++; -``` - -记录发送失败次数。 - -随后等待 50 ms 后再次发送。 - -因此一条快递即使第一次发送失败,也会一直重试到发送成功。 - -当前程序中发送线程每产生一条快递后只延时 20 ms,而分拣线程处理一条消息后延时 100 ms。 - -因此生产速度大于消费速度时,消息队列可能达到容量上限。 - -## 7.5 分拣线程 - -分拣线程通过: - -```c -rt_mq_recv(); -``` - -持续接收快递。 - -每收到一条正常快递后: - -1. 总快递数量加 1; -2. 对对应区域数量加 1; -3. 将快递重量加入总重量; -4. 打印快递 ID、区域和重量。 - -两个发件线程全部结束以后,最后一个发件线程发送特殊结束消息。 - -分拣线程收到结束消息后退出并打印统计结果。 - -## 7.6 实验现象 - -运行: - -```text -package_demo -``` - -后,可以看到类似: - -```text -package 1001 -> east, weight: 1300 g -package 2001 -> south, weight: 1100 g -... -``` - -当队列暂时满时,可能出现: - -```text -send package xxxx failed, retry later -``` - -说明生产速度超过了当前分拣速度。 - -但由于发送线程进行了重试,所以最终仍会完成全部 10 条快递的处理。 - -根据当前源码的确定性生成规则,最终统计应满足: - -```text -total packages: 10 -east: 3, south: 4, north: 3 -total weight: 11200 g -``` - -最后还会打印: - -```text -send failed: x -``` - -其中 `x` 会受到实际运行时线程调度和队列占用情况影响,因此具体数值应以实际 MSH 输出为准。 - -![image-20260820113553835](figures/image-20260820113553835.png) - -## 7.7 结果分析 - -消息队列将发送线程和分拣线程分离。 - -发送线程只负责产生数据,分拣线程只负责处理数据,双方不需要直接操作彼此线程内部的数据。 - -同时,由于消息队列容量有限,当生产速度超过消费速度时可能产生发送失败。 - -因此实际程序中必须正确处理: - -```text -队列已满 -``` - -这种情况,而不能假设消息一定能够一次发送成功。 - ---- - -# 八、实验中遇到的问题及解决方法 - -## 8.1 信号量返回值判断 - -停车场实验中,必须正确判断: - -```c -rt_sem_take() == RT_EOK -``` - -只有返回成功才表示真正获得了停车位。 - -发生超时时,车辆应该等待后重新申请。 - -## 8.2 售票竞态条件 - -不使用互斥量时,两个线程可以在相同时间读取同一个余票值。 - -通过在读取和修改之间加入短暂延时,可以更容易观察到重复售票现象。 - -加入互斥量以后,将整个售票流程作为临界区保护,问题得到解决。 - -## 8.3 多模块完成条件判断 - -系统启动实验中,各模块完成时间不同。 - -如果只使用固定延时启动主业务,无法准确判断模块是否真正准备完成。 - -使用事件集后,每个模块主动发送完成状态,主业务线程可以准确等待所有模块就绪。 - -## 8.4 消息队列满 - -快递实验中,生产速度高于消费速度时,消息队列会暂时达到最大容量。 - -程序通过记录发送失败次数并重试来避免消息永久丢失。 - ---- - -# 九、实验总结 - -通过本次实验,对 RT-Thread 中多线程同步和 IPC 机制有了更加直观的认识。 - -停车场实验表明,**计数信号量适合管理数量有限的共享资源**,能够从机制上限制同时获得资源的线程数量。 - -售票实验通过无互斥量和有互斥量两种程序的对比,说明了多线程同时修改共享变量时可能产生竞态条件。**互斥量可以保护完整临界区,保证共享数据的一致性。** - -系统启动实验说明,**事件集适合表示多个独立条件的完成状态**。使用 AND 模式可以保证所有条件全部满足后再执行主业务,同时结合超时等待还能检查未完成模块。 - -快递分拣实验说明,**消息队列适合在线程之间传递结构化数据**,能够实现生产者和消费者之间的解耦,同时也需要考虑队列容量有限以及发送失败等情况。 - -四种 IPC 机制的典型应用可以总结如下: - -| IPC 机制 | 主要作用 | 本实验中的应用 | -|---|---|---| -| 计数信号量 | 管理有限数量资源 | 停车场车位 | -| 互斥量 | 保护共享数据 | 多窗口售票 | -| 事件集 | 等待多个状态条件 | 系统启动检查 | -| 消息队列 | 在线程间传递数据 | 快递分拣 | - -通过这些实验可以认识到,多线程程序不仅需要实现功能,还必须考虑线程之间的执行顺序、共享资源竞争、同步条件以及线程间的数据传递。选择合适的 IPC 机制,是保证实时操作系统程序正确性和可靠性的关键。 -::: ​​ \ No newline at end of file diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/\347\220\206\350\256\272\346\200\273\347\273\223.md" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/\347\220\206\350\256\272\346\200\273\347\273\223.md" deleted file mode 100644 index ea712345dc30d6b8dc203f125a9c894338675ac8..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/\347\220\206\350\256\272\346\200\273\347\273\223.md" +++ /dev/null @@ -1,378 +0,0 @@ -# RT-Thread线程间同步与通信学习随笔 - -今天继续往RT-Thread的内核机制里走。 - -前面学习线程时,我更多关注的是每个线程自身的状态、优先级、时间片以及挂起和恢复。但真正写一个稍微复杂一点的嵌入式程序后,很快就会遇到一个问题:线程不是各跑各的,它们一定会共享硬件、共享数据,也需要互相通知。 - -比如一个线程采集传感器,一个线程负责显示,一个线程负责通信。如果没有同步机制,几个线程可能同时修改同一份数据;如果没有通信机制,采集线程得到了数据以后,显示线程甚至不知道新数据已经来了。 - -所以今天学完以后,我对RTOS里线程间同步和线程间通信的理解开始清晰了一些: - -> **同步解决的是什么时候可以做、谁先做、谁能访问资源的问题;通信解决的是线程之间要传什么信息的问题。** - -虽然两者经常混在一起使用,但它们关注的重点其实不同。 - ---- - -# 一、线程间同步:重点不是一起运行,而是有秩序地运行 - -## 1.信号量:最容易理解的资源计数器和通知器 - -RT-Thread里的信号量可以看成一个带计数值的同步对象。 - -一个线程通过`rt_sem_take()`获取信号量,通过`rt_sem_release()`释放信号量。如果信号量当前还有资源,线程就可以继续运行;如果信号量已经为0,那么等待线程可以进入挂起状态,直到其他线程或者中断释放信号量。 - -我觉得用停车场来理解信号量很直观。假设停车场有3个位置,那么信号量初始值就是3: - -```text -初始值 = 3 - -线程A获取 → 2 -线程B获取 → 1 -线程C获取 → 0 -线程D再获取 → 没有资源,等待 - -线程A释放 → 线程D获得机会继续运行 -``` - -所以信号量其实可以做两件事。一种是**资源计数**,比如系统只有3个可用缓冲区,就把信号量初始值设为3。另一种是**线程同步**,例如采集线程完成ADC采样以后释放一个信号量,处理线程一直等待这个信号量。 - -```text -采集线程 - ↓ -ADC完成 - ↓ -rt_sem_release() - ↓ -处理线程被唤醒 - ↓ -处理采样数据 -``` - -这一点和我以前使用FreeRTOS时的Binary Semaphore、Counting Semaphore非常接近。FreeRTOS也把二值信号量用于任务之间或者ISR到任务的同步,把计数信号量用于资源计数。 - -ChibiOS的Semaphore思路也类似,同样属于计数型同步原语,而且还提供超时等待、Reset等操作。 - -Linux里也有POSIX Semaphore: - -```c -sem_wait(); -sem_post(); -``` - -从概念上看,四个系统其实非常接近。真正让我感觉不同的地方是,RT-Thread把线程、信号量、互斥量、事件、邮箱、消息队列统一放在“内核对象”这一套体系里,所以学习的时候会比较容易从整体上理解这些IPC对象之间的关系。 - -## 2.互斥量:和信号量很像,但真正重点是“所有权” - -刚开始看互斥量时,很容易产生一个疑问:信号量初始值设成1,不也能保证同一时间只有一个线程进入临界区吗?为什么还需要Mutex? - -今天重新理解以后,我觉得最重要的区别不是“值是不是1”,而是**互斥量有所有者,而信号量没有真正意义上的所有者**。 - -例如两个线程都会访问同一个I2C总线: - -```text -thread_sensor - \ - → I2C总线 - / -thread_display -``` - -如果两个线程同时开始操作I2C,就可能把一次完整传输打断。因此需要先获得互斥量,完成访问以后再释放。 - -```c -rt_mutex_take(&i2c_lock, RT_WAITING_FOREVER); - -/* 临界区 */ -read_sensor(); - -rt_mutex_release(&i2c_lock); -``` - -这里还有一个非常重要的问题:**优先级反转**。 - -假设: - -```text -高优先级线程H -中优先级线程M -低优先级线程L -``` - -L先拿到了Mutex,然后H需要这个Mutex,于是H只能等待。如果这时M不断抢占L,那么真正高优先级的H反而会因为L迟迟得不到CPU而一直被卡住。 - -RT-Thread的互斥量使用优先级继承机制来减轻这种情况:低优先级线程持有Mutex并阻塞了高优先级线程时,可以临时提升持有者的优先级,让它尽快完成临界区并释放Mutex。 - -这一点和FreeRTOS很像。FreeRTOS的Mutex也带优先级继承机制,而Binary Semaphore不带,因此更适合把Semaphore用于同步,把Mutex用于保护共享资源。 - -ChibiOS在这一点上也很明确:Mutex具有Owner属性,实现Priority Inheritance,而且还能配置Recursive Mutex。 - -Linux的`pthread_mutex_t`则更加通用。普通Mutex默认并不一定采用优先级继承,但POSIX提供`PTHREAD_PRIO_INHERIT`、`PTHREAD_PRIO_PROTECT`等协议,可以针对实时线程配置。 - -所以我现在会这样区分: - -```text -Semaphore: -更像“有几个资源”或者“某件事发生了” - -Mutex: -更像“这个资源现在属于谁” -``` - -这个区别比单纯记API更重要。 - -## 3.事件集:当一个线程需要同时关心很多条件时特别好用 - -事件集是今天我觉得很有意思的一部分。 - -RT-Thread用一个32位无符号整数表示事件集合,每一位代表一个事件: - -```text -bit0 → ADC完成 -bit1 → UART收到数据 -bit2 → 网络连接成功 -bit3 → 按键触发 -... -``` - -例如: - -```c -#define EVENT_ADC (1 << 0) -#define EVENT_UART (1 << 1) -``` - -等待线程可以选择OR模式或者AND模式。OR模式表示任意一个感兴趣的事件发生都可以唤醒线程;AND模式表示多个条件全部满足以后才唤醒。RT-Thread还可以配合`RT_EVENT_FLAG_CLEAR`,在线程成功接收到事件以后自动清除对应标志。 - -我现在觉得信号量比较像“有人敲门了”,而事件集更像“ADC完成了、UART有数据了、网络连接了、按键按下了”,而且可以决定满足其中一个就处理,还是所有条件满足以后再处理。 - -这里还有一个很关键的特性:**事件本身不负责传输数据,而且同一个事件重复发送不会像计数信号量一样不断累积。** - -FreeRTOS里对应的是Event Group,也可以通过bit等待多个条件,并选择等待任意bit还是全部bit。因此从使用体验上,RT-Thread Event和FreeRTOS Event Group很接近。 - -ChibiOS也有完整的Events机制,而且支持Event Source、Listener、Event Flags以及waitOne、waitAny、waitAll。在我看来,ChibiOS的事件系统更偏“事件发布/订阅”的感觉,而RT-Thread的Event Set结构更直接。 - -Linux线程里没有一个完全等价于RTOS Event Group的单一POSIX线程API。实际开发中可能使用Condition Variable、eventfd、信号或者epoll等组合来实现类似需求。Linux的能力明显更强,但同时抽象层次也更复杂。 - ---- - -# 二、线程间通信:线程不只是要互相等,还要真正交换数据 - -## 1.邮箱:轻量级的消息传递方式 - -如果说同步主要解决数据准备好了没有,那么通信解决的就是准备好的数据到底是什么? - -RT-Thread的Mailbox就是一个比较轻量的通信机制。按照RT-Thread文档里的设计,邮箱一次传递一个4字节大小的消息,因此可以直接发送整数、状态值、地址或者指针。 - -例如: - -```text -按键线程 - ↓ -检测KEY1按下 - ↓ -发送消息1 - ↓ -LED线程收到1 - ↓ -LED翻转 -``` - -如果需要传递一个较大的结构体,也可以把结构体放在缓冲区里,然后通过Mailbox传递指针。 - -这让我想到FreeRTOS的Task Notification。Task Notification可以把任务内部的通知值当作轻量级Semaphore、Event甚至Mailbox来使用,而且不需要单独创建Queue对象,在一对一通知场景下开销很小。 - -ChibiOS也有Mailbox,底层是一个消息的Circular Queue,可以在线程和ISR上下文中Post和Fetch。从这个角度看,ChibiOS Mailbox和RT-Thread Mailbox的思想非常接近。 - -但Linux里我反而很少会把“Mailbox”作为线程通信的第一选择。Linux同一个进程中的线程本身共享地址空间,所以很多情况下直接使用“共享数据+Mutex+Condition Variable”就能完成线程通信。 - -这体现出了MCU RTOS和Linux思维上的一个区别: - -> **RTOS更倾向于使用显式的内核通信对象组织线程关系,而Linux线程天然共享进程内存,通信和同步往往是组合起来设计的。** - -## 2.消息队列:比邮箱更适合真正的数据传递 - -如果需要传递的不只是一个状态或者一个指针,Message Queue就更合适。 - -RT-Thread的消息队列允许消息在设定的最大消息长度内携带不同长度的数据,而且消息会被缓存起来。 - -```text -Producer - ↓ -Message1 -Message2 -Message3 - ↓ -Message Queue - ↓ -Consumer -``` - -如果队列为空,接收线程可以等待;新消息到达后再被唤醒。这是一种典型的异步通信方式,也就是说发送方不需要等接收方马上处理。 - -例如串口接收: - -```text -UART中断 - ↓ -得到一段数据 - ↓ -rt_mq_send() - ↓ -消息进入队列 - ↓ -协议处理线程被唤醒 - ↓ -解析数据 -``` - -这样中断只负责快速搬运数据,而耗时的协议解析放在线程里完成,非常符合RTOS里“中断尽量短,把工作推迟到线程”的思路。 - -RT-Thread还有`rt_mq_urgent()`,普通消息放到队尾,而Urgent Message放到队头,可以优先处理。 - -FreeRTOS里Queue是非常核心的通信机制,消息默认按值复制,也可以传递指针。 - -ChibiOS除了Mailbox以外,还有很有特色的Synchronous Messages,更像一个轻量级Client/Server调用模型。这个设计和RT-Thread的异步Mailbox/Message Queue思路明显不同。 - -Linux里则有POSIX Message Queue: - -```c -mq_send(); -mq_receive(); -``` - -它允许消息带Priority,而且不仅能在线程之间使用,也可以用于进程间通信。因此Linux的消息队列进入了更完整的系统资源管理体系。 - -## 3.Signal:让我第一次明显感觉到RT-Thread和Linux之间的联系 - -RT-Thread的Signal让我感觉很像Linux。 - -RT-Thread把Signal描述为一种“软中断”,用于通知线程发生了异步事件。线程可以安装Signal Handler,可以屏蔽或者解除屏蔽,其他线程则可以通过`rt_thread_kill()`向目标线程发送Signal。 - -```c -rt_signal_install(); -rt_signal_mask(); -rt_signal_unmask(); -rt_thread_kill(); -``` - -这种设计和Linux里的: - -```c -pthread_kill(); -sigaction(); -pthread_sigmask(); -``` - -在概念上非常接近。 - -Linux中`pthread_kill()`可以把一个Signal定向发送给同一进程中的指定线程;RT-Thread则把这种思想放到了嵌入式线程环境中。 - -FreeRTOS内核本身没有POSIX Signal这种完全对应的核心机制。类似“通知某个任务发生了某个异步事件”的需求,通常会用Task Notification、Event Group、Queue或者Semaphore。 - -ChibiOS也更倾向于使用Event、Synchronous Message等机制完成异步通知,没有必要完全照搬POSIX Signal模型。 - -所以如果之前接触过Linux,我觉得RT-Thread的Signal会显得比较亲切。 - ---- - -# 三、把RT-Thread、FreeRTOS、ChibiOS和Linux放在一起以后,我的一些发现 - -## 1.四个系统解决的问题很像,但它们的“性格”不一样 - -|需求|RT-Thread|FreeRTOS|ChibiOS|Linux/POSIX| -|---|---|---|---|---| -|资源计数/同步|Semaphore|Binary/Counting Semaphore|Semaphore|POSIX Semaphore| -|临界区互斥|Mutex|Mutex|Mutex|pthread_mutex| -|等待多个事件|Event Set|Event Group|Events|Condition/Eventfd等组合| -|轻量通知|Mailbox/Signal|Task Notification|Events/Synchronous Message|Signal/Eventfd| -|消息传递|Mailbox/Message Queue|Queue/Message Buffer|Mailbox/Synchronous Message|POSIX Message Queue| -|优先级反转处理|Mutex优先级继承|Mutex优先级继承|Mutex优先级继承|可配置PRIO_INHERIT等协议| - -FreeRTOS给我的感觉一直是**Kernel味最浓**。Task、Queue、Semaphore、Mutex、Event Group这些东西很直接,尤其Task Notification很有自己的特色,很多简单IPC可以不创建额外内核对象。 - -ChibiOS给我的感觉是**非常强调结构和效率**。Mutex、Condition Variable、Event Source、Listener、Synchronous Message这些设计都有比较清晰的体系,而且能看出它很重视小开销和确定性。 - -Linux则是**通用操作系统思维**。同步和通信不只服务于几个MCU线程,还要面对多进程、权限、文件描述符、命名空间等问题,所以工具非常多,但系统复杂度也高得多。 - -RT-Thread在这几个里面给我的感觉比较居中。它既有Semaphore、Mutex、Event、Mailbox、Message Queue这些典型RTOS机制,又加入了Signal、POSIX接口、FinSH、设备框架、文件系统、网络组件和软件包等更接近完整操作系统的能力。 - -所以RT-Thread不像FreeRTOS那样只给我一种“实时内核”的感觉,而更像是: - -> **从MCU RTOS向完整嵌入式操作系统延伸的一套系统。** - -## 2.我开始发现:IPC选择其实是在选择“线程之间的关系” - -以前刚学RTOS的时候,我比较容易把这些东西当成API表:Semaphore怎么创建、Mutex怎么Take、Queue怎么Send。 - -但今天学完以后,我觉得真正重要的是先问: - -> 这两个线程到底是什么关系? - -如果只是线程A告诉线程B“事情做完了”,Semaphore或者Notification就够了。 - -如果线程A和线程B都要访问I2C,应该优先想到Mutex。 - -如果线程需要同时等待网络连接完成、时间同步完成、传感器初始化完成,Event Set会更自然。 - -如果UART线程要把一帧数据交给协议线程,Message Queue明显更适合。 - -如果只是通知一个异步异常事件,Signal或者轻量通知机制可能更合适。 - -所以我觉得IPC不是“哪个API更高级”,而是一个选择过程: - -```text -线程关系 - ↓ -数据量 - ↓ -是否需要缓存 - ↓ -是否允许阻塞 - ↓ -是否来自ISR - ↓ -是否涉及共享资源 - ↓ -选择合适的IPC -``` - -这个思维方式比直接背API更接近真正做系统设计。 - -## 3.今天最大的感受:RTOS的核心不是多线程,而是可控的并发 - -刚开始接触RTOS时,我会把它理解成: - -```text -裸机只能while(1) -RTOS可以创建很多线程 -``` - -但现在慢慢感觉这个理解太浅了。 - -真正复杂的地方不是“创建很多线程”,而是多个线程什么时候运行、谁先运行、共享资源怎么保护、一个线程怎么通知另一个线程、数据应该放在哪里、线程等待时能不能让出CPU、中断和线程之间怎么配合、优先级反转怎么办。 - -也就是说: - -> **多线程本身并不是目标,把并发行为控制在一个可以预测、可以维护的范围内才是RTOS真正的价值。** - -之前使用FreeRTOS时,我更多是在用API完成任务;使用ChibiOS时,我会明显感觉到它的内核设计很紧凑;接触Linux线程以后,又会看到一个成熟通用操作系统里同步、通信和进程模型是怎么结合的。 - -现在再回来看RT-Thread,我会发现它刚好处在一个很有意思的位置: - -```text -比FreeRTOS更像一个完整OS -比Linux更轻量、更贴近MCU -API和概念又保留了很多传统RTOS的直观性 -``` - -对我来说,这次学习最大的收获已经不只是知道`rt_sem_take()`、`rt_mutex_take()`、`rt_event_recv()`、`rt_mb_send()`、`rt_mq_send()`这些函数怎么调用,而是开始能够从系统角度理解: - -```text -线程为什么需要同步 -线程为什么需要通信 -不同IPC为什么会同时存在 -以及什么时候应该选哪一个 -``` - -我觉得这才是今天这部分内容真正值得记下来的地方。 diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index ea712345dc30d6b8dc203f125a9c894338675ac8..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,378 +0,0 @@ -# RT-Thread线程间同步与通信学习随笔 - -今天继续往RT-Thread的内核机制里走。 - -前面学习线程时,我更多关注的是每个线程自身的状态、优先级、时间片以及挂起和恢复。但真正写一个稍微复杂一点的嵌入式程序后,很快就会遇到一个问题:线程不是各跑各的,它们一定会共享硬件、共享数据,也需要互相通知。 - -比如一个线程采集传感器,一个线程负责显示,一个线程负责通信。如果没有同步机制,几个线程可能同时修改同一份数据;如果没有通信机制,采集线程得到了数据以后,显示线程甚至不知道新数据已经来了。 - -所以今天学完以后,我对RTOS里线程间同步和线程间通信的理解开始清晰了一些: - -> **同步解决的是什么时候可以做、谁先做、谁能访问资源的问题;通信解决的是线程之间要传什么信息的问题。** - -虽然两者经常混在一起使用,但它们关注的重点其实不同。 - ---- - -# 一、线程间同步:重点不是一起运行,而是有秩序地运行 - -## 1.信号量:最容易理解的资源计数器和通知器 - -RT-Thread里的信号量可以看成一个带计数值的同步对象。 - -一个线程通过`rt_sem_take()`获取信号量,通过`rt_sem_release()`释放信号量。如果信号量当前还有资源,线程就可以继续运行;如果信号量已经为0,那么等待线程可以进入挂起状态,直到其他线程或者中断释放信号量。 - -我觉得用停车场来理解信号量很直观。假设停车场有3个位置,那么信号量初始值就是3: - -```text -初始值 = 3 - -线程A获取 → 2 -线程B获取 → 1 -线程C获取 → 0 -线程D再获取 → 没有资源,等待 - -线程A释放 → 线程D获得机会继续运行 -``` - -所以信号量其实可以做两件事。一种是**资源计数**,比如系统只有3个可用缓冲区,就把信号量初始值设为3。另一种是**线程同步**,例如采集线程完成ADC采样以后释放一个信号量,处理线程一直等待这个信号量。 - -```text -采集线程 - ↓ -ADC完成 - ↓ -rt_sem_release() - ↓ -处理线程被唤醒 - ↓ -处理采样数据 -``` - -这一点和我以前使用FreeRTOS时的Binary Semaphore、Counting Semaphore非常接近。FreeRTOS也把二值信号量用于任务之间或者ISR到任务的同步,把计数信号量用于资源计数。 - -ChibiOS的Semaphore思路也类似,同样属于计数型同步原语,而且还提供超时等待、Reset等操作。 - -Linux里也有POSIX Semaphore: - -```c -sem_wait(); -sem_post(); -``` - -从概念上看,四个系统其实非常接近。真正让我感觉不同的地方是,RT-Thread把线程、信号量、互斥量、事件、邮箱、消息队列统一放在“内核对象”这一套体系里,所以学习的时候会比较容易从整体上理解这些IPC对象之间的关系。 - -## 2.互斥量:和信号量很像,但真正重点是“所有权” - -刚开始看互斥量时,很容易产生一个疑问:信号量初始值设成1,不也能保证同一时间只有一个线程进入临界区吗?为什么还需要Mutex? - -今天重新理解以后,我觉得最重要的区别不是“值是不是1”,而是**互斥量有所有者,而信号量没有真正意义上的所有者**。 - -例如两个线程都会访问同一个I2C总线: - -```text -thread_sensor - \ - → I2C总线 - / -thread_display -``` - -如果两个线程同时开始操作I2C,就可能把一次完整传输打断。因此需要先获得互斥量,完成访问以后再释放。 - -```c -rt_mutex_take(&i2c_lock, RT_WAITING_FOREVER); - -/* 临界区 */ -read_sensor(); - -rt_mutex_release(&i2c_lock); -``` - -这里还有一个非常重要的问题:**优先级反转**。 - -假设: - -```text -高优先级线程H -中优先级线程M -低优先级线程L -``` - -L先拿到了Mutex,然后H需要这个Mutex,于是H只能等待。如果这时M不断抢占L,那么真正高优先级的H反而会因为L迟迟得不到CPU而一直被卡住。 - -RT-Thread的互斥量使用优先级继承机制来减轻这种情况:低优先级线程持有Mutex并阻塞了高优先级线程时,可以临时提升持有者的优先级,让它尽快完成临界区并释放Mutex。 - -这一点和FreeRTOS很像。FreeRTOS的Mutex也带优先级继承机制,而Binary Semaphore不带,因此更适合把Semaphore用于同步,把Mutex用于保护共享资源。 - -ChibiOS在这一点上也很明确:Mutex具有Owner属性,实现Priority Inheritance,而且还能配置Recursive Mutex。 - -Linux的`pthread_mutex_t`则更加通用。普通Mutex默认并不一定采用优先级继承,但POSIX提供`PTHREAD_PRIO_INHERIT`、`PTHREAD_PRIO_PROTECT`等协议,可以针对实时线程配置。 - -所以我现在会这样区分: - -```text -Semaphore: -更像“有几个资源”或者“某件事发生了” - -Mutex: -更像“这个资源现在属于谁” -``` - -这个区别比单纯记API更重要。 - -## 3.事件集:当一个线程需要同时关心很多条件时特别好用 - -事件集是今天我觉得很有意思的一部分。 - -RT-Thread用一个32位无符号整数表示事件集合,每一位代表一个事件: - -```text -bit0 → ADC完成 -bit1 → UART收到数据 -bit2 → 网络连接成功 -bit3 → 按键触发 -... -``` - -例如: - -```c -#define EVENT_ADC (1 << 0) -#define EVENT_UART (1 << 1) -``` - -等待线程可以选择OR模式或者AND模式。OR模式表示任意一个感兴趣的事件发生都可以唤醒线程;AND模式表示多个条件全部满足以后才唤醒。RT-Thread还可以配合`RT_EVENT_FLAG_CLEAR`,在线程成功接收到事件以后自动清除对应标志。 - -我现在觉得信号量比较像“有人敲门了”,而事件集更像“ADC完成了、UART有数据了、网络连接了、按键按下了”,而且可以决定满足其中一个就处理,还是所有条件满足以后再处理。 - -这里还有一个很关键的特性:**事件本身不负责传输数据,而且同一个事件重复发送不会像计数信号量一样不断累积。** - -FreeRTOS里对应的是Event Group,也可以通过bit等待多个条件,并选择等待任意bit还是全部bit。因此从使用体验上,RT-Thread Event和FreeRTOS Event Group很接近。 - -ChibiOS也有完整的Events机制,而且支持Event Source、Listener、Event Flags以及waitOne、waitAny、waitAll。在我看来,ChibiOS的事件系统更偏“事件发布/订阅”的感觉,而RT-Thread的Event Set结构更直接。 - -Linux线程里没有一个完全等价于RTOS Event Group的单一POSIX线程API。实际开发中可能使用Condition Variable、eventfd、信号或者epoll等组合来实现类似需求。Linux的能力明显更强,但同时抽象层次也更复杂。 - ---- - -# 二、线程间通信:线程不只是要互相等,还要真正交换数据 - -## 1.邮箱:轻量级的消息传递方式 - -如果说同步主要解决数据准备好了没有,那么通信解决的就是准备好的数据到底是什么? - -RT-Thread的Mailbox就是一个比较轻量的通信机制。按照RT-Thread文档里的设计,邮箱一次传递一个4字节大小的消息,因此可以直接发送整数、状态值、地址或者指针。 - -例如: - -```text -按键线程 - ↓ -检测KEY1按下 - ↓ -发送消息1 - ↓ -LED线程收到1 - ↓ -LED翻转 -``` - -如果需要传递一个较大的结构体,也可以把结构体放在缓冲区里,然后通过Mailbox传递指针。 - -这让我想到FreeRTOS的Task Notification。Task Notification可以把任务内部的通知值当作轻量级Semaphore、Event甚至Mailbox来使用,而且不需要单独创建Queue对象,在一对一通知场景下开销很小。 - -ChibiOS也有Mailbox,底层是一个消息的Circular Queue,可以在线程和ISR上下文中Post和Fetch。从这个角度看,ChibiOS Mailbox和RT-Thread Mailbox的思想非常接近。 - -但Linux里我反而很少会把“Mailbox”作为线程通信的第一选择。Linux同一个进程中的线程本身共享地址空间,所以很多情况下直接使用“共享数据+Mutex+Condition Variable”就能完成线程通信。 - -这体现出了MCU RTOS和Linux思维上的一个区别: - -> **RTOS更倾向于使用显式的内核通信对象组织线程关系,而Linux线程天然共享进程内存,通信和同步往往是组合起来设计的。** - -## 2.消息队列:比邮箱更适合真正的数据传递 - -如果需要传递的不只是一个状态或者一个指针,Message Queue就更合适。 - -RT-Thread的消息队列允许消息在设定的最大消息长度内携带不同长度的数据,而且消息会被缓存起来。 - -```text -Producer - ↓ -Message1 -Message2 -Message3 - ↓ -Message Queue - ↓ -Consumer -``` - -如果队列为空,接收线程可以等待;新消息到达后再被唤醒。这是一种典型的异步通信方式,也就是说发送方不需要等接收方马上处理。 - -例如串口接收: - -```text -UART中断 - ↓ -得到一段数据 - ↓ -rt_mq_send() - ↓ -消息进入队列 - ↓ -协议处理线程被唤醒 - ↓ -解析数据 -``` - -这样中断只负责快速搬运数据,而耗时的协议解析放在线程里完成,非常符合RTOS里“中断尽量短,把工作推迟到线程”的思路。 - -RT-Thread还有`rt_mq_urgent()`,普通消息放到队尾,而Urgent Message放到队头,可以优先处理。 - -FreeRTOS里Queue是非常核心的通信机制,消息默认按值复制,也可以传递指针。 - -ChibiOS除了Mailbox以外,还有很有特色的Synchronous Messages,更像一个轻量级Client/Server调用模型。这个设计和RT-Thread的异步Mailbox/Message Queue思路明显不同。 - -Linux里则有POSIX Message Queue: - -```c -mq_send(); -mq_receive(); -``` - -它允许消息带Priority,而且不仅能在线程之间使用,也可以用于进程间通信。因此Linux的消息队列进入了更完整的系统资源管理体系。 - -## 3.Signal:让我第一次明显感觉到RT-Thread和Linux之间的联系 - -RT-Thread的Signal让我感觉很像Linux。 - -RT-Thread把Signal描述为一种“软中断”,用于通知线程发生了异步事件。线程可以安装Signal Handler,可以屏蔽或者解除屏蔽,其他线程则可以通过`rt_thread_kill()`向目标线程发送Signal。 - -```c -rt_signal_install(); -rt_signal_mask(); -rt_signal_unmask(); -rt_thread_kill(); -``` - -这种设计和Linux里的: - -```c -pthread_kill(); -sigaction(); -pthread_sigmask(); -``` - -在概念上非常接近。 - -Linux中`pthread_kill()`可以把一个Signal定向发送给同一进程中的指定线程;RT-Thread则把这种思想放到了嵌入式线程环境中。 - -FreeRTOS内核本身没有POSIX Signal这种完全对应的核心机制。类似“通知某个任务发生了某个异步事件”的需求,通常会用Task Notification、Event Group、Queue或者Semaphore。 - -ChibiOS也更倾向于使用Event、Synchronous Message等机制完成异步通知,没有必要完全照搬POSIX Signal模型。 - -所以如果之前接触过Linux,我觉得RT-Thread的Signal会显得比较亲切。 - ---- - -# 三、把RT-Thread、FreeRTOS、ChibiOS和Linux放在一起以后,我的一些发现 - -## 1.四个系统解决的问题很像,但它们的“性格”不一样 - -|需求|RT-Thread|FreeRTOS|ChibiOS|Linux/POSIX| -|---|---|---|---|---| -|资源计数/同步|Semaphore|Binary/Counting Semaphore|Semaphore|POSIX Semaphore| -|临界区互斥|Mutex|Mutex|Mutex|pthread_mutex| -|等待多个事件|Event Set|Event Group|Events|Condition/Eventfd等组合| -|轻量通知|Mailbox/Signal|Task Notification|Events/Synchronous Message|Signal/Eventfd| -|消息传递|Mailbox/Message Queue|Queue/Message Buffer|Mailbox/Synchronous Message|POSIX Message Queue| -|优先级反转处理|Mutex优先级继承|Mutex优先级继承|Mutex优先级继承|可配置PRIO_INHERIT等协议| - -FreeRTOS给我的感觉一直是**Kernel味最浓**。Task、Queue、Semaphore、Mutex、Event Group这些东西很直接,尤其Task Notification很有自己的特色,很多简单IPC可以不创建额外内核对象。 - -ChibiOS给我的感觉是**非常强调结构和效率**。Mutex、Condition Variable、Event Source、Listener、Synchronous Message这些设计都有比较清晰的体系,而且能看出它很重视小开销和确定性。 - -Linux则是**通用操作系统思维**。同步和通信不只服务于几个MCU线程,还要面对多进程、权限、文件描述符、命名空间等问题,所以工具非常多,但系统复杂度也高得多。 - -RT-Thread在这几个里面给我的感觉比较居中。它既有Semaphore、Mutex、Event、Mailbox、Message Queue这些典型RTOS机制,又加入了Signal、POSIX接口、FinSH、设备框架、文件系统、网络组件和软件包等更接近完整操作系统的能力。 - -所以RT-Thread不像FreeRTOS那样只给我一种“实时内核”的感觉,而更像是: - -> **从MCU RTOS向完整嵌入式操作系统延伸的一套系统。** - -## 2.我开始发现:IPC选择其实是在选择“线程之间的关系” - -以前刚学RTOS的时候,我比较容易把这些东西当成API表:Semaphore怎么创建、Mutex怎么Take、Queue怎么Send。 - -但今天学完以后,我觉得真正重要的是先问: - -> 这两个线程到底是什么关系? - -如果只是线程A告诉线程B“事情做完了”,Semaphore或者Notification就够了。 - -如果线程A和线程B都要访问I2C,应该优先想到Mutex。 - -如果线程需要同时等待网络连接完成、时间同步完成、传感器初始化完成,Event Set会更自然。 - -如果UART线程要把一帧数据交给协议线程,Message Queue明显更适合。 - -如果只是通知一个异步异常事件,Signal或者轻量通知机制可能更合适。 - -所以我觉得IPC不是“哪个API更高级”,而是一个选择过程: - -```text -线程关系 - ↓ -数据量 - ↓ -是否需要缓存 - ↓ -是否允许阻塞 - ↓ -是否来自ISR - ↓ -是否涉及共享资源 - ↓ -选择合适的IPC -``` - -这个思维方式比直接背API更接近真正做系统设计。 - -## 3.今天最大的感受:RTOS的核心不是多线程,而是可控的并发 - -刚开始接触RTOS时,我会把它理解成: - -```text -裸机只能while(1) -RTOS可以创建很多线程 -``` - -但现在慢慢感觉这个理解太浅了。 - -真正复杂的地方不是“创建很多线程”,而是多个线程什么时候运行、谁先运行、共享资源怎么保护、一个线程怎么通知另一个线程、数据应该放在哪里、线程等待时能不能让出CPU、中断和线程之间怎么配合、优先级反转怎么办。 - -也就是说: - -> **多线程本身并不是目标,把并发行为控制在一个可以预测、可以维护的范围内才是RTOS真正的价值。** - -之前使用FreeRTOS时,我更多是在用API完成任务;使用ChibiOS时,我会明显感觉到它的内核设计很紧凑;接触Linux线程以后,又会看到一个成熟通用操作系统里同步、通信和进程模型是怎么结合的。 - -现在再回来看RT-Thread,我会发现它刚好处在一个很有意思的位置: - -```text -比FreeRTOS更像一个完整OS -比Linux更轻量、更贴近MCU -API和概念又保留了很多传统RTOS的直观性 -``` - -对我来说,这次学习最大的收获已经不只是知道`rt_sem_take()`、`rt_mutex_take()`、`rt_event_recv()`、`rt_mb_send()`、`rt_mq_send()`这些函数怎么调用,而是开始能够从系统角度理解: - -```text -线程为什么需要同步 -线程为什么需要通信 -不同IPC为什么会同时存在 -以及什么时候应该选哪一个 -``` - -我觉得这才是今天这部分内容真正值得记下来的地方。 diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/led1_resume.png" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/led1_resume.png" deleted file mode 100644 index 17501500b177b69e9366e607b5c72700f3529fff..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/led1_resume.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/led1_suspend.png" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/led1_suspend.png" deleted file mode 100644 index 600e6dad134b18a09981027f3bb622fb2da39368..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/led1_suspend.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/msh_export.png" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/msh_export.png" deleted file mode 100644 index 3451eed5ee3931c4c99561d674edf26facea24ca..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/msh_export.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/thread_study_run.png" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/thread_study_run.png" deleted file mode 100644 index f3a3212d1dfcf1b33f3bb11f5fffc761a02a3e59..0000000000000000000000000000000000000000 Binary files "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/figures/thread_study_run.png" and /dev/null differ diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/thread_study.c" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/thread_study.c" deleted file mode 100644 index e772d05a96ae3b02c35c91a4681b61ab37beff4d..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/thread_study.c" +++ /dev/null @@ -1,602 +0,0 @@ -/* - * Copyright (c) 2006-2026, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2026-08-18 RT-Thread first version - */ - -#include -#include - -#define DBG_TAG "thread_study" -#define DBG_LVL DBG_LOG -#include - -/* pin definition */ -#define GPIO_LED_B GET_PIN(F, 11) -#define GPIO_LED_R GET_PIN(F, 12) -#define GPIO_BTN_UP GET_PIN(C, 5) - -/* led thread configuration */ -#define LED_THREAD_STACK_SIZE 1024 -#define LED_THREAD_PRIORITY 16 -#define LED_THREAD_TIMESLICE 5 - -/* monitor thread configuration */ -#define MONITOR_THREAD_STACK_SIZE 1024 -#define MONITOR_THREAD_PRIORITY 10 -#define MONITOR_THREAD_TIMESLICE 5 - -/* application timing configuration */ -#define LED_PERIOD_MS 1000 -#define MONITOR_PERIOD_MS 2000 -#define KEY_DEBOUNCE_MS 50 - -/* led2 static thread object */ -static struct rt_thread led2_thread; -static rt_uint8_t led2_stack[LED_THREAD_STACK_SIZE]; - -/* dynamic thread handle */ -static rt_thread_t led1_tid = RT_NULL; -static rt_thread_t monitor_tid = RT_NULL; - -/* key semaphore */ -static struct rt_semaphore key_sem; - -/* led1 suspend control */ -static volatile rt_bool_t led1_suspend_request = RT_FALSE; -static volatile rt_bool_t led1_suspended = RT_FALSE; - -/* study start flag */ -static rt_bool_t study_started = RT_FALSE; - -static void th_led1(void *parameter); -static void th_led2(void *parameter); -static void th_monitor(void *parameter); - -static void board_io_init(void); -static rt_err_t app_thread_init(void); -static rt_err_t thread_study_start(void); -static int thread_study(int argc, char **argv); - -static void led_toggle(rt_base_t pin); - -static const char *get_thread_state(rt_thread_t thread); -static rt_size_t get_stack_free(rt_thread_t thread); -static void monitor_print_thread_info(void); - -static void key_irq_callback(void *args); -static rt_err_t key_init(rt_base_t key_pin); -static void key_event_process(void); - -/** - * @brief 启动线程学习示例。 - * - * 初始化板载 LED、按键中断以及示例线程。 - * - * @return RT_EOK 启动成功。 - * @return 其他错误码 启动失败。 - */ -static rt_err_t thread_study_start(void) -{ - rt_err_t result; - - if (study_started == RT_TRUE) - { - LOG_W("thread study already started"); - return RT_EOK; - } - - /* board io init */ - board_io_init(); - - /* key irq init */ - result = key_init(GPIO_BTN_UP); - if (result != RT_EOK) - { - LOG_E("key initialization failed: %d", result); - return result; - } - - /* thread init */ - result = app_thread_init(); - if (result != RT_EOK) - { - LOG_E("thread initialization failed: %d", result); - return result; - } - - study_started = RT_TRUE; - - LOG_I("thread study started"); - - return RT_EOK; -} - -/** - * @brief MSH 命令入口。 - * - * 在 MSH 中执行 thread_study 命令启动线程学习示例。 - * - * @param argc 命令参数个数。 - * @param argv 命令参数列表。 - * - * @return RT_EOK 命令执行成功。 - * @return 其他错误码 命令执行失败。 - */ -static int thread_study(int argc, char **argv) -{ - RT_UNUSED(argc); - RT_UNUSED(argv); - - return thread_study_start(); -} - -/** - * @brief 初始化板载 LED GPIO。 - */ -static void board_io_init(void) -{ - /* led gpio init */ - rt_pin_mode(GPIO_LED_B, PIN_MODE_OUTPUT); - rt_pin_mode(GPIO_LED_R, PIN_MODE_OUTPUT); - - /* set led default level */ - rt_pin_write(GPIO_LED_B, PIN_LOW); - rt_pin_write(GPIO_LED_R, PIN_HIGH); -} - -/** - * @brief LED1 工作线程入口函数。 - * - * 动态创建,周期翻转红色 LED,并打印运行计数和系统 Tick。 - * 当收到挂起请求时,由线程主动挂起自身。 - * - * @param parameter 线程入口参数,本例未使用。 - */ -static void th_led1(void *parameter) -{ - rt_uint32_t count = 0; - rt_err_t result; - - RT_UNUSED(parameter); - - while (1) - { - /* 检查挂起请求 */ - if (led1_suspend_request == RT_TRUE) - { - LOG_I("th_led1 suspend"); - - result = rt_thread_suspend(rt_thread_self()); - if (result == RT_EOK) - { - led1_suspended = RT_TRUE; - - rt_schedule(); - - /* 线程恢复后继续执行 */ - led1_suspended = RT_FALSE; - - LOG_I("th_led1 resume"); - } - else - { - LOG_E("th_led1 suspend failed: %d", result); - } - } - - /* update led1 count */ - count++; - - /* toggle led1 */ - led_toggle(GPIO_LED_R); - - /* print led1 info */ - LOG_D("th_led1 count=%u, tick=%u", - (unsigned int)count, - (unsigned int)rt_tick_get()); - - rt_thread_mdelay(LED_PERIOD_MS); - } -} - -/** - * @brief LED2 工作线程入口函数。 - * - * 静态创建,周期翻转蓝色 LED,并打印运行计数。 - * - * @param parameter 线程入口参数,未使用。 - */ -static void th_led2(void *parameter) -{ - rt_uint32_t count = 0; - - RT_UNUSED(parameter); - - while (1) - { - /* update led2 count */ - count++; - - /* toggle led2 */ - led_toggle(GPIO_LED_B); - - /* print led2 info */ - LOG_D("th_led2 count=%u", - (unsigned int)count); - - rt_thread_mdelay(LED_PERIOD_MS); - } -} - -/** - * @brief 监控线程入口函数。 - * - * 周期打印两个 LED 线程的运行状态和剩余栈空间, - * 同时等待并处理按键事件。 - * - * @param parameter 线程入口参数,本例未使用。 - */ -static void th_monitor(void *parameter) -{ - rt_tick_t period_tick; - rt_tick_t last_tick; - rt_tick_t current_tick; - rt_tick_t elapsed_tick; - rt_tick_t timeout_tick; - rt_err_t result; - - RT_UNUSED(parameter); - - period_tick = rt_tick_from_millisecond(MONITOR_PERIOD_MS); - last_tick = rt_tick_get(); - - while (1) - { - current_tick = rt_tick_get(); - elapsed_tick = current_tick - last_tick; - - /* 计算监控线程等待超时时间 */ - if (elapsed_tick < period_tick) - { - timeout_tick = period_tick - elapsed_tick; - } - else - { - timeout_tick = 0; - } - - if (timeout_tick == 0) - { - monitor_print_thread_info(); - last_tick = rt_tick_get(); - continue; - } - - /* wait key event or timeout */ - result = rt_sem_take(&key_sem, timeout_tick); - - if (result == RT_EOK) - { - /* process key event */ - key_event_process(); - } - else if (result == -RT_ETIMEOUT) - { - /* print thread info */ - monitor_print_thread_info(); - last_tick = rt_tick_get(); - } - } -} - -/** - * @brief 创建并启动应用线程。 - * - * @return RT_EOK 所有线程初始化并启动成功。 - * @return 其他错误码 线程创建、初始化或启动失败。 - */ -static rt_err_t app_thread_init(void) -{ - rt_err_t result; - - /* 动态创建 LED1 线程 */ - led1_tid = rt_thread_create("th_led1", - th_led1, - RT_NULL, - LED_THREAD_STACK_SIZE, - LED_THREAD_PRIORITY, - LED_THREAD_TIMESLICE); - - if (led1_tid == RT_NULL) - { - LOG_E("create th_led1 failed"); - return -RT_ENOMEM; - } - - result = rt_thread_startup(led1_tid); - if (result != RT_EOK) - { - LOG_E("startup th_led1 failed: %d", result); - return result; - } - - /* 静态初始化 LED2 线程 */ - result = rt_thread_init(&led2_thread, - "th_led2", - th_led2, - RT_NULL, - led2_stack, - sizeof(led2_stack), - LED_THREAD_PRIORITY, - LED_THREAD_TIMESLICE); - - if (result != RT_EOK) - { - LOG_E("initialize th_led2 failed: %d", result); - return result; - } - - result = rt_thread_startup(&led2_thread); - if (result != RT_EOK) - { - LOG_E("startup th_led2 failed: %d", result); - return result; - } - - /* 动态创建监控线程 */ - monitor_tid = rt_thread_create("th_monitor", - th_monitor, - RT_NULL, - MONITOR_THREAD_STACK_SIZE, - MONITOR_THREAD_PRIORITY, - MONITOR_THREAD_TIMESLICE); - - if (monitor_tid == RT_NULL) - { - LOG_E("create th_monitor failed"); - return -RT_ENOMEM; - } - - result = rt_thread_startup(monitor_tid); - if (result != RT_EOK) - { - LOG_E("startup th_monitor failed: %d", result); - return result; - } - - return RT_EOK; -} - -/** - * @brief 翻转指定 GPIO 的输出电平。 - * - * @param pin RT-Thread PIN 设备引脚编号。 - */ -static void led_toggle(rt_base_t pin) -{ - rt_base_t level; - - /* read pin level */ - level = rt_pin_read(pin); - - /* toggle pin level */ - if (level == PIN_LOW) - { - rt_pin_write(pin, PIN_HIGH); - } - else - { - rt_pin_write(pin, PIN_LOW); - } -} - -/** - * @brief 获取线程状态对应的可读字符串。 - * - * @param thread 目标线程句柄。 - * - * @return 线程状态字符串。 - */ -static const char *get_thread_state(rt_thread_t thread) -{ - if (thread == RT_NULL) - { - return "NULL"; - } - - switch (thread->stat & RT_THREAD_STAT_MASK) - { - case RT_THREAD_INIT: - return "INIT"; - - case RT_THREAD_READY: - return "READY"; - - case RT_THREAD_RUNNING: - return "RUNNING"; - - case RT_THREAD_SUSPEND: - return "SUSPEND"; - - case RT_THREAD_CLOSE: - return "CLOSE"; - - default: - return "UNKNOWN"; - } -} - -/** - * @brief 估算线程历史未使用的栈空间。 - * - * RT-Thread 在线程栈初始化时使用 '#' 填充栈区域, - * 本函数通过统计仍保持填充值的连续区域估算栈剩余空间。 - * - * @param thread 目标线程句柄。 - * - * @return 估算得到的未使用栈空间,单位为字节。 - */ -static rt_size_t get_stack_free(rt_thread_t thread) -{ - rt_uint8_t *stack_ptr; - rt_size_t free_size = 0; - - if (thread == RT_NULL) - { - return 0; - } - - /* get stack start address */ - stack_ptr = (rt_uint8_t *)thread->stack_addr; - - /* calculate free stack size */ - while ((free_size < thread->stack_size) && - (*stack_ptr == '#')) - { - stack_ptr++; - free_size++; - } - - return free_size; -} - -/** - * @brief 打印 LED1 和 LED2 的线程状态及剩余栈空间。 - */ -static void monitor_print_thread_info(void) -{ - LOG_D("th_led1: state=%s, stack_free=%u", - get_thread_state(led1_tid), - (unsigned int)get_stack_free(led1_tid)); - - LOG_D("th_led2: state=%s, stack_free=%u", - get_thread_state(&led2_thread), - (unsigned int)get_stack_free(&led2_thread)); -} - -/** - * @brief 按键 GPIO 中断回调函数。 - * - * 中断中仅完成软件消抖和信号量释放,不执行耗时业务。 - * - * @param args 中断回调参数,本例未使用。 - */ -static void key_irq_callback(void *args) -{ - static rt_tick_t last_tick = 0; - - rt_tick_t current_tick; - rt_tick_t debounce_tick; - - RT_UNUSED(args); - - current_tick = rt_tick_get(); - debounce_tick = rt_tick_from_millisecond(KEY_DEBOUNCE_MS); - - /* 按键软件消抖 */ - if ((last_tick != 0) && - ((current_tick - last_tick) < debounce_tick)) - { - return; - } - - last_tick = current_tick; - - /* 中断中释放按键信号量 */ - rt_sem_release(&key_sem); -} - -/** - * @brief 初始化按键信号量及 GPIO 外部中断。 - * - * @param key_pin 按键对应的 RT-Thread PIN 编号。 - * - * @return RT_EOK 初始化成功。 - * @return 其他错误码 信号量或 GPIO 中断初始化失败。 - */ -static rt_err_t key_init(rt_base_t key_pin) -{ - rt_err_t result; - - /* semaphore init */ - result = rt_sem_init(&key_sem, - "key", - 0, - RT_IPC_FLAG_PRIO); - - if (result != RT_EOK) - { - LOG_E("initialize key semaphore failed: %d", result); - return result; - } - - /* key gpio init */ - rt_pin_mode(key_pin, PIN_MODE_INPUT_PULLUP); - - /* attach key irq */ - result = rt_pin_attach_irq(key_pin, - PIN_IRQ_MODE_FALLING, - key_irq_callback, - RT_NULL); - - if (result != RT_EOK) - { - LOG_E("attach key irq failed: %d", result); - return result; - } - - /* enable key irq */ - result = rt_pin_irq_enable(key_pin, PIN_IRQ_ENABLE); - - if (result != RT_EOK) - { - LOG_E("enable key irq failed: %d", result); - return result; - } - - return RT_EOK; -} - -/** - * @brief 处理按键事件并控制 LED1 线程挂起或恢复。 - * - * 第一次触发设置挂起请求,第二次触发恢复已挂起的 LED1 线程。 - */ -static void key_event_process(void) -{ - rt_err_t result; - - if (led1_suspend_request == RT_FALSE) - { - /* 设置 LED1 挂起请求 */ - led1_suspend_request = RT_TRUE; - - LOG_I("request th_led1 suspend"); - } - else - { - /* 清除 LED1 挂起请求 */ - led1_suspend_request = RT_FALSE; - - if (led1_suspended == RT_TRUE) - { - /* resume LED1 thread */ - result = rt_thread_resume(led1_tid); - - if (result != RT_EOK) - { - LOG_E("resume th_led1 failed: %d", result); - return; - } - } - - LOG_I("request th_led1 resume"); - } -} - -MSH_CMD_EXPORT(thread_study, start thread study example); diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 0a3861dbe6d7f06e7188a4cf2bfeefcd8b50f81a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\344\275\234\344\270\232/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,642 +0,0 @@ -# RT-Thread线程培训学习随笔 - -今天的培训算是真正开始进入RT-Thread的“内核世界”了。前面搭环境、用Git、提PR,更像是在为后面的学习准备工具;而今天开始接触线程以后,很多以前只知道名字的概念,终于慢慢连成了一条线。尤其是从裸机开发讲到RTOS,再讲到线程控制块、线程状态、优先级、时间片,最后落到`rt_thread_init()`、`rt_thread_create()`和`rt_thread_startup()`这些API上,我第一次比较完整地看到了一个线程从“被创建”到“真正运行”的全过程。 - -这次培训对我来说最大的收获,是开始理解:**RTOS到底是怎样把一个复杂的嵌入式程序拆成多个任务,再让这些任务按照一定规则共享CPU的。** - ---- - -## 一、从裸机开发走到RTOS多任务 - -### 1.以前的`while(1)`为什么会慢慢变得难维护 - -刚开始学单片机的时候,最熟悉的程序结构往往就是一个`while(1)`大循环。初始化完成后,ADC采集、SPI读取、USB处理、LCD刷新、文件写入等功能,一个接一个地放在循环里执行。遇到外部事件时,再通过中断服务程序进行处理。 - -大概就是下面这种感觉: - -```c -void main(void) -{ - init(); - - while (1) - { - ADC_Read(); - SPI_Read(); - USB_Packet(); - LCD_Update(); - Audio_Decode(); - File_Write(); - } -} -``` - -这种写法在程序规模很小的时候其实很好理解。哪个功能先执行、哪个功能后执行,一眼就能看出来。但是今天培训里提到的一个问题让我印象很深:**只要其中一个函数出现了比较长的等待,整个后台循环都会被拖住。** - -比如`ADC_Read()`里面为了等待转换结束,一直卡在某个循环中,那么后面的`SPI_Read()`、`LCD_Update()`这些任务就只能排队等着。程序功能少的时候问题可能还不明显,但功能一旦变复杂,所有任务互相影响,实时性就越来越难保证。 - -课件把裸机模式的问题总结成了几个方面:并发性低、实时性难保证、可维护性差、可重用性低。我觉得这几个词放到实际代码里非常容易理解。一个大循环里塞了十几个功能以后,如果想改其中一个模块,往往要同时考虑它对其他流程有没有影响;而且很多功能之间通过各种全局变量和状态标志联系在一起,改动一个地方可能就会牵出一串问题。 - -所以我现在对裸机开发的理解是: - -> 裸机不是不能做复杂程序,而是当任务越来越多时,程序员需要自己承担越来越多的“调度工作”。 - -而RTOS出现的意义,就是把这部分工作交给操作系统。 - -### 2.线程让我第一次真正理解“分而治之” - -今天培训里讲线程时,用到了“分而治之”这个说法,我觉得非常贴切。 - -一个复杂系统里,可能同时存在很多事情:传感器要采集数据、串口要接收命令、网络要发送数据、屏幕要刷新、按键还要及时响应。如果全部塞进一个大循环里,就很容易互相干扰。 - -RTOS的思路则是把这些事情拆开: - -```text -传感器采集 → 一个线程 -串口处理 → 一个线程 -网络通信 → 一个线程 -界面刷新 → 一个线程 -数据存储 → 一个线程 -``` - -每个线程只专注于自己的任务,然后由RTOS的调度器决定它们什么时候运行。 - -这里有一点我以前容易误解,就是“多线程”看起来像很多任务一起跑,但如果是在单核CPU上,某一个时刻真正运行的仍然只有一个线程。只是操作系统切换得很快,所以从整体效果上看,多个任务像是在并行工作。 - -我觉得这个概念非常重要,因为它说明RTOS的核心并不是简单地“多创建几个函数”,而是: - -1.把功能拆成多个独立任务; -2.为每个任务建立线程; -3.由调度器统一分配CPU时间; -4.线程之间按照优先级、状态等规则切换。 - -也就是说,**线程是任务真正被操作系统管理起来之后的实体。** - -### 3.RT-Thread启动以后,`main()`其实已经在一个线程里 - -今天还看了RT-Thread的启动流程图。虽然流程比较长,但我觉得有一件事特别值得记住:在RT-Thread里,`main()`并不是脱离操作系统单独运行的,它本身就在主线程中执行。 - -系统从启动文件进入以后,会执行`rtthread_startup()`,然后完成一系列初始化工作,例如板级初始化、系统定时器初始化、调度器初始化、应用初始化等。之后系统会创建主线程、空闲线程等系统线程,最后启动调度器。 - -我现在可以把这个流程粗略理解成: - -```text -启动文件 - ↓ -rtthread_startup() - ↓ -板级初始化 - ↓ -定时器、调度器等内核初始化 - ↓ -创建系统线程 - ↓ -启动调度器 - ↓ -main_thread_entry() - ↓ -main() -``` - -也就是说,当我在`main()`里写: - -```c -int main(void) -{ - rt_kprintf("hello world!\n"); - return 0; -} -``` - -这段代码其实已经是在RT-Thread建立好的线程环境中运行了。 - -这让我对“操作系统启动”和“应用程序启动”的关系清楚了很多。以前总觉得程序从`main()`开始,实际上在RTOS里,真正进入`main()`之前,系统已经完成了很多内核层面的准备工作。 - ---- - -## 二、线程不是一个函数,而是一整套运行信息 - -### 1.线程控制块就像线程的“个人档案” - -如果RT-Thread要管理很多线程,那么系统必须知道每个线程现在是什么情况。比如这个线程叫什么名字、优先级是多少、现在是运行还是挂起、线程栈在哪里、入口函数是什么、还剩多少时间片等等。 - -这些信息都需要一个地方保存,这个地方就是**线程控制块**。 - -RT-Thread中的线程控制块由`struct rt_thread`定义。课件里提到,它会保存线程的优先级、线程名称、线程状态,还会保存线程栈、入口函数、链表结构以及其他内核管理信息。 - -我觉得可以把它想成一个线程的“个人档案”: - -```text -struct rt_thread -│ -├── 线程名称 -├── 线程状态 -├── 当前优先级 -├── 初始优先级 -├── 线程入口函数 -├── 入口函数参数 -├── 线程栈地址 -├── 栈大小 -├── 时间片 -├── 定时器 -└── 内核管理链表等信息 -``` - -所以线程并不只是那段入口函数代码。入口函数只定义“这个线程要做什么”,而线程控制块才真正让操作系统能够“认识、记录和管理”这个线程。 - -另外,`struct rt_thread`继承自RT-Thread中的基础对象`rt_object`。这说明RT-Thread内部很多资源都不是零散管理的,而是通过统一的内核对象机制组织起来。线程只是其中一种对象,其他像信号量、互斥量、消息队列、定时器、设备等,也有相应的对象结构。 - -这一点让我觉得RT-Thread的内部设计其实很有体系,而不是简单地堆API。 - -### 2.线程状态不是固定的,而是在不断变化 - -今天线程状态这一部分我觉得特别重要,因为调度其实就是围绕这些状态展开的。 - -RT-Thread里主要有以下几种线程状态: - -- `RT_THREAD_INIT`:初始状态; -- `RT_THREAD_READY`:就绪状态; -- `RT_THREAD_RUNNING`:运行状态; -- `RT_THREAD_SUSPEND`:挂起状态; -- `RT_THREAD_CLOSE`:关闭状态。 - -一开始创建或者初始化线程以后,线程还不能直接运行,而是处于初始状态。调用`rt_thread_startup()`之后,线程才进入就绪状态,开始参加调度。 - -如果调度器选中了它,它就从就绪态进入运行态。运行过程中,如果调用了延时函数、等待某个资源,或者主动挂起,它就会进入挂起状态。等延时时间到了或者等待条件满足以后,它又会重新回到就绪状态。 - -最后如果线程执行结束,就会进入关闭状态,不再参与调度。 - -我觉得最值得记住的主线就是: - -```text -创建/初始化 - ↓ -初始态 - ↓ rt_thread_startup() -就绪态 - ↓ 调度器选中 -运行态 - ↓ 延时/等待资源 -挂起态 - ↓ 条件满足 -就绪态 - ↓ -再次被调度 -``` - -这样一看,线程调度就没有那么抽象了。调度器实际上一直在“就绪线程”里选择最应该运行的线程,而挂起状态的线程暂时不会参与竞争。 - -### 3.优先级、时间片和线程栈决定线程怎样运行 - -线程创建时需要设置很多参数,其中让我印象最深的三个就是:**优先级、时间片和线程栈。** - -先说优先级。RT-Thread中优先级数值越小,优先级越高,`0`是最高优先级。这个地方和生活里“数字越大级别越高”的直觉相反,所以很容易记错。 - -如果当前正在运行一个优先级为20的线程,而此时一个优先级为10的线程进入就绪状态,那么后者优先级更高,可以马上抢占CPU。 - -所以我现在可以把优先级理解成: - -> 谁更重要,谁就应该更早得到CPU。 - -不过如果两个线程优先级一样,就需要用到时间片。时间片只对**相同优先级的就绪线程**有意义。比如线程A和线程B优先级相同,A的时间片是10个OSTick,B的时间片是5个OSTick,那么系统会让它们轮流运行,每次分别执行对应的节拍数量。 - -所以可以简单记成: - -> 优先级决定谁先跑,时间片决定同优先级线程一次跑多久。 - -线程栈则是另一个很重要的资源。每个线程都有自己独立的栈空间。线程切换时,当前线程的上下文信息会保存到它自己的栈里,等以后重新运行时再恢复。函数调用过程中产生的局部变量等内容也会使用线程栈。 - -这让我意识到:线程栈不是“随便给个数字”就行。栈给得太小,复杂函数调用时可能出现栈溢出;栈给得太大,又会浪费宝贵的RAM。所以以后真正做项目时,线程栈大小应该根据实际任务复杂度去调整,而不是所有线程统一写一个很大的值。 - ---- - -## 三、从API开始真正创建一个线程 - -### 1.静态线程和动态线程,其实是在选择“内存由谁准备” - -今天开始接触线程管理API以后,我觉得线程终于从概念变成了可以实际操作的东西。 - -RT-Thread中创建线程主要有两种方式:静态线程和动态线程。 - -静态线程使用`rt_thread_init()`进行初始化。它的特点是线程控制块和线程栈都由用户自己准备。 - -例如: - -```c -static struct rt_thread thread1; -static char thread1_stack[1024]; -``` - -这里`thread1`是线程控制块,`thread1_stack`是线程运行需要的栈空间。 - -然后调用: - -```c -rt_thread_init(&thread1, - "thread1", - thread1_entry, - RT_NULL, - &thread1_stack[0], - sizeof(thread1_stack), - 20, - 10); -``` - -这些参数分别对应线程控制块、线程名称、入口函数、入口参数、栈地址、栈大小、优先级和时间片。 - -静态线程让我感觉更“可控”,因为内存是在编译阶段就准备好的,不依赖运行时动态分配。 - -动态线程则使用`rt_thread_create()`: - -```c -rt_thread_t tid; - -tid = rt_thread_create("thread1", - thread1_entry, - RT_NULL, - 1024, - 20, - 10); -``` - -调用这个函数后,RT-Thread会从动态堆中为线程控制块和线程栈申请空间。 - -所以我现在对这两种方式有一个比较简单的记法: - -> `rt_thread_init()`:内存我准备,系统帮我初始化。 -> `rt_thread_create()`:内存也由系统帮我申请。 - -静态和动态并没有绝对的好坏,更多是看实际项目中对内存、确定性和使用方便程度的要求。 - -### 2.创建成功不代表已经运行,还需要`rt_thread_startup()` - -这一点是今天特别需要注意的地方。 - -无论使用`rt_thread_init()`还是`rt_thread_create()`,线程刚创建完成以后都只是处于**初始状态**,还没有真正进入调度器的就绪队列。 - -还需要调用: - -```c -rt_thread_startup(thread); -``` - -或者动态线程中: - -```c -rt_thread_startup(tid); -``` - -线程才会变成就绪状态。 - -因此一个线程真正跑起来的过程应该是: - -```text -准备线程入口函数 - ↓ -准备线程控制块和线程栈(静态线程) - ↓ -rt_thread_init() -或 -rt_thread_create() - ↓ -线程处于初始态 - ↓ -rt_thread_startup() - ↓ -线程进入就绪队列 - ↓ -调度器进行调度 - ↓ -线程开始运行 -``` - -这个过程让我对“创建”和“启动”两个概念分得更清楚了。 - -更有意思的是,如果新启动的线程优先级比当前线程更高,那么它进入就绪态后,调度器可能马上发生切换,让这个新线程立即运行。也就是说,`rt_thread_startup()`之后程序的实际执行顺序,已经不完全由代码书写顺序决定,而是开始受调度器控制。 - -这也是RTOS和裸机程序非常明显的区别。 - -### 3.线程不仅要会“抢CPU”,也要会主动让出CPU - -今天最后讲到线程延时时,我觉得这个概念把前面的调度逻辑补完整了。 - -如果一个线程一直在下面这样的死循环里工作: - -```c -while (1) -{ - do_something(); -} -``` - -并且里面没有阻塞、延时或等待,那么它可能会一直保持就绪甚至运行状态,不断占用CPU。 - -在抢占式系统里,一个设计合理的线程应该在暂时没有事情需要处理时,把CPU让给其他线程。 - -RT-Thread可以使用: - -```c -rt_thread_delay(10); -``` - -这里的延时单位是OSTick。 - -也可以使用更容易理解的毫秒接口: - -```c -rt_thread_mdelay(500); -``` - -线程调用延时以后,会暂时进入挂起状态。此时它不再参与调度,CPU可以去执行其他就绪线程。等时间到了以后,它再重新回到就绪状态,等待调度器安排执行。 - -因此一个比较典型的线程入口函数可以写成: - -```c -static void thread1_entry(void *parameter) -{ - while (1) - { - rt_kprintf("thread1 running\n"); - - /* 当前任务完成,暂时让出CPU */ - rt_thread_mdelay(500); - } -} -``` - -以前看到`delay`时,我第一反应只是“让程序停一会儿”。但在RTOS里,它的意义其实完全不同: - -> 它不仅是延时,更重要的是让当前线程暂时退出CPU竞争,把执行机会交给其他线程。 - -从这个角度看,线程的运行其实一直是在“运行—等待—再次运行”之间循环,而不是一个线程永远霸占处理器。 - -在这次实际练习里,我把前面这些线程状态和调度概念放到了`thread_study`示例中观察。相比只看API说明,直接从日志里看两个线程的计数、状态变化和挂起/恢复过程,会更容易理解线程到底是怎么被调度器管理的。 - -首先把这个实验命令导出到了msh中。在msh帮助列表里可以看到新增的: - -```text -thread_study - start thread study example -``` - -这样就不需要每次都在`main()`中固定调用实验代码,而是可以在系统启动后通过msh手动执行`thread_study`。我觉得这种方式很适合做RT-Thread学习实验:系统正常启动以后,需要测试哪一部分功能,就在msh里运行对应命令,调试过程会灵活很多。 - -![msh导出界面](./figures/msh_export.png) - -*图:msh命令列表中已经出现`thread_study`,说明实验命令成功导出到RT-Thread Shell。* - -在正常运行`thread_study`时,`th_led1`和`th_led2`都会持续执行。日志里可以看到两个线程的`count`不断增加,其中`th_led1`还打印了系统`tick`。同时程序会周期性读取线程状态和剩余栈空间,可以看到`stack_free=776`。截图中偶尔显示`state=SUSPEND`并不代表线程被永久挂起,因为线程入口中本身存在延时操作,监测线程状态的时刻刚好可能落在线程延时期间。 - -![thread_study运行过程](./figures/thread_study_run.png) - -*图:`thread_study`正常运行过程,可以观察两个线程的计数、tick以及线程状态和剩余栈空间。* - -接着尝试主动挂起`th_led1`。从日志可以看到程序先输出`request th_led1 suspend`,随后`th_led1`进入`SUSPEND`状态,而`th_led2`仍然处于`READY`并继续增加计数。此时最直观的现象就是`th_led1`的计数停止,而`th_led2`仍然正常运行。这让我第一次比较直观地看到:**挂起一个线程并不是整个系统停下来,而只是让这个线程暂时退出调度,其他就绪线程仍然可以继续获得CPU。** - -![led1_th挂起](./figures/led1_suspend.png) - -*图:主动挂起`th_led1`后,`th_led1`保持`SUSPEND`,而`th_led2`继续执行。* - -之后再恢复`th_led1`。日志中可以看到`request th_led1 resume`以及`th_led1 resume`,紧接着`th_led1 count`重新开始增加,说明它重新具备了被调度执行的条件。后面状态监测再次出现`SUSPEND`,这里同样需要结合线程本身的延时逻辑来看:恢复操作已经成功,只是线程运行后又进入了正常的延时等待阶段,所以抓取状态时仍可能看到`SUSPEND`。 - -![led1_th恢复](./figures/led1_resume.png) - -*图:恢复`th_led1`后,其计数重新增长,说明线程重新进入正常调度流程。* - ---- - -## 今天学习后的整体理解:再和以前用过的系统对照一下 - -今天把RT-Thread线程这一套逻辑学完以后,我很自然地想到了之前接触过的FreeRTOS、ChibiOS,还有Linux里的线程。以前用这些系统的时候,很多API更多是“照着例程会用”,但没有特别认真地把它们放到一起比较。现在再回头看,会发现它们虽然都有“任务”“线程”“优先级”“调度”“延时”这些概念,但背后的定位和使用习惯其实差别很明显。 - -这部分不是课件原本的内容,而是我在今天RT-Thread线程培训的基础上,结合RT-Thread、FreeRTOS、ChibiOS官方文档以及Linux线程文档做的一次对照整理。对我来说,这种横向比较反而更容易理解RT-Thread为什么要这样设计。 - -### 1.和FreeRTOS相比:很多概念很熟悉,但RT-Thread更像一个完整的“小型操作系统” - -FreeRTOS应该是我最容易拿来和RT-Thread对比的一个系统,因为两者都是面向嵌入式场景的RTOS,而且最核心的多任务思路很接近。 - -在FreeRTOS里通常把运行实体叫做`Task`,而RT-Thread直接叫`Thread`。名字不同,但从最基本的使用角度看,它们其实非常相似:每个任务都有自己的栈、优先级和运行状态,调度器总是优先让更高优先级的就绪任务运行;如果多个任务处于相同优先级,也可以进行时间片轮转。 - -以前写FreeRTOS时,一个很典型的创建任务方式是: - -```c -xTaskCreate(TaskEntry, - "task1", - 256, - NULL, - 3, - &task_handle); -``` - -对应到RT-Thread里,会变成类似: - -```c -rt_thread_t tid; - -tid = rt_thread_create("thread1", - thread1_entry, - RT_NULL, - 1024, - 20, - 10); - -rt_thread_startup(tid); -``` - -这两个系统放在一起看,有几个区别我觉得特别值得记。 - -**第一个区别是优先级数字的方向刚好相反。** - -在FreeRTOS里,任务优先级通常是**数字越大,优先级越高**,`0`是最低的任务优先级,空闲任务也位于最低优先级。而RT-Thread正好反过来,**数字越小,优先级越高,`0`是最高优先级**。 - -所以从FreeRTOS切到RT-Thread以后,如果还按照以前的习惯写优先级,就非常容易把高低关系写反。 - -我现在会直接记成: - -```text -FreeRTOS:数字越大 → 优先级越高 -RT-Thread:数字越小 → 优先级越高 -``` - -**第二个区别是“创建”和“启动”的关系。** - -FreeRTOS里的`xTaskCreate()`创建任务以后,会把任务加入可调度的任务列表;如果调度器已经启动,新任务满足调度条件,就可能直接开始运行。 - -而RT-Thread把这两个动作拆得更加明显: - -```text -rt_thread_create() - ↓ -线程只是被创建,处于初始态 - ↓ -rt_thread_startup() - ↓ -进入就绪队列 - ↓ -参与调度 -``` - -这种设计一开始比FreeRTOS多了一步,但我觉得它的好处是线程生命周期更直观。创建对象和让对象参与调度是两个清晰的阶段,如果需要先把若干线程都准备好,再按照自己的顺序启动,会比较容易理解。 - -**第三个区别是系统整体给我的感觉不同。** - -FreeRTOS的Kernel本身非常聚焦,最核心的内容就是任务调度、队列、信号量、事件组、软件定时器以及内存管理等。它给我的感觉更像一个非常精炼的实时内核:需要什么功能,就围绕Kernel和对应库去组合。 - -RT-Thread除了实时内核以外,还把设备模型、组件、软件包、BSP、FinSH/msh、POSIX接口、Env、menuconfig和软件包管理工具放进了同一套体系中。尤其是在今天看到`rt_object`、线程对象容器这些内容之后,我会感觉RT-Thread的风格更接近一个“小型完整操作系统”,而不仅仅是一个任务调度内核。 - -这也是我目前觉得RT-Thread相对FreeRTOS比较明显的优点之一: - -> 对于只需要几个任务、队列和中断的小型工程,FreeRTOS很直接;而当项目逐渐增加设备驱动、文件系统、网络、Shell、软件包和更多组件以后,RT-Thread提供的整体框架会让我更容易把这些东西放到同一个系统里管理。 - -当然,这并不代表FreeRTOS能力弱。FreeRTOS本身非常成熟,支持的处理器架构很多,Kernel接口也比较精炼,而且现在还有网络、SMP以及云服务相关组件。只是从我当前学习RT-Thread的角度看,RT-Thread更强调“内核+设备+组件+开发工具+软件包”的整体体验。 - -### 2.和ChibiOS相比:两者都很强调线程,但RT-Thread的组件化和工具链让我感觉更完整 - -ChibiOS和RT-Thread放在一起比较也很有意思,因为ChibiOS本身就非常强调`Thread`这个概念,而不是像FreeRTOS那样习惯称为`Task`。 - -ChibiOS/RT中的线程同样拥有自己的处理器上下文、状态以及独立栈空间,而且也支持静态线程和动态线程。系统初始化以后,也会存在主线程和空闲线程。从这些概念上看,它和今天学习的RT-Thread非常接近。 - -比如以前在ChibiOS里创建一个静态线程时,会先准备Working Area: - -```c -static THD_WORKING_AREA(waThread1, 128); -``` - -然后调用: - -```c -chThdCreateStatic(waThread1, - sizeof(waThread1), - NORMALPRIO, - Thread1, - NULL); -``` - -这里和RT-Thread静态线程的思路很像,本质上都是提前准备好线程需要的内存和栈,再交给内核管理。 - -不过两者还是有一些明显的使用差异。 - -**首先还是优先级方向。**ChibiOS常见的优先级定义中,`NORMALPRIO`位于中间,而`HIGHPRIO`对应更高的数值,整体习惯上也是**数值越大,线程优先级越高**。RT-Thread则仍然是数字越小优先级越高。 - -所以如果把三个RTOS放在一起记: - -```text -FreeRTOS:高数字 = 高优先级 -ChibiOS:高数字 = 高优先级 -RT-Thread:低数字 = 高优先级 -``` - -这个区别对实际写代码非常重要。 - -**其次是线程创建接口的风格。**ChibiOS的`chThdCreateStatic()`本身就是“创建并启动一个线程”的常用接口;而RT-Thread更明确地把`rt_thread_init()`/`rt_thread_create()`和`rt_thread_startup()`分开。RT-Thread这种方式在我看来更容易观察一个线程从“对象已经存在”到“进入调度队列”的状态变化,也更贴合今天课件里讲的初始态、就绪态这些概念。 - -ChibiOS的优势也很明显。它的RT内核非常强调小体积、高性能和严格的优先级调度,同时ChibiOS不只有RT内核,还包含HAL等一整套嵌入式组件,所以做MCU项目时,从驱动到线程都可以保持统一的ChibiOS风格。 - -而我现在觉得RT-Thread比较吸引我的地方,是它在这种完整性之外,又增加了比较明显的“软件生态”思路。比如使用`menuconfig`进行裁剪、通过Env管理工程和软件包、用`pkgs`获取组件,以及比较统一的设备框架。它还提供POSIX兼容层,这意味着以后如果需要把一些Linux/POSIX风格的代码迁移到RT-Thread上,思路会更顺一些。 - -所以从我自己的使用感觉上,可以暂时这样概括: - -> ChibiOS给我的感觉是“紧凑、高性能、RT内核和HAL结合得很紧”;RT-Thread则更像“从MCU实时内核继续向完整嵌入式操作系统扩展”,尤其适合后面还想继续接触设备框架、文件系统、网络、Shell和软件包的场景。 - -### 3.和Linux线程相比:名字都叫线程,但实际上是在两个完全不同的系统层级里工作 - -Linux线程和RT-Thread线程的对比是我觉得最有意思的一组,因为表面上它们都叫`thread`,都可以创建多个执行流,也都有线程栈、优先级、阻塞和唤醒,但它们所在的系统环境完全不同。 - -Linux用户态最常见的是POSIX Threads,也就是Pthreads: - -```c -pthread_t tid; -pthread_create(&tid, NULL, thread_entry, NULL); -``` - -一个Linux进程中可以有多个线程。这些线程共享同一个进程的数据段和堆,但每个线程有自己的栈、线程ID以及部分独立属性。Linux现在常用的NPTL实现是1:1模型,也就是说一个用户线程会对应一个内核调度实体,由Linux调度器进行调度。 - -这一点和RT-Thread最大的区别,是Linux首先有一个非常完整的**进程模型**。 - -可以简单理解为: - -```text -Linux -│ -├── 进程A -│ ├── 线程1 -│ ├── 线程2 -│ └── 线程3 -│ -└── 进程B - ├── 线程1 - └── 线程2 -``` - -而我们今天学习的标准RT-Thread里,重点是直接由内核管理线程,常见MCU场景通常没有Linux这种完整的用户进程+虚拟地址空间模型,各个线程更直接地共享系统资源和地址空间。(RT-Thread Smart带有进程和用户态机制,这里讨论的是今天培训中的标准RT-Thread线程模型。) - -另外,Linux默认线程调度也不是简单的“固定优先级最高者永远先跑”。普通Linux线程通常使用`SCHED_OTHER`这类面向公平性的分时调度策略,系统会考虑CPU使用情况,让不同任务都能够取得运行机会。Linux同时也提供`SCHED_FIFO`、`SCHED_RR`和`SCHED_DEADLINE`等实时调度策略,并且在启用`PREEMPT_RT`后可以进一步强化实时能力。 - -RT-Thread的默认思路则简单直接得多: - -```text -找到最高优先级就绪线程 - ↓ - 立即运行 - ↓ -出现更高优先级就绪线程 - ↓ - 立即抢占 -``` - -这种调度模型对嵌入式实时控制很容易推理。比如我需要一个紧急保护线程必须比日志线程更快响应,只要合理设置优先级并控制临界区,就能比较直接地分析它什么时候能够拿到CPU。 - -Linux的优势在于功能完整:进程隔离、虚拟内存、用户态和内核态、成熟的文件系统、网络协议栈、驱动框架以及庞大的用户态软件生态,这些都是普通MCU上的RTOS无法直接相比的。但这些能力也意味着系统更庞大,调度和资源管理层次更多。 - -RT-Thread的优势则正好体现在嵌入式实时场景:系统小、路径短、线程调度规则直接、和硬件联系更紧密,对RAM和Flash有限的MCU更加友好。特别是在控制类应用里,我们往往更关心“这个高优先级任务能不能及时运行”,而不是桌面系统那种“所有应用长期运行是否足够公平”。 - -还有一个让我觉得很有用的地方是,RT-Thread本身提供了POSIX接口支持,Pthreads相关接口实际上是对RT-Thread内核线程能力的封装。也就是说,以前在Linux里学习过的`pthread_create()`、mutex、condition variable等概念并没有完全浪费,它们可以帮助我理解RT-Thread里的线程和IPC;只是到了RT-Thread底层,最终还是会落到`rt_thread`以及RT-Thread自己的调度和内核对象上。 - -如果把这四套系统用一句比较粗略的话放在一起,我现在的感觉是: - -| 系统 | 我现在对它的理解 | 比较突出的特点 | -| ---------- | -------------------------------------- | ------------------------------------------------------------ | -| FreeRTOS | 精炼的嵌入式实时内核 | Kernel简单直接、任务和IPC接口清晰、适合快速构建实时任务系统 | -| ChibiOS/RT | 高性能RT内核+紧密的嵌入式组件/HAL体系 | 线程模型紧凑、调度直接、HAL结合度高 | -| RT-Thread | 从RTOS内核继续向完整嵌入式OS发展的体系 | 线程和内核对象清晰,设备框架、组件、POSIX、Env和软件包体系完整 | -| Linux | 完整的通用操作系统 | 进程隔离、虚拟内存、POSIX线程、丰富驱动和用户态生态,系统能力最完整 | - -如果只从今天学习的“线程”这一件事来看,RT-Thread并没有发明一套完全陌生的概念。它和FreeRTOS、ChibiOS一样,都离不开栈、优先级、阻塞、唤醒和调度;它和Linux的Pthreads也有很多概念上的对应关系。 - -真正让我感觉RT-Thread有特色的地方,是它把这些RTOS最核心的机制保留下来,同时又往上增加了一套比较完整的设备、组件、Shell、POSIX和软件包体系。对于我这种已经接触过FreeRTOS、ChibiOS和Linux线程的人来说,很多基础概念其实可以直接迁移过来,而需要重新适应的重点主要是RT-Thread自己的对象体系、优先级方向、线程生命周期以及开发工具链。 - ---- - -### 把今天的内容重新串起来 - - -今天这节线程培训的信息量比较大,但把内容串起来以后,我觉得RT-Thread线程其实可以用一条完整的逻辑来理解: - -```text -复杂程序 - ↓ -拆成多个独立任务 - ↓ -每个任务建立一个线程 - ↓ -线程拥有自己的控制块、栈、优先级和时间片 - ↓ -线程创建后进入初始状态 - ↓ -调用rt_thread_startup()进入就绪状态 - ↓ -调度器根据优先级选择线程运行 - ↓ -线程执行自己的入口函数 - ↓ -延时或等待时主动让出CPU - ↓ -条件满足后重新进入就绪状态 -``` - -如果只记API,很容易觉得`rt_thread_init()`、`rt_thread_create()`、`rt_thread_startup()`、`rt_thread_mdelay()`都是互相独立的函数。但今天学完之后,我发现这些API其实是在描述线程生命周期的不同阶段。 - -`rt_thread_init()`和`rt_thread_create()`负责让线程“出生”;`rt_thread_startup()`让线程正式进入调度体系;调度器决定它什么时候获得CPU;`rt_thread_mdelay()`等接口则让它在暂时不需要运行时主动退出竞争。 - -这样一来,RTOS里的多任务也没有最开始想象得那么神秘。它本质上就是不断维护线程状态、管理线程资源,再按照优先级和调度规则,让不同线程轮流使用CPU。 - -今天算是第一次真正从“会用RT-Thread工程”往“理解RT-Thread为什么能这样运行”迈了一步。后面如果继续学习线程同步、信号量、互斥量、邮箱、消息队列这些内容,我觉得它们应该都是建立在今天这个线程和调度模型之上的。 diff --git "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" deleted file mode 100644 index 0a3861dbe6d7f06e7188a4cf2bfeefcd8b50f81a..0000000000000000000000000000000000000000 --- "a/2026/\347\254\2544\347\273\204/\351\272\273\345\263\273\350\217\201/\347\254\254\344\272\214\345\244\251\344\275\234\344\270\232/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" +++ /dev/null @@ -1,642 +0,0 @@ -# RT-Thread线程培训学习随笔 - -今天的培训算是真正开始进入RT-Thread的“内核世界”了。前面搭环境、用Git、提PR,更像是在为后面的学习准备工具;而今天开始接触线程以后,很多以前只知道名字的概念,终于慢慢连成了一条线。尤其是从裸机开发讲到RTOS,再讲到线程控制块、线程状态、优先级、时间片,最后落到`rt_thread_init()`、`rt_thread_create()`和`rt_thread_startup()`这些API上,我第一次比较完整地看到了一个线程从“被创建”到“真正运行”的全过程。 - -这次培训对我来说最大的收获,是开始理解:**RTOS到底是怎样把一个复杂的嵌入式程序拆成多个任务,再让这些任务按照一定规则共享CPU的。** - ---- - -## 一、从裸机开发走到RTOS多任务 - -### 1.以前的`while(1)`为什么会慢慢变得难维护 - -刚开始学单片机的时候,最熟悉的程序结构往往就是一个`while(1)`大循环。初始化完成后,ADC采集、SPI读取、USB处理、LCD刷新、文件写入等功能,一个接一个地放在循环里执行。遇到外部事件时,再通过中断服务程序进行处理。 - -大概就是下面这种感觉: - -```c -void main(void) -{ - init(); - - while (1) - { - ADC_Read(); - SPI_Read(); - USB_Packet(); - LCD_Update(); - Audio_Decode(); - File_Write(); - } -} -``` - -这种写法在程序规模很小的时候其实很好理解。哪个功能先执行、哪个功能后执行,一眼就能看出来。但是今天培训里提到的一个问题让我印象很深:**只要其中一个函数出现了比较长的等待,整个后台循环都会被拖住。** - -比如`ADC_Read()`里面为了等待转换结束,一直卡在某个循环中,那么后面的`SPI_Read()`、`LCD_Update()`这些任务就只能排队等着。程序功能少的时候问题可能还不明显,但功能一旦变复杂,所有任务互相影响,实时性就越来越难保证。 - -课件把裸机模式的问题总结成了几个方面:并发性低、实时性难保证、可维护性差、可重用性低。我觉得这几个词放到实际代码里非常容易理解。一个大循环里塞了十几个功能以后,如果想改其中一个模块,往往要同时考虑它对其他流程有没有影响;而且很多功能之间通过各种全局变量和状态标志联系在一起,改动一个地方可能就会牵出一串问题。 - -所以我现在对裸机开发的理解是: - -> 裸机不是不能做复杂程序,而是当任务越来越多时,程序员需要自己承担越来越多的“调度工作”。 - -而RTOS出现的意义,就是把这部分工作交给操作系统。 - -### 2.线程让我第一次真正理解“分而治之” - -今天培训里讲线程时,用到了“分而治之”这个说法,我觉得非常贴切。 - -一个复杂系统里,可能同时存在很多事情:传感器要采集数据、串口要接收命令、网络要发送数据、屏幕要刷新、按键还要及时响应。如果全部塞进一个大循环里,就很容易互相干扰。 - -RTOS的思路则是把这些事情拆开: - -```text -传感器采集 → 一个线程 -串口处理 → 一个线程 -网络通信 → 一个线程 -界面刷新 → 一个线程 -数据存储 → 一个线程 -``` - -每个线程只专注于自己的任务,然后由RTOS的调度器决定它们什么时候运行。 - -这里有一点我以前容易误解,就是“多线程”看起来像很多任务一起跑,但如果是在单核CPU上,某一个时刻真正运行的仍然只有一个线程。只是操作系统切换得很快,所以从整体效果上看,多个任务像是在并行工作。 - -我觉得这个概念非常重要,因为它说明RTOS的核心并不是简单地“多创建几个函数”,而是: - -1.把功能拆成多个独立任务; -2.为每个任务建立线程; -3.由调度器统一分配CPU时间; -4.线程之间按照优先级、状态等规则切换。 - -也就是说,**线程是任务真正被操作系统管理起来之后的实体。** - -### 3.RT-Thread启动以后,`main()`其实已经在一个线程里 - -今天还看了RT-Thread的启动流程图。虽然流程比较长,但我觉得有一件事特别值得记住:在RT-Thread里,`main()`并不是脱离操作系统单独运行的,它本身就在主线程中执行。 - -系统从启动文件进入以后,会执行`rtthread_startup()`,然后完成一系列初始化工作,例如板级初始化、系统定时器初始化、调度器初始化、应用初始化等。之后系统会创建主线程、空闲线程等系统线程,最后启动调度器。 - -我现在可以把这个流程粗略理解成: - -```text -启动文件 - ↓ -rtthread_startup() - ↓ -板级初始化 - ↓ -定时器、调度器等内核初始化 - ↓ -创建系统线程 - ↓ -启动调度器 - ↓ -main_thread_entry() - ↓ -main() -``` - -也就是说,当我在`main()`里写: - -```c -int main(void) -{ - rt_kprintf("hello world!\n"); - return 0; -} -``` - -这段代码其实已经是在RT-Thread建立好的线程环境中运行了。 - -这让我对“操作系统启动”和“应用程序启动”的关系清楚了很多。以前总觉得程序从`main()`开始,实际上在RTOS里,真正进入`main()`之前,系统已经完成了很多内核层面的准备工作。 - ---- - -## 二、线程不是一个函数,而是一整套运行信息 - -### 1.线程控制块就像线程的“个人档案” - -如果RT-Thread要管理很多线程,那么系统必须知道每个线程现在是什么情况。比如这个线程叫什么名字、优先级是多少、现在是运行还是挂起、线程栈在哪里、入口函数是什么、还剩多少时间片等等。 - -这些信息都需要一个地方保存,这个地方就是**线程控制块**。 - -RT-Thread中的线程控制块由`struct rt_thread`定义。课件里提到,它会保存线程的优先级、线程名称、线程状态,还会保存线程栈、入口函数、链表结构以及其他内核管理信息。 - -我觉得可以把它想成一个线程的“个人档案”: - -```text -struct rt_thread -│ -├── 线程名称 -├── 线程状态 -├── 当前优先级 -├── 初始优先级 -├── 线程入口函数 -├── 入口函数参数 -├── 线程栈地址 -├── 栈大小 -├── 时间片 -├── 定时器 -└── 内核管理链表等信息 -``` - -所以线程并不只是那段入口函数代码。入口函数只定义“这个线程要做什么”,而线程控制块才真正让操作系统能够“认识、记录和管理”这个线程。 - -另外,`struct rt_thread`继承自RT-Thread中的基础对象`rt_object`。这说明RT-Thread内部很多资源都不是零散管理的,而是通过统一的内核对象机制组织起来。线程只是其中一种对象,其他像信号量、互斥量、消息队列、定时器、设备等,也有相应的对象结构。 - -这一点让我觉得RT-Thread的内部设计其实很有体系,而不是简单地堆API。 - -### 2.线程状态不是固定的,而是在不断变化 - -今天线程状态这一部分我觉得特别重要,因为调度其实就是围绕这些状态展开的。 - -RT-Thread里主要有以下几种线程状态: - -- `RT_THREAD_INIT`:初始状态; -- `RT_THREAD_READY`:就绪状态; -- `RT_THREAD_RUNNING`:运行状态; -- `RT_THREAD_SUSPEND`:挂起状态; -- `RT_THREAD_CLOSE`:关闭状态。 - -一开始创建或者初始化线程以后,线程还不能直接运行,而是处于初始状态。调用`rt_thread_startup()`之后,线程才进入就绪状态,开始参加调度。 - -如果调度器选中了它,它就从就绪态进入运行态。运行过程中,如果调用了延时函数、等待某个资源,或者主动挂起,它就会进入挂起状态。等延时时间到了或者等待条件满足以后,它又会重新回到就绪状态。 - -最后如果线程执行结束,就会进入关闭状态,不再参与调度。 - -我觉得最值得记住的主线就是: - -```text -创建/初始化 - ↓ -初始态 - ↓ rt_thread_startup() -就绪态 - ↓ 调度器选中 -运行态 - ↓ 延时/等待资源 -挂起态 - ↓ 条件满足 -就绪态 - ↓ -再次被调度 -``` - -这样一看,线程调度就没有那么抽象了。调度器实际上一直在“就绪线程”里选择最应该运行的线程,而挂起状态的线程暂时不会参与竞争。 - -### 3.优先级、时间片和线程栈决定线程怎样运行 - -线程创建时需要设置很多参数,其中让我印象最深的三个就是:**优先级、时间片和线程栈。** - -先说优先级。RT-Thread中优先级数值越小,优先级越高,`0`是最高优先级。这个地方和生活里“数字越大级别越高”的直觉相反,所以很容易记错。 - -如果当前正在运行一个优先级为20的线程,而此时一个优先级为10的线程进入就绪状态,那么后者优先级更高,可以马上抢占CPU。 - -所以我现在可以把优先级理解成: - -> 谁更重要,谁就应该更早得到CPU。 - -不过如果两个线程优先级一样,就需要用到时间片。时间片只对**相同优先级的就绪线程**有意义。比如线程A和线程B优先级相同,A的时间片是10个OSTick,B的时间片是5个OSTick,那么系统会让它们轮流运行,每次分别执行对应的节拍数量。 - -所以可以简单记成: - -> 优先级决定谁先跑,时间片决定同优先级线程一次跑多久。 - -线程栈则是另一个很重要的资源。每个线程都有自己独立的栈空间。线程切换时,当前线程的上下文信息会保存到它自己的栈里,等以后重新运行时再恢复。函数调用过程中产生的局部变量等内容也会使用线程栈。 - -这让我意识到:线程栈不是“随便给个数字”就行。栈给得太小,复杂函数调用时可能出现栈溢出;栈给得太大,又会浪费宝贵的RAM。所以以后真正做项目时,线程栈大小应该根据实际任务复杂度去调整,而不是所有线程统一写一个很大的值。 - ---- - -## 三、从API开始真正创建一个线程 - -### 1.静态线程和动态线程,其实是在选择“内存由谁准备” - -今天开始接触线程管理API以后,我觉得线程终于从概念变成了可以实际操作的东西。 - -RT-Thread中创建线程主要有两种方式:静态线程和动态线程。 - -静态线程使用`rt_thread_init()`进行初始化。它的特点是线程控制块和线程栈都由用户自己准备。 - -例如: - -```c -static struct rt_thread thread1; -static char thread1_stack[1024]; -``` - -这里`thread1`是线程控制块,`thread1_stack`是线程运行需要的栈空间。 - -然后调用: - -```c -rt_thread_init(&thread1, - "thread1", - thread1_entry, - RT_NULL, - &thread1_stack[0], - sizeof(thread1_stack), - 20, - 10); -``` - -这些参数分别对应线程控制块、线程名称、入口函数、入口参数、栈地址、栈大小、优先级和时间片。 - -静态线程让我感觉更“可控”,因为内存是在编译阶段就准备好的,不依赖运行时动态分配。 - -动态线程则使用`rt_thread_create()`: - -```c -rt_thread_t tid; - -tid = rt_thread_create("thread1", - thread1_entry, - RT_NULL, - 1024, - 20, - 10); -``` - -调用这个函数后,RT-Thread会从动态堆中为线程控制块和线程栈申请空间。 - -所以我现在对这两种方式有一个比较简单的记法: - -> `rt_thread_init()`:内存我准备,系统帮我初始化。 -> `rt_thread_create()`:内存也由系统帮我申请。 - -静态和动态并没有绝对的好坏,更多是看实际项目中对内存、确定性和使用方便程度的要求。 - -### 2.创建成功不代表已经运行,还需要`rt_thread_startup()` - -这一点是今天特别需要注意的地方。 - -无论使用`rt_thread_init()`还是`rt_thread_create()`,线程刚创建完成以后都只是处于**初始状态**,还没有真正进入调度器的就绪队列。 - -还需要调用: - -```c -rt_thread_startup(thread); -``` - -或者动态线程中: - -```c -rt_thread_startup(tid); -``` - -线程才会变成就绪状态。 - -因此一个线程真正跑起来的过程应该是: - -```text -准备线程入口函数 - ↓ -准备线程控制块和线程栈(静态线程) - ↓ -rt_thread_init() -或 -rt_thread_create() - ↓ -线程处于初始态 - ↓ -rt_thread_startup() - ↓ -线程进入就绪队列 - ↓ -调度器进行调度 - ↓ -线程开始运行 -``` - -这个过程让我对“创建”和“启动”两个概念分得更清楚了。 - -更有意思的是,如果新启动的线程优先级比当前线程更高,那么它进入就绪态后,调度器可能马上发生切换,让这个新线程立即运行。也就是说,`rt_thread_startup()`之后程序的实际执行顺序,已经不完全由代码书写顺序决定,而是开始受调度器控制。 - -这也是RTOS和裸机程序非常明显的区别。 - -### 3.线程不仅要会“抢CPU”,也要会主动让出CPU - -今天最后讲到线程延时时,我觉得这个概念把前面的调度逻辑补完整了。 - -如果一个线程一直在下面这样的死循环里工作: - -```c -while (1) -{ - do_something(); -} -``` - -并且里面没有阻塞、延时或等待,那么它可能会一直保持就绪甚至运行状态,不断占用CPU。 - -在抢占式系统里,一个设计合理的线程应该在暂时没有事情需要处理时,把CPU让给其他线程。 - -RT-Thread可以使用: - -```c -rt_thread_delay(10); -``` - -这里的延时单位是OSTick。 - -也可以使用更容易理解的毫秒接口: - -```c -rt_thread_mdelay(500); -``` - -线程调用延时以后,会暂时进入挂起状态。此时它不再参与调度,CPU可以去执行其他就绪线程。等时间到了以后,它再重新回到就绪状态,等待调度器安排执行。 - -因此一个比较典型的线程入口函数可以写成: - -```c -static void thread1_entry(void *parameter) -{ - while (1) - { - rt_kprintf("thread1 running\n"); - - /* 当前任务完成,暂时让出CPU */ - rt_thread_mdelay(500); - } -} -``` - -以前看到`delay`时,我第一反应只是“让程序停一会儿”。但在RTOS里,它的意义其实完全不同: - -> 它不仅是延时,更重要的是让当前线程暂时退出CPU竞争,把执行机会交给其他线程。 - -从这个角度看,线程的运行其实一直是在“运行—等待—再次运行”之间循环,而不是一个线程永远霸占处理器。 - -在这次实际练习里,我把前面这些线程状态和调度概念放到了`thread_study`示例中观察。相比只看API说明,直接从日志里看两个线程的计数、状态变化和挂起/恢复过程,会更容易理解线程到底是怎么被调度器管理的。 - -首先把这个实验命令导出到了msh中。在msh帮助列表里可以看到新增的: - -```text -thread_study - start thread study example -``` - -这样就不需要每次都在`main()`中固定调用实验代码,而是可以在系统启动后通过msh手动执行`thread_study`。我觉得这种方式很适合做RT-Thread学习实验:系统正常启动以后,需要测试哪一部分功能,就在msh里运行对应命令,调试过程会灵活很多。 - -![msh导出界面](./figures/msh_export.png) - -*图:msh命令列表中已经出现`thread_study`,说明实验命令成功导出到RT-Thread Shell。* - -在正常运行`thread_study`时,`th_led1`和`th_led2`都会持续执行。日志里可以看到两个线程的`count`不断增加,其中`th_led1`还打印了系统`tick`。同时程序会周期性读取线程状态和剩余栈空间,可以看到`stack_free=776`。截图中偶尔显示`state=SUSPEND`并不代表线程被永久挂起,因为线程入口中本身存在延时操作,监测线程状态的时刻刚好可能落在线程延时期间。 - -![thread_study运行过程](./figures/thread_study_run.png) - -*图:`thread_study`正常运行过程,可以观察两个线程的计数、tick以及线程状态和剩余栈空间。* - -接着尝试主动挂起`th_led1`。从日志可以看到程序先输出`request th_led1 suspend`,随后`th_led1`进入`SUSPEND`状态,而`th_led2`仍然处于`READY`并继续增加计数。此时最直观的现象就是`th_led1`的计数停止,而`th_led2`仍然正常运行。这让我第一次比较直观地看到:**挂起一个线程并不是整个系统停下来,而只是让这个线程暂时退出调度,其他就绪线程仍然可以继续获得CPU。** - -![led1_th挂起](./figures/led1_suspend.png) - -*图:主动挂起`th_led1`后,`th_led1`保持`SUSPEND`,而`th_led2`继续执行。* - -之后再恢复`th_led1`。日志中可以看到`request th_led1 resume`以及`th_led1 resume`,紧接着`th_led1 count`重新开始增加,说明它重新具备了被调度执行的条件。后面状态监测再次出现`SUSPEND`,这里同样需要结合线程本身的延时逻辑来看:恢复操作已经成功,只是线程运行后又进入了正常的延时等待阶段,所以抓取状态时仍可能看到`SUSPEND`。 - -![led1_th恢复](./figures/led1_resume.png) - -*图:恢复`th_led1`后,其计数重新增长,说明线程重新进入正常调度流程。* - ---- - -## 今天学习后的整体理解:再和以前用过的系统对照一下 - -今天把RT-Thread线程这一套逻辑学完以后,我很自然地想到了之前接触过的FreeRTOS、ChibiOS,还有Linux里的线程。以前用这些系统的时候,很多API更多是“照着例程会用”,但没有特别认真地把它们放到一起比较。现在再回头看,会发现它们虽然都有“任务”“线程”“优先级”“调度”“延时”这些概念,但背后的定位和使用习惯其实差别很明显。 - -这部分不是课件原本的内容,而是我在今天RT-Thread线程培训的基础上,结合RT-Thread、FreeRTOS、ChibiOS官方文档以及Linux线程文档做的一次对照整理。对我来说,这种横向比较反而更容易理解RT-Thread为什么要这样设计。 - -### 1.和FreeRTOS相比:很多概念很熟悉,但RT-Thread更像一个完整的“小型操作系统” - -FreeRTOS应该是我最容易拿来和RT-Thread对比的一个系统,因为两者都是面向嵌入式场景的RTOS,而且最核心的多任务思路很接近。 - -在FreeRTOS里通常把运行实体叫做`Task`,而RT-Thread直接叫`Thread`。名字不同,但从最基本的使用角度看,它们其实非常相似:每个任务都有自己的栈、优先级和运行状态,调度器总是优先让更高优先级的就绪任务运行;如果多个任务处于相同优先级,也可以进行时间片轮转。 - -以前写FreeRTOS时,一个很典型的创建任务方式是: - -```c -xTaskCreate(TaskEntry, - "task1", - 256, - NULL, - 3, - &task_handle); -``` - -对应到RT-Thread里,会变成类似: - -```c -rt_thread_t tid; - -tid = rt_thread_create("thread1", - thread1_entry, - RT_NULL, - 1024, - 20, - 10); - -rt_thread_startup(tid); -``` - -这两个系统放在一起看,有几个区别我觉得特别值得记。 - -**第一个区别是优先级数字的方向刚好相反。** - -在FreeRTOS里,任务优先级通常是**数字越大,优先级越高**,`0`是最低的任务优先级,空闲任务也位于最低优先级。而RT-Thread正好反过来,**数字越小,优先级越高,`0`是最高优先级**。 - -所以从FreeRTOS切到RT-Thread以后,如果还按照以前的习惯写优先级,就非常容易把高低关系写反。 - -我现在会直接记成: - -```text -FreeRTOS:数字越大 → 优先级越高 -RT-Thread:数字越小 → 优先级越高 -``` - -**第二个区别是“创建”和“启动”的关系。** - -FreeRTOS里的`xTaskCreate()`创建任务以后,会把任务加入可调度的任务列表;如果调度器已经启动,新任务满足调度条件,就可能直接开始运行。 - -而RT-Thread把这两个动作拆得更加明显: - -```text -rt_thread_create() - ↓ -线程只是被创建,处于初始态 - ↓ -rt_thread_startup() - ↓ -进入就绪队列 - ↓ -参与调度 -``` - -这种设计一开始比FreeRTOS多了一步,但我觉得它的好处是线程生命周期更直观。创建对象和让对象参与调度是两个清晰的阶段,如果需要先把若干线程都准备好,再按照自己的顺序启动,会比较容易理解。 - -**第三个区别是系统整体给我的感觉不同。** - -FreeRTOS的Kernel本身非常聚焦,最核心的内容就是任务调度、队列、信号量、事件组、软件定时器以及内存管理等。它给我的感觉更像一个非常精炼的实时内核:需要什么功能,就围绕Kernel和对应库去组合。 - -RT-Thread除了实时内核以外,还把设备模型、组件、软件包、BSP、FinSH/msh、POSIX接口、Env、menuconfig和软件包管理工具放进了同一套体系中。尤其是在今天看到`rt_object`、线程对象容器这些内容之后,我会感觉RT-Thread的风格更接近一个“小型完整操作系统”,而不仅仅是一个任务调度内核。 - -这也是我目前觉得RT-Thread相对FreeRTOS比较明显的优点之一: - -> 对于只需要几个任务、队列和中断的小型工程,FreeRTOS很直接;而当项目逐渐增加设备驱动、文件系统、网络、Shell、软件包和更多组件以后,RT-Thread提供的整体框架会让我更容易把这些东西放到同一个系统里管理。 - -当然,这并不代表FreeRTOS能力弱。FreeRTOS本身非常成熟,支持的处理器架构很多,Kernel接口也比较精炼,而且现在还有网络、SMP以及云服务相关组件。只是从我当前学习RT-Thread的角度看,RT-Thread更强调“内核+设备+组件+开发工具+软件包”的整体体验。 - -### 2.和ChibiOS相比:两者都很强调线程,但RT-Thread的组件化和工具链让我感觉更完整 - -ChibiOS和RT-Thread放在一起比较也很有意思,因为ChibiOS本身就非常强调`Thread`这个概念,而不是像FreeRTOS那样习惯称为`Task`。 - -ChibiOS/RT中的线程同样拥有自己的处理器上下文、状态以及独立栈空间,而且也支持静态线程和动态线程。系统初始化以后,也会存在主线程和空闲线程。从这些概念上看,它和今天学习的RT-Thread非常接近。 - -比如以前在ChibiOS里创建一个静态线程时,会先准备Working Area: - -```c -static THD_WORKING_AREA(waThread1, 128); -``` - -然后调用: - -```c -chThdCreateStatic(waThread1, - sizeof(waThread1), - NORMALPRIO, - Thread1, - NULL); -``` - -这里和RT-Thread静态线程的思路很像,本质上都是提前准备好线程需要的内存和栈,再交给内核管理。 - -不过两者还是有一些明显的使用差异。 - -**首先还是优先级方向。**ChibiOS常见的优先级定义中,`NORMALPRIO`位于中间,而`HIGHPRIO`对应更高的数值,整体习惯上也是**数值越大,线程优先级越高**。RT-Thread则仍然是数字越小优先级越高。 - -所以如果把三个RTOS放在一起记: - -```text -FreeRTOS:高数字 = 高优先级 -ChibiOS:高数字 = 高优先级 -RT-Thread:低数字 = 高优先级 -``` - -这个区别对实际写代码非常重要。 - -**其次是线程创建接口的风格。**ChibiOS的`chThdCreateStatic()`本身就是“创建并启动一个线程”的常用接口;而RT-Thread更明确地把`rt_thread_init()`/`rt_thread_create()`和`rt_thread_startup()`分开。RT-Thread这种方式在我看来更容易观察一个线程从“对象已经存在”到“进入调度队列”的状态变化,也更贴合今天课件里讲的初始态、就绪态这些概念。 - -ChibiOS的优势也很明显。它的RT内核非常强调小体积、高性能和严格的优先级调度,同时ChibiOS不只有RT内核,还包含HAL等一整套嵌入式组件,所以做MCU项目时,从驱动到线程都可以保持统一的ChibiOS风格。 - -而我现在觉得RT-Thread比较吸引我的地方,是它在这种完整性之外,又增加了比较明显的“软件生态”思路。比如使用`menuconfig`进行裁剪、通过Env管理工程和软件包、用`pkgs`获取组件,以及比较统一的设备框架。它还提供POSIX兼容层,这意味着以后如果需要把一些Linux/POSIX风格的代码迁移到RT-Thread上,思路会更顺一些。 - -所以从我自己的使用感觉上,可以暂时这样概括: - -> ChibiOS给我的感觉是“紧凑、高性能、RT内核和HAL结合得很紧”;RT-Thread则更像“从MCU实时内核继续向完整嵌入式操作系统扩展”,尤其适合后面还想继续接触设备框架、文件系统、网络、Shell和软件包的场景。 - -### 3.和Linux线程相比:名字都叫线程,但实际上是在两个完全不同的系统层级里工作 - -Linux线程和RT-Thread线程的对比是我觉得最有意思的一组,因为表面上它们都叫`thread`,都可以创建多个执行流,也都有线程栈、优先级、阻塞和唤醒,但它们所在的系统环境完全不同。 - -Linux用户态最常见的是POSIX Threads,也就是Pthreads: - -```c -pthread_t tid; -pthread_create(&tid, NULL, thread_entry, NULL); -``` - -一个Linux进程中可以有多个线程。这些线程共享同一个进程的数据段和堆,但每个线程有自己的栈、线程ID以及部分独立属性。Linux现在常用的NPTL实现是1:1模型,也就是说一个用户线程会对应一个内核调度实体,由Linux调度器进行调度。 - -这一点和RT-Thread最大的区别,是Linux首先有一个非常完整的**进程模型**。 - -可以简单理解为: - -```text -Linux -│ -├── 进程A -│ ├── 线程1 -│ ├── 线程2 -│ └── 线程3 -│ -└── 进程B - ├── 线程1 - └── 线程2 -``` - -而我们今天学习的标准RT-Thread里,重点是直接由内核管理线程,常见MCU场景通常没有Linux这种完整的用户进程+虚拟地址空间模型,各个线程更直接地共享系统资源和地址空间。(RT-Thread Smart带有进程和用户态机制,这里讨论的是今天培训中的标准RT-Thread线程模型。) - -另外,Linux默认线程调度也不是简单的“固定优先级最高者永远先跑”。普通Linux线程通常使用`SCHED_OTHER`这类面向公平性的分时调度策略,系统会考虑CPU使用情况,让不同任务都能够取得运行机会。Linux同时也提供`SCHED_FIFO`、`SCHED_RR`和`SCHED_DEADLINE`等实时调度策略,并且在启用`PREEMPT_RT`后可以进一步强化实时能力。 - -RT-Thread的默认思路则简单直接得多: - -```text -找到最高优先级就绪线程 - ↓ - 立即运行 - ↓ -出现更高优先级就绪线程 - ↓ - 立即抢占 -``` - -这种调度模型对嵌入式实时控制很容易推理。比如我需要一个紧急保护线程必须比日志线程更快响应,只要合理设置优先级并控制临界区,就能比较直接地分析它什么时候能够拿到CPU。 - -Linux的优势在于功能完整:进程隔离、虚拟内存、用户态和内核态、成熟的文件系统、网络协议栈、驱动框架以及庞大的用户态软件生态,这些都是普通MCU上的RTOS无法直接相比的。但这些能力也意味着系统更庞大,调度和资源管理层次更多。 - -RT-Thread的优势则正好体现在嵌入式实时场景:系统小、路径短、线程调度规则直接、和硬件联系更紧密,对RAM和Flash有限的MCU更加友好。特别是在控制类应用里,我们往往更关心“这个高优先级任务能不能及时运行”,而不是桌面系统那种“所有应用长期运行是否足够公平”。 - -还有一个让我觉得很有用的地方是,RT-Thread本身提供了POSIX接口支持,Pthreads相关接口实际上是对RT-Thread内核线程能力的封装。也就是说,以前在Linux里学习过的`pthread_create()`、mutex、condition variable等概念并没有完全浪费,它们可以帮助我理解RT-Thread里的线程和IPC;只是到了RT-Thread底层,最终还是会落到`rt_thread`以及RT-Thread自己的调度和内核对象上。 - -如果把这四套系统用一句比较粗略的话放在一起,我现在的感觉是: - -| 系统 | 我现在对它的理解 | 比较突出的特点 | -| ---------- | -------------------------------------- | ------------------------------------------------------------ | -| FreeRTOS | 精炼的嵌入式实时内核 | Kernel简单直接、任务和IPC接口清晰、适合快速构建实时任务系统 | -| ChibiOS/RT | 高性能RT内核+紧密的嵌入式组件/HAL体系 | 线程模型紧凑、调度直接、HAL结合度高 | -| RT-Thread | 从RTOS内核继续向完整嵌入式OS发展的体系 | 线程和内核对象清晰,设备框架、组件、POSIX、Env和软件包体系完整 | -| Linux | 完整的通用操作系统 | 进程隔离、虚拟内存、POSIX线程、丰富驱动和用户态生态,系统能力最完整 | - -如果只从今天学习的“线程”这一件事来看,RT-Thread并没有发明一套完全陌生的概念。它和FreeRTOS、ChibiOS一样,都离不开栈、优先级、阻塞、唤醒和调度;它和Linux的Pthreads也有很多概念上的对应关系。 - -真正让我感觉RT-Thread有特色的地方,是它把这些RTOS最核心的机制保留下来,同时又往上增加了一套比较完整的设备、组件、Shell、POSIX和软件包体系。对于我这种已经接触过FreeRTOS、ChibiOS和Linux线程的人来说,很多基础概念其实可以直接迁移过来,而需要重新适应的重点主要是RT-Thread自己的对象体系、优先级方向、线程生命周期以及开发工具链。 - ---- - -### 把今天的内容重新串起来 - - -今天这节线程培训的信息量比较大,但把内容串起来以后,我觉得RT-Thread线程其实可以用一条完整的逻辑来理解: - -```text -复杂程序 - ↓ -拆成多个独立任务 - ↓ -每个任务建立一个线程 - ↓ -线程拥有自己的控制块、栈、优先级和时间片 - ↓ -线程创建后进入初始状态 - ↓ -调用rt_thread_startup()进入就绪状态 - ↓ -调度器根据优先级选择线程运行 - ↓ -线程执行自己的入口函数 - ↓ -延时或等待时主动让出CPU - ↓ -条件满足后重新进入就绪状态 -``` - -如果只记API,很容易觉得`rt_thread_init()`、`rt_thread_create()`、`rt_thread_startup()`、`rt_thread_mdelay()`都是互相独立的函数。但今天学完之后,我发现这些API其实是在描述线程生命周期的不同阶段。 - -`rt_thread_init()`和`rt_thread_create()`负责让线程“出生”;`rt_thread_startup()`让线程正式进入调度体系;调度器决定它什么时候获得CPU;`rt_thread_mdelay()`等接口则让它在暂时不需要运行时主动退出竞争。 - -这样一来,RTOS里的多任务也没有最开始想象得那么神秘。它本质上就是不断维护线程状态、管理线程资源,再按照优先级和调度规则,让不同线程轮流使用CPU。 - -今天算是第一次真正从“会用RT-Thread工程”往“理解RT-Thread为什么能这样运行”迈了一步。后面如果继续学习线程同步、信号量、互斥量、邮箱、消息队列这些内容,我觉得它们应该都是建立在今天这个线程和调度模型之上的。