2014年(33)
分类: LINUX
2014-08-13 10:46:59
原文地址:嵌入式操作系统内核实现(六)(转载) 作者:yuchuan2008
这应该是本章的结尾部分了,上面说了那么多的文字,最终还是要用程序语言来实现,这才是最重要的。ByCore的内存管理部分提供的函数接口不多,全部的函数如下:
1) void initmem(void);
2) void *kmalloc(uword_t Size);
3) void free(void *pfree);
4) void kfree(void *pfree);
5) void kmemset(void *buffer, uword_t c, uword_t count);
6) page_t* kgetmemlist(void);
第一个函数为初始化内存块,该函数在内核初始化阶段被调用,用户不必调用它;第二个函数向内核申请分配一个大小为size的空间;第三和第四为释放有pfree指向的空间,kfree与free的区别在于是否关闭了中断,kfree只是简单调用free;第五个函数将buffer指向的空间设置为c代表的值;最后一个函数简单返回整个内存的起始地址,这个地址被转化成page_t类型,这样可以在应用程序中检索所有的内存分配情况。下面按照编号顺序逐个解释这些函数的实现。
1. initmem( )函数
此函数的主要功能是初始化空闲块索引表,并将整个内存空间当成一个空闲块插入到空闲块索引表中。它的源代码如下:
void initmem(void){
uword_t i;
uword_t index = 1;
page_t *page;
/* 初始化空闲块索引表free_area[] */
for(i=0;i
free_area[i].free_link.next = &free_area[i].free_link;
free_area[i].free_link.prev = &free_area[i].free_link;
free_area[i].size = index * MEM_PAGE_SIZE;
index *= 2;
}
page = ((page_t *)MEM_START_ADDR); /* 将内存首地址转化成page_t结构,该结构用于保存该块的数据信息 */
mem = page; /* mem为全局指针,它的定义为static page_t *mem; */
page->free_link.prev = &page->free_link; /* 初始化page_t结构中的各个域 */
page->free_link.next = &page->free_link;
page->mem_up = NULL;
page->mem_down = NULL;
page->size = MEM_SIZE;
page->status = MEM_FREE;
add_node_seque_rear(&free_area[MEM_PAGE_LIST-1].free_link,&page->free_link); /* 将该空闲块插入到适当的空闲队列中,add_node_seque_rear 函数可以参见前面的内容,前面已经提供了源代码 */
}
2. kmalloc ( )函数
这个函数应该算是比较核心的函数了,应用程序会调用它申请一个大小为size的空间,如果该函数成功将返回一个大小为Size的内存块的首地址。如果理解了前面小节的内容看懂该函数的源代码也很容易,具体源代码如下所示:
void *kmalloc(uword_t Size){
list_t *plist;
page_t *page;
page_t *pleft;
uword_t i;
uword_t flag = FALSE;
uword_t tmp_size = Size + sizeof(page_t); /* 这里的做法是因为一个内存块的描述结构page_t就在该块的最前面,所以这里应该在Size的基础上多申请一个page_t大小的空间。 */
if(tmp_size & 0x03){ /* 这里这样做是让需要的空间按照四字节对齐,这里这样做只想让ByCore在32为机上面跑,当然这里设计也比较垃圾。以后再修改吧 */
tmp_size >>= 0x2;
tmp_size += 0x1;
tmp_size <<= 0x02;
}
mac_disable_irq(); /* 需要关闭中断 */
for(i=0;i
if(free_area[i].size < tmp_size || (free_area[i].free_link.next == &free_area[i].free_link))
continue; /* 这里开始检索空闲块索引表 */
else{
plist = free_area[i].free_link.next;
do{
page = mac_find_entry(plist,page_t,free_link);
if(page->size >= tmp_size){ /* 找到合适的空闲块 */
flag = TRUE;
del_node_seque(&free_area[i].free_link,plist); /* 将该空闲块从空闲队列中删除 */
break;
}else plist = plist->next; /* 继续检索此空闲块队列的下一个空闲块 */
}while(plist != free_area[i].free_link.next);
}
if(flag == TRUE) break; /* 找到合适的空闲块,退出检索 */
}
if(flag != TRUE){ /* 检索完所有的空闲内存块,未找到合适的空闲块,打开中断并返回NULL。*/
mac_enable_irq();
return NULL;
}
if(page->size - tmp_size <= MEM_PAGE_LIMIT){ /* 检查该空闲块是否需要分裂,主要通过该空闲块的大小与需分配块大小的差来判断,如果差小于等于MEM_PAGE_LIMIT就全部分配该块,反之,需要分裂此空闲块。 */
page->status = MEM_ALLOC; /* 将该块标记为MEM_ALLOC表示为已分配。 */
mac_enable_irq();
return (page+1);
}
/* 下面的代码表示空闲块需要分裂成两块,一块分配出去,另一块插入到合适的空闲块链表中 */
pleft = (page_t *)((char_t *)page + tmp_size);
pleft->size = page->size - tmp_size;
pleft->status = MEM_FREE;
pleft->mem_up = page;
pleft->mem_down = page->mem_down;
if(page->mem_down != NULL)
page->mem_down->mem_up = pleft;
page->status = MEM_ALLOC;
page->mem_down = pleft;
page->size = tmp_size;
/* 将剩余的空闲块(pleft指向的),插入到合适的空闲块队列中。 */
for(i=0;i
if(free_area[i].size >= pleft->size){
add_node_seque_rear(&free_area[i].free_link,&pleft->free_link);
break;
}
}
mac_enable_irq(); /* 开中断返回 */
return (page + 1);
}
3. free (void *pfree)函数
该函数也是非常重要的,它的功能是将pfree指向的一块空间收回,主要算法为,首先根据pfree找到该块的page_t结构,上面说过page_t结构就从该块的起始处开始,根据kmalloc源码的实现,只要将pfree转换成page_t类型然后减一就能找到该块page_t结构。所以这里就有个要求,该要求就是pfree通过转换成page_t后减一必须能找到该块的page_t的起始地址,这个要求给编程带来的不方便,这也是比较垃圾的地方,有机会一定要修改,不过现在还没有想到如何修改。先看看free的代码是怎样实现的:
void free(void *pfree){
uword_t i;
page_t *page;
page_t *up;
page_t *down;
page = (((page_t*)(pfree )) - 1); /* 得到当前块的page_t结构,取得它的数据信息 */
if(page->status != MEM_ALLOC)
return;
page->status = MEM_FREE; /* 设置当前块为空闲,up和down指针分别指向它的上面和下面的内存块 */
up = page->mem_up;
down = page->mem_down;
if(down != NULL && down->status == MEM_FREE){ /* 如果存在下面块,且状态为空闲,则将当前块与它的下面块合并为一个块 */
for(i=0;i
if(free_area[i].size >= down->size){ /* 找到下面块的队列头,调用del_node_seque函数将下面块从队列中删除 */
del_node_seque(&free_area[i].free_link,&down->free_link);
break;
}
}
page->size += down->size; /* down指针指向的page_t结构已经没有用处了,修改page指针指向的page_t结构 */
down->size = 0;
down->status = 0;
down->mem_up = NULL;
page->mem_down = down->mem_down;
if(down->mem_down != NULL){ // 如果下面块还有下面块,则将它的mem_up指向page
down->mem_down->mem_up = page;
down->mem_down = NULL;
}
}
if(up != NULL && up->status == MEM_FREE){ /* 如果存在上面块,且状态为空闲,则将当前块与下面块合并,合并算法与上面类似 */
for(i=0;i
if(free_area[i].size >= up->size){
del_node_seque(&free_area[i].free_link,&up->free_link);
break;
}
}
up->size += page->size;
page->size = 0;
page->status = 0;
page->mem_up = NULL;
up->mem_down = page->mem_down;
if(page->mem_down != NULL){
page->mem_down->mem_up = up;
page->mem_down = NULL;
}
page = up; /* page指向合并好的新块 */
}
for(i=0;i
if(free_area[i].size >= page->size){
add_node_seque_rear(&free_area[i].free_link,&page->free_link);
break;
}
}
}
4. kfree (void *pfree)函数
kfree( )函数的实现非常简单,它只是在调用free( )之前,关闭中断,free( )返回之后再打开中断。free( )函数一般在内核中被调用,应用程序不需要调用free( )函数,而只需调用kfree( )函数,kfree( )函数的实现如下:
void kfree(void *pfree){
mac_disable_irq();
free(pfree);
mac_enable_irq();
}
5. kmemset( )函数与kgetmemlist( )函数
kmemset( )函数的原型是(void *buffer, uword_t c, uword_t count),它的功能是将从buffer地址开始的count个字节设置成c;kgetmemlist( )函数的功能只是返回内存的起始地址,也就是前面提到的page_t *mem指向的位置。它们的实现如下:
void kmemset(void *buffer, uword_t c, uword_t count){
char_t *p = buffer;
while(count--)
*p++ = c;
}
page_t* kgetmemlist(void){
return mem;
}
OK,到现在为止,内存管理一节到此结束,后面的很多地方需要调用kmalloc( )和kfree( )函数,如果不明白它们的实现方式和工作原理,也没关系,记住它们的功能就行了。好了,see you next time!
To be continued……
------ anmnmnly
------ 2008.04.16