FreeRTOS任务切换学习

news2024/12/23 22:31:10

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 值会被恢复为即将运行的任务的任务函数,新的任务开始运行!至此,任务切换成功。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Git 安装和配置

下载 Git 网址: https://git-scm.com/download 安装 Git 双击安装包, 开始安装. 修改安装路径, 选择非中文无空格路径: 开始安装: 安装成功: 配置 Git 安装完成后, 在任意文件夹内, 右键, 可以显示两个 Git 选项, 就说明安装成功了.

浅聊java集合框架中的java.util.LinkedList

java集合框架总览 Java集合框架是一个用来代表和操纵集合的统一架构,它为管理和组织对象的集合提供了一组类和接口。这个框架包含三个主要部分:接口、实现和算法。 接口: Collection:这是集合框架的根接口,定义了集…

1.2.3 利用注解配置类取代Spring配置文件

本实战将演示如何使用注解配置类取代Spring配置文件,实现基于注解的IoC容器的配置。 创建新包 在net.huawei.spring根包里创建day03子包。 拷贝类和接口 将day02子包里的类和接口拷贝到day03子包。 创建注解配置类 在day03子包里创建SpringConfig类。在该类上添加…

06 Php学习:字符串

PHP 中的字符串变量 在 PHP 中,字符串是一种常见的数据类型,用于存储文本数据。字符串变量可以包含字母、数字、符号等字符,并且可以进行各种操作和处理。以下是关于 PHP 中字符串变量的一些重要信息: 定义字符串变量&#xff1…

进制转换(2 8 10 16 String)

题目 public class Main {static String s "0123456789abcdef";//m 2 8 10 16public static int res(int n,int m) {StringBuffer sb new StringBuffer(); while(n!0) {sb.append(s.charAt(n%m));n/m;}//转换为对应进制之后String s sb.reverse().toString();ch…

达索PLM助力落地新型工业化

中国新时代新征程推进新型工业化 新型工业化,坚持以信息化带动工业化,以工业化促进信息化,就是科技含量高、经济效益好、资源消耗低、环境污染少、人力资源优势得到充分发挥的工业化道路。 新型工业化以高质量发展为目标。传统工业化注重规…

windows 之 redis非安装版,启动与初始化密码

1、下载redis 免安装版 2、解压后,启动服务 3、双击客服端 4、设置密码 config set requirepass root123456成功后,退出服务再次双击 5、登录 再次执行命名时已经没权限了 使用 auth password 登录 成功后,就可以了 auth root123456 …

简单爬虫(求过审核)

游客可以领取七天vip,愉快的开始爬取吧! 首先从单章入手:逆天邪神漫画 第1话 两世为人 - 漫客栈 一章有很多图片,每一张图片都有自己的地址,目标就是找到一个包,包含这一章所有图片的地址。 打开开发者工具——刷新…

人脸识别业务(基于腾讯人脸识别接口)

使用腾讯云人脸识别接口,基于优图祖母模型。 一、准备工作 人脸识别账号 申请腾讯云服务器账号,生成自己的秘钥。记录秘钥和秘钥ID。 创建人员库 记下人员库id 在配置文件application.yml中添加配置。 plateocr:SecretId: 秘钥IDSecretKey: 秘钥ser…

全国水科技大会 免费征集《水环境治理减污降碳协同增效示范案例》

申报时间截止到2024年4月15日,请各单位抓紧申报,申报条件及申报表请联系:13718793867 围绕水环境治理减污降碳协同增效领域,以资源化、生态化和可持续化为导向,面向生态、流城、城市、农村、工业园区、电力、石化、钢…

高效实现红黑树范围查询:RB-ENUMERATE操作的设计与分析

高效实现红黑树范围查询:RB-ENUMERATE操作的设计与分析 一、RB-ENUMERATE操作的需求分析二、RB-ENUMERATE操作的设计思路三、RB-ENUMERATE操作的具体实现四、性能分析五、结论 在红黑树的广泛应用中,我们经常需要对树中的元素进行查询和操作。除了基本的…

堆放砖块-第12届蓝桥杯选拔赛Python真题精选

[导读]:超平老师的Scratch蓝桥杯真题解读系列在推出之后,受到了广大老师和家长的好评,非常感谢各位的认可和厚爱。作为回馈,超平老师计划推出《Python蓝桥杯真题解析100讲》,这是解读系列的第47讲。 堆放砖块&#xf…

SuperMap GIS基础产品FAQ集锦(202403)

一、SuperMap GIS基础产品桌面GIS-FAQ集锦 问题1:【iDesktop】安装了idesktop 11i,现想进行插件开发,根据安装指南安装SuperMap.Tools.RegisterTemplate.exe,运行多次均失败 【问题原因】该脚本是之前老版本针对VS2010写的&…

亚信安慧AntDB:点亮数据灯塔

亚信安慧AntDB 是国产的分布式数据库,它具备快速发展的潜力。随着互联网技术的迅猛发展,大数据时代的到来,数据库的需求不断增长。在这样的背景下,国产分布式数据库正逐渐崭露头角,AntDB作为其中的重要代表&#xff0c…

MySQL学习笔记(数据类型, DDL, DML, DQL, DCL)

Learning note 1、前言2、数据类型2.1、数值类型2.2、字符串类型2.3、日期类型 3、DDL总览数据库/表切换数据库查看表内容创建数据库/表删除数据库/表添加字段删除字段表的重命名修改字段名(以及对应的数据类型) 4、DML往字段里写入具体内容修改字段内容…

Android输入框架

输入是一个操作系统的重要组成部分,没有输入,用户就无法向系统发送指令,也就没法完成人机交互。在Android系统中,输入系统是不可缺少的,下面简单介绍输入系统的整体框架,以下内容参考清华出版社出版的《And…

[react] useRef场景

1.记忆功能 -- 清定时器 先看和useState的差别 代码如下 不断地开启定时器 加上缓存就行,这样每次都是它 2.获取dom节点 当然!你可以直接在模板上面写函数! 函数变种也是可以的 你想获取整个组件也是没问题的 import React, { useRef, forwardRef } from "react";…

uniapp 2.0可视化开发工具高级事件使用技巧探索

摘要 随着移动应用市场的不断扩大和前端技术的飞速发展,开发者们对于快速、高效构建跨平台应用的需求日益增强。uniapp作为一款优秀的跨平台应用开发框架,凭借其强大的功能和易用的特性,赢得了广大开发者的青睐。在uniapp 2.0版本中&#xf…

ht1622不显示无反应问题解决

如果你正在写ht1622 驱动时,怎么看程序都没问题,抓取波形,示波器分析波形,如果都没有问题,那么很大可能是硬件问题,检测看看 ht1622 RD是不是接地了。 RD 低会进入读取模式,所以不用RD 请将RD悬…

基于Java+SpringBoot+Vue养老院管理系统(源码+文档+部署+讲解)

一.系统概述 随着信息时代的来临,过去的传统管理方式缺点逐渐暴露,对过去的传统管理方式的缺点进行分析,采取计算机方式构建养老院管理系统。本文通过课题背景、课题目的及意义相关技术,提出了一种社区活动、活动记录、床位信息、…