• nginx源码分析—内存池结构ngx_pool_t及内存管理


    Content

    0. 

    1. 内存池结构

    1.1 ngx_pool_t结构

    1.2 其他相关结构

    1.3 ngx_pool_t的逻辑结构

    2. 内存池操作

    2.1 创建内存池

    2.2 销毁内存池

    2.3 重置内存池

    2.4 分配内存

    2.4.1 ngx_palloc()函数分析

    2.4.2 ngx_palloc_block()函数分析

    2.5 释放内存

    2.6 注册cleanup

    2.7 内存池的物理结构

    3. 一个例子

    3.1 代码

    3.2 如何编译

    3.3 运行结果

    4. 小结

    5. 致谢

    0. 

     

    nginx对内存的管理由其自己实现的内存池结构ngx_pool_t来完成,本文重点叙述nginx的内存管理。

     

    nginx内存管理相关文件:

    (1) ./src/os/unix/ngx_alloc.h/.c

    • 内存相关的操作,封装了最基本的内存分配函数
    • free/malloc/memalign/posix_memalign,分别被封装为ngx_freengx_alloc/ngx_calloc, ngx_memalign
      • ngx_alloc:封装malloc分配内存
      • ngx_calloc:封装malloc分配内存,并初始化空间内容为0
      • ngx_memalign:返回基于一个指定alignment的大小为size的内存空间,且其地址为alignment的整数倍,alignment2的幂。

    (2) ./src/core/ngx_palloc.h/.c

    • 封装创建/销毁内存池,从内存池分配空间等函数

     

    .表示nginx-1.0.4代码目录,本文为/usr/src/nginx-1.0.4

     

    1. 内存池结构

    nginx对内存的管理均统一完成,例如,在特定的生命周期统一建立内存池(main函数系统启动初期即分配1024B大小的内存池),需要内存时统一分配内存池中的内存,在适当的时候释放内存池的内存(如关闭http链接时调用ngx_destroy_pool进行销毁)

    因此,开发者只需在需要内存时进行申请即可,不用过多考虑内存的释放等问题,大大提高了开发的效率。先看一下内存池结构。

     

    1.1 ngx_pool_t结构

    此处统一一下概念,内存池的数据块:即分配内存在这些数据块中进行,一个内存池可以有多一个内存池数据块。nginx的内存池结构如下。 

    [cpp] view plaincopy
     
    1. 00048: typedef struct {  
    2. 00049:   u_char      *last;  //当前内存池分配到此处,即下一次分配从此处开始  
    3. 00050:   u_char      *end;   //内存池结束位置  
    4. 00051:   ngx_pool_t  *next;  //内存池里面有很多块内存,这些内存块就是通过该指针连成链表的  
    5. 00052:   ngx_uint_t  failed; //内存池分配失败次数  
    6. 00053: } ngx_pool_data_t;    //内存池的数据块位置信息  
    7. 00054:  
    8. 00055:  
    9. 00056: struct ngx_pool_s{    //内存池头部结构  
    10. 00057:    ngx_pool_data_t     d;       //内存池的数据块  
    11. 00058:    size_t              max;     //内存池数据块的最大值  
    12. 00059:    ngx_pool_t         *current; //指向当前内存池  
    13. 00060:    ngx_chain_t        *chain;   //该指针挂接一个ngx_chain_t结构  
    14. 00061:    ngx_pool_large_t   *large;   //大块内存链表,即分配空间超过max的内存  
    15. 00062:    ngx_pool_cleanup_t *cleanup; //释放内存池的callback  
    16. 00063:    ngx_log_t          *log;     //日志信息  
    17. 00064: };  

    其中,sizeof(ngx_pool_data_t)=16Bsizeof(ngx_pool_t)=40B
    nginx将几乎所有的结构体放在ngx_core.h文件中重新进行了申明,如下。

    [cpp] view plaincopy
     
    1. typedef struct ngx_module_s      ngx_module_t;  
    2. typedef struct ngx_conf_s        ngx_conf_t;  
    3. typedef struct ngx_cycle_s       ngx_cycle_t;  
    4. typedef struct ngx_pool_s        ngx_pool_t;  
    5. typedef struct ngx_chain_s       ngx_chain_t;  
    6. typedef struct ngx_log_s         ngx_log_t;  
    7. typedef struct ngx_array_s       ngx_array_t;  
    8. typedef struct ngx_open_file_s   ngx_open_file_t;  
    9. typedef struct ngx_command_s     ngx_command_t;  
    10. typedef struct ngx_file_s        ngx_file_t;  
    11. typedef struct ngx_event_s       ngx_event_t;  
    12. typedef struct ngx_event_aio_s   ngx_event_aio_t;  
    13. typedef struct ngx_connection_s  ngx_connection_t;  

    1.2 其他相关结构

    其他与内存池相干的数据结构,如清除资源的cleanup链表,分配的大块内存链表等,如下。

    [cpp] view plaincopy
     
    1. 00015: /* 
    2. 00016: * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86. 
    3. 00017: * On Windows NT it decreases a number of locked pages in a kernel. 
    4. 00018: */  
    5. 00019: #define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)  //在x86体系结构下,该值一般为4096B,即4K  
    6. 00020:  
    7. 00021: #define NGX_DEFAULT_POOL_SIZE    (16* 1024)  
    8. 00022:  
    9. 00023: #define NGX_POOL_ALIGNMENT    16  
    10. 00024: #define NGX_MIN_POOL_SIZE      
    11. 00025:    ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)),      
    12. 00026:    NGX_POOL_ALIGNMENT)  
    13. 00027:  
    14. 00028:  
    15. 00029: typedef void (*ngx_pool_cleanup_pt)(void *data);    //cleanup的callback类型  
    16. 00030:  
    17. 00031: typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;  
    18. 00032:  
    19. 00033: struct ngx_pool_cleanup_s{  
    20. 00034:    ngx_pool_cleanup_pt handler;  
    21. 00035:    void    *data;              //指向要清除的数据  
    22. 00036:    ngx_pool_cleanup_t *next;   //下一个cleanup callback  
    23. 00037: };  
    24. 00038:  
    25. 00039:  
    26. 00040: typedef struct ngx_pool_large_s ngx_pool_large_t;  
    27. 00041:  
    28. 00042: struct ngx_pool_large_s{  
    29. 00043:    ngx_pool_large_t  *next;    //指向下一块大块内存  
    30. 00044:    void    *alloc;             //指向分配的大块内存  
    31. 00045: };  
    32. ...  
    33. ...  
    34. 00067: typedef struct {  
    35. 00068:    ngx_fd_t   fd;  
    36. 00069:    u_char    *name;  
    37. 00070:    ngx_log_t *log;  
    38. 00071: } ngx_pool_cleanup_file_t;  
    39. 00072:  

    (gdb) p getpagesize()

    $18 = 4096

     

    全局变量ngx_pagesize的初始化是在如下函数中完成的。./src/os/unix/ngx_posix_init.c

    [cpp] view plaincopy
     
    1. ngx_int_t  
    2. ngx_os_init(ngx_log_t *log)  
    3. {  
    4.     ngx_uint_t  n;  
    5.   
    6. #if (NGX_HAVE_OS_SPECIFIC_INIT)  
    7.     if (ngx_os_specific_init(log) != NGX_OK) {  
    8.         return NGX_ERROR;  
    9.     }  
    10. #endif  
    11.   
    12.     ngx_init_setproctitle(log);  
    13.   
    14.     /** 该函数为glibc的库函数,由系统调用实现,返回内核中的PAGE_SIZE,该值依赖体系结构*/  
    15.     ngx_pagesize = getpagesize();        
    16.     ngx_cacheline_size = NGX_CPU_CACHE_LINE;  
    17.     ...  
    18. }  

    这些数据结构之间的关系,请参考后面的图。

     

    1.3 ngx_pool_t的逻辑结构

     

    这些数据结构逻辑结构图如下。注:本文采用UML的方式画出该图。

    2. 内存池操作

    2.1 创建内存池

    创建内存池有ngx_create_pool()函数完成,代码如下。

    [cpp] view plaincopy
     
    1. 00015: ngx_pool_t *  
    2. 00016: ngx_create_pool(size_t size, ngx_log_t *log)  
    3. 00017: {  
    4. 00018:    ngx_pool_t *p;  
    5. 00019:  
    6. 00020:    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  
    7. 00021:    if (p == NULL) {  
    8. 00022:       return NULL;  
    9. 00023:    }  
    10. 00024:  
    11. 00025:    p->d.last = (u_char *) p + sizeof(ngx_pool_t);  //last指向ngx_pool_t结构体之后数据取起始位置  
    12. 00026:    p->d.end = (u_char *) p + size;  //end指向分配的整个size大小的内存的末尾  
    13. 00027:    p->d.next = NULL;  
    14. 00028:    p->d.failed = 0;  
    15. 00029:  
    16. 00030:    size = size - sizeof(ngx_pool_t);  
    17. 00031:    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;  //最大不超过4095B  
    18. 00032:  
    19. 00033:    p->current = p;  
    20. 00034:    p->chain = NULL;  
    21. 00035:    p->large = NULL;  
    22. 00036:    p->cleanup = NULL;  
    23. 00037:    p->log = log;  
    24. 00038:  
    25. 00039:    return p;  
    26. 00040: }  

    例如,调用ngx_create_pool(1024, 0x80d1c4c)后,创建的内存池物理结构如下图。

    2.2 销毁内存池

     

    销毁内存池由如下函数完成。

    void ngx_destroy_pool(ngx_pool_t *pool)

    该函数将遍历内存池链表,所有释放内存,如果注册了clenup(也是一个链表结构)亦将遍历该cleanup链表结构依次调用clenuphandler清理。同时,还将遍历large链表,释放大块内存。

     

    2.3 重置内存池

     

    重置内存池由下面的函数完成。

    void ngx_reset_pool(ngx_pool_t *pool);

    该函数将释放所有large内存,并且将d->last指针重新指向ngx_pool_t结构之后数据区的开始位置,同刚创建后的位置相同。

     

    2.4 分配内存

     

    内存分配的函数如下。

    void *ngx_palloc(ngx_pool_t *pool, size_t size);

    void *ngx_pnalloc(ngx_pool_t *pool, size_t size);

    void *ngx_pcalloc(ngx_pool_t *pool, size_t size);

    void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);

     

    返回值为分配的内存起始地址。选择其中的两个函数进行分析,其他的也很好理解,省略。

     

    2.4.1 ngx_palloc()函数分析

     

    ngx_palloc()代码如下,分析请参考笔者所加的注释。

    [cpp] view plaincopy
     
    1. 00115: void *  
    2. 00116: ngx_palloc(ngx_pool_t *pool, size_t size)  
    3. 00117: {  
    4. 00118:    u_char    *m;  
    5. 00119:    ngx_pool_t *p;  
    6. 00120:  
    7. 00121:    if (size <= pool->max) {//判断待分配内存与max值  
    8. 00122:  
    9. 00123:       p = pool->current;   //小于max值,则从current节点开始遍历pool链表  
    10. 00124:  
    11. 00125:       do {  
    12. 00126:          m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);  
    13. 00127:  
    14. 00128:          if ((size_t) (p->d.end - m) >= size) {  
    15. 00129:             p->d.last = m + size;  //在该节点指向的内存块中分配size大小的内存  
    16. 00130:  
    17. 00131:             return m;  
    18. 00132:          }  
    19. 00133:  
    20. 00134:          p = p->d.next;  
    21. 00135:  
    22. 00136:       } while (p);  
    23. 00137:  
    24. 00138:       return ngx_palloc_block(pool, size); //链表里没有能分配size大小内存的节点,则生成一个新的节点并在其中分配内存  
    25. 00139:    }  
    26. 00140:  
    27. 00141:    return ngx_palloc_large(pool, size);  //大于max值,则在large链表里分配内存  
    28. 00142: }  

    例如,在2.1节中创建的内存池中分配200B的内存,调用ngx_palloc(pool, 200)后,该内存池物理结构如下图。

    2.4.2 ngx_palloc_block()函数分析

     

    ngx_palloc_block函数代码如下,分析请参考笔者所加的注释。

    [cpp] view plaincopy
     
    1. 00175: static void *  
    2. 00176: ngx_palloc_block(ngx_pool_t *pool, size_t size)  
    3. 00177: {  
    4. 00178:    u_char    *m;  
    5. 00179:    size_t    psize;  
    6. 00180:    ngx_pool_t *p, *new, *current;  
    7. 00181:  
    8. 00182:    psize = (size_t) (pool->d.end - (u_char *) pool);      //计算pool的大小  
    9. 00183:  
    10. 00184:    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);//分配一块与pool大小相同的内存  
    11. 00185:    if (m == NULL) {  
    12. 00186:       return NULL;  
    13. 00187:    }  
    14. 00188:  
    15. 00189:    new = (ngx_pool_t *) m;  
    16. 00190:  
    17. 00191:    new->d.end = m + psize; //设置end指针  
    18. 00192:    new->d.next = NULL;  
    19. 00193:    new->d.failed = 0;  
    20. 00194:  
    21. 00195:    m += sizeof(ngx_pool_data_t); //让m指向该块内存ngx_pool_data_t结构体之后数据区起始位置  
    22. 00196:    m = ngx_align_ptr(m, NGX_ALIGNMENT); //按4字节对齐  
    23. 00197:    new->d.last = m + size;       //在数据区分配size大小的内存并设置last指针  
    24. 00198:  
    25. 00199:    current = pool->current;  
    26. 00200:  
    27. 00201:    for (p = current; p->d.next; p = p->d.next) {  
    28. 00202:       if (p->d.failed++ > 4) {   //failed的值只在此处被修改  
    29. 00203:          current = p->d.next;    //失败4次以上移动current指针  
    30. 00204:       }  
    31. 00205:    }  
    32. 00206:  
    33. 00207:    p->d.next = new;  //将这次分配的内存块new加入该内存池  
    34. 00208:  
    35. 00209:    pool->current = current ? current : new;  
    36. 00210:  
    37. 00211:    return m;  
    38. 00212: }  

    注意:该函数分配一块内存后,last指针指向的是ngx_pool_data_t结构体(大小16B)之后数据区的起始位置。而创建内存池时时,last指针指向的是ngx_pool_t结构体(大小40B)之后数据区的起始位置。

     

    结合2.7节的内存池的物理结构,更容易理解。

     

    2.5 释放内存

     

    请参考如下函数,不再赘述。 

    ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)

    需要注意的是该函数只释放large链表中注册的内存,普通内存在ngx_destroy_pool中统一释放。

     

    2.6 注册cleanup

     

    请参考如下函数,该函数实现也很简单,此处不再赘述。

    ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)

     

    2.7 内存池的物理结构

     

    针对本文第3节的例子,画出的内存池的物理结构如下图。

      

    从该图也能看出2.4节的结论,即内存池第一块内存前40字节为ngx_pool_t结构,后续加入的内存块前16个字节为ngx_pool_data_t结构,这两个结构之后便是真正可以分配内存区域。

     

    因此,本文Reference中的内存分配相关中的图是有一点点小问题的,并不是每一个节点的前面都是ngx_pool_t结构。

     

    3. 一个例子

     

    理解并掌握开源软件的最好方式莫过于自己写一些测试代码,或者改写软件本身,并进行调试来进一步理解开源软件的原理和设计方法。本节给出一个创建内存池并从中分配内存的简单例子。

     

    3.1 代码

    [cpp] view plaincopy
     
    1. /** 
    2.  * ngx_pool_t test, to test ngx_palloc, ngx_palloc_block, ngx_palloc_large 
    3.  */  
    4.   
    5. #include <stdio.h>  
    6. #include "ngx_config.h"  
    7. #include "ngx_conf_file.h"  
    8. #include "nginx.h"  
    9. #include "ngx_core.h"  
    10. #include "ngx_string.h"  
    11. #include "ngx_palloc.h"  
    12.   
    13. volatile ngx_cycle_t  *ngx_cycle;  
    14.   
    15. void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,  
    16.             const char *fmt, ...)  
    17. {  
    18. }  
    19.   
    20. void dump_pool(ngx_pool_t* pool)  
    21. {  
    22.     while (pool)  
    23.     {  
    24.         printf("pool = 0x%x ", pool);  
    25.         printf("  .d ");  
    26.         printf("    .last = 0x%x ", pool->d.last);  
    27.         printf("    .end = 0x%x ", pool->d.end);  
    28.         printf("    .next = 0x%x ", pool->d.next);  
    29.         printf("    .failed = %d ", pool->d.failed);  
    30.         printf("  .max = %d ", pool->max);  
    31.         printf("  .current = 0x%x ", pool->current);  
    32.         printf("  .chain = 0x%x ", pool->chain);  
    33.         printf("  .large = 0x%x ", pool->large);  
    34.         printf("  .cleanup = 0x%x ", pool->cleanup);  
    35.         printf("  .log = 0x%x ", pool->log);  
    36.         printf("available pool memory = %d ", pool->d.end - pool->d.last);  
    37.         pool = pool->d.next;  
    38.     }  
    39. }  
    40.   
    41. int main()  
    42. {  
    43.     ngx_pool_t *pool;  
    44.   
    45.     printf("-------------------------------- ");  
    46.     printf("create a new pool: ");  
    47.     printf("-------------------------------- ");  
    48.     pool = ngx_create_pool(1024, NULL);  
    49.     dump_pool(pool);  
    50.   
    51.     printf("-------------------------------- ");  
    52.     printf("alloc block 1 from the pool: ");  
    53.     printf("-------------------------------- ");  
    54.     ngx_palloc(pool, 512);  
    55.     dump_pool(pool);  
    56.   
    57.     printf("-------------------------------- ");  
    58.     printf("alloc block 2 from the pool: ");  
    59.     printf("-------------------------------- ");  
    60.     ngx_palloc(pool, 512);  
    61.     dump_pool(pool);  
    62.   
    63.     printf("-------------------------------- ");  
    64.     printf("alloc block 3 from the pool : ");  
    65.     printf("-------------------------------- ");  
    66.     ngx_palloc(pool, 512);  
    67.     dump_pool(pool);  
    68.   
    69.     ngx_destroy_pool(pool);  
    70.     return 0;  
    71. }  

     

    3.2 如何编译

     

    这个问题是编写测试代码或者改写软件本身最迫切需要解决的问题,否则,编写的代码无从编译或运行,那也无从进行调试并理解软件了。

     

    如何对自己编写的测试代码进行编译,可参考Linux平台代码覆盖率测试-编译过程自动化及对链接的解释Linux平台如何编译使用Google test写的单元测试?。我们要做的是学习这种编译工程的方法,针对该例子,笔者编写的makefile文件如下。——这便是本节的主要目的。

    [plain] view plaincopy
     
    1. CXX = gcc  
    2. CXXFLAGS += -g -Wall -Wextra  
    3.   
    4. NGX_ROOT = /usr/src/nginx-1.0.4  
    5.   
    6. TARGETS = ngx_pool_t_test  
    7. TARGETS_C_FILE = $(TARGETS).c  
    8.   
    9. CLEANUP = rm -f $(TARGETS) *.o  
    10.   
    11. all: $(TARGETS)  
    12.   
    13. clean:  
    14.     $(CLEANUP)  
    15.   
    16. CORE_INCS = -I.   
    17.     -I$(NGX_ROOT)/src/core   
    18.     -I$(NGX_ROOT)/src/event   
    19.     -I$(NGX_ROOT)/src/event/modules   
    20.     -I$(NGX_ROOT)/src/os/unix   
    21.     -I$(NGX_ROOT)/objs   
    22.   
    23. NGX_PALLOC = $(NGX_ROOT)/objs/src/core/ngx_palloc.o  
    24. NGX_STRING = $(NGX_ROOT)/objs/src/core/ngx_string.o  
    25. NGX_ALLOC = $(NGX_ROOT)/objs/src/os/unix/ngx_alloc.o  
    26.   
    27. $(TARGETS): $(TARGETS_C_FILE)  
    28.     $(CXX) $(CXXFLAGS) $(CORE_INCS) $(NGX_PALLOC) $(NGX_STRING) $(NGX_ALLOC) $^ -o $@  

    3.3 运行运行结果

    [plain] view plaincopy
     
    1. # ./ngx_pool_t_test  
    2. --------------------------------  
    3. create a new pool:  
    4. --------------------------------  
    5. pool = 0x8922020  
    6.   .d  
    7.     .last = 0x8922048  
    8.     .end = 0x8922420  
    9.     .next = 0x0  
    10.     .failed = 0  
    11.   .max = 984  
    12.   .current = 0x8922020  
    13.   .chain = 0x0  
    14.   .large = 0x0  
    15.   .cleanup = 0x0  
    16.   .log = 0x0  
    17. available pool memory = 984  
    18.   
    19. --------------------------------  
    20. alloc block 1 from the pool:  
    21. --------------------------------  
    22. pool = 0x8922020  
    23.   .d  
    24.     .last = 0x8922248  
    25.     .end = 0x8922420  
    26.     .next = 0x0  
    27.     .failed = 0  
    28.   .max = 984  
    29.   .current = 0x8922020  
    30.   .chain = 0x0  
    31.   .large = 0x0  
    32.   .cleanup = 0x0  
    33.   .log = 0x0  
    34. available pool memory = 472  
    35.   
    36. --------------------------------  
    37. alloc block 2 from the pool:  
    38. --------------------------------  
    39. pool = 0x8922020  
    40.   .d  
    41.     .last = 0x8922248  
    42.     .end = 0x8922420  
    43.     .next = 0x8922450  
    44.     .failed = 0  
    45.   .max = 984  
    46.   .current = 0x8922020  
    47.   .chain = 0x0  
    48.   .large = 0x0  
    49.   .cleanup = 0x0  
    50.   .log = 0x0  
    51. available pool memory = 472  
    52.   
    53. pool = 0x8922450  
    54.   .d  
    55.     .last = 0x8922660  
    56.     .end = 0x8922850  
    57.     .next = 0x0  
    58.     .failed = 0  
    59.   .max = 0  
    60.   .current = 0x0  
    61.   .chain = 0x0  
    62.   .large = 0x0  
    63.   .cleanup = 0x0  
    64.   .log = 0x0  
    65. available pool memory = 496  
    66.   
    67. --------------------------------  
    68. alloc block 3 from the pool :  
    69. --------------------------------  
    70. pool = 0x8922020  
    71.   .d  
    72.     .last = 0x8922248  
    73.     .end = 0x8922420  
    74.     .next = 0x8922450  
    75.     .failed = 1  
    76.   .max = 984  
    77.   .current = 0x8922020  
    78.   .chain = 0x0  
    79.   .large = 0x0  
    80.   .cleanup = 0x0  
    81.   .log = 0x0  
    82. available pool memory = 472  
    83.   
    84. pool = 0x8922450  
    85.   .d  
    86.     .last = 0x8922660  
    87.     .end = 0x8922850  
    88.     .next = 0x8922880  
    89.     .failed = 0  
    90.   .max = 0  
    91.   .current = 0x0  
    92.   .chain = 0x0  
    93.   .large = 0x0  
    94.   .cleanup = 0x0  
    95.   .log = 0x0  
    96. available pool memory = 496  
    97.   
    98. pool = 0x8922880  
    99.   .d  
    100.     .last = 0x8922a90  
    101.     .end = 0x8922c80  
    102.     .next = 0x0  
    103.     .failed = 0  
    104.   .max = 0  
    105.   .current = 0x0  
    106.   .chain = 0x0  
    107.   .large = 0x0  
    108.   .cleanup = 0x0  
    109.   .log = 0x0  
    110. available pool memory = 496  

    4. 小结

     

    本文针对nginx-1.0.4的内存管理进行了较为全面的分析,包括相关内存池数据结构,内存池的创建、销毁,以及从内存池中分配内存等。最后通过一个简单例子向读者展示nginx内存池的创建和分配操作,同时借此向读者展示编译测试代码的方法。

     

    分析完nginx的内存管理,你一定惊叹于nginx作者的聪明才智。这种内存管理的设计方法小巧、快捷,值得借鉴!

    Content

    0. 

    1. 内存池结构

    1.1 ngx_pool_t结构

    1.2 其他相关结构

    1.3 ngx_pool_t的逻辑结构

    2. 内存池操作

    2.1 创建内存池

    2.2 销毁内存池

    2.3 重置内存池

    2.4 分配内存

    2.4.1 ngx_palloc()函数分析

    2.4.2 ngx_palloc_block()函数分析

    2.5 释放内存

    2.6 注册cleanup

    2.7 内存池的物理结构

    3. 一个例子

    3.1 代码

    3.2 如何编译

    3.3 运行结果

    4. 小结

    5. 致谢

    0. 

     

    nginx对内存的管理由其自己实现的内存池结构ngx_pool_t来完成,本文重点叙述nginx的内存管理。

     

    nginx内存管理相关文件:

    (1) ./src/os/unix/ngx_alloc.h/.c

    • 内存相关的操作,封装了最基本的内存分配函数
    • free/malloc/memalign/posix_memalign,分别被封装为ngx_freengx_alloc/ngx_calloc, ngx_memalign
      • ngx_alloc:封装malloc分配内存
      • ngx_calloc:封装malloc分配内存,并初始化空间内容为0
      • ngx_memalign:返回基于一个指定alignment的大小为size的内存空间,且其地址为alignment的整数倍,alignment2的幂。

    (2) ./src/core/ngx_palloc.h/.c

    • 封装创建/销毁内存池,从内存池分配空间等函数

     

    .表示nginx-1.0.4代码目录,本文为/usr/src/nginx-1.0.4

     

    1. 内存池结构

    nginx对内存的管理均统一完成,例如,在特定的生命周期统一建立内存池(main函数系统启动初期即分配1024B大小的内存池),需要内存时统一分配内存池中的内存,在适当的时候释放内存池的内存(如关闭http链接时调用ngx_destroy_pool进行销毁)

    因此,开发者只需在需要内存时进行申请即可,不用过多考虑内存的释放等问题,大大提高了开发的效率。先看一下内存池结构。

     

    1.1 ngx_pool_t结构

    此处统一一下概念,内存池的数据块:即分配内存在这些数据块中进行,一个内存池可以有多一个内存池数据块。nginx的内存池结构如下。 

    [cpp] view plaincopy
     
    1. 00048: typedef struct {  
    2. 00049:   u_char      *last;  //当前内存池分配到此处,即下一次分配从此处开始  
    3. 00050:   u_char      *end;   //内存池结束位置  
    4. 00051:   ngx_pool_t  *next;  //内存池里面有很多块内存,这些内存块就是通过该指针连成链表的  
    5. 00052:   ngx_uint_t  failed; //内存池分配失败次数  
    6. 00053: } ngx_pool_data_t;    //内存池的数据块位置信息  
    7. 00054:  
    8. 00055:  
    9. 00056: struct ngx_pool_s{    //内存池头部结构  
    10. 00057:    ngx_pool_data_t     d;       //内存池的数据块  
    11. 00058:    size_t              max;     //内存池数据块的最大值  
    12. 00059:    ngx_pool_t         *current; //指向当前内存池  
    13. 00060:    ngx_chain_t        *chain;   //该指针挂接一个ngx_chain_t结构  
    14. 00061:    ngx_pool_large_t   *large;   //大块内存链表,即分配空间超过max的内存  
    15. 00062:    ngx_pool_cleanup_t *cleanup; //释放内存池的callback  
    16. 00063:    ngx_log_t          *log;     //日志信息  
    17. 00064: };  

    其中,sizeof(ngx_pool_data_t)=16Bsizeof(ngx_pool_t)=40B
    nginx将几乎所有的结构体放在ngx_core.h文件中重新进行了申明,如下。

    [cpp] view plaincopy
     
    1. typedef struct ngx_module_s      ngx_module_t;  
    2. typedef struct ngx_conf_s        ngx_conf_t;  
    3. typedef struct ngx_cycle_s       ngx_cycle_t;  
    4. typedef struct ngx_pool_s        ngx_pool_t;  
    5. typedef struct ngx_chain_s       ngx_chain_t;  
    6. typedef struct ngx_log_s         ngx_log_t;  
    7. typedef struct ngx_array_s       ngx_array_t;  
    8. typedef struct ngx_open_file_s   ngx_open_file_t;  
    9. typedef struct ngx_command_s     ngx_command_t;  
    10. typedef struct ngx_file_s        ngx_file_t;  
    11. typedef struct ngx_event_s       ngx_event_t;  
    12. typedef struct ngx_event_aio_s   ngx_event_aio_t;  
    13. typedef struct ngx_connection_s  ngx_connection_t;  

    1.2 其他相关结构

    其他与内存池相干的数据结构,如清除资源的cleanup链表,分配的大块内存链表等,如下。

    [cpp] view plaincopy
     
    1. 00015: /* 
    2. 00016: * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86. 
    3. 00017: * On Windows NT it decreases a number of locked pages in a kernel. 
    4. 00018: */  
    5. 00019: #define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)  //在x86体系结构下,该值一般为4096B,即4K  
    6. 00020:  
    7. 00021: #define NGX_DEFAULT_POOL_SIZE    (16* 1024)  
    8. 00022:  
    9. 00023: #define NGX_POOL_ALIGNMENT    16  
    10. 00024: #define NGX_MIN_POOL_SIZE      
    11. 00025:    ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)),      
    12. 00026:    NGX_POOL_ALIGNMENT)  
    13. 00027:  
    14. 00028:  
    15. 00029: typedef void (*ngx_pool_cleanup_pt)(void *data);    //cleanup的callback类型  
    16. 00030:  
    17. 00031: typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;  
    18. 00032:  
    19. 00033: struct ngx_pool_cleanup_s{  
    20. 00034:    ngx_pool_cleanup_pt handler;  
    21. 00035:    void    *data;              //指向要清除的数据  
    22. 00036:    ngx_pool_cleanup_t *next;   //下一个cleanup callback  
    23. 00037: };  
    24. 00038:  
    25. 00039:  
    26. 00040: typedef struct ngx_pool_large_s ngx_pool_large_t;  
    27. 00041:  
    28. 00042: struct ngx_pool_large_s{  
    29. 00043:    ngx_pool_large_t  *next;    //指向下一块大块内存  
    30. 00044:    void    *alloc;             //指向分配的大块内存  
    31. 00045: };  
    32. ...  
    33. ...  
    34. 00067: typedef struct {  
    35. 00068:    ngx_fd_t   fd;  
    36. 00069:    u_char    *name;  
    37. 00070:    ngx_log_t *log;  
    38. 00071: } ngx_pool_cleanup_file_t;  
    39. 00072:  

    (gdb) p getpagesize()

    $18 = 4096

     

    全局变量ngx_pagesize的初始化是在如下函数中完成的。./src/os/unix/ngx_posix_init.c

    [cpp] view plaincopy
     
    1. ngx_int_t  
    2. ngx_os_init(ngx_log_t *log)  
    3. {  
    4.     ngx_uint_t  n;  
    5.   
    6. #if (NGX_HAVE_OS_SPECIFIC_INIT)  
    7.     if (ngx_os_specific_init(log) != NGX_OK) {  
    8.         return NGX_ERROR;  
    9.     }  
    10. #endif  
    11.   
    12.     ngx_init_setproctitle(log);  
    13.   
    14.     /** 该函数为glibc的库函数,由系统调用实现,返回内核中的PAGE_SIZE,该值依赖体系结构*/  
    15.     ngx_pagesize = getpagesize();        
    16.     ngx_cacheline_size = NGX_CPU_CACHE_LINE;  
    17.     ...  
    18. }  

    这些数据结构之间的关系,请参考后面的图。

     

    1.3 ngx_pool_t的逻辑结构

     

    这些数据结构逻辑结构图如下。注:本文采用UML的方式画出该图。

    2. 内存池操作

    2.1 创建内存池

    创建内存池有ngx_create_pool()函数完成,代码如下。

    [cpp] view plaincopy
     
    1. 00015: ngx_pool_t *  
    2. 00016: ngx_create_pool(size_t size, ngx_log_t *log)  
    3. 00017: {  
    4. 00018:    ngx_pool_t *p;  
    5. 00019:  
    6. 00020:    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  
    7. 00021:    if (p == NULL) {  
    8. 00022:       return NULL;  
    9. 00023:    }  
    10. 00024:  
    11. 00025:    p->d.last = (u_char *) p + sizeof(ngx_pool_t);  //last指向ngx_pool_t结构体之后数据取起始位置  
    12. 00026:    p->d.end = (u_char *) p + size;  //end指向分配的整个size大小的内存的末尾  
    13. 00027:    p->d.next = NULL;  
    14. 00028:    p->d.failed = 0;  
    15. 00029:  
    16. 00030:    size = size - sizeof(ngx_pool_t);  
    17. 00031:    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;  //最大不超过4095B  
    18. 00032:  
    19. 00033:    p->current = p;  
    20. 00034:    p->chain = NULL;  
    21. 00035:    p->large = NULL;  
    22. 00036:    p->cleanup = NULL;  
    23. 00037:    p->log = log;  
    24. 00038:  
    25. 00039:    return p;  
    26. 00040: }  

    例如,调用ngx_create_pool(1024, 0x80d1c4c)后,创建的内存池物理结构如下图。

    2.2 销毁内存池

     

    销毁内存池由如下函数完成。

    void ngx_destroy_pool(ngx_pool_t *pool)

    该函数将遍历内存池链表,所有释放内存,如果注册了clenup(也是一个链表结构)亦将遍历该cleanup链表结构依次调用clenuphandler清理。同时,还将遍历large链表,释放大块内存。

     

    2.3 重置内存池

     

    重置内存池由下面的函数完成。

    void ngx_reset_pool(ngx_pool_t *pool);

    该函数将释放所有large内存,并且将d->last指针重新指向ngx_pool_t结构之后数据区的开始位置,同刚创建后的位置相同。

     

    2.4 分配内存

     

    内存分配的函数如下。

    void *ngx_palloc(ngx_pool_t *pool, size_t size);

    void *ngx_pnalloc(ngx_pool_t *pool, size_t size);

    void *ngx_pcalloc(ngx_pool_t *pool, size_t size);

    void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);

     

    返回值为分配的内存起始地址。选择其中的两个函数进行分析,其他的也很好理解,省略。

     

    2.4.1 ngx_palloc()函数分析

     

    ngx_palloc()代码如下,分析请参考笔者所加的注释。

    [cpp] view plaincopy
     
    1. 00115: void *  
    2. 00116: ngx_palloc(ngx_pool_t *pool, size_t size)  
    3. 00117: {  
    4. 00118:    u_char    *m;  
    5. 00119:    ngx_pool_t *p;  
    6. 00120:  
    7. 00121:    if (size <= pool->max) {//判断待分配内存与max值  
    8. 00122:  
    9. 00123:       p = pool->current;   //小于max值,则从current节点开始遍历pool链表  
    10. 00124:  
    11. 00125:       do {  
    12. 00126:          m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);  
    13. 00127:  
    14. 00128:          if ((size_t) (p->d.end - m) >= size) {  
    15. 00129:             p->d.last = m + size;  //在该节点指向的内存块中分配size大小的内存  
    16. 00130:  
    17. 00131:             return m;  
    18. 00132:          }  
    19. 00133:  
    20. 00134:          p = p->d.next;  
    21. 00135:  
    22. 00136:       } while (p);  
    23. 00137:  
    24. 00138:       return ngx_palloc_block(pool, size); //链表里没有能分配size大小内存的节点,则生成一个新的节点并在其中分配内存  
    25. 00139:    }  
    26. 00140:  
    27. 00141:    return ngx_palloc_large(pool, size);  //大于max值,则在large链表里分配内存  
    28. 00142: }  

    例如,在2.1节中创建的内存池中分配200B的内存,调用ngx_palloc(pool, 200)后,该内存池物理结构如下图。

    2.4.2 ngx_palloc_block()函数分析

     

    ngx_palloc_block函数代码如下,分析请参考笔者所加的注释。

    [cpp] view plaincopy
     
    1. 00175: static void *  
    2. 00176: ngx_palloc_block(ngx_pool_t *pool, size_t size)  
    3. 00177: {  
    4. 00178:    u_char    *m;  
    5. 00179:    size_t    psize;  
    6. 00180:    ngx_pool_t *p, *new, *current;  
    7. 00181:  
    8. 00182:    psize = (size_t) (pool->d.end - (u_char *) pool);      //计算pool的大小  
    9. 00183:  
    10. 00184:    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);//分配一块与pool大小相同的内存  
    11. 00185:    if (m == NULL) {  
    12. 00186:       return NULL;  
    13. 00187:    }  
    14. 00188:  
    15. 00189:    new = (ngx_pool_t *) m;  
    16. 00190:  
    17. 00191:    new->d.end = m + psize; //设置end指针  
    18. 00192:    new->d.next = NULL;  
    19. 00193:    new->d.failed = 0;  
    20. 00194:  
    21. 00195:    m += sizeof(ngx_pool_data_t); //让m指向该块内存ngx_pool_data_t结构体之后数据区起始位置  
    22. 00196:    m = ngx_align_ptr(m, NGX_ALIGNMENT); //按4字节对齐  
    23. 00197:    new->d.last = m + size;       //在数据区分配size大小的内存并设置last指针  
    24. 00198:  
    25. 00199:    current = pool->current;  
    26. 00200:  
    27. 00201:    for (p = current; p->d.next; p = p->d.next) {  
    28. 00202:       if (p->d.failed++ > 4) {   //failed的值只在此处被修改  
    29. 00203:          current = p->d.next;    //失败4次以上移动current指针  
    30. 00204:       }  
    31. 00205:    }  
    32. 00206:  
    33. 00207:    p->d.next = new;  //将这次分配的内存块new加入该内存池  
    34. 00208:  
    35. 00209:    pool->current = current ? current : new;  
    36. 00210:  
    37. 00211:    return m;  
    38. 00212: }  

    注意:该函数分配一块内存后,last指针指向的是ngx_pool_data_t结构体(大小16B)之后数据区的起始位置。而创建内存池时时,last指针指向的是ngx_pool_t结构体(大小40B)之后数据区的起始位置。

     

    结合2.7节的内存池的物理结构,更容易理解。

     

    2.5 释放内存

     

    请参考如下函数,不再赘述。 

    ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)

    需要注意的是该函数只释放large链表中注册的内存,普通内存在ngx_destroy_pool中统一释放。

     

    2.6 注册cleanup

     

    请参考如下函数,该函数实现也很简单,此处不再赘述。

    ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)

     

    2.7 内存池的物理结构

     

    针对本文第3节的例子,画出的内存池的物理结构如下图。

      

    从该图也能看出2.4节的结论,即内存池第一块内存前40字节为ngx_pool_t结构,后续加入的内存块前16个字节为ngx_pool_data_t结构,这两个结构之后便是真正可以分配内存区域。

     

    因此,本文Reference中的内存分配相关中的图是有一点点小问题的,并不是每一个节点的前面都是ngx_pool_t结构。

     

    3. 一个例子

     

    理解并掌握开源软件的最好方式莫过于自己写一些测试代码,或者改写软件本身,并进行调试来进一步理解开源软件的原理和设计方法。本节给出一个创建内存池并从中分配内存的简单例子。

     

    3.1 代码

    [cpp] view plaincopy
     
    1. /** 
    2.  * ngx_pool_t test, to test ngx_palloc, ngx_palloc_block, ngx_palloc_large 
    3.  */  
    4.   
    5. #include <stdio.h>  
    6. #include "ngx_config.h"  
    7. #include "ngx_conf_file.h"  
    8. #include "nginx.h"  
    9. #include "ngx_core.h"  
    10. #include "ngx_string.h"  
    11. #include "ngx_palloc.h"  
    12.   
    13. volatile ngx_cycle_t  *ngx_cycle;  
    14.   
    15. void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,  
    16.             const char *fmt, ...)  
    17. {  
    18. }  
    19.   
    20. void dump_pool(ngx_pool_t* pool)  
    21. {  
    22.     while (pool)  
    23.     {  
    24.         printf("pool = 0x%x ", pool);  
    25.         printf("  .d ");  
    26.         printf("    .last = 0x%x ", pool->d.last);  
    27.         printf("    .end = 0x%x ", pool->d.end);  
    28.         printf("    .next = 0x%x ", pool->d.next);  
    29.         printf("    .failed = %d ", pool->d.failed);  
    30.         printf("  .max = %d ", pool->max);  
    31.         printf("  .current = 0x%x ", pool->current);  
    32.         printf("  .chain = 0x%x ", pool->chain);  
    33.         printf("  .large = 0x%x ", pool->large);  
    34.         printf("  .cleanup = 0x%x ", pool->cleanup);  
    35.         printf("  .log = 0x%x ", pool->log);  
    36.         printf("available pool memory = %d ", pool->d.end - pool->d.last);  
    37.         pool = pool->d.next;  
    38.     }  
    39. }  
    40.   
    41. int main()  
    42. {  
    43.     ngx_pool_t *pool;  
    44.   
    45.     printf("-------------------------------- ");  
    46.     printf("create a new pool: ");  
    47.     printf("-------------------------------- ");  
    48.     pool = ngx_create_pool(1024, NULL);  
    49.     dump_pool(pool);  
    50.   
    51.     printf("-------------------------------- ");  
    52.     printf("alloc block 1 from the pool: ");  
    53.     printf("-------------------------------- ");  
    54.     ngx_palloc(pool, 512);  
    55.     dump_pool(pool);  
    56.   
    57.     printf("-------------------------------- ");  
    58.     printf("alloc block 2 from the pool: ");  
    59.     printf("-------------------------------- ");  
    60.     ngx_palloc(pool, 512);  
    61.     dump_pool(pool);  
    62.   
    63.     printf("-------------------------------- ");  
    64.     printf("alloc block 3 from the pool : ");  
    65.     printf("-------------------------------- ");  
    66.     ngx_palloc(pool, 512);  
    67.     dump_pool(pool);  
    68.   
    69.     ngx_destroy_pool(pool);  
    70.     return 0;  
    71. }  

     

    3.2 如何编译

     

    这个问题是编写测试代码或者改写软件本身最迫切需要解决的问题,否则,编写的代码无从编译或运行,那也无从进行调试并理解软件了。

     

    如何对自己编写的测试代码进行编译,可参考Linux平台代码覆盖率测试-编译过程自动化及对链接的解释Linux平台如何编译使用Google test写的单元测试?。我们要做的是学习这种编译工程的方法,针对该例子,笔者编写的makefile文件如下。——这便是本节的主要目的。

    [plain] view plaincopy
     
    1. CXX = gcc  
    2. CXXFLAGS += -g -Wall -Wextra  
    3.   
    4. NGX_ROOT = /usr/src/nginx-1.0.4  
    5.   
    6. TARGETS = ngx_pool_t_test  
    7. TARGETS_C_FILE = $(TARGETS).c  
    8.   
    9. CLEANUP = rm -f $(TARGETS) *.o  
    10.   
    11. all: $(TARGETS)  
    12.   
    13. clean:  
    14.     $(CLEANUP)  
    15.   
    16. CORE_INCS = -I.   
    17.     -I$(NGX_ROOT)/src/core   
    18.     -I$(NGX_ROOT)/src/event   
    19.     -I$(NGX_ROOT)/src/event/modules   
    20.     -I$(NGX_ROOT)/src/os/unix   
    21.     -I$(NGX_ROOT)/objs   
    22.   
    23. NGX_PALLOC = $(NGX_ROOT)/objs/src/core/ngx_palloc.o  
    24. NGX_STRING = $(NGX_ROOT)/objs/src/core/ngx_string.o  
    25. NGX_ALLOC = $(NGX_ROOT)/objs/src/os/unix/ngx_alloc.o  
    26.   
    27. $(TARGETS): $(TARGETS_C_FILE)  
    28.     $(CXX) $(CXXFLAGS) $(CORE_INCS) $(NGX_PALLOC) $(NGX_STRING) $(NGX_ALLOC) $^ -o $@  

    3.3 运行运行结果

    [plain] view plaincopy
     
    1. # ./ngx_pool_t_test  
    2. --------------------------------  
    3. create a new pool:  
    4. --------------------------------  
    5. pool = 0x8922020  
    6.   .d  
    7.     .last = 0x8922048  
    8.     .end = 0x8922420  
    9.     .next = 0x0  
    10.     .failed = 0  
    11.   .max = 984  
    12.   .current = 0x8922020  
    13.   .chain = 0x0  
    14.   .large = 0x0  
    15.   .cleanup = 0x0  
    16.   .log = 0x0  
    17. available pool memory = 984  
    18.   
    19. --------------------------------  
    20. alloc block 1 from the pool:  
    21. --------------------------------  
    22. pool = 0x8922020  
    23.   .d  
    24.     .last = 0x8922248  
    25.     .end = 0x8922420  
    26.     .next = 0x0  
    27.     .failed = 0  
    28.   .max = 984  
    29.   .current = 0x8922020  
    30.   .chain = 0x0  
    31.   .large = 0x0  
    32.   .cleanup = 0x0  
    33.   .log = 0x0  
    34. available pool memory = 472  
    35.   
    36. --------------------------------  
    37. alloc block 2 from the pool:  
    38. --------------------------------  
    39. pool = 0x8922020  
    40.   .d  
    41.     .last = 0x8922248  
    42.     .end = 0x8922420  
    43.     .next = 0x8922450  
    44.     .failed = 0  
    45.   .max = 984  
    46.   .current = 0x8922020  
    47.   .chain = 0x0  
    48.   .large = 0x0  
    49.   .cleanup = 0x0  
    50.   .log = 0x0  
    51. available pool memory = 472  
    52.   
    53. pool = 0x8922450  
    54.   .d  
    55.     .last = 0x8922660  
    56.     .end = 0x8922850  
    57.     .next = 0x0  
    58.     .failed = 0  
    59.   .max = 0  
    60.   .current = 0x0  
    61.   .chain = 0x0  
    62.   .large = 0x0  
    63.   .cleanup = 0x0  
    64.   .log = 0x0  
    65. available pool memory = 496  
    66.   
    67. --------------------------------  
    68. alloc block 3 from the pool :  
    69. --------------------------------  
    70. pool = 0x8922020  
    71.   .d  
    72.     .last = 0x8922248  
    73.     .end = 0x8922420  
    74.     .next = 0x8922450  
    75.     .failed = 1  
    76.   .max = 984  
    77.   .current = 0x8922020  
    78.   .chain = 0x0  
    79.   .large = 0x0  
    80.   .cleanup = 0x0  
    81.   .log = 0x0  
    82. available pool memory = 472  
    83.   
    84. pool = 0x8922450  
    85.   .d  
    86.     .last = 0x8922660  
    87.     .end = 0x8922850  
    88.     .next = 0x8922880  
    89.     .failed = 0  
    90.   .max = 0  
    91.   .current = 0x0  
    92.   .chain = 0x0  
    93.   .large = 0x0  
    94.   .cleanup = 0x0  
    95.   .log = 0x0  
    96. available pool memory = 496  
    97.   
    98. pool = 0x8922880  
    99.   .d  
    100.     .last = 0x8922a90  
    101.     .end = 0x8922c80  
    102.     .next = 0x0  
    103.     .failed = 0  
    104.   .max = 0  
    105.   .current = 0x0  
    106.   .chain = 0x0  
    107.   .large = 0x0  
    108.   .cleanup = 0x0  
    109.   .log = 0x0  
    110. available pool memory = 496  

    4. 小结

     

    本文针对nginx-1.0.4的内存管理进行了较为全面的分析,包括相关内存池数据结构,内存池的创建、销毁,以及从内存池中分配内存等。最后通过一个简单例子向读者展示nginx内存池的创建和分配操作,同时借此向读者展示编译测试代码的方法。

     

    分析完nginx的内存管理,你一定惊叹于nginx作者的聪明才智。这种内存管理的设计方法小巧、快捷,值得借鉴!

  • 相关阅读:
    u盘安装linux提示:Loader exited unexpectedly!……install exitedabnormally
    接口练习代码
    c#里面的索引器注意
    MD5方法代码(生成小写的md5) C#版本
    sql 中set和select区别
    sql 中convert和cast区别
    数据库触发器inserted和deleted详解
    (转载)处理delete不走索引导致锁等待异常
    Mysql Using FileSort问题
    (转)[MySQL高级](一) EXPLAIN用法和结果分析
  • 原文地址:https://www.cnblogs.com/405845829qq/p/4379093.html
Copyright © 2020-2023  润新知