加入工作一年多了,最近准备读一些nginx源码加强一下读代码的能力,找了相关电子书籍学习了一下,记录一下学习笔记、个人理解和测试源码。
第一章:nginx的内存池管理:结合源码和书籍、其它CSDN文档的解释,内存分为大块内存和小块内存,大块内存则由ngx_pool_t中ngx_pool_large_t*指针(其实就是一个链表,每个表节点管理一片大内存的首地址和下一个节点)管理:
链表销毁时会编译所有节点销毁内存:
小内存块则统一由内存池分配的内存来管理,分一块小的内存地址供使用,如果内存池空间不足以分配小内存,注意到有个ngx_pool_data_t的结构,里面有一个next指针,多片小块内存其实也是由一块链表来维护的,如果这个节点不够分就把current指针指向下一个节点,让当前有内存的节点去进行分配。P上其它一个大佬总结的指针图,结合代码可以看的非常清楚:
常用的函数:
/* 创建内存池,该函数定义于 src/core/ngx_palloc.c 文件中 */
ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log);
/* 销毁内存池 */
void ngx_destroy_pool(ngx_pool_t *pool);
//获取对齐的内存
void *ngx_palloc(ngx_pool_t *pool, size_t size);
//获取初始化为0的内存
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
结合API来看,个人认为nginx内存管理性能上存在做的不好的地方,就是会产生内存空洞,比如A B C 各拿走了一片内存,B内存现在已经不要用了,这片内存在调用ngx_destroy_pool之前既不能回收也不能复用,却又占用内存空间,如果用ngx_reset_pool的话,倘若A B还在用,那总不能把别人的内存也干掉了,另一方面会造成内存池”虚假分配满“的场景,假设A B C D E各自分配了这个pool的一点空间,A C E的内存都用不到了,但是(ngx_pool_t*)pool->d.last还是指向E后面的那块内存,再次分配内存时可能会让系统误认为内存不足而再去新增一个ngx_pool_t*的内存节点,设想如果一块内存池长期使用不停止并反复分配,虽然不至于内存泄漏,但是却出现大量的内存闲置的节点,作为一个代理服务器,这样就真的合理吗。所以本人在自己手撕共享内存池时就考虑到这个问题,给每片内存分为n*size块,用头部数组节点寻址的方式管理,每块都有nextPtr指针,当整块内存不用时就遍历nextPtr地址整块释放,被释放的块下一次还能重复利用。
第二章:数组和链表管理
之所以选择数组和链表一起写,是因为其实链表里面是覆盖了数组的管理模式的,可以看看链表的结构体定义:
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;
struct ngx_list_part_s {
void *elts;
ngx_uint_t nelts;
ngx_list_part_t *next;
};
一般初始化的都是ngx_list_t的结构,而真正管理首节点的是ngx_list_part_t part; 注意到每个节点都有个elts(元素存储位置)和nelts(元素存储个数),可见其实这个链表是允许每个节点存储多个元素的,当执行void *ngx_list_push(ngx_list_t *l);时,链表并非直接去创建一个新的节点,而是去看这个节点内部的结构中nelts的个数是不是比nalloc来的小,如果比他小则从这个节点的连续内存数组中取一块内存指针,并让用户层往指针里面覆盖信息。如果last->nelts == l->nalloc说明这个节点里面自带的数组的内存已经用完,需要再创建一个新的节点,源码实现如下:
if (last->nelts == l->nalloc) {
/* the last part is full, allocate a new list part */
last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
if (last == NULL) {
return NULL;
}
last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
if (last->elts == NULL) {
return NULL;
}
last->nelts = 0;
last->next = NULL;
l->last->next = last;
l->last = last;
}
elt = (char *) last->elts + l->size * last->nelts;
last->nelts++;
需要注意的是使用者在初始化链表内存时,链表的大小理想情况最好要比sizeof(类型)大1,因为单节点的多个数组内存空间之间是连续的,如果没有分隔符,所有数据是处于一片连续的内存空间中,操作不当的情况就容易导致越界使用。其次注意到elts的内存是由palloc分配的,这意味着数组里面的东西我们使用前最好要memset一下,以免不测。
接下来讨论链表在内存上分配的情况,参考书籍和网上资料图片:
如图所示可以很清楚的看到内存占用的情况,实测打印也确实符合情况,测试代码为:
/*
* Copyright (C) Mengying
* Copyright (C) Mengying, Inc.
*/
#include<stdio.h>
#include"ngx_conf_file.h"
#include"ngx_config.h"
#include"ngx_palloc.h"
#include"nginx.h"
#include"ngx_core.h"
#include"ngx_string.h"
#include"ngx_list.h"
void dump_list_part(ngx_list_t* list,ngx_list_part_t* part)
{
char* ptr =(char*)(part->elts);
int loop = 0,i = 0;
printf("打印offset:%li\n",((int)ptr - (int)((char*)(list)) + sizeof(ngx_list_t)));
//printf("打印数组第%d节点元素\n", ptr[0]);
for(i = 0;i < part->nelts;++i)
{
printf("%s ", ptr + i*list->size);
}
printf("\n");
}
void dump_list(ngx_list_t* list)
{
if(list)
{
printf("last:%d\n",&(list->last));
printf("nalloc:%d\n",&(list->nalloc));
printf("part:%d\n",&(list->part));
printf("pool:%d\n",(list->pool));
printf("size:%d\n",&(list->size));
printf("pool_size:%d\n",sizeof(ngx_list_part_t*));
printf("==================================\n");
printf("occupid mem:%li\n",(int)(list->last) - (int)list);
printf("size = %d\n", list->size);
ngx_list_part_t *part = &(list->part);
while(part)
{
dump_list_part(list, part);
part = part->next;
}
}
}
void exampleForListProgam()
{
ngx_pool_t* pool;
int i;
pool = ngx_create_pool(1024,NULL);
ngx_list_t* list = ngx_list_create(pool,5,8);
if(NULL == list)
{
printf("create list error");
return;
}
for(i=0;i<6;++i)
{
char* ptr = ngx_list_push(list);
memset((void*)(ptr),'\0',8);
strcpy((char*)(ptr),"123\0");
// if(i == 3)
// ptr
}
dump_list(list);
ngx_destroy_pool(pool);
return ;
}
int main()
{
exampleForListProgam();
return 0;
}
编译源码时注意要先进行./configure配置,把如下两个头文件也要含进去,以及os/unix的包含路径,不然编译会爆红:
值得吐槽的是这链表也做的太拉了,居然都不支持遍历删除等等,源码也没看见相关实现,或许作者自身用不到?!
看完链表后ngx_array_t结构其实就类似链表单节点数组管理的模式,具体参考源码,这里不多解释了。
第三章:队列结构
这个书上总结说的很好:在 Nginx 的队列实现中,实质就是 具有头节点的双向循环链表,这里的双向链表中的节点是没有数据区的,只有两个指向节点的指针。在编译测试代码时就明显发现这点,因为队列整个都是用列表宏来维护的,并未用到他们提供的排序函数之类,编译甚至都不用把ngx_queue.c编译进去。没有数据区的问题,就需要在声明队列结构时采用类似如下方式:
struct XXX
{
Type score;
ngx_queue_t Que;
};
//访问链表数据区
//node:链表节点指针
struct XXX* data = ngx_queue_data(node,XXX,Que);
//查询相关宏定义就是:
#define ngx_queue_data(q, type, link) \
(type *) ((u_char *) q - offsetof(type, link))
//通过q扣掉Que在XXX这个结构中的偏移量来获取数据的首地址
也就是一般在结构体尾部搞个队列指针,然后数据域在前面,由此访问数据区域。
典型的体现链表结构的宏定义:
//初始化队列,头节点和尾节点一致
#define ngx_queue_init(q) \
(q)->prev = q; \
(q)->next = q
//队列判空
#define ngx_queue_empty(h) \
(h == (h)->prev)
//头插队列
#define ngx_queue_insert_head(h, x) \
(x)->next = (h)->next; \
(x)->next->prev = x; \
(x)->prev = h; \
(h)->next = x
#define ngx_queue_insert_after ngx_queue_insert_head
//尾部插入
#define ngx_queue_insert_tail(h, x) \
(x)->prev = (h)->prev; \
(x)->prev->next = x; \
(x)->next = h; \
(h)->prev = x
测试代码:
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;
#include<stdio.h>
#include"ngx_queue.h"
#include"ngx_conf_file.h"
#include"ngx_config.h"
#include"ngx_palloc.h"
#include"nginx.h"
#include"ngx_core.h"
#include"ngx_string.h"
#include"ngx_list.h"
#define MAX 10
typedef struct Score
{
unsigned int score;
ngx_queue_t Que;
}ngx_queue_score;
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,...)
{
}
ngx_int_t CMP(const ngx_queue_t* x,const ngx_queue_t* y)
{
ngx_queue_score* xinfo = ngx_queue_data(x,ngx_queue_score, Que);
ngx_queue_score* yinfo = ngx_queue_data(y,ngx_queue_score, Que);
return(xinfo->score > yinfo->score);
}
void print_ngx_queue(ngx_queue_t* queue)
{
ngx_queue_t* q = ngx_queue_head(queue);
printf("Score:");
for(;q != ngx_queue_sentinel(queue);q=ngx_queue_next(q))
{
ngx_queue_score* ptr = ngx_queue_data(q,ngx_queue_score,Que);
if(ptr)
{
printf(" %d\t",ptr->score);
}
}
}
void exampleForQueueProgram()
{
ngx_pool_t* pool;
ngx_queue_t* queue;
ngx_queue_score* QScore;
pool = ngx_create_pool(1024,NULL);
queue = ngx_palloc(pool,sizeof(ngx_queue_t));
ngx_queue_init(queue);
int i;
for(i=1;i<MAX;++i)
{
QScore = (ngx_queue_score*)ngx_palloc(pool,sizeof(ngx_queue_score));
QScore->score = i;
ngx_queue_init(&QScore->Que);
if(i % 2)
{
ngx_queue_insert_tail(queue,&QScore->Que);
}
else
{
ngx_queue_insert_head(queue,&QScore->Que);
}
}
print_ngx_queue(queue);
ngx_destroy_pool(pool);
// ngx_queue_sort();
}
int main()
{
exampleForQueueProgram();
return 0;
}
今天对一些基础的数据结构总结就到这里,后面将对哈希表、红黑树进行系统理解。