您的位置:首页 > 运维架构 > Linux

Linux内核0.11版 lib/Malloc.c 文件分析

2014-12-14 13:15 288 查看
Linux 0.11版 lib/Malloc.c是一个内核内存动态分配模块,该模块服务于内核内存动态分配,注意是服务于内核,而不是一般用户进程。该模块代码量很少,对链表的使用很到位,值得一读。

lib/Malloc.c 模块中涉及到的数据结构和全局变量如下:

struct bucket_desc {     /* 16 bytes */

     void               *page;                    /*物理页的基址,4KB对齐 */

     struct bucket_desc     *next;     /*指向下一个bucket_desc结构*/

     void               *freeptr;                  /*page页面中下一个可供分配出去的内存块地址*/

     unsigned short          refcnt;      /*page页面中已经分配出去的内存块的个数*/

     unsigned short          bucket_size;  /*page页面中每一个内存块的大小,共有4KB/bucket_size个内存块*/

};

struct _bucket_dir {     /* 8 bytes */

     int               size;                                /*bucket_desc链表节点指向的页面可分配的内存块的大小*/

     struct bucket_desc     *chain;        /*指向bucket_desc类型链表*/

};

struct _bucket_dir bucket_dir[] = {

     { 16,     (struct bucket_desc *) 0},

     { 32,     (struct bucket_desc *) 0},

     { 64,     (struct bucket_desc *) 0},

     { 128,     (struct bucket_desc *) 0},

     { 256,     (struct bucket_desc *) 0},

     { 512,     (struct bucket_desc *) 0},

     { 1024,     (struct bucket_desc *) 0},

     { 2048, (struct bucket_desc *) 0},

     { 4096, (struct bucket_desc *) 0},

     { 0,    (struct bucket_desc *) 0}};          

/* 通常一个物理页面4KB,按照不同的块大小,页面可以被分成(4KB/块大小)个内存块,这个全局变量维护着16B~4096B不同大小的内存块链表。因此内核内存动态分配最小是16B,最大是4096B。 */

struct bucket_desc *free_bucket_desc = (struct bucket_desc *) 0;  /*指向下一个可用的 bucket_desc 节点*/

lib/Malloc.c 模块中主要有三个主要函数

static inline void init_bucket_desc();          

void *malloc(unsigned int len);                

void free_s(void *obj, int size);        

     

static inline void init_bucket_desc();

这个函数首先调用get_free_page获得一个4KB大小的物理页面,即为4KB大小的一块内存,将这块内存初始化为一个bucket_desc结构链表,将这个链表插入到free_bucket_desc的前端。

void *malloc(unsigned int len);  

这个函数首先根据len,从bucket_dir数组中找到大小适合的内存桶,即bucket_desc结构指针,bucket_desc的page包含一系列大小为len的内存块,bucket_desc的freeptr指向下一个可用的内存块。若有可用的内存块,则分配出去。若没有找到合适内存块或内存桶,则分配新的内存页,形成新的bucket_desc空闲链表和空闲内存块链表,再进行分配。

void free_s(void *obj, int size);

这个函数释放指定大小的内存块,若一个物理页上的所有内存块都被释放了,则调用free_page释放该物理页。

模块中用到了两个链表,一个是bucket_desc结构链表,一个是固定大小的内存块链表。两种链表都是在物理页面的基础上构建起来的。逻辑上稍微有点复杂,理清关系应该不难理解。

附源码如下:
/*
* malloc.c --- a general purpose kernel memory allocator for Linux.
*
* Written by Theodore Ts'o (tytso@mit.edu), 11/29/91
*
* This routine is written to be as fast as possible, so that it
* can be called from the interrupt level.
*
* Limitations: maximum size of memory we can allocate using this routine
*     is 4k, the size of a page in Linux.
*
* The general game plan is that each page (called a bucket) will only hold
* objects of a given size.  When all of the object on a page are released,
* the page can be returned to the general free pool.  When malloc() is
* called, it looks for the smallest bucket size which will fulfill its
* request, and allocate a piece of memory from that bucket pool.
*
* Each bucket has as its control block a bucket descriptor which keeps
* track of how many objects are in use on that page, and the free list
* for that page.  Like the buckets themselves, bucket descriptors are
* stored on pages requested from get_free_page().  However, unlike buckets,
* pages devoted to bucket descriptor pages are never released back to the
* system.  Fortunately, a system should probably only need 1 or 2 bucket
* descriptor pages, since a page can hold 256 bucket descriptors (which
* corresponds to 1 megabyte worth of bucket pages.)  If the kernel is using
* that much allocated memory, it's probably doing something wrong.  :-)
*
* Note: malloc() and free() both call get_free_page() and free_page()
*     in sections of code where interrupts are turned off, to allow
*     malloc() and free() to be safely called from an interrupt routine.
*     (We will probably need this functionality when networking code,
*     particularily things like NFS, is added to Linux.)  However, this
*     presumes that get_free_page() and free_page() are interrupt-level
*     safe, which they may not be once paging is added.  If this is the
*     case, we will need to modify malloc() to keep a few unused pages
*     "pre-allocated" so that it can safely draw upon those pages if
*      it is called from an interrupt routine.
*
*      Another concern is that get_free_page() should not sleep; if it
*     does, the code is carefully ordered so as to avoid any race
*     conditions.  The catch is that if malloc() is called re-entrantly,
*     there is a chance that unecessary pages will be grabbed from the
*     system.  Except for the pages for the bucket descriptor page, the
*     extra pages will eventually get released back to the system, though,
*     so it isn't all that bad.
*/

