激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

云服務器|WEB服務器|FTP服務器|郵件服務器|虛擬主機|服務器安全|DNS服務器|服務器知識|Nginx|IIS|Tomcat|

服務器之家 - 服務器技術 - Nginx - nginx內存池源碼解析

nginx內存池源碼解析

2021-11-24 16:54BugMaker-shen Nginx

內存池是在真正使用內存之前,預先申請分配一定數量的、大小相等(一般情況下)的內存塊留作備用,接下來通過本文給大家介紹nginx內存池源碼,本文通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧

內存池概述

    內存池是在真正使用內存之前,預先申請分配一定數量的、大小相等(一般情況下)的內存塊留作備用。當有新的內存需求時,就從內存池中分出一部分內存塊,若內存塊不夠用時,再繼續申請新的內存。

   內存池的好處有減少向系統申請和釋放內存的時間開銷,解決內存頻繁分配產生的碎片,提示程序性能,減少程序員在編寫代碼中對內存的關注等

   目前一些常見的內存池實現方案有STL中的內存分配區,boost中的object_pool,nginx中的ngx_pool_t,google的開源項目TCMalloc等。

為了自身使用的方便,Nginx封裝了很多有用的數據結構,比如ngx_str_t ,ngx_array_t, ngx_pool_t 等等,對于內存池,nginx設計的十分精煉,值得我們學習,本文重點給大家介紹nginx內存池源碼,并用一個實際的代碼例子作了進一步的講解。

一、nginx數據結構

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// SGI STL小塊和大塊內存的分界點:128B
// nginx(給HTTP服務器所有的模塊分配內存)小塊和大塊內存的分界點:4096B
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)
 
// 內存池默認大小
#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)
 
// 內存池字節對齊,SGI STL對其是8B
#define NGX_POOL_ALIGNMENT       16
#define NGX_MIN_POOL_SIZE        ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)), \
                                 NGX_POOL_ALIGNMENT)
 
// 將開辟的內存調整到16的整數倍
#define ngx_align(d, a)          (((d) + (a - 1)) & ~(a - 1))
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef struct ngx_pool_s ngx_pool_t;
 
typedef struct {
    u_char               *last;   // 指向可用內存的起始地址
    u_char               *end;    // 指向可用內存的末尾地址
    ngx_pool_t           *next;   // 指向下一個內存塊 
    ngx_uint_t            failed; // 當前內存塊分配空間失敗的次數
} ngx_pool_data_t;
 
// 內存池塊的類型
struct ngx_pool_s {
    ngx_pool_data_t       d;          // 內存池塊頭信息
    size_t                max; 
    ngx_pool_t           *current;    // 指向可用于分配空間的內存塊(failed < 4)的起始地址
    ngx_chain_t          *chain;      // 連接所有的內存池塊
    ngx_pool_large_t     *large;      // 大塊內存的入口指針
    ngx_pool_cleanup_t   *cleanup;    // 內存池塊的清理操作,用戶可設置回調函數,在內存池塊釋放之前執行清理操作
    ngx_log_t            *log;        // 日志
};

nginx內存池源碼解析

二、nginx向OS申請空間ngx_create_pool

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 根據size進行內存開辟
ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log){
    ngx_pool_t  *p;
    // 根據系統平臺定義的宏以及用戶執行的size,調用不同平臺的API開辟內存池
    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);              // 能使用的空間 = 總空間 - 頭信息
    // 指定的大小若大于一個頁面就用一個頁面,否則用指定的大小
    // max = min(size, 4096),max指的是除開頭信息以外的內存塊的大小
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
 
    p->current = p;         // 指向可用于分配空間的內存塊的起始地址
    p->chain = NULL;
    p->large = NULL;        // 小塊內存直接在內存塊開辟,大塊內存在large指向的內存開辟
    p->cleanup = NULL;
    p->log = log;
 
    return p;
}

nginx內存池源碼解析

三、nginx向內存池申請空間

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        // 當前分配的空間小于max,小塊內存的分配
        return ngx_palloc_small(pool, size, 1);   // 考慮內存對齊
    }
#endif
 
    return ngx_palloc_large(pool, size);
}
 
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 0);  // 不考慮內存對齊
    }
#endif
 
    return ngx_palloc_large(pool, size);
}
 
