说到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(每二个节点)插入到合适它的位置。
本文详细介绍了APR内存池的工作原理,包括环形双向链表的构建方式、内存分配策略及内部节点管理机制等核心内容。
504

被折叠的 条评论
为什么被折叠?



