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

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

Motor_Handle Motor_Create(void) {

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

if (pooli.is_allocated == 0) { // A

pooli.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_pooli.is_allocated == 0) {

motor_pooli.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_pooli.lock);

return i;

}

}

2.3 在 API 中自动加锁

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

int Motor_SetSpeed(Motor_Handle h, uint8_t speed) {

// 1. 校验句柄

if (h >= MAX_MOTORS || !motor_poolh.is_allocated) return ERROR;

struct Motor_t *p = &motor_poolh;

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

相关推荐
天天进步201512 小时前
Python全栈项目--基于深度学习的图像超分辨率系统
开发语言·python·深度学习
糖果店的幽灵12 小时前
langgraph的四种state解析
java·前端·javascript·langgraph
杨充12 小时前
4.接口而非实现编程
java·后端·架构
不会就选b12 小时前
算法日常・每日刷题--<快排>4
算法
梦梦代码精12 小时前
基于ThinkPHP6 + Vue3的家政预约系统全解析:从LBS定位到自动派单的完整实现
java·docker·开源·php·代码规范
Keven_1112 小时前
算法札记:树状数组的用途
数据结构·算法
wear工程师12 小时前
@Transactional 标了却没开事务?先看这次调用有没有经过代理
java·spring
Joey_friends12 小时前
指纹authenticate流程图
android·java·c++
用户6774371758112 小时前
C++函数参数传递方式详解:string、string&、const string、const string&该怎么选?
算法
小小晓.13 小时前
C++小白记:C风格字符串和数组用法
c语言·开发语言·c++