FreeRTOS学习笔记之调度机制

发布于:2025-07-24 ⋅ 阅读:(27) ⋅ 点赞:(0)

一、前提介绍

1.1 通用寄存器

在讲解调度原理之前,先看一下STM32内核通用寄存器,Cortex-M3和 Cortex-M4处理器的寄存器组中有16个寄存器,其中13个为32位通用目的寄存器,其他3个则有特殊用途
在进行其他的数据处理时,寄存器组中可以临时存储一些数据变量,而无须更新到系统存储器及在使用时将它们读回,这种方式一般被称作“加载一存储架构”

通用寄存器

通用目的寄存器 R0-R7被称为低组寄存器。所有指令都能访问它们。它们的字长全是 32 位,复位后的初始值是不可预料的

通用目的寄存器 R8-R12被称为高组寄存器。这是因为只有很少的 16 位 Thumb 指令能访问它们,32位的指令则不受限制。它们也是 32 位字长,且复位后的初始值是不可预料的。

堆栈指针 R13:
 
 主堆栈指针(MSP),或写作 SP_main。这是缺省的堆栈指针,它由 OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用(用于无OS,应用程序/ISR(复位后的默认堆栈指针)
  进程堆栈指针(PSP),或写作 SP_process。用于常规的应用程序代码(不处于异常服用例程中时)(用于有OS,每个任务都有自己的任务栈
若无RTOS就只有一个栈,有RTOS,STM32本身就有一个栈(MSP),RTOS在堆空间中还开辟了任务栈(任务创建时才有PSP)

STM32中的栈是向下生长的,向低地址生长

R14,链接寄存器(LR)用于函数或子程序调用时返回地址的保存。
若某函数需要调用另外一个函数或子程序,则它需要首先将LR的数值保存在栈中,否则,当执行了函数调用后,LR的当前值会丢失。
在异常/中断处理期间,LR也会被自动更新为特殊的EXC_RETURN(异常返回)数值,之后该数值会在异常处理结束时触发异常返回。
LR的第0位可以设定ARM内核的状态:ARM状态(0)/Thumb状态(1),但M3内核无ARM状态

程序计数器 R15:程序计数器,在汇编代码中你也可以使用名 PC 来访问它;指示当前程序执行位置,若是读取PC的话将返回当前指令的地址+4;
 

当程序PC指向Sort()函数执行时,会将Num++的地址保存在LR中,当printf()执行时,会将Q--的地址保存在LR中,在保存之前LR中原来的保存的Num++地址会压到MSP中。当printf()执行完毕后,Q--的地址会赋值给PC;当Q--执行完以后会将Num++的地址赋值给PC。

1.2 特殊功能寄存器

1.2.1中断屏蔽寄存器组

256个

STM32有16(确切的说15)个异常,240个外部中断 

PRIMASK寄存器(只有第0位有效)
<1> 第0位=1:阻止不可屏蔽中断(NMI)和HardFault异常之外的所有异常(包括中断)。
实际上,它是将当前异常优先级提升为0,这也是可编程异常/中断的最高优先级。
<2> 第0位=0:使能中断;
PRIMASK最常见的用途为,在时间要求很严格的进程中禁止所有中断,在该进程完成后,需要将PRIMASK清除以重新使能中断。

FAULTMASK寄存器 (只有第0位有效)
<1> 第0位=1:阻止不可屏蔽中断(NMI)之外的所有异常(包括中断)。
实际上,是将异常优先级提升到了-1。错误处理代码可以使用FAULTMASK以免在错误处理期间引发其他的错误。
<2> 第0位=0:使能中断;
与PRIMASK不同,FAULTMASK在异常返回时会被自动清除

BASEPRI寄存器
允许用户设置一个优先级阈值(BASEPRI的宽度取决于中断优先级位数),所有优先级数值大于或等于该阈值的中断将被屏蔽(即暂时禁止响应),而优先级低于阈值的中断仍可正常响应。这种机制提供了比全局中断开关(如PRIMASK)更灵活的中断控制方式,适用于需要动态调整中断响应优先级的场景。

 CONTROL寄存器 (只能在特权模式下被修改)

<1>在 Cortex‐M3 的 handler 模式(中断模式)中,CONTROL[1]总是 0。在线程模式中则可以为 0 或 1。仅当处于特权级的线程模式下,此位才可写,其它场合下禁止写此位。改变处理器的模式也有其它的方式:在异常返回时,通过修改 LR 的位 2,也能实现模式切换。
<2> 仅当在特权级(特权级可以访问很多寄存器)下操作时才允许写CONTROL[0]位。一旦进入了用户级,唯一返回特权级的途径,就是触发一个(软)中断,再由服务例程改写该位。

1.3 操作模式

Cortex‐M3 支持 2 个模式和两个特权等级;当处理器处在线程状态下时,既可以使用特权级,也可以使用用户级;当处理器处在handler下时,总是特权级的。在复位后,处理器进入线程模式+特权级。

1.4 双堆栈

当 CONTROL[1]=0 时,只使用 MSP,此时用户程序和异常 handler 共享同一个堆栈。这也是复位后的默认

CONTROL[1]=0时的堆栈使用情况

当 CONTROL[1]=1 时,线程模式将不再使用 MSP,而改用 PSP(handler 模式永远使用MSP)此时,进入异常时的自动压栈使用的是进程堆栈,进入异常 handler 后才自动改为 MSP,退出异常时切换回 PSP,并且从进程堆栈上弹出数据

CONTROL[1]=1时的堆栈使用情况

由于中断服务函数不是任务,中断服务函数使用的栈一定为主堆栈,通过MSP访问

二、FreeRTOS任务创建

2.1任务TCB核心成员

typedef struct tskTaskControlBlock
{
  volatile StackType_t * pxTopOfStack        ; //uint32_t *用于保存栈的栈顶的指针
  ListItem_t xStateListItem                  ; //状态列表项【链表节点】,还未插入链表
  ListItem_t xEventListItem                  ; //事件列表项【链表节点】,还未插入链表
  UBaseType_t uxPriority                     ; //任务优先级
  StackType_t * pxStack                      ; //任务栈底
  char pcTaskName[configMAX_TASK_NAME_LEN]   ; //保存任务名
  #if ( configUSE_TASK_NOTIFICATIONS == 1 )
    volatile uint32_t ulNotifiedValue[configTASK_NOTIFICATION_ARRAY_ENTRIES];
    volatile uint8_t ucNotifyState[configTASK_NOTIFICATION_ARRAY_ENTRIES ];
  #endif
} tskTCB;
typedef tskTCB TCB_t;

<1>pxTopOfStack
作用:指向任务堆栈的栈顶指针,用于保存和恢复任务上下文(如寄存器值)。
关键性:必须是TCB的第一个成员,因为FreeRTOS内核直接通过指针偏移量访问该字段,确保上下文切换效率。
<2>任务状态与事件管理
xStateListItem :用于将任务挂接到就绪、阻塞或挂起队列。
xEventListItem :用于将任务挂接到事件队列(如队列、信号量)。
工作原理:
当任务就绪时, xStateListItem 被添加到就绪列表;
当任务等待事件时, xEventListItem 被添加到对应事件的等待列表
<3> 优先级管理
uxPriority :存储任务优先级(数值越大优先级越高)。
调度依据:调度器根据 uxPriority 决定任务执行顺序,支持动态优先级调整(通vTaskPrioritySet() 函数)
<4>栈信息
pxStack :指向任务栈的起始地址(栈底)。pxEndOfStack (可选):指向栈尾,用于检测栈溢出(需配置configRECORD_STACK_HIGH_ADDRESS )
<5> 任务名称
pcTaskName :字符数组,存储任务名称(字符串形式),用于调试和日志记录。
<6> 任务通知
configUSE_TASK_NOTIFICATIONS 是一个编译时配置选项,用于启用或禁用任务通知(Task Notifications)功能。
当该选项设置为 1 时,FreeRTOS会在任务控制块(TCB)中添加与任务通知相关的字段,以支持轻量级的任务间通信机制。
  ①ulNotifiedValue
  类型: volatile uint32_t 数组(大小为configTASK_NOTIFICATION_ARRAY_ENTRIES )
  作用:存储任务通知的值。每个通知可以携带一个32位无符号整数(如状态码、数据指针等)
  ②ucNotifyState
      作用:记录每个通知的当前状态,可能的取值包括:
      taskNOT_WAITING_NOTIFICATION (0):任务未等待通知
      taskWAITING_NOTIFICATION (1):任务正在等待通知(如阻塞在ulTaskNotifyTake()
      taskNOTIFICATION_RECEIVED (2):任务已收到通知,但尚未处理

2.2 任务创建 /初始化

创建任务

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                            const char * const pcName, 
                            const configSTACK_DEPTH_TYPE usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask )
    {
        TCB_t * pxNewTCB;                                                 //<1> 定义一个TCB_T指针,保存后期从堆区申请TCB空间地址
        BaseType_t xReturn;                                               //<2> 定义返回值变量
                                                                          
        #if ( portSTACK_GROWTH > 0 )
            {
                 .......//此部分代码是栈从低地址向高地址增长
            }
        #else   //此部分代码是栈从高地址向低地址增长:STM32栈空间使用这种
            {
                StackType_t * pxStack;                                    //<3> 定义任务栈底指针,因为FreeRTOS从自己管理的堆中分配,首地址为低地址
                pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );                                                        //<4> 为当前创建的任务分配栈空间,大小=usStackDepth*4
                if( pxStack != NULL )                                     //如果栈空间分配成功
                {
                    pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); //<5> 继续从FreeRTOS管理的空间中分配TCB_T大小的空间,该空间用于保存当前创建的任务信息
                    if( pxNewTCB != NULL )
                    {
                        /* Store the stack location in the TCB. */
                        pxNewTCB->pxStack = pxStack;                        //<6> 如果TCB空间开辟成功,则将任务栈底指针保存至TCB成员中的pxStack
                    }
                    else
                    {
                        vPortFreeStack( pxStack );                          //<7> 如果TCB空间开辟失败,则释放之前申请的任务栈空间
                    }
                }
                else
                {
                    pxNewTCB = NULL;
                }
            }
        #endif 
        if( pxNewTCB != NULL )
        {
            #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) 
                {
                    .......
                }
            #endif
    //<8> 如果任务栈和TCB空间都分配成功,则使用下面函数来初始化当前任务
            prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
            prvAddNewTaskToReadyList( pxNewTCB );                          //<9> 将新任务添加到就绪列表
            xReturn = pdPASS;                                              //<10> 返回pdPASS
        }
        else
        {
            xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
        }

        return xReturn;
    }

 初始化任务

