问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

nginx的物理内存是做什么的??

发布网友 发布时间:2022-02-27 02:43

我来回答

1个回答

热心网友 时间:2022-02-27 04:13

先来看内存池的实现,nginx的内存池实现的非常简单。

这里内存池的一些图表可以看老朱同学的slides :

http://blog.zhuzhaoyuan.com/2009/09/nginx-internals-slides-video/

当内存池初始化的时候(下面会分析到)ngx_poll_s只相当于内存池的一个头,保存了当前内存池的一些必要信息而已。

当从内存池存取数据的时候,nginx是分为两种类型来处理得,一种是小块数据,它是直接从内存池中取得数据,另一方面,当为大块数据时,它是直接malloc一块数据(也就是从内存池外部分配数据),然后保存这个指针到内存池。可以看到很多内存池,比如py的内存池实现,也基本是这个思想。这里的细节,我们将会在下面分析内存池相关函数的时候详细分析。

这里还有一个要注意就是这些子内存池和父内存池是不一样的,我们后面分析函数的时候会详细介绍。

大小块数据得分割线是你创建内存池时传递进来的size和页大小之间的最小值。

下面就是内存池的结构:

Java代码 收藏代码
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_data_t。

这个结构很简单,它就是包含了我们所需要操作这个内存池的数据的一些指针。

其中last表示当前的数据区的已经使用的数据的结尾。

end表示当前的内存池的结尾。也就是说end-last就是内存池未使用的大小。

我们要知道当我们从一个内存池请求一块内存时,如果此时内存池已经满掉,这是一般都是扩大内存池,而nginx中不是这么做的,它会直接再分配一个内存池,然后链接到data的next指针上。也就是说在nginx中,其实每个内存池都会包含一些子内存池。因此我们请求内存的时候都会遍历这些子内存池。

failed域主要是为了标记我们请求内存(小块内存)由于内存池空间不够,我们需要重新分配一个子内存池的次数。 下面分析函数的时候会再会看到这个域。

Java代码 收藏代码
typedef struct {
u_char *last;
u_char *end;
//指向下一块内存池
ngx_pool_t *next;
///失败标记
ngx_uint_t failed;
} ngx_pool_data_t;

ngx_chain_t这里就先不介绍了,我们现在只需要知道它是与buf相关的。

然后是ngx_pool_large_s,它表示了大块的内存。可以看到这个结构非常简单,就是一个指针指向下一块large,一个alloc指向数据。

Java代码 收藏代码
struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};

接下来是ngx_pool_cleanup_s,这个结构用来表示内存池中的数据的清理handler。

其中handler表示清理函数。
data表示传递给清理函数的数据。
next表示下一个清理handler,也就是说当destroy这个pool的时候会遍历清理handler链表,然后调用handler。

Java代码 收藏代码
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler;
void *data;
ngx_pool_cleanup_t *next;
}

通过ngx_create_temp_buf创建一个buff,然后通过ngx_alloc_chain_link创建一个chain,然后通过cl->buf = rb->buf;将buff链接到chain中.

下面就是pool的内存图,摘自老朱同学的nginx internal。

ok,接下来我们来通过分析具体的函数,来更好的理解pool的实现。

首先来看ngx_create_pool也就是创建一个pool。

这里我们要知道虽然我们传递进来的大小是size可是我们真正能使用的数据区大小是要减去ngx_pool_t的大小的。

Java代码 收藏代码
///内存池的数据区的最大容量。
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)

ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
///可以看到直接分配size大小,也就是说我们只能使用size-sizeof(ngx_poll_t)大小
p = ngx_alloc(size, log);
if (p == NULL) {
return NULL;
}

///开始初始化数据区。

///由于一开始数据区为空,因此last指向数据区的开始。
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
///end也就是数据区的结束位置
p->d.end = (u_char *) p + size;
p->d.next = NULL;
p->d.failed = 0;

///这里才是我们真正能使用的大小。
size = size - sizeof(ngx_pool_t);

///然后设置max。内存池的最大值也就是size和最大容量之间的最小值。
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

///current表示当前的内存池。
p->current = p;

///其他的域置NULL。
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
///返回指针。
return p;
}

接下来我们来看如何从内存池中分配一块内存来使用。在nginx中有3个函数可以使用,分别是ngx_palloc,ngx_calloc,ngx_pnalloc。这三个函数的区别就是第一个函数分配的内存会对齐。第二个函数用来分配一块清0的内存,第三个函数分配的内存不会对齐。

由于这三个函数差不多,因此我们就只分析一个就够了。我们就来看ngx_palloc.

Java代码 收藏代码
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;

///首先判断当前申请的大小是否超过max,如果超过则说明是大块,此时进入large
if (size <= pool->max) {

///得到当前的内存池指针。
p = pool->current;

///开始遍历内存池,
do {
///首先对齐last指针。
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);

///然后得到当前内存池中的可用大小。如果大于请求大小,则直接返回当前的last,也就是数据的指针。
if ((size_t) (p->d.end - m) >= size) {
///更新last,然后返回前面保存的last。
p->d.last = m + size;

return m;
}
///否则继续遍历
p = p->d.next;

} while (p);
///到达这里说明内存池已经满掉,因此我们需要重新分配一个内存池然后链接到当前的data的next上。(紧接着我们会分析这个函数)
return ngx_palloc_block(pool, size);
}

///申请大块。
return ngx_palloc_large(pool, size);
}

接下来就来看ngx_palloc_block的实现,这个函数主要就是重新分配一块内存池,然后链接到当前内存池的数据区指针。

然后要注意这里重新new的这个内存池大小是和它的父内存池一样大的。

并且新得内存池只保存了ngx_pool_data_t这个结构,也就是说数据区直接跟在ngx_pool_data_t下面。

Java代码 收藏代码
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_alloc(psize, pool->log);
if (m == NULL) {
return NULL;
}

new = (ngx_pool_t *) m;

///接下来和我们create一个内存池做的操作一样。就是更新一些指针
new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;

///这里要注意了,可以看到last指针是指向ngx_pool_data_t的大小再加上要分配的size大小,也就是现在的内存池只包含了ngx_pool_data_t和数据。
m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT);
new->d.last = m + size;

///设置current。
current = pool->current;

///这里遍历所有的子内存池,这里主要是通过failed来标记重新分配子内存池的次数,然后找出最后一个大于4的,标记它的下一个子内存池为current。
for (p = current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) {
current = p->d.next;
}
}

///链接到最后一个内存池后面
p->d.next = new;

///如果current为空,则current就为new。
pool->current = current ? current : new;

return m;
}

这里解释一下为什么这样设置current,这里的主要原因是我们在ngx_palloc中分配内存是从current开始的,而这里也就是设置current为比较新分配的内存。而当failed大于4说明我们至少请求了4次内存分配,都不能满足我们的请求,此时我们就假设老的内存都已经没有空间了,因此我们就从比较新的内存块开始。

接下来是ngx_palloc_large,这个函数也是很简单就是malloc一块ngx_poll_large_t,然后链接到主的内存池上。

Java代码 收藏代码
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;
///开始遍历large链表,如果有alloc(也就是内存区指针)为空,则直接指针赋值然后返回。一般第一次请求大块内存都会直接进入这里。并且大块内存是可以被我们手动释放的。
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
///这里不太懂什么意思。
if (n++ > 3) {
break;
}
}

///malloc一块ngx_pool_large_t。
large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}

///然后链接数据区指针p到large。这里可以看到直接插入到large链表的头的。
large->alloc = p;
large->next = pool->large;
pool->large = large;

return p;
}

ok,分配看完了,我们来看释放。这里要知道在nginx中,只有大块内存提供了free接口,可以供我们收工释放,而小块内存是没有提供这个接口的。也就是说小块内存只有当整个内存池被desrtoy掉时,才会被释放。

这里一些简单的函数就不分析了。
比如ngx_pfree(ngx_pool_t *pool, void *p),这个函数就是从pool的large链表中找到p,然后free掉它。

ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)

这个函数也就是添加一个ngx_pool_cleanup_t到当前的pool上,然后返回,我们此时就能通过返回的结构来给对应的handler赋值。

而ngx_pool_cleanup_t这个主要是当内存池destroy的时候我们可能需要做一些清理工作,此时我们就能add这些清理handler到pool中,然后当内存池要释放的时候就会自动调用。

ok,现在来看pool 被free的实现。

这个函数主要是遍历large,遍历current,然后一一释放。

