柔性数组与队列杂记
柔性数组
柔性数组是 C 语言(C99 标准及之后)中的一个特性,指结构体的最后一个成员是一个未知大小的数组,用于在结构体中动态分配连续的内存空间,以高效存储变长数据。
它有三个关键点:①结构体最后一个成员是大小未知的数组;
②结构体内必须包含其他成员;
③柔性数组不占结构体用空间本身的内存;
④实际使用时需要动态分配内存。
队列
在嵌入式开发中,队列(Queue) 是一种核心的数据结构,遵循 “先进先出(FIFO,First-In-First-Out)” 原则,主要用于解决任务间通信、数据缓冲、时序解耦等关键问题。由于嵌入式系统通常存在多任务(如 RTOS 环境)、资源受限、实时性要求高的特点,队列的作用尤为突出,具体可从以下 6 个核心场景展开:
1. 多任务 / 中断间的安全通信
嵌入式系统(尤其是带 RTOS 的系统,如 FreeRTOS、RT-Thread)中,任务与任务、任务与中断服务函数(ISR) 之间无法直接传递数据(易导致数据竞争、内存溢出),而队列是实现 “线程安全通信” 的标准方案。
任务间通信:例如 “传感器采集任务” 采集到温湿度数据后,无需直接调用 “数据处理任务”,只需将数据放入队列,处理任务从队列中读取数据即可。这种方式避免了任务间的直接耦合,符合 RTOS 的 “任务解耦” 设计原则。
中断与任务通信:中断服务函数(ISR)的执行时间必须极短(避免阻塞其他中断),因此 ISR 不能直接处理复杂数据(如解析串口数据、处理传感器协议)。此时 ISR 可将 “待处理数据” 快速放入队列,再由后台任务从队列中取出并处理(RTOS 通常提供
xQueueSendFromISR
等专用接口,确保中断上下文的安全性)。
示例:串口接收中断收到 1 字节数据后,立即将数据放入 “串口接收队列”,后台 “串口数据解析任务” 循环从队列中读取数据,拼接成完整的协议帧后再处理。
2. 数据缓冲与流量削峰
嵌入式系统中,数据产生速度与处理速度往往不匹配(如高速传感器、串口 / USB 等外设的数据流),若直接传递数据会导致 “数据丢失” 或 “处理任务过载”,队列可作为 “缓冲池” 平衡两者速度差。
外设数据缓冲:例如 SPI 接口的加速度传感器每秒产生 1000 组数据(每组 4 字节),而 MCU 的 “数据存储任务” 因需写入 Flash,每秒仅能处理 200 组数据。此时用队列缓存传感器数据,可避免数据因处理不及时而丢失(队列深度需根据速度差设计,如设为 800,防止缓冲溢出)。
突发流量处理:当外设(如以太网、CAN 总线)突发发送大量数据时,队列可临时 “囤积” 数据,让处理任务按自身节奏逐步处理,避免系统因瞬时高负载而崩溃(即 “削峰” 作用)。
3. 任务同步与时序解耦
嵌入式系统中,多个任务的执行时序可能存在依赖(如 “初始化任务” 完成后,“业务任务” 才能启动),或需避免 “快任务等待慢任务” 的低效场景,队列可实现灵活的任务同步。
信号同步:队列可传递 “空数据”(仅作为同步信号),例如 “按键扫描任务” 检测到按键按下后,向队列发送一个 “触发信号”,“LED 控制任务” 从队列读取到信号后,执行 LED 闪烁操作(无需传递具体数据,仅需同步事件)。
时序解耦:例如 “ADC 采样任务” 需每 10ms 采样一次,而 “数据上传任务” 需每 1 秒上传一次采样结果。若两者直接耦合,采样任务需等待上传任务完成,会破坏 10ms 的采样周期;用队列缓存每次采样结果,上传任务每秒从队列中读取 100 个数据批量上传,可完全解耦两者的时序。
4. 资源共享与冲突避免
嵌入式系统中的共享资源(如串口、Flash、LCD)若被多个任务同时访问,会导致 “资源竞争”(如串口数据错乱、Flash 写入错误)。队列可作为 “资源请求队列”,实现资源的有序分配。
示例:系统中有 “日志打印任务”“参数上传任务” 两个任务需使用串口。设计一个 “串口请求队列”,两个任务需使用串口时,将 “数据 + 操作类型” 放入队列;再创建一个 “串口管理任务”,独占串口资源,循环从队列中读取请求,按顺序处理(先处理日志打印,再处理参数上传),彻底避免资源冲突。
5. 中断服务函数的轻量化处理
中断服务函数(ISR)的核心要求是 “快进快出”,若在 ISR 中执行复杂逻辑(如数据解析、CRC 校验),会延长中断响应时间,甚至阻塞更高优先级的中断。队列可将 “复杂处理” 转移到后台任务。
示例:CAN 总线中断收到一帧数据后,ISR 仅需完成 “数据拷贝”(将 CAN 数据寄存器的值存入缓冲区),再将 “缓冲区地址” 放入队列,随后立即退出;后台 “CAN 数据解析任务” 从队列中取出地址,执行 CRC 校验、协议解析、业务逻辑处理等耗时操作,既保证了 ISR 的轻量化,又不影响数据处理的完整性。
6. 实时性保障与优先级支持
主流 RTOS(如 FreeRTOS、RTX)的队列均支持优先级继承或优先级排序,可保障高优先级任务的实时性需求。
优先级读取:当多个任务同时从一个队列读取数据时,RTOS 会优先调度 “高优先级任务”。例如 “紧急故障处理任务”(高优先级)和 “普通数据处理任务”(低优先级)都监听同一个队列,当队列中收到 “故障数据” 时,高优先级的故障任务会优先读取数据并处理,满足实时性要求。
优先级发送:部分 RTOS 的队列支持 “按优先级插入数据”(如 FreeRTOS 的
xQueueSendToFront
),例如 “紧急报警数据” 可插入队列头部,优先被处理,而 “普通状态数据” 插入队列尾部,确保紧急事件的响应速度。
嵌入式队列的关键特性(与通用队列的区别)
由于嵌入式系统的资源受限性,嵌入式场景中的队列通常具备以下适配特性:
固定大小:队列创建时需指定 “队列深度”(最大数据个数)和 “每个数据的大小”,避免动态内存分配(减少内存碎片)。
阻塞机制:任务从空队列读取数据时,可设置 “阻塞时间”(如等待 100ms),期间任务进入阻塞态,不占用 CPU 资源;队列有数据后,任务自动唤醒,提升系统效率。
中断安全:提供 ISR 专用接口(如 FreeRTOS 的
xQueueSendFromISR
、RT-Thread 的rt_queue_send_from_isr
),确保在中断上下文操作队列时不会破坏数据结构。
总结
队列是嵌入式开发中的 “通信与缓冲核心”,其本质是通过FIFO 规则实现 “数据 / 事件的有序传递”,解决了多任务 / 中断间的通信、数据缓冲、时序解耦、资源冲突等核心痛点。在 RTOS 驱动开发、外设管理、业务逻辑设计中,队列几乎是不可或缺的工具,直接影响嵌入式系统的稳定性、实时性和可维护性。
代码实例:
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <stdbool.h>
#include <windows.h> typedef int qDataType;typedef struct queueNode {int data;struct queueNode *next;
}qNode;typedef struct {qNode *head;qNode *tail;int size;
}queue;void queueInit(queue *q)
{q->head = NULL;q->tail = NULL;q->size = 0;
}bool queueEmpty(queue *q)
{if (q->size == 0){return true;}else {return false;}
}void queueDestroy(queue *q)
{while (q->size != 0){qNode* tmp = q->head;q->head->next = q->head;free(tmp);q->size--;}
}int getQueueSize(queue *q)
{assert(q);return q->size;
}qDataType getQueueHead(queue *q)
{assert(q);assert(!queueEmpty(q));return q->head->data;
}qDataType getQueueTail(queue *q)
{assert(q);assert(!queueEmpty(q));return q->tail->data;
}void queuePush(queue *q, qDataType iData)
{assert(q);qNode* newNode = (qNode*)malloc(sizeof(qNode));if (newNode==NULL){printf("malloc failed\n");return;}else {newNode->data = iData;newNode->next = NULL;if (queueEmpty(q)){q->head = newNode;q->tail = newNode;}else {q->tail->next = newNode;q->tail = q->tail->next;}q->size++;}
}void queuePop(queue *q)
{assert(q);assert(!queueEmpty(q));if (q->size=1&&(q->head)==(q->tail)){qNode* tmp = q->head;q->head = NULL;q->tail = NULL;free(tmp);}else {qNode* tmp = q->head;q->head = q->head->next;free(tmp);}q->size--;
}
void qPrintFromHead(queue *q)
{if (q == NULL || q->head == NULL) {printf("队列是空的,无数据可输出\n");return;}qNode* current = q->head; printf("队列从头至尾的输出:");while (current != NULL) {printf("%d ", current->data); current = current->next; }printf("\n");
}int main()
{queue* qPTR = (queue*)malloc(sizeof(queue));queueInit(qPTR);for (int i = 0; i < 5; i++) {queuePush(qPTR, i);}qPrintFromHead(qPTR);Sleep(1000);queuePop(qPTR);qPrintFromHead(qPTR);return 0;
}