FreeRTOS-V10 源码分析——链表(list),任务(task)

下篇:《FreeRTOS-V10 源码分析——队列(queue)》

目录

前言

为什么选择FreeRTOS?

1. 文档结构

2. 移植

3.源码分析

1)LIST

2)TASK

taskYIELD

taskENTER_CRITICAL

taskEXIT_CRITICAL

xTaskCreate

xTaskCreateStatic

vTaskDelete

vTaskDelay

vTaskDelayUntil

xTaskAbortDelay

uxTaskPriorityGet

uxTaskPriorityGetFromISR

eTaskGetState

vTaskGetInfo

vTaskPrioritySet

vTaskResume

xTaskResumeFromISR

vTaskStartScheduler

vTaskEndScheduler

vTaskSuspendAll

xTaskResumeAll

xTaskGenericNotify

xTaskGenericNotifyFromISR、vTaskNotifyGiveFromISR

xTaskNotifyWait、ulTaskNotifyTake

DEBUG API

参考资料


前言

 

为什么选择FreeRTOS?

FreeRTOS是真正的免费和受支持的,即使用于商业应用中也是如此。该FreeRTOS的开源MIT许可并不要求你暴露你的专有IP。您可以使用FreeRTOS将产品推向市场,而无需与我们联系,更不用说支付任何费用,成千上万的人这样做。如果您希望在任何时候获得额外的备份,或者您的法律团队需要额外的书面担保或赔偿,则可以使用简单的低成本商业升级途径。您可以选择在任何时候选择采用商业路线,这使您高枕无忧。

FreeRTOS 官网以及源码下载: https://www.freertos.org/

请下载源码结合源码阅读本文(文中会贴出一些关联源码的部分),本文版本FreeRTOS Kernel V10.2.1,根据.h文件定义顺序阅读源码,V10相比V9有部分改动增加一些特性。

1. 文档结构

下载源码之后目录结构如下:

FreeRTOS-V10 源码分析——链表(list),任务(task)_第1张图片

/FreeRTOS 下主要是FreeRTOS内核和例子,也就是本文的分析内容。

/FreeRTOS-labs和/FreeRTOS-Plus主要是一些搭配FreeRTOS拓展的库和例子,这里不涉及。

FreeRTOS-V10 源码分析——链表(list),任务(task)_第2张图片

/FreeRTOS/Demo下是一些已经移植好的FreeRTOS的工程示例,直接就可以使用。

/FreeRTOS/Source下是FreeRTOS的源码。

这里只有task.c, list.c, queue.c 是必要的,当然还少不了/portble,/inlcude,/portable/MemMang的对应内容。

FreeRTOS-V10 源码分析——链表(list),任务(task)_第3张图片

2. 移植

这里不想洗展开讲移植,系统主要是依靠ARM的CMSIS标准库提供的systick、pendsv、svc这三个接口实现,详细可以参考《ARM Cortex M3 与 Cortex M4 权威指南》的OS支持特性相关章节。网上移植教程也很多。

3.源码分析

1)LIST

    list 是一个带链表头信息的双向循环链表,用于任务调度器的调度任务存储和检索,也可自己新建使用,链表的数据结构如下:

/*
 * Definition of the only type of object that a list can contain.
 */
struct xLIST;
struct xLIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	configLIST_VOLATILE TickType_t xItemValue;			/*< The value being listed.  In most cases this is used to sort the list in descending order. */
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;		/*< Pointer to the next ListItem_t in the list. */
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;	/*< Pointer to the previous ListItem_t in the list. */
	void * pvOwner;										/*< Pointer to the object (normally a TCB) that contains the list item.  There is therefore a two way link between the object containing the list item and the list item itself. */
	struct xLIST * configLIST_VOLATILE pxContainer;		/*< Pointer to the list in which this list item is placed (if any). */
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
};
typedef struct xLIST_ITEM ListItem_t;					/* For some reason lint wants this as two separate definitions. */

