rt_hw_stack_init 线程栈初始化参数分析

news2024/11/25 20:51:38

rt_hw_stack_init rt-thread线程栈初始化参数分析

文章目录

  • `rt_hw_stack_init` rt-thread线程栈初始化参数分析
    • Q:
    • A:
      • 1. `rt_hw_stack_init`调用分析
      • 2. `rt_hw_stack_init` 实现分析
        • 2.1 向下增长型栈 rt_hw_stack_init 实现
        • 2.1 向上增长型栈 rt_hw_stack_init 实现
    • 扩展知识:

本文代码内采用的rtthread源码采用master分支分析,对应commit:dbf1463176921bed3310fbd9dd400897b64f501b

Github 源码地址链接!!!

Q:

RT-Thread 在线程初始化的代码内有一段初始化线程堆栈的代码,如下:

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 *)_thread_exit);

在调用 rt_hw_stack_init() 初始化堆栈的时候传入线程栈起始地址进行了 -sizeof(rt-ubase_t) 操作,而在 rt_hw_stack_init() 函数内又进行 stk = stack_addr + sizeof(rt_uint32_t); 将其给加了回去,这操作的意义是什么呢?还是说是历史遗留问题?

在《野火 RT-Thread内核实现与应用开发指南》内也有说到此处的设计,但并未进行升入说明,仅简单的一笔带过,因此大多数读者和我一样都对此充满疑问。

A:

1. rt_hw_stack_init调用分析

分析此问题,首先我们需要结合完整版本的 rt-thread 内核代码进行阅读才能更好的充分理解。

在rt-thread内核代码中,初始化线程堆栈的时候其实是有一个宏声明进行选择的,具体代码如下:

#ifdef ARCH_CPU_STACK_GROWS_UPWARD
    thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
                                          (void *)((char *)thread->stack_addr),
                                          (void *)_thread_exit);
#else
    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 *)_thread_exit);
#endif /* ARCH_CPU_STACK_GROWS_UPWARD */

在这里插入图片描述
也就是针对不同架构的CPU实际传入此函数的参数还存在着不一样的地方!

针对 栈是向下增长型 的CPU架构,传入的参数为:(rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t))
针对 栈是向上增长型 的CPU架构,传入的参数为:(void *)((char *)thread->stack_addr)
而此参数的含义为栈的起始地址!

线程的栈也就是一块连续地址空间的数组,这个是理解栈的前提;针对向上增长型的栈,栈起始地址就是 thread->stack_addr 这很好理解,对于向下增长型的栈,就需要注意了,起始地址并不是,thread->stack_addr + thread->stack_size!!!

既然栈就是一块数组,那我们不妨用数组来理解,char table[100],数组table的最顶部的成员不是table[100],而是table[99],即table[100-1]。因此向下增长的栈从顶部往底部填充数据就类似于数组从尾部往头部填充数据,起始地址为: (rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t))


同时此处的代码是放在 thread.c 内,thread.c是内核文件,是公共的文件,不管你是什么硬件平台,不管你是什么CPU架构,在内核的角度看,它只管给 rt_hw_stack_init 函数传入栈的起始地址即可,因此针对向下增长型的栈在这里 -sizeof(rt_ubase_t)) 并没有任何问题。

再往下层,具体到cpu上,每个cpu都会有对应的 cpuport.c 来实现对应的 rt_hw_stack_init 函数,并根据各自的cpu结构来实现具体的线程栈初始化。

2. rt_hw_stack_init 实现分析

2.1 向下增长型栈 rt_hw_stack_init 实现

针对向下增长型的栈,以 cortex-m4 内核为例:

rt_uint8_t *rt_hw_stack_init(void       *tentry,
                             void       *parameter,
                             rt_uint8_t *stack_addr,
                             void       *texit)
{
    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 */

#if USE_FPU
    stack_frame->flag = 0;
#endif /* USE_FPU */

    /* return task's current stack address */
    return stk;
}

