FreeRTOS从代码层面进行原理分析(2 任务的启动)

FreeRTOS分析二—任务的启动

上一篇文章我们带着三个问题开始了对 FreeRTOS 代码的探究。

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

并且在上一篇文章 FreeRTOS从代码层面进行原理分析(1 任务的建立) 中对任务的创建进行了分析。
这篇文章让我们一起对 FreeRTOS 是如何进行任务的调度和切换从代码的角度进行逻辑分析。

任务的调度

在 FreeRTOS 官方教程中,在创建完成任务之后只要调用 xTaskStartScheduler 函数就可以将创建的任务给执行起来了。
在这里插入图片描述
那么在 FreeRTOS 中任务究竟是如何调度起来的呢?现在让我们看一下 xTaskStartScheduler 这个函数的内部。

BaseType_t xPortStartScheduler( void )
{
   ...
   
    /* Make PendSV and SysTick the lowest priority interrupts. */
    portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;

    /* Start the timer that generates the tick ISR.  Interrupts are disabled
     * here already. */
    vPortSetupTimerInterrupt();

    /* Initialise the critical nesting count ready for the first task. */
    uxCriticalNesting = 0;

    /* Start the first task. */
    prvPortStartFirstTask();

    /* Should never get here as the tasks will now be executing!  Call the task
     * exit error function to prevent compiler warnings about a static function
     * not being called in the case that the application writer overrides this
     * functionality by defining configTASK_RETURN_ADDRESS.  Call
     * vTaskSwitchContext() so link time optimisation does not remove the
     * symbol. */
    vTaskSwitchContext();
    prvTaskExitError();

    /* Should not get here! */
    return 0;
}

配置 SysTick

经过省略,上面的代码中的逻辑已经非常清晰的展现在我们的面前了。先是调整 PendSV & SysTick 的中断优先级。
然后调用 vPortSetupTimerInterrupt 设置 SysTick 中断,对应 FreeRTOS 里面就是设置任务的切换时间颗粒度。这个函数里面的代码也是很简单的。

__attribute__( ( weak ) ) void vPortSetupTimerInterrupt( void )
{
...
    /* Stop and clear the SysTick. */
    portNVIC_SYSTICK_CTRL_REG = 0UL;
    portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;

    /* Configure SysTick to interrupt at the requested rate. */
    portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
    portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}

其中两个关于 SysTick 寄存器定义如下

#define portNVIC_SYSTICK_CTRL_REG             ( *( ( volatile uint32_t * ) 0xe000e010 ) )
#define portNVIC_SYSTICK_LOAD_REG             ( *( ( volatile uint32_t * ) 0xe000e014 ) )

在 cortex-m3 中, 0xe000e014 地址对应的是 系统滴答定时器重载值寄存器(Systick Reload Value Register)。
该寄存器类似一个倒计时的感觉,产生一次系统时钟就在这个寄存器上减少1,这个寄存器中的数值就是这个倒计时的初始值。当倒数结束时可以产生系统滴答定时器中断。
需要注意的是,如果是多次循环倒数的话,时钟脉冲就会多一次,如果需要 100 的初始值,那么实际设置 SRVR 寄存器的时候就要设置为 99 ( 所以代码中才需要减 1UL )。如果是单次倒数的话,那么就按实际情况进行设置就可以了,不用减 1 。

0xe000e010 中断类型控制寄存器(Interrupt Controller Type Register)。设置的内容在代码的注释中也说的比较清楚了,下面直接上图。

在这里插入图片描述
所以这里的代码翻译过来就是将 SysTick重新加载值寄存器 设置好后,当寄存器里面的值减到 0 时,就会引发中断。这个中断后面就会用来对 FreeRTOS 中的任务进行切换。切换的代码在后面的章节进行分析。
现在先继续分析后面的代码。

启动第一个任务

prvPortStartFirstTask 这个函数也是根据不同的单片机架构进行实现的。 在 FreeRTOS\Source\portable\GCC\ARM_CM3\port.c 中可以找到关于 Cortex-m3 的实现。

static void prvPortStartFirstTask( void )
{
    __asm volatile (
        " ldr r0, =0xE000ED08 	\n"/* Use the NVIC offset register to locate the stack. */
        " ldr r0, [r0] 			\n"
        " ldr r0, [r0] 			\n"
        " msr msp, r0			\n"/* Set the msp back to the start of the stack. */
        " cpsie i				\n"/* Globally enable interrupts. */
        " cpsie f				\n"
        " dsb					\n"
        " isb					\n"
        " svc 0					\n"/* System call to start first task. */
        " nop					\n"
        " .ltorg				\n"
        );
}

