[APR源码解析]内存池
说到apr的内存池,必须知道如何构建一个环形双向链表。因为apr内存池apr_pool_t的active就是一个环形双向链表。
#define list_insert(node, point) do { \ node->ref = point->ref; \ *node->ref = node; \ node->next = point; \ point->ref = &node->next; \ } while (0)
这个micro的用处,是将point插到node的前面。 假设point前的节点为bpoint。
node->ref = point->ref=&bpoint->next。因此*node->ref=bpoint->next=node,这样就让bpoint的next指向了node.
经过node->next=point 后。就可以得到链表:bpoint->next=node,node->next=point。point->ref=&node->next,node->ref=&bpoint->next。所以很巧妙的是ref,他既能表示指向自己(用**ref获取),用能让上一个节点的next(用*ref获取)指向其他节点。插入时,用他来指向新节点。删除时,用他来指向下一个节点。下面的代码就实现这个。
#define list_remove(node) do { \ *node->ref = node->next; \ node->next->ref = node->ref; \ } while (0)
还有一种特殊情况:目前系统只有一个节点。即bpoint=point。此时就得模拟一个bpoint(bpoint=point)。
bpoint->next = point
point->ref = &bpoint->next
这个代码在apr_pool_create_ex就会出现。
node->next = node; node->ref = &node->next;
有了以上基础,内存池处理就简单了。
1.apr_pool_initialize内存池初始化
主要作用是通过apr_pool_create_ex创建一个apr_pool_t,此pool放在一个node节点中。此node又放在了pool的active中,形成一个active环形双向链表的第一个节点。当然也同时实现了对bpoint的模拟。
2.apr_palloc内存分配
APR_DECLARE(void *) apr_palloc(apr_pool_t *pool, apr_size_t in_size) { apr_memnode_t *active, *node; void *mem; apr_size_t size, free_index; size = APR_ALIGN_DEFAULT(in_size); if (size < in_size) { if (pool->abort_fn) pool->abort_fn(APR_ENOMEM); return NULL; } active = pool->active; /* If the active node has enough bytes left, use it. */ if (size <= node_free_space(active)) { mem = active->first_avail; active->first_avail += size; return mem; } node = active->next; if (size <= node_free_space(node)) { list_remove(node); } else { if ((node = allocator_alloc(pool->allocator, size)) == NULL) { if (pool->abort_fn) pool->abort_fn(APR_ENOMEM); return NULL; } } node->free_index = 0; mem = node->first_avail; node->first_avail += size; list_insert(node, active); pool->active = node; free_index = (APR_ALIGN(active->endp - active->first_avail + 1, BOUNDARY_SIZE) - BOUNDARY_SIZE) >> BOUNDARY_INDEX; active->free_index = (APR_UINT32_TRUNC_CAST)free_index; node = active->next; if (free_index >= node->free_index) return mem; do { node = node->next; } while (free_index < node->free_index); list_remove(active); list_insert(active, node); return mem; }分配的原则是,如果pool的active的第一节点(node1,为了方便,以下第二节点称为node2,第三为node3......)有足够内存则直接返回内存。mem指向内存的首地址。并将node的内存剩余空间扣去size。如果pool的node1不足,则选择node2,如果node2足够则使用node2(此时要将这个节点先拿出来list_remove(node);),否则就用分配子分配一个node。并将node插入到第一个节点中( list_insert(node, active);)。 此时的active就成为第二节点。
active = pool->active; /* If the active node has enough bytes left, use it. */ if (size <= node_free_space(active)) { mem = active->first_avail; active->first_avail += size; return mem; } node = active->next; if (size <= node_free_space(node)) { list_remove(node); } else { if ((node = allocator_alloc(pool->allocator, size)) == NULL) { if (pool->abort_fn) pool->abort_fn(APR_ENOMEM); return NULL; } } node->free_index = 0; mem = node->first_avail; node->first_avail += size; list_insert(node, active); pool->active = node;
为了下次分配效率更高,此时需将active这个环形双向链表做一些调整。确保第二个节点为所有节点剩余空间最多的(即从第二节点开始按剩余空间的大小降序排列)。这样下一次分配空间时,就只要判断以上代码。
降序排列是怎么实现呢?其实只要将每次的node2节点放到适合他的位置就可以,上一次是降序,因为只有node2会改变大小。所以只要将node2放到合适位置去,从第二个节点开始后的链表还是降序。
那如何放node2呢?
第一,获取node2的free_index。
free_index = (APR_ALIGN(active->endp - active->first_avail + 1, BOUNDARY_SIZE) - BOUNDARY_SIZE) >> BOUNDARY_INDEX; active->free_index = (APR_UINT32_TRUNC_CAST)free_index;第二,和一下节点(node3)的free_index比较,如果大于等于node3则,什么都不用变化。如果小于,则找一个合适的位置。
do { node = node->next; } while (free_index < node->free_index);如果node2这个节点的内存大小比任何一个节点的大小都要小,这样怎么办。应该把位置放哪里?这就是apr巧妙的地方,因为active是一个环形链表,所以最后个节点的下一个节点就是node1。代码中有一句 node->free_index = 0;(此时的node就是node1),这一句相信很多人都不知为什么要这样做。其实就是要让以上的循环永远可以终止。因为node2的剩余大小肯定大于0,所以node2这个节点的内存大小比任何一个节点的大小都要小时,其实他会大于第一个节点。这样他就会被插在第一个节点的前面,也就是成为最后一个节点
list_remove(active); list_insert(active, node);
这个代码实现将active(每二个节点)插入到合适它的位置。
推荐阅读
-
java String源码和String常量池的全面解析
-
netty源码解析(4.0)-28 ByteBuf内存池:PooledByteBufAllocator-把一切组装起来
-
netty源码解解析(4.0)-23 ByteBuf内存管理:分配和释放
-
java线程池源码解析
-
并发编程(十三)—— Java 线程池 实现原理与源码深度解析 之 Executors(三)
-
TensorFlow Lite源码解析之二(内存管理)
-
【JDK源码分析】线程池ThreadPoolExecutor原理解析
-
【Java并发编程】21、线程池ThreadPoolExecutor源码解析
-
spark内存管理器--MemoryManager源码解析
-
Flink 源码解析 —— 深度解析 Flink 是如何管理好内存的?