FreeRTOS学习

基于FreeRTOS Kernel V10.0.1

前后台系统 VS RTOS

在这里插入图片描述
在这里插入图片描述

不可剥夺型内核 VS 可剥夺型内核

不可剥夺型内核也叫合作型多任务内核。在这种内核中,总是优先级别高的任务最先获得CPU的使用权。为防止某个任务始终霸占CPU的使用权,这种内核要求每个任务必须能主动放弃CPU的使用权。

可剥夺型内核中,CPU总是运行多个任务中优先级别最高的那个任务,即使CPU正在运行某个低级别的任务,当有高优先级别的任务准备就绪时,该优先级别的任务就会剥夺正在运行任务的CPU使用权,从而使自己获得CPU的使用权。
由于可剥夺型内核实时性较好,所以目前大多数嵌入式实时操作系统是可剥夺型内核。

FreeRTOS中的全局变量

变量名描述
xTickCount标识运行后到现在的系统产生的tick数,每个tick发生时,xTickCount++,当xTickCount发生翻转时,pxDelayedTaskList和pxOverflowDelayedTaskList进行对调,Overflowlist变为正常的delay list
xNumOfOverflows记录了xTickCount溢出的次数
pxCurrentTCB记录现在运行的任务
pxReadyTasksLists[configMAX_PRIORITIES]列表数组表示已经就绪的任务,因为每一个优先级都有一个就绪列表,所以是个列表数组
xDelayedTaskList1延时任务列表1
xDelayedTaskList2延时任务列表2,使用两个延时任务列表的原因主要是解决tick溢出问题的
xSuspendedTaskList被暂停的任务列表
xPendingReadyList当调度器上锁之后,就绪的task就会加入到此列表中来,在调度器恢复之后,就会转到就绪态中去
pxDelayedTaskList列表指针,指向xDelayedTaskList1
pxOverflowDelayedTaskList列表指针,指向xDelayedTaskList2
xTasksWaitingTermination记录了等待空闲任务处理的自己删除自己的任务的数目。在任务删除自己的时候由于不能立刻释放自己TCB 和stack所占用的内存(前提是动态分配,而且是任务自己删除自己),就会将自己添加到这个列表中去,等待空闲任务来遍历这个列表来释放动态ram
xSchedulerRunning调度器是否已经运行(挂起的任务调度器也算在运行状态
uxSchedulerSuspended调度器暂定标志
xIdleTaskHandle表示空闲任务的句柄
uxCurrentNumberOfTasks当前的任务数目
uxTaskNumber用来记录全局任务数,为新建的任务分配一个task number
xNextTaskUnblockTime保存着下一个要取消阻塞的任务的时间点
uxPendedTicks在vTaskSuspendAll中暂停了调度器,如果这期间tick的timer发送中断,这时uxPendedTicks记录了未被处理的ticks个数,在xTaskIncrementTick函数中进行累加
xYieldPending在某种临界状态下,任务状态发生改变,需要等待从新调度
xNumOfOverflows记录tick计数翻转的次数
xNextTaskUnblockTime记录了延时列表中,第一个需要被唤醒的任务时间点
uxDeletedTasksWaitingCleanUp这个如果为0的话,空闲任务就会忽略去遍历xTasksWaitingTermination列表,只有不为0的情况下才会去释放动态内存
uxTopReadyPriority当前ready list中优先级最高的任务
xIdleTaskHandleIdleTask是任务调度器在启动时便自动创建的空闲任务,用于回收内存等操作,这个任务句柄指向IdleTask

List

数据结构

/*
 * Definition of the only type of object that a list can contain.
 */
struct xLIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< 如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值. */
	configLIST_VOLATILE TickType_t xItemValue;			/*< 正在列出的值。在大多数情况下,这用于按对列表进行排序. */
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;		/*< 指向列表中下一个ListItem_t的指针. */
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;	/*< 指向列表中前一个ListItem_t的指针. */
	void * pvOwner;										/*< 指向包含列表项目的对象(通常是TCB)的指针。因此,包含列表项目的对象与列表项目本身之间存在双向链接. */
	void * configLIST_VOLATILE pvContainer;				/*< 指向此列表项目所在列表的指针(如果有),用来记录此列表项属于哪个列表. */
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*<  <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值. */
};
typedef struct xLIST_ITEM ListItem_t;					/* 由于某种原因,lint希望将其作为两个单独的定义. */

