genalloc — 通用内存分配器

看ion代码时看到genalloc的代码,网上搜索了一下。

genalloc 是 linux 内核提供的通用内存分配器,这个分配器为独立于内核以外的内存块提供分配方法;其原理采用bitmap方式管理该块内存,分配释放的最小单位由创建函数的参数min_alloc_order决定:

struct gen_pool *gen_pool_create(int min_alloc_order, int nid)

ion carveout heap使用12(4kB),

struct ion_heap *ion_carveout_heap_create(struct ion_platform_heap *heap_data)
{
...

 carveout_heap->pool = gen_pool_create(12, -1);

...

}
分配方式比较简单,就是从前往后搜索bitmap,找到符合要求size的区域就返回,没有碎片消除功能。debug信息也比较少,这方面值可根据需要添加一点;

 

arm架构使用genalloc管理ARM TCM (Tightly-Coupled Memory),就是SOC片内的容量不大的RAM(Some ARM SoC:s have a so-called TCM (Tightly-Coupled Memory). This is usually just a few (4-64) KiB of RAM inside the ARM processor.),具体可参考内核文档:Documentation/arm/tcm.txt

 

关于tcm,搜索了几篇文章:

http://blog.chinaunix.net/uid-20746501-id-1878552.html

TCM是一个固定大小的RAM,紧密地耦合至处理器内核,提供与cache相当的性能,相比于cache的优点是,程序代码可以精确地控制什么函数或代码放在那儿(RAM)。当然TCM永远不会被踢出主存储器,因此,他会有一个被用户预设的性能,而不是象cache那样是统计特性的性能提高。
TCM对于以下几种情况的代码是非常有用、也是需要的:可预见的实时处理(中断处理)、时间可预见(加密算法)、避免cache分析(加密算法)、或者只是要求高性能的代码(编解码功能)。随着cache大小的增加以及总线性能的规模,TCM将会变得越来越不重要,但是他提供了一个让你权衡的机会
那么,哪一个更好呢?他取决于你的应用。Cache是一个通用目的的加速器,他会加速你的所有代码,而不依赖于存储方式。TCM只会加速你有意放入TCM的代码,其余的其他代码只能通过cache加速。Cache是一个通用目的解决方案,TCM在某些特殊情况下是非常有用的。假如你不认为需要TCM的话,那么你可能就不需要了,转而加大你的cache,从而加速运行于内核上的所有软件代码

=====================

genalloc 是 linux 内核提供的通用内存分配器,源码位于 lib/genalloc.c。这个分配器为独立于内核以外的内存块提供分配方法,采用的是最先适配原则,android 最新的 ION 内存管理器对 ION_HEAP_TYPE_CARVEOUT 类型的内存就是采用的这个分配器。

1、基础数据结构
首先看下分配器用到的几个数据结构,struct gen_pool 用来描述一个内存池:
[cpp]  
struct gen_pool {  
    rwlock_t lock;             /* 链表读写锁 */  
    struct list_head chunks;   /* 内存池中内存块的链表 */  
    int min_alloc_order;       /* 内存池最小分配单元的阶数,大小为 2^min_alloc_order */  
};  
在使用的时候需要向内存池中加入内存块,一个内存块即一大块连续的物理内存,用 struct gen_pool_chunk 来描述:
[cpp]  
struct gen_pool_chunk {  
    spinlock_t lock;              /* 操作内存块时用到的自旋锁 */  
    struct list_head next_chunk;  /* 加入内存池的节点 */  
    unsigned long start_addr;     /* 内存块的起始地址 */  
    unsigned long end_addr;       /* 内存块的结束地址 */  
    unsigned long bits[0];        /* 内存块的位图 */  
};  
2、函数接口及调用方法
genalloc 用到的函数接口有下面几个:
[cpp]  
/* 创建一个内存池,主要工作是完成 struct gen_pool 的初始化 */  
struct gen_pool *gen_pool_create(int min_alloc_order, int nid);  
例 如 gen_pool_create(12, -1); 那 么 分 配 的 最 小 单员 为 2 的 12次 方 ,为  4K

/* 向内存池中加入内存块,addr 为起始地址,size 为大小 */  
int gen_pool_add(struct gen_pool *pool, unsigned long addr, size_t size, int nid);  
例 如 :
PGProtFlags = PGPROT_WC ( PAGE_KERNEL );
pool_start = __vmalloc ( POOL_SIZE , GFP_KERNEL | __GFP_HIGHMEM   PGProtFlags );
gen_pool_add ( _pool_ ,    pool_start , POOL_SIZE , - 1 );

