本期示例反映的编程思想是实时系统中比较常见的一种高效的数据管理实现,在一些高级语言架构的源码实现中也能见到这种思想,希望在下的讲述能带给各位一定启发。
1、数据对象的管理方式
程序运行过程中,我们通常使用一些特定数据结构来存储数据信息,比如数组、链表,它们各自有其优点:随机访问方式的数组能最快定位数据,顺序访问的数组能方便地增删数据。甚至一个变量、一个寄存器变量,都算作程序运行中的数据存储的方式。
假定这么一个抽象场景:用程序来表示一辆公共汽车的座位资源(假定站着的人最多30人,算作30个座位资源),每个上车的乘客都能分配到一个座位资源,分配不到座位资源时不能上车。现在需要构建一个程序结构来处理乘客上下车及乘坐过程,能查看当前有人座和空座信息,能按座位号查询信息,设定这辆车的座位资源数为70。
第一反应,可以用链表或数组实现,如果你有面向对象编程的思维,可能会把对象作为链表或数组的元素来处理。这里明显能知道,资源数量是固定有限的,所以数组是完全可以用的。
如果用数组实现,我们可以采用以下这种方案(不唯一):
这种方案有一个好处,内存是连续的,从操作系统角度看,不会产生很多的内存碎片;另一个好处是,由于数组角标即座位号(即使不是也能用hash表实现快速定位),定位元素和删除(即下车)操作得益于数组的随机存取方式会很高效。
但缺点也是显而易见的,要查询有人座位或无人座位都需要遍历数组的所有元素,在这个案例中数量只有70,暂时是可以接受的,但对于更大型的数组而言,这种实现的时间复杂度就会很大。同理,插入(即上车)操作的时间复杂度随着数组长度递增。
纯粹用链表实现,可以有以下这种方案(不唯一):
相比数组这种实现的好处在于,查询有人座和空座信息的速度快、效率高,上下车操作简化为结点在链表间移动,效率也很高。
这种实现的缺点在于:频繁增删结点,即分配和回收内存空间的操作既可能导致内存碎片的产生,内存操作又很耗时,对于长期运行的系统是大不利的;定向查询信息的效率也不如数组,这主要是链表的顺序存储方式导致的。
要是以上二者的优点能结合起来会不会更好?
答案也是显而易见的,但如何结合数组与链表,如果你还记得用指针访问数组元素的方式,你差不多也能顿悟内存池的实现思想了。
2、内存池的好处
内存池基本思想在于:初始时分配一段连续内存,用数组方式保存连续内存,用链表管理内存资源分配。
对于实时系统最大的好处就在于,减少了频繁的内存操作带来的效率降低和内存碎片的发生,这是有利于一个长期运行的系统的(分钟级的重启对企业造成的损失经常是上百万的,很多设备或系统可能几年都不关闭的,这时的计算机资源是不容浪费的)。
减少频繁申请和释放操作的思维在其他很多池的原理中也能见到:
1、线程池:一次性开启若干个线程,阻塞并等待使用申请到来,管理线程分配其任务时开始运行(比如支持并行传输的TFTP服务器,很多服务器程序有这种实现)
2、连接池:常见于数据库连接,一次性开启多个连接先接入数据库,有数据库操作需求时直接调资源使用,省去连接耗时,HTTP中也有连接池技术
3、内存池的基本设计
这里介绍一种简单的内存池实现思想。
1、根据规格数量分配一段连续内存(数组)
2、用链表的方式管理这段连续内存的分配
再简而言之,即数组的每个元素结点,同时也是某个链表的结点,用指针来管理这些关系。
这样做的好处基本就囊括了以上说的数组和链表的优势了,随机存取的方式能方便定位查询和删除,数组实现使得内存碎片减少,顺序存取方式方便了资源分配和回收。
这种实现如下图所示:
类似的实现思想在ucosII系统的源码中就能看到,对于系统源码还有很多精巧的位操作使得时间复杂度和空间复杂度都很低,有兴趣的可以去了解一下那些源码中各种池的实现思想。
内存池结构对于外部模块而言,最基本的一般会提供获取、释放两个操作,本质就是向一段分配好的内存资源申请一个数据结点来使用,使用完毕需要销毁时再调用释放操作,内存池的管理结构会将这段内存初始化,以便下一次资源申请。具体实现思路:
(1)获取资源:从AvailableList取一个结点(从头从尾取都行,维护好首尾指针就行),外部数据存储到结点中,将该结点从AvailableList链表删除,插入到UsedList链表中(各种插入方法都行,维护好首尾指针)。
(2)释放资源:定位UsedList中某个要释放的资源,将内存清零,从UsedList删除,将结点插入到AvailableList中。
对于结点的查询,通过数组角标、hash表都能很快进行,链表结构又能最快找到允许分配的结点,这就是内存池在算法的时间复杂度上的优势。在内存资源维护上也功劳不少,特别是处理较大对象实例、数组的时候(从内存角度看,一个完整的学生类的实例可能需要上kB的内存长度,再延展为学生类数组,一个数组的大小可能就能达到MB级)
4、C语言示范
此处用C语言实现以上所述内存池思想:
/* 内存池示例
* Johan Joe King
* 2020-2-12
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SOURCE_AMOUNT (70)
#define OPER_OK (0)
#define OPER_ERROR (-1)
#define EMPTY_YES (1)
#define EMPTY_NO (0)
/* 座位结构 */
typedef struct seat{
int id; /* 座位号 */
int empty; /* 是否空座 */
char name[30]; /* 乘客姓名 */
struct seat *pre; /* pre指针 */
struct seat *next; /* next指针 */
}Seat;
/* 内存池管理结构 */
typedef struct{
Seat seats[SOURCE_AMOUNT]; /* 内存结点资源 */
Seat *availableList; /* 可用链表 */
Seat *availableListTail; /* 可用链表尾结点 */
Seat *usedList; /* 已用链表 */
Seat *usedListTail; /* 已用链表尾结点 */
int availableCnt; /* 可用结点数量 */
int usedCnt; /* 已用结点数量 */
}MemoryPoolManager;
MemoryPoolManager *g_memPoolMngr = NULL; /* 内存池管理 */
/* 初始化内存池管理 */
void initMemoryPoolManager()
{
g_memPoolMngr = malloc(sizeof(MemoryPoolManager));
int i = 0;
/* 清空内存 */
for (i = 0; i < SOURCE_AMOUNT; i++)
{
memset(&g_memPoolMngr->seats[i], 0, sizeof(Seat));
g_memPoolMngr->seats[i].id = i + 1;
}
/* 构造链表 */
g_memPoolMngr->availableCnt = SOURCE_AMOUNT;
g_memPoolMngr->availableList = &g_memPoolMngr->seats[0];
g_memPoolMngr->availableListTail = &g_memPoolMngr->seats[SOURCE_AMOUNT - 1];
g_memPoolMngr->usedCnt = 0;
g_memPoolMngr->usedList = NULL;
g_memPoolMngr->usedListTail = NULL;
for (i = 1; i < SOURCE_AMOUNT - 1; i++)
{
g_memPoolMngr->seats[i].next = &g_memPoolMngr->seats[i + 1];
g_memPoolMngr->seats[i].pre = &g_memPoolMngr->seats[i - 1];
}
g_memPoolMngr->seats[0].pre = NULL;
g_memPoolMngr->seats[0].next = &g_memPoolMngr->seats[1];
g_memPoolMngr->seats[SOURCE_AMOUNT - 1].pre = &g_memPoolMngr->seats[SOURCE_AMOUNT - 2];
g_memPoolMngr->seats[SOURCE_AMOUNT - 1].next = NULL;
printf("[System]Initialized...\n");
}
/* 打印可用链表和已用链表 */
void displayList()
{
printf("============================== Used List ==================================\n");
if (g_memPoolMngr->usedList == NULL)
{
printf("NULL\n0 uesd.\n");
}
else
{
Seat *cur = NULL;
cur = g_memPoolMngr->usedList;
while (cur)
{
printf("id:%-2d empty:%-4s name:%s\n",
cur->id, (cur->empty == EMPTY_NO) ? "No" : "Yes", cur->name);
cur = cur->next;
}
printf("%d used.\n", g_memPoolMngr->usedCnt);
}
printf("============================ Available List ===============================\n");
if (g_memPoolMngr->availableList == NULL)
{
printf("NULL\n0 available.\n");
}
else
{
Seat *cur = NULL;
cur = g_memPoolMngr->availableList;
while (cur)
{
printf("id:%-2d empty:%-4s name:%s\n",
cur->id, (cur->empty == 0) ? "No" : "Yes", cur->name);
cur = cur->next;
}
printf("%d available.\n", g_memPoolMngr->availableCnt);
}
}
/* 获取资源 */
Seat *getResource()
{
Seat *node = NULL;
node = g_memPoolMngr->availableList; /* 取可用链表头结点 */
if (node == NULL)
goto final;
g_memPoolMngr->availableList = node->next;
node->next = NULL;
node->pre = NULL;
if (g_memPoolMngr->usedList == NULL) /* 插入到已用链表尾部 */
{
g_memPoolMngr->usedList = node;
g_memPoolMngr->usedListTail = node;
}
else
{
g_memPoolMngr->usedListTail->next = node;
node->pre = g_memPoolMngr->usedListTail;
g_memPoolMngr->usedListTail = node;
}
node->empty = EMPTY_YES;
g_memPoolMngr->availableCnt--;
g_memPoolMngr->usedCnt++;
final:
return node;
}
/* 释放资源 */
int freeResource(int id)
{
int ret = OPER_ERROR;
int index = id - 1;
Seat *cur = NULL;
cur = &g_memPoolMngr->seats[index];
if (cur == NULL)
goto final;
/* 从已用链表取出结点 */
if (cur->pre)
cur->pre->next = cur->next;
if (cur->next)
cur->next->pre = cur->pre;
if (cur == g_memPoolMngr->usedList)
g_memPoolMngr->usedList = cur->next;
/* 清空内存 */
cur->pre = NULL;
cur->next = NULL;
memset(cur, 0, sizeof(Seat));
cur->id = id;
/* 插入到可用链表尾部 */
if (g_memPoolMngr->availableList == NULL)
{
g_memPoolMngr->availableList = cur;
g_memPoolMngr->availableListTail = cur;
}
else
{
g_memPoolMngr->availableListTail->next = cur;
cur->pre = g_memPoolMngr->availableListTail;
g_memPoolMngr->availableListTail = cur;
}
ret = OPER_OK;
g_memPoolMngr->availableCnt++;
g_memPoolMngr->usedCnt--;
final:
return ret;
}
int main()
{
initMemoryPoolManager();
int i;
Seat *cur = NULL;
for (i = 0; i < 14; i++) /* 申请一些资源 */
{
cur = getResource();
strcpy(cur->name, "Johan");
}
/* 释放一些资源 */
freeResource(2);
freeResource(5);
freeResource(6);
freeResource(8);
freeResource(11);
/* 查看内存池情况 */
displayList();
return 0;
}
运行结果:
能看到释放的结点放在了可用链表尾部:
以上是一种内存池的简单实现方法,结合堆、树等数据结构和算法能得到更高效率的内存池架构,有兴趣的可以去搜索一下。