stm32数据对齐、PRESERVE8、freertos的heap堆栈、

文章详细解释了内存对齐的原因,包括提高读取效率,避免跨边界存储。内容涵盖全局变量、局部变量的对齐规则,结构体的内存布局,以及在STM32等硬件中的应用,强调了8字节对齐的重要性。同时,提到了编译器的pack指令和栈指针的管理,以及在FreeRTOS系统中的堆内存管理策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么需要数据对齐?

避免数据在内存中跨边界存储,减少读取数据次数,提高效率,本质上是以空间换时间的做法

下图中属于同一水平位置的为同一边界
变量在同一边界里的一次存储周期就可以读取
一旦跨了上下两个边界来存储就需要至少两个存储周期来读取

根据存储器结构,如下图,参考链接:多体并行:高位/低位交叉编址
在这里插入图片描述
CPU数据线有32位一次最多可以从内存读取32数据,这里的一次指一次存储周期
LDR r1 , [pc,#4], 是从pc+#4地址处开始连续读取4个字节的数据到r1寄存器
LDRH r1 , [pc,#4], 是从pc+#4地址处开始连续读取2个字节的数据到r1寄存器
LDRB r1 , [pc,#4] 直接取pc+#4当前那个地址的数据
上述指令都是在一个存储周期里完成的

一般地址线只能确定一个字节所在的地址,而上述指令地址都一样,却不止读到仅仅一个字节的数据,还能读到2个字节或者4个字节的数据,可以看到上述指令除了操作码不一样其它都一样,按照上图的存储结构,指令的地址一样那么体内地址肯定是一样的,体内地址一样就横向选中了同一水平位置的存储,操作码的不同说明控制线输出的信号应该不同,就可以控制一个类似体号的控制器,选中连续的好几个字节的存储
地址线加上控制线就可以实现上述的操作

LDM r1 ,{r0-r1} ; 是将r1指向的内存地址开始连续8个字节的数据存放到r0和r1寄存器中
该指令是在2个存储周期里完成的

详解边界对齐
为什么要内存对齐?字节对齐和边界对齐介绍

对于数据在内存中的位置,需要确定两个因素:大小、起始位置
根据存储器结构,就是通过起始位置和数据大小获取内存中的数据

一、全局变量静态变量,地址从小增大

1、原子类型数据

在这里插入图片描述
按照开始的存储结构的图,short型变量起始位置放在地址1的位置应该也放得下,也能一次性读取,为什么必须得是0,2,4,6.。。。
那是因为放在不好判断地址1和地址3的不同,放在地址3就会跨界,而为了区分地址1和3做出来的电路更为复杂得不偿失,
8字节的也是同理

2、组合类型数据

详细参考链接:
详解边界对齐

// structure C 
typedef struct structc_tag 
{
   
    
   char        c; 
   double      d; 
   int         s; 
} structc_t; 

在这里插入图片描述
这个结构的大小应该是 sizeof(char) + 7 + sizeof(double) + sizeof(int) = 1 +7 + 8 + 4 = 20

然而,正确答案是24

所以,所有结构的起始存储位置必须是结构中对边界要求最严格的数据类型所要求的位置。

在这里插入图片描述

	struct {
   
   
		int a;
		short b;
		int c;
		char d;
	}A;

	struct {
   
   
		double a;
		short b;
		int c;
		char d;
	}B;

在这里插入图片描述
上述总结还有另一个表达,组合数据结构的大小是组合结构中最大原子数据大小的整数倍:上述结构B就是20不是8的整数倍,24是8的整数倍

总结:

第一,编译器按照成员列表的顺序给每个成员分配内存.   
第二,当成员需要满足正确的边界对齐时,成员之间用额外字节填充.
第三,结构体的首地址必须满足结构体中边界要求最为严格的数据类型所要求的地址.  (也即首地址为最宽基本类型的整数倍) 
第四,结构体的大小为其最宽基本类型的整数倍.

3、#pragma pack (2)的作用

详细参考链接:为什么要内存对齐?字节对齐和边界对齐介绍。
文章中说的编译器默认4字节对齐是错误的,默认是8字节对齐其它分析都对

这里的指定几字节对齐,意思是数据本身的对齐数大于指定的话就按指定的对齐数来,也就是数据起始地址为指定对齐数的倍数
下面的struct中int a的自身对齐数为4大于指定对齐数2,所以按照2来,它存放的起始地址就为0,2,4,6.。。。这些

在这里插入图片描述

4、结构体中嵌套结构体

#pragma  pack(8)
struct example1 {
   
   
    short a;
    long b;
};

struct example2 {
   
   
    char c;
    struct example1 e;
    short s;
};

struct example2数据结构的大小是16个字节,这个值是这样计算出来的:
1(char c) + 3(padding) + 8(struct example1 e) + 2(short s) + 2(padding) = 16。

该类型也是按照组合类型数据的规则来计算大小

数据结构内存边界对齐的三条原则

二、局部变量,栈中的变量

不同的数据(或数据结构)按顺序从地址大处向地址低处,同一数据内按顺序地址从小到大
sp要8字节对齐,sp的地址只会是8的倍数,(现象就是每次压栈出栈都是8的倍数个,参考下面的)
栈中的数据与全局数据相同的是大小的计算方法一样,不同的是栈中的数据起始地址不是自身对齐值的倍数而是4的倍数或8的倍数(如果自身对齐值小于4就是4的倍数大于4就是8的倍数)

1、结构体变量加lr寄存器大小小于8字节

typedef struct
{
   
   
        char a;
        char b;
} Tchar;

void temp()
{
   
   
	
    Tchar a2 = {
   
   '!','s'};	
	a2.a = a2.a + a2.b;
	
	printf("the size of struct Tchar  is %d\r\n ",sizeof(a2));
	printf("the address of struct Tchar  is %p\r\n ",&a2);
	printf("the address of struct Tchar a is %p\r\n ",&a2.a);
	printf("the address of struct Tchar b is %p\r\n ",
### FreeRTOS 移植到 STM32F103C8T6 的开发环境配置 为了成功将 FreeRTOS 移植到 STM32F103C8T6 微控制器上,需要完成以下几个方面的设置: #### 1. 工程目录结构调整 在项目工程中,需包含 `FreeRTOS` 源码的相关路径。具体来说,在工程文件夹下应加入以下两个子目录: - `FreeRTOS\Source\include`: 提供核心头文件支持。 - `FreeRTOS\Source\portable\RVDS\ARM_CM3`: 针对 ARM Cortex-M3 架构的端口层实现。 这些路径可以通过 IDE 或编译器中的选项进行指定[^1]。 #### 2. 文件修改与配置 ##### (1) 修改 `FreeRTOSConfig.h` 此文件用于定义 FreeRTOS 的运行参数以及中断优先级等相关配置项。以下是几个重要的宏定义及其作用说明: - 定义调度器异常处理函数映射关系: ```c #define xPortPendSVHandler PendSV_Handler #define vPortSVCHandler SVC_Handler ``` 这些宏指定了系统调用服务(SVC)和挂起保存(PendSV)中断的具体处理器名称[^4]。 - 启用特定功能的支持: 添加如下配置启用任务状态查询接口: ```c #define INCLUDE_xTaskGetSchedulerState 1 ``` 通过上述更改可以确保 RTOS 能够正确响应来自硬件层面的时间片切换请求并提供必要的调试手段。 ##### (2) 更新 `stm32f10x_it.c` 该源文件包含了所有外设中断的服务程序模板代码。对于多线程应用而言,则至少要补充下面两部分内容来满足实时操作系统的需求: - **SysTick Timer Handler**: 实现周期性的上下文切换触发机制; - **PendSV Interrupt Service Routine**: 执行实际的任务切换逻辑。 示例片段可能看起来像这样: ```c void SysTick_Handler(void){ /* Increment the tick count */ if(xTaskIncrementTick() != pdFALSE){ taskYIELD(); // Force a context switch. } } __asm void PendSV_Handler(){ PRESERVE8 mrs r0, psp ; Get current process stack pointer value. ldr r3, =pxCurrentTCB; Load address of pxCurrentTCB variable into R3 register. str r0, [r3]; Save new top-of-stack to TCB structure associated with currently running thread. ...省略部分汇编指令... bx lr ; Return from interrupt using link register content as target PC. } ``` 以上实现了基于堆栈的操作以管理不同进程间的转换过程[^2]。 #### 3. 使用 HAL 库简化流程 如果采用 STMicroelectronics 推荐的标准外设驱动方法——即 Hardware Abstraction Layer(HAL),那么整个移植工作的复杂度会有所降低。只需按照官方指导操作即可快速集成好基础框架。例如利用 MDK-ARM Keil uVision 创建新项目时记得勾选对应中间件组件,并手动添加名为 Middlewares/FreeRTOS_CODE 和 Middlewares/FreeRTOS_PORT 的分组以便更好地组织资源文件[^3]。 最后提醒一点,务必仔细阅读所依赖版本文档资料里关于兼容性和已知问题的部分以防踩坑! ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值