参考资料
RT-Thread文档中心
猪哥嵌入式 RTOS系列
ARM Cortex-M m3/m4权威指南
野火FreeRtos内核实现与应用开发指南
前言
近期在看keil中的map文件时发现一个问题,问题如下:
首先三个一样的工程,都在GD32F303CG上运行
启动文件全部一样关于堆栈的分配情况如下:
第一个工程
完全的裸机开发 全篇没有使用过malloc动态分配内存最后的map文件关于堆栈的内容如下:
可以看到只有栈空间,并没有堆空间
第二个工程
其余设置全部保持一致,仅仅加入一行代码
uint32_t *cq = (uint32_t *)malloc(sizeof(uint32_t));
再次查看map如下:
第三个工程
移植了RT-Thread操作系统使用rt_malloc进行分配内存,得到的map如下:
通过对比发现哪怕是使用了操作系统只要没有使用malloc就不会开辟堆空间,而操作系统的堆空间是一个全局变量。然后就有点好奇,不是说操作系统会引入动态内存管理吗,为什么同样没有堆开辟。
探究结果
对于嵌入式实时操作系统来说创建一个线程需要一个线程控制块,例如RT-Thread的线程控制块
/* 线程控制块 */
struct rt_thread
{
/* rt 对象 */
char name[RT_NAME_MAX]; /* 线程名称 */
rt_uint8_t type; /* 对象类型 */
rt_uint8_t flags; /* 标志位 */
rt_list_t list; /* 对象列表 */
rt_list_t tlist; /* 线程列表 */
/* 栈指针与入口指针 */
void *sp; /* 栈指针 */
void *entry; /* 入口函数指针 */
void *parameter; /* 参数 */
void *stack_addr; /* 栈地址指针 */
rt_uint32_t stack_size; /* 栈大小 */
/* 错误代码 */
rt_err_t error; /* 线程错误代码 */
rt_uint8_t stat; /* 线程状态 */
/* 优先级 */
rt_uint8_t current_priority; /* 当前优先级 */
rt_uint8_t init_priority; /* 初始优先级 */
rt_uint32_t number_mask;
......
rt_ubase_t init_tick; /* 线程初始化计数值 */
rt_ubase_t remaining_tick; /* 线程剩余计数值 */
struct rt_timer thread_timer; /* 内置线程定时器 */
void (*cleanup)(struct rt_thread *tid); /* 线程退出清除函数 */
rt_uint32_t user_data; /* 用户数据 */
};
可以看到在控制块中定义了栈指针相关参数,在动态创建线程和初始化线程的时候,会使用到内部的线程初始化函数_rt_thread_init(),_rt_thread_init() 函数会调用栈初始化函数 rt_hw_stack_init(),在栈初始化函数里会手动构造一个上下文内容,这个上下文内容将被作为每个线程第一次执行的初始值。
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;
/* 把所有寄存器的默认值设置为 0xdeadbeef */
for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
{
((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
}
/* 根据 ARM APCS 调用标准,将第一个参数保存在 r0 寄存器 */
stack_frame->exception_stack_frame.r0 = (unsigned long)parameter;
/* 将剩下的参数寄存器都设置为 0 */
stack_frame->exception_stack_frame.r1 = 0; /* r1 寄存器 */
stack_frame->exception_stack_frame.r2 = 0; /* r2 寄存器 */
stack_frame->exception_stack_frame.r3 = 0; /* r3 寄存器 */
/* 将 IP(Intra-Procedure-call scratch register.) 设置为 0 */
stack_frame->exception_stack_frame.r12 = 0; /* r12 寄存器 */
/* 将线程退出函数的地址保存在 lr 寄存器 */
stack_frame->exception_stack_frame.lr = (unsigned long)texit;
/* 将线程入口函数的地址保存在 pc 寄存器 */
stack_frame->exception_stack_frame.pc = (unsigned long)tentry;
/* 设置 psr 的值为 0x01000000L,表示默认切换过去是 Thumb 模式 */
stack_frame->exception_stack_frame.psr = 0x01000000L;
/* 返回当前线程的栈地址 */
return stk;
}
也就是说实时操作系统在创建任务的时候会给这个任务分配一个单独的栈空间,而不像裸机程序一样自始至终都是一个。这点参考ARM Cortex-M m3/m4权威指南可以得到验证
对于具有嵌入式OS或RTOS的系统,异常处理(包括部分OS内核)使用MSP,而应用任务则使用PSP。每个应用任务都有自己的栈空间,如图10.1所示。OS中的上下文切换代码在每次上下文切换时都会更新PSP。
具体的实时操作系统任务跳转,上下文切换相关内容强烈建议查看上面的猪哥嵌入式 RTOS系列,讲的十分详细。
2023-8-15记录