127.nginx内存池创建和重置函数

127.nginx内存池创建和重置函数

#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)//能从内存池中分配的最大的内存

1.小块大块内存分界

#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)//默认池子大小
#define NGX_POOL_ALIGNMENT       16//内存分配的字节对齐数
#define NGX_MIN_POOL_SIZE       ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)),  NGX_POOL_ALIGNMENT)
//内存池最小的大小, 把ngx大小调整到NGX_POOL_ALIGNMENT倍数
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);//创建内存池
void ngx_destroy_pool(ngx_pool_t *pool);//销毁内存池
void ngx_reset_pool(ngx_pool_t *pool);//重置内存池

void *ngx_palloc(ngx_pool_t *pool, size_t size);//考虑内存对齐分配内存
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);//不考虑内存对齐分配内存
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);//考虑分配内存并初始化清零
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);//释放内存
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);//NGX_POOL_ALIGNMENT内存对齐字节数16   可以根据不同系统平台定义的宏调用不同系统API 根据用户指定大小开辟内存池
    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;//可用空间小于4096(1)就用size,大的话用一个页面,大块内存小块内存分界面   小块内存最大值

    p->current = p;//起始地址
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}
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;//日志
};

typedef struct 
{
    u_char               *last;//不是所有1024个字节都可以数据,内存池还有个头信息  last指向开辟内存除了内存头信息以外的内存的起始地址
    u_char               *end;//end = p + size指向内存池末尾
    ngx_pool_t           *next;//nexy刚开始为NULL
    ngx_uint_t            failed;//为0,记录内存分配失败次数
} ngx_pool_data_t;

内存池创建流程

1.定义了一个指向 ngx_pool_t 类型的指针变量 p

ngx_pool_t 是保存内存池头信息

2.ngx_pool_t 用来存储通过 ngx_memalign 函数创建的内存池的起始地址。根据不同系统平台定义的宏调用不同系统API,根据用户指定大小开辟内存池

3.头信息初始值设置 size是可以使用的内存块的大小,总大小减去内存头信息

p->d.last = (u_char *) p + sizeof(ngx_pool_t);//p->d.last指向待分配内存池的第一个位置
p->d.end = (u_char *) p + size;//p->d.end指向内存池的最后一个chunk块位置
p->d.next = NULL;//下一个chunk块地址
p->d.failed = 0;

4.内存块属性设置

size = size - sizeof(ngx_pool_t);//可以使用的内存快的个数,总大小减去内存头信息
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;//可用空间小于4096(1)就用size,大的话用一个页面,大块内存小块内存分界面   小块内存最大值

5.内存池初始化

p->current = p;//起始地址  指向哪个内存池从哪个内存池开始分配
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;

很多模块想使用内存都会调用ngx_create_pool。

void *
ngx_alloc(size_t size, ngx_log_t *log)
{
    void  *p;

    p = malloc(size);
    if (p == NULL) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "malloc(%uz) failed", size);
    }

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);

    return p;
}
#if (NGX_HAVE_POSIX_MEMALIGN)

void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
    void  *p;
    int    err;

    err = posix_memalign(&p, alignment, size);

    if (err) {
        ngx_log_error(NGX_LOG_EMERG, log, err,
                      "posix_memalign(%uz, %uz) failed", alignment, size);
        p = NULL;
    }

    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
                   "posix_memalign: %p:%uz @%uz", p, size, alignment);

    return p;
}

#elif (NGX_HAVE_MEMALIGN)

void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
    void  *p;

    p = memalign(alignment, size);
    if (p == NULL) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "memalign(%uz, %uz) failed", alignment, size);
    }

    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
                   "memalign: %p:%uz @%uz", p, size, alignment);

    return p;
}

#endif

如果定义NGX_HAVE_POSIX_MEMALIGN宏,调用系统API,开辟内存时根据传入的alignment进行内存对齐,按照16字节进行

第一个内存池有头信息,包含信息比较详细,last指向可用内存起始地址,end指向可用末尾内存地址

void *
ngx_palloc(ngx_pool_t *pool, size_t size)//pool指向入口块地址,size是索要申请内存的大小
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) //小于等于1个页面,小内存块分配块
    {
        return ngx_palloc_small(pool, size, 1);
    }
#endif//大于1个页面,大内存块分配块

    return ngx_palloc_large(pool, size);
}


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

