欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > 微型操作系统内核源码详解系列五(3):cm3下调度的开启

微型操作系统内核源码详解系列五(3):cm3下调度的开启

2024/10/24 19:17:15 来源:https://blog.csdn.net/2301_77061352/article/details/139866194  浏览:    关键词:微型操作系统内核源码详解系列五(3):cm3下调度的开启

系列一:微型操作系统内核源码详解系列一:rtos内核源码概论篇(以freertos为例)-CSDN博客

系列二:微型操作系统内核源码详解系列二:数据结构和对象篇(以freertos为例)-CSDN博客

系列三:微型操作系统内核源码详解系列三(0):空间存储及内存管理篇(前置篇)-CSDN博客

                微型操作系统内核源码详解系列三(1):任务及切换篇(任务函数定义)-CSDN博客

                微型操作系统内核源码详解系列三(2):任务及切换篇(任务函数定义)-CSDN博客

                微型操作系统内核源码详解系列三(3):任务及切换篇(任务函数定义)-CSDN博客

                微型操作系统内核源码详解系列三(4):arm架构篇-CSDN博客

                微型操作系统内核源码详解系列三(5):进程与线程-CSDN博客

系列四:

 ​​​​​微型操作系统内核源码详解系列四(1):操作系统调度算法(linux0.11版本内核)-CSDN博客

微型操作系统内核源码详解系列四(2):操作系统调度算法(rt-thread内核)-CSDN博客

微型操作系统内核源码详解系列四(3):操作系统调度算法(FreeRTOS内核篇上)-CSDN博客

微型操作系统内核源码详解系列四(4):操作系统调度算法(FreeRTOS内核篇下)-CSDN博客

系列五:

微型操作系统内核源码详解系列五(1):arm cortex m3架构-CSDN博客

微型操作系统内核源码详解系列五(2):cm3下栈的初始化-CSDN博客

微型操作系统内核源码详解系列五(3):cm3下调度的开启-CSDN博客

微型操作系统内核源码详解系列五(四):cm3下svc启动任务-CSDN博客

微型操作系统内核源码详解系列五(五):cm3下Pendsv切换任务上篇-CSDN博客

微型操作系统内核源码详解系列五(六):Pendsv切换任务下篇-CSDN博客

上一篇文章,我们已经完成了栈的初始化,接下来笔者将会讲解调度器是如何配置并且开启第一个任务的。

在此之前,让我们重新看看官方的指导:

2f0a51283a3943f2b647ea25afd1c2f0.png

PSP和MSP分别是cm3下的两个堆栈指针,任何情况下只能使用其中一个,详细内容参考:

Cortex-M3 双堆栈指针(MSP&PSP) - uTank - 博客园 (cnblogs.com)

(笔者在系列五的博客截取了很多张cm3手册中的信息,这些都是至关重要的的信息,是指导如何写出一个月FreeRTOS的蓝图,请读者一定要把这些信息认真看完。

如果读者阅读到某一篇博客时感觉一点都看不懂了,那么笔者推荐把所在系列从头到尾认认真真看一遍,或者把全系列看一遍,因为笔者描述思路时比较零散,你可能要看到后面才能看懂前面的东西。如果发现实在是看不懂,大可不必纠结,可以选择搁置一段时间再来重新阅读。)

通过上文,大概可以知道:在cm3下的双堆栈中,特权级别的代码(例如异常服务调用)会使用MSP,用户级别的代码(例如线程任务)会使用PSP。

e2156bef0c564eb09e4b5e93318096d7.png

这就是任务的轮转调度中断部分,任务的切换主要是由systick中断和PendSV中断完成。

有读者可能好奇,既然调度器的职责是完成上一个认为与下一个任务的切换,也就是说我们只需要在PendSV中断里面保存上一个任务的状态,再加载下一个任务的状态就行了,那么第一个任务从哪里来呢?

为了开启第一个任务,我们使用SVC调用,SVC调用是特权级的中断,毫无疑问更安全。

SVC系统调用主要职责就是开启os下的第一个任务。

但是调用SVC中断前需要进行配置:


BaseType_t xPortStartScheduler( void )
{#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 APIfunctions can be called.  ISR safe functions are those that end in"FromISR".  FreeRTOS maintains separate thread and ISR API functions toensure 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 allpossible 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 lowestpriority. */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 numberof bits read back. */ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE ){ulMaxPRIGROUPValue--;ucMaxPriorityValue <<= ( uint8_t ) 0x01;}/* Shift the priority group value back to its position within the AIRCRregister. */ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;/* Restore the clobbered interrupt priority register to its originalvalue. */*pucFirstUserPriorityRegister = ulOriginalPriority;}#endif /* conifgASSERT_DEFINED *//* Make PendSV and SysTick the lowest priority interrupts. */portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;/* Start the timer that generates the tick ISR.  Interrupts are disabledhere already. */vPortSetupTimerInterrupt();/* Initialise the critical nesting count ready for the first task. */uxCriticalNesting = 0;/* Ensure the VFP is enabled - it should be anyway. */prvEnableVFP();/* Lazy save always. */*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;/* Start the first task. */prvStartFirstTask();/* Should not get here! */return 0;
}

上面的函数,宏定义里面的configASSERT_DEFINED是断言,用来调试,让我们关注这些关键部分:

f0499a1440cd4bf29233efe0ef513e6f.png

解释一下,这段代码的作用是:配置pendsv和systck中断的优先级为最低(防止打断其他中断任务),然后设置systck定时器产生ISR(中断服务程序),就是设置SysTick定时器以生成系统tick中断。uxCriticalNesting用来记录进入临界区的次数,prvEnableVFP作用是支持浮点数运算,下一行代码的作用是设置浮点上下文寄存器以启用惰性保存机制,之后就是开启第一个任务的入口函数。

顺便一提,不能使用systick中断进行任务上下文切换的原因是:systick响应会抢占中断,但是OS不允许中断过程中执行上下文切换。

下面这个带firsttask命名的函数的作用还不是开启第一个任务,它的作用是产生SVC调用,SVC调用里面的函数才会把第一个任务的状态加载到CPU,这才是开启第一个任务。

66280367acad447a879096b4af6cccb9.png

ldr这三行代码的作用是--找到主栈的栈顶指针:

dad212f16eed4893b3dd528c4932d925.png

关于它是如何找到栈顶指针的,可以参考下图:

bea8a5cc452a4a13b6c2f2d1c5562cec.png

简单来说就是,先找到向量表偏移量寄存器,再找到向量表,向量表记录了msp的初始值:

4580158378ca490c85885ea6611b2840.png

svc下是特权模式运行,所以使用msp作为堆栈指针。这段代码将栈顶指针的值存储到msp,现在msp就指向栈顶指针了:

a6ac30ab11aa417f9d5c1e0ae105222f.png

之后的四行代码就是开启全局中断和异常:

6863c6aba6234f609e6cbef277a3cb7d.png

毫无疑问,这里就是开启svc中断响应了,nop指令表示延时,在计算机体系架构中,很多指令并不能持续执行,可能需要延时,如果读者学过流水线这些可能会知道nop的重要性:

20ce8bedb183481399b191c4781b91c7.png

到这里,SVC调用将会被响应,我们将会迎来arm架构下最精彩的部分:任务的开启与切换。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com