- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章nginx内存池源码解析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
内存池是在真正使用内存之前,预先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够用时,再继续申请新的内存.
内存池的好处有减少向系统申请和释放内存的时间开销,解决内存频繁分配产生的碎片,提示程序性能,减少程序员在编写代码中对内存的关注等 。
目前一些常见的内存池实现方案有STL中的内存分配区,boost中的object_pool,nginx中的ngx_pool_t,google的开源项目TCMalloc等.
为了自身使用的方便,Nginx封装了很多有用的数据结构,比如ngx_str_t ,ngx_array_t, ngx_pool_t 等等,对于内存池,nginx设计的十分精炼,值得我们学习,本文重点给大家介绍nginx内存池源码,并用一个实际的代码例子作了进一步的讲解.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// SGI STL小块和大块内存的分界点:128B
// nginx(给HTTP服务器所有的模块分配内存)小块和大块内存的分界点:4096B
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
// 内存池默认大小
#define NGX_DEFAULT_POOL_SIZE (16 * 1024)
// 内存池字节对齐,SGI STL对其是8B
#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)
// 将开辟的内存调整到16的整数倍
#define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1))
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
typedef struct ngx_pool_s ngx_pool_t;
typedef struct {
u_char *last; // 指向可用内存的起始地址
u_char *end; // 指向可用内存的末尾地址
ngx_pool_t *next; // 指向下一个内存块
ngx_uint_t failed; // 当前内存块分配空间失败的次数
} ngx_pool_data_t;
// 内存池块的类型
struct ngx_pool_s {
ngx_pool_data_t d; // 内存池块头信息
size_t max;
ngx_pool_t *current; // 指向可用于分配空间的内存块(failed < 4)的起始地址
ngx_chain_t *chain; // 连接所有的内存池块
ngx_pool_large_t *large; // 大块内存的入口指针
ngx_pool_cleanup_t *cleanup; // 内存池块的清理操作,用户可设置回调函数,在内存池块释放之前执行清理操作
ngx_log_t *log; // 日志
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// 根据size进行内存开辟
ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log){
ngx_pool_t *p;
// 根据系统平台定义的宏以及用户执行的size,调用不同平台的API开辟内存池
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); // 能使用的空间 = 总空间 - 头信息
// 指定的大小若大于一个页面就用一个页面,否则用指定的大小
// max = min(size, 4096),max指的是除开头信息以外的内存块的大小
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
p->current = p; // 指向可用于分配空间的内存块的起始地址
p->chain = NULL;
p->large = NULL; // 小块内存直接在内存块开辟,大块内存在large指向的内存开辟
p->cleanup = NULL;
p->log = log;
return p;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
if (size <= pool->max) {
// 当前分配的空间小于max,小块内存的分配
return ngx_palloc_small(pool, size, 1); // 考虑内存对齐
}
#endif
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);
}
void* ngx_pcalloc(ngx_pool_t *pool, size_t size){
void *p;
p = ngx_palloc(pool, size); // 考虑内存对齐
if (p) {
ngx_memzero(p, size); // 可以初始化内存为0
}
return p;
}
|
ngx_palloc_small 分配效率高,只做了指针的偏移 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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;
// 从第一个内存块的current指针指向的内存池进行分配
p = pool->current;
do {
m = p->d.last; // m指向可分配内存的起始地址
if (align) {
// 把m调整为NGX_ALIGNMENT整数倍
m = ngx_align_ptr(m, NGX_ALIGNMENT);
}
// 内存池分配内存的核心代码
if ((size_t) (p->d.end - m) >= size) {
// 若可分配空间 >= 申请的空间
// 偏移d.last指针,记录空闲空间的首地址
p->d.last = m + size;
return m;
}
// 当前内存块的空闲空间不够分配,若有下一个内存块则转向下一个内存块
// 若没有,p会被置空,退出while
p = p->d.next;
} while (p);
return ngx_palloc_block(pool, size);
}
|
当前内存池的块足够分配:
当前内存池的块不够分配:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
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);
// 将psize对齐为NGX_POOL_ALIGNMENT的整数倍后,向OS申请空间
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; // 下一块内存的地址为NULL
new->d.failed = 0; // 当前内存块分配空间失败的次数
// 指向头信息的尾部,而max,current、chain等只在第一个内存块有
m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT);
new->d.last = m + size; // last指向当前块空闲空间的起始地址
// 由于每次都是从pool->current开始分配空间
// 若执行到这里,除了new这个内存块分配成功,其他的内存块全部分配失败
for (p = pool->current; p->d.next != NULL; p = p->d.next) {
// 对所有的内存块的failed都++,直到该内存块分配失败的次数大于4了
// 就表示该内存块的剩余空间很小了,不能再分配空间了
// 就修改current指针,下次从current开始分配空间,再次分配的时候可以不用遍历前面的内存块
if (p->d.failed++ > 4) {
pool->current = p->d.next;
}
}
p->d.next = new; // 连接可分配空间的首个内存块 和 新开辟的内存块
return m;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s {
ngx_pool_large_t *next; // 下一个大块内存的起始地址
void *alloc; // 大块内存的起始地址
};
static void * ngx_palloc_large(ngx_pool_t *pool, size_t size){
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;
// 调用的就是malloc
p = ngx_alloc(size, pool->log);
if (p == NULL) {
return NULL;
}
n = 0;
// for循环遍历存储大块内存信息的链表
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
// 当大块内存被ngx_pfree时,alloc为NULL
// 遍历链表,若大块内存的首地址为空,则把当前malloc的内存地址写入alloc
large->alloc = p;
return p;
}
// 遍历4次后,若还没有找到被释放过的大块内存对应的信息
// 为了提高效率,直接在小块内存中申请空间保存大块内存的信息
if (n++ > 3) {
break;
}
}
// 通过指针偏移在小块内存池上分配存放大块内存*next和*alloc的空间
large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
if (large == NULL) {
// 如果在小块内存上分配存储*next和*alloc空间失败,则无法记录大块内存
// 释放大块内存p
ngx_free(p);
return NULL;
}
large->alloc = p; // alloc指向大块内存的首地址
large->next = pool->large; // 这两句采用头插法,将新内存块的记录信息存放于以large为头结点的链表中
pool->large = large;
return p;
}
|
大块内存的释放 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 释放p指向的大块内存
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p){
ngx_pool_large_t *l;
for (l = pool->large; l; l = l->next) {
// 遍历存储大块内存信息的链表,找到p对应的大块内存
if (p == l->alloc) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"free: %p", l->alloc);
// 释放大块内存,但不释放存储信息的内存空间
ngx_free(l->alloc); // free
l->alloc = NULL; // alloc置空
return NGX_OK;
}
}
return NGX_DECLINED;
}
|
就用了last和end两个指着标识空闲的空间,是无法将已经使用的空间合理归还到内存池的,只是会重置内存池。同时还存储了指向大内存块large和清理函数cleanup的头信息 。
考虑到nginx的效率,小块内存分配高效,同时也不回收内存 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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);
}
}
// 遍历小块内存的链表,重置last、failed、current、chain、large等管理信息
for (p = pool; p; p = p->d.next) {
// 由于只有第一个内存块有除了ngx_pool_data_t以外的管理信息,别的内存块只有ngx_pool_data_t的信息
// 不会出错,但是会浪费空间
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
p->d.failed = 0;
}
// current指向可用于分配内存的内存块
pool->current = pool;
pool->chain = NULL;
pool->large = NULL;
}
|
nginx本质是http服务器,通常处理的是短链接,间接性提供服务,需要的内存不大,所以不回收内存,重置即可.
客户端发起一个requests请求后,nginx服务器收到请求会返回response响应,若在keep-alive时间内没有收到客户的再次请求,nginx服务器会主动断开连接,此时会reset内存池。下一次客户端请求再到来时,可以复用内存池.
如果是处理长链接,只要客户端还在线,服务器的资源就无法释放,直到系统资源耗尽。长链接一般使用SGI STL内存池的方式进行内存的开辟和释放,而这种方式分配和回收空间的效率就比nginx低 。
假设如下情况:
1
2
3
4
5
6
7
8
9
|
// 假设内存对齐为4B
typedef struct{
char* p;
char data[508];
}stData;
ngx_pool_t *pool = ngx_create_pool(512, log); // 创建一个总空间为512B的nginx内存块
stData* data_ptr = ngx_alloc(512); // 因为可用的实际内存大小为:512-sizeof(ngx_pool_t),所以属于大内存开辟
data_ptr->p = malloc(10); // p指向外界堆内存,类似于C++对象中对用占用了外部资源
|
当回收大块内存的时候,调用ngx_free,就会导致内存泄漏 。
以上内存泄漏的问题,可以通过回调函数进行内存释放(通过函数指针实现) 。
1
2
3
4
5
6
7
8
9
10
|
typedef void (*ngx_pool_cleanup_pt)(void *data);
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
// 以下结构体由ngx_pool_s.cleanup指向,也是存放在内存池的小块内存
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler;
void *data; // 指向需要释放的资源
ngx_pool_cleanup_t *next; // 释放资源的函数都放在一个链表,用next指向这个链表
};
|
nginx提供的函数接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// p表示内存池的入口地址,size表示p->cleanup->data指针的大小
// p->cleanup指向含有清理函数信息的结构体
// ngx_pool_cleanup_add返回 含有清理函数信息的结构体 的指针
ngx_pool_cleanup_t* ngx_pool_cleanup_add(ngx_pool_t *p, size_t size){
ngx_pool_cleanup_t *c;
// 开辟清理函数的结构体,实际上也是存放在内存池的小块内存
c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
if (c == NULL) {
return NULL;
}
if (size) {
// 为c->data申请size的空间
c->data = ngx_palloc(p, size);
if (c->data == NULL) {
return NULL;
}
} else {
c->data = NULL;
}
c->handler = NULL;
// 采用头插法,将当前结构体串在pool->cleanup后
c->next = p->cleanup;
p->cleanup = c;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
return c;
}
|
使用方式:
1
2
3
4
5
6
7
8
9
|
void release(void* p){
free(p);
}
ngx_pool_cleanup_t* clean_ptr = ngx_clean_cleanup_add(pool, sizeof(char*));
clean_ptr->handler = &release; // 用户设置销毁内存池前需要调用的函数
clean_ptr->data = data_ptr->p; // 用户设置销毁内存池前需要释放的内存的地址
ngx_destroy_pool(pool); // 用户销毁内存池
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
void ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
ngx_pool_cleanup_t *c;
// 遍历cleanup链表(存放的时释放前需要调用的函数),可释放外部占用的资源
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) {
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;
}
}
}
|
执行configure生成Makefile文件(若报错则表示需要apt安装软件) 。
Makefile如下:
执行make命令使用Makefile编译源码,在相应目录下生成 .o文件 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
#include <ngx_config.h>
#include <nginx.h>
#include <ngx_core.h>
#include <ngx_palloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void
ngx_log_error_core(ngx_uint_t level, ngx_log_t *
log
, ngx_err_t err,
const
char
*fmt, ...){
}
typedef
struct
Data stData;
struct
Data{
char
*ptr;
FILE
*pfile;
};
void
func1(
char
*p){
printf
(
"free ptr mem!\n"
);
free
(p);
}
void
func2(
FILE
*pf){
printf
(
"close file!\n"
);
fclose
(pf);
}
void
main(){
// max = 512 - sizeof(ngx_pool_t)
// 创建总空间为512字节的nginx内存块
ngx_pool_t *pool = ngx_create_pool(512, NULL);
if
(pool == NULL){
printf
(
"ngx_create_pool fail..."
);
return
;
}
// 从小块内存池分配的
void
*p1 = ngx_palloc(pool, 128);
if
(p1 == NULL){
printf
(
"ngx_palloc 128 bytes fail..."
);
return
;
}
// 从大块内存池分配的
stData *p2 = ngx_palloc(pool, 512);
if
(p2 == NULL){
printf
(
"ngx_palloc 512 bytes fail..."
);
return
;
}
// 占用外部堆内存
p2->ptr =
malloc
(12);
strcpy
(p2->ptr,
"hello world"
);
// 文件描述符
p2->pfile =
fopen
(
"data.txt"
,
"w"
);
ngx_pool_cleanup_t *c1 = ngx_pool_cleanup_add(pool,
sizeof
(
char
*));
c1->handler = func1;
// 设置回调函数
c1->data = p2->ptr;
// 设置资源地址
ngx_pool_cleanup_t *c2 = ngx_pool_cleanup_add(pool,
sizeof
(
FILE
*));
c2->handler = func2;
c2->data = p2->pfile;
// 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存
ngx_destroy_pool(pool);
return
;
}
|
由于ngx_pool_cleanup_add中用头插法将创建的清理块链入pool->cleanup,所以ngx_destroy_pool的时候先清理文件后清理堆内存.
相关测试代码推送到:https://github.com/BugMaker-shen/nginx_sgistl_pool 。
到此这篇关于nginx内存池源码解析的文章就介绍到这了,更多相关nginx内存池内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://blog.csdn.net/qq_42500831/article/details/121328558 。
最后此篇关于nginx内存池源码解析的文章就讲到这里了,如果你想了解更多关于nginx内存池源码解析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个无所事事的盒子,已经运行了一段时间,今天,由于某种原因,当我尝试重新启动nginx时,得到了以下提示。 nginx: [emerg] host not found in upstream "w
我注意到,当我使用 ubuntu 命令“nginx”启动 nginx 并执行 systemctl status nginx 时。它表明 systemctl 已禁用。此外,如果我首先使用命令 syste
我的 nginx 配置如下: proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $re
周围有两个配置文件,/etc/nginx/conf.d/default.conf和 /etc/nginx/nginx.conf,但是启用了哪一个呢?我运行的是 CentOS6.4 和 nginx/1.
我的 Nginx 配置仅适用于根位置,所有其他位置都返回“Cannot GET {location}”,其中位置是域后地址的其余部分。 这是我的/etc/nginx/sites-enabled/def
我在 nginx 中为 node.js 服务器设置了反向代理。 server { listen 80; server_name sub.domain.tld; location
我的应用程序将在两个位置提供静态文件,一个是/my/path/project/static,另一个是/my/path/project/jsutils/static。 我很难让网络服务器在两个目录中查找
我的域名注册商的 DNS 访问我的服务器并获取 nginx 默认页面,因此配置正确 我复制了一个当前正在工作的 nginx 虚拟主机,更改了 server_name和 conf 文件的名称,仅此而已。
这个问题在这里已经有了答案: Can't login in to phpPgAdmin (2 个回答) 3年前关闭。 我在centos中遇到了phpPgAdmin登录的奇怪问题,我做了所有需要的事情
我要为PoC进行的操作是向来自动态后端服务器的网页添加href。使用“ subs_filter”可以很容易地添加href,但是我需要使用响应中嵌入的信息来构造href。 是否可以使用LUA处理来自pr
我有网站服务器,它有两个代理(鱿鱼,CF),它们使用不同的 header 来获取真实的 ip。 我猜 nginx 命令 set_real_ip_from ;real_ip_header X-Forwa
在控制台显示如下: Job for nginx.service failed because the control process exited witherror code. See "syste
我有一个问题,我怀疑是 NGINX 问题。基本上,当我尝试登录到我创建的网站时,出现以下错误…… 您要查找的页面暂时不可用。请稍后再试。 有没有人以前遇到过这个? 最佳答案 如果 NGINX 虚拟主机
这是我的 nginx 配置文件: server { listen 80; server_name localhost; location / {
在我的/etc/nginx/nginx.conf 文件中,我有配置。作为:- user nginx; worker_processes 1; error_log /var/log/nginx/e
有谁知道nginx支持软退出吗?这意味着它会一直运行直到所有连接都消失或超时(超过特定时间间隔)并且在此期间也不允许新连接吗? 例如: nginx stop nginx running (2 conn
有没有办法将 Nginx 配置为类似这样的直接服务器返回 (DSR) 负载平衡器: http://blog.haproxy.com/2011/07/29/layer-4-load-balancing-
我通过 apt-get 安装了 Nginx不久前在 Debian 上,我有几个网站在上面。现在我需要安装一些额外的模块,因为我不想搞砸任何事情,所以我想在执行之前仔细检查我的过程。希望这也能帮助其他不
我知道 Apache 的 pagespeed 模块可以使页面访问更快,所以,我想知道 Nginx 是否有等效的模块? 提前致谢! 最佳答案 https://github.com/pagespeed/n
如何将worker_rlimit_nofile设置为一个更大的数字,它可以是或建议最大为多少? 我正在尝试遵循以下建议: 大多数人遇到的第二个最大限制是 与您的操作系统有关。打开一个shell,su给
我是一名优秀的程序员,十分优秀!