rt-thread------任务调度

news2024/10/10 10:31:04

rt-thread------任务调度

1. 线程初始化

在rt-thread中线程主要包括以下一些内容,线程控制块、线程栈、函数入口。
在这里插入图片描述

1.1线程创建函数

RTOS基本都包括两种线程方式:动态创建rt_thread_create()和静态创建rt_thread_init()
因为有些系统设计时对安全性要求比较高,内存需要提前分配好,只能使用静态创建的方式。

1.2 线程控制块

1.2.1线程结构体

线程结构体的一些主要成员

struct rt_thread
{
    /* stack point and entry */
    void       *sp;                                     /**< stack point */
    void       *entry;                                  /**< entry */
    void       *parameter;                              /**< parameter */
    void       *stack_addr;                             /**< stack address */
    rt_uint32_t stack_size;                             /**< stack size */
    /* error code */
    rt_err_t    error;                                  /**< error code */
    rt_uint8_t  stat;                                   /**< thread status */
    /* priority */
    rt_uint8_t  current_priority;                       /**< current priority */
    rt_uint8_t  init_priority;                          /**< initialized priority */
    
    rt_ubase_t  init_tick;                              /**< thread's initialized tick */
    rt_ubase_t  remaining_tick;                         /**< remaining tick */
    struct rt_timer thread_timer;                       /**< built-in thread timer */
};

优先级里有当current_priorityinit_priority后续文章讲到互斥量时有优先级继承时会详细说明。
init_tickremaining_tick在第3.2节时间片概念中会详细说明。

1.2.2 线程创建

无论选择动态还是静态创建最后都会调用这个函数static rt_err_t _thread_init()。简要分析一下这个函数的作用

    thread->entry = (void *)entry;//函数指针
    thread->parameter = parameter;//创建任务时可以带个参数

    /* stack init 任务栈初始化*/
    thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
                                          (rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t)),
                                          (void *)rt_thread_exit);
    thread->stack_addr = stack_start;//初始化栈顶指针
    thread->stack_size = stack_size;//初始化栈的大小
    /* priority init 优先级初始化*/
    RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);
    thread->init_priority    = priority;//初始化初始化优先级
    thread->current_priority = priority;//初始化当前优先级
    /* tick init */
    thread->init_tick      = tick;//初始化时间片
    thread->remaining_tick = tick;//初始化剩余时间片

1.2.3 线程栈的初始化

线程栈的结构体如下代码,其实就是我们熟悉ARM中16个寄存器。但是他们定义的顺序不是r0-15,而是r4~ r11,r0~ r3,r12,lr,pc,psr,其原因在2.2.1节会讲明。

struct exception_stack_frame
{
    rt_uint32_t r0;
    rt_uint32_t r1;
    rt_uint32_t r2;
    rt_uint32_t r3;
    rt_uint32_t r12;
    rt_uint32_t lr;
    rt_uint32_t pc;
    rt_uint32_t psr;
};

struct stack_frame
{
    /* r4 ~ r11 register */
    rt_uint32_t r4;
    rt_uint32_t r5;
    rt_uint32_t r6;
    rt_uint32_t r7;
    rt_uint32_t r8;
    rt_uint32_t r9;
    rt_uint32_t r10;
    rt_uint32_t r11;

    struct exception_stack_frame exception_stack_frame;
};

线程栈初始化函数主要实现:

    struct stack_frame *stack_frame;
    rt_uint8_t         *stk;
    unsigned long       i;

    stk  = stack_addr + sizeof(rt_uint32_t);
    stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
    stk -= sizeof(struct stack_frame);

    stack_frame = (struct stack_frame *)stk;

    /* init all register */
    for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
    {
        ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
    }

    stack_frame->exception_stack_frame.r0  = (unsigned long)parameter; /* r0 : argument */
    stack_frame->exception_stack_frame.r1  = 0;                        /* r1 */
    stack_frame->exception_stack_frame.r2  = 0;                        /* r2 */
    stack_frame->exception_stack_frame.r3  = 0;                        /* r3 */
    stack_frame->exception_stack_frame.r12 = 0;                        /* r12 */
    stack_frame->exception_stack_frame.lr  = (unsigned long)texit;     /* lr */
    stack_frame->exception_stack_frame.pc  = (unsigned long)tentry;    /* entry point, pc */
    stack_frame->exception_stack_frame.psr = 0x01000000L;              /* PSR */