struct xMINI_LIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 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				/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	volatile UBaseType_t uxNumberOfItems;
	ListItem_t * configLIST_VOLATILE pxIndex;			/*< Used to walk through the list.  Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */
	MiniListItem_t xListEnd;							/*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
	listSECOND_LIST_INTEGRITY_CHECK_VALUE				/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
} List_t;

ListItem_t用于存储链表的成员;MiniListItem_t用于存储链表的末尾成员是一个ListItem_t的简化结构;List_t用于存储链表头信息包含一个MiniListItem_t;链表的机构如下图所示:

FreeRTOS-V10 源码分析——链表(list),任务(task)_第4张图片

链表头和链表项在使用和插入前需要初始化,初始化结构体的初始值:

    void vListInitialise( List_t * const pxList )

    void vListInitialiseItem( ListItem_t * const pxItem )

链表插入有两种方式:

    void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )

    按照链表项的xItemValue的值大小进行升序插入;

    void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )

    直接追加到列表的末尾;

链表删除:

    UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )

    链表删除是建立在该链表项是正确插入的,将其前一项和后一项直接链接;

2)TASK

  • taskYIELD

        以Cortex-M4为例,任务的手动切换portYIELD_FROM_ISR  portYIELD_WITHIN_API  taskYIELD  portYIELD,最终调用的挂起PendSV中断,对pxCurrentTCB执行入栈出栈,进行任务调度。

#define taskYIELD() portYIELD()
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired != pdFALSE ) portYIELD()
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )
#define portYIELD_WITHIN_API portYIELD

/* Scheduler utilities. */
#define portYIELD()											\
{															\
	/* Set a PendSV to request a context switch. */			\
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;			\
	__DSB();												\
	__ISB();												\
}

/*-----------------------------------------------------------*/

xPortPendSVHandler:
	mrs r0, psp
	isb
	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB
	ldr	r2, [r3]

	/* Is the task using the FPU context?  If so, push high vfp registers. */
	tst r14, #0x10
	it eq
	vstmdbeq r0!, {s16-s31}

	/* Save the core registers. */
	stmdb r0!, {r4-r11, r14}

	/* Save the new top of stack into the first member of the TCB. */
	str r0, [r2]

	stmdb sp!, {r0, r3}
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext
	mov r0, #0
	msr basepri, r0
	ldmia sp!, {r0, r3}

	/* The first item in pxCurrentTCB is the task top of stack. */
	ldr r1, [r3]
	ldr r0, [r1]

	/* Pop the core registers. */
	ldmia r0!, {r4-r11, r14}

	/* Is the task using the FPU context?  If so, pop the high vfp registers
	too. */
	tst r14, #0x10
	it eq
	vldmiaeq r0!, {s16-s31}

	msr psp, r0
	isb
	#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
		#if WORKAROUND_PMU_CM001 == 1
			push { r14 }
			pop { pc }
		#endif
	#endif

	bx r14


/*-----------------------------------------------------------*/

以上汇编流程:入栈,进行basepri的中断屏蔽中执行vTaskSwitchContext  进行选择最高优先级就绪任务,并将对应的任务的TCB控制信息给pxCurrentTCB ,出栈,完成任务的切换;

void vTaskSwitchContext( void )
{
	if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
	{
		/* The scheduler is currently suspended - do not allow a context
		switch. */
		xYieldPending = pdTRUE;
	}
	else
	{
		xYieldPending = pdFALSE;
		traceTASK_SWITCHED_OUT();

		#if ( configGENERATE_RUN_TIME_STATS == 1 )
		{
			#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
				portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
			#else
				ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
			#endif

			/* Add the amount of time the task has been running to the
			accumulated time so far.  The time the task started running was
			stored in ulTaskSwitchedInTime.  Note that there is no overflow
			protection here so count values are only valid until the timer
			overflows.  The guard against negative values is to protect
			against suspect run time stat counter implementations - which
			are provided by the application, not the kernel. */
			if( ulTotalRunTime > ulTaskSwitchedInTime )
			{
				pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
			ulTaskSwitchedInTime = ulTotalRunTime;
		}
		#endif /* configGENERATE_RUN_TIME_STATS */

		/* Check for stack overflow, if configured. */
		taskCHECK_FOR_STACK_OVERFLOW();

		/* Before the currently running task is switched out, save its errno. */
		#if( configUSE_POSIX_ERRNO == 1 )
		{
			pxCurrentTCB->iTaskErrno = FreeRTOS_errno;
		}
		#endif

		/* Select a new task to run using either the generic C or port
		optimised asm code. */
		taskSELECT_HIGHEST_PRIORITY_TASK(); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
		traceTASK_SWITCHED_IN();

		/* After the new task is switched in, update the global errno. */
		#if( configUSE_POSIX_ERRNO == 1 )
		{
			FreeRTOS_errno = pxCurrentTCB->iTaskErrno;
		}
		#endif

		#if ( configUSE_NEWLIB_REENTRANT == 1 )
		{
			/* Switch Newlib's _impure_ptr variable to point to the _reent
			structure specific to this task. */
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif /* configUSE_NEWLIB_REENTRANT */
	}
}

/*-----------------------------------------------------------*/

	#define taskSELECT_HIGHEST_PRIORITY_TASK()														\
	{																								\
	UBaseType_t uxTopPriority;																		\
																									\
		/* Find the highest priority list that contains ready tasks. */								\
		portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );								\
		configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );		\
		listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );		\
	} /* taskSELECT_HIGHEST_PRIORITY_TASK() */

