1.论述
我们采取的动态内存分配策略,也是linux内核的动态内存分配策略,称为Slab内存分配器。
2.实践
对动态内存分配采取的策略是。
1.设计一个全局对象实例进行固定某尺寸动态内存的分配与回收。
struct Slab_cache
{
unsigned long size;
unsigned long total_using;
unsigned long total_free;
struct Slab * cache_pool;
struct Slab * cache_dma_pool;
void *(* constructor)(void * Vaddress,unsigned long arg);
void *(* destructor)(void * Vaddress,unsigned long arg);
};
2.我们全局实例对象的类型是Slab_cache,注意到其存在成员变量cahe_pool。我们用Slab_cache的实例对象完成固定某尺寸动态内存的分配与回收。
具体动作的执行者是Slab类型实例对象。
struct Slab
{
struct List list;
struct Page * page;
unsigned long using_count;
unsigned long free_count;
void * Vaddress;
unsigned long color_length;
unsigned long color_count;
unsigned long * color_map;
};
一个Slab对象拥有一个物理页,并管理这个物理页内的固定尺寸内存块的分配和回收。
Slab对象包含List类型成员变量,这样可以将多个Slab对象加入双向链表结构。
color_map作用是加快空间内存块的定位,加快分配和释放过程。
3.理清了上述关系后,为了实现动态内存分配器。我们定义16个Slab_cache类型的全局实例对象,分别用于实现尺寸为32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576的固定尺寸内存块的分配。
struct Slab_cache kmalloc_cache_size[16] =
{
{32 ,0 ,0 ,NULL ,NULL ,NULL ,NULL},
{64 ,0 ,0 ,NULL ,NULL ,NULL ,NULL},
{128 ,0 ,0 ,NULL ,NULL ,NULL ,NULL},
{256 ,0 ,0 ,NULL ,NULL ,NULL ,NULL},
{512 ,0 ,0 ,NULL ,NULL ,NULL ,NULL},
{1024 ,0 ,0 ,NULL ,NULL ,NULL ,NULL}, //1KB
{2048 ,0 ,0 ,NULL ,NULL ,NULL ,NULL},
{4096 ,0 ,0 ,NULL ,NULL ,NULL ,NULL}, //4KB
{8192 ,0 ,0 ,NULL ,NULL ,NULL ,NULL},
{16384 ,0 ,0 ,NULL ,NULL ,NULL ,NULL},
{32768 ,0 ,0 ,NULL ,NULL ,NULL ,NULL},
{65536 ,0 ,0 ,NULL ,NULL ,NULL ,NULL}, //64KB
{131072 ,0 ,0 ,NULL ,NULL ,NULL ,NULL}, //128KB
{262144 ,0 ,0 ,NULL ,NULL ,NULL ,NULL},
{524288 ,0 ,0 ,NULL ,NULL ,NULL ,NULL},
{1048576,0 ,0 ,NULL ,NULL ,NULL ,NULL}, //1MB
};
4.对16个实例对象进行初始化。包含为每个实例对象分配一个Slab对象,为每个Slab对象分配一个Page对象,及划分出一个可用的2MB区域。
在动态内存分配器实现之前,内存区域的分配全部都是采用直接圈定的方式。要想圈定出来的区域可以被正常的访问,需要预先为圈定出来的区域完成页表注册。
unsigned long slab_init()
{
struct Page * page = NULL;
unsigned long * virtual = NULL;
unsigned long i,j;
// 实例对象得end_of_struct是一个动态变化值。维持着目前可用区域起始线性地址。
unsigned long tmp_address = memory_management_struct.end_of_struct;
// 1.对16个实例对象,为其初始化分配一个Slab对象。
for(i = 0; i < 16; i++)
{
// 划分出Slab的区域。在动态内存分配器实现之前,
// 划分区域采用的都是预先保证所划分区域已经完成页表注册。然后,直接圈定范围进行使用。
struct Slab* lpSlab = (struct Slab *)memory_management_struct.end_of_struct;
// 在Slab对象之后预留80字节空间。然后更新目前可用区域起始线性地址。
memory_management_struct.end_of_struct = memory_management_struct.end_of_struct + sizeof(struct Slab) + sizeof(long) * 10;
// Slab对象初始化
// list初始化
list_init(&lpSlab->list);
lpSlab->using_count = 0;
// 计算一个2MB物理页内可拆分出固定尺寸内存块的数量
lpSlab->free_count = PAGE_2M_SIZE / kmalloc_cache_size[i].size;
// color_length是一个字节尺寸,该尺寸既能容纳指定数量比特位。又是可被8整除的。
lpSlab->color_length =((PAGE_2M_SIZE / kmalloc_cache_size[i].size + sizeof(unsigned long) * 8 - 1) >> 6) << 3;
// color_count是实际比特位数量
lpSlab->color_count = lpSlab->free_count;
// color_map所占据尺寸采用圈定范围分配的方式得到。
lpSlab->color_map = (unsigned long *)memory_management_struct.end_of_struct;
// 更新目前可用区域起始线性地址。留出一点空间。同时保证可用区域起始线性地址可被8整除。
memory_management_struct.end_of_struct = (unsigned long)(memory_management_struct.end_of_struct + lpSlab->color_length + sizeof(long) * 10) & ( ~ (sizeof(long) - 1));
// color_map中所有比特位设置为1.
memset(lpSlab->color_map,0xff,lpSlab->color_length);
// 将指定位置的比特位设置为0
for(j = 0; j < lpSlab->color_count; j++)
*(lpSlab->color_map + (j >> 6)) ^= 1UL << j % 64;
// 对实例对象字段进行初始化
kmalloc_cache_size[i].cache_pool = lpSlab;
kmalloc_cache_size[i].total_free = lpSlab->color_count;
kmalloc_cache_size[i].total_using = 0;
}
// 2.因为我们采用直接圈定范围方式又使用的一些新的物理内存区域。
// 所以,需要找到这些区域对应的Page对象。对其进行相应的设置。
i = Virt_To_Phy(memory_management_struct.end_of_struct) >> PAGE_2M_SHIFT;
for(j = PAGE_2M_ALIGN(Virt_To_Phy(tmp_address)) >> PAGE_2M_SHIFT; j <= i; j++)
{
page = memory_management_struct.pages_struct + j;
// 设置page对应比特位为1
*(memory_management_struct.bits_map + ((page->PHY_address >> PAGE_2M_SHIFT) >> 6)) |= 1UL << (page->PHY_address >> PAGE_2M_SHIFT) % 64;
if(page->zone_struct)
{
page->zone_struct->page_using_count++;
page->zone_struct->page_free_count--;
}
page_init(page,PG_PTable_Maped | PG_Kernel_Init | PG_Kernel);
}
color_printk(ORANGE,BLACK,
"2.memory_management_struct.bits_map:%#018lx\tzone_struct->page_using_count:%d\tzone_struct->page_free_count:%d\n",
*memory_management_struct.bits_map,memory_management_struct.zones_struct->page_using_count,memory_management_struct.zones_struct->page_free_count);
for(i = 0; i < 16; i++)
{
// 采用直接圈定的方式分配出一个2MB可用区域。
// 采用直接圈定方式分配出的区域,要想区域能正常被访问。需要提前在页表中为分配出的区域进行注册。
virtual = (unsigned long *)((memory_management_struct.end_of_struct + PAGE_2M_SIZE * i + PAGE_2M_SIZE - 1) & PAGE_2M_MASK);
// 通过虚拟地址定位到物理地址,再通过物理地址定位到对应的Page对象
page = Virt_To_2M_Page(virtual);
// 将bits_map中对应比特位设置为1
*(memory_management_struct.bits_map + ((page->PHY_address >> PAGE_2M_SHIFT) >> 6)) |= 1UL << (page->PHY_address >> PAGE_2M_SHIFT) % 64;
if(page->zone_struct)
{
page->zone_struct->page_using_count++;
page->zone_struct->page_free_count--;
}
// 给Page对象进行属性设置
page_init(page,PG_PTable_Maped | PG_Kernel_Init | PG_Kernel);
// 为Slab对象提供关联的Page对象。Slab对象有了Page对象后,才能进行实际的固定内存块的分配和回收操作。
kmalloc_cache_size[i].cache_pool->page = page;
// 为Slab对象设置关联Page对象代表的物理页的起始虚拟地址
kmalloc_cache_size[i].cache_pool->Vaddress = virtual;
}
color_printk(ORANGE,BLACK,"3.memory_management_struct.bits_map:%#018lx\tzone_struct->page_using_count:%d\tzone_struct->page_free_count:%d\n",*memory_management_struct.bits_map,memory_management_struct.zones_struct->page_using_count,memory_management_struct.zones_struct->page_free_count);
color_printk(ORANGE,BLACK,"start_code:%#018lx,end_code:%#018lx,end_data:%#018lx,end_brk:%#018lx,end_of_struct:%#018lx\n",memory_management_struct.start_code,memory_management_struct.end_code,memory_management_struct.end_data,memory_management_struct.end_brk, memory_management_struct.end_of_struct);
return 1;
}
5.有了上述的基础后,我们就可以进行固定尺寸内存块的分配和回收工作了。因为,正常申请动态内存块时候,申请的尺寸是不固定的,但是我们可以在我们支持的16中固定尺寸中找到一个恰好大于其尺寸的固定尺寸,按此固定尺寸进行分配即可。
// 动态内存申请
void * kmalloc(unsigned long size, unsigned long gfp_flages)
{
int i,j;
struct Slab * slab = NULL;
// 内存分配器对一次可申请的最大尺寸有限制
if(size > 1048576)
{
color_printk(RED,BLACK,"kmalloc() ERROR: kmalloc size too long:%08d\n",size);
return NULL;
}
// 寻找容量恰好大于所需尺寸的实例对象
for(i = 0; i < 16; i++)
if(kmalloc_cache_size[i].size >= size)
break;
// 通过实例对象得到Slab对象
slab = kmalloc_cache_size[i].cache_pool;
// 如实例对象下Slab对象池中至少有一个块可被分配
if(kmalloc_cache_size[i].total_free != 0)
{
// 在实例对象下的Slab对象集合中进行分配
do
{
// 若当前Slab对象内无块可被分配
if(slab->free_count == 0)
// 因为我们已经知道Slab对象集合中至少有一个块可被分配。那么找到下个Slab对象
// 从slab->list找到下个slab的list,从该list找到slab指针
slab = container_of(list_next(&slab->list), struct Slab, list);
else
// 当前slab内有块可被分配
break;
}while(slab != kmalloc_cache_size[i].cache_pool);
}
else
{
// 因为实例对象下Slab对象池里没有块可被分配。
// 所以,我们构造新的Slab对象并将其加入实例对象的Slab对象池
slab = kmalloc_create(kmalloc_cache_size[i].size);
if(slab == NULL)
{
color_printk(BLUE,BLACK,"kmalloc()->kmalloc_create()=>slab == NULL\n");
return NULL;
}
// 这样实例对象下Slab对象池里可供分配块数量一下子增加了
kmalloc_cache_size[i].total_free += slab->color_count;
color_printk(BLUE,BLACK,"kmalloc()->kmalloc_create()<=size:%#010x\n",kmalloc_cache_size[i].size);///
// 将新的Slab对象加入实例对象对象集合中(双向链表)
list_add_to_before(&kmalloc_cache_size[i].cache_pool->list, &slab->list);
}
// 执行到这里,可以确定slab对象里面至少有一个块可供分配
for(j = 0;j < slab->color_count; j++)
{
// 通过color_map快速定位到可被分配块的位置
if(*(slab->color_map + (j >> 6)) == 0xffffffffffffffffUL)
{
j += 63;
continue;
}
// 到这里可知j所在unsigned long的64个比特位里面必然存在数值为0的
// 找到时
if( (*(slab->color_map + (j >> 6)) & (1UL << (j % 64))) == 0 )
{
// 将比特位设置为1
*(slab->color_map + (j >> 6)) |= 1UL << (j % 64);
// slab对象在用块数量
slab->using_count++;
// slab对象可用块数量
slab->free_count--;
// 隶属的实例对象可用块数量
kmalloc_cache_size[i].total_free--;
// 隶属的实例对象在用块数量
kmalloc_cache_size[i].total_using++;
// 通过slab找到隶属物理页起始线性地址
// 利用固定尺寸,分配块索引,快速定位到分配块起始线性地址
return (void *)((char *)slab->Vaddress + kmalloc_cache_size[i].size * j);
}
}
// 无法完成分配。这里属于异常情形了。
color_printk(BLUE,BLACK,"kmalloc() ERROR: no memory can alloc\n");
return NULL;
}
我们只要找到Slab对象,就可进一步找到Page对象,然后得到物理页基地址,将其转换为线性地址,根据固定尺寸块的索引和块的尺寸可以很快得到固定尺寸块的线性地址。
当实例对象下Slab对象集合中均无新块可供分配时,我们将得到新的Slab对象,并将其加入实例对象的Slab对象集合里。
struct Slab * kmalloc_create(unsigned long size)
{
int i;
struct Slab * slab = NULL;
struct Page * page = NULL;
unsigned long * vaddresss = NULL;
long structsize = 0;
// 1.分配一个Page对象。这里分配出来的page对象转换得到的线性区域是直接可用的。
page = alloc_pages(ZONE_NORMAL, 1, 0);
if(page == NULL)
{
color_printk(RED, BLACK, "kmalloc_create()->alloc_pages()=>page == NULL\n");
return NULL;
}
// 初始化
page_init(page, PG_Kernel);
switch(size)
{
case 32:
case 64:
case 128:
case 256:
case 512:
// 如果Slab服务于分配尺寸小于等于512的内存块分配
// 得到物理页线性地址
vaddresss = Phy_To_Virt(page->PHY_address);
// 由于固定尺寸块较小,所以我们选择在一个物理页中同时用于分配固定尺寸块,和存放Slab及其依赖的color_map区域。
// 尺寸计算=一个Slab本身,一个color_map占据尺寸(以字节为单位)。
// color_map所占尺寸足以容纳指定数量比特位,还会有剩余。
structsize = sizeof(struct Slab) + PAGE_2M_SIZE / size / 8;
// 找到Slab对象线性地址
slab = (struct Slab *)((unsigned char *)vaddresss + PAGE_2M_SIZE - structsize);
// 找到color_map区域线性地址
slab->color_map = (unsigned long *)((unsigned char *)slab + sizeof(struct Slab));
// 计算分配区可分配内存块数量
slab->free_count = (PAGE_2M_SIZE - (PAGE_2M_SIZE / size / 8) - sizeof(struct Slab)) / size;
// 设置Slab已经分配块数
slab->using_count = 0;
// 设置color_map中有效比特位数量
slab->color_count = slab->free_count;
// 设置Slab所管理物理页起始线性地址
slab->Vaddress = vaddresss;
// 设置Slab关联的Page对象
slab->page = page;
// Slab->list初始化
list_init(&slab->list);
// 这里给出的color_length是一个足以容纳指定数量的比特位,且8字节对齐的尺寸。
// 前面为color_map预留的尺寸本身就是8字节对齐的。所以,color_length不会超出物理页范围。
slab->color_length = ((slab->color_count + sizeof(unsigned long) * 8 - 1) >> 6) << 3;
memset(slab->color_map,0xff,slab->color_length);
// 将color_map中有效比特位设置为0
for(i = 0;i < slab->color_count;i++)
*(slab->color_map + (i >> 6)) ^= 1UL << i % 64;
break;
case 1024: //1KB
case 2048:
case 4096: //4KB
case 8192:
case 16384:
case 32768:
case 65536:
case 131072: //128KB
case 262144:
case 524288:
case 1048576: //1MB
// 如果Slab服务于分配尺寸小于等于1024的内存块分配
// 则Slab对象自身采用动态内存分配方式得到所需空间
// 这样物理页本身可以全部用于数据块分配
slab = (struct Slab *)kmalloc(sizeof(struct Slab),0);
// 可供分配物理块数量
slab->free_count = PAGE_2M_SIZE / size;
// 已经分配物理块数量
slab->using_count = 0;
// 设置color_map中有效比特位数量
slab->color_count = slab->free_count;
// 计算color_map区域尺寸
slab->color_length = ((slab->color_count + sizeof(unsigned long) * 8 - 1) >> 6) << 3;
// color_map本身占据区域也采用动态内存分配方式得到。
slab->color_map = (unsigned long *)kmalloc(slab->color_length,0);
memset(slab->color_map,0xff,slab->color_length);
// 设置Slab管理物理页起始线性地址
slab->Vaddress = Phy_To_Virt(page->PHY_address);
// 设置Slab关联的Page对象
slab->page = page;
// slab->init初始化
list_init(&slab->list);
// 将color_map中有效比特位设置为0
for(i = 0;i < slab->color_count;i++)
*(slab->color_map + (i >> 6)) ^= 1UL << i % 64;
break;
default:
color_printk(RED,BLACK,"kmalloc_create() ERROR: wrong size:%08d\n",size);
free_pages(page,1);
return NULL;
}
return slab;
}
6.有了动态内存分配过程的实现,作为一个完整的过程我们也需实现动态内存分配释放
unsigned long kfree(void * address)
{
int i;
int index;
struct Slab * slab = NULL;
// 利用线性地址,得到线性地址所在物理页起始线性地址
void * page_base_address = (void *)((unsigned long)address & PAGE_2M_MASK);
// 遍历每个实例对象
for(i = 0; i < 16; i++)
{
// 遍历实例对象的每个Slab对象
slab = kmalloc_cache_size[i].cache_pool;
do
{
// 找到了此线性地址所在的Slab对象
if(slab->Vaddress == page_base_address)
{
// (通过线性地址-Slab可分配起始线性地址) / 隶属实例对象可分配块尺寸,得到块索引
index = (address - slab->Vaddress) / kmalloc_cache_size[i].size;
// 设置slab中color_map中对应比特位为1
*(slab->color_map + (index >> 6)) ^= 1UL << index % 64;
// 设置slab下可分配块数
slab->free_count++;
// 设置slab下已分配块数
slab->using_count--;
// 设置隶属的实例对象下可分配块数
kmalloc_cache_size[i].total_free++;
// 设置隶属的实例对象下已分配块数
kmalloc_cache_size[i].total_using--;
// 如果slab对象内无已经分配快数
// 且隶属的实例对象中可供分配块数 >= slab下总的块数的1.5倍
// 且此slab不是初始时的slab
if((slab->using_count == 0)
&& (kmalloc_cache_size[i].total_free >= slab->color_count * 3 / 2)
&& (kmalloc_cache_size[i].cache_pool != slab))
{
// 这样我们对此slab进行回收
switch(kmalloc_cache_size[i].size)
{
case 32:
case 64:
case 128:
case 256:
case 512:
// 从双向链表中移除自身
list_del(&slab->list);
// 隶属的实例对象中可分配块数快速减少
kmalloc_cache_size[i].total_free -= slab->color_count;
// 关联page的清理
page_clean(slab->page);
// 关联page的释放
free_pages(slab->page,1);
break;
default:
// 从双向链表中移除自身
list_del(&slab->list);
// 隶属的实例对象中可分配块数快速减少
kmalloc_cache_size[i].total_free -= slab->color_count;
// color_map所占区域是动态分配出来的,所以需要通过动态释放来释放
kfree(slab->color_map);
// 关联page的清理
page_clean(slab->page);
// 关联page的释放
free_pages(slab->page,1);
// slab所占区域是动态分配出来的,所以需要通过动态释放来释放
kfree(slab);
break;
}
}
return 1;
}
else
slab = container_of(list_next(&slab->list),struct Slab,list);
}while(slab != kmalloc_cache_size[i].cache_pool);
}
// 这里属于异常情况了
color_printk(RED,BLACK,"kfree() ERROR: can`t free memory\n");
return 0;
}