用一张图片来展示这个过程
在这里插入图片描述

2. 线程状态切换

2.1线程的状态

rt-thread将线程分成5个状态:初始状态、就绪状态、运行状态、挂起状态和关闭状态。如图所示:
在这里插入图片描述
注:rt-thread现在只允许线程A挂起线程A,不允许线程A挂起线程B,所以rt_thread_suspend()理论上并不能把一个就绪态的任务转换到挂起态,而是把运行态任务切换至挂起态。

  • 初始状态
    任务使用rt_thread_init()/ rt_thread_create()创建后则处于初始状态。
    任务会被添加到对应优先级的就绪态链表中,如下图所示:
    在这里插入图片描述

  • 就绪状态
    就绪态顾名思义就是程序准备运行的状态,以下是几种会让程序处于就绪态的方式:

  1. 创建线程后当调用rt_thread_startup()后进入就绪状态
  2. 在运行状态中被更高优先级线程抢占了
  3. 时间片用完轮到同优先级线程执行
  4. 从挂起状态恢复后由于有更高优先级任务需要执行
  • 运行状态
    当前运行的线程
  • 挂起状态
  1. 通过系统阻塞函数
  2. 等待同步资源
  • 关闭状态
    从运行态退出的程序
    使用rt_thread_delete()/rt_thread_detach()删除的线程

2.2 线程切换

线程A运行一段时间后切换线程B,需要先保存A线程的现场,再去切换线程B。若再切换成线程A,则再需要保存线程B的现场,再去切换线程A。

2.2.1保护现场

保护现场保护什么呢?以下面一段代码解释一下:

void task_A()
{
	int a = 1;
	int b ;
	b = a + 2;

}

其汇编代码如下图所示:
大致意思是这样的(也不用全懂哈哈)