/*-----------------------------------------------------------*/

#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;											\
}
  • taskENTER_CRITICAL

        任务进入临界段,调用taskENTER_CRITICAL  portENTER_CRITICAL  vPortEnterCritical 本质都是调用__set_BASEPRI( configMAX_SYSCALL_INTERRUPT_PRIORITY );进行屏蔽指定configMAX_SYSCALL_INTERRUPT_PRIORITY 等级以下的中断。调度任务除了手动调度PenSV就是systick中断调用的PenSV ,而systick在MCU中 是等级最低的中断,默认设置下自然就会被屏蔽进而禁止任务调度。 configMAX_SYSCALL_INTERRUPT_PRIORITY定义在FreeRTOSconfig.h中,注释里里也清楚地描述了调用FreeRTOS的中断安全API一定要中断优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY。__set_BASEPRI请参考《ARM Cortex M3 与 Cortex M4 权威指南》相关章节。和...EXIT_CRITICAL成对嵌套使用。

#define taskENTER_CRITICAL()		portENTER_CRITICAL()
#define portENTER_CRITICAL()		vPortEnterCritical()

/*-----------------------------------------------------------*/

void vPortEnterCritical( void )
{
	portDISABLE_INTERRUPTS();
	uxCriticalNesting++;

	/* This is not the interrupt safe version of the enter critical function so
	assert() if it is being called from an interrupt context.  Only API
	functions that end in "FromISR" can be used in an interrupt.  Only assert if
	the critical nesting count is 1 to protect against recursive calls if the
	assert function also uses a critical section. */
	if( uxCriticalNesting == 1 )
	{
		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
	}
}

#define portDISABLE_INTERRUPTS() \
{ \
	__set_BASEPRI( configMAX_SYSCALL_INTERRUPT_PRIORITY ); \
	__DSB(); \
	__ISB(); \
}

/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY	5

  • taskEXIT_CRITICAL

        退出临界段,调用taskEXIT_CRITICAL  portEXIT_CRITICAL  vPortExitCritical 本质都是调用 __set_BASEPRI( 0 )清除中断屏蔽。和...ENTER_CRITICAL成对嵌套使用。

#define taskEXIT_CRITICAL()			portEXIT_CRITICAL()
#define portEXIT_CRITICAL()			vPortExitCritical()

/*-----------------------------------------------------------*/

void vPortExitCritical( void )
{
	configASSERT( uxCriticalNesting );
	uxCriticalNesting--;
	if( uxCriticalNesting == 0 )
	{
		portENABLE_INTERRUPTS();
	}
}

#define portENABLE_INTERRUPTS()					__set_BASEPRI( 0 )
  • xTaskCreate

        函数原型

