0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

冬至子 来源:jaffer 作者:jaffer 2023-08-14 16:50 次阅读

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

1.jpg

也就是针对不同架构的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]作为栈举例:

stk = stack_addr + sizeof(rt_uint32_t); 拿到栈的最顶端的值,也就是100,注意table[100]这个成员是不能写值的。

1.jpg

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

1.jpg

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]不会去访问。

1.jpg

因此无论字节对齐的时候有没有保留字节,第一步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指针再填写有效数据;而对于空堆栈则是先填写有效数据再移动堆栈指针。

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

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

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储器
    +关注

    关注

    38

    文章

    7462

    浏览量

    163663
  • cpu芯片
    +关注

    关注

    0

    文章

    46

    浏览量

    13599
  • Cortex-M4
    +关注

    关注

    6

    文章

    89

    浏览量

    46524
  • FPU
    FPU
    +关注

    关注

    0

    文章

    42

    浏览量

    21301
  • RT-Thread
    +关注

    关注

    31

    文章

    1277

    浏览量

    39972
收藏 人收藏

    评论

    相关推荐

    RT-Thread自动初始化详解

    我们知道,在写裸机程序时,当我们完成硬件初始化后,就需要在主函数中进行调用。当我们使用RT-Thread后,完全不需要这样做了,我们可以将硬件等自动初始化RT-Thread 自动
    的头像 发表于 06-25 21:38 1.1w次阅读
    <b class='flag-5'>RT-Thread</b>自动<b class='flag-5'>初始化</b>详解

    什么是RT-Thread线程管理看完你就懂了

    () 函数里添加自己的应用程序初始化代码。线程的管理方式本章前面 2 节对线程的功能与工作机制进行了概念上的讲解,相信大家对线程已经不再陌生。本节将深入到
    发表于 03-29 06:16

    RT-Thread 踩坑记录 - 初始化线程时使用局部变量

    前言为了不再CTRL+C,CTRL+V,修改,我开始尝试手敲代码。RT-Thread线程可以静态初始化,也可以动态申请内存的方式创建静态初始化线程
    发表于 05-13 18:40

    RT-Thread线程简介

    文章目录RT-Thread线程简介源码分析初始化线程线程脱离启动
    发表于 08-24 07:56

    如何对RT-Thread系统进行初始化

    RT-Thread是如何启动的?如何对RT-Thread系统进行初始化呢?
    发表于 11-30 07:54

    RT-Thread自动初始化机制简介

    的 main 线程里被调用执行,这个时候硬件环境和操作系统已经初始化完成,可以执行应用相关代码。rt_components_init() 函数会遍历通过剩下的其他几个宏申明的初始化函数
    发表于 04-06 18:08

    RT-Thread系统初始化与启动流程详细描述

    系统定时器线程voidrt_application_init ()创建用户线程详细描述RT-Thread 的启动流程RT-Thread 的启动流程,大致可以分为四个部分:(1)
    发表于 08-25 15:15

    如何对RT-Thread系统的线程进行初始化

    , rt_uint32_t tick);rt_thread_init函数用来初始化静态线程对象。而线程句柄(或者说
    发表于 08-30 14:51

    RT-Thread操作系统中的线程脱离是什意思

    例程可调用函数参数参数描述thread线程句柄,它应该是由rt_thread_init进行初始化
    发表于 08-30 14:53

    RT-Thread自动初始化原理分析

    ;}这里我们直接就可以使用 printf 进行打印,而没有进行一些其它的初始化,参考这个思路引出了 RT-Thread 的自动初始化机制。RT-Thread 自动
    发表于 12-05 14:17

    一文详解RT-Thread自动初始化

    在学RT-Thread时,经常能听到这个词:自动初始化。用起来也非常容易,一个宏就解决了,但是原理是什么呢?
    的头像 发表于 07-21 10:17 7555次阅读
    一文详解<b class='flag-5'>RT-Thread</b>自动<b class='flag-5'>初始化</b>

    RT-Thread全球技术大会:如何使用组件以及自动初始化流程

    RT-Thread全球技术大会:如何使用组件和自动初始化流程           审核编辑:彭静
    的头像 发表于 05-27 15:16 915次阅读
    <b class='flag-5'>RT-Thread</b>全球技术大会:如何使用组件以及自动<b class='flag-5'>初始化</b>流程

    RT-Thread自动初始化机制

      在分析之前首先查阅 RT-Thread 的官方文档 [RT-Thread 自动初始化机制](https://www.rt-thread.
    的头像 发表于 06-17 08:52 2580次阅读
    <b class='flag-5'>RT-Thread</b>自动<b class='flag-5'>初始化</b>机制

    rt-thread 优化系列(六)启动流程重构

    去年此时,笔者刚接触 rt-thread 的时候,被它的初始化过程深深折服了。第一次打开一个 rt-thread 的项目,竟然没找到多线程在哪儿初始
    的头像 发表于 07-04 15:30 1722次阅读
    <b class='flag-5'>rt-thread</b> 优化系列(六)启动流程重构

    RT-Thread使用经验分享:链表未初始化造成死机

    最近在开发调试基于RT-Thread 的驱动时,遇到一个比较奇怪的死机问题,后来经过一步步排查,终于发现是驱动的链表节点没有初始化造成的死机
    的头像 发表于 10-08 14:49 897次阅读
    <b class='flag-5'>RT-Thread</b>使用经验分享:链表未<b class='flag-5'>初始化</b>造成死机