news 2026/9/18 6:20:50

uC/OS-II 事件控制块与信号量源码解析:RTOS 同步公共地基

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
uC/OS-II 事件控制块与信号量源码解析:RTOS 同步公共地基

前阵子帮朋友看一块工业采集板,两个任务定时往同一片 SPI Flash 写日志,代码里用一个全局volatile标志当锁,跑起来偶尔还是写花。翻 uC/OS-II 内核源码的时候我就想,这类问题其实在本系列第 6 篇要讲的这块代码里早就给出答案了——今天的主角是事件控制块(OS_EVENT),以及它最典型的使用者:信号量。整套 uC/OS-II 内核源码加起来 6736 行左右,其中真正撑起所有同步机制的公共地基,就是os_core.c里那几百行事件操作代码。读透它,信号量、互斥量、消息邮箱、消息队列、事件标志组这五样东西基本就是同一套骨架换皮。这篇适合两类人:一是在用 RTOS 做项目、想知道OSSemPend背后到底发生了什么的嵌入式工程师;二是准备 RTOS 面试题、被问"信号量怎么实现的"答不利索的同学。我会把结构体逐字段拆开、把等待唤醒链路一步步走完、再给出移植到 GD32F103 上实测的数据,代码版本以 V2.92 为准。

1. 为什么第 6 篇要专门啃事件控制块这块"公共地基"

1.1 先看清 6736 行代码是怎么分布的

很多人读 RTOS 源码的习惯是从OSStart()一路顺着往下读,读到调度就卡住了,因为调度后面会不断跳到os_sem.cos_q.cos_flag.c,每个文件里都有一堆看着差不多的等待表操作。我第一次读的时候也绕了很久,后来把文件行数统计出来贴在屏幕边上,思路一下就清楚了。

文件大致行数职责
os_core.c约 1500初始化、调度器、就绪表、事件控制块公共操作
os_task.c约 1000任务创建、删除、挂起、恢复
os_tmr.c约 600软件定时器
os_q.c约 600消息队列
os_flag.c约 500事件标志组
os_time.c约 300延时、时钟节拍
os_mutex.c约 300互斥量、优先级继承
os_mem.c约 300内存分区管理
os_sem.c约 200信号量
os_mbox.c约 200消息邮箱

加起来 6700 行上下,跟我标题里说的 6736 行基本吻合(不同版本、不同编译选项下会有几十行浮动,我这份开了OS_EVENT_NAME_ENOS_TASK_NAME_EN,关掉大概能少 100 行左右)。

关键发现是:os_sem.c只有 200 行,os_mbox.c也只有 200 行,而这 200 行里真正属于"信号量独有"的逻辑可能只有 40 行,剩下的全是参数校验和调用os_core.c里的公共函数。这就是为什么我说事件控制块是公共地基——它不在os_sem.c里,也不在os_q.c里,它在os_core.c里,被所有同步对象共用。

1.2 那个全局标志为什么会失效

回到开头那个 SPI Flash 的例子。原来的代码大概长这样:

volatile uint8_t flash_busy = 0; void task_log(void *p_arg) { for (;;) { if (flash_busy == 0) { /* 检查 */ flash_busy = 1; /* 占用 */ flash_write_page(...); flash_busy = 0; /* 释放 */ } OSTimeDly(10); } }

看着挺对,问题出在"检查"和"占用"这两条语句之间。uC/OS-II 是抢占式调度,OSTimeTick每秒会打断任务几百次,只要 tick 中断正好落在第 4 行和第 5 行之间,并且中断退出时触发了任务切换,另一个任务就会看到flash_busy还是 0,于是两个任务同时进了临界区。这种 bug 不常复现,但上了产线就是灾难。

要修,要么用OS_ENTER_CRITICAL()把这两行包起来关中断,要么交给内核的信号量。前者的问题是关中断时间不可控,Flash 写一页要好几百微秒,全局关中断这么久,整个系统的实时性就废了。所以正确答案是信号量:让它去管"谁在等、谁被唤醒、计数怎么变"。

1.3 先划边界:ECB 不做哪些事

读源码最怕的就是把它的职责想多了。事件控制块这块代码,我总结下来明确"不干"的三件事:

  • 它不保护你的数据OSSemPend关中断保护的只是等待表和计数器,你 pend 之后操作的那个全局变量、那片 Flash,还是得你自己保证只有一个任务在碰。
  • 它不做优先级继承。信号量就是纯粹的计数和排队,谁先来谁先得。优先级继承是os_mutex.cOSMutexPend里额外加的一段代码,跟 ECB 本体无关。
  • 它不可重入、不递归。同一个任务连续 pend 两次同一个信号量,第二次会把自己挂上去,然后你就等着看它永远阻塞吧。这是新手最常见的死锁来源之一。

把这三条边界记住,后面看代码会顺很多:ECB 只负责一件事——维护一张"谁在等这个事件"的优先

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 6:20:41

JSP流浪动物救助系统:从源码部署到避坑实践全解析

先说个真实场景:前阵子有个学弟拿着这套JSP流浪动物救助系统源码来找我,说代码能打开、数据库也导进去了,可首页就是白屏,他自己调了一晚上也没头绪。我扫了几眼就发现是数据库连接串里的时区参数没加,MySQL 8.0版本下…

作者头像 李华
网站建设 2026/9/18 6:20:25

Security-101 应用安全关键概念(AppSec Key Concepts)入门指南

Security-101 应用安全关键概念(AppSec Key Concepts)入门指南 【免费下载链接】Security-101 8 Lessons, Kick-start Your Cybersecurity Learning. 项目地址: https://gitcode.com/GitHub_Trending/se/Security-101 应用安全(Applic…

作者头像 李华
网站建设 2026/9/18 6:18:51

嵌入式开发面试新趋势:从Linux内核到AI部署的全栈技能盘点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 6:18:10

oh-my-hermes:为消息队列开发体验而生的命令行工具集

先说我自己的感受:消息队列这个东西,项目一多、环境一杂,真的会把人逼疯。每个服务都得配连接参数,本地测一套、测试环境一套、线上又是另一套,稍不注意配置文件就飘了;上了生产之后,日常查堆积…

作者头像 李华
网站建设 2026/9/18 6:17:33

华为硬件机试14套40题拆解:核心考点与备考策略

华为硬件技术工程师的校招实习机试,是很多想进大厂做硬件的同学在投完简历后遇到的第一道硬门槛。最近“华为2026届校招实习-硬件技术工程师-硬件通用/单板开发”方向的机试题,一共14套、每套40题的消息,在求职群里传得沸沸扬扬。这套题其实信…

作者头像 李华
网站建设 2026/9/18 6:16:56

自动重合闸MATLAB仿真:原理、模型搭建与波形分析

简介:电力系统自动重合闸MATLAB仿真分析文档,面向电力系统专业学生、继电保护初学者及从事输配电仿真研究的工程技术人员,用于理解单相及三相自动重合闸的工作原理、启动方式,以及基于MATLAB/Simulink的建模仿真方法。资源为1个do…

作者头像 李华