目錄
一、簡介
#二、資料結構
2.1 記憶體池主要結構
2.2 大記憶體鏈
2.3 清理任務鏈
三、記憶體結構圖
3.1 邏輯
#3.2 實際
當需要分配的空間小於max時,將使用小記憶體分配方式(即從記憶體池中分配空間),而ngx_pnalloc和ngx_palloc相比只是呼叫ngx_palloc_small時的最後一個參數為0。
4.6 大内存释放
4.7 分配并清空数据
4.8 回调文件清理
首頁 運維 Nginx nginx記憶體池如何實現

nginx記憶體池如何實現

May 17, 2023 pm 01:26 PM
nginx

一、簡介

最新穩定版本nginx1.20.2。
為了能高效、快速的分配內存,以及減少內存碎片等,nginx實現了自己的內存池基礎組件。
主要實作檔案ngx_palloc.h, ngx_palloc.c

#二、資料結構

2.1 記憶體池主要結構

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;
    ngx_chain_t          *chain;
    ngx_pool_large_t     *large;
    ngx_pool_cleanup_t   *cleanup;
    ngx_log_t            *log;
};
登入後複製

記憶體池中第一個成員是一個結構體:
使用ngx_pool_data_t結構體來表示目前記憶體池資訊。
last :下次開始分配的位址
end: 記憶體池的結束位址
next: 記憶體池鍊錶,將多個記憶體池連接起來

max
整個記憶體池的最大大小

current
指向從目前記憶體池開始尋找可用記憶體

chain
buffer所使用的,這裡不涉及

large
當需要的記憶體大於記憶體池最大大小時,需要透過malloc直接分配,然後形成鍊錶進行組織

cleanup
清理工作的回呼鍊錶

log
日誌句柄

2.2 大記憶體鏈

當需要分配的記憶體比記憶體池的最大大小都大時,記憶體池無法滿足分配,所以直接從系統中分配,然後構成一個鍊錶進行維護。

typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s {
    ngx_pool_large_t     *next;
    void                 *alloc;
};
登入後複製

2.3 清理任務鏈

有一個回呼任務的鍊錶,當記憶體池銷毀時,將依序遍歷此鍊錶,逐一回調handler進行清理工作。

typedef void (*ngx_pool_cleanup_pt)(void *data);

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;
    void                 *data;
    ngx_pool_cleanup_t   *next;
};
登入後複製

三、記憶體結構圖

3.1 邏輯

nginx記憶體池如何實現

#3.2 實際

nginx記憶體池如何實現

nginx記憶體池如何實現

可以看出,很多節點都是從記憶體池中分配的,所以可以把精力都放在實際的資料上而不必在意其他細節上。

    四、實作
  • 4.1 建立記憶體池<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>/* * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86. * On Windows NT it decreases a number of locked pages in a kernel. */ #define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1) #define NGX_DEFAULT_POOL_SIZE (16 * 1024)</pre><div class="contentsignin">登入後複製</div></div><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>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-&gt;d.last = (u_char *) p + sizeof(ngx_pool_t); p-&gt;d.end = (u_char *) p + size; p-&gt;d.next = NULL; p-&gt;d.failed = 0; size = size - sizeof(ngx_pool_t); p-&gt;max = (size &lt; NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; p-&gt;current = p; p-&gt;chain = NULL; p-&gt;large = NULL; p-&gt;cleanup = NULL; p-&gt;log = log; return p; }</pre><div class="contentsignin">登入後複製</div></div>從程式碼可以看到,記憶體池最大不超過pagesize的大小

  • #4.2 從記憶體池中分配空間

    分配函數分了記憶體對齊和記憶體不對齊,但這只控制了記憶體池中分配空間,不控制大記憶體分配。
(1)分配小空間

記憶體對齊

ngx_palloc


記憶體不對齊

ngx_pnalloc

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
#endif

    return ngx_palloc_large(pool, size);
}
登入後複製

當需要分配的空間小於max時,將使用小記憶體分配方式(即從記憶體池中分配空間),而ngx_pnalloc和ngx_palloc相比只是呼叫ngx_palloc_small時的最後一個參數為0。

從pool->current指向的記憶體池開始遍歷,尋找滿足分配大小的空間,找到則返回首地址

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);
}
登入後複製

