Nginx源码阅读:ngx_palloc 内存池

简介: Nginx源码阅读:ngx_palloc 内存池

一、内存池

内存池主要是为了解决内存碎片的问题,如果有大量客户端连接,并且每次只占用一点内存,会出现很多的内存碎片。

nginx中为了更好地管理内存,分为大块和小块(chunk),大块用于存储大的内存,小块用于存储比较小的内存。

应用:

nginx中一个tcp连接来了之后,处理该连接的所有数据,内存都由内存池来管理。

二、大块

nginx中大块的结构

typedef struct ngx_pool_large_s  ngx_pool_large_t;
//大块
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;
    void                 *alloc;
};

大块的存储结构图

通过链表将节点串起来

三、chunck(小块)

//ngx_pool_data_t可以理解为是嵌入到ngx_pool_s里面的
typedef struct {
    u_char               *last;//内存块中last指向还没有用过的内存的头部(比如要分配5个字节,那么从last开始分配)
    u_char               *end;//当前内存块中末尾
    ngx_pool_t           *next;//下一块chunck
    ngx_uint_t            failed;
} ngx_pool_data_t;
//内存池
struct ngx_pool_s {
    ngx_pool_data_t       d;//存储一些chunck块的指针信息
    size_t                max;//chunk块的大小
    ngx_pool_t           *current;//当前pool(chunck)查找空余chunck的起始遍历的地址
    ngx_chain_t          *chain;//用于存储buffer的链结构(在内存池中并没有使用)
    ngx_pool_large_t     *large;//大块
    ngx_pool_cleanup_t   *cleanup;//用于清理小块内存
    ngx_log_t            *log;//日志相关
};

只有第一个chunck块(x小块),才会指向大块。后面几个结构中的large都是没有用的。

四、nginx内存池的结构图

五、源码阅读

1、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);//分配一块根据size大小,并以16字节对齐(是要16字节的整数倍)的内存。
    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;//不超过(uintptr_t)-1的大小,也就是uintptr_t类型的最大值
    p->current = p;//指向当前的内存池
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;
    return p;
}

2、ngx_destroy_pool

销毁内存池

void
ngx_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) {//遍历chunck,并调用当前chunck的清理chunck内存的回调函数
        if (c->handler) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "run cleanup: %p", c);
            c->handler(c->data);
        }
    }
    //清理大块
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }
    //释放每个chunck的头部
    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);
        if (n == NULL) {
            break;
        }
    }
}

3、ngx_reset_pool

重置内存池

1.释放所有大块

2.将所有chunk中的last指针重置,也就是放在头部的后面。不需要清空chunck里面的数据,使用的时候直接覆盖就行了

void
ngx_reset_pool(ngx_pool_t *pool)
{
    ngx_pool_t        *p;
    ngx_pool_large_t  *l;
    //释放所有的大块内存(但是,大块的链表节点依然还在,因为它存储在chunck里面)
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }
    //将chunck中last指针初始化,chunck内存中的数据不需要清理,因为,可以直接覆盖
    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        p->d.failed = 0;
    }
    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

4、ngx_palloc

从内存池中取一块来进行分配。

如果小于chunck可以分配的最大大小,那么就分配到chunck中(小块)

如果大于chunck可以分配的最大大小,那么就分配到大块中

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

5、ngx_pnalloc

和ngx_palloc类似,只是,不采用内存对齐

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

6、ngx_palloc_small

在chunck(小块)中分配一块内存

遍历chunck链,如果找到一块可以存放的空间,就返回可分配的内存空间的起始地址

如果找不到,就创建一个新的chunck

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) {//如果使用内存对齐,那么下面就执行对齐。比如按照4个字节对齐,内存已经使用了14字节,那么都当作已经用完了16个字节,从这后面再开始使用
            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;//如果当前chunck没有内存可以供当前大小内存分配,就查找下一个chunck
    } while (p);
    return ngx_palloc_block(pool, size);//遍历完所有chunck都没有可以分配的,那么就新创建一个chunck
}

7、ngx_palloc_block

创建一块新的chunk内存空间,也就是在ngx_palloc_small的chunck链中没找到可以分配的空间,那么就创建一块。

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);//分配一块psize大小的内存
    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) {//如果failed > 4, 就漏过这个chunck(也就是说每个块,在查找块的时候只允许失败4次,失败4次后,让current指向该节点的下一个)
            pool->current = p->d.next;
        }
    }
    //链表尾部用来存新的chunck
    p->d.next = new;
    return m;
}

