08.FreeRTOS任务调度与任务切换

news2024/11/23 10:50:13

文章目录

  • 08. FreeRTOS任务调度与任务切换
    • 1. FreeRTOS任务调度
    • 2. SVC中断服务函数源码调试结果分析
    • 3. FreeRTOS任务切换
      • 3.1 PendSV异常
      • 3.2 PendSV中断服务函数
      • 3.3 PendSV中断服务函数源码调试分析
      • 3.4 确定下一个要执行的任务

08. FreeRTOS任务调度与任务切换

1. FreeRTOS任务调度

任务调度的流程:

在这里插入图片描述

  1. 调用vTaskStartScheduler()函数,开启任务调度;
  2. vTaskStartScheduler()函数中调用xPortStartScheduler()函数,此函数主要用于启动任务调度器中与硬件架构相关的配置部分,以及开启第一个任务。配置PendSV和SysTick的中断优先级为最低优先级,配置SysTick中断并初始化临界区嵌套计数器;
  3. xPortStartScheduler()函数调用prvStartFirstTask()函数,从向量表中获取栈顶指针、配置主堆栈MSP、启动中断、触发SVC中断服务函数来调用操作系统服务;
  4. prvStartFirstTask()函数中触发SVC中断服务函数vPortSVCHandler(),用来读取当前任务控制块的栈顶指针,恢复保存的寄存器值,设置任务的栈指针为PSP,清除中断优先级掩码。
  • vTaskStartScheduler()函数具体实现:

在这里插入图片描述

