目录
一、概述
1、前言
本文将介绍Freertos中创建任务(内部源码)。前言这里会简单介绍下链表、TCB结构体。本文展示源码做过精简,核心不变,建议大家可以自行对照阅读源码。
2、ITEM结构体
ITEM结构体是TCB结构体的父类,尽管C语言没有这种概念。ITEM结构体是TCB结构体挂载在链表上的关键。
struct xLIST_ITEM
{
/*该变量被用于排序*/
configLIST_VOLATILE TickType_t xItemValue;
/*指向下一个链表*/
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
/*指向前一个链表*/
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
/*指向TCB结构体*/
void * pvOwner;
/*指向链表头*/
struct xLIST * configLIST_VOLATILE pxContainer;
};
typedef struct xLIST_ITEM ListItem_t;
3、TCB结构体
TCB结构体就是任务!同时句柄也是TCB结构体,只不过换了个形式,所谓操作句柄进行删除、挂起操作就是在操作TCB结构体。为什么这么说?我们来看下TCB结构体源码。TCB结构体中包含栈顶地址、状态链表(包含就绪、阻塞、挂起三种状态)、事件链表(表示进行什么事件)、优先级、栈起始地址、函数名字(configMAX_TASK_NAME_LEN宏定义表示NAME长度)。这里做过大量精简、不过核心就是这些。
状态链表和事件链表就是ITEM结构体。
typedef struct tskTaskControlBlock
{
volatile StackType_t * pxTopOfStack; /*栈顶*/
ListItem_t xStateListItem; /*状态链表*/
ListItem_t xEventListItem; /*事件链表*/
UBaseType_t uxPriority; /*优先级*/
StackType_t * pxStack; /*指向堆栈的起点*/
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*函数名字*/
} tskTCB;
4、链表初始化
众所周知,freertos运行的核心就是链表,TCB结构体(任务)也是挂载在链表上运行的。在首次创建任务时,会创建对应的链表头,包括不同优先级的链表、延时链表、延时溢出链表、挂起链表等。
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
PRIVILEGED_DATA static List_t xDelayedTaskList1;
PRIVILEGED_DATA static List_t xDelayedTaskList2;
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
PRIVILEGED_DATA static List_t xPendingReadyList;
PRIVILEGED_DATA static List_t xSuspendedTaskList;
下面是链表头相关初始化。vListInitialiseconfig()函数用于初始化相关链表头,MAX_PRIORITIES表示优先级数量。创建任务中会用到链表初始化函数。
static void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;
/*创建不同优先级的就绪链表*/
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
/*创建两个Delay链表*/
vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 ); //延时溢出链表
/*创建就绪但尚未执行的任务。*/
vListInitialise( &xPendingReadyList );
#if ( INCLUDE_vTaskDelete == 1 )
{
/*删除任务但是未释放内存的链表*/
vListInitialise( &xTasksWaitingTermination );
}
#endif /* INCLUDE_vTaskDelete */
/*创建挂起链表*/
#if ( INCLUDE_vTaskSuspend == 1 )
{
vListInitialise( &xSuspendedTaskList );
}
#endif /* INCLUDE_vTaskSuspend */
/*设置延时链表*/
pxDelayedTaskList = &xDelayedTaskList1;
/*设置延时溢出链表*/
pxOverflowDelayedTaskList = &xDelayedTaskList2;
}
二、内部源码
1、核心内容
- 初始化任务栈
- 初始化TCB结构体
- 伪造现场
- 将TCB结构体放入就绪链表中
初始化任务栈: 每个任务都有属于自己独立的栈空间,这是一定的。切换任务本质就是触发中断,把当前运行任务的CPU寄存器保存到当前任务的栈中,切换到下一个任务的栈,将此任务栈内保存的寄存器恢复到CPU内部寄存器中,便开始运行。这便是切换的本质。(后续会出一篇文章详细介绍)
伪造现场: 为什么需要伪造现场?因为RTOS运行起来后,切换下一个任务,要取下一个任务的栈中存放的寄存器恢复到CPU内部寄存器。所以我们需要在正式运行之前,就要把每个任务的栈进行伪造(这里的伪造就是伪造部分寄存器,这些寄存器是CPU需要的寄存器),具体可以去看中断处理的过程这章内容。
2、源码部分
这里做了大量精简,核心不变,建议对照原文进行学习。
创建任务源码,这里主要分配任务栈、分配TCB结构体空间、执行设置TCB结构体函数、放入就绪链表中。
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, /*函数地址名*/
const char * const pcName, /*任务Name*/
const configSTACK_DEPTH_TYPE usStackDepth, /*分配栈空间*/
void * const pvParameters, /*参数*/
UBaseType_t uxPriority, /*优先级*/
TaskHandle_t * const pxCreatedTask ) /*任务句柄*/
{
TCB_t * pxNewTCB;
BaseType_t xReturn;
StackType_t * pxStack;
/* 为正在创建的任务使用的堆栈分配空间 */
pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
if( pxStack != NULL )
{
/* 为TCB分配空间. */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
/* 记录栈首地址 */
pxNewTCB->pxStack = pxStack;
}
else
{
/* The stack cannot be used as the TCB was not created. Free
* it again. */
vPortFreeStack( pxStack );
}
}
else
{
pxNewTCB = NULL;
}
if( pxNewTCB != NULL )
{
/*伪造现场、设置TCB结构体*/
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
/*把TCB放入就绪链表*/
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
else
{
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
}
这个函数会更详细的配置TCB结构体,传入的参数和创建任务相似。首先会进行栈对齐操作。NAME、优先级、Owner、Value初始化到TCB结构体中。同时会进行伪造现场、设置句柄等操作。这里做过大量精简,建议对照原文进行学习。
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
const char * const pcName,
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;
/*向上对其分配栈*/
pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
/*将Name赋值给TCB结构体 */
if( pcName != NULL )
{
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
if( pcName[ x ] == ( char ) 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
}
else
{
pxNewTCB->pcTaskName[ 0 ] = 0x00;
}
/*configMAX_PRIORITIES宏定义表示最大优先级*/
configASSERT( uxPriority < configMAX_PRIORITIES );
/*赋值优先级给TCB结构体*/
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/*初始化优先级到结构体中*/
pxNewTCB->uxPriority = uxPriority;
/*将状态链表和事件链表添加到对应链表头*/
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
/* 状态Item.Owner->TCB结构体 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/* 将当前TCB结构体的优先级存储在事件Item->Value */
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
/* 事件Item.Owner->TCB结构体 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
/*伪造现场(写入对应寄存器)*/
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
if( pxCreatedTask != NULL )
{
/* 手柄可以用来更改已创建任务的优先级,删除已创建的任务等.*/
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
伪造现场的函数在下节单独讲,这里介绍下讲TCB结构体放入就绪链表函数。
pxCurrentTCB表示当前指向的TCB结构体。
xSchedulerRunning表示当前是否处于运行状态。
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
{
/*进入临界区操作*/
taskENTER_CRITICAL();
{ /*记录当前任务数量*/
uxCurrentNumberOfTasks++;
/*如果首次创建任务*/
if( pxCurrentTCB == NULL )
{
pxCurrentTCB = pxNewTCB;
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
/*会在这里初始化链表!前文介绍过!!*/
prvInitialiseTaskLists();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/*确保当前任务不在就绪列表中*/
if( xSchedulerRunning == pdFALSE )
{
/*如果当前选择的TCB->Prior 小于等于 New->Prior 则赋值*/
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
/*这就是为什么同优先级任务后创建的先执行*/
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
uxTaskNumber++;
/*添加任务到就绪链表尾中*/
prvAddTaskToReadyList( pxNewTCB );
}
/*退出临界区*/
taskEXIT_CRITICAL();
if( xSchedulerRunning != pdFALSE )
{
/* 如果创建的任务优先级高于当前任务那么它现在应该运行了 */
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
/*进行任务切换*/
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
3、伪造现场
在查看伪造现场函数之前,一定要对相关内部寄存器有一定了解,可以查看之前的文章(中断的处理过程)。
这里为了更好的理解,可以对着代码和图观看。
StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack, /*栈*/
TaskFunction_t pxCode, /*函数首地址*/
void * pvParameters ) /*传入参数*/
{
/* 伪造现场 */
pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
*pxTopOfStack = portINITIAL_XPSR; /*第二十五个位置设置为1 使用thumb指令集 */ /* xPSR */
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */ /*函数地址*/
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */ /*返回为特殊的值*/
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
三、总结
本文对创建任务源码做了大量精简,只保留核心内容,为了让学者更快的入门源码。后续会介绍任务调度(上下文切换),这也是操作系统的核心。但是在这之前,理解伪造现场和中断的处理过程也是至关重要的。