8、ngx_palloc_large

分配一个大块节点(节点存到(chunck)小块里面),通过链表的方式,串起来。

然后节点上的指针,指向大块,通过这种方式,将大块组织起来

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) {//如果查找3次还没有找到空的节点,那么就直接break,然后采用头插法
            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;
}

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

10、ngx_pfree

释放内存池,小块不会被释放,只有大块释放了

ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;
    for (l = pool->large; l; l = l->next) {//遍历链表,释放大块
        if (p == l->alloc) {
            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;
}

大块可以通过ngx_pfree来释放内存,那么小块呢?

一个tcp连接,就会创建内存池,小块在tcp断开连接时候,才会释放,就是ngx_destory_pool的时候

11、ngx_pcalloc

从内存池中分配一块内存,并初始化为0

//分配内存,并初始化为0
void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
    void *p;
    p = ngx_palloc(pool, size);
    if (p) {
        ngx_memzero(p, size);
    }
    return p;
}
相关文章
|
10月前
|
应用服务中间件 Linux 网络安全
CentOS 7.4源码编译nginx1.12 并且隐藏nginx的版本
CentOS 7.4源码编译nginx1.12 并且隐藏nginx的版本
149 0
|
6月前
|
负载均衡 网络协议 应用服务中间件
web群集--rocky9.2源码部署nginx1.24的详细过程
Nginx 是一款由 Igor Sysoev 开发的开源高性能 HTTP 服务器和反向代理服务器,自 2004 年发布以来,以其高效、稳定和灵活的特点迅速成为许多网站和应用的首选。本文详细介绍了 Nginx 的核心概念、工作原理及常见使用场景,涵盖高并发处理、反向代理、负载均衡、低内存占用等特点,并提供了安装配置教程,适合开发者参考学习。
114 1
|
7月前
|
存储 缓存 编译器
Linux源码阅读笔记06-RCU机制和内存优化屏障
Linux源码阅读笔记06-RCU机制和内存优化屏障
|
7月前
|
应用服务中间件 Linux nginx
在CentOS上使用源码包安装Nginx、以及手动启动Nginx的步骤过程
这篇文章介绍了在CentOS系统上使用Nginx源码包进行安装和配置的详细步骤,包括源码包的获取、解压、配置、编译、安装、启动验证以及注意事项。
561 0
在CentOS上使用源码包安装Nginx、以及手动启动Nginx的步骤过程
|
10月前
|
存储 NoSQL 算法
Redis源码、面试指南(2)内存编码数据结构(下)
Redis源码、面试指南(2)内存编码数据结构
84 4
|
10月前
|
前端开发 应用服务中间件 网络安全
nginx配置SSL证书配置https访问网站 超详细(附加配置源码+图文配置教程)
nginx配置SSL证书配置https访问网站 超详细(附加配置源码+图文配置教程)
934 1
|
10月前
|
消息中间件 Java 关系型数据库
JAVA云HIS医院管理系统源码、基于Angular+Nginx+ Java+Spring,SpringBoot+ MySQL + MyCat
JAVA云HIS医院管理系统 常规模版包括门诊管理、住院管理、药房管理、药库管理、院长查询、电子处方、物资管理、媒体管理等,为医院管理提供更有力的保障。 HIS系统以财务信息、病人信息和物资信息为主线,通过对信息的收集、存储、传递、统计、分析、综合查询、报表输出和信息共享,及时为医院领导及各部门管理人员提供全面、准确的各种数据。
170 1
|
10月前
|
存储 NoSQL API
Redis源码、面试指南(2)内存编码数据结构(上)
Redis源码、面试指南(2)内存编码数据结构
90 0
|
4月前
|
缓存 应用服务中间件 网络安全
Nginx中配置HTTP2协议的方法
Nginx中配置HTTP2协议的方法
293 7
|
14天前
|
应用服务中间件 nginx
Nginx进程配置指令详解
Nginx进程配置指令主要包括:`worker_processes`设置工作进程数;`worker_cpu_affinity`绑定CPU核心;`worker_rlimit_nofile`设置最大文件描述符数量;`worker_priority`设置进程优先级;`worker_connections`设置最大连接数;`daemon`控制守护进程模式;`master_process`启用主进程模式;`pid`设置PID文件路径;`user`指定用户和组;`error_log`配置错误日志。这些指令在`nginx.conf`中配置,用于优化和控制Nginx的运行行为。
46 10