void* ngx_pcalloc(ngx_pool_t *pool, size_t size){
    void *p;
    p = ngx_palloc(pool, size); // 考慮內存對齊
    if (p) {
        ngx_memzero(p, size);   // 可以初始化內存為0
    }
 
    return p;
}

ngx_palloc_small 分配效率高,只做了指針的偏移

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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;
    // 從第一個內存塊的current指針指向的內存池進行分配
    p = pool->current;
 
    do {
        m = p->d.last;  // m指向可分配內存的起始地址
 
        if (align) {
            // 把m調整為NGX_ALIGNMENT整數倍
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }
        // 內存池分配內存的核心代碼
        if ((size_t) (p->d.end - m) >= size) {
            // 若可分配空間 >= 申請的空間
            // 偏移d.last指針,記錄空閑空間的首地址
            p->d.last = m + size;
            return m;
        }
        // 當前內存塊的空閑空間不夠分配,若有下一個內存塊則轉向下一個內存塊
        // 若沒有,p會被置空,退出while
        p = p->d.next;
    } while (p);
    
    return ngx_palloc_block(pool, size);
}

當前內存池的塊足夠分配:

nginx內存池源碼解析

當前內存池的塊不夠分配:

  1. 開辟新的內存塊,修改新內存塊頭信息的last、end、next、failed
  2. 前面所有內存塊的failed++
  3. 連接新的內存塊以及前面的內存塊
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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);
    
    // 將psize對齊為NGX_POOL_ALIGNMENT的整數倍后,向OS申請空間
    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;         // 下一塊內存的地址為NULL
    new->d.failed = 0;          // 當前內存塊分配空間失敗的次數
    
    // 指向頭信息的尾部,而max,current、chain等只在第一個內存塊有
    m += sizeof(ngx_pool_data_t); 
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;                // last指向當前塊空閑空間的起始地址
    
    // 由于每次都是從pool->current開始分配空間
    // 若執行到這里,除了new這個內存塊分配成功,其他的內存塊全部分配失敗
    for (p = pool->current; p->d.next != NULL; p = p->d.next) {
        // 對所有的內存塊的failed都++,直到該內存塊分配失敗的次數大于4了
        // 就表示該內存塊的剩余空間很小了,不能再分配空間了
        // 就修改current指針,下次從current開始分配空間,再次分配的時候可以不用遍歷前面的內存塊
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }
    
    p->d.next = new;   // 連接可分配空間的首個內存塊 和 新開辟的內存塊
 
    return m;
}

nginx內存池源碼解析

四、大塊內存的分配與釋放

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
typedef struct ngx_pool_large_s  ngx_pool_large_t;
 
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;   // 下一個大塊內存的起始地址
    void                 *alloc;  // 大塊內存的起始地址
};
 
static void * ngx_palloc_large(ngx_pool_t *pool, size_t size){
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;
    
    // 調用的就是malloc
    p = ngx_alloc(size, pool->log);
    if (p == NULL) {
        return NULL;
    }
 
    n = 0;
    // for循環遍歷存儲大塊內存信息的鏈表
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            // 當大塊內存被ngx_pfree時,alloc為NULL
            // 遍歷鏈表,若大塊內存的首地址為空,則把當前malloc的內存地址寫入alloc
            large->alloc = p;
            return p;
        }
        // 遍歷4次后,若還沒有找到被釋放過的大塊內存對應的信息
        // 為了提高效率,直接在小塊內存中申請空間保存大塊內存的信息
        if (n++ > 3) {
            break;
        }
    }
    // 通過指針偏移在小塊內存池上分配存放大塊內存*next和*alloc的空間
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        // 如果在小塊內存上分配存儲*next和*alloc空間失敗,則無法記錄大塊內存
        // 釋放大塊內存p
        ngx_free(p);
        return NULL;
    }
    
    large->alloc = p;               // alloc指向大塊內存的首地址
    large->next = pool->large;       // 這兩句采用頭插法,將新內存塊的記錄信息存放于以large為頭結點的鏈表中
    pool->large = large;
 
    return p;
}

nginx內存池源碼解析

