- CosOS内核中使用一个内核堆来管理内存,内核通过kmalloc和kfree从内核堆中申请和释放内存。CosOS为用户态编写的库函数中也实现了用户态堆,应用程序通过malloc和free从堆中申请释放内存。
- 内核堆和用户态堆的算法类似,都通过调用alloc和free_int来操作堆。下面以代码和注释的形式详细介绍CosOS中 alloc和free_int这两个函数的实现。
- 在堆中,可以用的空闲内存块成为hole,堆使用了一个链表来保存堆中所有可用的hole。Alloc函数就是在这个链表中查找到一个大小合适的hole然后根据请求分配的大小把hole切分成两部分,一部分返回给调用者使用,剩下的重新加入到链表中。
- /* alloc两个参数,size代表请求分配内存的大小,page_align代表请求内存空间的首地址是否必须页对齐,即地址是能被0x1000整除的。实现这个特性是因为内核中有些空间必须页对齐,例如页表、也目录。*/
- void *alloc(u32int size, u8int page_align)
- {
- register struct hole *hp, *prev_ptr;
- u32int old_base;
- prev_ptr = NIL_HOLE;
- hp = hole_head;
- //通过变量hole链表,查找一个合适的hole
- while(hp != NIL_HOLE)
- {
- if(page_align)
- {
- //由于页数限制,页对齐实现部分代码省略。
- }else if (hp->h_len >= size)
- {
- //找到了一个足够大的hole,使用这个hole。
- //将其地址保存在临时变量old_base中,作为alloc的返回值,
- old_base =hp->h_base;
- //由于hole比请求的空间大,剩余部分需放入堆中。只需重新调整hole的首地址和长度即可
- hp->h_base +=size;
- hp->h_len -=size;
- //记住使用使用过的内存当中,最高的地址,保存在high_watermark当中
- if(hp->h_base > high_watermark)
- high_watermark= hp->h_base;
- //经过上面代码调整hole的大小之后,如果hole的大小为0,即hole的大小与请求大小相同,则将其从hole链表中移除。
- if (hp->h_len == 0)
- del_slot(prev_ptr,hp);
- //返回分配内存块得首地址。
- return (void*)(old_base);
- }
- prev_ptr = hp;
- hp = hp->h_next;
- }
- //若运行到这里,说明堆中没有合适的内存块,返回一个空指针。
- return0;
- }
- free_int用于将首地址为base,长度为clicks的内存块释放,重新加入到堆的hole列表中。
- void free_int(u32int base, u32int clicks)
- {
- struct hole*hp, *new_ptr, *prev_ptr;
- //如果要释放的内存块大小为0,则直接返回
- if (clicks ==0)
- return;
- if ( (new_ptr= free_slots) == NIL_HOLE)
- ASSERT(0);
- //将内存块设置成一个hole
- new_ptr->h_base = base;
- new_ptr->h_len = clicks;
- free_slots = new_ptr->h_next;
- hp = hole_head;
- //如果内存块得地址是当前hole链表中最小的或者hole链表为空,则把它插入到链表的第一个位置。
- if (hp ==NIL_HOLE || base <= hp->h_base) {
- //插入到第一个位置
- new_ptr->h_next = hp;
- hole_head = new_ptr;
- //检测是否能与相邻的hole合并成一个更大的hole,这样可以减少内存碎片。
- merge(new_ptr);
- return;
- }
- //hole没有被加入到首位置,则执行下面代码
- prev_ptr = NIL_HOLE;
- //hole链表中的hole是按照内存块首地址由低到高排序的,插入新的hole时必须保证插入之后链表还是有序的。因此需要找到一个合适的位置插入。
- while (hp !=NIL_HOLE && base > hp->h_base) {
- prev_ptr = hp;
- hp = hp->h_next;
- }
- //找到了合适的位置,插入其中
- new_ptr->h_next = prev_ptr->h_next;
- prev_ptr->h_next = new_ptr;
- //检测是否能与相邻的hole合并成一个更大的hole,这样可以减少内存碎片。
- merge(prev_ptr);
- }
- Hole合并的实现较为简单,只需检测hole的base加上size是否与下一hole的base相同即可。这里不详细介绍
- alloc和free_int是最核心最原始的两个函数。free_int使用时第二个参数需要确定释放内存的大小,而用户态的malloc、free中,free是不需要知道内存大小,只需传入需要释放的地址即可。
- 为了在使用free时无需知道内存块得大小也可以正确释放内存块,CosOS中采用的方法是,将内存块得大小保存在内存块首地址前得一个整型空间中,具体实现如下:
- //size为申请内存的大小
- void * malloc(u32int size)
- {
- //使用alloc从堆中申请内存时,多申请4个字节,用来保存内存块大小。
- void *p= alloc(size + 4);
- //在返回内存块的前4个字节中(即一个整型中)保存内存块大小。
- *((u32int*)p) = size;
- //返回这4个字节之后的地址,防止使用内存块时,大小被破坏。
- return(void*)((u32int)p + 4);
- }
- void free(void *p)
- {
- //从内存块首地址的前4个字节中获取内存块的大小,保存在size中。
- u32int size = *((u32int*)p - 1);
- //使用free_int释放这块内存。
- free_int((u32int)p - 4, size + 4);
- }
CosOS内核中使用一个内核堆来管理内存,内核通过kmalloc和kfree从内核堆中申请和释放内存。CosOS为用户态编写的库函数中也实现了用户态堆,应用程序通过malloc和free从堆中申请释放内存。
内核堆和用户态堆的算法类似,都通过调用alloc和free_int来操作堆。下面以代码和注释的形式详细介绍CosOS中 alloc和free_int这两个函数的实现。
在堆中,可以用的空闲内存块成为hole,堆使用了一个链表来保存堆中所有可用的hole。Alloc函数就是在这个链表中查找到一个大小合适的hole然后根据请求分配的大小把hole切分成两部分,一部分返回给调用者使用,剩下的重新加入到链表中。
/* alloc两个参数,size代表请求分配内存的大小,page_align代表请求内存空间的首地址是否必须页对齐,即地址是能被0x1000整除的。实现这个特性是因为内核中有些空间必须页对齐,例如页表、也目录。*/
void *alloc(u32int size, u8int page_align)
{
register struct hole *hp, *prev_ptr;
u32int old_base;
prev_ptr = NIL_HOLE;
hp = hole_head;
//通过变量hole链表,查找一个合适的hole
while(hp != NIL_HOLE)
{
if(page_align)
{
//由于页数限制,页对齐实现部分代码省略。
}else if (hp->h_len >= size)
{
//找到了一个足够大的hole,使用这个hole。
//将其地址保存在临时变量old_base中,作为alloc的返回值,
old_base =hp->h_base;
//由于hole比请求的空间大,剩余部分需放入堆中。只需重新调整hole的首地址和长度即可
hp->h_base +=size;
hp->h_len -=size;
//记住使用使用过的内存当中,最高的地址,保存在high_watermark当中
if(hp->h_base > high_watermark)
high_watermark= hp->h_base;
//经过上面代码调整hole的大小之后,如果hole的大小为0,即hole的大小与请求大小相同,则将其从hole链表中移除。
if (hp->h_len == 0)
del_slot(prev_ptr,hp);
//返回分配内存块得首地址。
return (void*)(old_base);
}
prev_ptr = hp;
hp = hp->h_next;
}
//若运行到这里,说明堆中没有合适的内存块,返回一个空指针。
return0;
}
free_int用于将首地址为base,长度为clicks的内存块释放,重新加入到堆的hole列表中。
void free_int(u32int base, u32int clicks)
{
struct hole*hp, *new_ptr, *prev_ptr;
//如果要释放的内存块大小为0,则直接返回
if (clicks ==0)
return;
if ( (new_ptr= free_slots) == NIL_HOLE)
ASSERT(0);
//将内存块设置成一个hole
new_ptr->h_base = base;
new_ptr->h_len = clicks;
free_slots = new_ptr->h_next;
hp = hole_head;
//如果内存块得地址是当前hole链表中最小的或者hole链表为空,则把它插入到链表的第一个位置。
if (hp ==NIL_HOLE || base <= hp->h_base) {
//插入到第一个位置
new_ptr->h_next = hp;
hole_head = new_ptr;
//检测是否能与相邻的hole合并成一个更大的hole,这样可以减少内存碎片。
merge(new_ptr);
return;
}
//hole没有被加入到首位置,则执行下面代码
prev_ptr = NIL_HOLE;
//hole链表中的hole是按照内存块首地址由低到高排序的,插入新的hole时必须保证插入之后链表还是有序的。因此需要找到一个合适的位置插入。
while (hp !=NIL_HOLE && base > hp->h_base) {
prev_ptr = hp;
hp = hp->h_next;
}
//找到了合适的位置,插入其中
new_ptr->h_next = prev_ptr->h_next;
prev_ptr->h_next = new_ptr;
//检测是否能与相邻的hole合并成一个更大的hole,这样可以减少内存碎片。
merge(prev_ptr);
}
Hole合并的实现较为简单,只需检测hole的base加上size是否与下一hole的base相同即可。这里不详细介绍
alloc和free_int是最核心最原始的两个函数。free_int使用时第二个参数需要确定释放内存的大小,而用户态的malloc、free中,free是不需要知道内存大小,只需传入需要释放的地址即可。
为了在使用free时无需知道内存块得大小也可以正确释放内存块,CosOS中采用的方法是,将内存块得大小保存在内存块首地址前得一个整型空间中,具体实现如下:
//size为申请内存的大小
void * malloc(u32int size)
{
//使用alloc从堆中申请内存时,多申请4个字节,用来保存内存块大小。
void *p= alloc(size + 4);
//在返回内存块的前4个字节中(即一个整型中)保存内存块大小。
*((u32int*)p) = size;
//返回这4个字节之后的地址,防止使用内存块时,大小被破坏。
return(void*)((u32int)p + 4);
}
void free(void *p)
{
//从内存块首地址的前4个字节中获取内存块的大小,保存在size中。
u32int size = *((u32int*)p - 1);
//使用free_int释放这块内存。
free_int((u32int)p - 4, size + 4);
}