博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
分析Nginx 源码 - ngx_palloc文件总结
阅读量:5919 次
发布时间:2019-06-19

本文共 6825 字,大约阅读时间需要 22 分钟。

关于

palloc是nginx自身实现的一个内存池模块,其遍及整个nginx的源码之中,也是nginx能简洁高效处理各个请求的基础所在。本文先从ngx_allocngx_palloc2个文件来解读内存模块。

ngx_alloc文件

整个ngx_alloc包含了3个函数:ngx_allocngx_callocngx_memalign

其中ngx_allocngx_calloc方法都是利用malloc方法来分配内存,不同的是ngx_calloc方法会在分配后进行初始化工作。
ngx_memalign方法,则是利用memalignposix_memalign方法申请一个内存对齐的内存块。

内存对齐的用处首先是可以提高cpu效率,因为不对齐会导致cpu访问内存时候需要拆分内存块;第二是方便平台的移植。

ngx_palloc模块结构体

上节的ngx_alloc文件是对c语言内存的封装,此后的内存分配都是通过调取其中的三个方法进行的。那么我们先来了解一下ngx_palloc包含的结构体。

ngx_pool_s结构体

struct ngx_pool_s {    ngx_pool_data_t       d;    size_t                max;    ngx_pool_t           *current;    ngx_chain_t          *chain;    ngx_pool_large_t     *large;    ngx_pool_cleanup_t   *cleanup;    ngx_log_t            *log;};

ngx_pool_s结构体是整个内存池的核心结构体。它本身是一个记录表,其中记录了整个内内存池的内存分配信息链的头指针。其中主要的属性分别是dlargecleanup三个属性,这也是我们接下来要了解的三个结构体的指针。

ngx_pool_data_t结构体

typedef struct {    u_char               *last;    u_char               *end;    ngx_pool_t           *next;    ngx_uint_t            failed;} ngx_pool_data_t;

ngx_pool_data_t结构体其实就像是ngx_pool_s结构体的一个详细描述,其中描述了一个内存池的信息,包括当前分配完的内存地址、内存池最后的内存地址、下一个内存池指针以及分配内存失败次数。

ngx_pool_large_s结构体

struct ngx_pool_large_s {    ngx_pool_large_t     *next;    void                 *alloc;};

这个结构体就比较简单,就算一个链表,并包含一个指针指向当前分配的内存块。

ngx_pool_cleanup_s结构体

struct ngx_pool_cleanup_s {    ngx_pool_cleanup_pt   handler;    void                 *data;    ngx_pool_cleanup_t   *next;};

ngx_pool_cleanup_s结构体的功能主要是用来在销毁内存池时,需要处理一下其他的操作来保证内存的正常销毁,避免内存的泄露。因此,在销毁内存期间,会触发这个ngx_pool_cleanup_s的链表,并以此执行销毁函数。

ngx_pool_cleanup_file_t结构体

typedef struct {    ngx_fd_t              fd;    u_char               *name;    ngx_log_t            *log;} ngx_pool_cleanup_file_t;

这个结构体,主要用途就是为了在销毁内存块的时候,能对文件描述符进行关闭等操作。(感觉是这样)

ngx_palloc模块函数

ngx_create_pool方法

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log){    ngx_pool_t  *p;    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);    if (p == NULL) {        return NULL;    }    p->d.last = (u_char *) p + sizeof(ngx_pool_t);    p->d.end = (u_char *) p + size;    p->d.next = NULL;    p->d.failed = 0;    size = size - sizeof(ngx_pool_t);    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;    p->current = p;    p->chain = NULL;    p->large = NULL;    p->cleanup = NULL;    p->log = log;    return p;}

这个方法主要是利用ngx_memalign方法来分配内存块,然后计算出d.lastd.end的2个属性,其他属性都比较容易理解。

ngx_palloc方法以及ngx_pnalloc方法

void *ngx_palloc(ngx_pool_t *pool, size_t size){#if !(NGX_DEBUG_PALLOC)    if (size <= pool->max) {        return ngx_palloc_small(pool, size, 1);    }#endif    return ngx_palloc_large(pool, size);}

该函数理解比较简单,就算判断内存块大小是否大于最大的内存块,若大于则使用大块内存的分配。

ngx_palloc_small方法

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);}

