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 链表
  • 设置位后,会唤醒"所有符合条件"的任务

  • 等待位的时候,条件不满足则会休眠

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