可以看出这里就是使用汇编代码进行实现的了,代码中的注释比较多,非常有助于我们对逻辑的理解。
0xE000ED08 是向量表偏移寄存器(Vector Table Offset Register),这个寄存器是用于记录 中断向量表的存储位置和其地址的偏移。

在这里插入图片描述在这里插入图片描述
在汇编代码中将中断向量表的的起始地址读出来,写入到 MSP 中,用于在 handler 模式下。
这里稍微补充一下 Cortex-m3 中的 SP 寄存器分为 MSP 和 PSP,MSP 用于在 handler 模式下, PSP 用于在 thread 模式下。这样对实现 OS 的各个进程/线程 的多个堆栈是十分方便的办法。详细可以看这篇优秀的博客中的介绍。

后面的 cpsie i & f 注释中已经说的比较清晰了就是 中断的开启。 再往下的 dsb & isb 是设置内存屏障,在这里面作为刷新流水线的作用。

重点介绍 SVC 0 这一行汇编代码。

在这里插入图片描述

SVC 指令会引发 SVC 异常,在 STM32F10x_Startup.s 文件中有中断向量表,其顺序对应 STM32 参考手册中的中断和异常向量。代码和文档都太长了,所以就各截取了部分。
在这里插入图片描述

从上面途中可以看出 SVC 异常对应的就是 SCVall 对应到代码中就是调用 vPortSVCHandler 函数。让我们继续看这个函数。

void vPortSVCHandler( void )
{
    __asm volatile (
        "	ldr	r3, pxCurrentTCBConst2		\n"/* Restore the context. */
        "	ldr r1, [r3]					\n"/* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
        "	ldr r0, [r1]					\n"/* The first item in pxCurrentTCB is the task top of stack. */
        "	ldmia r0!, {r4-r11}				\n"/* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
        "	msr psp, r0						\n"/* Restore the task stack pointer. */
        "	isb								\n"
        "	mov r0, #0 						\n"
        "	msr	basepri, r0					\n"
        "	orr r14, #0xd					\n"
        "	bx r14							\n"
        "									\n"
        "	.align 4						\n"
        "pxCurrentTCBConst2: .word pxCurrentTCB				\n"
        );
}

这里前三行汇编代码表明, r0 中存入了当前 TCB(Task Control Blank, 前面提到过可以利用这个结构索引到任务的栈空间) 的栈定位置的内容。

使用 msr 指令将 r0 的内容存到 PSP 中。关于 PSP 前面有介绍,要是忘记的话这里可直接理解为线程堆栈的指针。

isb 内存屏障,因为本文的主旨是探究 FreeRTOS 是如何对任务进行调度和切换的,所以 这里就暂时不深究为什么 prvPortStartFirstTask 函数使用了 dsb & isb 两个指令,而 vPortSVCHandler 函数只使用了 isb 这一个指令。好奇的读者也可以自行研究一下,欢迎把查到的资料放到评论区哦。

msr	basepri, r0	

对于这行汇编也十分的好懂,我就直接把 basepri 的介绍放在下面的图片中了。
在这里插入图片描述

切换上下文到任务中

"	orr r14, #0xd					\n"
"	bx r14	      						\n"

看到了这两行代码之后大家可能立马就出现了了个疑问, 我知道 R14 代表的是 LR 寄存器,里面一般存的都是函数或者异常的返回地址。
那么新的任务的运行地址是什么时候如何操作才能把它存到 R14 中呢?

实际情况并不是这样的,如果从 handler 模式中返回有三种情况可以使得将 EXC_RETURN 载入 PC 寄存器中。

  1. 使用 POP 指令从 PC 中进行弹栈操作
  2. 使用 BX 指令跳转到任意寄存器
  3. 使用 LDM 或者 LDR 指令给 PC 指定地址

实际上汇编代码正是使用 BX 指令。而 EXC_RETURN[3:0] bits 是可以指定接下来的 SP 是使用 MSP 还是 PSP。

在这里插入图片描述
到这里我们已经知道了,BX r14 会返回 thread mode 并将 SP 设置为 PSP。
但是这样还是不够的,要如何能跳转到正确的任务呢?

