運營研發團隊 施洪寶nginx
一. 概述
- 應用程序的內存能夠簡單分爲堆內存,棧內存。對於棧內存而言,在函數編譯時,編譯器會插入移動棧當前指針位置的代碼,實現棧空間的自管理。而對於堆內存,一般須要程序員進行管理。咱們一般說的內存管理亦是隻堆空間內存管理。
- 對於內存,咱們的使用能夠簡化爲3步,申請內存、使用內存、釋放內存。申請內存,使用內存一般須要程序員顯示操做,釋放內存卻並不必定須要程序員顯示操做,目前不少的高級語言提供了垃圾回收機制,能夠自行選擇時機釋放內存,例如: Go、Java已經實現垃圾回收, C語言目前還沒有實現垃圾回收,C++中能夠經過智能指針達到垃圾回收的目的。
- 除了語言層面的內存管理外,有時咱們須要在程序中自行管理內存,整體而言,對於內存管理,我認爲主要是解決如下問題:
- 用戶申請內存時,如何快速查找到知足用戶需求的內存塊?
- 用戶釋放內存時,如何避免內存碎片化?
不管是語言層面實現的內存管理仍是應用程序自行實現的內存管理,大都將內存按照大小分爲幾種,每種採用不一樣的管理模式。常見的分類是按照2的整數次冪分,將不一樣種類的內存經過鏈表連接,查詢時,從相應大小的鏈表中尋找,若是找不到,則能夠考慮從更大塊內存中,拿取一塊,將其分爲多個小點的內存。固然,對於特別大的內存,語言層面的內存管理能夠直接調用內存管理相關的系統調用,應用層面的內存管理則能夠直接使用語言層面的內存管理。
- nginx內存管理總體能夠分爲2個部分,
- 第一部分是常規的內存池,用於進程平時所需的內存管理;
- 第二部分是共享內存的管理。整體而言,共享內存教內存池要複雜的多。
二. nginx內存池管理
2.1 說明
- 本部分使用的nginx版本爲1.15.3
- 具體源碼參見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)
//從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 具體實現
- 以下圖所示,nginx將內存分爲2種,一種是小內存,一種是大內存,當申請的空間大於pool->max時,咱們認爲是大內存空間,不然是小內存空間。
//建立內存池的參數size減去頭部管理結構ngx_pool_t的大小
pool->max = size - sizeof(ngx_pool_t);

- 對於小塊內存空間, 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 說明
- 本部分使用的nginx版本是1.15.3
- 本部分源碼詳見src/core/ngx_slab.c, src/core/ngx_shmtx.c
- nginx共享內存內容相對較多,本文僅作簡單概述。
3.2 直接使用共享內存
3.2.1 基礎
- nginx中須要建立互斥鎖,用於後面多進程同步使用。除此以外,nginx可能須要一些統計信息,例如設置(stat_stub),對於這些變量,咱們並不須要特地管理,只須要開闢共享空間後,直接使用便可。
- 設置stat_stub後所需的統計信息,亦是放到共享內存中,咱們此處僅以nginx中的互斥鎖進行說明。
3.2.2 nginx互斥鎖的實現
- nginx互斥鎖,有兩種方案,當系統支持原子操做時,採用原子操做,不支持時採用文件鎖。本節源碼見ngx_event_module_init函數。
- 下圖爲文件鎖實現互斥鎖的示意圖。

- 下圖爲原子操做實現互斥鎖的示意圖。

- 問題
- reload時,新啓動的master向老的master發送信號後直接退出,舊的master,從新加載配置(ngx_init_cycle函數), 新建立工做進程, 新的工做進程與舊的工做進程使用的鎖是相同的。
- 平滑升級時, 舊的master會建立新的master, 新的master會繼承舊的master監聽的端口(經過環境變量傳遞監聽套接字對應的fd),新的進程並無從新綁定監聽端口。可能存在新老worker同時監聽某個端口的狀況,此時操做系統會保證只會有一個進程處理該事件(雖然epoll_wait都會被喚醒)。
3.3 經過slab管理共享內存
- nginx容許各個模塊開闢共享空間以供使用,例如ngx_http_limit_conn_module模塊。
- nginx共享內存管理的基本思想有:
- 將內存按照頁進行分配,每頁的大小相同, 此處設爲page_size。
- 將內存塊按照2的整數次冪進行劃分, 最小爲8bit, 最大爲page_size/2。例如,假設每頁大小爲4Kb, 則將內存分爲8, 16, 32, 64, 128, 256, 512, 1024, 2048共9種,每種對應一個slot, 此時slots數組的大小n即爲9。申請小塊內存(申請內存大小size <= page_size/2)時,直接給用戶這9種中的一種,例如,須要30bit時,找大小爲32的內存塊提供給用戶。
- 每一個頁只會劃分一種類型的內存塊。例如,某次申請內存時,現有內存沒法知足要求,此時會使用一個新的頁,則這個新頁此後只會分配這種大小的內存。
- 經過雙向鏈表將全部空閒的頁鏈接。圖中ngx_slab_pool_t中的free變量即便用來連接空閒頁的。
- 經過slots數組將全部小塊內存所使用的頁連接起來。
- 對於大於等於頁面大小的空間請求,計算所需頁數,找到連續的空閒頁,將空閒頁的首頁地址返回給客戶使用,經過每頁的管理結構ngx_slab_page_t進行標識。
- 全部頁面只會有3中狀態,空閒、未滿、已滿。空閒,未滿都是經過雙向鏈表進行整合,已滿頁面則不存在與任何頁面,當空間被釋放時,會將其加入到某個鏈表。
- nginx共享內存的基本結構圖以下:

- 在上圖中,除了最右側的ngx_slab_pool_t接口開始的一段內存位於共享內存區外,其餘內存都不是共享內存。
- 共享內存最終是從page中分配而來。