继续以char table[100]作为栈举例:

  1. stk = stack_addr + sizeof(rt_uint32_t); 拿到栈的最顶端的值,也就是100,注意table[100]这个成员是不能写值的。
    在这里插入图片描述

  2. stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8); 之后按8字节 向下对齐,那就 stk 就变成了 96,table[97]、table[98]、table[99]由于字节对齐就保留了,后续也不会去使用,至于table[96]用没用还不知道,我们接着看。
    在这里插入图片描述

  3. stk -= sizeof(struct stack_frame);,stk 减掉 struct stack_frame 结构大小存储 struct stack_frame 结构数据,假定 struct stack_frame 大小4字节, stk -= sizeof(struct stack_frame); 之后 stk 为92,之后写4字节数据,那么stk[92]、stk[93]、stk[94]、stk[95]填充了数据,stk[96]不会去访问。
    在这里插入图片描述

  4. 因此无论字节对齐的时候有没有保留字节,第一步stk虽然切换到了栈最顶端,但是并不会访问最顶端的那个成员,所以是安全的!

2.1 向上增长型栈 rt_hw_stack_init 实现

注意向上增长型栈初始化代码就不是上面那一份了!上面我们说了针对不同的cpu,会有不同的cpuport.c文件来实现对应的 rt_hw_stack_init,因此我们需要找到向上增长型的cpu对应的cpuport.c来分析才行。

在rtthread内核中,目前仅TI的tms320f28379为向上增长型,对应的cpuport.c在 libcpu/ti-dsp/c28x/cpuport.c内,它实现的 rt_hw_stack_init 函数如下:
(不要问我怎么找到的,根据宏全局搜ARCH_CPU_STACK_GROWS_UPWARD=y能发现只有ti这颗用的向上增长型! T_T 😦😦😦)

rt_uint8_t *rt_hw_stack_init(void       *tentry,
                             void       *parameter,
                             rt_uint8_t *stack_addr,
                             void       *texit)
{
    struct stack_frame *stack_frame;
    rt_uint8_t         *stk;
    unsigned long       i;

    stk  = stack_addr;
    stk  = (rt_uint8_t *)RT_ALIGN((rt_uint32_t)stk, 2);
    stk += 1; /*to work around the stack alignment*/

    stack_frame = (struct stack_frame *)stk;

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

    /* configure special registers*/
    stack_frame->exception_stack_frame.dp_st1  = 0x00000A08;
    stack_frame->xar4 = (rt_uint32_t)parameter;
    stack_frame->exception_stack_frame.return_address = (rt_uint32_t)tentry;
    stack_frame->rpc = (rt_uint32_t)texit;

#ifdef __TMS320C28XX_FPU32__
    stack_frame->stf = 0x00000200;
    stack_frame->rb = 0;
#endif

    /* return task's current stack address */
    return stk + sizeof(struct stack_frame);
}

向上增长型就简单了,直接加就可以了,不过向上增长型字节对齐采用的是 RT_ALIGN 向上对齐的方式!

扩展知识:

此外,关于栈除了向上增长和向下增长之外,还有一个知识点:满堆栈空堆栈

概念介绍:

  • 满堆栈不是指堆栈满了的意思,空堆栈也不是指堆栈空的意思,而是根据堆栈指针(SP指针)指向的空间是否存有数据来决定。

  • 当SP指针指向的地址空间没有存放有效数据,则称之为空堆栈

  • 当SP指针指向的地址空间存放有有效数据,则称之为满堆栈

因此针对满堆栈,写入数据的流程为先移动SP指针再填写有效数据;而对于空堆栈则是先填写有效数据再移动堆栈指针。

由满堆栈、空堆栈与向上增长堆栈、向下增长堆栈,共可组成四种组合:

  • 向上递增满堆栈(满增)
  • 向下递增满堆栈(满减)
  • 向上递增空堆栈(空增)
  • 向下递增空堆栈(空简)

创作不易,转载请注明出处!

关注、点赞+收藏,可快速查收博主有关分享!


  • 博客主页:爱出名的狗腿子(点击跳转)

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

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

