來(lái)源:不言 發(fā)布時(shí)間:2018-11-16 16:21:27 閱讀量:1226
本篇文章給大家?guī)?lái)的內(nèi)容是關(guān)于Nginx的內(nèi)存管理的深入理解(圖),有一定的參考價(jià)值,有需要的朋友可以參考一下,希望對(duì)你有所幫助。
一. 概述
應(yīng)用程序的內(nèi)存可以簡(jiǎn)單分為堆內(nèi)存,棧內(nèi)存。對(duì)于棧內(nèi)存而言,在函數(shù)編譯時(shí),編譯器會(huì)插入移動(dòng)棧當(dāng)前指針位置的代碼,實(shí)現(xiàn)棧空間的自管理。而對(duì)于堆內(nèi)存,通常需要程序員進(jìn)行管理。我們通常說(shuō)的內(nèi)存管理亦是只堆空間內(nèi)存管理。
對(duì)于內(nèi)存,我們的使用可以簡(jiǎn)化為3步,申請(qǐng)內(nèi)存、使用內(nèi)存、釋放內(nèi)存。申請(qǐng)內(nèi)存,使用內(nèi)存通常需要程序員顯示操作,釋放內(nèi)存卻并不一定需要程序員顯示操作,目前很多的高級(jí)語(yǔ)言提供了垃圾回收機(jī)制,可以自行選擇時(shí)機(jī)釋放內(nèi)存,例如: Go、Java已經(jīng)實(shí)現(xiàn)垃圾回收, C語(yǔ)言目前尚未實(shí)現(xiàn)垃圾回收,C++中可以通過(guò)智能指針達(dá)到垃圾回收的目的。
除了語(yǔ)言層面的內(nèi)存管理外,有時(shí)我們需要在程序中自行管理內(nèi)存,總體而言,對(duì)于內(nèi)存管理,我認(rèn)為主要是解決以下問(wèn)題:
用戶申請(qǐng)內(nèi)存時(shí),如何快速查找到滿足用戶需求的內(nèi)存塊?
用戶釋放內(nèi)存時(shí),如何避免內(nèi)存碎片化?
無(wú)論是語(yǔ)言層面實(shí)現(xiàn)的內(nèi)存管理還是應(yīng)用程序自行實(shí)現(xiàn)的內(nèi)存管理,大都將內(nèi)存按照大小分為幾種,每種采用不同的管理模式。常見的分類是按照2的整數(shù)次冪分,將不同種類的內(nèi)存通過(guò)鏈表鏈接,查詢時(shí),從相應(yīng)大小的鏈表中尋找,如果找不到,則可以考慮從更大塊內(nèi)存中,拿取一塊,將其分為多個(gè)小點(diǎn)的內(nèi)存。當(dāng)然,對(duì)于特別大的內(nèi)存,語(yǔ)言層面的內(nèi)存管理可以直接調(diào)用內(nèi)存管理相關(guān)的系統(tǒng)調(diào)用,應(yīng)用層面的內(nèi)存管理則可以直接使用語(yǔ)言層面的內(nèi)存管理。
nginx內(nèi)存管理整體可以分為2個(gè)部分,
第一部分是常規(guī)的內(nèi)存池,用于進(jìn)程平時(shí)所需的內(nèi)存管理;
第二部分是共享內(nèi)存的管理。總體而言,共享內(nèi)存較內(nèi)存池要復(fù)雜的多。
二. nginx內(nèi)存池管理
2.1 說(shuō)明
本部分使用的nginx版本為1.15.3
具體源碼參見src/core/ngx_palloc.c文件
2.2 nginx實(shí)現(xiàn)
2.2.1 使用流程
nginx內(nèi)存池的使用較為簡(jiǎn)單,可以分為3步,
調(diào)用ngx_create_pool函數(shù)獲取ngx_pool_t指針。
1 2 |
|
調(diào)用ngx_palloc申請(qǐng)內(nèi)存使用
1 2 |
|
釋放內(nèi)存(可以釋放大塊內(nèi)存或者釋放整個(gè)內(nèi)存池)
1 2 3 4 |
|
如下圖所示,nginx將內(nèi)存分為2種,一種是小內(nèi)存,一種是大內(nèi)存,當(dāng)申請(qǐng)的空間大于pool->max時(shí),我們認(rèn)為是大內(nèi)存空間,否則是小內(nèi)存空間。
1 2 |
|
對(duì)于小塊內(nèi)存空間, nginx首先查看當(dāng)前內(nèi)存塊待分配的空間中,是否能夠滿足用戶需求,如果可以,則直接將這部分內(nèi)存返回。如果不能滿足用戶需求,則需要重新申請(qǐng)一個(gè)內(nèi)存塊,申請(qǐng)的內(nèi)存塊與當(dāng)前塊空間大小相同,將新申請(qǐng)的內(nèi)存塊通過(guò)鏈表鏈接到上一個(gè)內(nèi)存塊,從新的內(nèi)存塊中分配用戶所需的內(nèi)存。
小塊內(nèi)存并不釋放,用戶申請(qǐng)后直接使用,即使后期不再使用也不需要釋放該內(nèi)存。由于用戶有時(shí)并不知道自己使用的內(nèi)存塊是大是小,此時(shí)也可以調(diào)用ngx_pfree函數(shù)釋放該空間,該函數(shù)會(huì)從大空間鏈表中查找內(nèi)存,找到則釋放內(nèi)存。對(duì)于小內(nèi)存而言,并未做任何處理。
對(duì)于大塊內(nèi)存, nginx會(huì)將這些內(nèi)存放到鏈表中存儲(chǔ),通過(guò)pool->large進(jìn)行管理。值得注意的是,用戶管理大內(nèi)存的ngx_pool_large_t結(jié)構(gòu)是從本內(nèi)存池的小塊內(nèi)存中申請(qǐng)而來(lái),也就意味著無(wú)法釋放這些內(nèi)存,nginx則是直接復(fù)用ngx_pool_large_t結(jié)構(gòu)體。當(dāng)用戶需要申請(qǐng)大內(nèi)存空間時(shí),利用c函數(shù)庫(kù)malloc申請(qǐng)空間,然后將其掛載某個(gè)ngx_pool_large_t結(jié)構(gòu)體上。nginx在需要一個(gè)新的ngx_pool_large_t結(jié)構(gòu)時(shí),會(huì)首先pool->large鏈表的前3個(gè)元素中,查看是否有可用的,如果有則直接使用,否則新建ngx_pool_large_t結(jié)構(gòu)。
三. nginx共享內(nèi)存管理
3.1 說(shuō)明
本部分使用的nginx版本是1.15.3
本部分源碼詳見src/core/ngx_slab.c, src/core/ngx_shmtx.c
nginx共享內(nèi)存內(nèi)容相對(duì)較多,本文僅做簡(jiǎn)單概述。
3.2 直接使用共享內(nèi)存
3.2.1 基礎(chǔ)
nginx中需要?jiǎng)?chuàng)建互斥鎖,用于后面多進(jìn)程同步使用。除此之外,nginx可能需要一些統(tǒng)計(jì)信息,例如設(shè)置(stat_stub),對(duì)于這些變量,我們并不需要特意管理,只需要開辟共享空間后,直接使用即可。
設(shè)置stat_stub后所需的統(tǒng)計(jì)信息,亦是放到共享內(nèi)存中,我們此處僅以nginx中的互斥鎖進(jìn)行說(shuō)明。
3.2.2 nginx互斥鎖的實(shí)現(xiàn)
nginx互斥鎖,有兩種方案,當(dāng)系統(tǒng)支持原子操作時(shí),采用原子操作,不支持時(shí)采用文件鎖。本節(jié)源碼見ngx_event_module_init函數(shù)。
下圖為文件鎖實(shí)現(xiàn)互斥鎖的示意圖。
下圖為原子操作實(shí)現(xiàn)互斥鎖的示意圖。
問(wèn)題
reload時(shí),新啟動(dòng)的master向老的master發(fā)送信號(hào)后直接退出,舊的master,重新加載配置(ngx_init_cycle函數(shù)), 新創(chuàng)建工作進(jìn)程, 新的工作進(jìn)程與舊的工作進(jìn)程使用的鎖是相同的。
平滑升級(jí)時(shí), 舊的master會(huì)創(chuàng)建新的master, 新的master會(huì)繼承舊的master監(jiān)聽的端口(通過(guò)環(huán)境變量傳遞監(jiān)聽套接字對(duì)應(yīng)的fd),新的進(jìn)程并沒(méi)有重新綁定監(jiān)聽端口??赡艽嬖谛吕蟱orker同時(shí)監(jiān)聽某個(gè)端口的情況,此時(shí)操作系統(tǒng)會(huì)保證只會(huì)有一個(gè)進(jìn)程處理該事件(雖然epoll_wait都會(huì)被喚醒)。
3.3 通過(guò)slab管理共享內(nèi)存
nginx允許各個(gè)模塊開辟共享空間以供使用,例如ngx_http_limit_conn_module模塊。
nginx共享內(nèi)存管理的基本思想有:
1、將內(nèi)存按照頁(yè)進(jìn)行分配,每頁(yè)的大小相同, 此處設(shè)為page_size。
2、將內(nèi)存塊按照2的整數(shù)次冪進(jìn)行劃分, 最小為8bit, 最大為page_size/2。例如,假設(shè)每頁(yè)大小為4Kb, 則將內(nèi)存分為8, 16, 32, 64, 128, 256, 512, 1024, 2048共9種,每種對(duì)應(yīng)一個(gè)slot, 此時(shí)slots數(shù)組的大小n即為9。申請(qǐng)小塊內(nèi)存(申請(qǐng)內(nèi)存大小size <= page_size/2)時(shí),直接給用戶這9種中的一種,例如,需要30bit時(shí),找大小為32的內(nèi)存塊提供給用戶。
3、每個(gè)頁(yè)只會(huì)劃分一種類型的內(nèi)存塊。例如,某次申請(qǐng)內(nèi)存時(shí),現(xiàn)有內(nèi)存無(wú)法滿足要求,此時(shí)會(huì)使用一個(gè)新的頁(yè),則這個(gè)新頁(yè)此后只會(huì)分配這種大小的內(nèi)存。
4、通過(guò)雙向鏈表將所有空閑的頁(yè)連接。圖中ngx_slab_pool_t中的free變量即使用來(lái)鏈接空閑頁(yè)的。
5、通過(guò)slots數(shù)組將所有小塊內(nèi)存所使用的頁(yè)鏈接起來(lái)。
6、對(duì)于大于等于頁(yè)面大小的空間請(qǐng)求,計(jì)算所需頁(yè)數(shù),找到連續(xù)的空閑頁(yè),將空閑頁(yè)的首頁(yè)地址返回給客戶使用,通過(guò)每頁(yè)的管理結(jié)構(gòu)ngx_slab_page_t進(jìn)行標(biāo)識(shí)。
7、所有頁(yè)面只會(huì)有3中狀態(tài),空閑、未滿、已滿??臻e,未滿都是通過(guò)雙向鏈表進(jìn)行整合,已滿頁(yè)面則不存在與任何頁(yè)面,當(dāng)空間被釋放時(shí),會(huì)將其加入到某個(gè)鏈表。
nginx共享內(nèi)存的基本結(jié)構(gòu)圖如下:
在上圖中,除了最右側(cè)的ngx_slab_pool_t接口開始的一段內(nèi)存位于共享內(nèi)存區(qū)外,其他內(nèi)存都不是共享內(nèi)存。
在線
客服
服務(wù)時(shí)間:周一至周日 08:30-18:00
選擇下列產(chǎn)品馬上在線溝通:
客服
熱線
7*24小時(shí)客服服務(wù)熱線
關(guān)注
微信
關(guān)注官方微信