文章目录
内存管理
小内存管理算法
步骤:
- 在空闲链表头寻找空闲内存块
- 对满足需求的空闲内存块做分割
- 满足需求指内存块大于需求12字节
- 其中的12个字节用于存储链表的节点信息
- 将分割后剩余的区域放回空闲链表
slab管理算法
内存分配
假设分配一个 32 字节的内存,slab 内存分配器会先按照 32 字节的值,从 zone array 链表表头数组中找到相应的 zone 链表。如果这个链表是空的,则向页分配器分配一个新的 zone,然后从 zone 中返回第一个空闲内存块。如果链表非空,则这个 zone 链表中的第一个 zone 节点必然有空闲块存在,那么就取相应的空闲块。如果分配完成后,zone 中所有空闲内存块都使用完毕,那么分配器需要把这个 zone 节点从链表中删除。
内存释放
空闲的区(zone)会被释放
memheap管理算法(RT-thread主要)
内存堆管理
示例程序
#include <rtthread.h>
#define THREAD_PRIORITY 25
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5
/* 线程入口 */
void thread1_entry(void *parameter)
{
int i;
char *ptr = RT_NULL; /* 内存块的指针 */
for (i = 0; ; i++)
{
/* 每次分配 (1 << i) 大小字节数的内存空间 */
// 保证了每次申请的内存空间呈指数增加
ptr = rt_malloc(1 << i);
/* 如果分配成功 */
if (ptr != RT_NULL)
{
rt_kprintf("get memory :%d byte\n", (1 << i));
/* 释放内存块 */
rt_free(ptr);
rt_kprintf("free memory :%d byte\n", (1 << i));
ptr = RT_NULL;
}
else
{
rt_kprintf("try to get %d byte memory failed!\n", (1 << i));
return;
}
}
}
int dynmem_sample(void)
{
rt_thread_t tid = RT_NULL;
/* 创建线程 1 */
tid = rt_thread_create("thread1",
thread1_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY,
THREAD_TIMESLICE);
if (tid != RT_NULL)
rt_thread_startup(tid);
return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(dynmem_sample, dynmem sample);
对内存块的相关操作
分配内存块
从内存堆上分配用户指定大小的内存块
void *rt_malloc(rt_size_t nbytes);
注意:参数为二进制数据
释放内存块
必要性:应用程序使用完从内存分配器中申请的内存后,必须及时释放,否则会造成内存泄漏。
void rt_free (void *ptr);
补充:官方文档
上面官方文档中定义了一些常见的内存管理错误
内存池管理
内存堆管理的缺点:每次分配内存都需要在空闲内存块中寻找,分配完内存容易出现内存碎片(许多小的内存被浪费)。
内存池控制块
struct rt_mempool
{
struct rt_object parent;
void *start_address; /* 内存池数据区域开始地址 */
rt_size_t size; /* 内存池数据区域大小 */
rt_size_t block_size; /* 内存块大小 */
rt_uint8_t *block_list; /* 内存块列表 */
/* 内存池数据区域中能够容纳的最大内存块数 */
rt_size_t block_total_count;
/* 内存池中空闲的内存块数 */
rt_size_t block_free_count;
/* 因为内存块不可用而挂起的线程列表 */
rt_list_t suspend_thread;
/* 因为内存块不可用而挂起的线程数 */
rt_size_t suspend_thread_count;
};
typedef struct rt_mempool* rt_mp_t;
内存块分配机制
内存池创建的时候先向系统申请一个大的空间,然后将内存块分成相同大小的小内存块。内存控制块的参数包括内存池名,内存缓冲区,内存块大小,块数以及一个等待线程队列
内存池应用实例
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-04-11 霄寒 the first version
*/
#include <rtthread.h>
static rt_uint8_t *ptr[50];
static rt_uint8_t mempool[4096];
static struct rt_mempool mp;
#define THREAD_PRIORITY 25
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5
/* 指向线程控制块的指针 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
/* 线程 1 入口 */
static void thread1_mp_alloc(void *parameter)
{
int i;
for (i = 0 ; i < 50 ; i++)
{
if (ptr[i] == RT_NULL)
{
/* 试图申请内存块 50 次,当申请不到内存块时,
线程 1 挂起,转至线程 2 运行 */
ptr[i] = rt_mp_alloc(&mp, RT_WAITING_FOREVER);
if (ptr[i] != RT_NULL)
rt_kprintf("allocate No.%d\n", i);
}
}
}
/* 线程 2 入口,线程 2 的优先级比线程 1 低,应该线程 1 先获得执行。*/
static void thread2_mp_release(void *parameter)
{
int i;
rt_kprintf("thread2 try to release block\n");
for (i = 0; i < 50 ; i++)
{
/* 释放所有分配成功的内存块 */
if (ptr[i] != RT_NULL)
{
rt_kprintf("release block %d\n", i);
rt_mp_free(ptr[i]);
ptr[i] = RT_NULL;
}
}
}
int mempool_sample(void)
{
int i;
for (i = 0; i < 50; i ++) ptr[i] = RT_NULL;
/* 初始化内存池对象 */
rt_mp_init(&mp, "mp1", &mempool[0], sizeof(mempool), 80); // 此处申请的内存块数量为 (mempool == 4096 ) / (80 + 4) == 48 块
// 所以程序申请到 47 号(i从0起)时申请不到,开始释放
/* 创建线程 1:申请内存池 */
tid1 = rt_thread_create("thread1", thread1_mp_alloc, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
/* 创建线程 2:释放内存池 */
tid2 = rt_thread_create("thread2", thread2_mp_release, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY + 1, THREAD_TIMESLICE);
if (tid2 != RT_NULL)
rt_thread_startup(tid2);
return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mempool_sample, mempool sample);
运行过程:
本例程在初始化内存池对象时,初始化了 4096 /(80+4) = 48 个内存块。
①线程 1 申请了 48 个内存块之后,此时内存块已经被用完,需要其他地方释放才能再次申请;但此时,线程 1 以一直等待的方式又申请了 1 个,由于无法分配,所以线程 1 挂起;
②线程 2 开始执行释放内存的操作;当线程 2 释放一个内存块的时候,就有一个内存块空闲出来,唤醒线程 1 申请内存,申请成功后再申请,线程 1 又挂起,再循环一次②;
③线程 2 继续释放剩余的内存块,释放完毕。
中断管理
中断处理过程
RT-Thread 中断管理中,将中断处理程序分为中断前导程序、用户中断服务程序、中断后续程序三部分,如下图
注:即为保存现场、中断处理函数、回复现场
中断前导程序
void rt_interrupt_enter(void)
{
rt_base_t level;
level = rt_hw_interrupt_disable();
rt_interrupt_nest ++;
rt_hw_interrupt_enable(level);
}
用户中断服务程序
一般情况:运行中断处理函数、中断后续程序 – 》退出
特殊情况:需要进行线程切换
中断后续程序
void rt_interrupt_leave(void)
{
rt_base_t level;
level = rt_hw_interrupt_disable();
rt_interrupt_nest --;
rt_hw_interrupt_enable(level);
}
进行线程切换的contex-M架构
中断嵌套
译为:中断的中断,内部中断要优先处理
中断栈
中断的栈空间,一般是位于线程的栈空间中,但是也可以独立开辟。嵌套中断的栈空间如果没有独立开辟,就在被嵌套的栈空间里开辟。
中断底半处理
在上半部分中,取得硬件状态和数据后,打开被屏蔽的中断,给相关线程发送一条通知,然后结束中断服务程序。
而接下来,相关的线程在接收到通知后,接着对状态或数据进行进一步的处理
由于以上两个部分的处理是异步过程,所以需要用户考虑两个过程系统的处理时间,从而选择是否使用底半处理的方式。
中断的一些操作
中断的挂载
rt_isr_handler_t rt_hw_interrupt_install(int vector,
rt_isr_handler_t handler,
void *param,
char *name);
参数 | 描述 |
---|---|
vector | vector 是挂载的中断号 |
handler | 新挂载的中断服务程序 |
param | param 会作为参数传递给中断服务程序 |
name | 中断的名称 |
返回 | —— |
return | 挂载这个中断服务程序之前挂载的中断服务程序的句柄 |
屏蔽中断源
void rt_hw_interrupt_mask(int vector);
参数 | 描述 |
---|---|
vector | 要屏蔽的中断号 |
打开屏蔽中断源
void rt_hw_interrupt_umask(int vector);
参数 | 描述 |
---|---|
vector | 要打开屏蔽的中断号 |
全局中断开关
rt_base_t rt_hw_interrupt_disable(void);// 关
void rt_hw_interrupt_enable(rt_base_t level); // 开
中断通知
void rt_interrupt_enter(void); // 通知内核已经进入了中断状态
void rt_interrupt_leave(void); // 通知内核已经离开了中断状态