struct xMINI_LIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< 如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值. */
	configLIST_VOLATILE TickType_t xItemValue;
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

/*
 * Definition of the type of queue used by the scheduler.
 */
typedef struct xLIST
{
	listFIRST_LIST_INTEGRITY_CHECK_VALUE				/*< 如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值. */
	volatile UBaseType_t uxNumberOfItems;               /*< 用来记录列表中列表项的数量.*/
	ListItem_t * configLIST_VOLATILE pxIndex;			/*< 它指向的地方就是列表的头,用于遍历列表, 指向由listGET_OWNER_OF_NEXT_ENTRY()调用返回的后一个列表项. */
	MiniListItem_t xListEnd;							/*< List item包含最大可能的项目值,这意味着它始终在列表的末尾,因此用作标记,它是一个迷你列表项. */
	listSECOND_LIST_INTEGRITY_CHECK_VALUE				/*< 如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值. */
} List_t;

注意

  • xItemValue则是链表进行排序时的参考值,list在不同地方引用,一般需要按照xItemValue进行排序(一般task中引用按照delay时间排序,queue中WaitToSend/Rcv列表按照优先级排序,timer中按照超时时间排序
  • xListEnd指向列表最后一项,类型为MiniListItem_t(xMINI_LIST_ITEM前面字节定义和xLIST_ITEM一致,可以直接转型为xLIST_ITEM,以读取链表的首尾元素

API

//设置列表项的所有者。列表项的所有者是包含列表项的对象(通常是TCB)。
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )		( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )
//获取列表项的所有者。列表项的所有者是包含列表项的对象(通常是TCB)。
#define listGET_LIST_ITEM_OWNER( pxListItem )	( ( pxListItem )->pvOwner )
//设置列表项的值。在大多数情况下,该值用于按降序对列表进行排序
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue )	( ( pxListItem )->xItemValue = ( xValue ) )
//检索列表项的值。该值可以表示任何东西——例如任务的优先级,或者应该解除任务阻塞的时间
#define listGET_LIST_ITEM_VALUE( pxListItem )	( ( pxListItem )->xItemValue )
//检索给定列表头部列表项的值。
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )	( ( ( pxList )->xListEnd ).pxNext->xItemValue )
//返回列表顶部的列表项。
#define listGET_HEAD_ENTRY( pxList )	( ( ( pxList )->xListEnd ).pxNext )
//返回列表顶部的列表项。
#define listGET_NEXT( pxListItem )	( ( pxListItem )->pxNext )
//返回标记列表末尾的列表项
#define listGET_END_MARKER( pxList )	( ( ListItem_t const * ) ( &( ( pxList )->xListEnd ) ) )
//确定列表是否包含任何项。只有当列表为空时,宏的值才为true
#define listLIST_IS_EMPTY( pxList )	( ( BaseType_t ) ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) )
//返回列表中项目的数量。
#define listCURRENT_LIST_LENGTH( pxList )	( ( pxList )->uxNumberOfItems )
//获取列表中下一个条目的所有者
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\
List_t * const pxConstList = ( pxList );													\
	/* Increment the index to the next item and return the item, ensuring */				\
	/* we don't return the marker used at the end of the list.  */							\
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
	{																						\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											\
}	
//获取列表中第一个条目的所有者。列表通常按项目值升序排序。
#define listGET_OWNER_OF_HEAD_ENTRY( pxList )  ( (&( ( pxList )->xListEnd ))->pxNext->pvOwner )
//检查列表项是否在列表中。列表项维护一个指向它所在列表的“容器”指针。这个宏所做的就是检查容器和列表是否匹配。
#define listIS_CONTAINED_WITHIN( pxList, pxListItem ) ( ( BaseType_t ) ( ( pxListItem )->pvContainer == ( void * ) ( pxList ) ) )
//返回列表项所属容器的指针。
#define listLIST_ITEM_CONTAINER( pxListItem ) ( ( pxListItem )->pvContainer )
//列表是否已经初始化
#define listLIST_IS_INITIALISED( pxList ) ( ( pxList )->xListEnd.xItemValue == portMAX_DELAY )
//必须在使用列表之前调用!这会初始化列表结构的所有成员,并将xListEnd项目插入到列表中,作为列表后面的标记。
PRIVILEGED_FUNCTION void vListInitialise( List_t * const pxList );
//必须在使用列表项之前调用。这将列表容器设置为null,这样项目就不会认为它已经包含在列表中。
PRIVILEGED_FUNCTION void vListInitialiseItem( ListItem_t * const pxItem );
//将列表项插入列表。项目将被插入到列表中,其位置由项目值决定。
PRIVILEGED_FUNCTION void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem );
//将列表项插入列表。该项目将被插入到一个位置,以便它将是listGET_OWNER_OF_NEXT_ENTRY多次调用返回的列表中的最后一个项目。
PRIVILEGED_FUNCTION void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem );
//从列表中删除项目。列表项有一个指向它所在列表的指针,因此只需要将列表项传递给函数。
PRIVILEGED_FUNCTION UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );

几点注意

列表与TCB里的列表项

在这里插入图片描述
状态列表项(当前任务处于什么状态,就将该状态列表项添加到状态列表中)

  • 任务就绪列表:调度器切换任务时的目的列表。每个优先级一个任务就绪列表。
  • 任务挂起就绪列表:调度器挂起时,如有任务准备就绪,只能先进任务挂起就绪列表。当调度器恢复时,任务从挂起就绪列表,移动到任务就绪列表。
  • 任务挂起列表:被suspended的任务。
  • 延时列表、溢出延时列表:当调用TaskDelay或者等待信号量有定义超时时间的,会放入延时列表。溢出延时列表,作为延时列表的扩展,当任务的延时唤醒时间溢出时,这个任务会被加入到溢出延时列表。当系统节拍计数溢出时,两个列表交换使用,原来的延时列表变为溢出延时列表,原来的溢出延时列表变为延时列表。

事件列表项(当前任务在等待什么事件,就将任务添加到事件列表中)

  • 事件(或信号量)创建队列时,里面包含两个列表: TaskWaitToSend/Recv
  • 事件列表是按照优先级存储的,因此从事件列表中移除的列表项具有最高的优先级(The event list is sorted in priority order, so the first in the list can be removed as it is known to be the highest priority)

关于pxIndex

  • 在**列表项的初始化函数(vListInitialise)**中,列表项指针pxIndex被初始化为指向列表项xListEnd;
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
  • 列表很像数据结构中的双向链表,那么可以把它看成一个环,是首尾相连的,列表中的成员pxIndex就是用来遍历列表项的,它指向的地方就是列表项的头,因此向列表尾部插入列表项,其实就是将列表项插入到pxIndex指向的列表项之前。在列表项的末尾插入函数(vListInsertEnd) 中,注意pxNewListItem插入前后pxIndex的pxPrevious指向、pxNext指向是怎么改变的(pxIndex的pxPrevious指向新插入的pxNewListItem,pxIndex的pxNext指向并未显式改变,但是被隐式改变了)(这是由于pxIndex指针的值并未改变,它指向End列表项或列表中的某个列表项,而对应列表项的pxNext指向被显示改变了,因此pxIndex的pxNext指向其实也被改变了)。正常情况下,如果只是单单调用vListInsert函数或vListInsertEnd函数,pxIndex指针的值并未更新,但是对应的pxPrevious指向、pxNext指向已经改变了。
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
pxNewListItem->pvContainer = ( void * ) pxList;

在这里插入图片描述

  • 列表项删除函数(uxListRemove)中,如果删除的列表项正好是pxIndex指向的列表项,则需要更新pxIndex的指针的值