BaseType_t xTaskCreate(	    TaskFunction_t pxTaskCode,
                            const char * const pcName,		/*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                            const configSTACK_DEPTH_TYPE usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask )

        任务创建函数,会申请一个描述任务信息的TCB和任务的运行栈空间: 

//描述任务信息的TCB
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
... ...
//任务的运行栈空间
 pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 

        通过给定的栈的写入方向(portSTACK_GROWTH)按照顺序申请TCB和栈;申请完成后,根据传入信息初始化TCB;

prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );

        初始化TCB完成之后,第一个任务将会调用prvInitialiseTaskLists()初始化任务调度的list;任务就是在这几个链表里面移动完成状态的切换,列表如下:

/* Lists for ready and blocked tasks. --------------------
xDelayedTaskList1 and xDelayedTaskList2 could be move to function scople but
doing so breaks some kernel aware debuggers and debuggers that rely on removing
the static qualifier. */
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1;						/*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2;						/*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;				/*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;		/*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList;						/*< Tasks that have been readied while the scheduler was suspended.  They will be moved to the ready list when the scheduler is resumed. */

        任务的状态有以下几种,每种状态都对应一个链表,在ready链表里面每个优先级对应一个链表,溢出也有独立的链表

FreeRTOS-V10 源码分析——链表(list),任务(task)_第5张图片

        会将任务TCB插入到任务调度的list的末尾中:

/*
 * Place the task represented by pxTCB into the appropriate ready list for
 * the task.  It is inserted at the end of the list.
 */
prvAddTaskToReadyList( pxNewTCB );

        如果调度器在运行且新建任务优先级高于运行任务,就会直接触发任务调度;

if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
    taskYIELD_IF_USING_PREEMPTION();
}
  • xTaskCreateStatic

       函数原型

TaskHandle_t xTaskCreateStatic(	    TaskFunction_t pxTaskCode,
                                    const char * const pcName,		/*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                                    const uint32_t ulStackDepth,
                                    void * const pvParameters,
                                    UBaseType_t uxPriority,
                                    StackType_t * const puxStackBuffer,
                                    StaticTask_t * const pxTaskBuffer )

       功能和xTaskCreate没有什么区别,只是不使用动态内存创建TCB和栈,采用传入的参数来制定内存地址,适用于对程序稳定性要求非常高而放弃动态内存的地方。同理xTaskCreateRestricted使用在MPU场合;

  • vTaskDelete

       函数原型

void vTaskDelete( TaskHandle_t xTaskToDelete )

       先获取需要删除的任务的TCB,NULL则为当前运行的任务的TCB;再从任务链表 list 里删除该链表项;

       如果删除的是当前任务,传入参数为NULL,则会将任务加入的待删除链表中,等待idle任务执行删除操作。

if( pxTCB == pxCurrentTCB )
{
    vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
    ++uxDeletedTasksWaitingCleanUp;
    portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}

       否则直接调用任务删除,释放动态申请的内存,并且重新更新下一个任务;

prvDeleteTCB( pxTCB );
/* Reset the next expected unblock time in case it referred to
the task that has just been deleted. */
prvResetNextTaskUnblockTime();

       若调度器在运行,则执行一次调度;

if( xSchedulerRunning != pdFALSE )
{
    if( pxTCB == pxCurrentTCB )
    {
        configASSERT( uxSchedulerSuspended == 0 );
        portYIELD_WITHIN_API();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
  • vTaskDelay

       函数原型

void vTaskDelay( const TickType_t xTicksToDelay ) 

       执行之后就会将任务的TCB移动到(先删除,再插入)延时状态的链表 list 中,

prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );

       如果没有被挂起全部,就执行一次任务调度;

portYIELD_WITHIN_API();

       关于任务从延时状态退出部分实现在SysTick中实现

#define xPortSysTickHandler SysTick_Handler

/*-----------------------------------------------------------*/

void xPortSysTickHandler( void )
{
	/* The SysTick runs at the lowest interrupt priority, so when this interrupt
	executes all interrupts must be unmasked.  There is therefore no need to
	save and then restore the interrupt mask value as its value is already
	known. */
	portDISABLE_INTERRUPTS();
	{
		/* Increment the RTOS tick. */
		if( xTaskIncrementTick() != pdFALSE )
		{
			/* A context switch is required.  Context switching is performed in
			the PendSV interrupt.  Pend the PendSV interrupt. */
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
		}
	}
	portENABLE_INTERRUPTS();
}


       每次进入SysTick中断,xTaskIncrementTick就会对等待状态的任务进行判定时间是否到达和事件是否触发,对指定的任务解除阻塞状态;因为在延时状态链表中,任务都是按照需要延时时间升序排列,所以只需要从头检查到底一个不满足的即可;

if( xConstTickCount < xItemValue )
{
	/* It is not time to unblock this item yet, but the
	item value is the time at which the task at the head
	of the blocked list must be removed from the Blocked
	state -	so record the item value in
	xNextTaskUnblockTime. */
	xNextTaskUnblockTime = xItemValue;
	break; /*lint !e9011 Code structure here is deedmed easier to understand with multiple breaks. */
}            
/* It is time to remove the item from the Blocked state. */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );

/* Is the task waiting on an event also?  If so remove
it from the event list. */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
	( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
  • vTaskDelayUntil

       函数原型

void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )

       首先对传入参数进行验证,是否需要延时,设置 xShouldDelay 的值,再更新*pxPreviousWakeTime的值:

xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
... ...
*pxPreviousWakeTime = xTimeToWake;

       再将任务的TCB移动到(先删除,再插入)延时状态的链表 list 中,

prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );

       如果没有被挂起全部,就执行一次任务调度;

