FreeRTOS 第五章 调度器开启过程

指导任务的创建和删除后,那么多个任务之间是如何切换的呢。这些功能都是由调度器完成的。接下来就研究下调度器的原理。

vTaskStartScheduler

创建完任务之后,就开启调度器。

创建一个idle task,因为如果所有的任务都没有运行,总要有一个任务要运行,所以需要一个idle task。优先级最低。

xReturn = prvCreateIdleTasks();

中断在这里是关闭的,以确保在调用xPortStartScheduler ( 之前或期间不会出现勾选:创建的任务的堆栈包含一个状态字,中断处于打开状态,因此当第一个任务开始运行时,中断将自动重新启用。

portDISABLE_INTERRUPTS();

 任务还没正常开始调度,所以delay设置为最大。调度器的运行状态设置成true。tickCout从0开始。

xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;

xPortStartScheduler

然后就开始启动调度器,这个函数不会返回,除非遇到错误。这个函数和具体的硬件有关系。

我们以ARM CortexM3为例。

设置PendSV和SysTick为最低优先级。为什么优先级最低和为什么是这两个中断后面再分析。 

    /* Make PendSV and SysTick the lowest priority interrupts. */
    portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;

我们只需要知道这里设置了两个中断。

SysTick中断设置完优先级后,需要配置timer,例如周期。

__attribute__( ( weak ) ) void vPortSetupTimerInterrupt( void )
{
    /* Calculate the constants required to configure the tick interrupt. */
    #if ( configUSE_TICKLESS_IDLE == 1 )
    {
        ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
        xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
        ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
    }
    #endif /* configUSE_TICKLESS_IDLE */

    /* Stop and clear the SysTick. */
    portNVIC_SYSTICK_CTRL_REG = 0UL;
    portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;

    /* Configure SysTick to interrupt at the requested rate. */
    portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
    portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}

先停止SysTick,count清0。然后更具用户配置来设置SysTick中断的周期。也就是task调度的周期。我们这里不用管具体的硬件是如何配置的,只需要知道原理。需要一个定时的中断函数类触发任务切换。

设置完systick后就可以开始第一个任务了。

/* Start the first task. */
static void prvPortStartFirstTask( void )
{
    __asm volatile (
        " ldr r0, =0xE000ED08   \n" /* Use the NVIC offset register to locate the stack. */
        " ldr r0, [r0]          \n"
        " ldr r0, [r0]          \n"
        " msr msp, r0           \n" /* Set the msp back to the start of the stack. */
        " cpsie i               \n" /* Globally enable interrupts. */
        " cpsie f               \n"
        " dsb                   \n"
        " isb                   \n"
        " svc 0                 \n" /* System call to start first task. */
        " nop                   \n"
        " .ltorg                \n"
        );
}
  • 从0xE000ED08(硬件寄存器)处获取中断向量表起始地址,默认是0x80000000。然后从中断向量表的起始地址获取主栈指针MSP的初始值,所以汇编的前三行就是为了获取MSP的初始值。
  • 打开中断。
  • 调用svc指针触发svc中断。也就是PendSV中断。

SVC中断异常函数如下:

void vPortSVCHandler( void )
{
    __asm volatile (
        "   ldr r3, pxCurrentTCBConst2      \n" /* Restore the context. */
        "   ldr r1, [r3]                    \n" /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
        "   ldr r0, [r1]                    \n" /* The first item in pxCurrentTCB is the task top of stack. */
        "   ldmia r0!, {r4-r11}             \n" /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
        "   msr psp, r0                     \n" /* Restore the task stack pointer. */
        "   isb                             \n"
        "   mov r0, #0                      \n"
        "   msr basepri, r0                 \n"
        "   orr r14, #0xd                   \n"
        "   bx r14                          \n"
        "                                   \n"
        "   .align 4                        \n"
        "pxCurrentTCBConst2: .word pxCurrentTCB             \n"
        );
}

pxCurrentTCBConst2是当前正在运行的任务TCB的存储地址。然后将地址所保存的值取出来,这样就获得了当前任务控制块的存储地址。然后获取栈顶指针。从栈顶指针里恢复寄存器的值。所以这就是为什么pxTopOfStack必须是tcb的第一个元素。

所以这几部就是为了获取栈顶地址。因为栈里保存着寄存器的值。

下面几条指定就是恢复寄存器(r4~r11)的值。其他寄存器(r0~r3 、pc等)会在退出中断的时候自动恢复。

恢复R4~R11、返回值后,pxTopOfStack指向0x20001028。中断退出后PSP从这个地址开始恢复其他寄存器值。msr psp, r0就是设置psp栈指针。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王涛的专栏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值