nginx 内存池的创建与内存分配

一:基础知识

内存对齐

内存对齐的概念在此不多描述,这里解释nginx中关于调整内存对齐的实现,在nginx使用了ngx_align_ptr 函数来调整指针指向位置,以实现字节对齐,ngx_align_ptr 在ngx_config.h中宏定义如下

#define ngx_align_ptr(p, a)                                                   \
    (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

先理解 m &~ n 的意思,举个例子说明

    int m = 50;
    int n = 4;
    printf("m   &~ n= %d\n",m &~ (n-1));
    printf("m+1 &~ n= %d\n",m+1 &~ (n-1));
    printf("m+2 &~ n= %d\n",m+2 &~ (n-1));
    printf("m+3 &~ n= %d\n",m+3 &~ (n-1));
    printf("m+4 &~ n= %d\n",m+4 &~ (n-1));

打印如下,很明显 从 0~m 中找出最大整除的数字.
在这里插入图片描述
在回过头来理解ngx_align_ptr (p,n),p是指针,n是字节对齐的大小,意思就是从0 ~(指针p+8所指向的地址值)中找到最大整除的值(也是地址值),为了直观的得出结论,假设p的地址是8,n也是8,展开宏就是:

(u_char *) (8) + (8 - 1)) & ~(8 - 1))

结合m &~ n 的结论,返回的是一个指向地址8的u_char指针,假设p的地址不能被8整除,而是7 或者 9呢?
显然,当p地址是7时 返回一个指向8的指针,是9时返回的是一个16的地址,这种情况地址9至地址15的内存浪费了,但是这样可以提升性能。所以从结果来看确实是按8字节对齐了。

所以最终ngx_align_ptr(p,n) 目的是将指针p调整到按n字节对齐的第一个较大地址p1
在这里插入图片描述

关键的宏定义

字节对齐大小

#define NGX_POOL_ALIGNMENT       16

大小块内存的阈值 NGX_MAX_ALLOC_FROM_POOL ,以此值来区分需要分配的是大块的内存还是小块的内存

#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)

ngx_pagesize 在ngx_posix_init.c 赋值的,就是内存分页的大小,源码如下

ngx_pagesize = getpagesize(); 

函数定义在ngx_palloc.h,实现在ngx_palloc.c中

二:内存池的创建

ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log) //创建一个 szie大小的内存

创建完成时各数据的内存分布及指针指向:
在这里插入图片描述

三:从内存池的分配内存

在此区分了大小块内存的分配过程,小块使用ngx_palloc_small 接口,大块使用ngx_palloc_large,区分的条件是pool->max,依据前面所得,就是内存页大小

void *ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 0);
    }
#endif
    return ngx_palloc_large(pool, size);
}
3.1小块内存分配
static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;
    p = pool->current;
    do {
        m = p->d.last;

        if (align) {
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }
        if ((size_t) (p->d.end - m) >= size) {
            p->d.last = m + size;
            return m;
        }
        p = p->d.next;
    } while (p);
    return ngx_palloc_block(pool, size);
}
3.1.1、遍历查找可以分配出去的内存

遍历一个如下图的单向链表,这个链表什么时候增加的节点先不论,后续讨论,
在这里插入图片描述

每次遍历的目的都是为了从当前节点中分配块内存出去,当然前提是有足够的大小,下面分析遍历的逻辑(结合源码):
为了方便说明,引入了2个指针,pstart 和pend,
pstart指向 剩余内存的起始位置,也就是p->d->last,
pend指向剩余内存的结束位置,也就是p->d->end,
在分配时会调用ngx_align_ptr,结合一节“基础知识”,可以知道,是找到pstart与pend之间的第一个按16字节对齐的位置,作为分配的起始位置,也就是源码中的m。(这里其实会浪费一些内存,就是pstart 与 m之间的空间,但是字节对齐后可以提高性能
if ((size_t) (p->d.end - m) >= size) 就是判断剩余空间的大小是否足够,就是下图中的m 与pend 之间的大小是否大于size
如果大于,说明有足够的空间用来分配,则调整剩余内存的起始位置,也就是last指针的位置
在这里插入图片描述

3.1.2、未找到剩余空间比待分配的内存要大的节点
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;
	//这里计算出一个节点的大小
    psize = (size_t) (pool->d.end - (u_char *) pool);

    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }

    new = (ngx_pool_t *) m;

    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;
	//pool->current找到内存池的尾部,并链接新分配的内存
    for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {//提高遍历性能
            pool->current = p->d.next;
        }
    }
    p->d.next = new;
    return m;
}

从系统重新分配一个同样大小的节点出来,然后用ngx_pool_t *指向这个内存地址,并把该节点链接到内存池的尾部,(可以对着上面的图与代码查看,这里就不在给图示了),每次遍历时都会从pool->current 开始遍历,这个指针会根据p->d.failed 的次数来调整位置,每次在内存池中分配不了内存,都会从系统新分配一次内存,这样表示内存池分配失败,p->d.failed+1,当大于4时,则会将pool->current 指向下一个指针,提高遍历性能

3.2 大块内存分配
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;

    p = ngx_alloc(size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

        if (n++ > 3) {
            break;
        }
    }

    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}
3.2.1 从系统分配一块内存
3.2.2 遍历large的链表,找到alloc 为空的的节点(一般是释放的空出来的节点),只找前面4个节点
3.2.3 从内存池中分配一个sizeof(ngx_pool_large_t)的大小(2个指针大小)的内存用来保存ngx_pool_large_t结构体本身,调整大块内存链表位置,将构造的ngx_pool_large_t 放在队首,pool->large指向ngx_pool_large_t

四:最终的内存池结构图

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dai1396734

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值