两个函数区别在参数传递,ngx_palloc考虑内存对齐,ngx_pnalloc不考虑内存对齐,max不可能超过一个页面大小,

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;//current指向哪个内存池就向哪个内存池进行分配

    do
    {
        m = p->d.last;//m指向可分配内存块的首地址

        if (align)
        {
            //#define NGX_ALIGNMENT   sizeof(unsigned long)    /* platform word */
            m = ngx_align_ptr(m, NGX_ALIGNMENT);//指针调整成平台相关的unsigned long类型的整数倍 
        }

        if ((size_t) (p->d.end - m) >= size) //可用分配的空间(内存池空闲空间)是max,内存池空闲空间>=申请的内存空间
        {
            p->d.last = m + size;//做指针偏移,效率高

            return m;
        }

        p = p->d.next;

    } while (p);

    return ngx_palloc_block(pool, size);
}

上面是小块内存分配函数

小块内存分配流程

第一种情况:当前内存块个数足够

1.指向当前内存池

p = pool->current;//current指向哪个内存池就向哪个内存池进行分配

2.m指向可分配内存块的首地址

m = p->d.last;//m指向可分配内存块的首地址

3.考虑内存对齐

m = ngx_align_ptr(m, NGX_ALIGNMENT);//指针调整成平台相关的unsigned long类型的整数倍 

4.判断内存池空闲空间>=申请的内存空间,分配内存

if ((size_t) (p->d.end - m) >= size) //可用分配的空间(内存池空闲空间)是max,内存池空闲空间>=申请的内存空间
{
    p->d.last = m + size;//做指针偏移,效率高

    return m;
}

第二种情况:分配过内存,当前内存块个数不够

当前未使用内存块空间不够,进入下一个内存块

p = p->d.next;
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);//和前面分配的内存块一样大
    
    //NGX_POOL_ALIGNMENT和平台相关,开辟大小psize
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL)
    {
        return NULL;
    }
    
    //new指向第二个内存池
    new = (ngx_pool_t *) m;
    
    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;
    
    //m指向新开辟内存起始地址
    m += sizeof(ngx_pool_data_t);//整个内存块管理相关的数据不需要了,只存在第一里面就可以了
    m = ngx_align_ptr(m, NGX_ALIGNMENT);//将m调整成特定平台相关的能被8整除的地址上
    new->d.last = m + size;//内存分配后的尾部赋值给last

    for (p = pool->current; p->d.next; p = p->d.next) //p = pool->current;还是指向当前内存池。p->d.next; 
    {
        if (p->d.failed++ > 4) //多次分配,都失败,说明剩余内存相当小  记录内存分配失败次数
        {
            pool->current = p->d.next;//指向下一个内存池
        }
    }

    p->d.next = new;

    return m;
}

都是从pool->current指向的内存池开始分配

1.计算前面内存块大小

psize = (size_t) (pool->d.end - (u_char *) pool);//和前面分配的内存块一样大

2.开辟和前面一样大尺寸的内存块

//NGX_POOL_ALIGNMENT和平台相关,开辟大小psize
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);

3.内存池头信息初始值设置

//new指向第二个内存池
new = (ngx_pool_t *) m;

new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;
//m指向新开辟内存起始地址
m += sizeof(ngx_pool_data_t);//整个内存块管理相关的数据不需要了,只存在第一里面就可以了
m = ngx_align_ptr(m, NGX_ALIGNMENT);//将m调整成特定平台相关的能被8整除的地址上
new->d.last = m + size;//内存分配后的尾部赋值给last

4.前面内存块空间不够,在当前内存块分配内存,将上个内存块和当前内存块连接起来

for (p = pool->current; p->d.next; p = p->d.next) //p = pool->current;还是指向当前内存池。p->d.next; 
{
    if (p->d.failed++ > 4) //多次分配,都失败,说明剩余内存相当小      记录内存分配失败次数
    {
        pool->current = p->d.next;//指向下一个内存池
    }
}

p->d.next = new;

5.当前内存块分配如果有空间就可以继续分配

6.连续进行内存申请,申请的内存比较大(每次遍历当前小块内存发现,都获取不到形影大小的内存),重新开辟小块内存内存池,进行for循环,failed++

return ngx_palloc_block(pool, size);
if (p->d.failed++ > 4) //多次分配,都失败,说明剩余内存相当小
{
	pool->current = p->d.next;//指向下一个内存池
}

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);//直接调用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) 
        {
            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;
}
typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s 
{
    ngx_pool_large_t     *next; //链表  连接大块内存的管理部分
    void                 *alloc;//保存大块内存起始地址
};