其实 《Cortex-m3 技术参考手册》上还介绍了,当处理器陷于异常的时候会自动对8个寄存器进行压栈,而从中断返回时又会对这 8 个寄存器自动进行出栈操作。

在这里插入图片描述
到这里是不是对前一篇文章 FreeRTOS从代码层面进行原理分析(1 任务的建立) 中 xTaskCreate 函数在创建任务的时候 在 port.c 文件中 Cortex-m3 是怎么对栈进行初始初始化的嘛~ 帮你回忆下~

StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
                                     TaskFunction_t pxCode,
                                     void * pvParameters )
{
    /* Simulate the stack frame as it would be created by a context switch
     * interrupt. */
    pxTopOfStack--;                                                      /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
    *pxTopOfStack = portINITIAL_XPSR;                                    /* xPSR */
    pxTopOfStack--;
    *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) portTASK_RETURN_ADDRESS;             /* LR */
    pxTopOfStack -= 5;                                                   /* R12, R3, R2 and R1. */
    *pxTopOfStack = ( StackType_t ) pvParameters;                        /* R0 */
    pxTopOfStack -= 8;                                                   /* R11, R10, R9, R8, R7, R6, R5 and R4. */

    return pxTopOfStack;
}

hhhh~ 是不是一下子就明白了,这些就是处理器自动帮助出入栈的寄存器。 其他寄存器 R4 ~ R11 单独也留了位置。这些设计好的位置会保存所有的寄存器免遭破坏,以达到恢复之前中断任务的作用(后面任务切换的时候会再提)。

再回头看 vPortSVCHandler 函数是不是就逻辑很清晰了~ 把 PSP 指向当前任务的堆栈。再设置通过 R14 设置 EXC_RETURN,将进入 thread 模式,SP 使用 PSP。 再通过 Cortex-m3 的自动将这 8 个寄存器弹栈的功能,完成切换当前执行任务的功能。

这篇写的也有点长了,任务的切换写到下一篇 FreeRTOS从代码层面进行原理分析(3 任务的切换)

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

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

相关文章

学习刷题-13

3.23 hw机试【二叉树】 剑指offer32 剑指 offer32(一、二、三)_剑指offer 32-CSDN博客 从上到下打印二叉树I 一棵圣诞树记作根节点为 root 的二叉树,节点值为该位置装饰彩灯的颜色编号。请按照从 左 到 右 的顺序返回每一层彩灯编号。 输…

产品推荐 | 基于 Zynq UltraScale+ XCZU27DR的 FACE-RFSoC-C高性能自适应射频开发平台

一、产品概述 FACE-RFSOC-C自适应射频开发平台,是FACE系列新一代的产品。 平台搭载有16nm工艺的Zynq UltraScale™ RFSoC系列主器件。该器件集成数千兆采样RF数据转换器和ARM Cortex-A53处理子系统和UltraScale可编程逻辑,是一款单芯片自适应射频平台。…

电脑卸载软件怎么清理干净?电脑清理的5种方法

随着我们在电脑上安装和卸载各种软件,很多时候我们会发现,即使软件被卸载,其残留的文件和注册表项仍然存在于电脑中,这不仅占用了宝贵的磁盘空间,还可能影响电脑的性能。那么,如何确保在卸载软件时能够彻底…

FakeLocation报虚拟位置服务连接失败,请重启设备再试

虚拟位置服务连接失败,请重启设备再试 最近遇到一个手机软件报的bug“虚拟位置服务连接失败,请重启设备再试” 因为我的实体“虚拟机”已经root,按道理是不可能报这个错的 折腾了2天,终于解决了 原来是这样,安装最新…

Linux文件系统 底层原理