/* 销毁一个内存池 */  
void gen_pool_destroy( struct gen_pool *pool);  
/* 内存池分配内存的函数 */  
unsigned long gen_pool_alloc( struct gen_pool *pool, size_t size);  
/* 内存池释放内存的函数 */  
void gen_pool_free(struct gen_pool *pool, unsigned long addr, size_t size);  
对通用内存分配器的一般使用方法如下:
[cpp]  
/* 初始化内存池,需要创建以及加入内存块,参数为:起始地址、大小、最小分配阶数 */  
static void *mm_init(uint32_t addr, uint32_t size, uint32_t order)  
{  
    struct gen_pool *pool;  
  
    pool = gen_pool_create(order, 0);  
    if (pool == NULL) {  
        return NULL;  
    }  
  
    if (gen_pool_add(pool, addr, size, 0) != 0) {  
        gen_pool_destroy(pool);  
  
        return NULL;  
    }  
  
    return pool;  
}  
  
/* 销毁内存池 */  
static void mm_exit(void *handle)  
{  
    gen_pool_destroy(handle);  
}  
  
/* 分配函数 */  
static uint32_t mm_alloc(void *handle, uint32_t size)  
{  
    return gen_pool_alloc(handle, size);  
}  
  
/* 释放函数 */  
static void mm_free(void *handle, uint32_t addr, uint32_t size)  
{  
    return gen_pool_free(handle, addr, size);  
}  
  
/* 提供给上一级内存管理器调用 */  
struct xxx_mem_ops mm_ops = {  
    .init = mm_init,  
    .exit = mm_exit,  
    .alloc = mm_alloc,  
    .free = mm_free,  
};  
3、分配函数解析
genalloc 通过 gen_pool_alloc 函数来分配内存,下面我们分析一下这个函数的代码:
[cpp]  
unsigned long gen_pool_alloc(struct gen_pool *pool, size_t size)  
{  
    struct list_head *_chunk;  
    struct gen_pool_chunk *chunk;  
    unsigned long addr, flags;  
    int order = pool->min_alloc_order;  
    int nbits, bit, start_bit, end_bit;  
  
    if (size == 0)  
        return 0;  
  
    nbits = (size + (1UL << order) - 1) >> order;  /* 计算申请的内存需要几个连续的最小单元 */  
  
    read_lock(&pool->lock);  
    list_for_each(_chunk, &pool->chunks) {         /* 遍历内存池 */  
        chunk = list_entry(_chunk, struct gen_pool_chunk, next_chunk);  
  
        end_bit = (chunk->end_addr - chunk->start_addr) >> order;        /* 计算当前内存池长度 */  
        end_bit -= nbits + 1;  
  
        spin_lock_irqsave(&chunk->lock, flags);  
        bit = -1;  
        while (bit + 1 < end_bit) {  /* 循环查找最先适配的内存区 */  
            bit = find_next_zero_bit(chunk->bits, end_bit, bit + 1);     /* 寻找为0的bit */  
            if (bit >= end_bit)      /* 循环结束 */  
                break;  
  
            start_bit = bit;         /* 起始位置 */  
            if (nbits > 1) {         /* 如果申请的内存大于一个最小单元,查找连续的nbits个单元 */  
                bit = find_next_bit(chunk->bits, bit + nbits,bit + 1);  
                if (bit - start_bit < nbits)  
                    continue;  
            }  
  
            addr = chunk->start_addr + ((unsigned long)start_bit << order);  /* 计算申请的内存的起始地址 */  
            while (nbits--)  
                __set_bit(start_bit++, chunk->bits);  /* 将申请到的单元全部标记为已用 */  
            spin_unlock_irqrestore(&chunk->lock, flags);  
            read_unlock(&pool->lock);  
            return addr;  
        }  
        spin_unlock_irqrestore(&chunk->lock, flags);  
    }  
    read_unlock(&pool->lock);  
    return 0;  
}  
因为是用的最先适配原则,所以逻辑比较简单,我们也可以根据自己的需求实现最适合分配器以及伙伴分配器。

你可能感兴趣的:(genalloc — 通用内存分配器)