/* Make sure the index is left pointing to a valid item. */
if( pxList->pxIndex == pxItemToRemove )
{
	pxList->pxIndex = pxItemToRemove->pxPrevious;
}
  • 在宏定义listGET_OWNER_OF_NEXT_ENTRY中*(用于从多个优先级相同的就绪任务中查找下一个要运行的任务),会更新pxIndex的指针的值
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;

列表项插入实验分析

在这里插入图片描述
在这里插入图片描述
vListInsert列表项插入时,是按照列表项的值进行升序插入的;vListInsertEnd列表项插入时,是在列表的尾部插入列表项

Task

任务特性

  • 支持抢占
  • 支持优先级
  • 每个任务都拥有堆栈导致了RAM 使用量增大
  • 如果使用抢占的话的必须仔细的考虑重入的问题

任务状态切换

在这里插入图片描述

部分函数调用图

vTaskStartScheduler函数

在这里插入图片描述

  • 任务调度就是将某个任务从一个状态切换到另一个状态,也就是将一个列表项从一个列表移除插入另一个列表的过程,其最底层的操作就是列表操作
  • 在开启调度器时,会创建一个空闲任务(空闲任务会进行删除任务的内存清理);会初始化全局变量,包括xNextTaskUnblockTime、xSchedulerRunning、xTickCount ;如果配置为使用软件定时器,会创建一个软件定时器处理任务(注意,如果想使用软件定时器,在使用软件计时器之前,必须显式地创建它
  • xPortStartScheduler函数中,会完成定时中断的配置

vTaskDelay函数、vTaskDelayUntil函数

在这里插入图片描述
在这里插入图片描述

在xTaskIncrementTick函数(会被定时中断服务函数调用、会被xTaskResumeAll函数调用)里,需要判断uxSchedulerSuspended 是否等于pdFALSE(即为0),如果等于pdFALSE,则会操作延时任务列表、就绪任务列表数组。vTaskSuspendAll中对uxSchedulerSuspended进行递增操作,当uxSchedulerSuspended变量不为0后,在xTaskIncrementTick函数里就不会操作延时任务列表、就绪任务列表数组,从而避免调度器和即将运行的函数操作发生冲突。

vTaskSuspend函数

在这里插入图片描述

vTaskResume函数

在这里插入图片描述

vTaskDelete函数

在这里插入图片描述

taskENTER_CRITICAL函数

在这里插入图片描述

部分函数流程图

xTaskCreate函数

在这里插入图片描述

prvInitialiseNewTask函数

在这里插入图片描述

prvInitialiseNewTask函数中,设置事件列表项的值为configMAX_PRIORITIES-uxPriority

prvAddNewTaskToReadyList函数

在这里插入图片描述

这个函数将NEW的task加入到就绪列表中去分为好几种情况
1.如果现在没有任何任务运行,那么这个任务机会被立即运行(其实也就是pxCurrentTCB 指向NEW的tcb),需要prvInitialiseTaskLists去初始化各个全局列表变量
2.如果不是系统中第一个任务,并且调度器还没运行,就看pxCurrentTCB 和 pxNewTCB的优先级,如果pxNewTCB优先级高的话,就会替代原来的TCB
3.如果不是系统中第一个任务,并且调度器已经开始运行,那就看现在运行的task的pxCurrentTCB 和 pxNewTCB 的优先级那个高,如果pxNewTCB的优先级高,那么就会直接导致任务切换,注意case1 \ 2 不会直接导致任务切换,仅仅是pxCurrentTCB 指针的指向发生变化而已。

prvInitialiseTaskLists函数

在这里插入图片描述

vTaskStartScheduler函数

在这里插入图片描述
在这里插入图片描述

xTaskIncrementTick函数

xTaskIncrementTick函数被定时中断复位函数和xTaskResumeAll函数调用。
在这里插入图片描述

Queue

数据结构

/*
 * Definition of the queue used by the scheduler.
 * Items are queued by copy, not reference.  See the following link for the
 * rationale: http://www.freertos.org/Embedded-RTOS-Queues.html
 */
typedef struct QueueDefinition
{
	int8_t *pcHead;					/*< 指向队列数据存储的起始位置,即第一个队列项. */
	int8_t *pcTail;					/*< 指向队列存储区结束后的下一个字节,一旦分配了多余的字节来存储队列项,这是用作标记的. */
	int8_t *pcWriteTo;				/*< 指向存储区域中空闲的下一个位置. */

	union							/* 使用union是编码标准的一个例外,以确保两个互斥的结构体成员不会同时出现(浪费RAM). */
	{
		int8_t *pcReadFrom;			/*< 指向结构用作队列时读取队列项的最后一个位置. */
		UBaseType_t uxRecursiveCallCount;/*< 在结构用作互斥锁时,维护递归互斥锁被递归地“占用”的次数的计数 */
	} u;

	List_t xTasksWaitingToSend;		/*< 等待向队列发送数据的任务列表,按照优先级顺序存储,其中的任务由于等待入队而阻塞(简称等待发送任务列表). */
	List_t xTasksWaitingToReceive;	/*< 等待从队列接收数据的任务列表,按照优先级顺序存储,其中的任务由于队列项而阻塞(简称等待接收任务列表). */

	volatile UBaseType_t uxMessagesWaiting;/*< 当前队列中的队列项数目. */
	UBaseType_t uxLength;			/*< 创建队列时指定的长度也就是中最大允许创建队列时指定的长度,也就是中最大允许队列项 (消息 )数量 . */
	UBaseType_t uxItemSize;			/*< 创建队列时指定的每个队列项时指定的每个队列项 (消息)最大长度,单位字节. */

	volatile int8_t cRxLock;		/*< 当队列上锁以后用来统计从中接收到的队列项数量,也就是出队的队列项数量,当没有上锁的话此字段为queueUNLOCKED. */
	volatile int8_t cTxLock;		/*< 当队列上锁以后用来统计发送到队列中的队列项数量,也就是入队的队列数量,当没有上锁的话此字段为queueUNLOCKED. */

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t ucStaticallyAllocated;	/*< 如果使用了静态存储的话把这个变量置为pdTURE,确保删除时使用静态方式 */
	#endif

	#if ( configUSE_QUEUE_SETS == 1 )
		struct QueueDefinition *pxQueueSetContainer;  /*< 队列集相关宏*/
	#endif

	#if ( configUSE_TRACE_FACILITY == 1 )            /*< 跟踪调试相关宏 */
		UBaseType_t uxQueueNumber;
		uint8_t ucQueueType;
	#endif

} xQUEUE;

/* The old xQUEUE name is maintained above then typedefed to the new Queue_t
name below to enable the use of older kernel aware debuggers. */
typedef xQUEUE Queue_t;

/* For internal use only.  These definitions *must* match those in queue.c. */
#define queueQUEUE_TYPE_BASE				( ( uint8_t ) 0U )   /*< 普通消息队列 */
#define queueQUEUE_TYPE_SET					( ( uint8_t ) 0U )   /*< 队列集 */
#define queueQUEUE_TYPE_MUTEX 				( ( uint8_t ) 1U )   /*< 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE	( ( uint8_t ) 2U )   /*< 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE	( ( uint8_t ) 3U )   /*< 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX		( ( uint8_t ) 4U )   /*< 递归互斥信号量 */

注意

  • xTasksWaitingToSend:如果队列存储区中尚有空间时,向队列中加入数据,可以立刻存入,但是如果队列已经满了,而任务还想向队列中存储数据,就会将队列挂接到 xTasksWaitingToSend上(前提是任务愿意等待),然后等待队列有空间时再将数据添加到队列中
  • xTasksWaitingToReceive:如果队列存储区有数据时,从队列中读取数据,可以立刻获取,但是如果队列为空,而任务还想从队列中获取数据,就会将队列挂接到 xTasksWaitingToReceive上(前提是任务愿意等待),然后等待队列有数据时再将从队列中获取数据

API

#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#define xQueueCreateStatic( uxQueueLength, uxItemSize, pucQueueStorage, pxQueueBuffer ) xQueueGenericCreateStatic( ( uxQueueLength ), ( uxItemSize ), ( pucQueueStorage ), ( pxQueueBuffer ), ( queueQUEUE_TYPE_BASE ) )

#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueOverwrite( xQueue, pvItemToQueue ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )

#define xQueueReset( xQueue ) xQueueGenericReset( xQueue, pdFALSE )

#define prvLockQueue( pxQueue )								\
	taskENTER_CRITICAL();									\
	{														\
		if( ( pxQueue )->cRxLock == queueUNLOCKED )			\
		{													\
			( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED;	\
		}													\
		if( ( pxQueue )->cTxLock == queueUNLOCKED )			\
		{													\
			( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED;	\
		}													\
	}														\
	taskEXIT_CRITICAL()
	
/*< 出队,并删除队列项*/
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
/*< 出队,并不删除队列项*/
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;

几点注意

创建完成后的初始队列

在这里插入图片描述

相比消息队列,使用全局数组存在的问题

  • 使用消息队列可以让 RTOS 内核有效地管理任务,而全局数组是无法做到的,任务的超时等机制需要用户自己去实现
  • 使用了全局数组就要防止多任务的访问冲突,而使用消息队列则处理好了这个问题,用户无需担心
  • 使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题
  • FIFO 机制更有利于数据的处理

中断方式的消息机制要注意的问题

  • 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
  • 实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
  • 中断服务程序中一定要调用专用于中断的消息队列函数,即以 FromISR 结尾的函数
  • 在操作系统中实现中断服务程序与裸机编程的区别:
    • 如果 FreeRTOS 工程的中断函数中没有调用 FreeRTOS 的消息队列 API 函数,与裸机编程是一样的
    • 如果 FreeRTOS 工程的中断函数中调用了 FreeRTOS 的消息队列的 API 函数,退出的时候要检测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换,这点与裸机编程有区别的

部分函数调用图

动态创建队列

在这里插入图片描述

队列在创建后如果再复位,由于复位队列后队列变成空的,则对于那些由于出队(从队列中读取消息)而阻塞的任务就依旧保持阻塞壮态,但是对于那些由于入队(向队列中发送消息)而阻塞的任务就不同了,这些任务要解除阻塞壮态,从队列的相应列表中移除。

xQueueGenericSend入队函数

在这里插入图片描述

  • 根据参数的不同,可以从队列尾入队、从队列首入队和覆盖式入队。覆盖式入队用于只有一个队列项的场合,入队时如果队列已满,则将之前的队列项覆盖掉

prvUnlockQueue队列解锁函数

在这里插入图片描述

xQueueReceive出队函数

在这里插入图片描述

部分函数流程图

xQueueGenericSend函数

摘自FreeRTOS高级篇5—FreeRTOS队列分析

在这里插入图片描述

Semphre

杂项

PRIVILEGED_FUNCTION

在这里插入图片描述
在这里插入图片描述

检查列表完整性

/* Macros that can be used to place known values within the list structures,
then check that the known values do not get corrupted during the execution of
the applicationI
. These may catch the lis t data structures being overwritten in
memory. They will not catch data errors caused by incorrect configuration or
use of FreeRTOS.*/

这点类似于包头包尾,在检查头部和尾部的数据为设定的值后,认为头和尾之间的数据没有被改写,是完整的。
在这里插入图片描述

mtCOVERAGE_TEST_MARKER

在这里插入图片描述

临界区

临界区是提供互斥功能的一种非常原始的实现方法,临界区的工作仅仅是简单的把中断全部关掉,当处理完临界区代码以后再打开中断;或者是关掉优先级在configMAX_SYSCALL_INTERRUPT_PRIORITY以下的中断。临界区必须具有很短的时间,否则会反过来影响中断的响应时间。

FreeRTOS与临界区代码保护有关的函数有4个,任务级临界区代码保护的有taskENTER_CRITICAL()、taskEXIT_CRITICAL(),
中断级临界区代码保护的有taskENTER_CRITICAL_FROM_ISR()、taskEXIT_CRITICAL_FROM_ISR(x)。

也可以将调度器挂起来保护临界区,vTaskSuspendAll挂起调度器、xTaskResumeAll恢复调度器,在vTaskSuspendAll和xTaskResumeAll之间不会进行任务调度,但可以响应中断,如果某个中断在调度器挂起过程中要求进行上下文切换,则这个要求也会被挂起,直到调度器被唤醒后才会得到执行。

临界区的代码如下,

/*********************************************************************/
/*< task.h*/
#define taskENTER_CRITICAL()		portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL()			portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#define taskDISABLE_INTERRUPTS()	portDISABLE_INTERRUPTS()
#define taskENABLE_INTERRUPTS()		portENABLE_INTERRUPTS()

/*********************************************************************/
/*< portmacro.h*/
/* These macros do not globally disable/enable interrupts.  They do mask off
interrupts that have a priority below configMAX_API_CALL_INTERRUPT_PRIORITY. */
#define portENTER_CRITICAL()		vPortEnterCritical();
#define portEXIT_CRITICAL()			vPortExitCritical();
#define portDISABLE_INTERRUPTS()	ulPortSetInterruptMask()
#define portENABLE_INTERRUPTS()		vPortClearInterruptMask( 0 )
#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortSetInterruptMask()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)	vPortClearInterruptMask(x)

zynq移植相关

任务优先级

在网上查资料时,在一些博文中看到configMAX_SYSCALL_INTERRUPT_PRIORITY,但它在Xilinx提供的移植中并未搜索到,最后在FreeRTOS的官网上搜索到configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY其实是等价的,

在这里插入图片描述
在这里插入图片描述

中断使能、禁能

开中断、关中断、清除中断时是对中断优先级掩码寄存器(ICCPMR) 进行操作,并没有更改中断的其他配置,使用了CPSID和CPSIE的汇编指令,中断相关的部分寄存器如下所示,
在这里插入图片描述
相关代码如下,

/*********************************************************************/
/*< port.c*/
/* In all GICs 255 can be written to the priority mask register to unmask all
(but the lowest) interrupt priority. */
#define portUNMASK_VALUE				( 0xFFUL )

/* The critical section macros only mask interrupts up to an application
determined priority level.  Sometimes it is necessary to turn interrupt off in
the CPU itself before modifying certain hardware registers. */
#define portCPU_IRQ_DISABLE()										\
	__asm volatile ( "CPSID i" ::: "memory" );						\
	__asm volatile ( "DSB" );										\
	__asm volatile ( "ISB" );

#define portCPU_IRQ_ENABLE()										\
	__asm volatile ( "CPSIE i" ::: "memory" );						\
	__asm volatile ( "DSB" );										\
	__asm volatile ( "ISB" );


/* Macro to unmask all interrupt priorities. */
#define portCLEAR_INTERRUPT_MASK()									\
{																	\
	portCPU_IRQ_DISABLE();											\
	portICCPMR_PRIORITY_MASK_REGISTER = portUNMASK_VALUE;			\
	__asm volatile (	"DSB		\n"								\
						"ISB		\n" );							\
	portCPU_IRQ_ENABLE();											\
}

内存分配算法

名称分配释放其他
首次适应算法(FirstFit)从空闲链表头开始查找空闲块,将找到的第一个满足要求(长度不小于n)的空闲块分配给用户,并将空闲块剩余空间重新组织为一个小的空闲块,插入到链表中内存释放时,查看相邻的内存块是否空闲,如果空闲则合并成一个大的内存空闲块内存浪费小,比较简单;如果频繁的分配释放,可能造成严重的内存碎片
最佳适应算法(BestFit)系统从空闲链表中查找长度和n最接近的空闲块分配给用户(这种方式下,为了避免在每次分配时都进行链表的全部遍历,会将各个空闲块按照空间大小由小到大依次组织起来)内存释放时,空闲块需要按照其大小插入到链表中最终可能导致内存中留下许多难以利用的小的空闲区
最差适应算法(WorstFit)系统从最大的空闲块中划分出n个字节分配给用户(这种方式下,通常空闲块链表会按照其空间由大到小的顺序进行组织,这样每次分配时无需查找,直接取出链表的第一个空闲块,分配其中的一部分给用户,并将剩余的空间重新组织,插入到空闲链表中的具体位置)内存释放时,空闲块需要按照其大小插入到链表中最终可能导致内存中缺少大的空闲区

行为同步

基本概念

一个任务的运行过程需要和其他任务的运行配合,才能得到预定的效果。任务之间的这种动作配合和协调关系称为“行为同步”;由于行为同步过程往往由某种条件来触发,故又称为“条件同步”。行为同步的结果体现为任务之间的运行按某种预定的顺序来进行,故又称为“顺序控制”。在每一次同步的过程中,其中一个任务(或ISR)为“控制方”,它使用操作系统提供的某种通信手段发出控制信息;另一个任务为“被控制方”,通过通信手段得到控制信息后即进入就绪状态,根据优先级高低,或者立即进入运行状态,或者随后某个时刻进入运行状态。被控制方的运行状态受到控制方发出的信息的控制,即被控制方的运行状态由控制方发出的信息来同步。

通信手段的选择

  • 当同步过程不需要传输具体内容时,可选择信号量类手段(二值信号量、计数信号量、事件标志组)
  • 当同步过程需要传输具体内容时,可选择消息类手段(消息邮箱、消息队列)
  • 当满足“任何时候同步信息的生产速度都比同步信息的消费速度慢”时,可选择简单的通信手段(二值信号量、事件标志组、消息邮箱)
  • 对于非周期性同步信息,不能保证“任何时候同步信息的生产速度都比同步信息的消费速度慢”时,可选择有缓冲功能的通信手段(计数信号量、消息队列)
  • 当同步信号为多个信号的逻辑运算结果时,采用事件标志组作为同步手段

任务同步

在这里插入图片描述

资源同步

基本概念

被两个以上并发程序单元(任务或ISR)访问的资源称为共享资源,共享资源一定是全局资源。但全局资源不一定是共享资源,那些只为一个任务(或ISR)使用的全局资源并不是共享资源,而是这个任务(或ISR)的私有资源,对自己的私有资源进行读/写操作是不受限制的。任务对共享资源进行访问的代码段落称为关键段落,各个任务访问同一共享资源的关键段落必须互斥,才能保障共享资源信息的可靠性和完整性,这种使得不同任务访问共享资源时能够确保共享资源信息可靠和完整的措施称为“资源同步”。

资源同步有关中断、关调度、使用互斥信号量和使用计数信号量几种方法,它们都能够在访问共享资源时保障共享资源信息的可靠性和完整性。

同步手段的选择

名称描述备注
关中断当参与访问共享资源的并发程序单元中包含ISR时,任务级程序单元只能用“关中断”的措施来访问共享资源关中断直接影响系统的实时性,常用于对全局变量和小规模全局数据结构的访问
关调度如果共享资源的使用者全部是任务(即不包含ISR),就可以采用关调度的方法来访问共享资源关调度后,中断没有关闭,系统能够相遇各种异步事件,但无法进行任务调度,使与该共享资源无关的任务受到影响
使用互斥信号量如果共享资源的使用者全部是任务(即不包含ISR),就可以采用关调度的方法来访问共享资源能够处理优先级翻转,对系统实时性影响小
使用计数信号量

任务划分

任务划分的目标

在这里插入图片描述

任务划分的方法

在这里插入图片描述

任务优先级的安排原则

在这里插入图片描述

参考博文链接

FreeRTOS临界区应用与总结
Zynq7000系列FreeRTOS源码分析
开关中断与cpsid/cpsie指令
Zynq + FreeRTOS interrupt problem
Using FreeRTOS on ARM Cortex-A9 Embedded Processors
记一次FreeRTOS错误配置导致无法进入临界区
FreeRTOS–疑难杂症
FreeRTOS任务调度研究

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值