从源码解析FreeRTOS是如何进行任务切换

从源码解析FreeRTOS是如何进行任务切换

一.SysTick_Handler()

在FreeRTOSConfig.h定义了系统节拍时钟频率 ,下面表示每s有1000次心跳,即1ms一次(用户自己可以根据自己的需求修改)

#define configTICK_RATE_HZ							( ( TickType_t ) 1000 )

根据上面的定义系统每隔1ms就会调用SysTick_Handler()

#define portNVIC_INT_CTRL_REG     ( *( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT    ( 1UL << 28UL )
// SysTick_Handler()在port.c中
void SysTick_Handler( void ) /* PRIVILEGED_FUNCTION */
{
    uint32_t ulPreviousMask;
	//开启屏蔽其他中断
    ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        /* xTaskIncrementTick()函数返回值:如果就绪链表中有比当前优先级高的或相同优先级的任务则返回pdTRUE,否者返回pdFALSE */
        if( xTaskIncrementTick() != pdFALSE )
        {
            /* 通过向中断控制和状态寄存器ICSR的bit28写入1挂起PendSv来启动PendSV中断 */
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
    }
    //退出屏蔽其他中断
    portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
}

二.xTaskIncrementTick() 函数

xTaskIncrementTick()是进行任务切换的重要函数,根据条件把阻塞的任务加入就绪链表中。

BaseType_t xTaskIncrementTick( void )
{
    TCB_t * pxTCB;
    TickType_t xItemValue;
    BaseType_t xSwitchRequired = pdFALSE;

    traceTASK_INCREMENT_TICK( xTickCount );//空函数;

    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )//任务调度是否挂起
    {
        const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;//系统时间加1

        xTickCount = xConstTickCount;
		//判断系统时间是否溢出,溢出则交换链表
        if( xConstTickCount == ( TickType_t ) 0U ) 
        {
            taskSWITCH_DELAYED_LISTS();//交换列表,交换延时列表指针pxDelayedTaskList和溢出列表指针pxDelayedTaskList所指向的列表,并更新了xNextTaskUnblockTime的值;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();//否则什么也不做,空函数
        }
        if( xConstTickCount >= xNextTaskUnblockTime )//如果系统节拍 xConstTickCount的值大于等于xNextTaskUnblockTime 的值,表示有任务需要解除阻塞,因为xNextTaskUnblockTime 里保存的是下一个解除阻塞的时间点值;
        {
            for( ; ; )
            {
                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) //判断延时列表是否为空,pdTRUE时表示延时列表为空,pdFALSE表示延时列表不为空;
                {
                   //延时列表为空,设置xNextTaskUnblockTime为最大值
                    xNextTaskUnblockTime = portMAX_DELAY;
                    break;//说明没有需要接触阻塞任务,退出循环
                }
                else
                {
                // 延时列表不为空,获取延时列表的第一个列表项的值,并获取具体值
                    pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); 
                    xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
// 根据判断这个值,这个值里面保存的是下一个解除阻塞态的任务对应的解除时间点,判断延时时间是否到了,到了之后就移除延时列表;
                    if( xConstTickCount < xItemValue )
                    {
					//延时时间还没到,需要更新xNextTaskUnblockTime 的值,用xItemValue来更新xNextTaskUnblockTime;
                        xNextTaskUnblockTime = xItemValue;
                        break; 
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER(); //空函数
                    }

//延时时间到了,将任务从延时列表中移除
                    listREMOVE_ITEM( &( pxTCB->xStateListItem ) );

//任务是否还在等待其他事件,如信号量、列队等,如果是的话就将任务从相应的事件列表中移除,相当于等待事件超时退出;
                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                    {
                        listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER(); //空函数
                    }

//任务延时时间到了,并以将任务从延时列表移除,所以需要把任务添加进就绪列表中;
                    prvAddTaskToReadyList( pxTCB );
//使能抢占式调度
                    #if ( configUSE_PREEMPTION == 1 )
                    {
                        if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
                        {
                            xSwitchRequired = pdTRUE;
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER(); //空函数
                        }
                    }
                    #endif /* configUSE_PREEMPTION */
                }
            }
        }

//如果使能了时间片调度的话还要处理同优先级下任务的调度
        #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
        {
            if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
            {
                xSwitchRequired = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER(); //空函数
            }
        }
        #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
//如果使能了时间片钩子函数的话就执行时间片钩子函数vApplicationTickHook(),函数的具体内容由用户自己编写;
        #if ( configUSE_TICK_HOOK == 1 )
        {
            /* Guard against the tick hook being called when the pended tick
             * count is being unwound (when the scheduler is being unlocked). */
            if( xPendedTicks == ( TickType_t ) 0 )
            {
                vApplicationTickHook();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER(); //空函数
            }
        }
        #endif /* configUSE_TICK_HOOK */
