柔性数组与队列杂记

发布于:2025-09-13 ⋅ 阅读:(18) ⋅ 点赞:(0)

柔性数组

柔性数组是 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),例如 “紧急报警数据” 可插入队列头部,优先被处理,而 “普通状态数据” 插入队列尾部,确保紧急事件的响应速度。

嵌入式队列的关键特性(与通用队列的区别)

由于嵌入式系统的资源受限性,嵌入式场景中的队列通常具备以下适配特性:

  1. 固定大小:队列创建时需指定 “队列深度”(最大数据个数)和 “每个数据的大小”,避免动态内存分配(减少内存碎片)。

  2. 阻塞机制:任务从空队列读取数据时,可设置 “阻塞时间”(如等待 100ms),期间任务进入阻塞态,不占用 CPU 资源;队列有数据后,任务自动唤醒,提升系统效率。

  3. 中断安全:提供 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;
}


网站公告

今日签到

点亮在社区的每一天
去签到