

在智慧档案馆、机房动环等场景中,一个以太网温湿度传感器往往同时扮演着两个角色:
这带来了嵌入式软件开发中的一个经典问题:当 Modbus TCP 正在读取传感器数据缓冲区时,SNMP Trap 任务是否允许打断并改写同一块数据?
如果处理不当,就会出现“脏读”(Dirty Read)——SCADA 读到一个正在被修改的半更新数据,或者 SNMP Trap 发出一个已经被覆盖的旧值。本文深入剖析这一并发冲突,并探讨嵌入式环境下的锁机制设计。
假设传感器内部有一个全局结构体 env_data,用于存储最新的环境参数:
typedef struct {
int32_t temperature; // 温度,放大10倍存储
int32_t humidity; // 湿度,放大10倍存储
int32_t dew_point; // 露点
} env_data_t;read_holding_registers()。它需要读取 temperature 和 humidity,并将其填入 TCP 响应报文。 temperature 和 humidity,填入 Trap PDU。 env_data。 在 32 位 MCU 上,读取 64 位结构体(或连续的两个 32 位变量)通常不是原子操作。
temperature(新值:256,代表 25.6℃)。 env_data:temperature 变为 258,humidity 变为 550。 humidity(新值:550,代表 55.0% RH)。 256(新温)和 550(新湿)组合发送给 SCADA。 看起来没问题?错! 如果 Task C 更新到一半被打断呢?
temperature = 258。 temperature(新值:258)。 humidity(旧值:520,代表 52.0% RH,因为 Task C 还没来得及更新它)。 针对上述问题,嵌入式系统(RTOS 环境,如 FreeRTOS、RT-Thread)通常采用以下几种同步机制。
原理:创建一个全局互斥量(Mutex)。Task A 和 Task B 在读取 env_data 前必须先 take Mutex,读完 give Mutex。Task C 在更新前也必须 take Mutex。
// 伪代码
void modbus_read_callback() {
xSemaphoreTake(xEnvDataMutex, portMAX_DELAY);
// 安全地读取 env_data
response.temp = env_data.temperature;
response.humi = env_data.humidity;
xSemaphoreGive(xEnvDataMutex);
}
void sensor_update_task() {
xSemaphoreTake(xEnvDataMutex, portMAX_DELAY);
// 安全地更新 env_data
env_data.temperature = new_temp;
env_data.humidity = new_humi;
xSemaphoreGive(xEnvDataMutex);
}优点:实现简单,逻辑清晰,保证数据一致性。
缺点:
原理:在读取/更新共享数据时,关闭全局中断或调度器。
void modbus_read_callback() {
taskENTER_CRITICAL();
// 原子操作
local_temp = env_data.temperature;
local_humi = env_data.humidity;
taskEXIT_CRITICAL();
// 后续处理
}优点:绝对原子性,无优先级反转问题。
缺点:严重增加中断延迟。如果 env_data 很大或处理复杂,关中断时间过长会影响系统实时性(如 POE 供电的脉冲检测)。仅适用于极短的操作。
原理:设立两个缓冲区 Buffer_A 和 Buffer_B。
env_data_t buffer[2];
volatile uint8_t read_idx = 0;
volatile uint8_t write_idx = 1;
void sensor_update_task() {
// 写入后台
buffer[write_idx].temperature = new_temp;
buffer[write_idx].humidity = new_humi;
// 交换角色(原子操作)
int temp = read_idx;
read_idx = write_idx;
write_idx = temp;
}
void modbus_read_callback() {
// 读取前台(无需锁,因为只读,且交换指针是原子操作)
local_temp = buffer[read_idx].temperature;
local_humi = buffer[read_idx].humidity;
}优点:读写完全分离,无锁,无阻塞,性能极高。
缺点:稍微增加内存占用;数据有一帧的延迟(读到的总是上一帧的数据,但在温湿度监测中完全可接受)。
原理:在结构体中增加一个 seq 字段。
seq (V1)。 seq (V2)。 优点:无锁,实现简单。
缺点:在竞争激烈时可能导致多次重试;对 32 位对齐有要求。
结合温湿度传感器的特性(数据量小、变化慢、允许微秒级延迟),推荐采用 “双缓冲区 + 原子指针交换” 的方案。
env_data_t 实例。 volatile env_data_t* current_ptr,指向当前有效的数据块(供 Modbus/SNMP 读取)。 next_ptr(非当前指针)写入新数据。 current_ptr 指向 next_ptr。 current_ptr 指向的数据。 SNMP Trap 通常是基于 UDP 的,且由 Agent 进程触发。在嵌入式系统中,Trap 的生成往往涉及:
关键点:Trap 中携带的 sysUpTime 和变量绑定值(OID 对应的值)必须是一致的瞬间状态。
recv 回调),调用了 xSemaphoreTake(Mutex)。网络栈回调可能运行在中断上下文或特殊任务中,不允许阻塞。 在以太网温湿度传感器这种资源受限、多协议并发的嵌入式设备中,设计并发控制机制时,请遵循以下原则:
最终推荐方案:
采用双缓冲区(Double Buffer)+ 原子指针交换。 Modbus TCP 和 SNMP Trap 始终读取当前活跃缓冲区,传感器采样始终写入后台缓冲区。指针交换瞬间完成,确保两个协议看到的数据永远是某一时刻的完整快照。
这种设计不仅解决了并发冲突,还最大程度地降低了协议栈之间的耦合度,提升了系统的稳定性和实时性。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。