一、内存池原理
平时我们直接所使用的 malloc,new,free,delete 等等 API 申请内存分配,这做缺点在于,由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。
memory pool 是一种内存分配方式,又被称为固定大小区块规划。内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等的内存块留作备用。当有新的内存需要的时候,就直接从内存池中分出一部分内存块,若内存块不够再继续申请新的内存,这样做优势,使得内存分配效率得到提升。

二、内存池源码分析
1.内存池数据结构源码
先看看内存池主要的数据结构,结构体位于include/linux/mempool.h文件中:

typedef struct mempool_s {spinlock_t lock;//防止多处理器并发而引入的锁int min_nr; //elements数组中的成员数量int curr_nr;//当前elements数组中空闲的成员数量void **elements;//用来存放内存成员的二维数组,等于elements[min_nr][内存对象的长度]//内存池与内核缓冲区结合使用的指针(这个指针专门用来指向这种内存对象对应的缓存区的指针)void *pool_data;mempool_alloc_t *alloc;//内存分配函数mempool_free_t *free;//内存释放函数wait_queue_head_t wait;//任务等待队列
} mempool_t;

2.内存池创建函数源码
内核里使用mempool_create()创建一个内存池,使用mempool_destroy()销毁一个内存池,使用mempool_alloc()申请内存和mempool_free()是否内存。mempool_create,函数位于mm/mempool.c文件中:

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,GFP_KERNEL, NUMA_NO_NODE);
}
EXPORT_SYMBOL(mempool_create);/******************
创建一个内存池对象
参数:
min_nr :     为内存池分配的最小内存成员数量
alloc_fn : 用户自定义内存分配函数(可以使用系统定义函数)
free_fn :   用户自定义内存释放函数(可以使用系统定义函数)
pool.data :根据用户自定义内存分配函数所提供的可选私有数据,一般是缓存区指针
gfp_mask : 内存分配掩码
node_id :    内存节点的id
******************/
mempool_t *mempool_create_node(int min_nr, mempool_alloc_t *alloc_fn,mempool_free_t *free_fn, void *pool_data,gfp_t gfp_mask, int node_id)
{mempool_t *pool;//为内存池对象分配内存pool = kzalloc_node(sizeof(*pool), gfp_mask, node_id);if (!pool)return NULL;//初始化内存池if (mempool_init_node(pool, min_nr, alloc_fn, free_fn, pool_data,gfp_mask, node_id)) {kfree(pool);return NULL;}return pool;//返回内存池结构体
}
EXPORT_SYMBOL(mempool_create_node);

mempool_create主要通过mempool_create_node来创建内存池,mempool_create_node首先分配内存池对象的内存后使用mempool_init_node初始化内存池,mempool_init_node:

int mempool_init_node(mempool_t *pool, int min_nr, mempool_alloc_t *alloc_fn,mempool_free_t *free_fn, void *pool_data,gfp_t gfp_mask, int node_id)
{//初始化内存池的相关参数spin_lock_init(&pool->lock);//初始化锁pool->min_nr  = min_nr;pool->pool_data = pool_data;pool->alloc    = alloc_fn;pool->free   = free_fn;init_waitqueue_head(&pool->wait);//初始化等待队列//分配一个长度为min_nr的数组用于存放申请后对象的指针pool->elements = kmalloc_array_node(min_nr, sizeof(void *),gfp_mask, node_id);if (!pool->elements)return -ENOMEM;/** First pre-allocate the guaranteed number of buffers.*///首先保证预分配的缓冲区数量while (pool->curr_nr < pool->min_nr) {void *element;//调用pool->alloc函数min_nr次element = pool->alloc(gfp_mask, pool->pool_data);if (unlikely(!element)) {//如果申请不到element,则直接销毁此内存池mempool_exit(pool);return -ENOMEM;}add_element(pool, element);//添加到elements指针数组中}return 0;
}
EXPORT_SYMBOL(mempool_init_node);

