首页 > 其他分享 >FreeRTOS从代码层面进行原理分析(3 任务的切换)

FreeRTOS从代码层面进行原理分析(3 任务的切换)

时间:2024-03-29 22:30:17浏览次数:14  
标签:r0 FreeRTOS r3 代码 list 任务 切换

FreeRTOS分析三 — 任务的切换

我们带着三个问题开始了对 FreeRTOS 代码的探究。

1. FreeRTOS 是如何建立任务的呢?
2. FreeRTOS 是调度和切换任务的呢?
3. FreeRTOS 是如何保证实时性呢?

前两篇文章分别从代码的层面分析了 FreeRTOS 是如何建立任务以及建立的任务是怎么样被调用,“跑”起来的。
FreeRTOS从代码层面进行原理分析(1 任务的建立)
FreeRTOS从代码层面进行原理分析(2 任务的启动)

本篇继续上个博文中的提到的有官任务的切换问题,继续从代码的逻辑上来分析,FreeRTOS 是如何做到的。

FreeRTOS 中任务切换的细粒度

在 FreeRTOS 中称其为时间片(Time Slicing),如果两个 FreeRTOS 的任务具有相同的优先级那么实际执行起来的样子就像下面的图片一样,两个任务各自交替运行,每次每个任务可以一个时间片。
在这里插入图片描述

这个时间片的能力实际上就是来自 Systick 产生的中断。再上一篇博文中,咱们提到了使用 vPortSetupTimerInterrupt 函数配置 SysTick的过程。

这细粒度的具体时间就是由一行代码来控制的。
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
每次时钟脉冲都会使得 reload 里面的值减 1,这样通过对时钟的设置和 reload 寄存器的初始值设置,就可以对 FreeRTOS 的时间片进行设置了。

这里没有很详细的讲解 Systick,因为那不是这篇博文的重点,而且有大把介绍 Systick 的文章有呢~

任务切换的触发

根据 STM32F10x_Startup.s 文件中对中断向量表的配置来看,当发生 SysTick 中断时 xPortSysTickHandler 函数将会被调用。
自己盗自己个图~ hhhh
在这里插入图片描述
xPortSysTickHandler 函数中的逻辑其实很简单。就是通过增加时间的形式,看看是不是真的有任务需要进行切换,如果切换的话就再触发另一个 PendSV(可挂起的系统服务) 中断 。

    #define portNVIC_INT_CTRL_REG     ( *( ( volatile uint32_t * ) 0xe000ed04 ) )
    #define portNVIC_PENDSVSET_BIT    ( 1UL << 28UL )

void xPortSysTickHandler( void )
{
    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();
}

在这里插入图片描述
在这里插入图片描述
就这个文档的图片一贴~ 直接就明白了。 那我们先忽略 xTaskIncrementTick 函数,直扑关键逻辑,直接看 xPortPendSVHandler 函数。 这个函数是触发 pendSV 后执行的中断函数。

任务切换的执行

void xPortPendSVHandler( void )
{
    /* This is a naked function. */

    __asm volatile
    (
        "	mrs r0, psp							\n"
        "	isb									\n"
        "										\n"
        "	ldr	r3, pxCurrentTCBConst			\n"/* Get the location of the current TCB. */
        "	ldr	r2, [r3]						\n"
        "										\n"
        "	stmdb r0!, {r4-r11}					\n"/* Save the remaining registers. */
        "	str r0, [r2]						\n"/* Save the new top of stack into the first member of the TCB. */
        "										\n"
        "	stmdb sp!, {r3, r14}				\n"
        "	mov r0, %0							\n"
        "	msr basepri, r0						\n"
        "	bl vTaskSwitchContext				\n"
        "	mov r0, #0							\n"
        "	msr basepri, r0						\n"
        "	ldmia sp!, {r3, r14}				\n"
        "										\n"/* Restore the context, including the critical nesting count. */
        "	ldr r1, [r3]						\n"
        "	ldr r0, [r1]						\n"/* The first item in pxCurrentTCB is the task top of stack. */
        "	ldmia r0!, {r4-r11}					\n"/* Pop the registers. */
        "	msr psp, r0							\n"
        "	isb									\n"
        "	bx r14								\n"
        "										\n"
        "	.align 4							\n"
        "pxCurrentTCBConst: .word pxCurrentTCB	\n"
        ::"i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY )
    );
}