void vTaskStartScheduler( void )
{
    BaseType_t xReturn;

    /* Add the idle task at the lowest priority. */
	//1、创建空闲任务
    #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
        {
            StaticTask_t * pxIdleTaskTCBBuffer = NULL;
            StackType_t * pxIdleTaskStackBuffer = NULL;
            uint32_t ulIdleTaskStackSize;

            /* The Idle task is created using user provided RAM - obtain the
             * address of the RAM then create the idle task. */
            vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
            xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
                                                 configIDLE_TASK_NAME,
                                                 ulIdleTaskStackSize,
                                                 ( void * ) NULL,       /*lint !e961.  The cast is not redundant for all compilers. */
                                                 portPRIVILEGE_BIT,     /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
                                                 pxIdleTaskStackBuffer,
                                                 pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */

            if( xIdleTaskHandle != NULL )
            {
                xReturn = pdPASS;
            }
            else
            {
                xReturn = pdFAIL;
            }
        }
    #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
        {
            /* The Idle task is being created using dynamically allocated RAM. */
            xReturn = xTaskCreate( prvIdleTask,
                                   configIDLE_TASK_NAME,
                                   configMINIMAL_STACK_SIZE,
                                   ( void * ) NULL,
                                   portPRIVILEGE_BIT,  /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
                                   &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
        }
    #endif /* configSUPPORT_STATIC_ALLOCATION */
	
	//2.如果使能软件定时器,则创建定时器任务
    #if ( configUSE_TIMERS == 1 )
        {
            if( xReturn == pdPASS )
            {
                xReturn = xTimerCreateTimerTask();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    #endif /* configUSE_TIMERS */

    if( xReturn == pdPASS )
    {
        /* freertos_tasks_c_additions_init() should only be called if the user
         * definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
         * the only macro called by the function. */
        #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
            {
                freertos_tasks_c_additions_init();
            }
        #endif

        /* Interrupts are turned off here, to ensure a tick does not occur
         * before or during the call to xPortStartScheduler().  The stacks of
         * the created tasks contain a status word with interrupts switched on
         * so interrupts will automatically get re-enabled when the first task
         * starts to run. */
		//关中断
        portDISABLE_INTERRUPTS();

        #if ( configUSE_NEWLIB_REENTRANT == 1 )
            {
                /* Switch Newlib's _impure_ptr variable to point to the _reent
                 * structure specific to the task that will run first.
                 * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
                 * for additional information. */
                _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
            }
        #endif /* configUSE_NEWLIB_REENTRANT */
		
        //下一个距离取消任务阻塞的时间,初始化为最大值
        xNextTaskUnblockTime = portMAX_DELAY;
        //任务调度器运行标志,设为已运行
        xSchedulerRunning = pdTRUE;
        //系统使用节拍计数器,宏 configINITIAL_TICK_COUNT 默认为 0
        xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;

        /* If configGENERATE_RUN_TIME_STATS is defined then the following
         * macro must be defined to configure the timer/counter used to generate
         * the run time counter time base.   NOTE:  If configGENERATE_RUN_TIME_STATS
         * is set to 0 and the following line fails to build then ensure you do not
         * have portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() defined in your
         * FreeRTOSConfig.h file. */
        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

        traceTASK_SWITCHED_IN();

        /* Setting up the timer tick is hardware specific and thus in the
         * portable interface. */
		 //该函数用于完成启动任务调度器中与硬件架构相关的配置部分,
		 //以及启动第一个任务
        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(). */
        }
    }
    else
    {
        /* This line will only be reached if the kernel could not be started,
         * because there was not enough FreeRTOS heap to create the idle task
         * or the timer task. */
        configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
    }

    /* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
     * meaning xIdleTaskHandle is not used anywhere else. */
    ( void ) xIdleTaskHandle;

    /* OpenOCD makes use of uxTopUsedPriority for thread debugging. Prevent uxTopUsedPriority
     * from getting optimized out as it is no longer used by the kernel. */
    ( void ) uxTopUsedPriority;
}
  • xPortStartScheduler()函数具体实现:

在这里插入图片描述

BaseType_t xPortStartScheduler( void )
{
	//1.检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误
    #if ( configASSERT_DEFINED == 1 )
        {
            volatile uint32_t ulOriginalPriority;
            volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
            volatile uint8_t ucMaxPriorityValue;

            /* Determine the maximum priority from which ISR safe FreeRTOS API
             * functions can be called.  ISR safe functions are those that end in
             * "FromISR".  FreeRTOS maintains separate thread and ISR API functions to
             * ensure interrupt entry is as fast and simple as possible.
             *
             * Save the interrupt priority value that is about to be clobbered. */
            ulOriginalPriority = *pucFirstUserPriorityRegister;

            /* Determine the number of priority bits available.  First write to all
             * possible bits. */
            *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;

            /* Read the value back to see how many bits stuck. */
            ucMaxPriorityValue = *pucFirstUserPriorityRegister;

            /* The kernel interrupt priority should be set to the lowest
             * priority. */
            configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue ) );

            /* Use the same mask on the maximum system call priority. */
            ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;

            /* Calculate the maximum acceptable priority group value for the number
             * of bits read back. */
            ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;

            while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
            {
                ulMaxPRIGROUPValue--;
                ucMaxPriorityValue <<= ( uint8_t ) 0x01;
            }

            #ifdef __NVIC_PRIO_BITS
                {
                    /* Check the CMSIS configuration that defines the number of
                     * priority bits matches the number of priority bits actually queried
                     * from the hardware. */
                    configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
                }
            #endif

            #ifdef configPRIO_BITS
                {
                    /* Check the FreeRTOS configuration that defines the number of
                     * priority bits matches the number of priority bits actually queried
                     * from the hardware. */
                    configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
                }
            #endif

            /* Shift the priority group value back to its position within the AIRCR
             * register. */
            ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
            ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;

            /* Restore the clobbered interrupt priority register to its original
             * value. */
            *pucFirstUserPriorityRegister = ulOriginalPriority;
        }
    #endif /* configASSERT_DEFINED */

    /* Make PendSV and SysTick the lowest priority interrupts. */
	//2.配置PendSV和SysTick的中断优先级为最低优先级
    portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;

    portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;

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

    /* Initialise the critical nesting count ready for the first task. */
	//4.初始化临界区嵌套计数器为0
    uxCriticalNesting = 0;

    /* Start the first task. */
	//5.启动第一个任务
    prvStartFirstTask();

    /* Should not get here! */
    return 0;
}
  • prvStartFirstTask()函数具体实现:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

