参考了lwip和btstack等开源协议栈的内存池的设计,我来分析一下内存池的设计:
下面是参考的(博主:老衲五木)对lwip动态内存池的理解,这对我理解btstack内存池的分配的理解帮助很大
目录
参考设计
动态内存池(POOL)分配策略可以说是一个比较笨的分配策略了,但其分配策略实现简单,内存的分配、释放效率高,可以有效防止内存碎片的产生。不过,他的缺点是会浪费部分内存。
为什么叫 POOL?这点很有趣,POOL 有很多种,而这点依赖于用户配置 LWIP 的方式。例如用户在头文件 opt.h 文件中定义 LWIP_UDP 为 1,则在编译的时候与 UDP 类型内存池就会被建立;定义 LWIP_TCP 为 1,则在编译的时候与 TCP 类型内存池就会被建立。另外,还有很多其他类型的内存池,如专门存放网络包数据信息的 PBUF_POOL、还有上面讲解动态内存堆分配策略时提到的 CUSTOM_POOLS 等等等等。某种类型的 POOL 其单个大小是固定的,而分配该类 POOL 的个数是可以用户配置的,用户应该根据协议栈实际使用状况进行配置。把协议栈中所有的 POOL 挨个放到一起,并把它们放在一片连续的内存区域,这呈现给用户的就是一个大的缓冲池。所以,所谓的缓冲池的内部组织应该是这样的:开始处放了 A 类型的 POOL 池 a 个,紧接着放上 B 类型的 POOL 池 b 个,再接着放上 C 类型的POOL 池 c 个….直至最后 N 类型的 POOL 池 n 个。这一点很像 UC/OSII 中进程控制块和事件控制块,先开辟一堆各种类型的放那,你要用直接来取就是了。注意,这里的分配必须是以单个缓冲池为基本单位的,在这样的情况下,可能导致内存浪费的情况。这是很明显的啊,不解释。
#具体设计
下面我到具体情景来分析
##蓝牙协议栈分层
低功耗蓝牙协议栈由许多层组成,如下图所示:
具体每一层是干嘛的,大家通过百度、谷歌都可以了解。
##内存分配
现在需要当协议栈工作的时候,需要进行内存分配,也就是需要为各种连接参数和服务等做一个内存管理,例如HCI连接参数,l2cap服务,l2cap信道等。
##进入正题
废话讲了这么多现在才进入正题:
内存创建函数
内存的分配是由一个一个链表的节点串起来的
定义的数据结构如下:
typedef struct node {
struct node * next;
} node_t;
然后是内存创建函数:memory_pool_create
void memory_pool_create(memory_pool_t *pool, void * storage, int count, int block_size){
node_t *free_blocks = (node_t*) pool;
char *mem_ptr = (char *) storage;
int i;
//为所有blocks创建单链表
free_blocks->next = NULL;
for (i = 0 ; i < count ; i++){
memory_pool_free(pool, mem_ptr);
mem_ptr += block_size;
}
}
其中pool是一个指向内存池最后一个节点的指针,storage是结构体数组的首地址,count是结构数组的成员数,block_size是结构体分配的内存大小(按照字节计数)。当调用memory_pool_create()时,过程如下图所示:
在内存创建函数中有一个memory_pool_free()函数,这个函数的作用是将结构体数组的首地址mem_ptr(=storage)链接到内存池中。具体函数如下:
void memory_pool_free(memory_pool_t *pool, void * block){
node_t *free_blocks = (node_t*)pool;
node_t *node = (node_t*)block;
// 如果节点已经在链表中就退出
node_t * it;
for (it = free_blocks->next; it; it = it->next){
if (it == node) {
return;
}
}
// 将结构体数组的首地址加入到链表中,如果结构体数组元素有很多个,就一个一个的加进去
node->next = free_blocks->next;
free_blocks->next = node;
}
###获取内存函数
那当需要内存的时候怎去获取呢,这就需要一个内存获取函数,如下:
void * memory_pool_get(memory_pool_t *pool){
node_t *free_blocks = (node_t*)pool;
if (!free_blocks->next) return NULL;
// 移除头节点
node_t *node = free_blocks->next;
free_blocks->next = node->next;
//返回头结点
return (void*)node;
}
下面就到真正使用的时候了,如果有需要可以设个断点,然后自己看看内存池是怎么生长和取出来使用的
###具体例子
#include <stdio.h>
#include <stddef.h>
typedef void* memory_pool_t;
typedef struct node {
struct node * next;
} node_t;
void memory_pool_free(memory_pool_t *pool, void * block){
node_t *free_blocks = (node_t*)pool;
node_t *node = (node_t*)block;
//如果节点已经在链表中就退出
node_t * it;
for (it = free_blocks->next; it; it = it->next){
if (it == node) {
return;
}
}
// 将结构体数组的首地址加入到链表中,如果结构体数组元素有很多个,就一个一个的加进去
node->next = free_blocks->next;
free_blocks->next = node;
}
void memory_pool_create(memory_pool_t *pool, void * storage, int count, int block_size){
node_t *free_blocks = (node_t*)pool;
char *mem_ptr = (char *)storage;
int i;
// 为所有blocks创建单链表
free_blocks->next = NULL;
for (i = 0; i < count; i++){
memory_pool_free(pool, mem_ptr);
mem_ptr += block_size;
}
}
void * memory_pool_get(memory_pool_t *pool){
node_t *free_blocks = (node_t*)pool;
if (!free_blocks->next) return NULL;
// 移除头节点
node_t *node = free_blocks->next;
free_blocks->next = node->next;
return (void*)node;
}
typedef struct
{
int a;
char b;
char c;
}hci_connection_t;
static hci_connection_t hci_connection_storage[2];
static memory_pool_t hci_connection_pool;
typedef struct
{
int a;
char b;
int c;
}l2cap_connection_t;
static l2cap_connection_t l2cap_connection_storage[3];
static memory_pool_t l2cap_connection_pool;
hci_connection_t * btstack_memory_hci_connection_get(void){
return (hci_connection_t *)memory_pool_get(&hci_connection_pool);
}
void btstack_memory_hci_connection_free(hci_connection_t *hci_connection){
memory_pool_free(&hci_connection_pool, hci_connection);
}
l2cap_connection_t * btstack_memory_l2cap_connection_get(void){
return (l2cap_connection_t *)memory_pool_get(&l2cap_connection_pool);
}
void btstack_memory_l2cap_connection_free(hci_connection_t *l2cap_connection){
memory_pool_free(&l2cap_connection_pool,l2cap_connection);
}
int main()
{
memory_pool_create(&hci_connection_pool, hci_connection_storage, 2, sizeof(hci_connection_t));
memory_pool_create(&l2cap_connection_pool, l2cap_connection_storage, 3, sizeof(l2cap_connection_t));
hci_connection_t *con = btstack_memory_hci_connection_get();
l2cap_connection_t *lcon = btstack_memory_l2cap_connection_get();
return 0;
}
这时候,内存池是这样分布的: