FreeRTOS任务切换学习

FreeRTOS任务切换学习

所谓任务切换,就是CPU寄存器的切换。假设当由任务A切换到任务B时,主要分为两步:
1:需暂停任务A的执行,并将此时任务A的寄存器保存到任务堆栈,这个过程叫做保存现场;
2:将任务B的各个寄存器值(被存于任务堆栈中)恢复到CPU寄存器中,这个过程叫做恢复现场;
对任务A保存现场,对任务B恢复现场,这个整体的过程称之为:上下文切换。下面要补充几个知识,以便更好理解任务切换。

PendSV异常

PendSV(可挂起的系统调用)异常对 OS 操作非常重要,其优先级可以通过编程设置。可以通过将中断控制和状态寄存器 ICSR 的 bit28,也就是 PendSV 的挂起位置 1 来触发 PendSV 中断。与 SVC 异常不同,它是不精确的,因此它的挂起状态可在更高优先级异常处理内设置,且
会在高优先级处理完成后执行。若将 PendSV 设置为最低的异常优先级,可以让 PendSV 异常处理在所有其他中断处理完成后执行,
下面我们直接来边解读程序边理解实现任务切换的过程:

__asm void xPortPendSVHandler( void )
{
    extern uxCriticalNesting;
    extern pxCurrentTCB;
    extern vTaskSwitchContext;

/* *INDENT-OFF* */
    PRESERVE8

    mrs r0, psp//读取进程栈指针,保存在寄存器 R0 里面。
    isb

    ldr r3, =pxCurrentTCB /* 得到正在运行指向任务控制块的指针的地址。*/
    ldr r2, [ r3 ]//得到任务控制块的地址

    stmdb r0 !, { r4 - r11 } /* 保存从R4到R11寄存器的值*/
    str r0, [ r2 ] /* 将此时的栈顶指针保存到任务控制块中的首个元素 */

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

    ldr r1, [ r3 ]
    ldr r0, [ r1 ] /* The first item in pxCurrentTCB is the task top of stack. */
    ldmia r0 !, { r4 - r11 } /* Pop the registers and the critical nesting count. */
    msr psp, r0
    isb
    bx r14
    nop
/* *INDENT-ON* */
}

xPortPendSVHandler首先我们要明白这个函数PendSV中断。中断中使用的是MSP指针,中断外使用的是PSP指针。具体可以在手册Cortext-M3手册中找到:
在这里插入图片描述
所以自动压栈都是用的PSP指针。并且完成自动压栈后PSP指针指向的位置如下图所示:
在这里插入图片描述
mrs r0, psp所以此时r0寄存器保存的此时指针的位置。
stmdb r0 !, { r4 - r11 }从r0指针指向的位置手动压栈将寄存器R4-R11寄存器的值保存起来。此时r0指针指向的地址如图中所示:

在这里插入图片描述
str r0, [ r2 ] 将此时的栈顶指针保存到任务控制块中的首个元素。以便后面从压栈后的最新指针出开始出栈。
stmdb sp !, { r3, r14 }R14 是连接寄存器(LR)。在一个汇编程序中,你可以把它写作 both LR 和 R14。LR 用于在调用子程序时存储返回地址,R3为任务控制块的地址,为了防止 R3 和 R14 的值被改写,所以这里临时将 R3和 R14 的值先压栈。
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0开启临界区,也就是关闭中断。
bl vTaskSwitchContext调用这个函数得到下一个要运行的任务。下面具体来看一下这个函数是如何实现的:

void vTaskSwitchContext( void )
{
    if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
    {
        /* The scheduler is currently suspended - do not allow a context
         * switch. */
        xYieldPending = pdTRUE;
    }
    else
    {
        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 /* configGENERATE_RUN_TIME_STATS */

        /* Check for stack overflow, if configured. */
        taskCHECK_FOR_STACK_OVERFLOW();

        /* Before the currently running task is switched out, save its errno. */
        #if ( configUSE_POSIX_ERRNO == 1 )
        {
            pxCurrentTCB->iTaskErrno = FreeRTOS_errno;
        }
        #endif

        /* Select a new task to run using either the generic C or port
         * optimised asm code. */
        taskSELECT_HIGHEST_PRIORITY_TASK(); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
        traceTASK_SWITCHED_IN();

        /* After the new task is switched in, update the global errno. */
        #if ( configUSE_POSIX_ERRNO == 1 )
        {
            FreeRTOS_errno = pxCurrentTCB->iTaskErrno;
        }
        #endif

        #if ( ( configUSE_NEWLIB_REENTRANT == 1 ) || ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 ) )
        {
            /* Switch C-Runtime's TLS Block to point to the TLS
             * Block specific to this task. */
            configSET_TLS_BLOCK( pxCurrentTCB->xTLSBlock );
        }
        #endif
    }
}

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() */

