目录
1.任务控制块(结构体TCB_t)
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
//任务堆栈栈顶
volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
//状态列表项
ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
//事件列表项
ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
//任务优先级
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
//任务栈起始地址
StackType_t *pxStack; /*< Points to the start of the stack. */
//任务名称
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
} tskTCB;
typedef tskTCB TCB_t;
2.FreeRTOS任务创建源码分析
2.1.分配任务堆栈空间;分配任务控制块内存空间
先申请任务栈再申请TCB,任务栈地址<TCB地址,这种情况下,随着程序运行,任务栈栈顶地址往低地址方向走,即使任务栈空间不足,也不会触碰到TCB区域,这样相对安全一些。
2.2.初始化任务控制块;初始化任务堆栈
2.3.添加任务到就绪列表
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_t *pxNewTCB;
BaseType_t xReturn;
//指硬件平台栈增长方式
//Cortex-M4权威指南4.4.3
//Cortex-M处理器使用的栈模型被称为“满递减”
//#define portSTACK_GROWTH ( -1 )表示满减栈
/* If the stack grows down then allocate the stack then the TCB so the stack
does not grow into the TCB. Likewise if the stack grows up then allocate
the TCB then the stack. */
#if( portSTACK_GROWTH > 0 )
{
}
#else /* portSTACK_GROWTH */
{
StackType_t *pxStack; //这个指针用于存储栈地址
/* Allocate space for the stack used by the task being created. */
//任务栈内存分配
pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */
if( pxStack != NULL ) //任务栈内存分配成功
{
//任务控制块内存分配
/* Allocate space for the TCB. */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */
if( pxNewTCB != NULL ) //任务控制块内存分配成功
{
//TCB中存储栈地址
/* Store the stack location in the TCB. */
pxNewTCB->pxStack = pxStack;
}
else
{
//TCB创建失败,释放栈空间
/* The stack cannot be used as the TCB was not created. Free
it again. */
vPortFree( pxStack );
}
}
else //任务栈内存分配失败
{
pxNewTCB = NULL;
}
}
#endif /* portSTACK_GROWTH */
if( pxNewTCB != NULL ) //任务控制块内存分配成功
{
//新建任务初始化,具体函数实现见下一个代码段
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
//把任务添加到就绪列表中
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
else
{
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
}
static void prvInitialiseNewTask( 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,
TaskHandle_t * const pxCreatedTask,
TCB_t *pxNewTCB,
const MemoryRegion_t * const xRegions )
{
StackType_t *pxTopOfStack;
UBaseType_t x;
//计算栈顶的地址
/* Calculate the top of stack address. This depends on whether the stack
grows from high memory to low (as per the 80x86) or vice versa.
portSTACK_GROWTH is used to make the result positive or negative as required
by the port. */
#if( portSTACK_GROWTH < 0 )
{
//把栈空间的高地址分配给栈顶
pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
//栈对齐,栈要8字节对齐
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 !e9033 !e9078 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. Checked by assert(). */
//检查是否有错误
/* Check the alignment of the calculated top of stack is correct. */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
}
#else /* portSTACK_GROWTH */
{
}
#endif /* portSTACK_GROWTH */
//在TCB中存储任务名称
/* Store the task name in the TCB. */
if( pcName != NULL )
{
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
/* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than
configMAX_TASK_NAME_LEN characters just in case the memory after the
string is not accessible (extremely unlikely). */
if( pcName[ x ] == ( char ) 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* Ensure the name string is terminated in the case that the string length
was greater or equal to configMAX_TASK_NAME_LEN. */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
}
else
{
/* The task has not been given a name, so just ensure there is a NULL
terminator when it is read out. */
pxNewTCB->pcTaskName[ 0 ] = 0x00;
}
//判断任务分配的优先级是否大于最大值
/* This is used as an array index so must ensure it's not too large. First
remove the privilege bit if one is present. */
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//在TCB中存储任务优先级
pxNewTCB->uxPriority = uxPriority;
//任务状态表、事件表初始化
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
//任务控制块链接到任务状态表中
/* Set the pxNewTCB as a link back from the ListItem_t. This is so we can get
back to the containing TCB from a generic item in a list. */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
//任务控制块链接到任务事件表中
/* Event lists are always in priority order. */
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
/* Initialize the TCB stack to look as if the task was already running,
but had been interrupted by the scheduler. The return address is set
to the start of the task function. Once the stack has been initialised
the top of stack variable is updated. */
//#define portUSING_MPU_WRAPPERS 0
#if( portUSING_MPU_WRAPPERS == 1 )
{
}
#else /* portUSING_MPU_WRAPPERS */
{
//任务堆栈初始化,之后返回任务栈顶
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
}
#endif /* portUSING_MPU_WRAPPERS */
if( pxCreatedTask != NULL )
{
//赋值任务句柄(就是TCB)
/* Pass the handle out in an anonymous way. The handle can be used to
change the created task's priority, delete the created task, etc.*/
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
//M4权威指南第八章节,分析异常处理
//任务调度其实就是通过CPU内核异常处理实现的
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
/* Simulate the stack frame as it would be created by a context switch
interrupt. */
/* Offset added to account for the way the MCU uses the stack on entry/exit
of interrupts, and to ensure alignment. */
pxTopOfStack--;
//入栈程序状态寄存器
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */
pxTopOfStack--;
//入栈PC指针
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */
pxTopOfStack--;
//入栈LR链接寄存器
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */
//R1,R2,R3,R12不需要初始化
/* Save code space by skipping register initialisation. */
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
//R0作为传参入栈
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
/* A save method is being used that requires each task to maintain its
own exec return value. */
pxTopOfStack--;
//异常返回值入栈 返回值是确定程序使用的栈地址是哪一个 MSP还是PSP
*pxTopOfStack = portINITIAL_EXC_RETURN;
//R4-R11不初始化
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
//最终返回栈顶值
return pxTopOfStack;
}
3.FreeRTOS任务删除源码分析
3.1.从就绪表中删除
3.2.从事件表中删除
3.3.释放任务控制块;释放任务堆栈内存
3.4.开始任务调度
void vTaskDelete( TaskHandle_t xTaskToDelete )
{
TCB_t *pxTCB;
//进入临界段
taskENTER_CRITICAL();
{
//获取任务控制块————参数传入的是任务句柄
//判断是任务本身还是其他任务
//如果是当前任务,传入的参数为NULL
/* If null is passed in here then it is the calling task that is
being deleted. */
pxTCB = prvGetTCBFromHandle( xTaskToDelete );
//从就绪列表中移除
/* Remove task from the ready/delayed list. */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//从事件列表中移除
/* Is the task waiting on an event also? */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Increment the uxTaskNumber also so kernel aware debuggers can
detect that the task lists need re-generating. This is done before
portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
not return. */
uxTaskNumber++;
//如果删除的是当前任务
if( pxTCB == pxCurrentTCB )
{
//不能在此删除,要在空闲任务中删除
//把任务添加到等待删除的任务列表中
/* A task is deleting itself. This cannot complete within the
task itself, as a context switch to another task is required.
Place the task in the termination list. The idle task will
check the termination list and free up any memory allocated by
the scheduler for the TCB and stack of the deleted task. */
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
//给空闲任务一个标记
/* Increment the ucTasksDeleted variable so the idle task knows
there is a task that has been deleted and that it should therefore
check the xTasksWaitingTermination list. */
++uxDeletedTasksWaitingCleanUp;
//钩子函数,需要用户自己实现
/* Call the delete hook before portPRE_TASK_DELETE_HOOK() as
portPRE_TASK_DELETE_HOOK() does not return in the Win32 port. */
traceTASK_DELETE( pxTCB );
/* The pre-delete hook is primarily for the Windows simulator,
in which Windows specific clean up operations are performed,
after which it is not possible to yield away from this task -
hence xYieldPending is used to latch that a context switch is
required. */
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}
else //如果删除的不是当前任务
{
//直接删除任务控制块
--uxCurrentNumberOfTasks;
traceTASK_DELETE( pxTCB );
//复位任务锁定时间
//操作系统根据最新的时间进行调度,所以删除任务后,要更新锁定时间
prvDeleteTCB( pxTCB );
/* Reset the next expected unblock time in case it referred to
the task that has just been deleted. */
prvResetNextTaskUnblockTime();
}
}
//退出临界段
taskEXIT_CRITICAL();
//判断调度器是否开启
/* Force a reschedule if it is the currently running task that has just
been deleted. */
if( xSchedulerRunning != pdFALSE ) //调度器开启中
{
if( pxTCB == pxCurrentTCB ) //如果是删除任务本身
{
//马上进行任务调度(释放CPU的使用权)
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
4.FreeRTOS任务挂起源码分析
4.1.从就绪表中删除
4.2.从事件表中删除
4.3.添加任务到挂起列表中
4.4.开始任务调度
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
{
TCB_t *pxTCB;
//进入临界段
taskENTER_CRITICAL();
{
//获取任务控制块
/* If null is passed in here then it is the running task that is
being suspended. */
pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
//从就绪列表中移除
/* Remove task from the ready/delayed list and place in the
suspended list. */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//从事件列表中移除
/* Is the task waiting on an event also? */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//把任务添加到挂起列表中
vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
}
//退出临界段
taskEXIT_CRITICAL();
//判断调度器是否开启
if( xSchedulerRunning != pdFALSE )
{
//更新下个任务的锁定时间
/* Reset the next expected unblock time in case it referred to the
task that is now in the Suspended state. */
taskENTER_CRITICAL();
{
prvResetNextTaskUnblockTime();
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( pxTCB == pxCurrentTCB ) //如果是当前任务
{
if( xSchedulerRunning != pdFALSE ) //如果调度器已开启
{
//直接开始任务调度,释放CPU的使用权
/* The current task has just been suspended. */
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else //如果调度器没有开启
{
//读取当前任务挂起列表的长度
/* The scheduler is not running, but the task that was pointed
to by pxCurrentTCB has just been suspended and pxCurrentTCB
must be adjusted to point to a different task. */
if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks ) /*lint !e931 Right has no side effect, just volatile. */
{
//如果挂起列表已经把所有任务挂起,把当前任务控制块赋值为NULL,不让任务控制块再使用了
/* No other tasks are ready, so set pxCurrentTCB back to
NULL so when the next task is created pxCurrentTCB will
be set to point to it no matter what its relative priority
is. */
pxCurrentTCB = NULL;
}
else
{
//任务上下文切换
//即在就绪列表中找到优先级最高的任务进行调度
vTaskSwitchContext();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
5.FreeRTOS任务恢复源码分析
5.1.从挂起表中删除
5.2.添加任务到就绪列表中
5.3.开始任务调度
void vTaskResume( TaskHandle_t xTaskToResume )
{
//获取要恢复的任务控制块
TCB_t * const pxTCB = xTaskToResume;
//检查
/* It does not make sense to resume the calling task. */
configASSERT( xTaskToResume );
//既然要使用任务控制块,肯定不能是NULL,也不能是当前的任务
/* The parameter cannot be NULL as it is impossible to resume the
currently executing task. */
if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) )
{
//进入临界段
taskENTER_CRITICAL();
{
//判断任务是否已经挂起
if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
{
//从挂起列表中移除
/* The ready list can be accessed even if the scheduler is
suspended because this is inside a critical section. */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
//添加任务到就绪列表
prvAddTaskToReadyList( pxTCB );
//判断要恢复的任务是否大于当前任务的优先级
//如果大于,释放CPU的使用权,开始内核调度
//前提条件是已经使能了抢占式调度器
/* A higher priority task may have just been resumed. */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
/* This yield may not cause the task just resumed to run,
but will leave the lists in the correct state for the
next yield. */
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
//退出临界段
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
6.任务API总结(部分)
API名称 | API说明 |
xTaskCreate | 动态创建任务 |
xTaskCreateStatic | 静态创建任务 |
vTaskDelete | 删除任务 |
vTaskSuspend | 挂起任务 |
vTaskResume | 恢复任务 |
xTaskResumeFromISR | 在中断中恢复任务 |
uxTaskPriorityGet | 获取任务优先级 |
vTaskPrioritySet | 设置任务优先级 |
vTaskDelay | 延时任务 |
Functions are prefixed with both the type they return and the file they are defined in. For example:
1.vTaskPrioritySet() returns a void and is defined within task.c.
2.xQueueReceive() returns a variable of type BaseType_t and is defined within queue.c.
3.vSemaphoreCreateBinary() returns a void and is defined within semphr.h.
File scope (private) functions are prefixed with ‘prv’.