相关文章

MybatisPlus中安装MybatisX插件及代码生成

创建一个新的模块 导入依赖 设置application.yml 连接数据库 点击下面红色框里面的&#xff0c;然后点apply&#xff0c;最后点ok 选好之后点next base path 根据自己的路径写 选完之后点finish 这样就自己生成了代码。 快速生成CRUD 选择方法后altenter 还可以多条件 方法…

NetSuite 固定资产租赁101

目录 前言 1.新租赁准则的相关内容 1.1 主要变化 1.2 IFRS 16/ASC 842/CAS 21的区别与联系 1.3 新租赁准则实行的意义 2.NetSuite中的租赁功能 2.1 概述 2.2 设置 2.2.1 相关科目设置 2.2.2 资产类型设置 2.3 功能详细说明 2.3.1 案例一 2.3.2 案例二 3.新租赁准则…

kafka 02——三个重要的kafka客户端

kafka 02——三个重要的kafka客户端 1. 前言1.1 关于 Kafka 的安装1.2 常用客户端简介1.3 依赖 2. AdminClient2.1 Admin Configs2.2 AdminClient API2.2.1 设置 AdminClient 对象2.2.2 创建 topic 获取 topic 列表2.2.3 删除topic2.2.4 查看 topic 的描述信息2.2.5 查看 topi…

小白带你部署LNMP分布式部署

目录 前言 一、概述 二、LNMP环境部署 三、配置nginx 1、yum安装 2、编译安装 四、安装 1、编译安装nginx 2、网络源 3、稍作优化 4、修改配置文件vim /usr/local/nginx/conf/nginx.conf 5、书写测试页面 五、部署应用 前言 LNMP平台指的是将Linux、Nginx、MySQL和…

Spring Boot + Vue3前后端分离实战wiki知识库系统<十二>--用户管理单点登录开发一

目标&#xff1a; 在上一次Spring Boot Vue3前后端分离实战wiki知识库系统&#xff1c;十一&#xff1e;--文档管理功能开发三我们已经完成了文档管理的功能模块开发&#xff0c;接下来则开启新模块的学习---用户登录&#xff0c;这块还是有不少知识点值得学习的&#xff0c;…

谈谈语音助手

目录 1.什么是语音助手 2.语音助手的发展过程 3.现在有哪些成熟的语音助手 4.语音助手对人类发展的影响 1.什么是语音助手 语音助手是一种能够通过语音交互与用户进行沟通和执行任务的虚拟助手。它基于人工智能和自然语言处理技术&#xff0c;能够理解用户的语音指令&#x…

LeetCode 36题:有效的数独

题目 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图&#xff…

MEC | 条款4 非必要不提供 default constructor

条款4 非必要不提供 default constructor 文章目录 条款4 非必要不提供 default constructorclasses 必须有默认构造函数&#xff1f;Example1. 产生数组解决方法 2.不适用于 template-based container clases3.虚基函数 探讨>>>>> 欢迎关注公众号【三戒纪元】 …

CANoe自动化工程的搭建

基于XMLCAPL建立自动化工程 1、导入ini文件2、新建 Test Environment3、报告类型4、代码编写 1、导入ini文件 工程的配置的文件&#xff0c;配置DUT相关信息&#xff0c;具体视工程而编写内容。 2、新建 Test Environment 1、新建XML测试用例环境 2、导入XML测试用例文件 …

vulnhub靶机Deathnote

难度&#xff1a;easy 下载地址&#xff1a;https://download.vulnhub.com/deathnote/Deathnote.ova 主机发现 arp-scan -l 端口扫描 nmap --min-rate 10000 -p- 192.168.21.140 进一步查看目标的端口的服务和版本 nmap -sV -sT -O -p22,80 192.168.21.140 扫描端口的漏洞…

微服务实战项目-学成在线-项目优化(redis缓存优化)