__asm void prvStartFirstTask( void )
{
	//8字节对齐
    PRESERVE8

    //0xE000ED08 为 VTOR 地址
    ldr r0, =0xE000ED08
	//从 0xE000ED08 读取值到 r0。这将获取 Vector Table 的基地址
    ldr r0, [ r0 ]
	//再次从 Vector Table 的基地址中读取值,得到主栈指针(MSP)的初始值。这个值是任务栈的起始地址
    ldr r0, [ r0 ]

	//将 r0 中的值设置为主堆栈指针 (MSP)。这将栈指针重置到任务栈的起始位置。
    msr msp, r0
	//启用中断(控制寄存器 CPSR 中的 I 位),允许处理器响应 IRQ(中断请求)
    cpsie i
	//启用快速中断(控制寄存器 CPSR 中的 F 位),允许处理器响应 FIQ(快速中断请求)
    cpsie f
	//数据同步屏障,确保所有的数据访问操作完成
    dsb
	//指令同步屏障,确保所有的指令完成
    isb
	//执行一个 SVC (SuperVisor Call) 指令,这会触发一个 SVC 中断。这个中断通常用于调用操作系统的内核服务来启动第一个任务
    svc 0
	//空操作指令
    nop
    nop
}
  • vPortSVCHandler()函数具体实现:

在这里插入图片描述

__asm void vPortSVCHandler( void )
{
    PRESERVE8
	
	//将 pxCurrentTCB(当前任务控制块的地址)加载到寄存器 r3。
	//pxCurrentTCB 是一个指向当前任务控制块的指针,该指针存储了任务的状态信息
    ldr r3, = pxCurrentTCB   
	//从 pxCurrentTCB 地址中读取任务控制块的实际地址,将其加载到寄存器 r1。这实际上是获取当前任务控制块的地址
    ldr r1, [ r3 ] 
	//从任务控制块中读取任务栈顶指针(栈指针),将其加载到寄存器 r0。任务控制块的第一个字段通常是任务栈的起始地址	
    ldr r0, [ r1 ]          
	//从栈中弹出寄存器 r4 到 r11 的值。这些寄存器是在任务上下文中保存的寄存器,
	//并且在异常入口时不会自动保存,因此需要在恢复任务上下文时手动恢复
    ldmia r0 !, { r4 - r11 } 
    //将任务的栈指针(从 r0 中获取的值)设置为进程栈指针(PSP)。这恢复了当前任务的栈指针
	msr psp, r0 
    isb
	
	//将 0 值加载到寄存器 r0
    mov r0, # 0
	//将 r0 的值(0)写入 BASEPRI 寄存器。BASEPRI 寄存器用于设置中断优先级掩码,0 表示不屏蔽任何中断
    msr basepri, r0
	
	//将 0xd(即条件码 0xd,表示 BX 指令的目标地址具有链接状态)或到寄存器 r14(也称为 lr,链接寄存器)中
    orr r14, # 0xd
	//使用 PSP 指针,并跳转到任务函数
    bx r14
}

2. SVC中断服务函数源码调试结果分析

  1. 软件定时器的任务创建

在这里插入图片描述

  1. 软件定时器任务创建完成,任务优先级为31,当前任务控制块的地址指向软件定时器任务的地址

在这里插入图片描述

  1. r3 指向优先级最高的就绪态任务的任务控制块,r1 为任务控制块地址

在这里插入图片描述

  1. r0 为任务控制块的第一个元素(栈顶)

在这里插入图片描述

  1. 栈顶元素出栈,栈顶指针变动

在这里插入图片描述

  1. 把任务的栈指针赋值给进程堆栈指针PSP,使能所有中断,使用PSP指针,并跳转到任务函数

在这里插入图片描述

3. FreeRTOS任务切换

在这里插入图片描述

在这里插入图片描述

3.1 PendSV异常

任务切换的过程在PendSV中断服务函数里边完成 ,PendSV 通过延迟执行任务切换,必须将 PendSV 的中断优先级设置为最低的中断优先等级。如果操作系统决定切换任务,那么就将 PendSV 设置为挂起状态,并在 PendSV 的中断服务函数中执行任务切换。切换示意图:

在这里插入图片描述

  1. 任务一触发 SVC 中断以进行任务切换(例如,任务一正等待某个事件发生)。
  2. 系统内核接收到任务切换请求,开始准备任务切换,并挂起 PendSV 异常。
  3. 当退出 SVC 中断的时候,立刻进入 PendSV 异常处理,完成任务切换。
  4. 当 PendSV 异常处理完成,返回线程模式,开始执行任务二。
  5. 中断产生,并进入中断处理函数。
  6. 当运行中断处理函数的时候,SysTick 异常(用于内核时钟节拍)产生。
  7. 操作系统执行必要的操作,然后挂起 PendSV 异常,准备进行任务切换。
  8. 当 SysTick 中断处理完成,返回继续处理中断。
  9. 当中断处理完成,立马进入 PendSV 异常处理,完成任务切换。
  10. 当 PendSV 异常处理完成,返回线程模式,继续执行任务一。