大塊內存的釋放

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 釋放p指向的大塊內存
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p){
    ngx_pool_large_t  *l;
 
    for (l = pool->large; l; l = l->next) {
        // 遍歷存儲大塊內存信息的鏈表,找到p對應的大塊內存
        if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "free: %p", l->alloc);
            // 釋放大塊內存,但不釋放存儲信息的內存空間
            ngx_free(l->alloc);  // free
            l->alloc = NULL;     // alloc置空
 
            return NGX_OK;
        }
    }
 
    return NGX_DECLINED;
}

五、關于小塊內存不釋放

就用了last和end兩個指著標識空閑的空間,是無法將已經使用的空間合理歸還到內存池的,只是會重置內存池。同時還存儲了指向大內存塊large和清理函數cleanup的頭信息

考慮到nginx的效率,小塊內存分配高效,同時也不回收內存

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
        }
    }
    
    // 遍歷小塊內存的鏈表,重置last、failed、current、chain、large等管理信息
    for (p = pool; p; p = p->d.next) {
        // 由于只有第一個內存塊有除了ngx_pool_data_t以外的管理信息,別的內存塊只有ngx_pool_data_t的信息
        // 不會出錯,但是會浪費空間
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        p->d.failed = 0;
    }
    
    // current指向可用于分配內存的內存塊
    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

nginx本質是http服務器,通常處理的是短鏈接,間接性提供服務,需要的內存不大,所以不回收內存,重置即可。

客戶端發起一個requests請求后,nginx服務器收到請求會返回response響應,若在keep-alive時間內沒有收到客戶的再次請求,nginx服務器會主動斷開連接,此時會reset內存池。下一次客戶端請求再到來時,可以復用內存池。

如果是處理長鏈接,只要客戶端還在線,服務器的資源就無法釋放,直到系統資源耗盡。長鏈接一般使用SGI STL內存池的方式進行內存的開辟和釋放,而這種方式分配和回收空間的效率就比nginx低

六、銷毀和清空內存池

假設如下情況:

?
1
2
3
4
5
6
7
8
9
// 假設內存對齊為4B
typedef struct{
    char* p;
    char data[508];
}stData;
 
ngx_pool_t *pool = ngx_create_pool(512, log);  // 創建一個總空間為512B的nginx內存塊
stData* data_ptr = ngx_alloc(512);            // 因為可用的實際內存大小為:512-sizeof(ngx_pool_t),所以屬于大內存開辟
data_ptr->p = malloc(10);                   // p指向外界堆內存,類似于C++對象中對用占用了外部資源

當回收大塊內存的時候,調用ngx_free,就會導致內存泄漏

nginx內存池源碼解析

以上內存泄漏的問題,可以通過回調函數進行內存釋放(通過函數指針實現)

?
1
2
3
4
5
6
7
8
9
10
typedef void (*ngx_pool_cleanup_pt)(void *data);
 
typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;
 
// 以下結構體由ngx_pool_s.cleanup指向,也是存放在內存池的小塊內存
struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;
    void                 *data;     // 指向需要釋放的資源
    ngx_pool_cleanup_t   *next;     // 釋放資源的函數都放在一個鏈表,用next指向這個鏈表
};

nginx提供的函數接口:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// p表示內存池的入口地址,size表示p->cleanup->data指針的大小
// p->cleanup指向含有清理函數信息的結構體
// ngx_pool_cleanup_add返回 含有清理函數信息的結構體 的指針
ngx_pool_cleanup_t* ngx_pool_cleanup_add(ngx_pool_t *p, size_t size){
    ngx_pool_cleanup_t  *c;
    
    // 開辟清理函數的結構體,實際上也是存放在內存池的小塊內存
    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }
    
    if (size) {
        // 為c->data申請size的空間
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }
    } else {
        c->data = NULL;
    }
 
    c->handler = NULL;
    // 采用頭插法,將當前結構體串在pool->cleanup后
    c->next = p->cleanup;
    p->cleanup = c;
 
    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
 
    return c;
}

使用方式:

?
1
2
3
4
5
6
7
8
9
void release(void* p){
    free(p);
}
 
ngx_pool_cleanup_t* clean_ptr = ngx_clean_cleanup_add(pool, sizeof(char*));
clean_ptr->handler = &release;   // 用戶設置銷毀內存池前需要調用的函數
clean_ptr->data = data_ptr->p;   // 用戶設置銷毀內存池前需要釋放的內存的地址
 
