【Nginx 源码学习】内存池 及 优秀案例赏析:Nginx内存池设计

本文详细解析了Nginx中的内存池结构,包括ngx_create_pool、ngx_destroy_pool、ngx_reset_pool和ngx_palloc等关键函数的作用,重点介绍了大内存管理和cleanup机制。
摘要由CSDN通过智能技术生成

struct ngx_pool_s {

ngx_pool_data_t d; // 指向内存池的第一个数据块

size_t max; // 内存池数据块的最大值(数目)

ngx_pool_t *current; // 指向当前内存池

ngx_chain_t *chain; // 该指针挂接一个ngx_chain_t结构

ngx_pool_large_t *large; // 大块内存链表,即分配空间超过max的内存

ngx_pool_cleanup_t *cleanup; // 释放内存池的callback

ngx_log_t *log; // 主要用于记录日志信息

};

在这里插入图片描述


大块内存:

struct ngx_pool_large_s {

ngx_pool_large_t *next; // 指向下一个large内存

void *alloc; // 指向分配的large内存

};

在这里插入图片描述


回收站:

struct ngx_pool_cleanup_s {

ngx_pool_cleanup_pt handler; // 指向用于cleanup本cleanup内存

void *data; // 指向分配的cleanup内存

ngx_pool_cleanup_t *next; // 指向下一个cleanup内存

};

在这里插入图片描述


ngx_pool_t 的逻辑结构:

在这里插入图片描述


源码分析


ngx_create_pool 创建内存池

用于创建一个内存池,我们创建时,传入我们的初始大小:

#define ngx_memalign(alignment, size, log) ngx_alloc(size, log)

//ngx_alloc:对malloc进行了简单封装

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;

}

// 可以看到 last 指向 pool 之后的位置,即下一个pool块分配的位置

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

// end 指向pool的size的最后,即当前pool可容纳的最大尺寸的结束位置

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;

/*

nginx对内存的管理分为大内存与小内存,

当某一个申请的内存大于某一个值时,就需要从大内存中分配空间,否则从小内存中分配空间。

nginx中的内存池是在创建的时候就设定好了大小,

在以后分配小块内存的时候,如果内存不够,则是重新创建一块内存串到内存池中,而不是将原有的内存池进行扩张。

当要分配大块内存时,则是在内存池外面再分配空间进行管理的,称为大块内存池。

*/

p->current = p;

p->chain = NULL;

p->large = NULL;

p->cleanup = NULL;

p->log = log;

return p;

}


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) {

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) {

ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, “free: %p”, l->alloc);

if (l->alloc) {

ngx_free(l->alloc);

}

}

#if (NGX_DEBUG)

… …

#endif

for (p = pool, n = pool->d.next; ; p = n, n = n->d.next) {

ngx_free§;

if (n == NULL) {

break;

}

}

}

遍历内存池链表,释放所有内存,包括pool,large,cleanup链表,如果指定了cleanup回调来释放,则调用cleanup的handler来释放cleanup链表中的内存。

先依次释放pool中cleanup,large类型的链表,最后释放pool本身的链表。


ngx_reset_pool 重置内存池

void ngx_reset_pool(ngx_pool_t *pool)

{

ngx_pool_t *p;

ngx_pool_large_t *l;

// 先遍历large链表,释放large内存

for (l = pool->large; l; l = l->next) {

if (l->alloc) {

ngx_free(l->alloc);

}

}

pool->large = NULL;

//重置所有小块内存区

for (p = pool; p; p = p->d.next) {

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

}

}


ngx_palloc 分配内存

ngx_align_ptr:一个用来内存地址取整的宏。取整可以降低CPU读取内存的次数,提高性能。这里并没有真正意义调用malloc等函数申请内存,而是移动指针标记而已,所以内存对齐的活,得自己动手。

#define ngx_align_ptr(p, a) \

(u_char *) (((uintptr_t) § + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

void *ngx_palloc(ngx_pool_t *pool, size_t size)

{

u_char *m;

ngx_pool_t *p;

if (size <= pool->max) { // 如果需要分配的size大于max,则使用palloc_large来分配

p = pool->current; // 小于max,则从current开始遍历pool链表

do {

// 每次从last处开始分配aligned内存

m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);

if ((size_t) (p->d.end - m) >= size) {

// 如果分配的内存够用,则就从此处分配,并调整last

p->d.last = m + size;

return m;

}

p = p->d.next;

} while §;

// 表示链表里没有能够分配size大小的内存节点

// 则生成一个新的节点,并在其中分配内存

return ngx_palloc_block(pool, size);

}

// 大于max的,就在large中进行分配

return ngx_palloc_large(pool, size);

}

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;//设置新内存块的end

new->d.next = NULL;

new->d.failed = 0;

m += sizeof(ngx_pool_data_t);//将指针m移动到d后面的一个位置,作为起始位置

m = ngx_align_ptr(m, NGX_ALIGNMENT);//对m指针按4字节对齐处理

new->d.last = m + size;//设置新内存块的last,即申请使用size大小的内存

current = pool->current;

//这里的循环用来找最后一个链表节点,这里failed用来控制循环的长度,如果分配失败次数达到5次,

//就忽略,不需要每次都从头找起

for (p = current; p->d.next; p = p->d.next) {

if(p->d.failed++ > 4) { //这儿咋总觉得哪里怪怪的

current = p->d.next;

}

}

p->d.next = new;

pool->current = current ? current : new;

return m;

}


ngx_palloc_large:开辟一个大内存检查后交给大内存链表管理。所以开辟的内存必定在大内存链表上。

