FreeRTOS任务创建、启动调度器、任务切换的过程分析——基于ARM-CotexM3

ARM-CM3创建任务、开启调度器、任务调度的整个流程:

【创建任务】

  1. 创建任务控制块。为任务申请空间并创建一个任务控制块NewTCB;
  2. 申请任务栈空间。为任务申请一块栈空间,并将起始地址存储到NewTCB.pxStack中;
  3. 初始化任务相关参数。将任务名、优先级和相关列表项等存放到任务控制块;
  4. 初始化任务栈中的上下文。计算栈顶指针pxTopOfStack,并通过该指针初始化上下文堆栈,主要包括xPSR、PC、LR,并给其他上下文留空,最后将栈顶指针pxTopOfStack存储到任务控制块TCB中。其中PC初始化为任务函数指针pxCode;
  5. 将新创建的任务加入任务就绪列表;

【启动任务调度器】

  1. 开启PendSV和Systick中断;
  2. 启动第一个任务。找到主栈的起始地址赋给MSP寄存器,使能中断并触发SVC服务来完成第一个任务的启动。SVC服务完成以下工作:(1)首先从TCB中获取第一个任务的栈顶指针,然后从栈顶开始恢复r11-r4寄存器;(2)将此时的栈顶指针赋给PSP寄存器供系统自动恢复其他上下文;(3)开启中断;(4)将r14(保存返回地址)或上0x0D,即设置返回时进入线程模式,从而在自动恢复上下文时使用PSP(ARM-CotexM3的堆栈指针分为主栈指针MSP和进程栈指针PSP);(5)最后执行<bx r14>从PSP处恢复上下文并返回,然后执行PC指向的任务函数;

【进行任务切换】

  1. 任务切换有两种场合:执行系统调用和触发SysTick中断,但最终都是依靠PendSV中断来实现。当发生任务切换时,进入PendSV中断,进入中断前已自动保存一部分上下文,从PSP指向地址开始入栈。进入中断后首先读取PSP到r0寄存器用于手动入栈操作,此时PSP应该指向另一部分上下文的起始地址,然后将另一部分上下文入栈保存,最后将此时的r0存储到任务控制块的栈顶指针成员即pxNewTCB->pxTopOfStack,至此完成了当前任务的上下文保存。接下来进行任务的切换,首先将r3和r14入主栈保存,防止调用vTaskSwitchContext函数时被覆盖,因为r14中保存了PendSV中断的返回地址;且当任务切换函数vTaskSwitchContext执行完成后,pxCurrentTCB被更新,而r3中保存了变量pxCurrentTCB的地址,因此可以继续使用r3访问它。找到新的任务后,要想使其运行,需要将其上下文出栈恢复,因此与启动第一个任务一样,从pxCurrentTCB中保存的任务栈顶指针处先恢复下文,然后将此时的栈顶指针赋给PSP,然后执行返回指令,从而自动恢复其他上下文并跳转至PC所指向的指令地址——任务函数地址。

关键函数代码如下:

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
    /* Simulate the stack frame as it would be created by a context switch
    interrupt. */
    pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
    *pxTopOfStack = portINITIAL_XPSR;    /* 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;
}

BaseType_t xPortStartScheduler( void )
{
/* Make PendSV and SysTick the lowest priority interrupts. */
    portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

    /* Start the timer that generates the tick ISR.  Interrupts are disabled
    here already. */
    vPortSetupTimerInterrupt();

    /* Initialise the critical nesting count ready for the first task. */
    uxCriticalNesting = 0;

    /* Start the first task. */
    prvStartFirstTask();

    /* Should not get here! */
    return 0;
}

__asm void prvStartFirstTask( void )
{
    PRESERVE8

    /* Use the NVIC offset register to locate the stack. */
    ldr r0, =0xE000ED08
    ldr r0, [r0]
    ldr r0, [r0]

    /* Set the msp back to the start of the stack. */
    msr msp, r0
    /* Globally enable interrupts. */
    cpsie i
    cpsie f
    dsb
    isb
    /* Call SVC to start the first task. */
    svc 0
    nop
    nop
}

__asm void vPortSVCHandler( void )
{
    PRESERVE8

    ldr    r3, =pxCurrentTCB    /* Restore the context. */
    ldr r1, [r3]            /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
    ldr r0, [r1]            /* The first item in pxCurrentTCB is the task top of stack. */
    ldmia r0!, {r4-r11}        /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
    msr psp, r0                /* Restore the task stack pointer. */
    isb
    mov r0, #0
    msr    basepri, r0
    orr r14, #0xd
    bx r14
}

__asm void xPortPendSVHandler( void )
{
    extern uxCriticalNesting;
    extern pxCurrentTCB;
    extern vTaskSwitchContext;

    PRESERVE8

    mrs r0, psp
    isb

    ldr    r3, =pxCurrentTCB        /* Get the location of the current TCB. */
    ldr    r2, [r3]

    stmdb r0!, {r4-r11}            /* Save the remaining registers. */
    str r0, [r2]                /* Save the new top of stack into the first member of the TCB. */
    stmdb sp!, {r3, r14}
    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
    msr basepri, r0
    dsb
    isb
    bl vTaskSwitchContext
    mov r0, #0
    msr basepri, r0
    ldmia sp!, {r3, r14}

    ldr r1, [r3]
    ldr r0, [r1]                /* The first item in pxCurrentTCB is the task top of stack. */
    ldmia r0!, {r4-r11}            /* Pop the registers and the critical nesting count. */
    msr psp, r0
    isb
    bx r14
    nop
}