大块内存池创建流程

1.直接调用malloc创建指定大小的大块内存

p = ngx_alloc(size, pool->log);//直接调用malloc

2.先看原来大块内存内存头,哪个内存头并没有记录大块内存(被释放过了),直接把大块内存写到相应的alloc中,如果遍历超过3次直接放弃(希望高效率)直接充分分配

for (large = pool->large; large; large = large->next) //刚开始 large为空,for循环体不执行
{
	if (large->alloc == NULL)
	{
		large->alloc = p;
		return p;
	}

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

3.将记录大块内存的内存头都分配到小块内存上,效率高(只需要做偏移)

large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);//记录大块内存的内存头都分配到小块内存上,效率高(只需要做偏移)
if (large == NULL) //很难为空,无法记录,把申请的大块内存free
{
    ngx_free(p);
    return NULL;
}

4.大块内存都有头信息,也就是内存池管理部分,将内存池管理部分连接起来(large连接内存池管理的头),其中alloc保存大块内存的起始地址

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

记录大块内存的内存头中包含next指针,串起来

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

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

遍历large的链表,如果large->alloc == NULL,只有在快内存free的时候为空。直接把当前开辟的大块内存的地址写入头信息的alloc指针域。

不是每次开辟大块内存都是在小块内存中创建一个新的大块内存的内存头,分配大块内的时候分配好了,而是先遍历大块内存的内存头,看哪个alloc为空(只有在快内存free的时候为空),找了几个(前3个)都没有直接把当前开辟的大块内存的地址写入头信息的alloc指针域,直接重新分配(效率高)。

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);//大块内存本来就是通过malloc分配的
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

大块内存释放,小块内存不释放。

这段代码是一个大块内存内存池的分配函数。它的作用是分配一块指定大小的内存,并将其加入内存池中。

首先,它会调用 ngx_alloc 函数分配所需大小的内存。ngx_alloc 函数是一个封装了系统 malloc 函数的函数,它会分配指定大小的内存,并返回一个指向该内存的指针。如果分配成功,则会继续执行后面的操作;如果分配失败,则会返回 NULL。

接下来,它会查找内存池中是否有空闲的大块内存可用。这里的大块内存指的是大小超过 ngx_pool_t 结构体中 max 字段所指定大小的内存块。如果找到了一个未被分配的内存块,就将其标记为已分配,并返回该内存块的指针。这里的标记方式是将 ngx_pool_large_t 结构体中的 alloc 字段指向当前分配到的内存块的地址。

如果遍历了 3 次仍未找到可用的内存块,则会调用 ngx_palloc_small 函数分配一个新的 ngx_pool_large_t 结构体,并将其指针加入内存池的链表中,然后返回分配到的内存块指针。这里的 ngx_palloc_small 函数是一个封装了内存池分配函数 ngx_palloc 的函数,它用于分配小块内存,例如结构体、指针等。这里将其用于分配 ngx_pool_large_t 结构体,因为这个结构体比较小,可以看作是小块内存。

如果新的结构体分配失败,则会释放之前分配的内存,并返回 NULL。

3.内存池重置函数

void
ngx_reset_pool(ngx_pool_t *pool)
{
    ngx_pool_t        *p;
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) 
    {
        if (l->alloc)
        {
            ngx_free(l->alloc);
        }
    }

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

流程

1.alloc管理的大块内存池全都free掉

for (l = pool->large; l; l = l->next)
{
    if (l->alloc) 
    {
        ngx_free(l->alloc);
    }
}

2.遍历小块内存

//下面做的不太正确,除了第一个内存块,后面没有max、current、large等数据
for (p = pool; p; p = p->d.next)
{
    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    p->d.failed = 0;
}

处理第一块内存池

p->d.last = (u_char *) p + sizeof(ngx_pool_t);
p->d.failed = 0;

处理第二块内存池

for (p = pool; p; p = p->d.next)
{
    p->d.last = (u_char *) p + sizeof(ngx_pool_data_t);
    p->d.failed = 0;
}

3.大块内存内存头在小块内存内存池上分配,这些内存池没用了

pool->current = pool;
pool->chain = NULL;
pool->large = NULL;
posted @ 2024-01-19 12:44  CodeMagicianT  阅读(31)  评论(0编辑  收藏  举报