GlusterFS之内存池(mem-pool)实现原理及代码详解

最近一直在研究glusterfs的源代码,自己也在上面做了一些小的改动。我最开始研究的是3.2.5这个版本,因为据同行和网上资料显示这个版本目前是最稳定的版本。glusterfs实现比较复杂,具体的设计思想和架构就不详细介绍了,网上有这方面的资料(CSDN博客里面就有很好介绍的文章)。

        研究开源系统的一个好处就是可以充分了解它的实现,如果是看这方面的论文只能了解一些原理性的东西,但是我们真正做项目还需要实际的实现。很多开源系统可能本身不一定就很适合你的系统,但是如果可以改造那么利用它来改造也是很值得划算的。研究开源系统最大的好处就是学习它的优秀的代码,今天这篇博文就是要分享glusterfs里面使用的内存池技术。

        glusterfs实现内存池技术的源文件和头文件分别是mem-pool.c和mem-pool.h,首先看看头文件中内存池对象结构体的定义如下:

[cpp]

  1. struct mem_pool {  
  2. struct list_head  list;//用于管理内存池的标准双向链表
  3. int               hot_count;//正在使用的内存数量计数
  4. int               cold_count;//未使用的内存数量计数
  5.         gf_lock_t         lock;  
  6.         unsigned long     padded_sizeof_type;//带有填充
  7. void             *pool;//内存池开始地址
  8. void             *pool_end;//内存池结束地址
  9. int               real_sizeof_type;//内存池存放对象的真实大小
  10.         uint64_t          alloc_count;//采用alloc分配的次数
  11.         uint64_t          pool_misses;//内出池缺少次数
  12. int               max_alloc;//采用alloc分配的最大次数
  13. int               curr_stdalloc;  
  14. int               max_stdalloc;  
  15. char             *name;  
  16. struct list_head  global_list;//加入到全局的内存池链表
  17. };  

然后我们在来分析几个重要的实现函数,第一个函数就是mem_pool_new_fn,它会新建一个内存池对象,然后按照传递进来的内存的大小和个数分配内存,还要加上一些额外存储内容的内存容量,如存放链表指针的因为这些内存池对象本身是通过通用链表来管理的,还有如标识内存是否在被使用的一个标志等。具体看下面代码的实现,关键代码都有注释:

[cpp]

  1. struct mem_pool *  
  2. mem_pool_new_fn (unsigned long sizeof_type,  
  3.                  unsigned long count, char *name)  
  4. {  
  5. struct mem_pool  *mem_pool = NULL;  
  6.         unsigned long     padded_sizeof_type = 0;  
  7. void             *pool = NULL;  
  8. int               i = 0;  
  9. int               ret = 0;  
  10. struct list_head *list = NULL;  
  11.         jdfs_ctx_t  *ctx = NULL;  
  12. if (!sizeof_type || !count) {  
  13.                 gf_log_callingfn (“mem-pool”, GF_LOG_ERROR, “invalid argument”);  
  14. return NULL;  
  15.         }  
  16.         padded_sizeof_type = sizeof_type + GF_MEM_POOL_PAD_BOUNDARY;//计算大小:对象本身所占内存+链表头+内存池指针+int内存大小(存放in_use变量)
  17.         mem_pool = GF_CALLOC (sizeof (*mem_pool), 1, gf_common_mt_mem_pool);  
  18. if (!mem_pool)  
  19. return NULL;  
  20.         ret = gf_asprintf (&mem_pool->name, “%s:%s”, THIS->name, name);//哪一个xlator分配什么名字内存
  21. if (ret < 0)  
  22. return NULL;  
  23. if (!mem_pool->name) {  
  24.                 GF_FREE (mem_pool);  
  25. return NULL;  
  26.         }  
  27.         LOCK_INIT (&mem_pool->lock);  
  28.         INIT_LIST_HEAD (&mem_pool->list);  
  29.         INIT_LIST_HEAD (&mem_pool->global_list);  
  30.         mem_pool->padded_sizeof_type = padded_sizeof_type;//总的对齐内存大小
  31.         mem_pool->cold_count = count;//数量:刚开始都是冷的(未使用的)
  32.         mem_pool->real_sizeof_type = sizeof_type;//使用内存池对象的真实内存大小
  33.         pool = GF_CALLOC (count, padded_sizeof_type, gf_common_mt_long);//分配count个padded_sizeof_type大小的内存
  34. if (!pool) {  
  35.                 GF_FREE (mem_pool->name);  
  36.                 GF_FREE (mem_pool);  
  37. return NULL;  
  38.         }  
  39. for (i = 0; i < count; i++) {  
  40.                 list = pool + (i * (padded_sizeof_type));//分配每一个内存对象大小到链表
  41.                 INIT_LIST_HEAD (list);  
  42.                 list_add_tail (list, &mem_pool->list);//加入到内存池的链表中去
  43.         }  
  44.         mem_pool->pool = pool;//记录分配的内存区域
  45.         mem_pool->pool_end = pool + (count * (padded_sizeof_type));//内存分配结束的地址
  46. /* add this pool to the global list */
  47.         ctx = jdfs_ctx_get ();  
  48. if (!ctx)  
  49. goto out;  
  50.         list_add (&mem_pool->global_list, &ctx->mempool_list);//加入全局的内存池链表
  51. out:  
  52. return mem_pool;  
  53. }  

