Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
原创征文
栈stack
rt_hw_stack_init 线程栈初始化参数分析
发布于 2023-08-13 18:34:36 浏览:760
订阅该版
[tocm] # `rt_hw_stack_init` rt-thread线程栈初始化参数分析 > 本文代码内采用的rtthread源码采用master分支分析,对应commit:dbf1463176921bed3310fbd9dd400897b64f501b > > [Github 源码地址链接!!!](https://github.com/RT-Thread/rt-thread/blob/a6959a9ef8a8b3233a2d0b395d43f07711bf765c/src/thread.c#L192) ## Q: RT-Thread 在线程初始化的代码内有一段初始化线程堆栈的代码,如下: ```c 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内核代码中,初始化线程堆栈的时候其实是有一个宏声明进行选择的,具体代码如下: ```c #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 */ ``` ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230813/c8ee6f8466034f4e5ceee6c4ce3d9b92.png.webp) 也就是针对不同架构的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 内核为例: ```c 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]这个成员是不能写值的。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230813/81d8d02242cfadb7e4e9fc0dac1ddc51.png) 2. `stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);` 之后按8字节 **向下对齐**,那就 stk 就变成了 96,table[97]、table[98]、table[99]由于字节对齐就保留了,后续也不会去使用,至于table[96]用没用还不知道,我们接着看。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230813/12b5048791421ccdea4a9cb9504122d7.png) 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]不会去访问。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230813/3dad746574fa59fcfe99aa713e1e0833.png) 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) ```c 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指针再填写有效数据;而对于空堆栈则是先填写有效数据再移动堆栈指针。 由满堆栈、空堆栈与向上增长堆栈、向下增长堆栈,共可组成四种组合: + 向上递增满堆栈(满增) + 向下递增满堆栈(满减) + 向上递增空堆栈(空增) + 向下递增空堆栈(空简) --- **创作不易,转载请注明出处!** **关注、点赞+收藏,可快速查收博主有关分享!** --- + 个人博客主页:[**`爱出名的狗腿子(点击跳转)`**](https://blog.csdn.net/qq_43332314) + RT-Thread个人主页:[jaffer](https://club.rt-thread.org/u/5f9bb32e3c9a8ca5.html) ---
1
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
jaffer
You can contact me by Email, jaffer.work@foxmail.com, if you want.
文章
4
回答
10
被采纳
0
关注TA
发私信
相关文章
1
局部变量的存储在哪段内存区域
2
内联函数不压栈,会因为中断导致栈内现场被破坏吗
3
串口输出线程输出异常
4
idle->sp被改写,导致内存溢出
5
如何在调试状态下查看线程堆栈的占用量?
6
map文件堆栈分配查看
7
请教线程堆栈的作用以及大小设置
8
如何打印出函数调用栈
9
线程初始化时栈地址为什么加4
10
rtthread中的栈设置请教?
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
a1012112796
20
个答案
3
次被采纳
张世争
11
个答案
3
次被采纳
踩姑娘的小蘑菇
7
个答案
3
次被采纳
rv666
9
个答案
2
次被采纳
用户名由3_15位
13
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
RTT_逍遥
1
篇文章
6
次点赞
大龄码农
1
篇文章
5
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部