portYIELD_WITHIN_API();
  • xTaskAbortDelay

       函数原型

BaseType_t xTaskAbortDelay( TaskHandle_t xTask )

        先获取任务是否在阻塞状态,若是则执行之后就会将任务的TCB移动到(先删除,再插入)就绪状态的链表 list 中;

prvAddTaskToReadyList( pxTCB );
  • uxTaskPriorityGet

       函数原型

UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask )

       直接返回pxTCB->uxPriority;

  • uxTaskPriorityGetFromISR

       函数原型

UBaseType_t uxTaskPriorityGetFromISR( const TaskHandle_t xTask )

UBaseType_t uxTaskPriorityGetFromISR( const TaskHandle_t xTask )
{
TCB_t const *pxTCB;
UBaseType_t uxReturn, uxSavedInterruptState;

	/* RTOS ports that support interrupt nesting have the concept of a
	maximum	system call (or maximum API call) interrupt priority.
	Interrupts that are	above the maximum system call priority are keep
	permanently enabled, even when the RTOS kernel is in a critical section,
	but cannot make any calls to FreeRTOS API functions.  If configASSERT()
	is defined in FreeRTOSConfig.h then
	portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
	failure if a FreeRTOS API function is called from an interrupt that has
	been assigned a priority above the configured maximum system call
	priority.  Only FreeRTOS functions that end in FromISR can be called
	from interrupts	that have been assigned a priority at or (logically)
	below the maximum system call interrupt priority.  FreeRTOS maintains a
	separate interrupt safe API to ensure interrupt entry is as fast and as
	simple as possible.  More information (albeit Cortex-M specific) is
	provided on the following link:
	https://www.freertos.org/RTOS-Cortex-M3-M4.html */
	portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

	uxSavedInterruptState = portSET_INTERRUPT_MASK_FROM_ISR();
	{
		/* If null is passed in here then it is the priority of the calling
		task that is being queried. */
		pxTCB = prvGetTCBFromHandle( xTask );
		uxReturn = pxTCB->uxPriority;
	}
	portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptState );

	return uxReturn;
}

       先屏蔽中断,返回pxTCB->uxPriority,再开启中断;

void vPortValidateInterruptPriority( void )
{
uint32_t ulCurrentInterrupt;
uint8_t ucCurrentPriority;

	/* Obtain the number of the currently executing interrupt. */
	__asm volatile( "mrs %0, ipsr" : "=r"( ulCurrentInterrupt ) :: "memory" );

	/* Is the interrupt number a user defined interrupt? */
	if( ulCurrentInterrupt >= portFIRST_USER_INTERRUPT_NUMBER )
	{
		/* Look up the interrupt's priority. */
		ucCurrentPriority = pcInterruptPriorityRegisters[ ulCurrentInterrupt ];
		
		configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
	}
	
	configASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= ulMaxPRIGROUPValue );
}

/* Constants required to check the validity of an interrupt priority. */
#define portFIRST_USER_INTERRUPT_NUMBER        ( 16 )
#define portNVIC_IP_REGISTERS_OFFSET_16        ( 0xE000E3F0 )
#define portAIRCR_REG                          ( * ( ( volatile uint32_t * ) 0xE000ED0C ) )
#define portMAX_8_BIT_VALUE                    ( ( uint8_t ) 0xff )
#define portTOP_BIT_OF_BYTE                    ( ( uint8_t ) 0x80 )
#define portMAX_PRIGROUP_BITS                  ( ( uint8_t ) 7 )
#define portPRIORITY_GROUP_MASK                ( 0x07UL << 8UL )
#define portPRIGROUP_SHIFT                     ( 8UL )