PendSV在RTOS的任务切换中,起着至关重要的作用,FreeRTOS的任务切换就是在PendSV中完成的。

在这里插入图片描述

3.2 PendSV中断服务函数

__asm void xPortPendSVHandler( void )
{
	//导入全局变量及定义
    extern uxCriticalNesting;
    extern pxCurrentTCB;
    extern vTaskSwitchContext;

	//8字节对齐
    PRESERVE8
	
	//R0 为 PSP,即当前运行任务的任务栈指针
    mrs r0, psp
    isb

	//R3为 pxCurrentTCB 的地址值,即指向当前运行任务控制块的指针
	//R2为 pxCurrentTCB 的值,即当前运行任务控制块的首地址
    ldr r3, =pxCurrentTCB 
    ldr r2, [ r3 ]
	
	//将 R4~R11 入栈到当前运行任务的任务栈中
    stmdb r0 !, { r4 - r11 } 
	//R2 指向的地址为此时的任务栈指针
    str r0, [ r2 ] 
	
	//将 R3、R14 入栈到 MSP 指向的栈中
    stmdb sp !, { r3, r14 }
	//屏蔽受 FreeRTOS 管理的所有中断
    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
    msr basepri, r0
    dsb
    isb
	//跳转到函数 vTaskSeitchContext
	//主要用于更新 pxCurrentTCB,
	//使其指向最高优先级的就绪态任务
    bl vTaskSwitchContext
	//使能所有中断
    mov r0, #0
    msr basepri, r0
	//将 R3、R14 重新从 MSP 指向的栈中出栈
    ldmia sp !, { r3, r14 }

	//注意:R3 为 pxCurrentTCB 的地址值,
    //pxCurrentTCB 已经在函数 vTaskSwitchContext 中更新为最高优先级的就绪态任务
    //因此 R1 为 pxCurrentTCB 的值,即当前最高优先级就绪态任务控制块的首地址
    ldr r1, [ r3 ]
	//R0 为最高优先级就绪态任务的任务栈指针
    ldr r0, [ r1 ] 
	//从最高优先级就绪态任务的任务栈中出栈 R4~R11
    ldmia r0 !, { r4 - r11 } 
	//更新 PSP 为任务切换后的任务栈指针
    msr psp, r0
    isb
	//跳转到切换后的任务运行
	//执行此指令,CPU 会自动从 PSP 指向的任务栈中,
	//出栈 R0、R1、R2、R3、R12、LR、PC、xPSR 寄存器,
	//接着 CPU 就跳转到 PC 指向的代码位置运行,
	//也就是任务上次切换时运行到的位置
    bx r14
    nop
}

从上面的代码可以看出,FreeRTOS在进行任务切换的时候,会将CPU的运行状态,在当前任务进行任务切换前,进行保存,保存到任务的任务栈中,然后从切换后运行任务的任务栈中恢复切换后运行任务在上一次被切换时保存的CPU信息。

但是从PendSV的中断回调函数代码中,只看到程序保存和恢复的CPU信息中的部分寄存器信息(R4寄存器~R11寄存器),这是因为硬件会自动出栈和入栈其他CPU寄存器的信息。

在任务运行的时候,CPU使用PSP作为栈空间使用,也就是使用运行任务的任务栈。当 SysTick中断(SysTick的中断服务函数会判断是否需要进行任务切换,相关内容在后续章节会进行讲解)发生时,在跳转到SysTick中断服务函数运行前,硬件会自动将除R4~R11寄存器的其他CPU寄存器入栈,因此就将任务切换前CPU的部分信息保存到对应任务的任务栈中。当退出PendSV时,会自动从栈空间中恢复这部分CPU信息,以共任务正常运行。

因此在PεndSV中断服务函数中,主要要做的事情就是,保存硬件不会自动入栈的CPU信息,已经确定写一个要运行的任务,并将pxCurrentTCB指向该任务的任务控制块,然后更新 PSP指针为该任务的任务堆栈指针。

