一.
划重点,划重点,划重点.
线程就是freertos运行管理的最小单位.一个线程有自己的生命周期,可以是一段时间也可以是forever.
具体看开发人员对于线程的规划,几个线程,每个线程处理什么事情.
先来看看线程长啥样?(任务是线程的别名,为了方便以后都称任务)
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
volatile StackType_t *pxTopOfStack; /*< 任务的桟顶. */
ListItem_t xStateListItem; /*< 任务的状态列表项,表明当前任务所处的状态 */
ListItem_t xEventListItem; /*< 任务的事件列表项,表明当前任务所持有的事件状态 */
UBaseType_t uxPriority; /*< 任务的优先级. */
StackType_t *pxStack; /*< 任务的堆栈. */
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< 任务的名字. */
#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t *pxEndOfStack; /*< 如果堆栈是先上增长,需要记录堆栈桟底. */
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< 如果使用锁,记录任务优先级,当优先级反转复原后,恢复之前的优先级. */
UBaseType_t uxMutexesHeld;
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue; /*< 任务的通知数值和状态. */
volatile uint8_t ucNotifyState;
#endif
} tskTCB;
typedef tskTCB TCB_t;
哇塞,内容有点多.不要慌,先...自行脑补
二.任务的创建.
任务可以静态或者动态创建,静态和动态是内存管理的事情,后面默认都是动态创建.对于主线的理解没有任何影响.
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;
/*判断堆栈的增长方向,一般都是向下增长*/
#if( portSTACK_GROWTH > 0 )
{
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
if( pxNewTCB->pxStack == NULL )
{
vPortFree( pxNewTCB );
pxNewTCB = NULL;
}
}
}
#else
{
StackType_t *pxStack;
pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
if( pxStack != NULL )
{
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
/*这里申请成功,赋值*/
pxNewTCB->pxStack = pxStack;
}
else
{
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;
/*根据堆栈增长方向的不同,不同的处理方式,*/
#if( portSTACK_GROWTH < 0 )
{
/*桟顶是高字节,向下增长,即是向低字节增长,通过桟指针和栈顶指针,判断桟的使用情况*/
pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
}
#else /* portSTACK_GROWTH */
{
/*桟顶是低字节,向上增长,即向高字节增长,必要额外记录栈顶指针*/
pxTopOfStack = pxNewTCB->pxStack;
pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
}
#endif /* portSTACK_GROWTH */
/* 记录任务的名字. */
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';
}
/*记录优先级*/
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
pxNewTCB->uxPriority = uxPriority;
/*使用锁,记录优先级,防止优先级反转*/
#if ( configUSE_MUTEXES == 1 )
{
pxNewTCB->uxBasePriority = uxPriority;
pxNewTCB->uxMutexesHeld = 0;
}
#endif /* configUSE_MUTEXES */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
/*状态列表项的pvowner指向任务*/
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/* 事件列表项的数值记录优先级,pvowner指向任务*/
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
{
pxNewTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
pxNewTCB->ulNotifiedValue = 0;
pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
#endif
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
if( pxCreatedTask != NULL )
{
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
}
这里有几个关键点.状态和事件列表项都指向的任务,并且把事件列表项设置成优先级相对于最大优先级的相反数.那我们可以大胆猜测任务运行的顺序就是通过任务的事件列表项数值来确定的.
还有就是桟顶的赋值.这里桟 顶必须在第一个位置,也是为了任务切换找到栈顶,从而进行任务的切换.
还有就是任务桟的初始化(以risc_v为例)
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
pxTopOfStack--;
*pxTopOfStack = portINITIAL_MSTATUS; /* MSTATUS */
#ifndef __riscv_32e
pxTopOfStack -= 22; /* X11 - X31. */
#else
pxTopOfStack -= 6; /* X11 - X15. */
#endif
*pxTopOfStack = ( StackType_t ) pvParameters; /* X10/A0 */
pxTopOfStack -= 6; /* X5 - X9 */
*pxTopOfStack = ( StackType_t ) portTASK_RETURN_ADDRESS; /* RA, X1 */
pxTopOfStack --;
*pxTopOfStack = ( ( StackType_t ) pxCode ) ; /* PC */
return pxTopOfStack;
}
任务的函数指针,参数,寄存器一一入栈.
再来看看加入就绪列表是个什么东东.
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
taskENTER_CRITICAL();
{
uxCurrentNumberOfTasks++;
if( pxCurrentTCB == NULL )
{
pxCurrentTCB = pxNewTCB;
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
prvInitialiseTaskLists();
}
}
else
{
if( xSchedulerRunning == pdFALSE )
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
}
}
uxTaskNumber++;
prvAddTaskToReadyList( pxNewTCB );
}
taskEXIT_CRITICAL();
if( xSchedulerRunning != pdFALSE )
{
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
}
}
1.首先进入临界区,防止中断打断
2.uxCurrentNumberOfTasks全局变量记录任务数量,
3.pxCurrentTCB全局变量,记录当前的任务句柄
4.如果是第一个任务则初始化其他全局变量,那些重要的全局变量了,如下面所说.
否则,如果调度器挂起了,pxCurrentTCB指向最高优先级任务
5.状态列表项插入对应优先级的就绪列表项尾部
6.如果调度器没有挂起,进行任务的切换
其他重要的全局变量:
static List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; /*就绪列表*/
static List_t xDelayedTaskList1; /*延时列表*/
static List_t xDelayedTaskList2; /*延时列表,用于时间溢出的处理*/
static List_t * volatile pxDelayedTaskList; /*指向xDelayedTaskList1*/
static List_t * volatile pxOverflowDelayedTaskList; /*指向xDelayedTaskList2*/
static List_t xPendingReadyList; /*等待就绪列表,由于调度器不能放入就绪列表,暂时放入.*/
static List_t xTasksWaitingTermination; /*任务删除列表.*/
static List_t xSuspendedTaskList; /*挂起任务列表*/
三.思考与总结
全局列表变量和状态转换是一一对应的.
回顾之前的任务创建,就一目了然了.
有一点还没有说明-任务的切换.放在任务调度章节来说明.
.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)