ok~ 现在开始回忆上一篇博文的内容,Cortex-m3 在进入中断前会自动将 8 个寄存器压栈。并在创建任务的博文中提到,在创建任务堆栈的时候也将 r4-r11 寄存器在堆栈上的位置做了预留。

这段代码的大致翻译过来的意思就是:

  1. 将切换前的任务堆栈上压入 r4-r11 寄存器,并将新的栈顶位置也压入任务堆栈中;
  2. 因为要在 handler 模式中进行对接下来要切换的任务进行挑选,需要借助 vTaskSwitchContext 函数,所以对可能被破坏的 r3、r14 两个寄存器进行压栈,这里的压栈中的 SP 由于在 handler 模式下为 MSP。
  3. 在 ·vTaskSwitchContext· 函数后,实际上 r3 已经被替换成最合适切换的新任务了。然后恢复r3、r14 。 再将新的任务的 r4-r11 进行出栈, 设置好新的任务的 PSP ,Cortex-m3 在推出中断后会自动将 8 个寄存器弹栈。如果忘记了,赶紧回去看看上一篇博文回忆一下吧。
  4. 已经切换完毕啦,在新的任务上运行中~

切换任务的选择

选择任务就是依靠上面汇编代码中的 vTaskSwitchContext 函数。这个函数在 FreeRTOS 中有两种实现方式。一种是通用的就是用 C 函数写的,另一种是对目标单片机架构进行优化过的也就是用汇编代码写的。

在这里插入图片描述

但是这两中代码的目的是一致的,在上面的代码中不是将 pxCurrentTCBConst 存入 r3 寄存器了么。 vTaskSwitchContext 函数就会将最高优先级的任务的堆栈找到,并且把找到任务替换到当前的任务中。也就是用新的任务将 r3 替换掉。这样就完成了任务的切换。

使用 C 语言写的一般方法:

#define taskSELECT_HIGHEST_PRIORITY_TASK()								\
	{																	\
		while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) )		\
		{																\
			configASSERT( uxTopReadyPriority );								\
			--uxTopReadyPriority;											\
		}																\
		listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &( pxReadyTasksLists[ uxTopReadyPriority ] ) );   \
	}

对 Cortex-m3 架构进行优化使用汇编写的函数:

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities )    uxTopPriority = ( 31 - __clz( ( uxReadyPriorities ) ) )

#define taskSELECT_HIGHEST_PRIORITY_TASK()								\
{																	\
	UBaseType_t uxTopPriority;												\
																	\
	portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );			\
	listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} 

这里的 __clz() 会被替换为 汇编指令 CLZ,这个指令用来计算一个变量从最高位开始的连续零的个数,在用 31 这么一减。 1 的位置也就被得出了。 也就是说 uxTopPriority 的值就是最低位置到达最高位是 1 的位数。
那么 pxReadyTasksLists[ uxTopPriority ] 也就是 pxReadyTasksLists 中最高优先级的任务了。

下面的一个宏 listGET_OWNER_OF_NEXT_ENTRY 字面意思已经很清晰了,直接返回 pxReadyTasksLists[ uxTopPriority ]pxCurrentTCB。至此替换 r3 的内容已经完成了。 后面无论是自动弹栈的 8 个寄存器,还是利用汇编 ldmia r0!, {r4-r11} 弹出 r4~r11 全部是来自于新的任务堆栈!

这个函数我留下了全部的注释,大家觉得好奇可以研究一下。

/*
 * Access function to obtain the owner of the next entry in a list.
 *
 * The list member pxIndex is used to walk through a list.  Calling
 * listGET_OWNER_OF_NEXT_ENTRY increments pxIndex to the next item in the list
 * and returns that entry's pxOwner parameter.  Using multiple calls to this
 * function it is therefore possible to move through every item contained in
 * a list.
 *
 * The pxOwner parameter of a list item is a pointer to the object that owns
 * the list item.  In the scheduler this is normally a task control block.
 * The pxOwner parameter effectively creates a two way link between the list
 * item and its owner.
 *
 * @param pxTCB pxTCB is set to the address of the owner of the next list item.
 * @param pxList The list from which the next item owner is to be returned.
 *
 * \page listGET_OWNER_OF_NEXT_ENTRY listGET_OWNER_OF_NEXT_ENTRY
 * \ingroup LinkedList
 */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                           \
    {                                                                                          \
        List_t * const pxConstList = ( pxList );                                               \
        /* Increment the index to the next item and return the item, ensuring */               \
        /* we don't return the marker used at the end of the list.  */                         \
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                           \
        if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
        {                                                                                      \
            ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                       \
        }                                                                                      \
        ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                                         \
    }