3.3 PendSV中断服务函数源码调试分析

  1. PSP指向当前任务的任务栈顶指针

    在这里插入图片描述

  2. r0指向任务A的栈顶指针

    在这里插入图片描述

  3. r2指向当前任务控制块

    在这里插入图片描述

  4. 将r4~r11压栈,r0改变

    在这里插入图片描述

  5. 把r0写入栈顶指针

    在这里插入图片描述

  6. 将 R3、R14 入栈到 MSP 指向的栈中

    在这里插入图片描述

  7. 关中断,然后更新当前任务控制块,使其指向最高优先级的就绪任务,然后开中断

    在这里插入图片描述

    任务控制块的改变过程:

    在这里插入图片描述

  8. r0指向任务B的栈顶指针

    在这里插入图片描述

  9. 任务B的寄存器r4~r11出栈,加载到CPU的寄存器中,栈顶指针变化

    在这里插入图片描述

  10. CPU从psp指向出自动出栈

    在这里插入图片描述

3.4 确定下一个要执行的任务

  • vTaskSwitchContext()函数
void vTaskSwitchContext( void )
{
     /* 判断任务调度器是否运行 */
     if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
     {
         /* 此全局变量用于在系统运行的任意时刻标记需要进行任务切换
         * 会在 SysTick 的中断服务函数中统一处理
         * 任务任务调度器没有运行,不允许任务切换,
         * 因此将 xYieldPending 设置为 pdTRUE
         * 那么系统会在 SysTick 的中断服务函数中持续发起任务切换
         * 直到任务调度器运行
         */
         xYieldPending = pdTRUE;
     }	
     else
     {
         /* 可以执行任务切换,因此将 xYieldPending 设置为 pdFALSE */
         xYieldPending = pdFALSE;
         /* 用于调试,不用理会 */
         traceTASK_SWITCHED_OUT();

         /* 此宏用于使能任务运行时间统计功能,不用理会 */
         #if ( configGENERATE_RUN_TIME_STATS == 1 )
        {
            #ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
             	portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
            #else
             	ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
            #endif

            if( ulTotalRunTime > ulTaskSwitchedInTime )
            {
                pxCurrentTCB->ulRunTimeCounter +=
                    ( ulTotalRunTime - ulTaskSwitchedInTime );
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }

            ulTaskSwitchedInTime = ulTotalRunTime;
        }
        #endif

         /* 检查任务栈是否溢出,
         * 未定义,不用理会
         */
         taskCHECK_FOR_STACK_OVERFLOW();

         /* 此宏为 POSIX 相关配置,不用理会 */
        #if ( configUSE_POSIX_ERRNO == 1 )
        {
         	pxCurrentTCB->iTaskErrno = FreeRTOS_errno;
        }
        #endif

         /* 将 pxCurrentTCB 指向优先级最高的就绪态任务
         * 有两种方法,由 FreeRTOSConfig.h 文件配置决定
         */
         taskSELECT_HIGHEST_PRIORITY_TASK();
         /* 用于调试,不用理会 */
         traceTASK_SWITCHED_IN();

         /* 此宏为 POSIX 相关配置,不用理会 */
        #if ( configUSE_POSIX_ERRNO == 1 )
        {
         	FreeRTOS_errno = pxCurrentTCB->iTaskErrno;
        }
        #endif

         /* 此宏为 Newlib 相关配置,不用理会 */
        #if ( configUSE_NEWLIB_REENTRANT == 1 )
        {
         	_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
        }
        #endif
     }
}
  • 软件方式实现taskSELECT_HIGHEST_PRIORITY_TASK() 函数
#define taskSELECT_HIGHEST_PRIORITY_TASK() 
{ 
     /* 全局变量 uxTopReadyPriority 以位图方式记录了系统中存在任务的优先级 */ 
     /* 将遍历的起始优先级设置为这个全局变量, */
     /* 而无需从系统支持优先级的最大值开始遍历, */ 
     /* 可以节约一定的遍历时间 */ 
     UBaseType_t uxTopPriority = uxTopReadyPriority; 

     /* Find the highest priority queue that contains ready tasks. */ 
     /* 按照优先级从高到低,判断对应的就绪态任务列表中是否由任务, */ 
     /* 找到存在任务的最高优先级就绪态任务列表后,退出遍历 */ 
     while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) 
     { 
         configASSERT( uxTopPriority ); 
         --uxTopPriority;
      } 

     /* 从找到了就绪态任务列表中取下一个任务, */ 
     /* 让 pxCurrentTCB 指向这个任务的任务控制块 */ 
     listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, 
     &( pxReadyTasksLists[ uxTopPriority ] ) ); 
     /* 更新任务优先级记录 */ 
     uxTopReadyPriority = uxTopPriority; 
}
  • 硬件方式实现taskSELECT_HIGHEST_PRIORITY_TASK() 函数
