搜尋
首頁運維Nginxnginx記憶體池如何實現

一、簡介

最新穩定版本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 建立記憶體池<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><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>從程式碼可以看到,記憶體池最大不超過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);
}

當現有記憶體池中都無法滿足分配條件時,在建立新的記憶體池<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>其中,在建立好新的記憶體池後,又做了一次遍歷,將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刪除
NGINX單元:關鍵功能NGINX單元:關鍵功能Apr 25, 2025 am 12:17 AM

NGINXUnit是一個開源應用服務器,支持多種編程語言,提供動態配置、零停機更新和內置負載均衡等功能。 1.動態配置:無需重啟即可修改配置。 2.多語言支持:兼容Python、Go、Java、PHP等。 3.零停機更新:支持不中斷服務的應用更新。 4.內置負載均衡:可將請求分發到多個應用實例。

NGINX單元與其他應用程序服務器NGINX單元與其他應用程序服務器Apr 24, 2025 am 12:14 AM

NGINXUnit優於ApacheTomcat、Gunicorn和Node.js內置HTTP服務器,適用於多語言項目和動態配置需求。 1)支持多種編程語言,2)提供動態配置重載,3)內置負載均衡功能,適合需要高擴展性和可靠性的項目。

NGINX單元:架構及其工作原理NGINX單元:架構及其工作原理Apr 23, 2025 am 12:18 AM

NGINXUnit通過其模塊化架構和動態重配置功能提高了應用的性能和可管理性。 1)模塊化設計包括主控進程、路由器和應用進程,支持高效管理和擴展。 2)動態重配置允許在運行時無縫更新配置,適用於CI/CD環境。 3)多語言支持通過動態加載語言運行時實現,提升了開發靈活性。 4)高性能通過事件驅動模型和異步I/O實現,即使在高並發下也保持高效。 5)安全性通過隔離應用進程提高,減少應用間相互影響。

使用NGINX單元:部署和管理應用程序使用NGINX單元:部署和管理應用程序Apr 22, 2025 am 12:06 AM

NGINXUnit可用於部署和管理多種語言的應用。 1)安裝NGINXUnit。 2)配置它以運行不同類型的應用,如Python和PHP。 3)利用其動態配置功能進行應用管理。通過這些步驟,你可以高效地部署和管理應用,提升項目效率。

NGINX與Apache:Web服務器的比較分析NGINX與Apache:Web服務器的比較分析Apr 21, 2025 am 12:08 AM

NGINX更适合处理高并发连接,而Apache更适合需要复杂配置和模块扩展的场景。1.NGINX以高性能和低资源消耗著称,适合高并发。2.Apache以稳定性和丰富的模块扩展闻名,适合复杂配置需求。

NGINX單元的優勢:靈活性和性能NGINX單元的優勢:靈活性和性能Apr 20, 2025 am 12:07 AM

NGINXUnit通過其動態配置和高性能架構提升應用的靈活性和性能。 1.動態配置允許在不重啟服務器的情況下調整應用配置。 2.高性能體現在事件驅動和非阻塞架構以及多進程模型上,能夠高效處理並發連接和利用多核CPU。

NGINX與Apache:性能,可伸縮性和效率NGINX與Apache:性能,可伸縮性和效率Apr 19, 2025 am 12:05 AM

NGINX和Apache都是強大的Web服務器,各自在性能、可擴展性和效率上有獨特的優勢和不足。 1)NGINX在處理靜態內容和反向代理時表現出色,適合高並發場景。 2)Apache在處理動態內容時表現更好,適合需要豐富模塊支持的項目。選擇服務器應根據項目需求和場景來決定。

終極攤牌:nginx vs. apache終極攤牌:nginx vs. apacheApr 18, 2025 am 12:02 AM

NGINX適合處理高並發請求,Apache適合需要復雜配置和功能擴展的場景。 1.NGINX採用事件驅動、非阻塞架構,適用於高並發環境。 2.Apache採用進程或線程模型,提供豐富的模塊生態系統,適合複雜配置需求。

See all articles

熱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

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

熱工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

記事本++7.3.1

記事本++7.3.1

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

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具