您现在的位置是:首页 > 正文

FreeRtos源码分析之任务切换原理(四)

2024-02-01 05:57:16阅读 1

一、CortexM3中断优先级

CortexM3支持多达240个外部中断和16个内部中断,每一个中断都对应一个中断都对应一个优先级寄存器。每一个优先级寄存器占用8位,STM32采用其中的高四位来表示优先级,低四位不可用。
在这里插入图片描述
在这里插入图片描述

FreeRtos一共会使用到三种中断:SysTick、SVC、PendSV。

  • SVC在启动任务调度的时候使用;
  • SysTIck定时器用于周期性的中断,为系统提供心跳;
  • PendSV用于任务切换;

对于实时操作系统而言,我们一般外部中断优先得到响应,所以SysTick和PendSV的优先级通常设置为最低。

    /* 使 PendSV and SysTick 的优先级最低. */
    portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;

portNVIC_PENDSV_PRI表示(0xf<<4)<<16,portNVIC_SYSTICK_PRI表示(0xf<<4)<<24,刚好将PendSV和SysTick优先级寄存器的最高4位全部置一(CortexM3的优先级寄存器值越大优先级越低)。

二、PendSV

SVC(系统服务调用,亦简称系统调用)和 PendSV(可悬起系统调用),它们多用于在操作系统之上的软件开发中。 SVC产生的中断必须立即得到响应,否则将触发硬Fault。PendSV是可悬挂的系统调用,如果有更高优先级的中断产生,PendSV中断会挂起,直到更高优先级的中断处理完成。
悬起 PendSV 的方法是: 手工往 NVIC 的 PendSV 悬起寄存器中写 1。

假设某个OS系统中存在比SysTick优先级更低的中断,那么当低优先级的IRQ在执行时会被SysTick打断,并在SyStick中断中执行上下文切换。由于执行上下文切换的时间在真实系统中所需要的时间是不可知的,所以低优先级的中断将会被延时执行。这种行为在任何一种实时操作系统中都是不能容忍的,在CortexM3中如果 OS 在某中断活跃时尝试切入线程模式,将触发fault 异常。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wr3mswWP-1611658781428)(en-resource://database/616:1)]
为解决此问题,早期的 OS 大多会检测当前是否有中断在活跃中,只有没有任何中断需要响应时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切换动作拖延很久(因为如果抢占了 IRQ,则本次 SysTick 在执行后不得作上下文切换,只能等待下一次 SysTick 异常),尤其是当某中断源的频率和 SysTick 异常的频率比较接近时,会发生“共振”。
现在好了, PendSV 来完美解决这个问题了。PendSV 异常会自动延迟上下文切换的请求,直到其它的 ISR 都完成了处理后才放行。为实现这个机制,需要把 PendSV 编程为最低优先级的异常。在FreeRtos中,每一次进入SysTick中断,系统都会检测是否有新的进入就绪态的任务需要运行,如果有,则悬挂PendSV异常, 以便缓期执行上下文切换。如图 7.17 所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nrHpZdKc-1611658781431)(en-resource://database/618:1)]

关于任务切换的内容,强烈建议参考《CortexM3权威指南》第9章内容。

三、FreeRtos任务切换的两种场景

FreeRtos任务切换有两种场景:

  1. 在SysTick定时器中监测是否有新的就绪态任务需要运行,如果有则进行任务切换;
void xPortSysTickHandler( void )
{
    /* The SysTick runs at the lowest interrupt priority, so when this interrupt
     * executes all interrupts must be unmasked.  There is therefore no need to
     * save and then restore the interrupt mask value as its value is already
     * known. */
    portDISABLE_INTERRUPTS();
    {
        /* Increment the RTOS tick. */
        if( xTaskIncrementTick() != pdFALSE )
        {
            /* A context switch is required.  Context switching is performed in
             * the PendSV interrupt.  Pend the PendSV interrupt. */
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
    }
    portENABLE_INTERRUPTS();
}

从上面的代码中可以看出,FreeRtos在进入SysTick中断后会屏蔽所有的其它中断,如果我们不使用用PendSV而在SysTick中断中来进行任务切换,那么SysTick中断会占用无法预知的时间,即使其它中断的优先级高于Systick,也依然要等到SysTick中断执行结束,从而导致系统发生不可预知的异常。
任务的被动切换很依赖于每一个任务中调用的系统延时或者阻塞,如果某个较高优先级的任务一直不停的运行,比如你在最高优先级的任务中写入了如下代码:

while(1);

那么你的系统有可能就会一直卡死在里面,或者不停的触发看门狗。
2. 用户主动调用portYIELD函数进行任务切换

/* Scheduler utilities. */
    #define portYIELD()                                 \
    {                                                   \
        /* Set a PendSV to request a context switch. */ \
        portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
        __DSB();                                        \
        __ISB();                                        \
    }

从代码中可以看出,这两种任务切换方式的原理一样,都是向PendSV中断寄存器写1,触发一次PendSV中断。接下来我们看下PendSV中断函数:

xPortPendSVHandler:
	mrs r0, psp
	isb
	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB
	ldr	r2, [r3]

	/* Is the task using the FPU context?  If so, push high vfp registers. */
	tst r14, #0x10
	it eq
	vstmdbeq r0!, {s16-s31}

	/* Save the core registers. */
	stmdb r0!, {r4-r11, r14}

	/* Save the new top of stack into the first member of the TCB. */
	str r0, [r2]

	stmdb sp!, {r0, r3}
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext
	mov r0, #0
	msr basepri, r0
	ldmia sp!, {r0, r3}

	/* The first item in pxCurrentTCB is the task top of stack. */
	ldr r1, [r3]
	ldr r0, [r1]

	/* Pop the core registers. */
	ldmia r0!, {r4-r11, r14}

	/* Is the task using the FPU context?  If so, pop the high vfp registers
	too. */
	tst r14, #0x10
	it eq
	vldmiaeq r0!, {s16-s31}

	msr psp, r0
	isb
	#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
		#if WORKAROUND_PMU_CM001 == 1
			push { r14 }
			pop { pc }
		#endif
	#endif

	bx r14

博主汇编知识有限,所以就不一一介绍每句代码的含义了,感兴趣的同学可以自行百度。这里只介绍vTaskSwitchContext函数。vTaskSwitchContext的核心任务是找到当前处于就绪态的最高优先级的任务,代码如下:

if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
    {
        /* 如果当前任务调度器处于挂起状态- 不允许任务切换 */
        xYieldPending = pdTRUE;
    }
    else
    {
        xYieldPending = pdFALSE;
        traceTASK_SWITCHED_OUT();

        //此处省略部分代码

        /* 使用c或者汇编代码选择最高优先级的就绪态任务作为下一个将要运行的任务*/
        taskSELECT_HIGHEST_PRIORITY_TASK(); 

    }

接下来我们看下taskSELECT_HIGHEST_PRIORITY_TASK函数是如何寻找最高优先级的任务的。

    #define taskSELECT_HIGHEST_PRIORITY_TASK()           \
    {                                                                     \
        UBaseType_t uxTopPriority;                                \
                                                                          \
        /* Find the highest priority list that contains ready tasks. */                         \
        portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );                          \
        configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
        listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );   \
    } /* taskSELECT_HIGHEST_PRIORITY_TASK() */