微服务实战项目-学成在线-项目优化(redis缓存优化) 1 优化需求 视频播放页面用户未登录也可以访问&#xff0c;当用户观看试学课程时需要请求服务端查询数据&#xff0c;接口如下&#xff1a; 1、根据课程id查询课程信息。 2、根据文件id查询视频信息。 这些接口在用户未认…

Java面试——一分钟搞懂限流算法

为什么限流 运营网站&#xff0c;经常会遇到各种挑战&#xff1a;某黑客发起DoS攻击、网络爬虫网页抓取、商品秒杀活动、双十一与618等场景&#xff0c;会使流量突然激增&#xff0c;如果不限制流量的访问就会使系统宕机。 常见的限流算法 1.漏桶算法&#xff08; LEAKY BUC…

Eclipse-配置彩色输出打印

文章目录 前言配置下载查看是否安装 前言 这是一篇古老的文章&#xff0c;那个时候还在用Eclipse &#xff0c;现在已经换 IDEA 了… 这是一篇 2018 年的文章&#xff0c;我只是将文章从个人比较挪到了CSDN 中 配置 配置完然后下载下面插件即可生成彩色代码。 下载 ANSI …

Vue2-收集表单数据、过滤器、内置指令与自定义指令、Vue生命周期

&#x1f954;&#xff1a;我徒越万重山 千帆过 万木自逢春 更多Vue知识请点击——Vue.js VUE2-Day4 收集表单数据1、不同标签的value属性2、v-model的三个修饰符 过滤器内置指令与自定义指令1、内置指令2、自定义指令定义语法&#xff08;1&#xff09;函数式&#xff08;2&am…

VMware vCenter 6.5 断电后无法启动修复方案

目录 第1章 前言 第2章 问题现象 第3章 解决方案 第4章、Tips 第1章 前言 本文主要介绍VMware vcsa 6.5由于电源异常/存储异常之后无法启动&#xff0c;进入磁盘自检模式处理方法。&#xff08;最近遇到类似情况比较多&#xff0c;Citrix Xenserver也遇到了&#xff0c;后来…

rocketMq启动broker报错找不到或无法加载主类 Files\Java\jdk1.8.0_171\lib\dt.jar;C:\Program]

假如弹出提示框提示‘错误: 找不到或无法加载主类 xxxxxx’。 1.打开runbroker.cmd 将"%CLASSPATH%"加上英文双引号&#xff0c;切勿别加中文双引号 2.打开runserver.cmd 同理 将"%CLASSPATH%"加上英文双引号&#xff0c;切勿别加中文双引号 3.正常执行即…

【Linux】邮件服务器搭建 postfix+dovecot+mysql (终极版 超详细 亲测多遍无问题)

&#x1f341;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; 文章目录 前言基础原理准备工作一 、安装关于权…

一.RocketMQ概念

RocketMQ概念 1.概念2.应用场景3.MQ的优点和缺点4.常见MQ对比 1.概念 MQ(Message Queue)&#xff0c;是一种提供消息队列服务的中间件&#xff0c;也称为消息中间件&#xff0c;是一套提供了消息生产、存储、消费全过程API的软件系统。 RocketMQ是阿里巴巴2016年MQ中间件&…

C++QT教程3——手册4.11.1自带教程(笔记)——创建一个QT快速应用

文章目录 创建一个QT快速应用创建项目创建主视图添加应用逻辑为视图添加动画素材文件 参考文章 创建一个QT快速应用 本教程使用内置的QML类型&#xff0c;介绍了Qt Quick的基本概念。有关可以选择的用户界面选项的更多信息&#xff0c;请参阅用户界面。 本教程描述了如何使用…

约束综合中的逻辑互斥时钟(Logically Exclusive Clocks)

注&#xff1a;本文翻译自Constraining Logically Exclusive Clocks in Synthesis 逻辑互斥时钟的定义 逻辑互斥时钟是指设计中活跃&#xff08;activate&#xff09;但不彼此影响的时钟。常见的情况是&#xff0c;两个时钟作为一个多路选择器的输入&#xff0c;并根据sel信号…