在分配小块内存时,就算不断的寻找是否存在符合条件的内存大小,若存在,则将内存块地址返回,并将d.last往后移动分配的内存大小,即完成了内存分配。若不存在,则利用ngx_palloc_block方法去生成一个新的内存块。

ngx_palloc_block方法

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;    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;}

该函数其实用途在于重新生成一个新的内存池,同时内存池的大小和最初的内存池是相同大小。关键在于,他会对失败大于4次的内存池的当前指针进行移动,这样可以提高之后的内存查找的效率。

ngx_palloc_large方法

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;}

nginx内存池有趣的地方就在于,他们直接可能会互相调用来实现自己的功能,例如当前的方法,首先它回去直接申请一个需要的内存块,之后它需要去查找ngx_pool_large_t的链表,看看有没有某个ngx_pool_large_talloc是为空的,这样就可以将分配好的地址挂载上去。

若不存在,那么就利用small方法申请一个ngx_pool_large_t的节点,然后将其加入ngx_pool_large_t的链表中。

ngx_pmemalign方法

void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment){    void              *p;    ngx_pool_large_t  *large;    p = ngx_memalign(alignment, size, pool->log);    if (p == NULL) {        return NULL;    }    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;}

该方法就是ngx_palloc_large简单暴力版,直接申请ngx_pool_large_t并加入链表中。

ngx_destroy_pool方法

voidngx_destroy_pool(ngx_pool_t *pool){    ngx_pool_t          *p, *n;    ngx_pool_large_t    *l;    ngx_pool_cleanup_t  *c;    for (c = pool->cleanup; c; c = c->next) {        if (c->handler) {            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,                           "run cleanup: %p", c);            c->handler(c->data);        }    }#if (NGX_DEBUG)    ... ...#endif    for (l = pool->large; l; l = l->next) {        if (l->alloc) {            ngx_free(l->alloc);        }    }    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {        ngx_free(p);        if (n == NULL) {            break;        }    }}

ngx_destroy_pool方法的执行流程主要如下:先进行cleanup操作,触发销毁方法、再进行大块内存的销毁、最后销毁销毁内存。销毁方法都是使用ngx_free,其实就算free方法。

ngx_pool_cleanup_add方法

ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size){    ngx_pool_cleanup_t  *c;    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));    if (c == NULL) {        return NULL;    }    if (size) {        c->data = ngx_palloc(p, size);        if (c->data == NULL) {            return NULL;        }    } else {        c->data = NULL;    }    c->handler = NULL;    c->next = p->cleanup;    p->cleanup = c;    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);    return c;}

该方法主要是为内存池添加一个销毁的接口对象,先进行分配内存块,之后再在该内存上初始化变量,变量类似ngx_pool_cleanup_file_t,然后设置handle属性,用于以后内存池销毁。

总结

nginx的内存池功能相对stl的内存池更好理解,也许是代码风格问题导致阅读难度的增加。不过学习了nginx的内存分配后,就可以开始其他的模块的阅读。

相关链接

转载地址:http://edbvx.baihongyu.com/

你可能感兴趣的文章
Ant scp upload文件至linux server(用java调用Ant api)
查看>>
不说技术~那些有文化的人们所说的各大主义,其实百度上都有
查看>>
便携式无线路由器的模式
查看>>
PHP执行insert语句报错“Data too long for column”解决办法
查看>>
黑苹果安装 this is an unknown cpu model 0x3a
查看>>
python面试题大全(一)
查看>>
高度自适应的CSS
查看>>
How to Prevent Cross-Site Scripting Attacks
查看>>
每日英语:Making the Most of Your Lunch Hour
查看>>
怎么利用ultraISO对一个文件夹制作ISO镜像
查看>>
ARM编译器4字节对齐
查看>>
SQL Server 索引的自动维护 <第十三篇>
查看>>
C#项目代码规范
查看>>
python 实现文件下载
查看>>
Windows Server Backup备份Exchange2010
查看>>
C#变量初始化问题:字段初始值无法引用非静态字段、方法或属性
查看>>
SQL Over
查看>>
关于地图坐标和定位偏差
查看>>
弹出框、遮罩层demo
查看>>
八款开源 Android 游戏引擎 (巨好的资源)
查看>>