//有时候调用其它的API函数会使用变量 xYieldPending来标记是否是要需要进行上下文切换;
        #if ( configUSE_PREEMPTION == 1 )
        {
            if( xYieldPending != pdFALSE )
            {
                xSwitchRequired = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER(); //空函数
            }
        }
        #endif /* configUSE_PREEMPTION */
    }
    else //任务调度器被挂起
    {
        ++xPendedTicks;; //它是全局变量,用uxPendedTicks来记录任务调度器挂起过程中的时钟节拍数;这样在用xTaskResumeAll()时就会调用uxPendedTicks次 xTaskIncrementTick(),xTickCount就会恢复,并且那些应该取消阻塞的任务也会取消阻塞;
        /* The tick hook gets called at regular intervals, even if the
         * scheduler is locked. */
        #if ( configUSE_TICK_HOOK == 1 )
        {
            vApplicationTickHook();
        }
        #endif
    }

    return xSwitchRequired; //返回 xSwitchRequired的值,它里面保存了是否任务切换的信息,若为pdTRUE表示需要任务切换,为pdFALSE表示不需要切换任务;
}

三. PendSV_Handler函数

FreeRTOS在PendSV_Handler函数进行任务切换,此函数使用的都是汇编语言

void PendSV_Handler( void ) /* __attribute__ (( naked )) PRIVILEGED_FUNCTION */
{
    __asm volatile //内联汇编 括号里面为汇编语言
    (
        "	.syntax unified									\n"
        "													\n"
        "	mrs r0, psp										\n"/* 读取当前进程栈指针到r0中 */
        #if ( ( configENABLE_FPU == 1 ) || ( configENABLE_MVE == 1 ) ) /* 如果使能FPU / MVU  s16 - s31 寄存器值入栈保存 */
            "	tst lr, #0x10									\n"
            "	it eq											\n"
            "	vstmdbeq r0!, {s16-s31}							\n"
        #endif /* configENABLE_FPU || configENABLE_MVE */
            "	mrs r2, psplim									\n"/* r2 = PSPLIM. */
            "	mov r3, lr										\n"/* r3 = LR/EXC_RETURN. */
            "	stmdb r0!, {r2-r11}								\n"/* Store on the stack - PSPLIM, LR and registers that are not automatically saved. */
        "													\n"
        "	ldr r2, pxCurrentTCBConst						\n"/* Read the location of pxCurrentTCB i.e. &( pxCurrentTCB ). */
        "	ldr r1, [r2]									\n"/* Read pxCurrentTCB. */
        "	str r0, [r1]									\n"/* Save the new top of stack in TCB. */
        "													\n"
        "	mov r0, %0										\n"/* r0 = configMAX_SYSCALL_INTERRUPT_PRIORITY */
        "	msr basepri, r0									\n"/* Disable interrupts upto configMAX_SYSCALL_INTERRUPT_PRIORITY. */
        "	dsb												\n"
        "	isb												\n"
        "	bl vTaskSwitchContext							\n"
        "	mov r0, #0										\n"/* r0 = 0. */
        "	msr basepri, r0									\n"/* Enable interrupts. */
        "													\n"
        "	ldr r2, pxCurrentTCBConst						\n"/* Read the location of pxCurrentTCB i.e. &( pxCurrentTCB ). */
        "	ldr r1, [r2]									\n"/* Read pxCurrentTCB. */
        "	ldr r0, [r1]									\n"/* The first item in pxCurrentTCB is the task top of stack. r0 now points to the top of stack. */
        "	ldmia r0!, {r2-r11}								\n"/* Read from stack - r2 = PSPLIM, r3 = LR and r4-r11 restored. */
        #if ( ( configENABLE_FPU == 1 ) || ( configENABLE_MVE == 1 ) )
            "	tst r3, #0x10									\n"/* Test Bit[4] in LR. Bit[4] of EXC_RETURN is 0 if the Extended Stack Frame is in use. */
            "	it eq											\n"
            "	vldmiaeq r0!, {s16-s31}							\n"/* Restore the additional FP context registers which are not restored automatically. */

            "	msr psplim, r2									\n"/* Restore the PSPLIM register value for the task. */
        "	msr psp, r0										\n"/* Remember the new top of stack for the task. */
        "	bx r3											\n"
        "													\n"
        "	.align 4										\n"
        "pxCurrentTCBConst: .word pxCurrentTCB				\n"
        ::"i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY )
    );
}


  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

VersionGod

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

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

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

打赏作者

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

抵扣说明:

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

余额充值