/*-----------------------------------------------------------*/

/* Calculate the maximum acceptable priority group value for the number
of bits read back. */
ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
	ulMaxPRIGROUPValue--;
	ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}

#ifdef __NVIC_PRIO_BITS
{
	/* Check the CMSIS configuration that defines the number of
	priority bits matches the number of priority bits actually queried
	from the hardware. */
	configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
}
#endif

#ifdef configPRIO_BITS
{
	/* Check the FreeRTOS configuration that defines the number of
	priority bits matches the number of priority bits actually queried
	from the hardware. */
	configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
}
#endif

/* Shift the priority group value back to its position within the AIRCR
register. */
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;

       首先portASSERT_IF_INTERRUPT_PRIORITY_INVALID();确认中断优先级是用户优先级,且优先级高于临界段屏蔽优先级(数字上小于),确认是合法调用。

这里指的注意的的是,《cortex-M3与M4权威指南》相关章节可以找到Cortex-M系列里面中断编号里1~15都是系统异常编号;16~255为可编程片上外设或外设中断源,所以将当前中断优先级与portFIRST_USER_INTERRUPT_NUMBER (16)比较;ucMaxSysCallPriority在任务调度器启动时初始化为临界段屏蔽优先级configMAX_SYSCALL_INTERRUPT_PRIORITY;因此第一个assert就是这个意思。中断与复位控制寄存器(AIRCR)的地址为0xE000ED0C,第8~10位为优先级分组配置寄存器(即& portPRIORITY_GROUP_MASK取出的几位);启动任务调度器时,ulMaxPRIGROUPValue就已经配置为最大值(7);

portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR()		__get_BASEPRI(); portDISABLE_INTERRUPTS()
#define portDISABLE_INTERRUPTS()\
{\
	__set_BASEPRI( configMAX_SYSCALL_INTERRUPT_PRIORITY );\
	__DSB();\
	__ISB();\
}

       然后获取当前任务优先级;

  • eTaskGetState

       函数原型

eTaskState eTaskGetState( TaskHandle_t xTask )

       返回 xTask 任务的运行状态,

/* Task states returned by eTaskGetState. */
typedef enum
{
	eRunning = 0,	/* A task is querying the state of itself, so must be running. */
	eReady,			/* The task being queried is in a read or pending ready list. */
	eBlocked,		/* The task being queried is in the Blocked state. */
	eSuspended,		/* The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
	eDeleted,		/* The task being queried has been deleted, but its TCB has not yet been freed. */
	eInvalid		/* Used as an 'invalid state' value. */
} eTaskState;
  • vTaskGetInfo

       函数原型

void vTaskGetInfo( TaskHandle_t xTask, TaskStatus_t *pxTaskStatus, BaseType_t xGetFreeStackSpace, eTaskState eState )

       获取指定任务的状态,存到*pxTaskStatus;

/* Used with the uxTaskGetSystemState() function to return the state of each task
in the system. */
typedef struct xTASK_STATUS
{
	TaskHandle_t xHandle;			/* The handle of the task to which the rest of the information in the structure relates. */
	const char *pcTaskName;			/* A pointer to the task's name.  This value will be invalid if the task was deleted since the structure was populated! */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	UBaseType_t xTaskNumber;		/* A number unique to the task. */
	eTaskState eCurrentState;		/* The state in which the task existed when the structure was populated. */
	UBaseType_t uxCurrentPriority;	/* The priority at which the task was running (may be inherited) when the structure was populated. */
	UBaseType_t uxBasePriority;		/* The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex.  Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */
	uint32_t ulRunTimeCounter;		/* The total run time allocated to the task so far, as defined by the run time stats clock.  See http://www.freertos.org/rtos-run-time-stats.html.  Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
	StackType_t *pxStackBase;		/* Points to the lowest address of the task's stack area. */
	configSTACK_DEPTH_TYPE usStackHighWaterMark;	/* The minimum amount of stack space that has remained for the task since the task was created.  The closer this value is to zero the closer the task has come to overflowing its stack. */
} TaskStatus_t;
  • vTaskPrioritySet

       函数原型

