FreeRTOS内部机制(二)
TaskA与TaskC需要用到信号量,而TaskB却不需要,因为某种原因,Task C最先运行,当TaskA准备就绪时,因为它无法获取信号量便阻塞在那里,此时TaskB也准备就绪,因为TaskB的优先级大于TaskC, 所以TaskB开始运行。写队列基本流程与读队列基本流程大致相似,我们在此不做过多赘述,而是讨论如下问题:一个任务写队列时,如果队列已经满了,它会被挂起,何时被唤醒?在下列的图示中,
文章目录
FreeRTOS内部机制(二)
3.队列(Queue)

3.1队列的核心:关中断、环形缓冲区、链表
3.1.1怎么互斥访问数据
简单粗暴:关中断!!!

3.1.2 怎么传递数据?
使用环形缓冲区

注意:pcTail指向队列存储区域的最后一个字节的下一个字节
3.1.3 怎么休眠/唤醒
有两个链表:
- 写队列不成功而挂起
- 读队列不成功而挂起

3.2操作示例
3.2.1创建队列
创建流程:
xQueueGenericCreate
/*1.计算环形缓冲区大小*/
xQueueSizeInBytes = ( size_t )( uxQueueLength * uxItemSize );
/*2.申请空间:环形缓冲区大小+sizeof(Queue_t)*/
pxNewQueue=(Queue_t*) pvPortMalloc( sizeof( Queue_t )+xQueueSizeInBytes);
->prvInitialiseNewQueue
/*初始化队列成员*/
pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
pxNewQueue->uxLength = uxQueueLength;
...
图示:

问:为什么pcReadFrom指向最后一个元素?
答:在xQueueReceive()函数中,pcReadFrom先向后移,如果出界再回绕,最后读!
prvCopyDataFromQueue
->prvCopyDataFromQueue
pxQueue->u.pcReadFrom += pxQueue->uxItemSize;//先移动
if( pxQueue->u.pcReadFrom >= pxQueue->pcTail )
{
pxQueue->u.pcReadFrom = pxQueue->pcHead;
}
3.2.2读队列
读队列基本流程:
taskENTER_CRITICAL();//关中断
/*环形缓冲区内有数据*/
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/*拷贝数据*/
prvCopyDataFromQueue( pxQueue, pvBuffer );
/*有效数据个数减1*/
pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;
/*唤醒向队列发送数据的任务*/
xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToSend));
/*开中断并返回成功*/
taskEXIT_CRITICAL();
return pdPASS;
}
/*环形缓冲区内没数据*/
else
{
/*不愿意等待,直接返回*/
if( xTicksToWait == ( TickType_t ) 0 )
{
return errQUEUE_EMPTY;
}
/*愿意等待,将自己挂入等待发送链表,然后休眠*/
else
{
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
portYIELD_WITHIN_API();
}
}
3.2.3写队列
写队列基本流程与读队列基本流程大致相似,我们在此不做过多赘述,而是讨论如下问题:一个任务写队列时,如果队列已经满了,它会被挂起,何时被唤醒?
-
超时:
- 任务写队列不成功时,它会被挂起:从ready list移到delayed list中
- 在delayed list中,按照"超时时间"排序
- 系统Tick中断不断发生,在Tick中断里判断delayed list中的任务时间到没?时间到后就唤醒它

-
别的任务读队列:

4.信号量(semaphore)

-
信号量就是特殊的队列
-
队列里使用环形缓冲区存放数据
-
信号量里只记录计数值
4.1优先级反转
我们假设一个场景:现在有三个任务,分别是TaskA, TaskB, TaskC,他们的优先级情况是Priority A>PriorityB>PriorityC;TaskA与TaskC需要用到信号量,而TaskB却不需要,因为某种原因,Task C最先运行,当TaskA准备就绪时,因为它无法获取信号量便阻塞在那里,此时TaskB也准备就绪,因为TaskB的优先级大于TaskC, 所以TaskB开始运行。此时就出现了优先级反转的情况—中等优先级的任务反而先运行完,最高优先级的任务无法得到实时性保障,这违反了“高优先级任务应优先于低优先级任务执行”的实时性原则

5. 互斥量(mutex)

-
互斥量就是特殊的队列
-
互斥量更是特殊的信号量
-
互斥量实现了优先级继承
5.1优先级继承
互斥量实现了优先级继承,解决了信号量优先级翻转的问题
优先级继承 (Priority Inheritance) 是实时操作系统(RTOS)中解决优先级反转问题的核心算法机制;当高优先级任务试图获取一个被低优先级任务持有的互斥量(Mutex)时,内核会临时提升低优先级任务的优先级,使其与等待该锁的最高优先级任务相同
目的:防止中等优先级任务抢占低优先级任务,从而让低优先级任务能尽快执行完临界区代码、释放锁,最终让高优先级任务尽早运行
在下列的图示中,L任务代表低优先级任务、M任务代表中优先级任务、H任务代表高优先级任务

6. 事件组(event group)
学校组织秋游,组长在等待:
- 张三:我到了
- 李四:我到了
- 王五:我到了
- 组长说:好,大家都到齐了,出发!
秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的
在这个日常生活场景中:
- 出发:要等待这3个人都到齐,他们是"与"的关系
- 交报告:只需等待这3人中的任何一个,他们是"或"的关系
在FreeRTOS中,可以使用事件组(event group)来解决这些问题

6.1事件组核心:关调度器、位操作、链表

6.1.1 怎么互斥访问数据?
简单粗暴:关调度器

问:为什么在队列、信号量和互斥量中是关中断,而事件组是关调度器?
先说结论:在xEventGroupSetBitsFromISR()函数中并不会直接设置标志位,而是写队列唤醒一个“守护任务”,该任务读到数据后会写标志位,因此:标志位是在任务中被设置的,所以只需要关闭调度器,下面我们在源码中进行证明:

6.1.2 位操作
- 设置位

- 等待位

6.1.3 链表
-
设置位后,会唤醒"所有符合条件"的任务
-
等待位的时候,条件不满足则会休眠
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)