當現有記憶體池中都無法滿足分配條件時,在建立新的記憶體池<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>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-&gt;d.end - (u_char *) pool); m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool-&gt;log); if (m == NULL) { return NULL; } new = (ngx_pool_t *) m; new-&gt;d.end = m + psize; new-&gt;d.next = NULL; new-&gt;d.failed = 0; m += sizeof(ngx_pool_data_t); m = ngx_align_ptr(m, NGX_ALIGNMENT); new-&gt;d.last = m + size; for (p = pool-&gt;current; p-&gt;d.next; p = p-&gt;d.next) { if (p-&gt;d.failed++ &gt; 4) { pool-&gt;current = p-&gt;d.next; } } p-&gt;d.next = new; return m; }</pre><div class="contentsignin">登入後複製</div></div>其中,在建立好新的記憶體池後,又做了一次遍歷,將failed計數加一,當大於4時,將跳過此記憶體池,下次就不從它開始查找。

即認為超過4次你都無法滿足分配,以後都不能滿足分配,不再用你了,減少遍歷個數,加快成功分配效率

(2)分配大空間

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;
}
登入後複製

可以看出,為了避免分配空間,遍歷large鏈查找可重用的節點,但是如果鍊錶過大又可能太慢,所以只查找前三個,如果三個都沒有找到,則直接分配(而且節點也是從記憶體池中分配的,所以後續清理時,不需要管節點,只需要釋放申請的大記憶體本身)
  • 記憶體對齊

    void *
    ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
    {
        void              *p;
        ngx_pool_large_t  *large;
    
        p = ngx_memalign(alignment, size, pool->log);
        if (p == NULL) {
            return NULL;
        }
    
        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;
    }
    登入後複製

    4.3 註冊清理任務
  • 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 = ngx_palloc(p, size);
            if (c->data == NULL) {
                return NULL;
            }
    
        } else {
            c->data = NULL;
        }
    
        c->handler = NULL;
        c->next = p->cleanup;
    
        p->cleanup = c;
    
        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
    
        return c;
    }
    登入後複製
  • 可以看出,這裡只是分配了一個節點,並沒有設置handler以及data數據,所以還得看具體的調用方進行設置,因為這裡返回了分配的節點。

    例如在函數
  • ngx_create_temp_file
  • ngx_int_t
    ngx_create_temp_file(ngx_file_t *file, ngx_path_t *path, ngx_pool_t *pool,
        ngx_uint_t persistent, ngx_uint_t clean, ngx_uint_t access)
    {
        ...
    
        cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
        if (cln == NULL) {
            return NGX_ERROR;
        }
    
           ...
            file->fd = ngx_open_tempfile(file->name.data, persistent, access);
    				...
            if (file->fd != NGX_INVALID_FILE) {
    
                cln->handler = clean ? ngx_pool_delete_file : ngx_pool_cleanup_file;
                clnf = cln->data;
    
                clnf->fd = file->fd;
                clnf->name = file->name.data;
                clnf->log = pool->log;
    
                return NGX_OK;
            }
    			...
    }
    登入後複製

    產生臨時文件,將fd以及文件名註冊到清理任務中,後續文件不使用了則不需要特殊處理,記憶體記憶體池釋放時將統一清理。
4.4 重置記憶體池


#釋放大記憶體重置記憶體中last

    #重置failed計數
  • 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;
    }
    登入後複製
  • 這裡有個現象:
  • 在記憶體池中空間不足時,將呼叫

    ngx_palloc_block

    建立一個新的記憶體池,而last指向的是
  • m = sizeof(ngx_pool_data_t);
  • , 因此目前新分配的記憶體池將比第一個記憶體池可用大小多了(max,current,chain,large, cleanup,log)這幾個欄位大小(可能沒有那麼多,因為要對齊,可能對齊後就完全一樣了),而現在重置時,

    p->d.last = (u_char *) p sizeof(ngx_pool_t);

    每個記憶體池可用大小又變成一樣的。 ######4.5 銷毀記憶體池############回呼清理任務############釋放大記憶體################### ##釋放記憶體池本身###
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) {
            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;
        }
    }
}
登入後複製

4.6 大内存释放

通过遍历找到要释放的节点,将内存释放,并且将alloc设置成NULL,则有了节点重用的情况。

ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
        if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "free: %p", l->alloc);
            ngx_free(l->alloc);
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}
登入後複製

4.7 分配并清空数据

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

    p = ngx_palloc(pool, size);
    if (p) {
        ngx_memzero(p, size);
    }

    return p;
}
登入後複製

正常分配的空间中都是垃圾数据,所以当前函数在分配空间后,将分配的空间清零。

4.8 回调文件清理

(1) 手动关闭指定fd

遍历清理任务,找到ngx_pool_cleanup_file的handler,如果是要关闭的fd,则回调

