版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/scnu20142005027/article/details/69831333
在大体看完 unp 后本来想练手写一个小型 http 服务器的,但是感觉少了点什么,所以打算先学习一下 nginx 源码,看看大牛们是怎么设计的。然而突然发现自己好像不怎么了解 nginx,所以花了一个多星期翻完了《深入理解 Nginx》感兴趣的部分,加强了对 nginx 的了解。计划是以 linux 为系统环境,看完 http 核心模块,事件模块就看 epoll 模块,然后模仿着写一个小的(砍掉大部分功能的) http 服务器出来。
内存池
Nginx 中的内存池分为大块内存和小块内存,分别由链表连接起来,此外还可以设置销毁内存池时需要执行的函数,结构如下图所示。创建与销毁内存池一般由各内置模块执行,我们在开发第三方模块时一般只需要用预先构造好的内存池对象分配内存,不用手动去释放,这样简化了内存管理,降低了犯错的可能性。
结构体定义
struct ngx_pool_s {
ngx_pool_data_t d; // 内存池链表中各内存池的数据
size_t max; // 小块内存的界限
ngx_pool_t *current; // 小块内存池链表的首个可分配的内存池对象
ngx_chain_t *chain; // 空闲的 chain 可以拿来复用 详见 ngx_alloc_chain_link
ngx_pool_large_t *large; // 大块内存链表
ngx_pool_cleanup_t *cleanup; // 清理对象链表
ngx_log_t *log; // 日志对象
};
typedef struct {
u_char *last; // 内存池中可用内存起始位置
u_char *end; // 内存池的结束位置
ngx_pool_t *next; // 链表中下一个内存池对象
ngx_uint_t failed; // 分配内存失败次数 用于决定这块内存池是否可用
} ngx_pool_data_t;
typedef void (*ngx_pool_cleanup_pt)(void *data);
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // 处理函数
void *data; // 用户数据 作为 handler 的参数
ngx_pool_cleanup_t *next; // 链表中下一个清理对象
};
typedef struct {
ngx_fd_t fd; // 关联的文件描述符
u_char *name; // 关联的文件路径
ngx_log_t *log; // 日志对象
} ngx_pool_cleanup_file_t;
创建内存池
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;
}
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;
}
- 向系统申请一块内存,地址是需要对齐的,这里用的是
ngx_memalign
函数,这个函数简单地包装了posix_memalign
- 在申请到的内存中,由于起始位置用于存放
ngx_pool_t
,所以需要计算可用内存起始位置 - 计算小块内存的界限,如果内存池中可用内存小于
NGX_MAX_ALLOC_FROM_POOL
则界限为可用内存大小,否则为NGX_MAX_ALLOC_FROM_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) {
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;
}
}
}
- 遍历
cleanup
链表,执行handler
指定的函数 - 遍历
large
链表,根据alloc
成员判断是否有分配内存,有则释放 - 遍历内存池链表,逐个调用
ngx_free
释放内存,ngx_free
就是free
函数的宏定义
重置内存池
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; // 由于存放节点的空间都是在当前各个内存池中分配的,所以不用 free
}
- 遍历
large
链表,根据alloc
成员判断是否有分配内存,有则释放 - 遍历内存池链表,调整各个内存池的
last
到初始位置,并清零failed
成员 - 由于
chain
和large
的节点都是在内存池中分配的,所以可以不用释放,直接设置为NULL
分配内存
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
if (size <= pool->max) { // 根据大小判断是否为大内存
return ngx_palloc_small(pool, size, 1);
}
return ngx_palloc_large(pool, size);
}
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
if (size <= pool->max) {
return ngx_palloc_small(pool, size, 0);
}
return ngx_palloc_large(pool, size);
}
将size
与max
比较,以决定是否为大块内存,ngx_palloc
分配的内存是根据NGX_ALIGNMENT
对齐的,ngx_pnalloc
不一定是对齐的,这两个函数的区别是调用ngx_palloc_small
时的第三个参数,也就是决定是否需要对齐的参数
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;
do {
m = p->d.last;
if (align) {
m = ngx_align_ptr(m, NGX_ALIGNMENT);
}
if ((size_t) (p->d.end - m) >= size) { // 判断剩余空间是否足够
p->d.last = m + size;
return m;
}
p = p->d.next; // 不够则尝试下一个内存池
} while (p);
return ngx_palloc_block(pool, size); // 现有内存池的剩余空间都不足则分配新的内存池
}
从current
开始遍历内存池,若找到大小足够的内存池则更新last
成员,返回地址,否则调用ngx_palloc_block
分配新的内存池
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);
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;
new->d.failed = 0;
m += sizeof(ngx_pool_data_t); // 计算 last
m = ngx_align_ptr(m, NGX_ALIGNMENT);
new->d.last = m + size;
for (p = pool->current; p->d.next; p = p->d.next) { // 调整 current 指向的内存池
if (p->d.failed++ > 4) {
pool->current = p->d.next;
}
}
p->d.next = new; // 链表末尾接上新分配的内存池
return m;
}
- 根据原有的内存池大小分配一个新的内存池
- 设置新内存池的各个成员,由于
d
之后的成员都使用第一个内存池的,所以计算last
时与之前不同 - 递增各个内存池的
failed
的值,并根据其调整current
指向的内存池 - 链表末尾接上新分配的内存池
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;
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;
}
- 调用
ngx_alloc
分配内存,这个函数简单地包装了malloc
函数 - 在
large
链表中寻找没有分配内存的节点,考虑到效率问题,最多找 3 次,若找到则赋值并返回 - 若没找到则从内存池中分配存放
ngx_pool_large_t
的空间,设置各成员并接到large
链表头部
其他函数
剩余的函数逻辑较为直观简单,简单地记录下作用
函数 | 说明 |
---|---|
ngx_pmemalign | 分配对齐的内存,按指定的 alignment 参数对齐,分配的内存被当作大块内存挂在 large 链表 |
ngx_pfree | 释放指定地址的大块内存 |
ngx_pcalloc | 调用 ngx_palloc 分配内存并清零 |
ngx_pool_cleanup_add | 添加清理对象 |
ngx_pool_run_cleanup_file | 执行 handler 为 ngx_pool_cleanup_file 的清理对象 |
下面的是提供的一些ngx_pool_cleanup_pt
函数
函数 | 说明 |
---|---|
ngx_pool_cleanup_file | 关闭指定的文件描述符 |
ngx_pool_delete_file | 删除并关闭指定的文件描述符 |