如果我们需要使用这种内存池中的内存,那么就从内存池中拿出一个对象(不同对象需要不同的内存池对象保存,每一个内存池对象只保存一种对象的内存结构)的内存,代码实现和注释如下:

[cpp]

  1. void *  
  2. mem_get (struct mem_pool *mem_pool)  
  3. {  
  4. struct list_head *list = NULL;  
  5. void             *ptr = NULL;  
  6. int             *in_use = NULL;  
  7. struct mem_pool **pool_ptr = NULL;  
  8. if (!mem_pool) {  
  9.                 gf_log_callingfn (“mem-pool”, GF_LOG_ERROR, “invalid argument”);  
  10. return NULL;  
  11.         }  
  12.         LOCK (&mem_pool->lock);  
  13.         {  
  14.                 mem_pool->alloc_count++;  
  15. if (mem_pool->cold_count) {//内存池中是否还有未使用的内存对象
  16.                         list = mem_pool->list.next;//取出一个
  17.                         list_del (list);//从链表中脱链
  18.                         mem_pool->hot_count++;  
  19.                         mem_pool->cold_count–;  
  20. if (mem_pool->max_alloc < mem_pool->hot_count)//最大以分配的内存是否小于正在使用的内存数量
  21.                                 mem_pool->max_alloc = mem_pool->hot_count;  
  22.                         ptr = list;  
  23.                         in_use = (ptr + GF_MEM_POOL_LIST_BOUNDARY +  
  24.                                   GF_MEM_POOL_PTR);//分配内存池对象的时候分配了这个区域来保存次块内存是否在使用
  25.                         *in_use = 1;//标记次块内存正在使用
  26. goto fwd_addr_out;  
  27.                 }  
  28. /* This is a problem area. If we’ve run out of
  29.                  * chunks in our slab above, we need to allocate
  30.                  * enough memory to service this request.
  31.                  * The problem is, these individual chunks will fail
  32.                  * the first address range check in __is_member. Now, since
  33.                  * we’re not allocating a full second slab, we wont have
  34.                  * enough info perform the range check in __is_member.
  35.                  *
  36.                  * I am working around this by performing a regular allocation
  37.                  * , just the way the caller would’ve done when not using the
  38.                  * mem-pool. That also means, we’re not padding the size with
  39.                  * the list_head structure because, this will not be added to
  40.                  * the list of chunks that belong to the mem-pool allocated
  41.                  * initially.
  42.                  *
  43.                  * This is the best we can do without adding functionality for
  44.                  * managing multiple slabs. That does not interest us at present
  45.                  * because it is too much work knowing that a better slab
  46.                  * allocator is coming RSN.
  47.                  */
  48.                 mem_pool->pool_misses++;//内存池缺失计数次数加1
  49.                 mem_pool->curr_stdalloc++;//系统标准分配次数加1
  50. if (mem_pool->max_stdalloc < mem_pool->curr_stdalloc)  
  51.                         mem_pool->max_stdalloc = mem_pool->curr_stdalloc;  
  52.                 ptr = GF_CALLOC (1, mem_pool->padded_sizeof_type,  
  53.                                  gf_common_mt_mem_pool);//分配一个内存池对象
  54.                 gf_log_callingfn (“mem-pool”, GF_LOG_DEBUG, “Mem pool is full. “
  55. “Callocing mem”);  
  56. /* Memory coming from the heap need not be transformed from a
  57.                  * chunkhead to a usable pointer since it is not coming from
  58.                  * the pool.
  59.                  */
  60.         }  
  61. fwd_addr_out:  
  62.         pool_ptr = mem_pool_from_ptr (ptr);  
  63.         *pool_ptr = (struct mem_pool *)mem_pool;  
  64.         ptr = mem_pool_chunkhead2ptr (ptr);//得到真正开始的内存
  65.         UNLOCK (&mem_pool->lock);  
  66. return ptr;  
  67. }  

