首頁  >  文章  >  後端開發  >  Nginx的記憶體管理的深入理解(圖)

Nginx的記憶體管理的深入理解(圖)

不言
不言轉載
2018-11-15 17:14:533683瀏覽

這篇文章帶給大家的內容是關於Nginx的記憶體管理的深入理解(圖),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

一. 概述

  1. 應用程式的記憶體可以簡單分為堆疊內存,堆疊記憶體。對於堆疊記憶體而言,在函數編譯時,編譯器會插入移動堆疊目前指標位置的程式碼,實現堆疊空間的自管理。而對於堆內存,通常需要程式設計師進行管理。我們通常說的記憶體管理亦是只堆空間記憶體管理。

  2. 對於內存,我們的使用可以簡化為3步,申請內存、使用內存、釋放內存。申請內存,使用內存通常需要程式設計師顯示操作,釋放內存卻不一定需要程式設計師顯示操作,目前很多的高級語言提供了垃圾回收機制,可以自行選擇時機釋放內存,例如: Go、Java已經實現垃圾回收, C語言目前尚未實現垃圾回收,C 中可以透過智慧指針達到垃圾回收的目的。

  3. 除了語言層面的記憶體管理外,有時我們需要在程式中自行管理內存,總體而言,對於記憶體管理,我認為主要是解決以下問題:

  • 使用者申請記憶體時,如何快速查找到滿足使用者需求的記憶體區塊?

  • 使用者釋放記憶體時,如何避免記憶體碎片化?

無論是語言層面實現的記憶體管理還是應用程式自行實現的記憶體管理,大都將記憶體按照大小分為幾種,每種採用不同的管理模式。常見的分類是按照2的整數次冪分,將不同種類的內存通過鍊錶鏈接,查詢時,從相應大小的鍊表中尋找,如果找不到,則可以考慮從更大塊內存中,拿取一塊,將其分為多個小點的記憶體。當然,對於特別大的內存,語言層面的內存管理可以直接調用內存管理相關的系統調用,應用層面的內存管理則可以直接使用語言層面的內存管理。

  1. nginx記憶體管理整體可以分成2個部分,

  • #第一部分是常規的記憶體池,用於進程平時所需的記憶體管理;

  • 第二部分是共享記憶體的管理。整體而言,共享記憶體較記憶體池要複雜的多。

二. nginx記憶體池管理

#2.1 說明

  1. ##本部分使用的nginx版本為1.15.3

  2. 具體原始碼參見src/core/ngx_palloc.c檔案

#2.2 nginx實作

2.2.1 使用流程

nginx記憶體池的使用較為簡單,可以分為3步,

  • 呼叫ngx_create_pool函數取得ngx_pool_t指標。

//size代表ngx_pool_t一块的大小
ngx_pool_t* ngx_create_pool(size_t size, ngx_log_t *log)
  • 呼叫ngx_palloc申請記憶體使用

//从pool中申请size大小的内存
void* ngx_palloc(ngx_pool_t *pool, size_t size)
  • 釋放記憶體(可以釋放大塊記憶體或釋放整個記憶體池)

//释放从pool中申请的大块内存
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)
//释放整个内存池
void ngx_destroy_pool(ngx_pool_t *pool)
2.2.2 具體實作

  1. 如下圖所示,nginx將記憶體分為2種,一種是小內存,一種是大內存,當申請的空間大於pool->max時,我們認為是大內存空間,否則是小內存空間。

  2. //创建内存池的参数size减去头部管理结构ngx_pool_t的大小
    pool->max = size - sizeof(ngx_pool_t);

Nginx的記憶體管理的深入理解(圖)

對於小塊記憶體空間, nginx首先查看目前記憶體區塊待分配的空間中,是否能夠滿足使用者需求,如果可以,則直接將這部分記憶體回傳。如果無法滿足使用者需求,則需要重新申請一個記憶體區塊,申請的記憶體區塊與目前區塊空間大小相同,將新申請的記憶體區塊透過鍊錶連結到上一個記憶體區塊,從新的記憶體區塊中分配使用者所需的內存。

小塊記憶體不會釋放,用戶申請後直接使用,即使後期不再使用也不需要釋放該記憶體。由於使用者有時不知道自己使用的記憶體區塊是大是小,此時也可以呼叫ngx_pfree函數來釋放該空間,該函數會從大空間鍊錶中尋找內存,找到則釋放記憶體。對於小內存而言,並未做任何處理。