首先我们看下portGET_HIGHEST_PRIORITY函数,这个函数的作用是返回系统中最高有优先级任务的优先级值,其源码为:

        #define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities )    uxTopPriority = ( 31UL - ( ( uint32_t ) __CLZ( ( uxReadyPriorities ) ) ) )

__CLZ是一条汇编指令,用于计算最高符号位与第一个1之间的0的个数。比如( ( uint32_t ) __CLZ( ( 0x00FFFFF0 ) ) )得到的值为8。
由于相同优先级的任务可能会存在多个,所以接下来便需要从就绪任务列表中找到位于链表最前面的优先级,将其赋值给pxCurrentTCB。至此,vTaskSwitchContext函数分析完毕。

FreeRtos带注释源码Gitee地址:https://gitee.com/zBlackShadow/FreeRtos10.4.3.git

网站文章

  • 路由守卫大全

    路由守卫是一个路由的访问机制,如果允许访问就放行,不允许访问就不放行,可以通过 router.beforeEach() 方法来实现对应的操作。

    2024-02-01 05:57:09
  • html如何修改span的值,如何动态改变div span的内容

    本文介绍了javascript动态改变div span的内容的教程,希望能帮助到大家先看一个实例对span的控制与div类似,但是它是按照行来显示的,看下面的代码:function chagespan...

    2024-02-01 05:56:39
  • Microsoft Word 教程:如何在 Word 中插入图片、图标?

    Microsoft Word 教程:如何在 Word 中插入图片、图标?

    欢迎观看 Microsoft Word 教程,小编带大家学习 Microsoft Word 的使用技巧,了解如何在 Word 中插入图片、图标。

    2024-02-01 05:56:31
  • 使用 Java Native Interface 的最佳实践

    使用 Java Native Interface 的最佳实践

    2019独角兽企业重金招聘Python工程师标准>>> ...

    2024-02-01 05:56:23
  • vue 水印添加

    vue 水印添加

    vue 页面添加水印

    2024-02-01 05:55:55
  • 用计算机算标准曲线,标准曲线计算软件

    用计算机算标准曲线,标准曲线计算软件

    标准曲线计算器电脑版是一款可计算标准曲线的电脑计算器软件。计算过标准曲线的用户都应该知道标准曲线的计算挺麻烦的,而且计算结果要非常的精准才可以。本软件已经设置好了公式,您只要往里面添加数据就可以得出标...

    2024-02-01 05:55:46
  • Java求geometry的面积最小外接矩形

    Java求geometry的面积最小外接矩形

    Java求geometry的面积最小外接矩形 geom.getEnvelope() 得到外接矩形,不一定是面积最小;可以对多边形的每一条边求外接矩形,然后比较得到面积最小外接矩形。这篇博客将分为3步进...

    2024-02-01 05:55:39
  • 常见的Web应用攻击手段

    常见的Web应用攻击手段 1.XSS攻击 XSS攻击即跨站点脚本攻击(Cross Site Script),指黑客通过篡改网页,注入恶意HTML脚本,在用户浏览网页时,控制用户浏览器进行恶意操作的一种...

    2024-02-01 05:55:09
  • unity 检测物体是否在相机视野范围内

    脚本挂在摄像机要显示的对象上前提:该对象有 render 组件public class visibleTT : MonoBehaviour{ public bool isRendering = false; public float lastTime = 0; public float curTime = 0; void Update() {

    2024-02-01 05:55:03
  • androidstudio 使用正则表达式替换xml相关内容

    androidstudio 使用正则表达式替换xml相关内容

    例如我在xml文件中,需要在edittext增加限制输入数据类型为数字类型 在替换栏中使用$n(n代表正则表达式中括号的索引,从1开始)来保存原来的换行等需要保留的内容 替换后的效果: ...

    2024-02-01 05:54:56