NGINX源码基础数据结构理解

       加入工作一年多了,最近准备读一些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;
}

今天对一些基础的数据结构总结就到这里,后面将对哈希表、红黑树进行系统理解。

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值