后台服务器:https://course.0voice.com/v1/course/intro?courseId=5&agentId=0
- ngx_array_t 数据结构
- 数据结构定义:
- 数据结构图:
- 基本操作
- 示例代码:
- ngx_list_t 数据结构
- 数据结构定义如下
- 数据结构图:
- 基本操作
- 示例代码
- 内存池操作
- 基于内存池的分配、释放内存操作
- 随着内存池释放同步释放资源的操作
- 与内存池无关的分配、释放操作
nginx为了做到跨平台, 定义、封装了一些基本的数据结构。由于nginx 对内存分配比较“吝啬”(当然咯,只有保证低内存消耗,才可能实现十万甚至百万级别的同时并发连接数),所以nginx 的数据结构天生都是尽可能少占用内存。学习优秀的代码,未来才能设计好整个系统平台。
可重点看ngx_pool_t 的设计思想
ngx_array_t 数据结构src/core
数据结构定义:在 Nginx 数组中,内存分配是基于内存池的,并不是固定不变的,也不是需要多少内存就申请多少,若当前内存不足以存储所需元素时,按照当前数组的两倍内存大小进行申请,这样做减少内存分配的次数,提高效率。
typedef struct {
void *elts; // 指向数组数据区域的首地址
ngx_uint_t nelts; // 数组实际数据的个数
size_t size; // 单个元素所占据的字节大小
ngx_uint_t nalloc; // 数组容量
ngx_pool_t *pool; // 数组对象所在的内存池
} ngx_array_t;
数据结构图:
基本操作
ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size); void ngx_array_destroy(ngx_array_t *a); void *ngx_array_push(ngx_array_t *a); void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);示例代码:
#includengx_list_t 数据结构#include #include "ngx_config.h" #include "ngx_core.h" #include "ngx_list.h" #include "ngx_palloc.h" #include "ngx_string.h" #define N 10 typedef struct Key { int id; char name[32]; }Key; volatile ngx_cycle_t *ngx_cycle; void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, const char *fmt, ...) { } void print_array(ngx_array_t* arr) { Key* key = arr->elts; int i = 0; for(i = 0; i < arr->nelts; i ++) { printf("%s.n", key[i].name); } } int main() { printf("Key = %dn", sizeof(Key)); // 36 printf("ngx_variable_value_t = %dn", sizeof(ngx_variable_value_t)); // 16 = 8 + 8 ngx_pool_t* pool = ngx_create_pool(1024, NULL); ngx_array_t* array = ngx_array_create(pool, N, sizeof(Key)); int i = 0; Key* key = NULL; for(i = 0; i < 24; i ++) { key = ngx_array_push(array); key->id = i + 1; sprintf(key->name, "Test %d", key->id); } key = ngx_array_push_n(array, 10); for(i = 0; i < 10; i ++) { key[i].id = 24 + i + 1; sprintf(key[i].name, "Other Test %d", key[i].id); } print_array(array); return 0; } // 编译命令 // gcc -o ngx_array_main ngx_array_main.c -I ../nginx_folds/nginx/src/core/ -I ../nginx_folds/nginx/objs/ -I ../nginx_folds/nginx/src/os/unix/ -I ../nginx_folds/pcre-8.41/ -I ../nginx_folds/nginx/src/event/ ../nginx_folds/nginx/objs/src/core/ngx_list.o ../nginx_folds/nginx/objs/src/core/ngx_string.o ../nginx_folds/nginx/objs/src/core/ngx_palloc.o ../nginx_folds/nginx/objs/src/os/unix/ngx_alloc.o ../nginx_folds/nginx/objs/src/core/ngx_array.o
数据结构定义如下ngx_list_t 是 Nginx 封装的链表容器,链表容器内存分配是基于内存池进行的,操作方便,效率高。Nginx 链表容器和普通链表类似,均有链表表头和链表节点,通过节点指针组成链表。
typedef struct ngx_list_part_s ngx_list_part_t;
struct ngx_list_part_s {
void *elts;
ngx_uint_t nelts;
ngx_list_part_t *next;
};
typedef struct {
ngx_list_part_t *last;
ngx_list_part_t part;
size_t size;
ngx_uint_t nalloc;
ngx_pool_t *pool;
} ngx_list_t;
数据结构图:
基本操作
ngx_list_t * ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size); static ngx_inline ngx_int_t ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size); void *ngx_list_push(ngx_list_t *l);示例代码
#include#include #include "ngx_config.h" #include "ngx_core.h" #include "ngx_list.h" #include "ngx_palloc.h" #include "ngx_string.h" #define N 10 volatile ngx_cycle_t *ngx_cycle; void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, const char *fmt, ...) { } void print_list(ngx_list_t *l) { ngx_list_part_t *p = &(l->part); while (p) { int i = 0; for (i = 0;i < p->nelts;i ++) { printf("%sn", (char*)(((ngx_str_t*)p->elts + i)->data)); } p = p->next; printf(" -------------------------- n"); } } // typedef struct { // size_t len; // long unsigned int // u_char *data; // } ngx_str_t; int main() { // 4, 4, 8, 8, 16 printf("int = %ldn", sizeof(int)); printf("unsigned int = %ldn", sizeof(unsigned int)); printf("long = %ldn", sizeof(long)); printf("long unsigned int = %ldn", sizeof(long unsigned int)); printf("sizeof(ngx_str_t) = %dn", sizeof(ngx_str_t)); // 16 ngx_pool_t *pool = ngx_create_pool(1024, NULL); ngx_list_t *l = ngx_list_create(pool, N, sizeof(ngx_str_t)); int i = 0; for (i = 0;i < 24;i ++) { ngx_str_t *ptr = ngx_list_push(l); char *buf = ngx_palloc(pool, 32); sprintf(buf, "MyList %d node", i+1); ptr->len = strlen(buf); ptr->data = buf; } print_list(l); return 0; } // 编译命令 // gcc -o ngx_list_main ngx_list_main.c -I ../nginx_folds/nginx/src/core/ -I ../nginx_folds/nginx/objs/ -I ../nginx_folds/nginx/src/os/unix/ -I ../nginx_folds/pcre-8.41/ -I ../nginx_folds/nginx/src/event/ ../nginx_folds/nginx/objs/src/core/ngx_list.o ../nginx_folds/nginx/objs/src/core/ngx_string.o ../nginx_folds/nginx/objs/src/core/ngx_palloc.o ../nginx_folds/nginx/objs/src/os/unix/ngx_alloc.o
首先在说明ngx_pool_t内存池前,先介绍相关的15个方法
内存池操作ngx_create_pool
ngx_destroy_pool
ngx_reset_pool
ngx_palloc : 分配地址对齐的内存。按总线的长度(例sizeof(unsigned long)对齐地址后,可以减少CPU读取内存的次数,当然代价是有一些内存浪费)
ngx_pnalloc : 分配内存时不进行地址对齐
ngx_pcalloc : 分配出地址对齐的内存后,在调用memset 将这些内存全部清0
ngx_pmemalign : 按照alignment进行地址对齐来分配内存。注意,这样分配出的内存不管申请的size大小,都是不会使用小块内存池的,它会从进程的堆中分配内存,并挂在大块内存组成的large单链表中
ngx_pfree : 提前释放大块内存。它的效率不高,其实就是遍历large链表,寻找ngx_pool_large_t 的alloc 成员等于待释放地址,找到后释放内存给操作系统,将ngx_pool_large_t 移出链表并删除
ngx_pool_cleanup_add : 添加一个需要在内存池释放时同步释放的资源。
ngx_pool_run_cleanup_file : 在内存池释放前,如果需要提前关闭文件(当然是调用过ngx_pool_cleanup_add 添加的文件,同时ngx_pool_cleanup_t 的handle成员被设为ngx_pool_cleanup_file), 则调用该方法
ngx_pool_cleanup_file : 以关闭文件来释放资源的方法,可以设置到ngx_pool_cleanup_t 的handle 成员
ngx_pool_delete_file : 以删除文件来释放资源的方法,可以设置到ngx_pool_cleanup_t 的handle 成员
ngx_alloc : 从操作系统中分配内存
ngx_calloc : 从操作系统中分配内存,在调用memset 把内存清0
ngx_free : 释放内存到操作系统
Nginx 已经提供封装了malloc、free的ngx_alloc 、ngx_free 方法,那为什么还需要一个复杂的内存池呢?对于没有垃圾回收机制的c语言编写引用来说,最容易犯的错就是内存泄露。当分配内存与释放内存的逻辑相距遥远时,还很容易发生同一块内存被释放两次。内存池就是来降低程序员犯错误的机率的。模块开发者只需要关心内存的分配,而释放内存则交个内存池来释放。
ngx_pool_t内存池的设计上还考虑了小块内存的频繁分配在效率上有提升空间,以及内存碎片还可以在减少些。不过在这里需要定义一下什么叫小块内存,NGX_MAX_ALLOC_FROM_POOL宏是一个很重要的标准:
#NGX_MAX_ALLOC_FROM_POOL (ngx_pageisze -1)
可见,在x86架构上就是4095 字节,通常,小于等于NGX_MAX_ALLOC_FROM_POOL 就意味这小块内存。这也不是绝对的,当调用ngx_create_pool 创建内存池时,如果传递的size参数小于NGX_MAX_ALLOC_FROM_POOL + sizeof(ngx_pool_t), 则对于这个内存池来说,size - sizeof(ngx_pool_t) 字节就是小块内存的标准。大块内存和小块内存的处理规则不同,这个在源码中看处理逻辑就知道了。
下面是有关ngx_pool_t 结构的一些数据结构:
// src/score/ngx_core.h // 首先说一下这个变量问题,这里_s 和 _t 为什么这样定义,我搜到的答案给我的解释其中合理的是: // _s 指的是struct 变量; _t 指的是 某一个type类型 #includetypedef struct ngx_module_s ngx_module_t; typedef struct ngx_conf_s ngx_conf_t; typedef struct ngx_cycle_s ngx_cycle_t; typedef struct ngx_pool_s ngx_pool_t; typedef struct ngx_chain_s ngx_chain_t; typedef struct ngx_log_s ngx_log_t; typedef struct ngx_open_file_s ngx_open_file_t; typedef struct ngx_command_s ngx_command_t; typedef struct ngx_file_s ngx_file_t; typedef struct ngx_event_s ngx_event_t;
src/core/ngx_palloc.{h,c}
typedef struct {
// 当前内存分配结束位置,即下一段可分配内存的位置 (指向未分配的空闲内存的首地址)
u_char *last;
u_char *end; // 小块内存池结束位置
ngx_pool_t *next; // 指向下一内存的指针,内存池是通过链表连接的
ngx_uint_t failed; //记录内存池分配失败的次数(4.4之后移向下一个小块内存池)
} ngx_pool_data_t; // 内存池的数据结构模块
struct ngx_pool_s {
// 内存池的数据块,结构如上,描述是小块内存池。当分配小块内存,剩余的空间不足时,会再分配1个ngx_pool_t, 它们会通过d中next成员构成单链表
ngx_pool_data_t d;
size_t max; // 评估申请内存属于小块内存还是大块内存的标准(=小块内存的最大值)
// 多个小块内存池构成链表时,current 指向分配内存时遍历的第1个小块内存池
ngx_pool_t *current;
// 与内存池关系不大
ngx_chain_t *chain; // 指针指向chain结构
// 大块内存都直接从进程的堆中分配,为了能够在销毁内存池时同时释放大块内存,就把
// 每一次分配的大块内存通过ngx_pool_large_t 组成单链表挂在large成员上
ngx_pool_large_t *large; // 指向大块内存链表,超过max的内存分配是不同规则的
// 所有待清理资源(例如需要关闭或者删除的文件)以ngx_pool_cleanup_t 对象构成单链表挂在cleanup 成员上
ngx_pool_cleanup_t *cleanup; // 释放内存池指针,内部包含函数指针结构,类似析构函数
// 内存池执行中输出日志的对象
ngx_log_t *log; // 内存分配的日志指针
};
// src/core/ngx_buf.h
struct ngx_chain_s {
ngx_buf_t *buf;
ngx_chain_t *next;
};
// ngx_palloc.h
struct ngx_pool_large_s {
// 所有大块内存通过next 指针连在一起
ngx_pool_large_t *next;
void *alloc;
};
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler;
void *data;
ngx_pool_cleanup_t *next;
};
从ngx_pool_s 结构中,可以知道,当申请的内存算是大块内存时(大于ngx_pool_t 的max成员),是直接调用ngx_alloc 从进程的堆中分配的,同时会再分配一个ngx_pool_large_t 结构体挂在large链表中,其定义如上面的 ngx_pool_large_s :
对于非常大的内存,如果它的生命周期远远的短于所述的内存池,那么在内存池销毁前提前的释放它就变得有意义了。而ngx_free 方法就是提前释放大块内存的,需要注意,它的实现是遍历large 链表,找打alloc 等于待师范地址的ngx_pool_large_t 后,调用ngx_free释放大块内存,但不是房ngx_pool_large_t结构体,而是把alloc 置为NULL。如此实现的意义是下次分配大块内存时,能复用这个结构体。所以可以想见,如果large 链表中的元素很多,那么ngx_free 的遍历损耗是很大,因此,最好不要调用ngx_pfree。
在看看小块内存,通过从进程的堆中预分配更过的内存(ngx_create_pool 的size参数决定分配的大小),而后直接使用这块内存的一部分作为小块内存返回给申请者,以此实现减少碎片和调用malloc 的次数。它们是放在成员d中维护管理的,看看ngx_pool_data_t 是如何定义。
//src/core/ngx_palloc.h
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p; // 这里就占80字节,里面有8个指针,两个long int 变量
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);
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
return p;
}
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);
}
}
#if (NGX_DEBUG)
for (l = pool->large; l; l = l->next) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
}
for (p = pool, n = pool->d.next; ; p = n, n = n->d.next) {
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"free: %p, unused: %uz", p, p->d.end - p->d.last);
if (n == NULL) {
break;
}
}
#endif
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
ngx_free(l->alloc);
}
}
for (p = pool, n = pool->d.next; ; p = n, n = n->d.next) {
ngx_free(p);
if (n == NULL) {
break;
}
}
}
下图是将ngx_pool_t 的内存逻辑画出来,可以直观地参考下图进行学习:
下面写了一个演示代码:
附上运行结果,可结合上面的逻辑图来看,还是比较清楚的。
pool_test.cpp
#include#include extern "C" { #include "ngx_config.h" #include "ngx_conf_file.h" #include "nginx.h" #include "ngx_core.h" #include "ngx_string.h" #include "ngx_palloc.h" } using namespace std; volatile ngx_cycle_t *ngx_cycle; void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, const char *fmt, ...) { } void dump_pool(ngx_pool_t* pool) { printf("------------start--------------n"); while (pool) { printf("pool = 0x%xn", pool); printf(" .dn"); printf(" .last = 0x%xn", pool->d.last); printf(" .end = 0x%xn", pool->d.end); printf(" .next = 0x%xn", pool->d.next); printf(" .failed = %dn", pool->d.failed); printf(" .max = %dn", pool->max); printf(" .current = 0x%xn", pool->current); printf(" .chain = 0x%xn", pool->chain); printf(" .large = 0x%xn", pool->large); printf(" .cleanup = 0x%xn", pool->cleanup); printf(" .log = 0x%xn", pool->log); printf("available pool memory = %dn", pool->d.end - pool->d.last); pool = pool->d.next; } printf("------------end--------------n"); } int main() { ngx_pool_t *pool; printf("--------------------------------n"); printf("create a new pool:n"); pool = ngx_create_pool(1024, NULL); dump_pool(pool); printf("alloc block 1 from the pool:n"); // printf("--------------------------------n"); ngx_palloc(pool, 512); dump_pool(pool); // printf("--------------------------------n"); printf("alloc block 2 from the pool:n"); // printf("--------------------------------n"); ngx_palloc(pool, 512); dump_pool(pool); printf("alloc block 3 from the pool :n"); ngx_palloc(pool, 512); dump_pool(pool); printf("alloc block 4 from the pool :n"); ngx_palloc(pool, 512); dump_pool(pool); ngx_destroy_pool(pool); return 0; }
最后我制作了一个里面包含了我学习nginx的一个镜像, 现在你们只需要执行下面命令就能拉取学习需要的环境了
docker pull registry.cn-hangzhou.aliyuncs.com/aclj/nginx:1.0.1
如果需要项目环境源代码,可以参考此github, 后续可根据需求增加东西,欢迎star~