#include <linux/kernel.h>
#include <linux/mm.h>
#include <asm/system.h>

struct bucket_desc {     /* 16 bytes */
void               *page;
struct bucket_desc     *next;
void               *freeptr;
unsigned short          refcnt;
unsigned short          bucket_size;
};

struct _bucket_dir {     /* 8 bytes */
int               size;
struct bucket_desc     *chain;
};

/*
* The following is the where we store a pointer to the first bucket
* descriptor for a given size.
*
* If it turns out that the Linux kernel allocates a lot of objects of a
* specific size, then we may want to add that specific size to this list,
* since that will allow the memory to be allocated more efficiently.
* However, since an entire page must be dedicated to each specific size
* on this list, some amount of temperance must be exercised here.
*
* Note that this list *must* be kept in order.
*/
struct _bucket_dir bucket_dir[] = {
{ 16,     (struct bucket_desc *) 0},
{ 32,     (struct bucket_desc *) 0},
{ 64,     (struct bucket_desc *) 0},
{ 128,     (struct bucket_desc *) 0},
{ 256,     (struct bucket_desc *) 0},
{ 512,     (struct bucket_desc *) 0},
{ 1024,     (struct bucket_desc *) 0},
{ 2048, (struct bucket_desc *) 0},
{ 4096, (struct bucket_desc *) 0},
{ 0,    (struct bucket_desc *) 0}};   /* End of list marker */

/*
* This contains a linked list of free bucket descriptor blocks
*/
struct bucket_desc *free_bucket_desc = (struct bucket_desc *) 0;

/*
* This routine initializes a bucket description page.
*/
static inline void init_bucket_desc()
{
struct b
4000
ucket_desc *bdesc, *first;
int     i;

first = bdesc = (struct bucket_desc *) get_free_page();
if (!bdesc)
panic("Out of memory in init_bucket_desc()");
for (i = PAGE_SIZE/sizeof(struct bucket_desc); i > 1; i--) {
bdesc->next = bdesc+1;
bdesc++;
}
/*
* This is done last, to avoid race conditions in case
* get_free_page() sleeps and this routine gets called again....
*/
bdesc->next = free_bucket_desc;
free_bucket_desc = first;
}

void *malloc(unsigned int len)
{
struct _bucket_dir     *bdir;
struct bucket_desc     *bdesc;
void               *retval;

/*
* First we search the bucket_dir to find the right bucket change
* for this request.
*/
for (bdir = bucket_dir; bdir->size; bdir++)
if (bdir->size >= len)
break;
if (!bdir->size) {
printk("malloc called with impossibly large argument (%d)\n",
len);
panic("malloc: bad arg");
}
/*
* Now we search for a bucket descriptor which has free space
*/
cli();     /* Avoid race conditions */
for (bdesc = bdir->chain; bdesc; bdesc = bdesc->next)
if (bdesc->freeptr)
break;
/*
* If we didn't find a bucket with free space, then we'll
* allocate a new one.
*/
if (!bdesc) {
char          *cp;
int          i;

if (!free_bucket_desc)
init_bucket_desc();
bdesc = free_bucket_desc;
free_bucket_desc = bdesc->next;
bdesc->refcnt = 0;
bdesc->bucket_size = bdir->size;
bdesc->page = bdesc->freeptr = (void *) cp = get_free_page();
if (!cp)
panic("Out of memory in kernel malloc()");
/* Set up the chain of free objects */
for (i=PAGE_SIZE/bdir->size; i > 1; i--) {
*((char **) cp) = cp + bdir->size;
cp += bdir->size;
}
*((char **) cp) = 0;
bdesc->next = bdir->chain; /* OK, link it in! */
bdir->chain = bdesc;
}
retval = (void *) bdesc->freeptr;
bdesc->freeptr = *((void **) retval);
bdesc->refcnt++;
sti();     /* OK, we're safe again */
return(retval);
}

/*
* Here is the free routine.  If you know the size of the object that you
* are freeing, then free_s() will use that information to speed up the
* search for the bucket descriptor.
*
* We will #define a macro so that "free(x)" is becomes "free_s(x, 0)"
*/
void free_s(void *obj, int size)
{
void          *page;
struct _bucket_dir     *bdir;
struct bucket_desc     *bdesc, *prev;
bdesc = prev = 0;
/* Calculate what page this object lives in */
page = (void *)  ((unsigned long) obj & 0xfffff000);
/* Now search the buckets looking for that page */
for (bdir = bucket_dir; bdir->size; bdir++) {
prev = 0;
/* If size is zero then this conditional is always false */
if (bdir->size < size)
continue;
for (bdesc = bdir->chain; bdesc; bdesc = bdesc->next) {
if (bdesc->page == page)
goto found;
prev = bdesc;
}
}
panic("Bad address passed to kernel free_s()");
found:
cli(); /* To avoid race conditions */
*((void **)obj) = bdesc->freeptr;
bdesc->freeptr = obj;
bdesc->refcnt--;
if (bdesc->refcnt == 0) {
/*
* We need to make sure that prev is still accurate.  It
* may not be, if someone rudely interrupted us....
*/
if ((prev && (prev->next != bdesc)) ||
(!prev && (bdir->chain != bdesc)))
for (prev = bdir->chain; prev; prev = prev->next)
if (prev->next == bdesc)
break;
if (prev)
prev->next = bdesc->next;
else {
if (bdir->chain != bdesc)
panic("malloc bucket chains corrupted");
bdir->chain = bdesc->next;
}
free_page((unsigned long) bdesc->page);
bdesc->next = free_bucket_desc;
free_bucket_desc = bdesc;
}
sti();
return;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息