内存池(Memery Pool)技术是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。
不仅在用户态应用程序中被广泛使用,同时在Linux内核也被广泛使用,在内核中有不少地方内存分配不允许失败。作为一个在这些情况下确保分配的方式,内核开发者创建了一个已知为内存池(或者是 "mempool" )的抽象,内核中内存池真实地只是相当于后备缓存,它尽力一直保持一个空闲内存列表给紧急时使用,而在通常情况下有内存需求时还是从公共的内存中直接分配,这样的做法虽然有点霸占内存的嫌疑,但是可以从根本上保证关键应用在内存紧张时申请内存仍然能够成功。
下面看下内核内存池的源码,内核内存池的源码在<mm/mempool.c>中,实现上非常简洁,描述内存池的结构mempool_t在头文件<linux/mempool.h>中定义,结构描述如下:
- typedef struct mempool_s {
- spinlock_t lock;/*保护内存池的自旋锁*/
- int min_nr;/*内存池中最少可分配的元素数目*/
- int curr_nr;/*尚余可分配的元素数目*/
- void **elements;/*指向元素池的指针*/
- void *pool_data;/*内存源,即池中元素真实的分配处*/
- mempool_alloc_t *alloc; /*分配元素的方法*/
- mempool_free_t *free;/*回收元素的方法*/
- wait_queue_head_t wait;/*被阻塞的等待队列*/
- } mempool_t;
内存池的创建函数mempool_create的函数原型如下:
- mempool_t *mempool_create(int min_nr, mempool_alloc_t*alloc_fn,
- mempool_free_t *free_fn, void *pool_data)
- {
- return mempool_create_node(min_nr,alloc_fn,free_fn, pool_data,-1);
- }
内存池的释放函数mempool_destory函数的原型很简单,应该也能猜到是依次将元素对象从池中移除,再释放给pool_data,最后释放池对象,如下:
- void mempool_destroy(mempool_t*pool)
- {
- while (pool->curr_nr){
- void *element= remove_element(pool);
- pool->free(element, pool->pool_data);
- }
- kfree(pool->elements);
- kfree(pool);
- }
值得注意的是内存池分配和回收对象的函数:mempool_alloc和mempool_free。mempool_alloc的作用是从指定的内存池中申请/获取一个对象,函数原型如下:
- void * mempool_alloc(mempool_t*pool, gfp_t gfp_mask){
- ......
- element = pool->alloc(gfp_temp, pool->pool_data);
- if (likely(element!= NULL))
- return element;
- spin_lock_irqsave(&pool->lock, flags);
- if (likely(pool->curr_nr)){
- element= remove_element(pool);/*从内存池中提取一个对象*/
- spin_unlock_irqrestore(&pool->lock, flags);
- /* paired with rmbin mempool_free(), read comment there*/
- smp_wmb();
- return element;
- }
- ......
- }
函数先是从pool_data中申请元素对象,当从pool_data无法成功申请到时,才会从池中提取对象使用,因此可以发现内核内存池mempool其实是一种后备池,在内存紧张的情况下才会真正从池中获取,这样也就能保证在极端情况下申请对象的成功率,单也不一定总是会成功,因为内存池的大小毕竟是有限的,如果内存池中的对象也用完了,那么进程就只能进入睡眠,也就是被加入到pool->wait的等待队列,等待内存池中有可用的对象时被唤醒,重新尝试从池中申请元素:
- init_wait(&wait);
- prepare_to_wait(&pool->wait,&wait, TASK_UNINTERRUPTIBLE);
- spin_unlock_irqrestore(&pool->lock, flags);
- io_schedule_timeout(5*HZ);
- finish_wait(&pool->wait,&wait);
池回收对象的函数mempool_free的原型如下:
- void mempool_free(void *element, mempool_t *pool)
- {
- if (pool->curr_nr < pool->min_nr) {
- spin_lock_irqsave(&pool->lock, flags);
- if (pool->curr_nr < pool->min_nr) {
- add_element(pool, element);
- spin_unlock_irqrestore(&pool->lock, flags);
- wake_up(&pool->wait);
- return;
- }
- spin_unlock_irqrestore(&pool->lock, flags);
- }
- pool->free(element, pool->pool_data);
- }
此外mempool也提供或者说指定了几对alloc/free函数,及在mempool_create创建池时必须指定的alloc和free函数,分别适用于不同大小或者类型的元素的内存池,具体如下:
- void *mempool_alloc_slab(gfp_t gfp_mask, void*pool_data)
- {
- struct kmem_cache *mem = pool_data;
- return kmem_cache_alloc(mem, gfp_mask);
- }
- void mempool_free_slab(void*element, void*pool_data)
- {
- struct kmem_cache *mem = pool_data;
- kmem_cache_free(mem,element);
- }
- void *mempool_kmalloc(gfp_t gfp_mask, void*pool_data)
- {
- size_t size =(size_t)pool_data;
- return kmalloc(size, gfp_mask);
- }
- void mempool_kfree(void*element, void*pool_data)
- {
- kfree(element);
- }
- void *mempool_alloc_pages(gfp_t gfp_mask, void*pool_data)
- {
- int order= (int)(long)pool_data;
- return alloc_pages(gfp_mask, order);
- }
- void mempool_free_pages(void*element, void*pool_data)
- {
- int order= (int)(long)pool_data;
- __free_pages(element, order);
- }
一、 初始化:
- int __init memory_pool_init(void)
- {
- int i;
- alloc_root = RB_ROOT;
- mutex_init(&alloc_mutex);
- for (i = 0; i < ARRAY_SIZE(mpools); i++) {
- mutex_init(&mpools[i].pool_mutex);
- mpools[i].gpool = NULL;
- }
- return 0;
- }
- Mpools结构体如下,最多能存放8个,存放类型由平台自己决定:
- #define MAX_MEMPOOLS 8
- struct mem_pool mpools[MAX_MEMPOOLS];
- struct mem_pool {
- struct mutex pool_mutex;
- struct gen_pool *gpool;
- unsigned long paddr; //存放的是物理或者虚拟地址都可以。
- unsigned long size; //pool 的size大小。
- unsigned long free; //还有多少空闲部分可用。
- unsigned int id;
- };
- 本平台定义的type如下:
- enum {
- MEMTYPE_NONE = -1,
- MEMTYPE_SMI_KERNEL = 0,
- MEMTYPE_SMI,
- MEMTYPE_EBI0,
- MEMTYPE_EBI1,
- MEMTYPE_MAX,
- };
- 下面函数是和平台相关,其中调用了kernel中的initialize_memory_pool函数,
- 当然自己使用的时候也可用按照这种写法:
- static void __init initialize_mempools(void)
- {
- struct mem_pool *mpool;
- int memtype;
- struct memtype_reserve *mt;
- //保留内存相关信息,其实type为MEMTYPE_EBI0部分才有size,
- 因为平台用的就是EBI1接口的DDR。
- mt = &reserve_info->memtype_reserve_table[0];
- for (memtype = 0; memtype < MEMTYPE_MAX; memtype++, mt++) {
- if (!mt->size)
- continue;
- //依次将平台所用到的保留内存信息保存到mpool中。
- mpool = initialize_memory_pool(mt->start, mt->size, memtype);
- if (!mpool)
- pr_warning("failed to create %s mempool\n",
- memtype_name[memtype]);
- }
- }
- 好了,看公共的函数initialize_memory_pool:
- struct mem_pool *initialize_memory_pool(unsigned long start,
- unsigned long size, int mem_type)
- {
- int id = mem_type;
- //类型不符合或者size小于4k就返回
- if (id >= MAX_MEMPOOLS || size <= PAGE_SIZE || size % PAGE_SIZE)
- return NULL;
- mutex_lock(&mpools[id].pool_mutex);
- mpools[id].paddr = start; //保留内存的虚拟地址,注意是虚拟地址。
- mpools[id].size = size; //能使用的总size
- mpools[id].free = size; //空闲size,一开始肯定和总size一样。
- mpools[id].id = id;
- mutex_unlock(&mpools[id].pool_mutex);
- pr_info("memory pool %d (start %lx size %lx) initialized\n",
- id, start, size);
- return &mpools[id];
- }
- 二、 使用:
- 平台提供了两种接口供我们分配mempool:allocate_contiguous_ebi 和 allocate_contiguous_ebi_nomap, 区别只在于是否map。
- void *allocate_contiguous_ebi(unsigned long size,
- unsigned long align, int cached)
- {
- return allocate_contiguous_memory(size, get_ebi_memtype(),
- align, cached);
- }
- EXPORT_SYMBOL(allocate_contiguous_ebi);
- unsigned long allocate_contiguous_ebi_nomap(unsigned long size,
- unsigned long align)
- {
- return _allocate_contiguous_memory_nomap(size, get_ebi_memtype(),
- align, __builtin_return_address(0));
- }
- EXPORT_SYMBOL(allocate_contiguous_ebi_nomap);
- static int get_ebi_memtype(void)
- {
- /* on 7x30 and 8x55 "EBI1 kernel PMEM" is really on EBI0 */
- if (cpu_is_msm7x30() || cpu_is_msm8x55())
- return MEMTYPE_EBI0;
- //平台返回的是这个。
- return MEMTYPE_EBI1;
- }
- 其实对应地就是调用了kernel的分配连续内存接口,就看allocate_contiguous_memory如何实现。
- void *allocate_contiguous_memory(unsigned long size,
- int mem_type, unsigned long align, int cached)
- {
- //叶框对齐
- unsigned long aligned_size = PFN_ALIGN(size);
- struct mem_pool *mpool;
- mpool = mem_type_to_memory_pool(mem_type);
- if (!mpool)
- return NULL;
- return __alloc(mpool, aligned_size, align, cached,
- __builtin_return_address(0));
- }
- 先看mem_type_to_memory_pool:
- static struct mem_pool *mem_type_to_memory_pool(int mem_type)
- {
- //取得mem_type对应的mpool.
- struct mem_pool *mpool = &mpools[mem_type];
- //这里只有MEMTYPE_EBI1对应的size有赋值,
- 所以其他的mpool都直接返回。
- if (!mpool->size)
- return NULL;
- mutex_lock(&mpool->pool_mutex);
- //初始化gpool
- if (!mpool->gpool)
- mpool->gpool = initialize_gpool(mpool->paddr, mpool->size);
- mutex_unlock(&mpool->pool_mutex);
- if (!mpool->gpool)
- return NULL;
- return mpool;
- }
- static struct gen_pool *initialize_gpool(unsigned long start,
- unsigned long size)
- {
- struct gen_pool *gpool;
- //先创建gpool
- gpool = gen_pool_create(PAGE_SHIFT, -1);
- if (!gpool)
- return NULL;
- //添加gen pool
- if (gen_pool_add(gpool, start, size, -1)) {
- gen_pool_destroy(gpool);
- return NULL;
- }
- return gpool;
- }
- struct gen_pool *gen_pool_create(int min_alloc_order, int nid)
- {
- struct gen_pool *pool;
- //比较简单,分配gen_pool空间。
- pool = kmalloc_node(sizeof(struct gen_pool), GFP_KERNEL, nid);
- if (pool != NULL) {
- spin_lock_init(&pool->lock);
- INIT_LIST_HEAD(&pool->chunks);
- // min_alloc_order为PAGE_SHIFT =12.
- pool->min_alloc_order = min_alloc_order;
- }
- return pool;
- }
- static inline int gen_pool_add(struct gen_pool *pool, unsigned long addr,
- size_t size, int nid)
- {
- return gen_pool_add_virt(pool, addr, -1, size, nid);
- }
- int gen_pool_add_virt(struct gen_pool *pool, unsigned long virt, phys_addr_t phys,
- size_t size, int nid)
- {
- struct gen_pool_chunk *chunk;
- //看意思是一个PAGE_SIZE作为一个bit来计算。
- int nbits = size >> pool->min_alloc_order;
- //nbits都存放在gen_pool_chunk的bits[0]数组中,用bitmap来管理。
- int nbytes = sizeof(struct gen_pool_chunk) +
- (nbits + BITS_PER_BYTE - 1) / BITS_PER_BYTE;
- //分配struct gen_pool_chunk空间。
- if (nbytes <= PAGE_SIZE)
- chunk = kmalloc_node(nbytes, __GFP_ZERO, nid);
- else
- chunk = vmalloc(nbytes);
- if (unlikely(chunk == NULL))
- return -ENOMEM;
- if (nbytes > PAGE_SIZE)
- memset(chunk, 0, nbytes);
- chunk->phys_addr = phys; //保存物理地址,传进来的是-1,说明还没计算出来。
- chunk->start_addr = virt; //其实这个值是虚拟或者物理地址都可以。如果是//物理地址,就调用allocate_contiguous_memory,会ioremap一次。否则使用//_allocate_contiguous_memory_nomap就可以了。
- chunk->end_addr = virt + size; //chuank结束地址。
- atomic_set(&chunk->avail, size); //保存当前chunk有效size到avail中。
- spin_lock(&pool->lock);
- //以rcu的形式添加到pool的chunks列表中。
- list_add_rcu(&chunk->next_chunk, &pool->chunks); spin_unlock(&pool->lock);
- return 0;
- }
- 再看__alloc,要动真格了:
- static void *__alloc(struct mem_pool *mpool, unsigned long size,
- unsigned long align, int cached, void *caller)
- {
- unsigned long paddr;
- void __iomem *vaddr;
- unsigned long aligned_size;
- int log_align = ilog2(align);
- struct alloc *node;
- aligned_size = PFN_ALIGN(size);
- //从gen pool去分配内存。
- paddr = gen_pool_alloc_aligned(mpool->gpool, aligned_size, log_align);
- if (!paddr)
- return NULL;
- node = kmalloc(sizeof(struct alloc), GFP_KERNEL);
- if (!node)
- goto out;
- //这里返回的肯定是物理内存,所以需要ioremap,调用、//_allocate_contiguous_memory_nomap那就不需要了。
- if (cached)
- vaddr = ioremap_cached(paddr, aligned_size);
- else
- vaddr = ioremap(paddr, aligned_size);
- if (!vaddr)
- goto out_kfree;
- node->vaddr = vaddr;
- //保留相对应参数到node节点中。
- node->paddr = paddr;
- node->len = aligned_size;
- node->mpool = mpool;
- node->caller = caller;
- //插入到红黑树中去管理。
- if (add_alloc(node))
- goto out_kfree;
- mpool->free -= aligned_size;
- return vaddr;
- out_kfree:
- if (vaddr)
- iounmap(vaddr);
- kfree(node);
- out:
- gen_pool_free(mpool->gpool, paddr, aligned_size);
- return NULL;
- }
- 分析关键函数gen_pool_alloc_aligned:
- unsigned long gen_pool_alloc_aligned(struct gen_pool *pool, size_t size,
- unsigned alignment_order)
- {
- struct gen_pool_chunk *chunk;
- unsigned long addr = 0, align_mask = 0;
- int order = pool->min_alloc_order;
- int nbits, start_bit = 0, remain;
- #ifndef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG
- BUG_ON(in_nmi());
- #endif
- if (size == 0)
- return 0;
- if (alignment_order > order)
- align_mask = (1 << (alignment_order - order)) - 1;
- //获取当前申请size所对应的nbits数量。
- nbits = (size + (1UL << order) - 1) >> order;
- rcu_read_lock();
- //在当前pool的chunks列表上依次查询
- list_for_each_entry_rcu(chunk, &pool->chunks, next_chunk) {
- unsigned long chunk_size;
- if (size > atomic_read(&chunk->avail))
- continue;
- //本chunk所以拥有的总chunk size.
- chunk_size = (chunk->end_addr - chunk->start_addr) >> order;
- retry:
- //寻找未被使用区域的start bit位置
- start_bit = bitmap_find_next_zero_area_off(chunk->bits, chunk_size,
- 0, nbits, align_mask,
- chunk->start_addr);
- //如果超出chunk size,那么再看下一个chunk。
- if (start_bit >= chunk_size)
- continue;
- //没超出那就设置nbits的大小表示这部分内存已经被使用了
- remain = bitmap_set_ll(chunk->bits, start_bit, nbits);
- if (remain) {
- remain = bitmap_clear_ll(chunk->bits, start_bit,
- nbits - remain);
- BUG_ON(remain);
- goto retry;
- }
- //获取当前申请size对应的address,这里为物理地址。
- addr = chunk->start_addr + ((unsigned long)start_bit << order);
- size = nbits << pool->min_alloc_order;
- //计算还有多少size可以供其他进程申请。
- atomic_sub(size, &chunk->avail);
- break;
- }
- rcu_read_unlock();
- return addr;
- }
- 对于bitmap如何使用,这里就不具体追踪了,看函数名知道大概就可以了。
- 最后,我们看下_allocate_contiguous_memory_nomap,其实和上面的区别在于是否remap.
- unsigned long _allocate_contiguous_memory_nomap(unsigned long size,
- int mem_type, unsigned long align, void *caller)
- {
- unsigned long paddr;
- unsigned long aligned_size;
- struct alloc *node;
- struct mem_pool *mpool;
- int log_align = ilog2(align);
- mpool = mem_type_to_memory_pool(mem_type);
- if (!mpool || !mpool->gpool)
- return 0;
- aligned_size = PFN_ALIGN(size);
- paddr = gen_pool_alloc_aligned(mpool->gpool, aligned_size, log_align);
- if (!paddr)
- return 0;
- node = kmalloc(sizeof(struct alloc), GFP_KERNEL);
- if (!node)
- goto out;
- node->paddr = paddr;
- /* We search the tree using node->vaddr, so set
- * it to something unique even though we don't
- * use it for physical allocation nodes.
- * The virtual and physical address ranges
- * are disjoint, so there won't be any chance of
- * a duplicate node->vaddr value.
- */
- //区别就在于这一步,因为这个函数传进来的就是虚拟地址,所以我们没必要再ioremap了,直接使用。
- node->vaddr = (void *)paddr;
- node->len = aligned_size;
- node->mpool = mpool;
- node->caller = caller;
- if (add_alloc(node))
- goto out_kfree;
- mpool->free -= aligned_size;
- return paddr;
- out_kfree:
- kfree(node);
- out:
- gen_pool_free(mpool->gpool, paddr, aligned_size);
- return 0;
- }
- Mempool还是比较简单的,后续的ION的使用我们就会看到它使用了mempool了。