前言.这篇文章可能稍长,需要你花点时间去理解.
这也是freertos核心.理解了它,其他的就不在话下了.
一.任务的启动
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
xReturn = xTaskCreate( prvIdleTask,
configIDLE_TASK_NAME,
configMINIMAL_STACK_SIZE,
( void * ) NULL,
portPRIVILEGE_BIT,
&xIdleTaskHandle );
}
if( xReturn == pdPASS )
{
portDISABLE_INTERRUPTS();
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
if( xPortStartScheduler() != pdFALSE )
{
/* Should not reach here as if the scheduler is running the
function will not return. */
}
else
{
/* Should only reach here if a task calls xTaskEndScheduler(). */
}
}
/* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
meaning xIdleTaskHandle is not used anywhere else. */
( void ) xIdleTaskHandle;
}
1.首先创建IDLE任务.IDLE任务优先级最低.
2.初始化变量
xNextTaskUnblockTime = portMAX_DELAY;/*下一次任务启动的时间*/
xSchedulerRunning = pdTRUE;/*调度器标志开启*/
xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;/*时间戳置位*/
3.运行xPortStartScheduler函数,正常函数不会返回.
二.开启调度
xPortStartScheduler需要由移植层提供,这里以RISC_V为例说明
/*
* See header file for description.
*/
BaseType_t xPortStartScheduler( void )
{
__disable_irq();
vPortSetupTimerInterrupt();
/* Start the first task. */
prvPortStartFirstTask();
vTaskSwitchContext();
prvTaskExitError();
/* Should not get here! */
return 0;
}
1.禁止中断
2.vPortSetupTimerInterrupt函数-使能mtimer寄存器并且配置mtimer为非向量中断(系统时间的节拍器,即tick多久产生一次中断)
使能mtimer的软件中断并配置为向量中断,立即切换上下文
3.prvPortStartFirstTask函数,开启第一个任务,进行上下文的切换
prvPortStartFirstTask:
la t0, __stack_end__
csrw CSR_MSCRATCH, t0
LOAD sp, pxCurrentTCB
LOAD sp, 0x0(sp)
LOAD t0, 0 * REGBYTES(sp)
csrw CSR_MEPC, t0
LOAD t0, (portRegNum - 1) * REGBYTES(sp)
csrw CSR_MSTATUS, t0
LOAD x1, 1 * REGBYTES(sp)
LOAD x5, 2 * REGBYTES(sp)
LOAD x6, 3 * REGBYTES(sp)
LOAD x7, 4 * REGBYTES(sp)
LOAD x8, 5 * REGBYTES(sp)
LOAD x9, 6 * REGBYTES(sp)
LOAD x10, 7 * REGBYTES(sp)
LOAD x11, 8 * REGBYTES(sp)
LOAD x12, 9 * REGBYTES(sp)
LOAD x13, 10 * REGBYTES(sp)
LOAD x14, 11 * REGBYTES(sp)
LOAD x15, 12 * REGBYTES(sp)
#ifndef __riscv_32e
LOAD x16, 13 * REGBYTES(sp)
LOAD x17, 14 * REGBYTES(sp)
LOAD x18, 15 * REGBYTES(sp)
LOAD x19, 16 * REGBYTES(sp)
LOAD x20, 17 * REGBYTES(sp)
LOAD x21, 18 * REGBYTES(sp)
LOAD x22, 19 * REGBYTES(sp)
LOAD x23, 20 * REGBYTES(sp)
LOAD x24, 21 * REGBYTES(sp)
LOAD x25, 22 * REGBYTES(sp)
LOAD x26, 23 * REGBYTES(sp)
LOAD x27, 24 * REGBYTES(sp)
LOAD x28, 25 * REGBYTES(sp)
LOAD x29, 26 * REGBYTES(sp)
LOAD x30, 27 * REGBYTES(sp)
LOAD x31, 28 * REGBYTES(sp)
#endif
addi sp, sp, portCONTEXT_SIZE
mret
逐句解释.
la t0, __stack_end__
csrw CSR_MSCRATCH, t0
1.把工程的桟底写入to寄存器
2.然后通过csrw指令写入内核暂存寄存器CSR_MSCRATCH
LOAD sp, pxCurrentTCB
LOAD sp, 0x0(sp)
1.把pxCurrentTCB赋予桟指正sp,而pxCurrentTCB就是任务,而任务结构体的第一项就是桟顶.这就对应起来了
2.把sp桟的第一项出站,第一项就是PC地址
后边的指令依次出栈.这就和任务创建时桟初始化对应起来了
PC指针赋予了任务的函数地址,此时跳转到任务函数运行啦.至此第一个任务运行起来了.
三.任务的节拍器
来看看mtimer中断都干了什么事情
void xPortSysTickHandler( void )
{
portDISABLE_INTERRUPTS();
{
SysTick_Config(SYSTICK_TICK_CONST);
if( xTaskIncrementTick() != pdFALSE )
{
portYIELD();
}
}
portENABLE_INTERRUPTS();
}
1.禁止中断
2.重新配置mtimer下次的触发时间
3.xTaskIncrementTick函数.增加系统的tick数值
4.如果有必要使用portYIELD进行切换.
portYIELD触发一次mtimer软件中断,保存当前任务上下文,切换运行任务上下文.切换的过程和prvPortStartFirstTask基本一直,只是需要二次切换
这里重点来看看xTaskIncrementTick干了些什么事情.
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
/*调度器没有挂起*/
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
{
taskSWITCH_DELAYED_LISTS();
}
/*判断延时列表是否超时*/
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; )
{
/*获取正确的下一次延时时间,延时时间在状态列表项中*/
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else
{
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
if( xConstTickCount < xItemValue )
{
xNextTaskUnblockTime = xItemValue;
break;
}
/*延时到了,把状态列表项从延时列表中删除*/
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
/* 如果有事件等待要处理,则事件列表项也删除*/
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
/* 添加任务到就绪列表*/
prvAddTaskToReadyList( pxTCB );
#if ( configUSE_PREEMPTION == 1 )
{
/*任务切换的判断*/
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
#if ( configUSE_TICK_HOOK == 1 )
{
if( xPendedTicks == ( TickType_t ) 0 )
{
vApplicationTickHook();
}
}
#endif /* configUSE_TICK_HOOK */
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* configUSE_PREEMPTION */
}
else
{
/*调度器挂起了,记录挂起的时间,等调度器唤醒后处理.*/
++xPendedTicks;
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
return xSwitchRequired;
}
重要的节点已经备注了.
xTaskIncrementTick函数增加tic数值,判断延时超时.如果是,则处理,并且做任务的切换.
思路还是很清晰的.
这里xPendedTicks记录在调度器挂起的情况下,中断了多少次,然后等调度器恢复后,做tick补偿等等工作.
调度器挂起只是不能切换上下文,中断还是可以响应的.挂起列表,用处就在这里
四.思考与总结
从任务的第一次启动,任务的运行,系统节拍器,软中断切换上下文.
如果你已经理解了上面的内容,那么恭喜你,你对freertos的理解更近的一步.
此时系统已经具备了运行的基础和核心.
本文深入探讨了FreeRTOS的核心——调度器,以RISC-V架构为例,详细介绍了任务启动、调度器开启、任务节拍器的工作原理,并通过xTaskIncrementTick函数分析了任务超时和上下文切换的过程,帮助读者理解FreeRTOS调度器的运行机制。
2212

被折叠的 条评论
为什么被折叠?