void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )

       将制定任务优先级修改

pxTCB->uxPriority = uxNewPriority;

       如果任务是就绪状态,则将任务TCB移动到对应的优先级的列表 list 中,

if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ uxPriorityUsedOnEntry ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
{
    if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
    {
        portRESET_READY_PRIORITY( uxPriorityUsedOnEntry, uxTopReadyPriority );
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
    prvAddTaskToReadyList( pxTCB );
}

       如果设置了,更高优先级的就绪任务,则立即调度任务;

if( xYieldRequired != pdFALSE )
{
    taskYIELD_IF_USING_PREEMPTION();
}
else
{
    mtCOVERAGE_TEST_MARKER();
}
  • vTaskSuspend

       函数原型

void vTaskSuspend( TaskHandle_t xTaskToSuspend )

       执行之后就会将任务的TCB移动到(先删除,再插入)挂起状态的链表 list 中,

vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );

       若是当前任务且调度器在运行,那么就启动一次调度

if( pxTCB == pxCurrentTCB )
{
    if( xSchedulerRunning != pdFALSE )
    {
        configASSERT( uxSchedulerSuspended == 0 );
        portYIELD_WITHIN_API();
    }
    else
    {
        if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks )
        {
            pxCurrentTCB = NULL;
        }
        else
        {
            vTaskSwitchContext();
        }
    }
}
  • vTaskResume

       函数原型

void vTaskResume( TaskHandle_t xTaskToResume )

       先检查对应任务是否被挂起,若被挂起那么就将任务移动到,就绪列表中;

( void ) uxListRemove(  &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
  • xTaskResumeFromISR

       函数原型

BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )

       操作时相比vTaskResume多了一个中断屏蔽和解除。中断屏蔽参见uxTaskPriorityGetFromISR

  • vTaskStartScheduler

       函数原型

void vTaskStartScheduler( void ) 

       首先会根据内存(动态/静态)申请方式进行创建Idle任务