void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
    ngx_pool_cleanup_t       *c;
    ngx_pool_cleanup_file_t  *cf;

    for (c = p->cleanup; c; c = c->next) {
        if (c->handler == ngx_pool_cleanup_file) {

            cf = c->data;

            if (cf->fd == fd) {
                c->handler(cf);
                c->handler = NULL;
                return;
            }
        }
    }
}
登入後複製

(2) 关闭fd

void
ngx_pool_cleanup_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
                   c->fd);

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}
登入後複製

(3) 删除文件并关闭fd

void
ngx_pool_delete_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_err_t  err;

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
                   c->fd, c->name);

    if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
        err = ngx_errno;

        if (err != NGX_ENOENT) {
            ngx_log_error(NGX_LOG_CRIT, c->log, err,
                          ngx_delete_file_n " \"%s\" failed", c->name);
        }
    }

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}
登入後複製

以上是nginx記憶體池如何實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1655
14
CakePHP 教程
1413
52
Laravel 教程
1306
25
PHP教程
1252
29
C# 教程
1226
24
nginx在windows中怎麼配置 nginx在windows中怎麼配置 Apr 14, 2025 pm 12:57 PM

如何在 Windows 中配置 Nginx?安裝 Nginx 並創建虛擬主機配置。修改主配置文件並包含虛擬主機配置。啟動或重新加載 Nginx。測試配置並查看網站。選擇性啟用 SSL 並配置 SSL 證書。選擇性設置防火牆允許 80 和 443 端口流量。

docker容器名稱怎麼查 docker容器名稱怎麼查 Apr 15, 2025 pm 12:21 PM

可以通過以下步驟查詢 Docker 容器名稱:列出所有容器(docker ps)。篩選容器列表(使用 grep 命令)。獲取容器名稱(位於 "NAMES" 列中)。

docker怎麼啟動容器 docker怎麼啟動容器 Apr 15, 2025 pm 12:27 PM

Docker 容器啟動步驟:拉取容器鏡像:運行 "docker pull [鏡像名稱]"。創建容器:使用 "docker create [選項] [鏡像名稱] [命令和參數]"。啟動容器:執行 "docker start [容器名稱或 ID]"。檢查容器狀態:通過 "docker ps" 驗證容器是否正在運行。

怎麼查看nginx是否啟動 怎麼查看nginx是否啟動 Apr 14, 2025 pm 01:03 PM

確認 Nginx 是否啟動的方法:1. 使用命令行:systemctl status nginx(Linux/Unix)、netstat -ano | findstr 80(Windows);2. 檢查端口 80 是否開放;3. 查看系統日誌中 Nginx 啟動消息;4. 使用第三方工具,如 Nagios、Zabbix、Icinga。

docker怎麼創建容器 docker怎麼創建容器 Apr 15, 2025 pm 12:18 PM

在 Docker 中創建容器: 1. 拉取鏡像: docker pull [鏡像名] 2. 創建容器: docker run [選項] [鏡像名] [命令] 3. 啟動容器: docker start [容器名]

nginx怎麼查版本 nginx怎麼查版本 Apr 14, 2025 am 11:57 AM

可以查詢 Nginx 版本的方法有:使用 nginx -v 命令;查看 nginx.conf 文件中的 version 指令;打開 Nginx 錯誤頁,查看頁面的標題。

nginx怎麼配置雲服務器域名 nginx怎麼配置雲服務器域名 Apr 14, 2025 pm 12:18 PM

在雲服務器上配置 Nginx 域名的方法:創建 A 記錄,指向雲服務器的公共 IP 地址。在 Nginx 配置文件中添加虛擬主機塊,指定偵聽端口、域名和網站根目錄。重啟 Nginx 以應用更改。訪問域名測試配置。其他注意事項:安裝 SSL 證書啟用 HTTPS、確保防火牆允許 80 端口流量、等待 DNS 解析生效。

nginx服務器掛了怎麼辦 nginx服務器掛了怎麼辦 Apr 14, 2025 am 11:42 AM

當 Nginx 服務器宕機時,可執行以下故障排除步驟:檢查 nginx 進程是否正在運行。查看錯誤日誌以獲取錯誤消息。檢查 nginx 配置語法正確性。確保 nginx 具有訪問文件所需的權限。檢查文件描述符打開限制。確認 nginx 正在偵聽正確的端口。添加防火牆規則以允許nginx流量。檢查反向代理設置,包括後端服務器可用性。如需進一步幫助,請聯繫技術支持。

See all articles