原文地址:https://www.cnblogs.com/uestcliming666/p/12411885.html

时间: 2024-10-11 00:01:52

FreeRTOS任务创建、启动调度器、任务切换的过程分析——基于ARM-CotexM3的相关文章

MySQL创建事件调度器

MySQL中的事件调度器,可以用来执行定时任务. 一.开启 事件调度默认是关闭的,开启可执行. 查看事件调度器是否开启:     SHOW VARIABLES LIKE 'event_scheduler';   SELECT @@event_scheduler; 开启事件调度器 SET GLOBAL event_scheduler=1; SET GLOBAL event_scheduler=ON; 或者在my.ini文件中加上event_scheduler=1 或者在启动命令后加上"-event

Quartz与Spring集成——启动调度器

前言 在<Quartz与Spring集成--创建调度器>一文中介绍了调度器的创建过程,本文将分析其启动过程.熟悉Spring原理的人都知道AbstractApplicationContext的refresh方法的重要性,在refresh方法中调用了finishRefresh方法,最后会调用到SchedulerFactoryBean的start方法,其调用栈如图1所示. 图1 SchedulerFactoryBean的start方法的调用栈 根据图1的内容,我们知道spring容器初始化完毕的最

FreeRTOS高级篇3---启动调度器

使用FreeRTOS,一个最基本的程序架构如下所示: int main(void) { 必要的初始化工作; 创建任务1; 创建任务2; ... vTaskStartScheduler(); /*启动调度器*/ while(1); } 任务创建完成后,静态变量指针pxCurrentTCB(见<FreeRTOS高级篇2---FreeRTOS任务创建分析>第7节内容)指向优先级最高的就绪任务.但此时任务并不能运行,因为接下来还有关键的一步:启动FreeRTOS调度器. 调度器是FreeRTOS操作系

Quartz与Spring集成——创建调度器

前言 在<Quartz与Spring集成-- SchedulerFactoryBean的初始化分析>一文中介绍过Spring集成Quartz时的初始化过程,其中简单的提到了创建调度器的方法createScheduler.本文将着重介绍Quartz初始化时是如何创建调度器的. 创建调度器 这里从createScheduler的实现(见代码清单1)来分析,其处理步骤如下: 设置线程上下文的类加载器: 通过单例方法获取SchedulerRepository的实例(见代码清单2): 从调度仓库实例Sc

(14)Reactor调度器与线程模型——响应式Spring的道法术器

本系列文章索引<响应式Spring的道法术器>前情提要 Spring WebFlux快速上手 | Spring WebFlux性能测试前情提要:Reactor 3快速上手 | 响应式流规范 | 自定义数据流本文测试源码 2.4 调度器与线程模型 在1.3.2节简单介绍了不同类型的调度器Scheduler,以及如何使用publishOn和subscribeOn切换不同的线程执行环境. 下边使用一个简单的例子再回忆一下: @Test public void testScheduling() { F

【TencentOS tiny】深度源码分析(2)——调度器

温馨提示:本文不描述与浮点相关的寄存器的内容,如需了解自行查阅(毕竟我自己也不懂) 调度器的基本概念 TencentOS tiny中提供的任务调度器是基于优先级的全抢占式调度,在系统运行过程中,当有比当前任务优先级更高的任务就绪时,当前任务将立刻被切出,高优先级任务抢占处理器运行. TencentOS tiny内核中也允许创建相同优先级的任务.相同优先级的任务采用时间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当前系统中无更高优先级就绪任务的情况下才有效. 为了保证系统的实

MySQL 事件调度器

MySQL中的事件调度器是 MySQL 5.1 以后才新增的功能.可以将数据库按照规定的时间周期对数据库做,增加,删除,修改等操作.相当于linux中的无人调度器 crontab(相关crontab的操作请看参看我些的linux的计划任务).避免了一些数据相关的定时任务在业务操作层,减少操作员误操作的风险, 大大缩短了工作量提高工作效率. 首先介绍下语法: CREATE EVENT [IF NOT EXISTS] event_name ON SCHEDULE schedule [ON COMPL

从零开始入门 K8s | 调度器的调度流程和算法介绍

导读:Kubernetes 作为当下最流行的容器自动化运维平台,以声明式实现了灵活的容器编排,本文以 v1.16 版本为基础详细介绍了 K8s 的基本调度框架.流程,以及主要的过滤器.Score 算法实现等,并介绍了两种方式用于实现自定义调度能力. 调度流程 调度流程概览 Kubernetes 作为当下最主流的容器自动化运维平台,作为 K8s 的容器编排的核心组件 kube-scheduler 将是我今天介绍的主角,如下介绍的版本都是以 release-1.16 为基础,下图是 kube-sch

八、mysql视图、存储过程、函数以及时间调度器

1.create or replace view emp_view as select * from t4 ;给t4表创建一个名为emp_view的视图 2.drop view emp_view 删除视图 ======================================= 1.创建一个存储过程(查询所有数据) create procedure p1 () READS SQL DATA BEGIN select * from t4; END 2.创建一个存储过程(查询传参数据) cre