有了按内存页申请内存之后就可以动态申请内存了,但这远远是不够的,因为如果每个程序都按照4KB的大小来申请内存,则会大大的浪费内存空间,比如要申请一个10个字节的内存,操作系统则会为它分配一个大小为4096B的内存页,另外一个程序要申请一个4字节的内存,操作系统仍然会为它分配一个4096B的内存页。这样的内存分配是不合理的,是对内存资源大大的浪费。为了能让内存资源更加合理被程序所利用,还需要对其进行进一步的处理:页内位图。
页内位图是指在一个内存页当中,每一个字节的使用状态都被位图记录下来,也就是用这个map位图来记录页内内存的使用状态。这里采用的是一个bit位代表4个字节的内存。那么对于一个内存页的4096B的内存使用状态则需要页内位图的大小是4096 / 4 / 8 = 128。一个动态内存页中前128字节为页内位图所占用的空间,而这部分空间在位图中占128 / 4 / 8 = 4个字节。如下:
如果申请内存大小大于一个页大小,则按整页分配内存:
if (size > (MM_PAGE_SIZE - 128))
{
//计算有多少个内存页
u32 count = (size / MM_PAGE_SIZE);
//如果有余数,说明要多分配一个页面
if (size % MM_PAGE_SIZE != 0)
{
count++;
}
//按整页分配内存
return alloc_page(count);
}
在动态分配内存时要按4字节对齐分配内存。这样会有效的避免内存浪费的问题,也就是说如果位图中每个bit位表示的内存字节太小,那么页内位图本身所占用的内存空间就会增大。如果让页内位图占用空间小,但是每个bit位所表示的内存字节就会有所浪费,比如某个程序申请1个字节时,操作系统也只能为其分配4个字节。这一个权衡值,这里使用的是4字节对齐。
如果申请内存大小大于一个页面的大小4096B则直接按申请内存页方式来申请:
u32 alloc_size = size / 4;
//如果有余数,说明要多分配一个4字节
if (size % 4 > 0)
{
alloc_size++;
}
下面代码中为内存分配相应的内存空间,其实现原理为:先找到一个可分配的内存页,可以是未使用的,也可以是动态分配的,然后在这个内存页中逐个向下查找内存位图,直到找到符合申请内存空间大小的内存区域,并将这个区域设置为已使用,并将其开始地址返回:
//i为map下标,j为页内map下标,k为页内字节位偏移,c为查找count,
int i, j, k, c = 0, break_status = 0, run_time = 0;
//is为起始map下标,js为页内起始map下标,ks为面内起始字节偏移
int is = -1, js = -1, ks = -1;
//从未被分配内存页的地方开始查找
for (i = MMAP_USED_SIZE; i < MAP_SIZE && !break_status; i++)
{
//如果是未使用或动态内存
if (mmap[i] == MM_FREE || mmap[i] == MM_DYNAMIC)
{
//取得页内map位图
u8 *mpmap = (u8*) (i * MM_PAGE_SIZE);
//跳过前4个字节,并小于128个字节中查找页内位图map
//一个页面为4096字节,前128字节为页内位图0x80 * 0x8 * 0x4 = 0x1000 = 4096
//这128个字节占用页内位图为0x80 / 0x8 / 0x4 = 0x4字节所以要跳过前4个字节
for (j = 4; j < 128 && !break_status; j++)
{
//字节偏移从0到7字节来查找可用内存
for (k = 0; k < 8 && !break_status; k++)
{
//如果可以使用
if (((mpmap[j] >> k) & 0x1) == 0)
{
//已找到数量自增
c++;
}
//如果找到可分配内存数量达到预先要申请的数量
if (c >= alloc_size)
{
//跳出
break_status = 1;
}
}
}
}
}
//清空分配数
c = 0;
//正式分配内存
if (break_status == 1 && is != -1 && js != -1 && ks != -1)
{
//从内存位图开始
for (i = is; i < MAP_SIZE; i++)
{
//如果是可分配内存
if (mmap[i] == MM_FREE || mmap[i] == MM_DYNAMIC)
{
//取得页内位图
u8 *mpmap = (u8*) (i * MM_PAGE_SIZE);
//从页内位图的第4个字节开始
for (j = 4; j < 128; j++)
{
//如果是第1次,找到起始地址
if (run_time == 0)
{
j = js;
}
//页内偏移
for (k = 0; k < 8; k++)
{
//如果是第1次,找到起始偏移
if (run_time == 0)
{
k = ks;
}
//更新页内位图
mpmap[j] |= (1 << k);
//找到数量自增
c++;
//次数加1
run_time++;
//找到预期的申请数量
if (c >= alloc_size)
{
//将此内存页设定为动态分配
mmap[i] = MM_DYNAMIC;
//返回申请内存地址
return (void*) (is * MM_PAGE_SIZE + (js * 8 * 4) + (ks * 4));
}
}
}
}
}
}
释放内存,如果要释放的内存大小大于一个内存页的大小,则直接按释放内存页的方式来释放内存:
//如果大小大于一个内存页大小,则按整内存页释放
if (size > (MM_PAGE_SIZE - 128))
{
//计算有多少个内存页
int count = (size / MM_PAGE_SIZE);
//如果有余数说明要多释放一个页
if (size % MM_PAGE_SIZE != 0)
{
count++;
}
//释放内存页
free_page(addr, count);
return;
}
4字节对齐,与分配是一样:
//按4字节对齐释放
int alloc_size = size / 4;
//如果有余数则说明要多释放一个4字节空间
if (size % 4 > 0)
{
alloc_size++;
}
按4字节来释放内存,并将页内位图的相应的标识位修改为未使用状态:
//释放内存起始号
int is, js, ks, run_time = 0, count = 0;
//计算位图起始号
is = (u32) addr / MM_PAGE_SIZE;
//计算页内位图起始号
js = ((u32) addr % MM_PAGE_SIZE) / (8 * 4);
//计算页内位图位偏移起始号
ks = ((u32) addr % MM_PAGE_SIZE) % (8 * 4) / 4;
//从内存页开始
for (int i = is; i < MAP_SIZE; i++)
{
//取得页内偏移
u8 *mpmap = (u8*) (i * MM_PAGE_SIZE);
//从页内位图中第4个开始
for (int j = 4; j < 128; j++)
{
//如果是第1次释放
if (run_time == 0)
{
j = js;
}
//从页内偏移位开始
for (int k = 0; k < 8; k++)
{
//如果是第1次释放
if (run_time == 0)
{
k = ks;
}
//设定页内位图为动态可用内存
mpmap[j] &= (~(1 << k));
//数量自增
count++;
//次数自增
run_time++;
//如果已释放了预期大小的内存
if (count >= alloc_size)
{
//完成,返回
return;
}
}
}
}
将安装内存申请模块加入start_kernel函数中:
//安装内存申请模块
install_alloc();
加入内存申请测试代码:
char *p = alloc_page(1);
printf("%x\n", p);
char *p1 = alloc_mm(4);
printf("%x\n", p1);
char *p2 = alloc_mm(4);
printf("%x\n", p2);
free_mm(p2, 4);
free_mm(p1, 4);
free_page(p, 1);
p = alloc_page(1);
printf("%x\n", p);
p1 = alloc_mm(4);
printf("%x\n", p1);
p2 = alloc_mm(4);
printf("%x\n", p2);
free_mm(p2, 4);
free_mm(p1, 4);
free_page(p, 1);
将alloc.c加入Makefile并编译运行,运行结果如下:
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git
git git@github.com:magicworldos/lidqos.git
subverion https://github.com/magicworldos/lidqos
branch v0.10
Copyright © 2015-2023 问渠网 辽ICP备15013245号