一、动态内存堆的使用
栈(stack):由编译器自动分配释放
堆(heap):一般由程序员分配和释放
int a = 0;//全局初始化区 char *p1;//全局未初始化区 void main() { int b;//栈 char s[] = "abc";//栈 char *p2;//栈 char *p3 = "123456";//123456\0在常量区,p3在栈上 static int c = 0;//全局(静态)初始化区 p1 = (char*)malloc(10);//堆 p2 = (char*)malloc(20);//堆 }
裸机系统动态内存配置
启动文件可设置Stack_Size Heap_Size
char *p; p = (char*)malloc(10); free(p);
RT-Thread 动态内存配置
API:rt_system_heap_init((void*)HEAP_BEGIN,(void*)HEAP_END);
HEAP_BEGIN : ((void *)&Image$$RW_IRAM1$$ZI$$Limit) HEAP = 0X20000000 + STM332_SRAM_SIZE * 1024
HEAP_BEGIN 是一个链接器导出的符号,代表ZI段的结束,也就是程序执行区的RAM结束后的地址,反过来也就是我们执行区的RAM未使用的RAM未使用的区域的起始地址。
Total RW + heap size = MCU total RAM size
char *p; p = (char*)rt_malloc(10); rt_free(p);
动态内存堆使用注意点:
内存复位:每次申请到新的内存块之后,建议对所申请到的内存块进行清零操作
内存泄漏(Memory Leak) 是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
使用动态内存时需要注意:rt_malloc需要和rt_free配套使用
其他动态内存相关API
void *rt_realloc(void *rmem,rt_size_t newsize); //在已分配内存块的基础上重新分配内存块的大小(增大或缩小)在进行重新分配内存块时,原来的内存块数据保持不变(缩小的情况下,后面的数据被自动截断) void *rt_calloc(rt_size_t count,rt_size_t size); //从内存堆中分配连续内存地址的多个内存块
二、线程创建
线程是实现任务的载体,它是RT-Thread中最基本的调度单位,它描述了一个任务执行的运行环境,也描述了这个任务所处的优先等级。
线程由三部分组成:线程代码(入口函数)、线程控制块、线程堆栈
线程代码:无限循环结构 、 顺序执行结构
线程控制块:操作系统用于管理线程的一个数据结构,存放线程信息:优先级、线程名称、线程状态等,也包含线程与线程之间连接用的链表结构,线程等待事件集合等
线程栈:每个线程都具有独立的栈空间,当进行线程切换时,系统会将当前线程的上下文保存在线程栈中,当线程要恢复运行时,再从线程栈中读取上下文信息,恢复线程的运行。
线程上下文是指线程执行时的环境:各个变量和数据包括所有寄存器变量、堆栈信息、内存信息。
线程栈在形式上是一段连续的内存空间,可以通过定义一个数组或者申请一段动态内存来作为线程的栈。
线程创建
//创建静态线程 rt_err_t rt_thread_init(struct rt_thread *thread, //线程控制块 const char *name, //线程名称 void(*entry)(void *parameter), //线程代码 void *parameter, //向线程代码传入的参数 void *stack_start, //线程栈起始地址 rt_uint32_t stack_size, //线程栈大小 rt_uint8_t priority, //线程优先级 rt_uint32_t tick) //线程时间片 //创建动态线程 rt_thread rt_thread_create(const char *name, void(*entry)(void *parameter), void *parameter, rt_uint32_t stack_size, rt_uint8_t priority, rt_uint32_t tick) //启动线程 rt_err_t rt_thread_startup(rt_thread_t thread) //调用此函数后创建的线程会被加入到线程的就绪队列,执行调度
静态线程VS动态线程
相关资源分配形式 静态线程线程控制块、线程栈需要先静态定义。
运行效率: 内部RAM 静态、动态无区别 外部RAM 动态 效率会下降
三、跑马灯线程示例
线程状态、系统心跳时钟
初始状态、就绪状态、运行状态、挂起状态、关闭状态
每一个操作系统存在系统心跳时钟,是操作系统中最小的时钟单位。作为操作系统运行的时间尺度,心跳时钟是由硬件定时器的定时中断产生。
系统的心跳时钟常称为系统滴答或时钟节拍,系统滴答的频率需要我们根据cpu的处理能力来决定,时钟节拍使得内核可以将线程延时若干个整数时钟节拍,以及线程等待事件发生时,提供等待超时的依据。
频率越快,内核函数介入系统运行的几率就越大,内核占用的处理器时间就越长,系统的负荷就变大;频率越小,时间处理精度又不够。
GPIO驱动架构操作IO
线程栈大小分配:先将线程栈大小设置一个固定值(比如2048),在线程运行时通过查看线程栈的使用情况,了解线程栈使用的实际情况,根据情况设置合理的栈大小。
一般将线程栈最大使用量设置在70%。
四、线程的时间片轮询调度
优先级和时间片是线程的两个重要参数,分别描述了线程竞争处理器资源的能力和持有处理器时间长短的能力。
线程优先级
RT-Thread 最大支持256个优先级(数值越小的优先级越高,0为最高优先级,最低优先级预留给空闲线程)
用户可以通过rt_config.h中的RT_THREAD_PRIORITY_MAX宏来修改最大支持的优先级。
针对STM32 默认设置最大支持32个优先级。
具体应用中,线程总数不受限制,能创建的线程总数只和具体硬件平台的内存有关。
线程时间片
时间片只有在相同优先级的就绪态线程中起作用,系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick).
假设有2个优先级相同的就绪态线程A与B,A线程的时间片设置为10,B线程的时间片设置为5,那么系统中不存在比A优先级高的就绪态线程时,系统会在A、B线程间来回切换执行,并且每次对A线程执行10个节拍的时长,对B线程执行5个节拍的时长。
线程调度规则
优先级抢占调度:操作系统总是让具有最高优先级的就绪任务优先运行:即当有任务的优先级高于当前优先级并且处于就绪态后,就一定会发生任务调度。
通过优先级抢占机制,最大限度的1111满足了系统的实时性。
时间片轮询调度:
当操作系统中存在相同优先级的线程时(优先级相同就不会抢占),操作系统会按照设置的时间片大小来轮流调度线程,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick)
通过时间片轮询,保证优先级相同的任务能够轮流占有处理器。
五、空闲线程及两个常用的钩子函数
空闲线程是一个比较特殊的系统线程,它具备最低的优先级。当系统中无其他就绪线程可运行时,调度器将调度到空闲线程。
空闲线程还负责一些系统资源回收以及将一些处于关闭态的线程从线程调度列表中移除的动作
空闲线程在形式上是一个无限循环结构,且永远不被挂起。
在RT-Thread实时操作系统中空闲线程向用户提供了钩子函数,空闲线程钩子函数可以让系统在空闲时执行一些非紧急事务,例如系统运行指示灯闪烁,CPU使用率统计。
//设置钩子函数 rt_err_t rt_thread_idle_sethook(void(*hook)(void)); //删除钩子函数 rt_err_t rt_thread_idle_delhook(void(*hook)(void));
注意事项:
空闲线程是一个线程状态永远为就绪态的线程,所以钩子函数中执行的相关代码必须保证空闲线程在任何时刻都不会被挂起,例如rt_thread_delay()、rt_sem_take()等可能会导致线程挂起的阻塞类函数都不能在钩子函数中使用。
空闲线程可以设置多个钩子函数。
系统调度钩子函数
系统的上下文切换是系统运行过程中最普遍的事件,有时用户可能会想知道在某一个时刻发生了什么样的线程切换,RT-Thread向用户提供了一个系统调度钩子函数,这个钩子函数在系统进行任务切换时运行,通过这个钩子函数,我们可以了解系统任务调度时的一些信息。
rt_scheduler_sethook(void(*hook)(struct rt_thread *from,struct rt_thread *to))
六、临界区保护
临界资源是指一次仅允许一个线程访问的共享资源。它可以是一个具体的硬件设备,也可以是一个变量、一个缓冲区。不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它们进行访问。
每个线程中访问(操作)临界资源的那段代码称为临界区(Critical Section),每次只允许一个线程进入临界区。
RT-Thread提供多种途径进行临界区保护
关闭系统调度保护临界区:禁止调度、关闭中断
互斥特性保护临界区:信号量、互斥量
1.禁止调度
禁止调度,即是把调度器锁住,不让其进行线程切换。这样就能保证当前运行的任务不被换出,直到调度器解锁,所以禁止调度是常用的临界区保护方法。
void thread_entry(void *parameter) { while(1) { /*调度器上锁,上锁后将不再切换到其他线程,仅响应中断*/ rt_enter_critical(); /*以下进入临界区*/ ......s /*调度器解锁*/ rt_exit_critical(); } }
2.关闭中断
因为所有线程的调度都是建立在中断的基础上的,所以关闭中断后,系统将不能再进行调度,线程自身自然不会被其他线程抢占了。
void thread_entry(void *parameter) { rt_base_t level; while(1) { /*关闭中断*/ level = rt_hw_interrupt_disable(); /*以下进入临界区*/ ...... /*关闭中断*/ rt_hw_interrupt_enable(level); } }
七、信号量的使用
在嵌入式系统中运行的代码主要包括线程和ISR,在它们的运行过程中,它们的运行步骤有时需要同步(按照预定的先后次序),它们的运行步骤有时需要互斥(一个时刻只允许一个线程访问资源),它们只是有时也要彼此交换数据。这些需求,有些为应用需求,有些是多线程编程模型带来的需求。
操作系统必须提供相应的机制来完成这些功能,这些机制统称为进(线)程间通信(Internal Process Communication) IPC,RT-Thread中的IPC机制包括信号量、互斥量、事件、邮箱、消息队列。
信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。
每个信号量对象独有一个信号量值和一个线程等待队列,信号量的值对应信号量对象的实例数目(资源数目),假如信号量值N,则表示共有N个信号量实例(资源)可以被使用,当信号量实例数目为0时,该信号量的线程就会挂起在该信号的等待队列上,等待可用的信号量实例(资源)。
信号量控制块
//在RT-thread中,信号量控制块是操作系统用于管理信号量的一个数据结构 struct rt_semaphore { struct rt_ipc_object parent;/*inherit from ipc_object*/ rt_uint16_t value; /*value of semaphore*/ } //定义静态信号量 struct rt_semaphore static_sem //定义动态信号量 struct rt_sem_t dynamic_sem
信号量的操作
//初始化与脱离 rt_err_t rt_sem_init(rt_sem_t sem,const char *name,rt_uint32_t value,rt_uint8_t flag); rt_err_t rt_sem_detach(rt_sem_t sem); //创建与删除 rt_sem_t rt_sem_create(const char *name,rt_uint32_t value,rt_uint8_t flag); //按照 RT_IPC_FLAG_FIFO 先进先出 RT_IPC_FLAG_PRIO 优先级 排列等候 rt_err_t rt_sem_delete(rt_sem_t sem); //获取信号量 rt_err_t rt_sem_take(rt_sem_t sem,rt_int32_t time) // RT_WAITING_FOREVER = -1 rt_err_t rt_sem_trytake(rt_sem_t sem); //释放信号量 rt_err_t rt_sem_release(rt_sem_t sem);
八、生产者消费者问题
生产者消费者问题是一个经典的、多线程同步问题。
有两个线程:一个生产者线程和一个消费者线程。两个线程共享一个初始为空、固定大小为n的缓存区。
生产者的工作是生产一段数据,只有缓冲区未满时,生产者才能把消息放入到缓冲区,否则必须等待,如此反复。
同时,只有缓冲区非空时,消费者才能从中取出数据,一次消费一段数据,否则必须等待,如此反复。
问题的核心:
1.要保证不让生产者在缓存还是满的时候仍然要向内写数据;、
2.不让消费者试图从空的缓存中取出数据。
解决生产者消费者问题实际上要解决线程间互斥关系问题和同步问题。
由于缓冲区是临界资源,它一个时刻只允许一个生产者放入消息,或者一个消费者从中取出消息,所以这是一个互斥问题。
同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,所以这是一个同步问题。
九、互斥量的使用
互斥量(互斥锁)是用于线程间互斥访问的IPC对象,它是一种特殊的二值性信号量。当某个线程访问系统中的共享资源时,通过引入互斥量机制,可以保证其他线程无法取得对此共享资源的访问权。
互斥量只有两种状态:LOCKED和UNLOCKED,分别代表加锁和开锁的两种情况。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权,相反,当这个线程释放它时,将对互斥量进行开锁,失去对它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它。持有该互斥量的线程也能够再次获得这个“锁”(递归持有)而不被挂起。
在RT-Thread 中,互斥量控制块是操作系统用于管理互斥量的一个数据结构。
struct rt_mutex { struct rt_ipc_object parent; /**inherit from ipc_object*/ rt_uint16_t value; /**value of mutex*/ rt_uint8_t original_priority; /**priority of last thread hold the mutex*/ rt_uint8_t hold; /**numbers of thread hold the mutex*/ struct rt_thread *owner; /**current owner of mutex*/ } //定义静态互斥量 struct rt_mutex static_mutex; //定义动态互斥量 rt_mutex_t dynamic_mutex;
互斥量的操作
//初始化与脱离 rt_err_t rt_mutex_init(rt_mutex_t mutex,const char *name,rt_uint8_t flag);//RT_IPC_FLAG_FIFO,RT_IPC_FLAG_PRIO rt_err_t rt_mutex_detach(rt_mutex_t mutex); //创建与删除 rt_mutex_t rt_mutex_create(const char *name,rt_uint8_t flag); rt_err_t rt_mutex_delete(rt_mutex_t mutex); //获取互斥量 rt_err_t rt_mutex_take(rt_mutex_t mutex,rt_int32_t time);//RT_WAITING_FOREVER = -1 //释放互斥量 rt_err_t rt_mutex_release(rt_mutex_t mutex);
信号量VS互斥量
1.信号量可以由任何线程(以及中断)释放,它用于同步的时候就像交通灯,线程只有在获得许可的时候才可以运行,强调的是运行步骤;
互斥量只能由持有它的线程释放,即只有"锁上"它的那个线程才有钥匙打开它。它用于互斥的时候就像一把钥匙,只有获得钥匙的线程才可以运行,强调的是许可和权限。
2.使用信号量可能导致线程优先级反转,而互斥量可通过优先级继承的方法解决优先级反转问题。
十、线程的优先级翻转
使用信号量会导致的另一个潜在问题是线程优先级翻转问题。所谓线程优先级翻转,即当一个高优先级线程试图通过某种互斥IPC对象机制访问共享资源时,如果该IPC对象已被一低优先级的线程所持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞的情况。优先级翻转会造成高优先级线程的实时性得不到保证。
在RT-Thread中,通过互斥量的优先级继承算法,可以有效的解决优先级翻转问题。
优先级继承是指提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程优先级相等**,从而得到更快的执行然后释放共享资源**,而当这个低优先级线程释放该资源时,优先级重新回到初始设定值。
继承优先级的线程避免了系统共享资源被任何中间优先级的线程抢占。
优先级翻转现象提醒编程人员对共享资源进行互斥访问的代码段应尽量短。
十一、事件集的使用
特定事件唤醒线程 任意单个事件唤醒线程 多个事件同时发生才能唤醒线程
事件集工作机制:信号量主要用于一对一的线程同步;当需要一对多、多对一、多对多的同步时,就需要事件集来处理了。
RT-Thread中的事件集用一个32位无符号整型变量来表示,变量中的一个位代表一个事件,线程通过"逻辑与"或"逻辑或"与一个或多个事件建立关联形成一个事件组合。
事件的逻辑或也称为独立型同步,指的是线程与任何事件之一发生同步,只要有一个事件发生,即满足条件;
事件的逻辑与也称为关联性同步,指的是线程与若干事件都发生同步。只有这些事件全部发生,才满足条件。
在RT-Thread中,事件集控制块是操作系统用于管理事件的一个数据结构。
struct rt_event { struct rt_ipc_object parent; /**inherit from ipc_object*/ rt_uint32_t set; /**event set*/ }; typedef struct rt_event *rt_event_t; //定义静态事件集: struct rt_event static_evt; //定义动态事件集: rt_event_t dynamic_evt;
事件集的操作
//初始化与脱离 rt_err_t rt_event_init(rt_event_t event,const char *name,rt_uint8_t flag);//RT_IPC_FLAG_FIFO、RT_IPC_FLAG_PRIO rt_err_t rt_event_detach(rt_event_t event); //创建与删除 rt_event_t rt_event_create(const char *name,rt_uint8_t flag); rt_err_t rt_event_delete(rt_event_t event); //发送事件 rt_err_t rt_event_send(rt_event_t event,rt_uint32_t set); //接收事件 RT_EVENT_FLAG_AND RT_EVENT_FLAG_OR RT_EVENT_FLAG_CLEAR rt_err_t rt_event_recv(rt_event_t event,rt_uint32_t set,rt_uint8_t option,rt_int32_t timeout,rt_uint32_t *recved);
十二、邮箱的使用
RT-Thread 操作系统的邮箱用于线程间通信,特点是开销比较低、效率较高。邮箱中每一封邮件只能容纳固定的4字节内容(针对32位处理系统,指针的大小即为4个字节,所以一封邮件恰好能够容纳一个指针)。
线程或中断服务例程把一封4字节长度的邮件发送到邮箱里,而其他需要的线程可以从邮箱中接收这些邮件并进行处理。
在RT-Thread中2,邮箱控制块是操作系统用于管理邮箱的一个数据结构。
struct rt_mailbox { struct rt_ipc_object parent; /*inherit from ipc_object*/ rt_uint32_t *msg_pool; /*start address of message buffer*/ rt_uint16_t size; /*size of message pool*/ rt_uint16_t entry; /*index of messages in msg_pool*/ rt_uint16_t in_offset; /*input offset of the message buffer*/ rt_uint16_t out_offset; /*output offset of the message buffer*/ rt_list_t suspend_sender_thread /*sender thread suspended on this mailbox*/ }; typedef struct rt_mailbox *rt_mailbox_t; //定义静态邮箱 struct rt_mailbox static_mb; //定义动态邮箱 rt_mailbox_t dynamic_mb;
邮箱的操作
//初始化与脱离 rt_err_t rt_mb_init(rt_mailbox_t mb,const char *name,void *msgpool,rt_size_t size,rt_uint8_t flag); rt_err_t rt_mb_detach(rt_mailbox_t mb); //创建与删除 rt_mailbox_t rt_mb_create(const char *name,rt_size_t size,rt_uint8_t flag); rt_err_t rt_mb_delete(rt_mailbox_t mb); //发送邮件 rt_err_t rt_mb_send(rt_mailbox_t mb,rt_uint32_t value); rt_err_t rt_mb_send_wait(rt_mailbox_t mb,rt_uint32_t value,rt_int32_t timeout); //接收邮件 rt_err_t rt_mb_recv(rt_mailbox_t mb,rt_uint32_t *value,rt_int32_t timeout);
十三、消息队列的使用
消息队列是RT-Thread中另一种常用的线程间通信方式,消息队列是对邮箱的扩展。
消息队列能够接收来自线程或中断服务例程中发出的不固定长度的消息,并把消息缓存在自己的内存空间中,而其他线程能够从消息队列中读取相应的消息并进行对应的处理。
在RT-Thread中,消息队列控制块是操作系统用于管理消息队列的一个数据结构。
struct rt_messagequeue { struct rt_ipc_object parent; /**inherit from ipc_object*/ void *msg_pool; /*start address of message queue*/ rt_uint16_t msg_size;/*message size of each message*/ //4字节的整数倍 rt_uint16_t max_msgs;/*max number of messages*/// 消息内存池大小/(消息大小+指针大小) rt_uint16_t entry; /*index of messages in the queue*/ void *msg_queue_head;/*list head*/ void *msg_queue_tail;/*list tail*/ void *msg_queue_free;/*pointer indicated the free node of queue*/ }; typedef struct rt_messagequeue *rt_mq_t; //定义静态消息队列: struct rt_messagequeue static_mq; //定义动态消息队列: struct rt_mq_t dynamic_mq;
消息队列的操作
//初始化与脱离 RT_IPC_FLAG_FIFO RT_IPC_FLAG_PRIO rt_err_t rt_mq_init(rt_mq_t mq,const char *name,void *msgpool,rt_size_t msg_size,rt_size_t pool_size.rt_uint8_t flag); //创建与删除 rt_mq_t rt_mq_create(const char *name,rt_size_t msg_size,rt_size_t max_msgs,rt_uint8_t flag); rt_err_t rt_mq_delete(rt_mq_t mq); //发送消息 rt_err_t rt_mq_send(rt_mq_t mq,void *buffer,rt_size_t size); rt_err_t rt_mq_urgent(rt_mq_t mq,void *buffer,rt_size_t size); //发送紧急消息 //接收消息 rt_err_t rt_mq_recv(rt_mq_t mq,void *buffer,rt_size_t size,rt_int32_t timeout);
十四、软件定时器的使用
软件定时器是由操作系统提供的一类系统接口,他构建在硬件定时器基础之上(系统滴答定时器)。软件定时器使系统能够提供不受数目限制的定时器服务。
RT-Thread操作系统提供的软件定时器,以系统节拍(OS Tick)的时间长度为定时单位,提供了基于系统节拍整数倍的定时能力,即定时数值是OS Tick的整数倍。
当软件定时器所设定的定时时间到了后,会调用用户设置的定时器timeout回调函数,用户需要定时运行的程序会在回调函数中得到处理。
定时器模式:
HARDTIMER模式:
HARDTIMER模式的定时器超时函数在中断上下文环境中执行,此模式在定时器初始化时指定。在中断上下文环境中执行时,对于超时函数的要求与中断服务例程的要求相同;执行时间应该尽量短,执行时不应导致当前上下文挂起。HARD_TIMER模式是RT-Thread软件定时器的默认方式。
SOFTTIMER模式:
SOFTTIMER模式的定时器超时函数在系统的timer线程的线程上下文中执行。通过宏定义RT_USING_TIMER_SOFT来决定是否启用该模式。当启用SOFTTIMER模式后,我们可以在定时器初始化时指定定时器工作在SOFTTIMER模式。
在RT-Thread中,软件定时器控制块是操作系统用于管理软件定时器的一个数据结构。
struct rt_timer { struct rt_object parent;/*inherit from rt_object*/ rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL]; void (*timeout_func)(void *parameter); //timeout function void *parameter; /*timeout function's parameter*/ rt_tick_t init_tick; //timer timeout tick*/ rt_tick_t timeout_tick;//timeout tick*/ }; //定义静态软件定时器 struct rt_timer static_timer; //定义动态软件定时器 rt_timer_t dynamic_timer;
软件定时器的操作
//初始化与脱离 RT_TIMER_FLAG_ONE_SHOT RT_TIMER_FLAG_PERIODIC RT_TIMER_FLAG_HARD_TIMER RT_TIMER_FLAG_SOFT_TIMER void rt_timer_init(rt_timer_t timer,const char *name,void(*timeout)(void *parameter),void *parameter,rt_tick_t time,rt_uint8_t flag); rt_err_t rt_timer_detach(rt_timer_t timer); //创建与删除 rt_timer_t rt_timer_create(const char *name,void (*timeout)(void *parameter),void *parameter,rt_tick_t time,rt_uint8_t flag); rt_err_t rt_timer_delete(rt_timer_t timer); //启动定时器 rt_err_t rt_timer_start(rt_timer_t timer); //停止定时器 rt_err_t rt_timer_stop(rt_timer_t timer);
十五、内存池的使用
动态内存堆可以分配任意大小的内存块,非常灵活和方便。但其存在明显的缺点:1.分配效率不高,在每次分配时,都要进行空闲内存块查找;2.容易产生内存碎片。
为了提高内存分配的效率,并且避免内存碎片,RT-Thread提供了另外一种内存管理方法:内存池(Memory Pool)
内存池是一种内存分配方式,用于分配大量大小相同的小内存块。使用内存池可以极大地加快内存分配与释放的速度,且能尽量避免内存碎片化。
RT-Thread的内存池支持线程挂起功能,当内存池中无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的线程唤醒。基于这个特点内存池非常适合需要通过内存资源进行同步的场景。
内存池在创建时先从系统中获取一大块内存(静态或动态),然后分成相同大小的多个小内存块,这些小内存块通过链表连接起来(此链表也称为空闲链表)。线程每次申请分配内存块的时候,系统从空闲链表中取出链头上第一个内存块,提供给申请者。
在RT-Thread中,内存池控制块是操作系统用于管理内存池的一个数据结构。
struct rt_mempool { struct rt_object parent; /*inherit from rt_object*/ void *start_address; /*memory pool start*/ rt_size_t size; /*size of memory pool*/ rt_size_t block_size; /*size of memory blocks*/ rt_uint8_t *block_list; /*memory blocks list*/ rt_size_t block_total_count; /*numbers of memory block*/ rt_size_t block_free_count; /*numbers of free memory block*/ rt_list_t suspend_thread; /*threads pended on this resource*/ rt_size_t suspend_thread_count;/*num of thread pended on this resource*/ }; typedef struct rt_mempool *rt_mp_t; //定义静态内存池 struct rt_mempool static_mp; //定义动态内存池 rt_mp_tdynamic_mp;
内存池的操作
//初始化与脱离 rt_err_t rt_mp_init(strcut rt_mwmpool *mp,const char *name,void *start,rt_size_t size,rt_size_t block_size); rt_err_t rt_mp_detach(struct rt_mempool *mp); //创建与删除 rt_mp_t rt_mp_create(const char *name,rt_size_t block_count,rt_size_t block_size); rt_err_t rt_mp_delete(rt_mp_t mp); //申请内存块 void *rt_mp_alloc(rt_mp_t mp,rt_int32_t time); //释放内存块 void rt_mp_free(void *block);