当我们使用完一个内存池中的内存结构以后就需要还给内存池以便被以后的程序使用,达到循环使用的目的。但是在归还以前我们首先需要判断是不是内存池对象的一个成员,判断的结果有三种,分别是:是,不是和错误情况(就是它在内存池的内存范围以内,但是不符合内存池对象的大小),实现如下:

[cpp]

  1. static int
  2. __is_member (struct mem_pool *pool, void *ptr)//判断ptr指向的内存是否是pool的成员
  3. {  
  4. if (!pool || !ptr) {  
  5.                 gf_log_callingfn (“mem-pool”, GF_LOG_ERROR, “invalid argument”);  
  6. return -1;  
  7.         }  
  8. if (ptr < pool->pool || ptr >= pool->pool_end)//ptr如果不再pool开始到结束的范围内就不是
  9. return 0;  
  10. if ((mem_pool_ptr2chunkhead (ptr) – pool->pool)  
  11.             % pool->padded_sizeof_type)//判断是否是一个符合内存块大小的内存对象
  12. return -1;  
  13. return 1;  
  14. }  

那么根据上面函数判断的结果,放入内存对象到内存池对象的函数就会做相应的处理,具体代码如下:

[cpp]

  1. void
  2. mem_put (void *ptr)//将ptr放回到内存池中去
  3. {  
  4. struct list_head *list = NULL;  
  5. int    *in_use = NULL;  
  6. void   *head = NULL;  
  7. struct mem_pool **tmp = NULL;  
  8. struct mem_pool *pool = NULL;  
  9. if (!ptr) {  
  10.                 gf_log_callingfn (“mem-pool”, GF_LOG_ERROR, “invalid argument”);  
  11. return;  
  12.         }  
  13.         list = head = mem_pool_ptr2chunkhead (ptr);//得到链表指针
  14.         tmp = mem_pool_from_ptr (head);  
  15. if (!tmp) {  
  16.                 gf_log_callingfn (“mem-pool”, GF_LOG_ERROR,  
  17. “ptr header is corrupted”);  
  18. return;  
  19.         }  
  20.         pool = *tmp;  
  21. if (!pool) {  
  22.                 gf_log_callingfn (“mem-pool”, GF_LOG_ERROR,  
  23. “mem-pool ptr is NULL”);  
  24. return;  
  25.         }  
  26.         LOCK (&pool->lock);  
  27.         {  
  28. switch (__is_member (pool, ptr))  
  29.                 {  
  30. case 1://是内存池中的内存
  31.                         in_use = (head + GF_MEM_POOL_LIST_BOUNDARY +  
  32.                                   GF_MEM_POOL_PTR);//得到是否正在使用变量
  33. if (!is_mem_chunk_in_use(in_use)) {//正在使用就暂时不回收
  34.                                 gf_log_callingfn (“mem-pool”, GF_LOG_CRITICAL,  
  35. “mem_put called on freed ptr %p of mem “
  36. “pool %p”, ptr, pool);  
  37. break;  
  38.                         }  
  39.                         pool->hot_count–;  
  40.                         pool->cold_count++;  
  41.                         *in_use = 0;  
  42.                         list_add (list, &pool->list);//加入到内存池中的链表
  43. break;  
  44. case -1://错误就终止程序
  45. /* For some reason, the address given is within
  46.                          * the address range of the mem-pool but does not align
  47.                          * with the expected start of a chunk that includes
  48.                          * the list headers also. Sounds like a problem in
  49.                          * layers of clouds up above us. ?
  50.                          */
  51.                         abort ();  
  52. break;  
  53. case 0://不是内存池中的内存直接释放掉
  54. /* The address is outside the range of the mem-pool. We
  55.                          * assume here that this address was allocated at a
  56.                          * point when the mem-pool was out of chunks in mem_get
  57.                          * or the programmer has made a mistake by calling the
  58.                          * wrong de-allocation interface. We do
  59.                          * not have enough info to distinguish between the two
  60.                          * situations.
  61.                          */
  62.                         pool->curr_stdalloc–;//系统分配次数减1
  63.                         GF_FREE (list);  
  64. break;  
  65. default:  
  66. /* log error */
  67. break;  
  68.                 }  
  69.         }  
  70.         UNLOCK (&pool->lock);  
  71. }  