#define taskSELECT_HIGHEST_PRIORITY_TASK() 
{ 
     UBaseType_t uxTopPriority; 

     /* 使用硬件方式从任务优先级记录中获取最高的任务优先等级 */ 
     portGET_HIGHEST_PRIORITY(uxTopPriority,uxTopReadyPriority); 
     configASSERT(listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[uxTopPriority])) > 0); 
     /* 从获取的任务优先级对应的就绪态任务列表中取下一个任务 */ 
     /* 让 pxCurrentTCB 指向这个任务的任务控制块 */ 
     listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &(pxReadyTasksLists[uxTopPriority])); 
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1986824.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MIMO技术入门(通俗易懂)

MIMO技术的思路 形象地形容就是&#xff0c;从原来的一个人在搬砖&#xff0c;转变成多个人在搬砖。 MIMO/SIMO/MISO示意图 MIMO用专业一点的词形容&#xff0c;就是发射端和接收端都有多个天线&#xff0c;这里的多天线并不是指有多个天线板&#xff0c;对于基站来说&#…

Ubuntu distro环境搭建

0 Preface/Foreword 1 环境搭建 1.1 安装make工具 sudo apt install make 1.1.1 查看make版本 1.1.2 查看make使用方法 2 搭建交叉编译工具链 2.1 解压交叉工具链到指定路径 命令解释如下&#xff1a; sudo&#xff0c; 表示使用administrative privilegetar&#xff0c;…

Unity补完计划 之 Mask SortingGroup

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正 1.Mask 遮罩故名思意就是起到遮挡作用的罩子:精灵遮罩 - Unity 手册 如果我想让sprite与遮罩发生交互&#xff0c;那么我…

宇哥18讲需要同步搭配他的1000题吗?

张宇老师本来就以“偏难怪”著称&#xff0c;无独有偶&#xff0c;24考研真题也是“偏难怪”&#xff01; 所以&#xff0c;24考研结束之后&#xff0c;大家欧鼓吹张宇「封神」 先不说张宇老师是不是真的符合考研的趋势&#xff0c;但是跟张宇老师的同学确实比跟其他老师的同…

工具变量模型及 Stata 具体操作步骤

目录 一、引言 二、文献综述 三、理论原理 四、实证模型 五、程序代码及解释 六、代码运行结果 一、引言 在实证研究中&#xff0c;我们常常面临内生性问题&#xff0c;即解释变量与误差项相关&#xff0c;这可能导致估计结果的偏差和不一致。工具变量&#xff08;Instrum…

<数据集>agv仓储机器人识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;1514张 标注数量(xml文件个数)&#xff1a;1514 标注数量(txt文件个数)&#xff1a;1514 标注类别数&#xff1a;3 标注类别名称&#xff1a;[G1PB2000_Paleteira_AGVS BYD, G1RB5000, AGV-P] 序号类别名称图片数…

Web性能监测的利器Performance Observer!!

前言 前段时间在研究前端异常监控平台&#xff0c;在思考性能监控时&#xff0c;想到了浏览器自带的观察者以及页面生命周期API 。于是在翻查资料时发现了&#xff0c;Performance ObserverAPI。正好趁着这个机会给大家好好讲讲Performance Observer API Performance Observe…

ArcGIS基础:二维面要素生成三维体模型

如下&#xff0c;为处理前的二维数据图斑以及需要用到的字段【高度】 使用ArcScene软件打开&#xff0c;找到【拉伸】设置&#xff0c;按照下述顺序进行设置参数&#xff0c;在【拉伸值或表达式】里选择【高度】字段 如下所示&#xff0c;就可以完成二维数据的三维表达&#x…

国产版Sora复现——智谱AI开源CogVideoX-2b 本地部署复现实践教程

目录 一、CogVideoX简介二、CogVideoX部署实践流程2.1、创建丹摩实例2.2、配置环境和依赖2.3、上传模型与配置文件2.4、开始运行 最后 一、CogVideoX简介 智谱AI在8月6日宣布了一个令人兴奋的消息&#xff1a;他们将开源视频生成模型CogVideoX。目前&#xff0c;其提示词上限为…