對於大塊記憶體, nginx會將這些內存放到鍊錶中存儲,透過pool->large進行管理。值得注意的是,使用者管理大內存的ngx_pool_large_t結構是從本內存池的小塊內存中申請而來,也就意味著無法釋放這些內存,nginx則是直接復用ngx_pool_large_t結構體。當使用者需要申請大記憶體空間時,利用c函數庫malloc申請空間,然後將其掛載某個ngx_pool_large_t結構體上。 nginx在需要一個新的ngx_pool_large_t結構時,會首先pool->large鍊錶的前3個元素中,查看是否有可用的,如果有則直接使用,否則新建ngx_pool_large_t結構。

三. nginx共享記憶體管理

3.1 說明

  1. 本部分使用的nginx版本是1.15.3

  2. 本部分原始碼詳見src/core/ngx_slab.c, src/core/ngx_shmtx.c

  3. #nginx共享記憶體內容相對較多,本文僅做簡單概述。

3.2 直接使用共享記憶體

#3.2.1 基礎

  1. nginx中需要建立互斥鎖,用於後面多進程同步使用。除此之外,nginx可能需要一些統計信息,例如設置(stat_stub),對於這些變量,我們並不需要特意管理,只需要開闢共享空間後,直接使用即可。

  2. 設定stat_stub後所需的統計信息,也放到共享記憶體中,我們此處僅以nginx中的互斥鎖進行說明。

3.2.2 nginx互斥鎖的實作

  1. #nginx互斥鎖,有兩種方案,當系統支援原子操作時,採用原子操作,不支援時採用文件鎖。本節原始碼見ngx_event_module_init函式。

  2. 下圖為檔案鎖定實現互斥鎖的示意圖。

Nginx的記憶體管理的深入理解(圖)

  1. 下圖為原子運算實作互斥鎖的示意圖。

Nginx的記憶體管理的深入理解(圖)

  1. 問題

#reload時,新啟動的master向老的master發送訊號後直接退出,舊的master,重新載入配置(ngx_init_cycle函數), 新建立工作進程, 新的工作進程與舊的工作進程所使用的鎖是相同的。

平滑升級時, 舊的master會創建新的master, 新的master會繼承舊的master監聽的端口(透過環境變數傳遞監聽套接字對應的fd),新的進程並沒有重新綁定監聽埠。可能存在新舊worker同時監聽某個連接埠的情況,此時作業系統會保證只會有一個行程處理該事件(雖然epoll_wait都會被喚醒)。

3.3 透過slab管理共享記憶體

  1. nginx允許各個模組開啟共享空間以供使用,例如ngx_http_limit_conn_module模組。

  2. nginx共享記憶體管理的基本想法有:

#1、將記憶體依照頁分配,每頁的大小相同, 此處設為page_size。

2、將記憶體區塊依照2的整數次方劃分, 最小為8bit, 最大為page_size/2。例如,假設每頁大小為4Kb, 則將記憶體分為8, 16, 32, 64, 128, 256, 512, 1024, 2048共9種,每種對應一個slot, 此時slots數組的大小n即為9。申請小塊記憶體(申請記憶體大小size

3、每頁只會劃分一種類型的記憶體區塊。例如,某次申請記憶體時,現有記憶體無法滿足要求,此時會使用一個新的頁,則這個新頁此後只會分配這種大小的記憶體。

4、透過雙向鍊錶將所有空閒的頁連接。圖中ngx_slab_pool_t中的free變數即是使用來連結空閒頁的。

5、透過slots陣列將所有小塊記憶體所使用的頁連結起來。

6、對於大於等於頁面大小的空間請求,計算所需頁數,找到連續的空閒頁,將空閒頁的首頁位址傳回給客戶使用,透過每頁的管理結構ngx_slab_page_t進行識別。

7、所有頁面只會有3中狀態,空閒、未滿、已滿。空閒,未滿都是透過雙向鍊錶進行整合,已滿頁面則不存在與任何頁面,當空間被釋放時,會將其加入到某個鍊錶。

  1. nginx共享記憶體的基本結構圖如下:

Nginx的記憶體管理的深入理解(圖)

#
  • 在上圖中,除了最右側的ngx_slab_pool_t介面開始的一段記憶體位於共享記憶體區外,其他記憶體都不是共享記憶體。

  • 共享記憶體最終是從page分配而來。

#

以上是Nginx的記憶體管理的深入理解(圖)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除