里面的实现主要又有2个函数,分别是portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority )listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) )

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

这个函数获取最高优先级是采用硬件的方法。也就是前导置零指令,这里也需要前面的一个知识点。在这里插入图片描述
就绪表分为多个优先级,就绪表的每个优先级可以容纳多个任务。每个就绪列表都是一个结构体。想要了解这部分可以看之前写的列表和列表项的知识:列表和列表项的知识回顾
在这里插入图片描述
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) )这个函数实现如下所示:

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                           \
    {                                                                                          \
        List_t * const pxConstList = ( pxList );                                               \
        /* 指向List_t类型的常量指针pxConstList,并将其初始化为pxList的值。 */               \
        /* 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;                                         \
    }

当列表中仅有一个任务时候,过程如下图所示:刚开始pxindex指向的是末尾列表项。( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; 这句代码将指针指向列表项1。

 if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
        {                                                                                      \
            ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                       \
        }  

if判断作用是用来略过末尾列表项的作用。( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; 这句代码作用指向包含此列表项的对象的指针。通常,这个指针指向一个任务控制块(TCB),但也可以指向其他使用列表项的数据结构。这实现了对象和其所属列表项之间的双向链接。
所以此时就得到该任务的任务控制块的地址。
在这里插入图片描述
当该就绪列表有多个任务时候,就要时间片流转了。这部分知识等到学到的时候继续补充。
在这里插入图片描述

mov r0, #0 msr basepri, r0 接下来分析继续执行的汇编代码。这2句汇编代码打开中断。退出临界区。
ldmia sp !, { r3, r14 }恢复寄存器 R3 和 R14 的值。注意,此时 pxCurrentTCB 的值已经改变了,所以读取 R3 所保存的地址处的数据就会发现其值改变了,成为了下一个要运行的任务的任务控制块的地址。
ldr r1, [ r3 ] ldr r0, [ r1 ] 因为R3所保存的是将要运行任务的任务控制块地址。所以r1中得到这个任务控制块,r0在得到栈顶指针。此时:
在这里插入图片描述
栈顶指针指向的位置如上图红色箭头所示:
ldmia r0 !, { r4 - r11 }将栈保存的值加载R4-R11寄存器中。也就是即将运行的任务的现场。
msr psp, r0更新进程栈指针 PSP 的值。此时R0指向的值为:
在这里插入图片描述
然后之后bx r14跳转到要执行的函数。因为R14保存函数返回的地址。执行此行代码以后硬件自动恢复寄存器 R0~R3、R12、LR、PC 和 xPSR 的值,确定异常返回以后应该进入处理器模式还是进程模式,使用主栈指针(MSP)还是进程栈指针(PSP)。很明显这里会进入进程模式,并且使用进程栈指针(PSP),寄存器 PC 值会被恢复为即将运行的任务的任务函数,新的任务开始运行!至此,任务切换成功。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【STL】list

目录 1. list的使用 1.1 list的构造 1.2 list iterator的使用 1.3 list capacity 1.4 list element access 1.5 list modifiers 1.6 list的迭代器失效 2. list的模拟实现 3. list与vector的对比 1. list的使用 1.1 list的构造 1.2 list iterator的使用 1. begin与end为…

雨污管网开挖深度的计算

一般的管网工程都有纵断面设计图,结合纵断面里的 管内底埋深-管厚度(直径0.6管厚0.06,直径0.8承插管直径0.08厚) - 砂砾石基础一般0.15厚 - 路面结构层厚度就是沟槽开挖深度了,是不是很简单。 管内底埋深其实就是管内流水面到设计路面顶的高…

PyCharm+PyQt5配置方法

一、前言 PyQt5PyQt5是一套Python绑定Digia QT5应用的框架。Qt库是最强大的GUI库之一PyQt5-toolsPyQt5中没有提供常用的Qt工具,比如图形界面开发工具Qt Designer,PyQt5-tools中包含了一系列常用工具Qt Designer可以通过Qt Designer来编写UI界面&#xf…

Docker快速上手及常用命令速查

Docker快速上手 安装 在ubuntu上安装docker: sudo apt-get install docker docker -v #查看版本在centos7上安装docker:(docker在YUM源的Extras仓库中) yum install docker systemctl start dockerdocker常用命令速查 #查看docker信息 docker info #查看本地镜…

网络基础三——其他周边问题

3.1ARP原理 ​ ARP不是一个单纯的数据链路层的协议,而是一个介于数据链路层和网络层之间的协议; ​ 以广播的形式(主机号填成全1)构建Mac帧,发送ARP请求包,告诉所有在局域网的主机我的IP地址和Mac帧,与目的IP相同的主…

[lesson16]类的真正形态

类的真正形态 类的关键字 struct在C语言中以及有了自己的含义,必须继续兼容 在C中提供了新的关键字class用于类的定义 class和struct的用法是完全相同的 在用struct定义类时,所有成员的默认访问级别为public 在用class定义类时,所有成员…

Leetcode算法训练日记 | day22

一、二叉搜索树的最近公共祖先 1.题目 Leetcode:第 235 题 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足…

liunx环境变量学习总结

环境变量 在操作系统中,环境变量是一种特殊的变量,它们为运行的进程提供全局配置信息和系统环境设定。本文将介绍如何自定义、删除环境变量,特别是对重要环境变量PATH的管理和定制,以及与环境变量相关的函数使用。 自定义环境变…

【复现】用友NC-Cloud文件上传漏洞_70

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一: 四.修复建议: 五. 搜索语法: 六.免责声明 一.概述 用友NC Cloud大型企业数字化平台,深度应用新一代数字智能技术,完全基于云原生架构,打造开放、…

MySQL进阶之(七)EXPLAIN 详解

七、EXPLAIN 详解 7.1 查询性能那些事7.1.1 查看系统性能参数7.1.2 统计 SQL 的查询成本7.1.3 定位执行慢的 SQL:慢查询日志01、开启慢查询日志参数02、关闭慢查询日志03、删除慢查询日志 7.1.4 查看 SQL 执行成本:SHOW PROFILE 7.2 EXPLAIN 语句输出中各…

pyqt5 QScrollArea组件

本示例中,演示了QScrollArea的使用,以及QScrollBar的样式设定,在代码中使用setStyleSheet设置样式,记得要优先设置scrollArea,再设置窗口的样式,不然QScrollBar的样式会不起作用,使用QSS设置没有…

SGD随机梯度下降

一、补充概念: 目标函数(Objective Function):这个术语通常指的是整个优化问题中需要最小化(或最大化)的函数。在机器学习和优化中,目标函数可以包括损失函数以及正则化项等。目标函数的最优化过…

Python程序设计 列表

教学案例八 列表 1. 计算并显示斐波那契数列 输入n,计算并显示斐波那契数列前n项.一行打印5项,每项显示宽度为6 什么是斐波那契数列 斐波那契数列(Fibonacci sequence),又称黄金分割数列、 因数学家莱昂纳多斐波那契&#xff…

基于SSM+Jsp+Mysql的农产品供销服务系统

开发语言:Java框架:ssm技术:JSPJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包…

windows系统安装mysql5.7

1、下载 下载路径:https://downloads.mysql.com/archives/community/ 2、创建配置文件my.ini 下载压缩包解压到安装目录(本机解压后在D:\mysql-5.7.44-winx64) 在bin的同级目录下创建my.ini文件 my.ini文件 [mysql] # 设置mysql客户端默认字符…

接口自动化测试(python+pytest+requests)

一、选取自动化测试用例 优先级高:先实现业务流程用例、后实现单接口用例功能较稳定的接口优先开展测试用例脚本的实现二、搭建自动化测试环境 核心技术:编程语言:python;测试框架:pytest;接口请求:requests安装/验证requests:命令行终端分别输入 pip install requests / p…

6.1Python之字典的初识

【1】字典的创建与价值 字典(Dictionary)是一种在Python中用于存储和组织数据的数据结构。元素由键和对应的值组成。其中,键(Key)必须是唯一的,而值(Value)则可以是任意类型的数据。…

国内如何实现GPT升级付款

本来想找国外的朋友代付的,但是他告诉我他的信用卡已经被绑定了他也升级了所以只能自己想办法了。就在一位博主下边发现了这个方法真的可以。只是需要与支付宝验证信息。刚开始不敢付款害怕被骗哈哈,我反诈骗意识绝对杠杠的 该方法就是我们办理一张虚拟…

3D可视化技术亮相高铁站,引领智慧出行新潮流

在科技飞速发展的今天,我们的生活正经历着前所未有的变革。高铁站作为现代交通的重要枢纽,也在不断地创新和进步。 3D可视化技术通过三维立体的方式,将高铁站内部和外部的结构、设施、流线等以更加直观、生动的形式呈现出来。乘客们只需通过手…

2、java语法之循环、数组与方法(找工作版)

写在前面:整个系列文章是自己学习慕课相关视频,进行的一个总结。文章只是为了记录学习课程的整个过程,方便以后查漏补缺,找到对应章节。 文章目录 一、Java循环结构1、while循环2、do-while循环3、for循环4、嵌套循环5、break语句…