摘要:而對于堆內(nèi)存,通常需要程序員進行管理。二內(nèi)存池管理說明本部分使用的版本為具體源碼參見文件實現(xiàn)使用流程內(nèi)存池的使用較為簡單可以分為步,調(diào)用函數(shù)獲取指針。將內(nèi)存塊按照的整數(shù)次冪進行劃分最小為最大為。
運營研發(fā)團隊 施洪寶
一. 概述應用程序的內(nèi)存可以簡單分為堆內(nèi)存,棧內(nèi)存。對于棧內(nèi)存而言,在函數(shù)編譯時,編譯器會插入移動棧當前指針位置的代碼,實現(xiàn)??臻g的自管理。而對于堆內(nèi)存,通常需要程序員進行管理。我們通常說的內(nèi)存管理亦是只堆空間內(nèi)存管理。
對于內(nèi)存,我們的使用可以簡化為3步,申請內(nèi)存、使用內(nèi)存、釋放內(nèi)存。申請內(nèi)存,使用內(nèi)存通常需要程序員顯示操作,釋放內(nèi)存卻并不一定需要程序員顯示操作,目前很多的高級語言提供了垃圾回收機制,可以自行選擇時機釋放內(nèi)存,例如: Go、Java已經(jīng)實現(xiàn)垃圾回收, C語言目前尚未實現(xiàn)垃圾回收,C++中可以通過智能指針達到垃圾回收的目的。
除了語言層面的內(nèi)存管理外,有時我們需要在程序中自行管理內(nèi)存,總體而言,對于內(nèi)存管理,我認為主要是解決以下問題:
用戶申請內(nèi)存時,如何快速查找到滿足用戶需求的內(nèi)存塊?
用戶釋放內(nèi)存時,如何避免內(nèi)存碎片化?
無論是語言層面實現(xiàn)的內(nèi)存管理還是應用程序自行實現(xiàn)的內(nèi)存管理,大都將內(nèi)存按照大小分為幾種,每種采用不同的管理模式。常見的分類是按照2的整數(shù)次冪分,將不同種類的內(nèi)存通過鏈表鏈接,查詢時,從相應大小的鏈表中尋找,如果找不到,則可以考慮從更大塊內(nèi)存中,拿取一塊,將其分為多個小點的內(nèi)存。當然,對于特別大的內(nèi)存,語言層面的內(nèi)存管理可以直接調(diào)用內(nèi)存管理相關的系統(tǒng)調(diào)用,應用層面的內(nèi)存管理則可以直接使用語言層面的內(nèi)存管理。
nginx內(nèi)存管理整體可以分為2個部分,
第一部分是常規(guī)的內(nèi)存池,用于進程平時所需的內(nèi)存管理;
第二部分是共享內(nèi)存的管理。總體而言,共享內(nèi)存教內(nèi)存池要復雜的多。
二. nginx內(nèi)存池管理 2.1 說明本部分使用的nginx版本為1.15.3
具體源碼參見src/core/ngx_palloc.c文件
2.2 nginx實現(xiàn) 2.2.1 使用流程nginx內(nèi)存池的使用較為簡單,可以分為3步,
調(diào)用ngx_create_pool函數(shù)獲取ngx_pool_t指針。
//size代表ngx_pool_t一塊的大小 ngx_pool_t* ngx_create_pool(size_t size, ngx_log_t *log)
調(diào)用ngx_palloc申請內(nèi)存使用
//從pool中申請size大小的內(nèi)存 void* ngx_palloc(ngx_pool_t *pool, size_t size)
釋放內(nèi)存(可以釋放大塊內(nèi)存或者釋放整個內(nèi)存池)
//釋放從pool中申請的大塊內(nèi)存 ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p) //釋放整個內(nèi)存池 void ngx_destroy_pool(ngx_pool_t *pool)2.2.2 具體實現(xiàn)
如下圖所示,nginx將內(nèi)存分為2種,一種是小內(nèi)存,一種是大內(nèi)存,當申請的空間大于pool->max時,我們認為是大內(nèi)存空間,否則是小內(nèi)存空間。
//創(chuàng)建內(nèi)存池的參數(shù)size減去頭部管理結(jié)構(gòu)ngx_pool_t的大小 pool->max = size - sizeof(ngx_pool_t);
對于小塊內(nèi)存空間, nginx首先查看當前內(nèi)存塊待分配的空間中,是否能夠滿足用戶需求,如果可以,則直接將這部分內(nèi)存返回。如果不能滿足用戶需求,則需要重新申請一個內(nèi)存塊,申請的內(nèi)存塊與當前塊空間大小相同,將新申請的內(nèi)存塊通過鏈表鏈接到上一個內(nèi)存塊,從新的內(nèi)存塊中分配用戶所需的內(nèi)存。
小塊內(nèi)存并不釋放,用戶申請后直接使用,即使后期不再使用也不需要釋放該內(nèi)存。由于用戶有時并不知道自己使用的內(nèi)存塊是大是小,此時也可以調(diào)用ngx_pfree函數(shù)釋放該空間,該函數(shù)會從大空間鏈表中查找內(nèi)存,找到則釋放內(nèi)存。對于小內(nèi)存而言,并未做任何處理。
對于大塊內(nèi)存, nginx會將這些內(nèi)存放到鏈表中存儲,通過pool->large進行管理。值得注意的是,用戶管理大內(nèi)存的ngx_pool_large_t結(jié)構(gòu)是從本內(nèi)存池的小塊內(nèi)存中申請而來,也就意味著無法釋放這些內(nèi)存,nginx則是直接復用ngx_pool_large_t結(jié)構(gòu)體。當用戶需要申請大內(nèi)存空間時,利用c函數(shù)庫malloc申請空間,然后將其掛載某個ngx_pool_large_t結(jié)構(gòu)體上。nginx在需要一個新的ngx_pool_large_t結(jié)構(gòu)時,會首先pool->large鏈表的前3個元素中,查看是否有可用的,如果有則直接使用,否則新建ngx_pool_large_t結(jié)構(gòu)。
三. nginx共享內(nèi)存管理 3.1 說明本部分使用的nginx版本是1.15.3
本部分源碼詳見src/core/ngx_slab.c, src/core/ngx_shmtx.c
nginx共享內(nèi)存內(nèi)容相對較多,本文僅做簡單概述。
3.2 直接使用共享內(nèi)存 3.2.1 基礎nginx中需要創(chuàng)建互斥鎖,用于后面多進程同步使用。除此之外,nginx可能需要一些統(tǒng)計信息,例如設置(stat_stub),對于這些變量,我們并不需要特意管理,只需要開辟共享空間后,直接使用即可。
設置stat_stub后所需的統(tǒng)計信息,亦是放到共享內(nèi)存中,我們此處僅以nginx中的互斥鎖進行說明。
3.2.2 nginx互斥鎖的實現(xiàn)nginx互斥鎖,有兩種方案,當系統(tǒng)支持原子操作時,采用原子操作,不支持時采用文件鎖。本節(jié)源碼見ngx_event_module_init函數(shù)。
下圖為文件鎖實現(xiàn)互斥鎖的示意圖。
下圖為原子操作實現(xiàn)互斥鎖的示意圖。
問題
3.3 通過slab管理共享內(nèi)存reload時,新啟動的master向老的master發(fā)送信號后直接退出,舊的master,重新加載配置(ngx_init_cycle函數(shù)), 新創(chuàng)建工作進程, 新的工作進程與舊的工作進程使用的鎖是相同的。
平滑升級時, 舊的master會創(chuàng)建新的master, 新的master會繼承舊的master監(jiān)聽的端口(通過環(huán)境變量傳遞監(jiān)聽套接字對應的fd),新的進程并沒有重新綁定監(jiān)聽端口。可能存在新老worker同時監(jiān)聽某個端口的情況,此時操作系統(tǒng)會保證只會有一個進程處理該事件(雖然epoll_wait都會被喚醒)。
nginx允許各個模塊開辟共享空間以供使用,例如ngx_http_limit_conn_module模塊。
nginx共享內(nèi)存管理的基本思想有:
將內(nèi)存按照頁進行分配,每頁的大小相同, 此處設為page_size。
將內(nèi)存塊按照2的整數(shù)次冪進行劃分, 最小為8bit, 最大為page_size/2。例如,假設每頁大小為4Kb, 則將內(nèi)存分為8, 16, 32, 64, 128, 256, 512, 1024, 2048共9種,每種對應一個slot, 此時slots數(shù)組的大小n即為9。申請小塊內(nèi)存(申請內(nèi)存大小size <= page_size/2)時,直接給用戶這9種中的一種,例如,需要30bit時,找大小為32的內(nèi)存塊提供給用戶。
每個頁只會劃分一種類型的內(nèi)存塊。例如,某次申請內(nèi)存時,現(xiàn)有內(nèi)存無法滿足要求,此時會使用一個新的頁,則這個新頁此后只會分配這種大小的內(nèi)存。
通過雙向鏈表將所有空閑的頁連接。圖中ngx_slab_pool_t中的free變量即使用來鏈接空閑頁的。
通過slots數(shù)組將所有小塊內(nèi)存所使用的頁鏈接起來。
對于大于等于頁面大小的空間請求,計算所需頁數(shù),找到連續(xù)的空閑頁,將空閑頁的首頁地址返回給客戶使用,通過每頁的管理結(jié)構(gòu)ngx_slab_page_t進行標識。
所有頁面只會有3中狀態(tài),空閑、未滿、已滿??臻e,未滿都是通過雙向鏈表進行整合,已滿頁面則不存在與任何頁面,當空間被釋放時,會將其加入到某個鏈表。
nginx共享內(nèi)存的基本結(jié)構(gòu)圖如下:
在上圖中,除了最右側(cè)的ngx_slab_pool_t接口開始的一段內(nèi)存位于共享內(nèi)存區(qū)外,其他內(nèi)存都不是共享內(nèi)存。
共享內(nèi)存最終是從page中分配而來。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/40180.html
摘要:第一階段基礎階段基礎程序員重點把搞熟練核心是安裝配置基本操作目標能夠完成基本的系統(tǒng)安裝,簡單配置維護能夠做基本的簡單系統(tǒng)的開發(fā)能夠在中型系統(tǒng)中支持某個功能模塊的開發(fā)。本項不做重點學習,除非對前端有興趣。 第一階段:基礎階段(基礎PHP程序員) 重點:把LNMP搞熟練(核心是安裝配置基本操作) 目標:能夠完成基本的LNMP系統(tǒng)安裝,簡單配置維護;能夠做基本的簡單系統(tǒng)的PHP開發(fā);能夠在P...
摘要:而對于堆內(nèi)存,通常需要程序員進行管理。我們通常說的內(nèi)存管理亦是只堆空間內(nèi)存管理。內(nèi)存管理整體可以分為個部分,第一部分是常規(guī)的內(nèi)存池,用于進程平時所需的內(nèi)存管理第二部分是共享內(nèi)存的管理。將內(nèi)存塊按照的整數(shù)次冪進行劃分最小為最大為。 施洪寶 一. 概述 應用程序的內(nèi)存可以簡單分為堆內(nèi)存,棧內(nèi)存。對于棧內(nèi)存而言,在函數(shù)編譯時,編譯器會插入移動棧當前指針位置的代碼,實現(xiàn)棧空間的自管理。而對于...
摘要:上圖中,每個紅圈表示一個請求,每一層的請求分別是上一層請求的子請求。換而言之,父請求是依賴于子請求的。特別地,的子請求運行時,會阻塞父請求掛起其對應的協(xié)程。 張超:又拍云系統(tǒng)開發(fā)高級工程師,負責又拍云 CDN 平臺相關組件的更新及維護。Github ID: tokers,活躍于 OpenResty 社區(qū)和 Nginx 郵件列表等開源社區(qū),專注于服務端技術的研究;曾為 ngx_lua 貢...
摘要:現(xiàn)在使用的各種哈希函數(shù)基本上只能保證較小概率出現(xiàn)兩個不同的其相同的情況。而出現(xiàn)兩個值對應的相同的情況,稱為哈希沖突。中的哈希表需要指出的是,中自造的哈希表屬于內(nèi)部使用的數(shù)據(jù)結(jié)構(gòu),因此,并不是一個通用的哈希表。 源文件路徑 版本:1.8.0 csrccoreNgx_hash.h srccoreNgx_hash.c 關于hash表 Nginx實現(xiàn)的hash表和常見的hash表大體...
閱讀 1003·2023-04-26 01:47
閱讀 1683·2021-11-18 13:19
閱讀 2050·2019-08-30 15:44
閱讀 665·2019-08-30 15:44
閱讀 2306·2019-08-30 15:44
閱讀 1242·2019-08-30 14:06
閱讀 1429·2019-08-30 12:59
閱讀 1907·2019-08-29 12:49