C语言从句柄到对象 (七) —— 给对象加把锁:RTOS 环境下的并发安全

前言: 在《抛弃 Malloc》一文中,我们设计了一个静态对象池:

Motor_Handle Motor_Create(void) {

for (int i = 0; i < MAX; i++) {

if (pool[i].is_allocated == 0) { // A

pool[i].is_allocated = 1; // B

return i;

}

}

}

这段代码在裸机(单线程)环境下是完美的。 但在 RTOS(多任务)环境下,它就是一个 "定时炸弹"

假设 任务 A 刚执行完语句 A(判断未占用),还没来得及执行 B(标记占用),就被高优先级的 任务 B 打断了。 任务 B 也执行了 Motor_Create,它发现该位置也是空的,于是占用了它,拿到了句柄 0。 任务 B 执行完,切回任务 A。任务 A 继续执行语句 B,也占用了位置 0,拿到了句柄 0

后果:两个任务拿到了同一个句柄,操作同一个内存块,系统逻辑彻底错乱。

这就是经典的 竞态条件 (Race Condition)


一、 保护资源分配:临界区 (Critical Section)

我们要保护的第一个地方,是 "分配资源" 的过程。 在扫描池子的时候,绝对不允许任何人打断。

1.1 改造 Create 函数

我们需要引入 RTOS 的 临界区保护(关中断或调度锁)。

// motor_driver.c

Motor_Handle Motor_Create(void) {

Motor_Handle h = MOTOR_INVALID_HANDLE;

// 【加锁】进入临界区 (关中断)

// 不同的 RTOS 写法不同,这里用伪代码

OS_ENTER_CRITICAL();

for (int i = 0; i < MAX_MOTORS; i++) {

if (motor_pool[i].is_allocated == 0) {

motor_pool[i].is_allocated = 1;

h = (Motor_Handle)i;

break; // 找到了,赶紧退出循环

}

}

// 【解锁】退出临界区 (开中断)

OS_EXIT_CRITICAL();

return h;

}

加上这两行代码,你的静态内存池就变成了 "线程安全 (Thread-Safe)" 的了。


二、 保护内部数据:对象锁 (Mutex)

仅仅保护分配是不够的。 假设任务 A 正在调用 Motor_SetSpeed 修改参数,刚改了一半(比如改了频率,还没改占空比),任务 B 插进来读取了状态。这时候任务 B 读到的就是 脏数据

我们需要给 每一个对象 配一把锁。

2.1 在结构体中引入互斥锁

// motor_driver.c (内部定义)

struct Motor_t {

uint8_t is_allocated;

// ... 业务数据 ...

// 【新增】每个对象一把互斥锁

OS_Mutex_t lock;

};

2.2 在 Create 时初始化锁

Motor_Handle Motor_Create(void) {

// ... 分配逻辑 ...

if (found) {

// 创建互斥锁

OS_Mutex_Create(&motor_pool[i].lock);

return i;

}

}

2.3 在 API 中自动加锁

这是架构设计的精髓:不要让用户去加锁,驱动要在内部自动处理。

int Motor_SetSpeed(Motor_Handle h, uint8_t speed) {

// 1. 校验句柄

if (h >= MAX_MOTORS || !motor_pool[h].is_allocated) return ERROR;

struct Motor_t *p = &motor_pool[h];

// 2. 【自动加锁】等待拿到锁,防止别人同时修改

OS_Mutex_Take(p->lock, TIMEOUT_FOREVER);

// 3. 修改数据 (临界区)

p->current_speed = speed;

HAL_PWM_Set(p->port, speed);

// 4. 【自动解锁】

OS_Mutex_Give(p->lock);

return SUCCESS;

}

三、 总结

通过引入 全局临界区 (保护池子)和 对象互斥锁 (保护个例),我们将一个"裸机驱动"升级为了 "工业级 RTOS 驱动"

  • 原则 1:资源分配(Create/Destroy)必须原子化。

  • 原则 2:对象状态读写必须互斥。

/*******************************************
* Description:
* 本文为作者《嵌入式开发基础与工程实践》系列文之一。
* 关注我即可订阅后续内容更新,采用异步推送机制。
* 转发本文可视为广播分发,有助于信息传播至更多节点。
*******************************************/

相关推荐
皮皮林55110 小时前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
归去_来兮11 小时前
拉格朗日插值算法原理及简单示例
算法·数据分析·拉格朗日插值
顺风尿一寸13 小时前
从 Java NIO poll 到 Linux 内核 poll:一次系统调用的完整旅程
java
程途知微13 小时前
JVM运行时数据区各区域作用与溢出原理
java
华仔啊16 小时前
为啥不用 MP 的 saveOrUpdateBatch?MySQL 一条 SQL 批量增改才是最优解
java·后端
千寻girling18 小时前
Python 是用来做 AI 人工智能 的 , 不适合开发 Web 网站 | 《Web框架》
人工智能·后端·算法
xiaoye201818 小时前
Lettuce连接模型、命令执行、Pipeline 浅析
java
颜酱21 小时前
一步步实现字符串计算器:从「转整数」到「带括号与优化」
javascript·后端·算法
beata21 小时前
Java基础-18:Java开发中的常用设计模式:深入解析与实战应用
java·后端
Seven971 天前
剑指offer-81、⼆叉搜索树的最近公共祖先
java