至此~ 再回头看一下对于 FreeRTOS 的三个问题,前两个是不是已经搞懂了呢~

1. FreeRTOS 是如何建立任务的呢?
2. FreeRTOS 是调度和切换任务的呢?
3. FreeRTOS 是如何保证实时性呢?

下一步让咱们来探索第三个问题。FreeRTOS 究竟有多 RTOS? 切换任务究竟有没有开销?开销是多大?

标签:r0,FreeRTOS,r3,代码,list,任务,切换
From: https://blog.csdn.net/Megahertz66/article/details/137028823

相关文章

  • win11切换桌面快捷键
    上回说到win11新设计的任务栏上图标不够放的问题,另一个方式多任务视图,就是多个桌面,这个功能十年前就在MacOS和Linux上流行了,win平台上10版上带有,只是win10有多行任务栏吗,就没探索这个功能,win11就得转正了,左ctrl+win+左右箭头切换,还有待进步啊,一是不能固定桌面数和单键循环,来回倒腾......
  • 代码随想录算法训练营第6天 | 哈希表
    哈希表理论基础用法:一般哈希表都是用来快速判断一个元素是否出现集合里,哈希法牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找eg:例如要查询一个名字是否在这所学校里,要枚举的话时间复杂度是O(n),但如果使用哈希表的话,只需要O(1)就可......
  • 每个程序员都应该了解的内存知识(五): 代码优化
    代码优化多线程优化尽量使用顺序读写因为分支预测的关系,顺序读写通常能够带来更好的性能.共享变量将只读变量和读写变量分离有可能因为缓存行的原因导致读写变量的更新影响到读变量,进而影响了运行速度提升数据的局部性,将一起使用的读写变量分组到一个结构中缓......
  • Spring Boot整合MyBatis-Plus,并通过AutoGenerator生成项目骨架代码
    作为一名Java后端开发,日常工作中免不了要生成数据库表对应的持久化对象PO,操作数据库的接口DAO,以及CRUD的XML,也就是mapper。MybatisGenerator是MyBatis官方提供的一个代码生成工具,完全可以胜任这个工作,不过最近在开发项目的时候试用了一下MyBatis-Plus官方提供......
  • KingbaseES V8R3集群运维案例之---failover切换后新主库启动过程
    案例说明:KingbaseESV8R3集群failover切换后,在生产环境中,新主库启动过程中可能会有业务访问,出现‘系统只读’的问题。如下图所示:适用版本:KingbaseESV8R3一、问题分析1、如下所示,failover切换过程:1)在master节点执行failover_stream.sh脚本执行failover切换。2)ping网关地......
  • kingbaseES V8R6集群运维案例之---配置priority防止failover切换案例
    案例说明:在一主多备的架构中,需要配置一台备库在主备切换时,不能选举为主库。对于repmgr主备切换主库的选择算法如下:Tips:Repmgr选举候选备节点会以以下顺序选举:LSN---->Priority---->Node_ID。系统会先选举一个LSN比较大者作为候选备节点;如LSN一样,会根据Priority优先级进行比......
  • 【无人机路径规划】基于深度强化学习的多无人机辅助边缘计算网络路径规划(Matlab代码实
    ......
  • 代码随想录算法训练营第六十天 | 84.柱状图中最大的矩形
      84.柱状图中最大的矩形 已解答困难 相关标签相关企业 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为1。求在该柱状图中,能够勾勒出来的矩形的最大面积。 示例1:输入:heights=[2,1,5,6,2,3]输出:10......
  • 低代码助力企业打造业务管理云平台
    随着数字化转型的不断深入,越来越多的企业开始寻求通过构建业务管理云平台来提升运营效率、降低成本并增强市场竞争力。在这个过程中,低代码平台正逐渐成为一种革命性的技术工具,以其高效、灵活的特点助力企业快速搭建和管理云平台。低代码平台的基本概念低代码平台是一种允许......
  • 低代码与移动办公平台开发:重塑企业生产力
    随着信息技术的飞速发展,移动办公已成为现代企业的标配。与此同时,低代码开发平台(Low-CodeDevelopmentPlatform,LCDP)的兴起,正为移动办公平台的快速构建和迭代提供了强大的技术支持。本文将探讨低代码开发平台如何助力移动办公平台的发展,以及它们如何共同重塑企业生产力。低......