ngx_destroy_pool(pool);          // 用戶銷毀內存池

七、編譯測試內存池接口功能

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;
    
    // 遍歷cleanup鏈表(存放的時釋放前需要調用的函數),可釋放外部占用的資源
    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);
        }
    }
 
    // 釋放大塊內存
    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;
        }
    }
}

nginx內存池源碼解析

執行configure生成Makefile文件(若報錯則表示需要apt安裝軟件)

nginx內存池源碼解析

Makefile如下:

nginx內存池源碼解析

執行make命令使用Makefile編譯源碼,在相應目錄下生成 .o文件

nginx內存池源碼解析

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <ngx_config.h>
#include <nginx.h>
#include <ngx_core.h>
#include <ngx_palloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
            const char *fmt, ...){
 
}
 
typedef struct Data stData;
struct Data{
    char *ptr;
    FILE *pfile;
};
 
void func1(char *p){
    printf("free ptr mem!\n");
    free(p);
}
 
void func2(FILE *pf){
    printf("close file!\n");
    fclose(pf);
}
 
void main(){
    // max = 512 - sizeof(ngx_pool_t)
    // 創建總空間為512字節的nginx內存塊
    ngx_pool_t *pool = ngx_create_pool(512, NULL);
    if(pool == NULL){
        printf("ngx_create_pool fail...");
        return;
    }
    
    // 從小塊內存池分配的
    void *p1 = ngx_palloc(pool, 128);
    if(p1 == NULL){
        printf("ngx_palloc 128 bytes fail...");
        return;
    }
    
    // 從大塊內存池分配的
    stData *p2 = ngx_palloc(pool, 512);
    if(p2 == NULL){
        printf("ngx_palloc 512 bytes fail...");
        return;
    }
    
    // 占用外部堆內存
    p2->ptr = malloc(12);
    strcpy(p2->ptr, "hello world");
    // 文件描述符
    p2->pfile = fopen("data.txt", "w");
    
    ngx_pool_cleanup_t *c1 = ngx_pool_cleanup_add(pool, sizeof(char*));
    c1->handler = func1;   // 設置回調函數
    c1->data = p2->ptr;    // 設置資源地址
 
    ngx_pool_cleanup_t *c2 = ngx_pool_cleanup_add(pool, sizeof(FILE*));
    c2->handler = func2;
    c2->data = p2->pfile;
    
    // 1.調用所有的預置的清理函數 2.釋放大塊內存 3.釋放小塊內存池所有內存
    ngx_destroy_pool(pool);
 
    return;
}

nginx內存池源碼解析

由于ngx_pool_cleanup_add中用頭插法將創建的清理塊鏈入pool->cleanup,所以ngx_destroy_pool的時候先清理文件后清理堆內存。

相關測試代碼推送到:https://github.com/BugMaker-shen/nginx_sgistl_pool

到此這篇關于nginx內存池源碼解析的文章就介紹到這了,更多相關nginx內存池內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/qq_42500831/article/details/121328558

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25
主站蜘蛛池模板: 久草手机视频在线观看 | 免费视频a | 国产一区二区三区视频观看 | 久久久国产精品电影 | 精品国产一区二区三区在线观看 | 热99精品视频| 久久艹逼 | 久久精品艹 | 在线免费观看毛片视频 | 国产免费一区二区三区最新不卡 | av视在线 | 国产成人精品一区在线播放 | 国产欧美日韩二区 | 亚洲午夜免费电影 | 久久96国产精品久久秘臀 | 免费一区二区三区 | 精品一区二区三区四区在线 | 欧美一区二区黄 | 中国一级免费视频 | 国产免费视频在线 | 最新国产毛片 | 久久精品视频3 | 中文字幕在线观看成人 | 精品三级内地国产在线观看 | 久久精品欧美电影 | 欧美人与牲禽动交精品一区 | av播放在线| 久久伊人精品视频 | 久久人人av | 在线观看视频毛片 | 色人阁在线视频 | 一区二区久久精品66国产精品 | av在线试看 | 九九热视频这里只有精品 | 久久精品伊人网 | 免费毛片在线视频 | 香蕉秀 | 欧美久久久久久久久 | 中文字幕免费一区 | 护士hd欧美free性xxxx | 久久国产精品久久精品国产演员表 |