博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
nginx代码分析-基本结构-ngx_pool_t内存池
阅读量:5987 次
发布时间:2019-06-20

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

hot3.png

自定义内存池作用:

提前分配好一定内存空间, 避免对于小块内存(不超过pagesize-1=4095B)的频繁分配和释放, 而是集中进行分配和释放, 提高效率并大大减少了内存碎片产生的可能性. 而对于大块内存的动态分配, 释放的时间消耗是可以接受的.

一. 主要数据结构

struct ngx_pool_large_s {    ngx_pool_large_t     *next;  // 链表指针    void                 *alloc;  // 指向分配的大块内存, 大于pagesize};typedef struct {    u_char               *last; // 指向已分配数据区最末    u_char               *end;  // 指向当前pool最末, last==end代表分配满    ngx_pool_t           *next; // 指向下一个 ngx_pool_t    ngx_uint_t            failed; //当前pool中不够分配, 调用ngx_palloc_block新加节点, current节点以及所有之后节点都会自加1} ngx_pool_data_t;struct ngx_pool_s {    ngx_pool_data_t       d;    size_t                max; //     ngx_pool_t           *current; // 指向当前分配内存时选择的第1个ngx_pool_t, failed 5次后指向current.d->next    ngx_chain_t          *chain;   // 挂一个chain结构, 由ngx_buf_t串联起来    ngx_pool_large_t     *large;   // 超过, 存放到大数据区    ngx_pool_cleanup_t   *cleanup;  // 挂载一些需要进行额外清理工作的数据, 如文件    ngx_log_t            *log;};typedef struct {    ngx_fd_t              fd;   // 对文件类型数据的封装    u_char               *name;    ngx_log_t            *log;} ngx_pool_cleanup_file_t;

 

二. ngx_pool_t的创建, 销毁, 重置

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log){ // 创建总长度为size大小的ngx_pool    ngx_pool_t  *p;    // 分配size大小的内存    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);  // 最大分配空间4095 B    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_pool_data_t中, 从last指向新分配的开始地址, end指向本节点可分配内存区末尾, last-end的大小来判断当前节点的内存是否够分配请求. next指向下一节点(当前节点满), Failed记录当前节点不能满足分配次数.

max记录当前内存池定义的一次最多分配空间长度(不超过4096B), current指向当前当前的ngx_pool_t节点, chain, large, cleanup目前未分配为空

voidngx_destroy_pool(ngx_pool_t *pool)  // pool上所有数据释放, 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); // 销毁cleanup链表中的data        }    }    for (l = pool->large; l; l = l->next) {        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);        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;        }    }}

三. 在内存池上的内存管理

3.1 正常情况

客户代码调用ngx_palloc, 在pool上分配size大小的内存, 返回起始地址.

void *ngx_palloc(ngx_pool_t *pool, size_t size){    u_char      *m;    ngx_pool_t  *p;    if (size <= pool->max) {        p = pool->current; // 跳到当前待分配pool节点        do {            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); // 内存对齐            if ((size_t) (p->d.end - m) >= size) { // 当前节点剩余空间够分配                p->d.last = m + size;                return m;            }            p = p->d.next; // 跳到pool中下一个节点        } while (p);        // 当前pool中所有节点无法分配size大小的区域        return ngx_palloc_block(pool, size);    }    // 超过该pool定义的最大分配内存空间    return ngx_palloc_large(pool, size);}void *ngx_pnalloc(ngx_pool_t *pool, size_t size)// p->d.last处直接分配, 不做内存对齐

3.2 pool->current节点上不够分配size大小内存

若需要分配内存空间size过大, 调用ngx_palloc_large在ngx_pool_t->large上直接分配.

若当前pool中每个节点都无法分配size大小内存区, 则ngx_palloc_block在pool上新增一个节点, 在其上分配

static void *ngx_palloc_block(ngx_pool_t *pool, size_t size){    u_char      *m;    size_t       psize;    ngx_pool_t  *p, *new, *current;    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); // 64位机器, NGX_ALIGNMENT==8byte对齐    new->d.last = m + size;    current = pool->current;    for (p = current; p->d.next; p = p->d.next) {        if (p->d.failed++ > 4) {            current = p->d.next;        }    }    p->d.next = new; // 节点加到pool的链表尾    pool->current = current ? current : new; // 若current已经指向队尾    return m;}

如果, p->current指向节点中不够分配size, 则新建节点, failed++, 在ngx_palloc_block创建新节点, 在新节点上分配size大小区域

 

3.3 大内存的分配, 释放

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); // 直接malloc内存    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) { // 链表中超过3个指向内存非空large_t结构, 则不遍历到队尾, 放在链表头            break;        }    }    large = ngx_palloc(pool, sizeof(ngx_pool_large_t));    if (large == NULL) {        ngx_free(p);        return NULL;    }    large->alloc = p; // 新分配的large内存放在链表头    large->next = pool->large;    pool->large = large;    return p;}ngx_int_tngx_pfree(ngx_pool_t *pool, void *p){    ngx_pool_large_t  *l;    for (l = pool->large; l; l = l->next) {        if (p == l->alloc) { // 遍历large链表, 找到分配内存p所在后释放            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,                           "free: %p", l->alloc);            ngx_free(l->alloc);            l->alloc = NULL;            return NGX_OK;        }    }    return NGX_DECLINED;}

如图, 对于超过pool->max的空间大小的分配, 每次会先分配size大小的内存, 再挂在ngx_palloc_large_t类型的链表上, 首先从链表头遍历是否有alloc指向null, 超过3个非空, 则新建一个pool_large结构放到队头, 这里不太明白为什么要设计成这样, 是否有根据统计值做过优化, 为什么不直接分配放到队头? ngx_pfree有可能多次释放的都是队尾的, 这样实际还是有很多pool_large被浪费.

,

3.4 ngx_pool_cleanup_t 资源的申请, 注册与销毁

ngx_pool_cleanup_add添加一个cleanup资源供客户代码使用, 在将需要资源挂载到内存池同时, 注册一个cleanup_pt函数地址, 供销毁数据时使用. 如果小内存, 仍然是在pool上分配的, 但对于fd等释放时需要额外操作的资源, 仅仅移动pool上的last指针是不够的,需要额外定义一些自己的清理工作.

ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size){    ngx_pool_cleanup_t  *c;    // 分配一个ngx_pool_cleanup_t结构    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));    if (c == NULL) {        return NULL;    }    // 为ngx_pool_cleanup_t->data分配size大小空间    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; // 新建节点放在pool->cleanup链表头    p->cleanup = c;    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);    return c;}voidngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd){  // 清理p->cleanup链表上的所有挂载文件fd    ngx_pool_cleanup_t       *c;    ngx_pool_cleanup_file_t  *cf; // ngx_pool_cleanup_t->data类型, 实际是对fd的封装    for (c = p->cleanup; c; c = c->next) {        if (c->handler == ngx_pool_cleanup_file) {            cf = c->data;            if (cf->fd == fd) { // 找到对应fd, 调用ngx_pool_cleanup_file清理fd                c->handler(cf);                c->handler = NULL;                return;            }        }    }}voidngx_pool_cleanup_file(void *data){ // 封装ngx_close_file, 关闭data    ngx_pool_cleanup_file_t  *c = data;    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",                   c->fd);    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,                      ngx_close_file_n " \"%s\" failed", c->name);    }}

如图, 客户代码调用ngx_pool_cleanup_add分配一个cleanup结构, 可存储size大小的数据, 挂载在pool->cleanup链表上, 同时再cleanup->hander指向自定义的cleanup资源处理函数. 可主动调用, 如ngx_pool_run_cleanup_file将制定的fd释放; 被动地, 在销毁内存池destroy_pool时会遍历cleanup链表由handler指向的函数地址来处理data域数据.

转载于:https://my.oschina.net/chenzhuo/blog/175510

你可能感兴趣的文章
网络最大流算法—最高标号预流推进HLPP
查看>>
Android 撕衣服(刮刮乐游戏)
查看>>
最后一次谈 VirtualBox的安装方法
查看>>
关于有默认值的字段在用EF做插入操作时的思考
查看>>
Android经常使用自己定义控件(二)
查看>>
Cocos Creator 鼠标事件
查看>>
Geoserver跨域请求设置
查看>>
类声明、类作用域、前向声明、this指针、嵌套类、PIMPL 技法 等
查看>>
neo4j图数据库入门
查看>>
Pmod使用的4种模式
查看>>
内核定时器timer_list
查看>>
VirtualBox - NAT虚拟机访问外网 + Host-Only物理主机虚拟机互访
查看>>
99. Recover Binary Search Tree
查看>>
appium+python自动化32-android_uiautomator定位进阶版
查看>>
《Java 多线程编程核心技术》- 笔记
查看>>
劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(六)...
查看>>
ashx的学习
查看>>
Installing ODIConsole application using weblogic server
查看>>
hp警告Creating default object from empty value 问题的解决方法
查看>>
C#游戏开发中快速的游戏循环
查看>>