0x08000144 2001      MOVS     r0,#0x01       :将1存入r0
0x08000146 9001      STR      r0,[sp,#0x04]	 :将r0存入sp+4的位置也就是局部变量a
     7:         b = a + 2; 
0x08000148 9801      LDR      r0,[sp,#0x04]  :将sp+4的值存入r0
0x0800014A 1C80      ADDS     r0,r0,#2		 :r0=r0+2
0x0800014C 9000      STR      r0,[sp,#0x00]  :将r0存入sp的位置
     8:         return b; 
0x0800014E 9800      LDR      r0,[sp,#0x00]   :sp+0的值存入r0

ARM架构中一共有16个寄存器(r0-r7,r8-r12,sp,lr,pc)中间运算的时候会将值存入r0,若此时任务切换则r0的值会在别的任务中被覆盖,恢复后是不可预料的。所以再任务切换前需要需要保护这16个寄存器。局部变量a不需要保存,因为他保存再线程A的栈中间,出栈即可。
保存现场的处理函数在pendSV异常中,主要是这两句汇编


    MRS     r1, psp                 ; get from thread stack pointer
    STMFD   r1!, {r4 - r11}         ; push r4 - r11 register

STMFD Store Multi Full Dec
找出线程A的线程栈,将r4 - r11多个寄存器满减压栈到线程A的栈中。前文说了有16个寄存器,软件只存了这8个剩下的几个寄存器在进入中断是已经存储到线程A的栈中,如下图:
在这里插入图片描述
还有一段描述

细心的读者一定在猜测:为啥袒护R0‐R3以及R12呢,R4‐R11就是下等公民?原来,在ARM
上,有一套的C函数调用标准约定(《C/C++ Procedure Call Standard for the ARM Architecture》,
AAPCS, Ref5)。个中原因就在它上面:它使得中断服务例程能用C语言编写,编译器优先使
用被入栈的寄存器来保存中间结果(当然,如果程序过大也可能要用到R4‐R11,此时编译器
负责生成代码来push它们。但是,ISR应该短小精悍,不要让系统如此操心——译者注

这也解释了rt-thread线程栈结构体r0-r15不是连续的原因。

2.2.2切换任务

有了保护现场的经验,切换任务就简单了。

    LDMFD   r1!, {r4 - r11}         ; pop r4 - r11 register
    MSR     psp, r1                 ; update stack pointer

pendsv_exit
    ; restore interrupt
    MSR     PRIMASK, r2

    ORR     lr, lr, #0x04

从线程B的栈中恢复r4-r11寄存器,跟新SP指针指向r11。推出异常硬件自动恢复线程B的R0‐R3以及R12。
任务切换比较深奥,多读两遍再结合韦东山老师的视频就能理解了。若还不能理解可以先学使用,再过几个月回过来看看。

2.2.3为什么是PendSV?

这个问题答案在《Cortex-M3 权威指南》中由说明。
在这里插入图片描述

上图是两个任务轮转调度的示意图。但若在产生 SysTick 异常时正在响应一个中断,则
SysTick 异常会抢占其 ISR。在这种情况下,OS 不得执行上下文切换,否则将使中断请求被延
迟,而且在真实系统中延迟时间还往往不可预知——任何有一丁点实时要求的系统都决不能
容忍这种事。因此,在 CM3 中也是严禁没商量——如果 OS 在某中断活跃时尝试切入线程模
式,将触犯用法 fault 异常

在这里插入图片描述

PendSV 异常会自动延迟上下文切换的请求,
直到其它的 ISR 都完成了处理后才放行。为实现这个机制,需要把 PendSV 编程为最低优先级的异常。如果 OS 检测到某 IRQ 正在活动并且被 SysTick 抢占,它将悬起一个 PendSV 异常,以便缓期执行上下文切换。

3. 线程优先级与时间片

3.1优先级

假设我们创建3个线程ABC,优先级分别时12,12,15(如下图所示)。在rt-thread中优先级数字越低优先级越高,这里与freeRTOS相反。rt-thread链表插入使用的是rt_list_insert_before()头插法,双向循环链表头的前面也等价于链表的最后面(可能有点绕),所以初始化的顺序是先初始化线程A再初始化线程B。
rt-thread优先级规则:任务调度器会优先找到优先级最高的链表的第一项执行
freeRTOS优先级规则:任务调度器会优先找到优先级最高的链表的最后一项执行

在这里插入图片描述
任务调度器会优先找到优先级最高的链表,也就是优先级为12的链表,然后取链表头后面的第一个线程(A),当A运行时则从就绪态链表中移除。当线程A被挂起时,任务调度器会继续寻找优先级最高的第一个线程,此时是B。
在这里插入图片描述
当任务B被挂起时,按照上述规则则会执行任务C。
当挂起态的任务结束阻塞调用rt_thread_resume()会重新插入到就绪态链表中。
在这里插入图片描述

3.2时间片

当A,B两个线程均无阻塞且处于当前最高就绪态相同优先级,A运行时间片为10个tick,B为5个tick,当前执行B线程。按照上述优先级规则执行那么永远只会执行B线程,A永远不会执行。为了避免此类情况发生,设计了时间片的概念。当B执行了5个tick后,则挂到就绪态当前优先级的队尾,此时任务切换后执行的便是A线程。A执行完10个tick则会切换会B。AB两个线程都得到了执行的时间
在这里插入图片描述
其背后原理是,当任务B的remaining_tick用完后会被移动到就绪态链表的最后面。当任务调度器调度按照3.1节内叙述的规则找到任务A的控制块运行。
在这里插入图片描述

参考

b站:韦东山RT-Thread系列教程: RT-Thread的内部机制
《RT-Thread 完全开发手册之快速入门》
《Cortex-M3 权威指南》

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

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

相关文章

硬件学习件Cadence day13 PCB设计中一些设置, 铜皮到钻孔的距离设置, 差分线的设置,板层信息表

1. 设置铺铜中铜皮到钻口&#xff0c;连线的距离。 1. 打开设置界面 2. 设计界面 调整到 铜皮设置界面 2. 高速线的设置 &#xff08;差分对传输线的设置&#xff09; 1. 打开设置界面 2. 来到 差分线设置界面 3. 把界面往右看&#xff0c; 设置差分线的之间距离&#xff0c;…

Python之并发编程介绍

一、并发编程介绍 1.1、串行、并行与并发的区别 串行(serial)&#xff1a;一个CPU上&#xff0c;按顺序完成多个任务并行(parallelism)&#xff1a;指的是任务数小于等于cpu核数&#xff0c;即任务真的是一起执行的并发(concurrency)&#xff1a;一个CPU采用时间片管理方式&am…

TrOCR – 基于 Transformer 的 OCR 入门指南

多年来,光学字符识别 (OCR) 出现了多项创新。它对零售、医疗保健、银行和许多其他行业的影响是巨大的。尽管有着悠久的历史和多种最先进的模型,研究人员仍在不断创新。与深度学习的许多其他领域一样,OCR 也看到了变压器神经网络的重要性和影响。如今,我们拥有像TrOCR(Tran…

franka_ros中的一些子包的使用

franka_visualization包 该软件包包含连接到机器人并发布机器人和夹爪关节状态以在 RViz 中进行可视化的发布者。要运行此包启动&#xff1a; roslaunch franka_visualization franka_visualization.launch robot_ip:<fci-ip> \load_gripper:<true|false> 比如&a…

UI自动化测试工具详解

常用工具 1、QTP&#xff1a;商业化的功能测试工具&#xff0c;收费&#xff0c;可用于web自动化测试 2、Robot Framework&#xff1a;基于Python可扩展的关键字驱动的测试自动化框架 3、Selenium &#xff1a;开源的web自动化测试工具&#xff0c;免费&#xff0c;主要用于功…

SpringCloud-微服务CAP原则

接上文 SpringCloud-Config配置中心 到此部分即微服务的入门。 总的来说&#xff0c;数据存放的节点数越多&#xff0c;分区容忍性就越高&#xff0c;但要复制更新的次数就越多&#xff0c;一致性就越难保证。同时为了保证一致性&#xff0c;更新所有节点数据所需要的时间就…

Python教程33:关于在使用zipfile模块,出现中文乱码的解决办法

zipfile是Python标准库中的一个模块&#xff0c;zipfile里有两个class, 分别是ZipFile和ZipInfo&#xff0c;用来创建和读取zip文件&#xff0c;而ZipInfo是存储的zip文件的每个文件的信息的。ZIP文件是一种常见的存档文件格式&#xff0c;它可以将多个文件和目录压缩为一个文件…

帝国cms后台访问链接提示“非法来源”解决方法

提示“非法来源”的原因 帝国CMS更新升级7.2后,新增了后台安全模式,后台推出了金刚模式来验证链接来源。后台所有链接都需要登录后才能访问,直接强制访问后台页面链接都会提示“非法来源”。不是正常登录后台的用户无法直接访问到内容,保证了后台数据安全。 那么我们在日常…

Table of Laplace Transforms

https://www.math.uh.edu/~etgen/LaplaceT.pdf http://web.mit.edu/2.737/www/handouts/LaplaceTransforms.pdf https://www.integral-table.com/downloads/LaplaceTable.pdf https://www.math.purdue.edu/~caiz/MA527-cai/lectures/Table%20of%20Laplace%20Transforms.pdf

阅读源码工具Sourcetrail

收费工具Source Insight、Understand Sourcetrail开源工具 一、下载安装 接下来就是download&#xff0c;在GitHub的release页面选择自己系统对应的发布版本下载安装&#xff1a; 安装好后&#xff0c;运行程序&#xff0c;会出现这样的界面&#xff1a; 二、应用 选择“New…

2023年最佳研发管理平台评选:哪家表现出色?

“研发管理平台哪家好&#xff1f;以下是一些知名的研发管理软件品牌&#xff1a;Zoho Projects、JIRA、Trello、Microsoft Teams、GitLab。’” 企业需要不断创新以保持竞争力。研发是企业创新的核心&#xff0c;而研发管理平台则为企业提供了一个有效的工具来支持和管理其研发…

SpringMvc第四战-【SpringMvc文件上传,下载】

目录 一.SpringMvc文件上传 1.导入依赖&#xff08;在pom.xml中&#xff09; 2.配置文件上传解析器&#xff08;在spring-mvc.xml中&#xff09; 3.前端标记多功能表单&#xff08;构建一个jsp界面来操作&#xff09; 4.将文件写出流&#xff0c;然后写入服务器 5.配置映…

【ccf-csp题解】第1次csp认证-第三题-命令行选项-字符串模拟

题目描述 思路讲解 本题是一个简单的字符串模拟题&#xff0c;这种题目是csp认证第三题的常客 大致思路是用两个bool数组记录某一个选项&#xff08;0--25下标对应小写字母a--z&#xff09;&#xff0c;第一个数组中无参选项为true&#xff0c;第二个数组中有参选项为true&a…

Pandas数据中的浅拷贝与深拷贝

pandas库主要有两种数据结构DataFrames和Series。这些数据结构在内部用索引数组和数据数组表示&#xff0c;索引数组标记数据&#xff0c;数据数组包含实际数据。现在&#xff0c;当我们试图复制这些数据结构&#xff08;DataFrames和Series&#xff09;时&#xff0c;我们实际…

如何使用PySide2将designer设计的ui文件加载到Python类上鼠标拖拽显示路径

应用场景&#xff1a; designer快速设计好UI文件后&#xff0c;需要增加一些特别的界面功能&#xff0c;如文件拖拽显示文件路径功能。 方法如下&#xff1a; from PySide2.QtWidgets import QApplication, QMainWindow from PySide2.QtUiTools import loadUiTypeUi_MainWindo…

Linux:LVS (DR群集搭建)

环境 dr服务器&#xff1a; ens33网络接口ip&#xff1a;192.168.254.4 ens33:0接口&#xff1a;192.168.254.66 web1服务器&#xff1a;ens33ip:192.168.254.1 lo:0接口ip:192.168.254.66 web2服务器&#xff1a;ens33ip:192.168.254.2 lo:0接口ip:192.168.254.66 nfs数据…

web 学习之 超链接文本

前言 HTML中的超链接文本用于创建可点击的链接&#xff0c;允许用户跳转到其他网页、文件或资源。超链接文本通常是可识别的文本或图像&#xff0c;当用户点击它时&#xff0c;浏览器会加载链接指定的目标。在HTML中&#xff0c;超链接文本使用标签&#xff08;anchor标签&…

OPC UA协议基础

C#开发使用参考github地址&#xff1a;GitHub - OPCFoundation/UA-.NETStandard: OPC Unified Architecture .NET Standard 同步到gitee的地址&#xff1a;UA-.NETStandard: github导入&#xff1a;https://github.com/OPCFoundation/UA-.NETStandard 协议参考网站&#xff1…

【23种设计模式】组合模式【⭐】

个人主页&#xff1a;金鳞踏雨 个人简介&#xff1a;大家好&#xff0c;我是金鳞&#xff0c;一个初出茅庐的Java小白 目前状况&#xff1a;22届普通本科毕业生&#xff0c;几经波折了&#xff0c;现在任职于一家国内大型知名日化公司&#xff0c;从事Java开发工作 我的博客&am…

多态语法,析构多态

目录 多态的构成条件 虚函数重写&#xff1a; 多态的构成条件 多态是在不同继承关系的类对象&#xff0c;去调用同一函数&#xff0c;产生了不同的行为。比如 Student 继承了 Person 。 Person 对象买票全价&#xff0c; Student 对象买票半价。 继承中要 构成多态两个条件 …