内存池(Memery Pool)技术是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。

不仅在用户态应用程序中被广泛使用,同时在Linux内核也被广泛使用,在内核中有不少地方内存分配不允许失败。作为一个在这些情况下确保分配的方式,内核开发者创建了一个已知为内存池(或者是 "mempool" )的抽象,内核中内存池真实地只是相当于后备缓存,它尽力一直保持一个空闲内存列表给紧急时使用,而在通常情况下有内存需求时还是从公共的内存中直接分配,这样的做法虽然有点霸占内存的嫌疑,但是可以从根本上保证关键应用在内存紧张时申请内存仍然能够成功。

下面看下内核内存池的源码,内核内存池的源码在中,实现上非常简洁,描述内存池的结构;

mempool_t在头文件中定义,结构描述如下:

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);
}

函数原型指定内存池可以容纳元素的个数、申请元素的方法、释放元素的方法,以及一个可选的内存源(通常是一个cache),内存池对象创建完成后会自动调用alloc方法从pool_data上分配min_nr个元素用来填充内存池。

内存池的释放函数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_allocmempool_freemempool_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 rmb in 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是对应的,释放对象时先看池中的可用元素是否充足(pool->curr_nr == pool->min_nr),如果不是则将元素对象释放回池中,否则将元素对象还给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);
}

总体上来讲mempool的实现很简约,但是不简单,而且非常轻便易用,这也是内核奥妙之所在。

Linux 内存池【转】的更多相关文章

  1. linux内存池

    在内核中有不少地方内存分配不允许失败. 作为一个在这些情况下确保分配的方式, 内核 开发者创建了一个已知为内存池(或者是 "mempool" )的抽象. 一个内存池真实地只是一 类 ...

  2. Linux编程之内存池的设计与实现(C++98)

    假设服务器的硬件资源"充裕",那么提高服务器性能的一个很直接的方法就是空间换时间,即"浪费"服务器的硬件资源,以换取其运行效率.提升服务器性能的一个重要方法就是 ...

  3. linux下C语言实现的内存池【转】

    转自:http://blog.chinaunix.net/uid-28458801-id-4254501.html 操作系统:ubuntu10.04 前言:     在通信过程中,无法知道将会接收到的 ...

  4. Linux设备驱动程序 之 内存池

    内核中有些地方的内存分配是不允许失败的,为了确保这种情况下的成功分配,内核开发者建立了一种称为内存池的抽象:内存池其实就是某种形式的后备高速缓存,它试图始终保存空闲的内存,以便在紧急状态下使用: me ...

  5. Linux 内核内存池

    内核中经常进行内存的分配和释放.为了便于数据的频繁分配和回收,通常建立一个空闲链表——内存池.当不使用的已分配的内存时,将其放入内存池中,而不是直接释放掉. Linux内核提供了slab层来管理内存的 ...

  6. Linux服务器内存池技术是如何实现的

    Linux服务器内存池技术是如何实现的

  7. Linux简易APR内存池学习笔记(带源码和实例)

    先给个内存池的实现代码,里面带有个应用小例子和画的流程图,方便了解运行原理,代码 GCC 编译可用.可以自己上网下APR源码,参考代码下载链接: http://pan.baidu.com/s/1hq6 ...

  8. linux内存源码分析 - 内存池

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 内存池是用于预先申请一些内存用于备用,当系统内存不足无法从伙伴系统和slab中获取内存时,会从内存池中获取预留的 ...

  9. Linux内存管理原理

    本文以32位机器为准,串讲一些内存管理的知识点. 1. 虚拟地址.物理地址.逻辑地址.线性地址 虚拟地址又叫线性地址.linux没有采用分段机制,所以逻辑地址和虚拟地址(线性地址)(在用户态,内核态逻 ...

随机推荐

  1. Java 动态生成 PDF 文件

    每片文章前来首小诗:   今日夕阳伴薄雾,印着雪墙笑开颜.我心仿佛出窗前,浮在半腰望西天.  --泥沙砖瓦浆木匠 需求: 项目里面有需要java动态生成 PDF 文件,提供下载.今天我找了下有关了,系 ...

  2. Mysql、MongoDB对比和使用场景

    MongoDB: 更高的写入负载 默认情况下,MongoDB更侧重高数据写入性能,而非事务安全,MongoDB很适合业务系统中有大量“低价值”数据的场景.但是应当避免在高事务安全性的系统中使用Mong ...

  3. go sync.once用法

    欢迎关注go语言微信公众号 每日go语言 golang_everyday sync.once可以控制函数只能被调用一次.不能多次重复调用.示例代码: package main import ( &qu ...

  4. solr源码分析之数据导入DataImporter追溯。

    若要搜索的信息都是被存储在数据库里面的,但是solr不能直接搜数据库,所以只有借助Solr组件将要搜索的信息在搜索服务器上进行索引,然后在客户端供客户使用. 1. SolrDispatchFilter ...

  5. Shell 实例:备份最后一天内所有修改过的文件

    在一个"tarball"中(经过 tar 和 gzip 处理过的文件)备份最后 24 小时之内当前目录下所有修改的文件. 程序代码如下: #!/bin/bash BACKUPFIL ...

  6. 操作Linux系统环境变量的几种方法

    一.使用environ指针输出环境变量 代码如下: #include<stdio.h> #include<string.h> #define MAX_INPUT 20 /* 引 ...

  7. MSSQL存储过程应用

    1.原始表inoutinfo 2.现在想输入时间范围和操作类型输出对应的结果 2.1创建存储过程 create proc selecttype@type nvarchar(10),@starttime ...

  8. Oracle em 此网站的安全证书存在问题

    https://www.cnblogs.com/hyz5525/p/4390252.html C:\>emctl status dbconsole Oracle Enterprise Manag ...

  9. 新建 .NET Core 控制台项目

    1. 安装 .NET Core SDK 1.0 参考微软官方网站 https://www.microsoft.com/net/download/windows 2. 打开命令提示符:输入以下代码验证S ...

  10. LINQ 【增、删、改、查】数据绑定

    LINQ,语言集成查询(Language Integrated Query) 是一组用于c#和Visual Basic语言的扩展.它允许编写C#或者Visual Basic代码以查询数据库相同的方式操 ...