FreeRTOS 任务调度 任务切换

本文深入解析FreeRTOS的任务调度启动过程,包括vTaskStartScheduler函数的调用,以及Cortex-M3架构下移植层调度器的实现。在启动调度器后,系统会创建并运行首个任务。任务切换主要依赖于PendSV异常,实现高优先级任务的抢占和时间片轮转。FreeRTOS确保实时响应,避免中断响应延迟。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

@(嵌入式)

Freertos
FreeRtos

简述

前面文章 < FreeRTOS 任务调度 任务创建 > 介绍了 FreeRTOS 中如何创建任务以及其具体实现。
一般来说, 我们会在程序开始先创建若干个任务, 而此时任务调度器还没又开始运行,因此每一次任务创建后都会依据其优先级插入到就绪链表,同时保证全局变量 pxCurrentTCB 指向当前创建的所有任务中优先级最高的一个,但是任务还没开始运行。
当初始化完毕后,调用函数 vTaskStartScheduler启动任务调度器开始开始调度,此时,pxCurrentTCB所指的任务才开始运行。
所以, 本章,介绍任务调度器启动以及如何进行任务切换。

调度器涉及平台底层硬件操作,本文以Cotex-M3 架构为例, 具体可以参考 《Cortex-M3权威指南》(文末附)

分析的源码版本是 v9.0.0
(为了方便查看,github 上保留了一份源码Source目录下的拷贝)

启动调度器

创建任务后,系统不会自动启动任务调度器,需要用户调用函数 vTaskStartScheduler 启动调度器。 该函数被调用后,会先创建系统自己需要用到的任务,比如空闲任务 prvIdleTask,定时器管理的任务等。 之后, 调用移植层提供的函数 xPortStartScheduler
代码解析如下,

void vTaskStartScheduler( void )
{
    BaseType_t xReturn;
    #if( configSUPPORT_STATIC_ALLOCATION == 1 )
    {
        // 采用静态内存创建空闲任务
        StaticTask_t *pxIdleTaskTCBBuffer = NULL;
        StackType_t *pxIdleTaskStackBuffer = NULL;
        uint32_t ulIdleTaskStackSize;
        // 获取静态内存地址/参数
        vApplicationGetIdleTaskMemory(
            &pxIdleTaskTCBBuffer, 
            &pxIdleTaskStackBuffer, 
            &ulIdleTaskStackSize );
        // 创建任务
        // 空闲任务优先级为 0, 也就是其优先级最低
        // !! 但是, 设置了特权位, 所以其运行在 特权模式
        xIdleTaskHandle = xTaskCreateStatic(prvIdleTask, "IDLE", 
            ulIdleTaskStackSize, (void *) NULL, 
            (tskIDLE_PRIORITY | portPRIVILEGE_BIT), 
            pxIdleTaskStackBuffer,
            pxIdleTaskTCBBuffer); 

        if( xIdleTaskHandle != NULL )
        {
            xReturn = pdPASS;
        }
        else
        {
            xReturn = pdFAIL;
        }
    }
    #else
    {
        // 动态申请内存创建任务
        xReturn = xTaskCreate(prvIdleTask,
            "IDLE", configMINIMAL_STACK_SIZE,
            (void *)NULL,
            (tskIDLE_PRIORITY | portPRIVILEGE_BIT),
            &xIdleTaskHandle );     
    }
    #endif

    // 如果工程使用了软件定时器, 需要创建定时器任务进行管理
    #if ( configUSE_TIMERS == 1 )
    {
        if( xReturn == pdPASS )
        {
            xReturn = xTimerCreateTimerTask();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif

    if( xReturn == pdPASS )
    {

        // 关闭中断, 避免调度器运行前节拍定时器产生中断
        // 中断在第一个任务启动时恢复
        portDISABLE_INTERRUP
### FreeRTOS 任务调度原理 在FreeRTOS中,任务调度的核心机制依赖于链表结构来管理和调度多个任务。每当TICK中断触发时,系统会产生一个定时器中断,用于评估当前系统的状态并决定下一个要执行的任务[^1]。 #### 链表管理任务 每个任务都有自己的控制块(Task Control Block, TCB),其中包含了关于此任务的各种信息,比如优先级、堆栈指针等。这些TCB通过双向链表连接起来形成就绪列表(Ready List)。当有新的事件发生或是现有任务的状态发生变化时(例如被阻塞或解除阻塞),相应的节点会在链表中移动位置以反映最新的情况。 #### 定时器中断(Tick Interrupt) 定时器溢出引发的周期性中断称为Tick中断,在每次Tick中断期间: - 更新计数器; - 检查是否有更高优先级的任务变为可运行态; - 如果存在,则调用上下文切换函数完成新旧任务之间的转换过程。 ```c void vPortYield(void){ /* Save current context */ __asm volatile ( "mrs r0, PSP\n\t" "push {r0}\n\t" : : :"memory", "cc"); } ``` 这段汇编代码展示了如何保存当前任务的处理器状态寄存器(PSP)[^2]。这一步骤对于确保不同任务间的数据隔离至关重要,并且允许操作系统安全地从一个任务跳转到另一个任务而不丢失任何重要数据。 #### 栈指针的选择 值得注意的是,在CPU启动初期由硬件初始化的第一个任务`resetTask`使用主栈指针(MSP)作为其工作区;然而一旦RTOS开始运作并且准备激活首个应用程序级别的线程(`taskA`)之后,所有的后续创建出来的进程都将采用处理程序模式下的栈指针(PSP)来进行操作。而在进入任意类型的异常处理流程当中时——无论是IRQ还是Fault Handler——ARM Cortex-M系列微控制器都会自动切换回MSP模式下执行相应服务例程。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值