freertos的核心---调度(RISC_V为例)

本文深入探讨了FreeRTOS的核心——调度器,以RISC-V架构为例,详细介绍了任务启动、调度器开启、任务节拍器的工作原理,并通过xTaskIncrementTick函数分析了任务超时和上下文切换的过程,帮助读者理解FreeRTOS调度器的运行机制。

前言.这篇文章可能稍长,需要你花点时间去理解.

这也是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的理解更近的一步.

此时系统已经具备了运行的基础和核心.

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值