linux文件、目录、Inode inode负责文件的元数据和数据存储,文件存储块负责实际数据的存储,而目录文件维护文件名和inode之间的联系。 1. 用户空间到内核空间 首先,当用户程序请求打开一个文件时(例如使用open系统调用&#xff09…

关系型数据库mysql(6)备份与恢复

一.数据备份的重要性 (1)在生产环境中,数据的安全性至关重要 (2)任何数据的丢失都可能产生严重的后果 (3)造成数据丢失的原因 程序错误人为操作失误运算错误磁盘故障灾难(如火灾…

YOLOv9改进策略:卷积魔改 | SCConv:空间和通道重建卷积,即插即用,助力检测 | CVPR2023

💡💡💡本文改进内容: CVPR2023 SCConv 由两个单元组成:空间重建单元(SRU)和通道重建单元(CRU)。 SRU利用分离重建方法来抑制空间冗余,而CRU使用分割-变换-融…

ssm004新生报到系统+jsp

新生报到系统的设计与实现 摘 要 互联网发展至今,无论是其理论还是技术都已经成熟,而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播,搭配信息管理工具可以很好地为人们提供服务。针对新生报到信息管理混乱,出错率…

【k8s网络】梳理cni发展脉络

参考 《深入剖析 Kubernetes(张磊)》 补充 详解 Calico 三种模式(与 Fannel 网络对比学习)_calico vxlan-CSDN博客 容器网络 容器的网络栈 每个容器有自己的 net namespace net namespace 可以称之为网络栈所谓“网络栈”&…

mac 系统如何生成秘钥

1.打开终端,输入 cd ~/.ssh 进入.ssh目录,输入 ls 检查是否已经存在SSH密钥。如果看到类似 id_rsa.pub 的文件,说明已经有一对公钥和私钥,不用新建,直接查看就可以,如果没有需要生成新的密钥。 2.在终端输…

【k8s调度】梳理调度相关知识与device plugin

资源 CPU —— 可压缩资源 —— 资源不足时,只会”饥饿“,不会退出内存 —— 不可压缩资源 —— 资源不足时,会 OOM 被内核杀死 请求与限制 调度时候, kube-scheduler 按照 Requests 的值进行计算真正设置 Cgroups 限制时&…

探索AI大模型学习的未来发展与挑战

🍎个人博客:个人主页 🏆个人专栏:日常聊聊 ⛳️ 功不唐捐,玉汝于成 目录 前言 正文 AI大模型学习的理论基础 AI大模型的训练与优化 AI大模型在特定领域的应用 AI大模型学习的伦理与社会影响 未来发展趋势与挑…

【智能算法】秃鹰搜索算法(BES)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2020年, Alsattar等人受到秃鹰猎食自然行为启发,提出了秃鹰搜索算法(Bald Eagle Search,BES)。 2.算法原理 2.1算法思想 BES主要分为三…

一篇复现Docker镜像操作与容器操作

华子目录 Docker镜像操作创建镜像方式1docker commit示例 方式2docker import示例1:从本地文件系统导入示例2:从远程URL导入注意事项 方式3docker build示例1:构建镜像并指定名称和标签示例2:使用自定义的 Dockerfile 路径构建镜像…

SiteServer 学习笔记 Day01 新增站点字段和获取站点字段

1、站点字段设置,在站点下选择设置管理->字段设置->站点字段设置。新增字段,Iphone、welcome、moblie、email,如下图所示: 2、查看内容字段设置,设置管理->字段设置->内容字段设置。新建站点后&#xff0c…

docker的部署与安装以及部署一个docker(容器)应用及docker容器常出现的问题

docker 架构图 一、docker的部署与安装 1、在 CentOS 上安装 Docker 移除旧版本(如果有的话):sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-…

TCP重传机制详解——04FACK

文章目录 TCP重传机制详解——04FACK什么是FACKFACK的发展为什么要引入FACK实战抓包讲解开启FACK场景,且达到dup ACK门限值开启FACK场景,未达到dup ACK门限值 为什么要淘汰FACK总结REF TCP重传机制详解——04FACK 什么是FACK FACK的全称是forward ackn…

如果出现虚拟机与此版本VMware版本不兼容问题,的解决方法

问题样式: 解决方法与步骤: 1.右击虚拟机 ————》管理 ————》更改硬件兼容性 2.再弹出的页面中点击下一步 ————》下一步 3.再这个页面点击更改此虚拟机 ————》下一步 然后就可以愉快的使用虚拟机了

计算机复试面试问答准备(未完)

目录 1、理解多态性2、怎么逆置⼀个链表3、顺序表和链表的区别4、树的存储结构5、什么是哈夫曼树?简述哈夫曼树的构造过程。介绍哈夫曼树的特性。6、哈夫曼编码的编码和解码过程7、图的遍历方式8、图的存储方式9、最小生成树10、迪杰斯特拉算法11、佛洛依德算法12、…

ssm005基于SSM框架的购物商城系统+jsp

购物商城系统的设计与实现 摘 要 网络技术和计算机技术发展至今,已经拥有了深厚的理论基础,并在现实中进行了充分运用,尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代,所以对于信息的宣传和管理就…