static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
                                  const char * const pcName, 
                                  const uint32_t ulStackDepth,
                                  void * const pvParameters,
                                  UBaseType_t uxPriority,
                                  TaskHandle_t * const pxCreatedTask,
                                  TCB_t * pxNewTCB,
                                  const MemoryRegion_t * const xRegions )
{
    StackType_t * pxTopOfStack;                                //<1> 定义栈顶指针,用于保存从FreeRTOS管理的堆区申请的任务栈的栈顶
    UBaseType_t x;
   //<块1>:设置栈顶指针并向8Byte对齐
    #if ( portSTACK_GROWTH < 0 ) //栈生长方向:由高到低生长
        {
            pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );//<2> 栈顶指针赋值:栈顶位置为pxStack[ulStackDepth - 1]
            pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); 
/*<3> 栈指针对齐的关键操作pxTopOfStack = pxTopOfStack & ~portBYTE_ALIGNMENT_MASK=pxTopOfStack & ~0x0007注意:STM32处理器的栈为8Byte对齐*/
           
            configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );

    #else //栈生长方向:由低到高生长
        {
        }
    #endif 

    //<块2> 设置任务名称
    if( pcName != NULL )
    {
    //<4> 循环将pcName的字符填入到pxNewTCB->pcTaskName中
        for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
        {
            pxNewTCB->pcTaskName[ x ] = pcName[ x ];

            if( pcName[ x ] == ( char ) 0x00 )
            {
                break;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

        //<5> 字符串结束标志
        pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
    }
    else
    {
        pxNewTCB->pcTaskName[ 0 ] = 0x00;
    }

    //<块3>:任务优先级设置
    configASSERT( uxPriority < configMAX_PRIORITIES );

    if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
    {
       //<6> 如果任务优先级超出设定的最大值,则当前任务优先级为最大值-1
        uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    pxNewTCB->uxPriority = uxPriority;
    #if ( configUSE_MUTEXES == 1 )
        {
            pxNewTCB->uxBasePriority = uxPriority;
            pxNewTCB->uxMutexesHeld = 0;
        }
    #endif 
   //<块4>:初始化状态列表项 和 事件列表项
    vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
   //<1> 设置状态列表项的的所属容器为NULL
    vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
   //<2> 设置事件列表项的的所属容器为NULL;
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
   //<3> 设置状态链表项节点的所属对象为当前任务
    listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
   //<4> 设置事件列表项节点的值=configMAX_PRIORITIES - uxPriority
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
  //<5> 设置事件链表项节点的所属对象为当前任务
    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        {
            pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
        }
    #endif /* portCRITICAL_NESTING_IN_TCB */

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        {
            pxNewTCB->pxTaskTag = NULL;
        }
    #endif /* configUSE_APPLICATION_TASK_TAG */

    #if ( configGENERATE_RUN_TIME_STATS == 1 )
        {
            pxNewTCB->ulRunTimeCounter = ( configRUN_TIME_COUNTER_TYPE ) 0;
        }
    #endif /* configGENERATE_RUN_TIME_STATS */

    #if ( portUSING_MPU_WRAPPERS == 1 )
        {
            vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
        }
    #else
        {
            /* Avoid compiler warning about unreferenced parameter. */
            ( void ) xRegions;
        }
    #endif

    #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
        {
            memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );
        }
    #endif

    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        {
            memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );
            memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );
        }
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        {
            /* Initialise this task's Newlib reent structure.
             * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
             * for additional information. */
            _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
        }
    #endif

    #if ( INCLUDE_xTaskAbortDelay == 1 )
        {
            pxNewTCB->ucDelayAborted = pdFALSE;
        }
    #endif
    #if ( portUSING_MPU_WRAPPERS == 1 )
        {
            #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
                {
                    #if ( portSTACK_GROWTH < 0 )
                        {
                            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );
                        }
                    #else /* portSTACK_GROWTH */
                        {
                            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );
                        }
                    #endif /* portSTACK_GROWTH */
                }
            #else /* portHAS_STACK_OVERFLOW_CHECKING */
                {
                    pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
                }
            #endif /* portHAS_STACK_OVERFLOW_CHECKING */
        }
    #else /* portUSING_MPU_WRAPPERS */
        {
            #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
                {
                    #if ( portSTACK_GROWTH < 0 )
                        {
                            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
                        }
                    #else /* portSTACK_GROWTH */
                        {
                            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
                        }
                    #endif /* portSTACK_GROWTH */
                }
            #else /* portHAS_STACK_OVERFLOW_CHECKING */
                {
                    pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
                }
            #endif /* portHAS_STACK_OVERFLOW_CHECKING */
        }
    #endif /* portUSING_MPU_WRAPPERS */

    if( pxCreatedTask != NULL )
    {
      //pxCreatedTask:任务句柄,传出参数,本质为新创建的任务的TCB的指针
        *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

我的理解:
当创建任务时,FreeRTOS系统会在自己管理的堆区中开辟:
任务的任务栈空间,大小为 ( ( size_t ) usStackDepth ) * sizeof( StackType_t );
以及对应TCB的所需要的空间并将TCB的地址赋值给变量pxNewTCB;
由于任务栈是向下生长(向低地址)且申请栈空间返回低地址,
会将任务栈的栈底地址赋值给TCB中的pxStack变量;
并将任务栈的栈顶地址赋值给prvInitialiseNewTask中的变量pxTopOfStack
 

pxTopOfStack 是任务运行时的当前栈顶指针,会随着任务的执行(压栈/弹栈)动态变化
TCB 所持有的内存空间不是栈,而是用于管理任务元数据的固定大小的数据结构体存储区

2.3 入栈  

当CM3开始响应一个中断时,会在它看不见的体内奔涌起三股暗流
<1> 入栈:把8个寄存器的值压入栈
<2> 取向量:从向量表中找出对应的服务程序入口地址
<3> 选择堆栈指针MSP/PSP,更新堆栈指针SP,更新连接寄存器LR,更新程序计数器PC

响应异常的第一个行动,就是自动保存现场的必要部分:依次把xPSR, PC, LR, R12以及R3‐R0由硬件自动压入适当的堆栈中:如果当响应异常时,当前的代码正在使用PSP,则压入PSP,即使用线程堆栈;否则压入MSP,使用主堆栈。一旦进入了服务例程,就将一直使用主堆栈
 

 入栈在机器的内部,并不是严格按堆栈操作的顺序的——但是机器会保证:正确的寄存器将被保存到正确的位置。先把PC与xPSR的值保存,就可以更早地启动服务例程指令的预取——因为这需要修改PC;同时,也做到了在早期就可以更新xPSR中IPSR位段的值。

2.4 取向量

当数据总线(系统总线)正在为入栈操作而忙得团团转时,指令总线(I‐Code总线)可不是凉快地坐着看热闹——它正在为响应中断紧张有序地执行另一项重要的任务:从向量表中找出正确的异常向量,然后在服务程序的入口处预取指。由此可以看到各自都有专用总线的好处:入栈与取指这两个工作能同时进行

2.5 更新寄存器

在入栈和取向量的工作都完毕之后,执行服务例程之前,还要更新一系列的寄存器
<1> SP:在入栈中会把堆栈指针(PSP或MSP)更新到新的位置。在执行服务例程后,将由MSP负责对堆栈的访问。
<2> PSR:IPSR位段(地处PSR的最低部分)会被更新为新响应的异常编号。
<3> PC:在向量取出完毕后,PC将指向服务例程的入口地址。
<4> LR:LR的用法将被重新解释,其值也被更新成一种特殊的值,称为“EXC_RETURN”,并且在异常返回时使用。EXC_RETURN的二进制值除了最低4位外全为1,而其最低4位则有另外的含义。

2.6 异常返回 

在进入异常服务程序后,LR的值被自动更新为特殊的EXC_RETURN,这是一个高28位全为1的值,只有[3:0]的值有特殊含义。当异常服务例程把这个值送往PC时,就会启动处理器的中断返回序列。因为LR的值是由CM3自动设置的,所以只要没有特殊需求,就不要改动它

图1 当有中断时的LR寄存器

<1>当没有中断发生时,LR寄存器用于保存进入函数前的下一条语句的地址,当函数执行完用于返回
<2>当有中断发生时而没有OS时(如图1所示),若没有OS主堆栈指针位MSP,当中断事件时,LR=0XFFFF_FFF9,主堆栈指针为MSP(Handler模式),中断进行嵌套时,LR=0XFFFF_FFF1...
<3>当有中断发生时有OS时(如图1所示,将线程模式替换为进程模式),有OS主堆栈指针位PSP,当中断事件时,LR=0XFFFF_FFF9,主堆栈指针为MSP(Handler模式),中断进行嵌套时,LR=0XFFFF_FFF1...

2.7 就绪链表

FreeRTOS会将相同优先级的任务放在相同的链表中,这些链表会由就绪链表数组来管理,这个数组的每个元素对应一个优先级的就绪链表,调度器在切换任务时,会遍历这个数组,找到最高优先级的非空就绪链表,并从中选择下一个要执行的任务

 三、任务调度

在 FreeRTOS(运行于 STM32,基于 ARM Cortex-M 内核)中,SVC、SysTick 和 PendSV 是三个关键的系统异常(异常号分别为 11、15、14),它们共同协作实现任务调度、上下文切换和系统心跳

当调用 vTaskStartScheduler()时,FreeRTOS会创建两个任务,一个是空闲任务,一个是软件定时器服务任务;若是一个任务被另一个任务删除,则被删除的任务资源会立即释放,若是一个任务删除它自身,则空闲任务释放这个被删除任务的资源;
首次调度任务时,系统通常通过 SVC 指令触发 SVC 异常,在特权模式下初始化任务的栈和寄存器状态
SysTick系统定时器提供系统时间基准(通常配置为 1ms 或 10ms),驱动 FreeRTOS 的时间片调度和延时函数;在 SysTick 中断服务程序(ISR)中,检查是否需要切换任务(如时间片用完或更高优先级任务就绪
PendSV 是用于在中断(如 SysTick)退出后执行实际的任务上下文切换,避免在中断中直接切换导致不可预测的延迟。

注意:在开启调度时(xPortStartScheduler(  )),FreeRTOS会将PendSV以及SysTick中断优先级设置为最低;系统自动的通过vPortSetupTimerInterrupt()函数设定中断频率


网站公告

今日签到

点亮在社区的每一天
去签到