3.内存池销毁函数源码
我们再看看mempool_destroy,mempool_destroy:

//销毁一个内存池
void mempool_destroy(mempool_t *pool)
{if (unlikely(!pool))return;mempool_exit(pool);//释放内存池中的内存块kfree(pool);//释放内存池结构体
}
EXPORT_SYMBOL(mempool_destroy);void mempool_exit(mempool_t *pool)
{while (pool->curr_nr) {void *element = remove_element(pool);//把elements指针数组中的内存移除pool->free(element, pool->pool_data);//释放elements数组中的所有对象}kfree(pool->elements);//销毁elements指针数组pool->elements = NULL;
}
EXPORT_SYMBOL(mempool_exit);

mempool_destroy也很简单,首先调用mempool_exit直接将elements存放的内存依个释放掉再释放elements指针数组结构体,后将mempool_t结构也释放掉。

4.内存池分配内存函数
现在我们看mempool_alloc()函数

//内存池分配对象
void *mempool_alloc(mempool_t *pool, gfp_t gfp_mask)
{void *element;unsigned long flags;wait_queue_entry_t wait;gfp_t gfp_temp;//形参gfp_mask中不能包含_GFP_ZEROVM_WARN_ON_ONCE(gfp_mask & __GFP_ZERO);//如果有__GFP_WAIT标志,则会先阻塞,切换进程//#define might_sleep_if(cond) do { if (cond) might_sleep(); } while (0)might_sleep_if(gfp_mask & __GFP_DIRECT_RECLAIM);gfp_mask |= __GFP_NOMEMALLOC;//不使用预留内存gfp_mask |= __GFP_NORETRY;//分配页时如果失败则返回,不进行重试gfp_mask |= __GFP_NOWARN;//分配失败不提供警告//gfp_mask只保留__GFP_DIRECT_RECLAIM和__GFP_IO标志gfp_temp = gfp_mask & ~(__GFP_DIRECT_RECLAIM|__GFP_IO);repeat_alloc://使用内存池中的alloc函数进行分配对象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();//写内存屏障,保证之前的写操作已经完成/** Update the allocation stack trace as this is more useful* for debugging.*/kmemleak_update_trace(element);//用于debugreturn element;}/** We use gfp mask w/o direct reclaim or IO for the first round.  If* alloc failed with that and @pool was empty, retry immediately.*///这里是内存池中也没有空闲内存对象的时候进行的操作//如果gfp_temp != gfp_maskif (gfp_temp != gfp_mask) {spin_unlock_irqrestore(&pool->lock, flags);gfp_temp = gfp_mask;goto repeat_alloc;//跳到repeat_alloc重新获取一次}/* We must not sleep if !__GFP_DIRECT_RECLAIM *///传入的参数gfp_mask不允许回收的等待,分配不到内存则直接退出if (!(gfp_mask & __GFP_DIRECT_RECLAIM)) {spin_unlock_irqrestore(&pool->lock, flags);return NULL;}/* Let's wait for someone else to return an element to @pool */init_wait(&wait);//初始化wait等待进程//加入到内存池的等待队列中,等待当内存池中有空闲对象或者等待超时prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE);spin_unlock_irqrestore(&pool->lock, flags);/** FIXME: this should be io_schedule().  The timeout is there as a* workaround for some DM problems in 2.6.18.*/io_schedule_timeout(5*HZ);//阻塞等待5秒finish_wait(&pool->wait, &wait);//从内存池的等待队列删除此进程goto repeat_alloc;//跳转到repeat_alloc,重新尝试获取内存对象
}
EXPORT_SYMBOL(mempool_alloc);

当模块从此内存池中获取内存对象时,会调用此函数,此函数优先从伙伴系统或slab缓冲区获取需要的内存对象,当内存不足导致无法获取内存对象时,才会从内存池elements数组中获取,如果elements也没有空闲的内存对象,根据传入的分配标识进行相应的处理,最终会每5秒进行一次重新请求分配。

5.内存池释放内存函数

/ 内存池释放内存对象操作
void mempool_free(void *element, mempool_t *pool)
{unsigned long flags;//传入的对象为空,则直接退出if (unlikely(element == NULL))return;/** Paired with the wmb in mempool_alloc().  The preceding read is* for @element and the following @pool->curr_nr.  This ensures* that the visible value of @pool->curr_nr is from after the* allocation of @element.  This is necessary for fringe cases* where @element was passed to this task without going through* barriers.** For example, assume @p is %NULL at the beginning and one task* performs "p = mempool_alloc(...);" while another task is doing* "while (!p) cpu_relax(); mempool_free(p, ...);".  This function* may end up using curr_nr value which is from before allocation* of @p without the following rmb.*/smp_rmb();//读内存屏障/** For correctness, we need a test which is guaranteed to trigger* if curr_nr + #allocated == min_nr.  Testing curr_nr < min_nr* without locking achieves that and refilling as soon as possible* is desirable.** Because curr_nr visible here is always a value after the* allocation of @element, any task which decremented curr_nr below* min_nr is guaranteed to see curr_nr < min_nr unless curr_nr gets* incremented to min_nr afterwards.  If curr_nr gets incremented* to min_nr after the allocation of @element, the elements* allocated after that are subject to the same guarantee.** Waiters happen iff curr_nr is 0 and the above guarantee also* ensures that there will be frees which return elements to the* pool waking up the waiters.*///如果当前内存池中空闲的内存对象少于内存池中应当保存的内存对象的数量时,优先把释放的对象加入到内存池空闲数组中if (unlikely(pool->curr_nr < pool->min_nr)) {spin_lock_irqsave(&pool->lock, flags);if (likely(pool->curr_nr < pool->min_nr)) {add_element(pool, element);//将用户释放的element重新加到缓存而当中spin_unlock_irqrestore(&pool->lock, flags);wake_up(&pool->wait);//唤醒等待队列,目前已经有人释放内存,可以再次申请这个内存来使用return;}spin_unlock_irqrestore(&pool->lock, flags);}pool->free(element, pool->pool_data);//直接调用释放函数
}
EXPORT_SYMBOL(mempool_free);

mempool_free将空闲内存对象释放到内存池中,当内存池中空闲对象不足时,优先将空闲内存对象放到elements数组中,把mempool填满,否则直接释放掉,让内存返回到伙伴系统或slab缓冲区中。

linux内存管理(十五)-内存池相关推荐

  1. 伙伴系统之伙伴系统概述--Linux内存管理(十五)

    日期 内核版本 架构 作者 GitHub CSDN 2016-09-02 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 博文 ...

  2. linux进程管理内存管理,Linux专业知识四:Linux系统进程管理及查看内存

    本文主讲Linux专业知识之Linux系统进程管理及查看内存的情况,以Redhat RHEL7操作系统为例. 一.进程 程序与进程:程序是静态的(文件),进程是动态的(运行的程序). 进程和线程:一个 ...

  3. 【Linux 内核 内存管理】优化内存屏障 ③ ( 编译器屏障 | 禁止 / 开启内核抢占 与 方法保护临界区 | preempt_disable 禁止内核抢占源码 | 开启内核抢占源码 )

    文章目录 一.禁止 / 开启内核抢占 与 方法保护临界区 二.编译器优化屏障 三.preempt_disable 禁止内核抢占 源码 四.preempt_enable 开启内核抢占 源码 一.禁止 / ...

  4. 第六讲:Obj-C 内存管理4 - 自动释放池

    转:http://tigercat1977.blog.163.com/blog/static/2141561122012111294616203/ 第六讲:Obj-C 内存管理4 - 自动释放池 主要 ...

  5. 属性与内存管理(属性与内存管理都是相互关联的)

    <span style="font-size:18px;"> 属性与内存管理(属性与内存管理都是相互关联的)第一部分一,属性:属性是OC2.0之后出来的新语法,用来取代 ...

  6. Java内存管理:Java内存区域 JVM运行时数据区

    Java内存管理:Java内存区域 JVM运行时数据区 在前面的一些文章了解到javac编译的大体过程.Class文件结构.以及JVM字节码指令. 下面我们详细了解Java内存区域:先说明JVM规范定 ...

  7. 【C 语言必知必会】内存管理、动态分配内存、野指针

    C 语言内存管理.动态分配内存.野指针 文章目录 C 语言内存管理.动态分配内存.野指针 前言: 1.内存分区 1.1 代码区 1.2.1 全局初始化数据区(静态数据区data段) 1.2.2 未初始 ...

  8. JVM自动内存管理机制——Java内存区域(下)

    一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...

  9. C++:内存管理:C++内存管理详解

    C++语言内存管理是指:对系统的分配.创建.使用这一系列操作.在内存管理中,由于是操作系统内存,使用不当会造成很麻烦的后果.本文将从系统内存的分配.创建出发,并且结合例子来说明内存管理不当会造成的结果 ...

  10. Unity 之 Mono内存管理与泄漏 — 内存是手游的硬伤(转)

    WeTest导读 内存是游戏的硬伤,如果没有做好内存的管理问题,游戏极有可能会出现卡顿,闪退等影响用户体验的现象.本文介绍了在腾讯游戏在Unity游戏开发过程中常见的Mono内存管理问题,并介绍了一系 ...

最新文章

  1. sql 基础--mysql 5 (6)
  2. php+redis+两种驱动,redis的php驱动两种方式
  3. rust门卡有什么用_Rust能力养成之(10)用Cargo进行项目管理:扩展 调用与优化
  4. ASP.NET的Page.IsPostBack 属性详细说明(转)
  5. cad怎么把图层英文变成中文_CAD图层管理器昨天是中文的今天怎么变英文 – 手机爱问...
  6. 7 计算机组成原理第五章 中央处理器 数据通路
  7. RTT线程管理篇——RTT时间片
  8. Linux内核精选文章向读者汇报 | 相遇Linux
  9. python怎么取出数字,如何在python中取数字的第n个数字
  10. 大数据Python学习大纲
  11. jmeter录制脚本的步骤(很详细)
  12. 【软件】XPS格式文件怎么打开,用XPSViewer(百度云免费下载链接)
  13. Total和Tellurian签署意向性协议,对Driftwood项目和2.5 mtpa LNG进行股权投资;就增加对Tellurian投资签署普通股购买协议
  14. 常见的虚拟化软件及其特点。
  15. 2019年前端开发工作总结
  16. OSPF多区域配置【eNSP实现】
  17. 跑步减肥的正确姿势与方法 不再担心女生跑步腿会变粗
  18. 计算机一级网址打不开怎么办,路由器设置网址打不开怎么办?
  19. [转|会计学习]资产盘盈、盘亏的会计处理
  20. 如何解决电脑任务栏无故不见了的问题 ?

热门文章

  1. IIS目录权限设置说明
  2. [ARC061E]すぬけ君の地下鉄旅行 / Snuke's Subway Trip
  3. 通过配置hosts.allow和hosts.deny文件允许或禁止ssh或telnet操作
  4. [JS+CSS] - 新浪微博滚动特效[兼容FF,Chrome和IE6,7,8]
  5. JMETER分布式原理
  6. linux系统管理命令使用,Linux系统管理命令使用说明
  7. java 16进制_JAVA十六进制数据接收与传输
  8. 在php中array函数的作用是什么意思,php中的array函数有什么用
  9. 2010计算机知识点总结,2010年全国职称计算机考试:知识点笔记第一章
  10. 今天讲个小故事,Javascript诞生记