... = xTaskCreate(    prvIdleTask,
                    configIDLE_TASK_NAME,
                    configMINIMAL_STACK_SIZE,
                    ( void * ) NULL,
...

       再根据需要创建用户时间任务

... = xTaskCreateStatic(	prvTimerTask,
                            configTIMER_SERVICE_TASK_NAME,
                            ulTimerTaskStackSize,
                            NULL,
...

       初始化3个任务运行参数

xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;

       调用调度器

if( xPortStartScheduler() != pdFALSE )

       调度器会先检查传入的值,然后将PendSV和SysTick中断优先级调成最低。

/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

       然后启动SysTick定时器;Cortex-M4为例。

vPortSetupTimerInterrupt();

/*
 * Setup the systick timer to generate the tick interrupts at the required
 * frequency.
 */
__weak void vPortSetupTimerInterrupt( void )
{
	/* Calculate the constants required to configure the tick interrupt. */
	#if( configUSE_TICKLESS_IDLE == 1 )
	{
		ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
		xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
		ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
	}
	#endif /* configUSE_TICKLESS_IDLE */

	/* Stop and clear the SysTick. */
	portNVIC_SYSTICK_CTRL_REG = 0UL;
	portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;

	/* Configure SysTick to interrupt at the requested rate. */
	portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
	portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}
/*-----------------------------------------------------------*/

       启动并设置FPU

vPortEnableVFP();
*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;

/*-----------------------------------------------------------*/

vPortEnableVFP:
	/* The FPU enable bits are in the CPACR. */
	ldr.w r0, =0xE000ED88
	ldr	r1, [r0]

	/* Enable CP10 and CP11 coprocessors, then save back. */
	orr	r1, r1, #( 0xf << 20 )
	str r1, [r0]
	bx	r14

       然后调用SVC中断从pxCurrentTCB中,启动第一个任务

vPortStartFirstTask();

/*-----------------------------------------------------------*/

vPortStartFirstTask
	/* Use the NVIC offset register to locate the stack. */
	ldr r0, =0xE000ED08
	ldr r0, [r0]
	ldr r0, [r0]
	/* Set the msp back to the start of the stack. */
	msr msp, r0
	/* Clear the bit that indicates the FPU is in use in case the FPU was used
	before the scheduler was started - which would otherwise result in the
	unnecessary leaving of space in the SVC stack for lazy saving of FPU
	registers. */
	mov r0, #0
	msr control, r0
	/* Call SVC to start the first task. */
	cpsie i
	cpsie f
	dsb
	isb
	svc 0


/*-----------------------------------------------------------*/

vPortSVCHandler:
	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB
	ldr r1, [r3]
	ldr r0, [r1]
	/* Pop the core registers. */
	ldmia r0!, {r4-r11, r14}
	msr psp, r0
	isb
	mov r0, #0
	msr	basepri, r0
	bx r14
  • vTaskEndScheduler

       函数原型

void vTaskEndScheduler( void )

       调用__set_BASEPRI屏蔽SysTick和PendSV中断,标志位置位,临界区嵌套次数给定1000UL

portDISABLE_INTERRUPTS();
xSchedulerRunning = pdFALSE;
vPortEndScheduler();
  • vTaskSuspendAll

       函数原型

void vTaskSuspendAll( void )

       将uxSchedulerSuspended标志位加一,禁止上下文切换;但是SysTick继续运行,当前任务还可以继续运行。

  • xTaskResumeAll

       函数原型

BaseType_t xTaskResumeAll( void )

       会将所有挂起状态的列表中的任务加入到就绪的列表 list 当中。

while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
{
	pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); 
	( void ) uxListRemove( &( pxTCB->xEventListItem ) );
	( void ) uxListRemove( &( pxTCB->xStateListItem ) );
	prvAddTaskToReadyList( pxTCB );

	/* If the moved task has a priority higher than the current
	task then a yield must be performed. */
	if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
	{
		xYieldPending = pdTRUE;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

       挂起期间有systick中断发生,就进行任务调度;

if( uxPendedCounts > ( UBaseType_t ) 0U )
{
	do
	{
		if( xTaskIncrementTick() != pdFALSE )
		{
			xYieldPending = pdTRUE;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
		--uxPendedCounts;
	} while( uxPendedCounts > ( UBaseType_t ) 0U );

	uxPendedTicks = 0;
}
  • xTaskGenericNotify

       函数原型

BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue ) PRIVILEGED_FUNCTION;
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL )
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )
#define xTaskNotifyGive( xTaskToNotify ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL )

       若开启NOTYFY功能,那么在任务的TCB中开辟了(uint8_t)ucNotifyState和(uint32_t)ulNotifiedValue量,根据这两个量的对应操作,传送这两个变量的值,如果改变对应等待任务的TCB状态列表,个人感觉不如信号量和队列灵活。也见到过利用ulNotifiedValue传递指针的,类似长度为1的队列。

  • xTaskGenericNotifyFromISR、vTaskNotifyGiveFromISR

       函数原型

BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )

       就是xTaskGenericNotify的中断屏蔽版本,中断屏蔽参见uxTaskPriorityGetFromISR

  • xTaskNotifyWait、ulTaskNotifyTake

       函数原型

BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ) 

       将当前任务TCB移动到等待状态链表 list ,开启调度器,等待到时间唤醒或收到NOTIFY唤醒,接收数据。

prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
traceTASK_NOTIFY_WAIT_BLOCK();
portYIELD_WITHIN_API();
  • DEBUG API

       xTaskGetTickCount、xTaskGetTickCountFromISR、uxTaskGetNumberOfTasks、pcTaskGetName、xTaskGetHandle、uxTaskGetStackHighWaterMark、uxTaskGetStackHighWaterMark2、xTaskCallApplicationTaskHook、xTaskGetIdleTaskHandle、uxTaskGetSystemState、vTaskList、vTaskGetRunTimeStats、xTaskGetIdleRunTimeCounter

       以上API常用语检测系统,这里不详细看了,基本也就是读取一些状态数据。

参考资料

FreeRTOS 基础友情链接:https://blog.csdn.net/zhzht19861011/category_9265276.html

FreeRTOS V9源码分析友情链接:https://blog.csdn.net/zhzht19861011/category_9265276.html

 

 

你可能感兴趣的:(FreeRTOS)