Java代码 收藏代码
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);
}
}

///free大块内存
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) {
///直接free掉。
ngx_free(p);

if (n == NULL) {
break;
}
}
}

通过上面我们可以看到在nginx中内存池中的小块数据是从来不释放的,这样就简化了内存池的操作。

接下来我们来看buf的实现。

buf分为两种类型,一种是file,一种是memory.因此这里会有文件的一些操作域。

可以看到buf相对于pool多了一个pos域(file_pos).这里我们要知道我们发送往套接字异或者其他的设备,我们这里会现将数据放到buf中,然后当设备或者套接字准备好了,我们就会从buf中读取,因此这里pos指针就是放到buf中的已经被执行的数据(也就是已经送往套接字)的位置。

Java代码 收藏代码
struct ngx_buf_s {
///pos表示已经执行的数据的位置。
u_char *pos;
///last和上面内存池中last一样,也就是使用的内存的最后一个字节的指针
u_char *last;
///文件指针
off_t file_pos;
off_t file_last;
///buf的开始指针
u_char *start; /* start of buffer */
u_char *end; /* end of buffer */

///这里表示这个buf从属于那个模块。
ngx_buf_tag_t tag;
ngx_file_t *file;
ngx_buf_t *shadow;

///一些标记
/* the buf's content could be changed */
unsigned temporary:1;

///在内存中是不能改变的。
unsigned memory:1;

///是否是mmap的内存
unsigned mmap:1;

unsigned recycled:1;

///是否文件。
unsigned in_file:1;
unsigned flush:1;
unsigned sync:1;
unsigned last_buf:1;
unsigned last_in_chain:1;

unsigned last_shadow:1;
unsigned temp_file:1;

/* STUB */ int num;
};

ok,接下来我们来看如何创建一个buf.在nginx中一般都是调用ngx_create_temp_buf来创建一个buf。函数很简单,就是从pool中分配内存然后初始化相关域。
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
在液压原理图中各种符号的表示意义是什么? 脚臭怎么办,一脱鞋,那味道我自己都受不了? ...经电加热后表面会形成一层黑色物质,请问是什么东西?用什么化学... 宿舍老是经常出现蚂蚁,放久的书本打开全是蚂蚁,或是布柜里放久的衣服... 为什么寝室地板上会有蚂蚁? 怎么计算产值excel 青春不再伤感经典句子(《青春伤感经典短句子》) 致我们失去的青春简短说说(怀念过去的经典句子) 分享回忆过去致青春的语录集锦(回忆的句子时光感言简短) 女生为什么总想摸我的脸 Linux中的nginx和windows中的nginx的有什么区别? nginx开启accesskey有什么作用 linux下搜了 一下有好多nginx,这些nginx有什么区别吗? linux 部署 nginx 有什么用 苹果手机自拍镜像怎么设置? 在微信复制连接怎么打开? 如何用电烤箱烤红薯片 如何用烤箱烤地瓜 qq如何取消幸运字符 怎样使用电烤箱烤地瓜 如何用电烤箱烤地瓜? 手机丢了怎样找回? 你知道扇贝应该怎么清洗吗 手机丢了怎么找到? 在华为手机如何查看发送的微信验证信息 手机丢了怎么找回了 华为手机微信在哪里 华为手机下载微信在哪里弄? 怎么查找微信与哪个华为账号绑定了? 华为手机在哪里设置微信加密 nginx动静分离有什么好处 Nginx 配合java 做WEB不流行吗? 苹果手机怎么把通讯录加到黑名单 nginx 不要http 模块 可以做什么 核桃怎么吃最营养最健康? 核桃怎么吃才能发挥最大的营养价值? 核桃应该怎样吃才最有营养? 核桃怎样吃最营养 核桃怎么吃才营养? 红心的柚子和白心的柚子有什么区别 微信中有个耳朵如何消除 老式微波炉烤地瓜怎么打 京东为什么不能领京豆了 请问老式微波炉怎么烤地瓜呢? 带玻璃转盘的老式微波炉能烤红薯吗 做vlog用什么手机比较好? 电脑开机显示windows未能启动怎么解决? 电脑无法开机,开机显示windows未能启动? 电脑windows未能启动怎么办? 电脑开机显示windows无法启动