除了上面介绍的,当然还有销毁内存池的功能函数mem_pool_destroy,辅助分配系统内存的一些封装函数等;另外还有一个对于调试有用的功能,那就是记录分配内存的信息,这些东西相对简单,可以自己直接看源码理解。

下班了,今天就到此为止吧!以后准备分享iobuf实现的原理以及源代码!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏雪胖纸的玩蛇日常

老男孩Python全栈开发(92天全)视频教程 自学笔记05

2134
来自专栏技术博文

PHP编程风格规范

本规范由 EasyChen 借鉴 SINA网络应用开发部《C++开发规范》和互动技术部《PHP4开发规范》,以及phpDocument规范 整理出的开发规范。我...

3647
来自专栏码云1024

C#文件

2885
来自专栏任浩强的运维生涯

mongodb高级应用

一、  高级查询 查询操作符 条件操作符:db.collection.find({“field”:{$gt/$lt/$gte/$lte/$eq/$ne:valu...

36610
来自专栏钟绍威的专栏

初学File类

对File类的基本方法的理解 今天刚开始学了File类 一开始看思想编程看得迷迷糊糊的,之后受不了了,直接去看API文档 归纳: File->jav...

19610
来自专栏Python、Flask、Django

PHP htmlspecialchars_decode()函数

1264
来自专栏林德熙的博客

C# 动态加载卸载 DLL

我最近做的软件,需要检测dll或exe是否混淆,需要反射获得类名,这时发现,C#可以加载DLL,但不能卸载DLL。于是在网上找到一个方法,可以动态加载DLL,不...

1531
来自专栏Java编程技术

结合JVM源码谈Java类加载器

之前文章 Java 类加载器揭秘 从Java层面讲解了Java类加载器的原理,这里我们结合JVM源码在稍微深入讲解下。

1021
来自专栏DOTNET

【翻译】MongoDB指南/CRUD操作(一)

【原文地址】https://docs.mongodb.com/manual/ MongoDB CRUD操作(一) 主要内容:CRUD操作简介,插入文档,查询文档...

3429
来自专栏JavaEE

java基础知识02

1、String字符串: 字符串一旦被初始化,就不可以被改变,存放在方法区中的常量池中。用length()方法获取长度。

762

扫码关注云+社区

领取腾讯云代金券