/*

  • 1)判断pool->large链表上查询是否有NULL的,只在链表上往下查询3次,主要判断大数据块是否有被释放的,有就给它赋值,如果没有则只能跳出。

  • 2)然后往下新创建一个pool->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);//注意该函数是单独调用malloc,所以它的内存与内存池链表的内存是不连续的或者叫无关。

if (p == NULL) {

return NULL;

}

n = 0;

// 1)判断pool->large链表上查询是否有NULL的,只在链表上往下查询3次,主要判断大数据块是否有被释放的,有就给它赋值,如果没有则只能跳出

for (large = pool->large; large; large = large->next) {

if (large->alloc == NULL) {

large->alloc = p;

return p;

}

if (n++ > 3) {

break;

}

}

// 2)新建pool->large结构体管理新内存,注意:是创建结构体的大小,属于小内存块(不要以为调用ngx_palloc_small后会造成递归调用)

large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);

if (large == NULL) {

ngx_free§;

return NULL;

}

large->alloc = p;//刚开辟的内存交给pool->large链表管理

large->next = pool->large;//插入新的pool->large结构体。注:插法是每次新的pool->large结构体插进第一个pool->large的后面。即pool->large->la3->la2->la1->NULL

pool->large = large;

return p;

}


ngx_pfree 内存清理

/**

  • 指定释放大内存块链表的某一块大内存。

  • 这里可以看到大内存的管理是支持释放某一块大内存的,所以上面的ngx_palloc_large函数每一次都检查前三个是否为空,

  • 确保前三个有内存空间可用,至于后面是否为空就只能不怎么关心了。

*/

ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)

{

ngx_pool_large_t *l;

//遍历大内存链表,若找到想要释放的大内存则释放,否则返回错误NGX_DECLINED

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;

}


cleanup机制

pool->cleanup本身是一个链表,每个ngx_pool_cleanup_t的数据结构上,保存着内存数据的本身cleanup->data和回调清理函数cleanup->handler。

ngx_pool_cleanup_add:分配一个可以用于回调函数清理内存块的内存。内存块仍旧在p->d或p->large上(因为调用的是ngx_palloc)

/**

  • 1)创建一个新的ngx_pool_cleanup_t结构体并给其内部成员开辟内存空间。

  • 2)使用头插法将新的结构体插入清理链表。

  • 注意:初始化时回调c->handler设为NULL,并且返回值为返回当前结构体,所以该内存可以由用户自定义并且自行处理,非常灵活。

  • 实际上该函数注意是用来添加以下两个内容:

    1. 文件描述符
    1. 外部自定义回调函数可以来清理内存

*/

*/

ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)

{

ngx_pool_cleanup_t *c;

// 1)创建新的清理结构体和开辟空间

c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));

if (c == NULL) {

return NULL;

}

if (size) {

c->data = ngx_palloc(p, size);//该函数调用samll或者large,所以内存块仍旧在p->d或p->large上

if (c->data == NULL) {

return NULL;

}

} else {

c->data = NULL;

}

// 2)使用头插法插入清理链表,并且回调设为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_run_cleanup_file:清除p->cleanup链表上的某个已打开的文件描述符fd占用的内存块(或者叫清除指定的文件描述符)

void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd) {

ngx_pool_cleanup_t *c;

ngx_pool_cleanup_file_t *cf;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

我个人认为,如果你想靠着背面试题来获得心仪的offer,用癞蛤蟆想吃天鹅肉形容完全不过分。想必大家能感受到面试越来越难,想找到心仪的工作也是越来越难,高薪工作羡慕不来,却又对自己目前的薪资不太满意,工作几年甚至连一个应届生的薪资都比不上,终究是错付了,错付了自己没有去提升技术。

这些面试题分享给大家的目的,其实是希望大家通过大厂面试题分析自己的技术栈,给自己梳理一个更加明确的学习方向,当你准备好去面试大厂,你心里有底,大概知道面试官会问多广,多深,避免面试的时候一问三不知。

大家可以把Java基础,JVM,并发编程,MySQL,Redis,Spring,Spring cloud等等做一个知识总结以及延伸,再去进行操作,不然光记是学不会的,这里我也提供一些脑图分享给大家:

希望你看完这篇文章后,不要犹豫,抓紧学习,复习知识,准备在明年的金三银四拿到心仪的offer,加油,打工人!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
码获取!!(备注Java获取)**

img

总结

我个人认为,如果你想靠着背面试题来获得心仪的offer,用癞蛤蟆想吃天鹅肉形容完全不过分。想必大家能感受到面试越来越难,想找到心仪的工作也是越来越难,高薪工作羡慕不来,却又对自己目前的薪资不太满意,工作几年甚至连一个应届生的薪资都比不上,终究是错付了,错付了自己没有去提升技术。

这些面试题分享给大家的目的,其实是希望大家通过大厂面试题分析自己的技术栈,给自己梳理一个更加明确的学习方向,当你准备好去面试大厂,你心里有底,大概知道面试官会问多广,多深,避免面试的时候一问三不知。

大家可以把Java基础,JVM,并发编程,MySQL,Redis,Spring,Spring cloud等等做一个知识总结以及延伸,再去进行操作,不然光记是学不会的,这里我也提供一些脑图分享给大家:

[外链图片转存中…(img-SX0S26iH-1713402105476)]

[外链图片转存中…(img-9JlTpDAD-1713402105476)]

[外链图片转存中…(img-o4dmZR1n-1713402105477)]

希望你看完这篇文章后,不要犹豫,抓紧学习,复习知识,准备在明年的金三银四拿到心仪的offer,加油,打工人!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值