2020C题 中小微企业的信贷决策论文分析复现笔记

比赛题目&#xff1a; 在实际中&#xff0c;由于中小微企业规模相对较小&#xff0c;也缺少抵押资产&#xff0c;因此银行通常是依据信贷政策、企业的交易票据信息和上下游企业的影响力&#xff0c;向实力强、供求关系稳定的企业提供贷款&#xff0c;并可以对信誉高、信贷风险…

“MySQL中的空间索引技术深度解析:利用Spatial Key优化地理空间数据查询“

目录 简介 空间数据类型 GEOMETRY&#xff08;抽象基类&#xff09; POINT LINESTRING POLYGON MULTIPOINT MULTILINESTRING MULTIPOLYGON GEOMETRYCOLLECTION 总结 函数 ST_GeomFromText() ST_X ST_Y ST_AsText 空间函数 总结 坐标系 WGS 84坐标系&#x…

03 LVS+Keepalived群集

3.1 Keepalived 双机热备基础知识 Keepalived 起初是专门针对 LVS 设计的一款强大的辅助工具&#xff0c;主要用来提供故障切换(Failover)和健康检査(HealthChecking)功能--判断LVS 负载调度器、节点服务器的可用性&#xff0c;当 master 主机出现故障及时切换到 backup 节点保…

(二十二)【Jmeter】深入刨析监听器:常用监听器常用使用场景、优缺点及操作实例

简述 在Jmeter中,监听器(Listener)是一个重要的组件,用于收集和显示测试结果。监听器的作用包括: 收集测试结果:监听器可以实时收集JMeter测试执行过程中的数据,包括请求和响应的各种信息。显示测试结果:监听器可以将收集到的测试结果以不同的方式展示出来,例如以树状…

【第七节】python多线程及网络编程

目录 一、python多线程 1.1 多线程的作用 1.2 python中的 threading 模块 1.3 线程锁 二、python网络编程 2.1 通过socket访问网络 2.2 python2.x中的编码问题 2.3 python3的编码问题 一、python多线程 1.1 多线程的作用 多线程技术在计算机编程中扮演着重要的角色&a…

五大避坑要点,让你轻松避开99%的雷区!随身wifi京东口碑排行榜,随身wifi推荐第一名!

在数字浪潮中&#xff0c;随身WiFi成为我们不可或缺的伴侣&#xff0c;但市场纷繁复杂&#xff0c;如何挑选成为难题。以下五大避坑要点&#xff0c;让你轻松避开99%的雷区&#xff01; 1.避小就大&#xff0c;信赖旗舰店&#xff1a;远离无名小品牌&#xff0c;选择知名品牌的…

Javacript 高级程序设计(系统学习)

以下为阅读 《Javacript 高级程序设计》部分笔记&#xff0c;待继续完善&#xff0c;后续会进行章节拆分。 第1章 什么是 javascript 历史回顾js 实现 / es / dom / bomjs 版本 javascript 最初为什么设计为单线程&#xff1f; JavaScript 最初设计为单线程的主要原因是出于简…

体验 Whisper ,本地离线部署自己的 ASR 语音识别服务

需求背景 最近看视频&#xff0c;过几天后经常忘记内容&#xff0c;所以有了把重点内容总结提炼到自己知识库的需求&#xff0c;这涉及到了提取视频中的音频数据、离线语音识别等功能。 提取视频中的音频数据&#xff0c;可以使用格式工厂或 FFmpeg 等工具&#xff0c; FFmpe…

详细解析socket

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

书生大模型实战营第三期——入门岛——Git基础知识

第三关&#xff1a;Git基础知识 任务如下&#xff1a; 任务描述 破冰活动&#xff1a;自我介绍 每位参与者提交一份自我介绍。 提交地址&#xff1a;GitHub - InternLM/Tutorial: LLM&VLM Tutorial 的 camp3 分支&#xff5e;实践项目&#xff1a;构建个人项目 创建一个个人…

PDF发票解析并将信息回填到前端(2)前端页面

本人前端基础薄弱&#xff0c;此处的前端仅仅是一个练习展示 1. 创建一个前端项目 打开终端使用以下命令创建一个基于webpack模板的新项目 vue init webpack my-project输入命令之后一直点击enter知道项目创建完成即可 进入项目&#xff0c;安装并运行 $ cd my-project //…