本文主要介紹內(nèi)存管理機制:物理內(nèi)存與虛擬內(nèi)存的關(guān)系,Linux內(nèi)存管理機制,Python內(nèi)存管理機制,Nginx內(nèi)存管理機制,環(huán)形緩沖區(qū)機制,以及TC-malloc內(nèi)存分配器的Andriod管理機制的簡單介紹。
一。 物理內(nèi)存與虛擬內(nèi)存
眾所周知,程序需要加載到物理內(nèi)存才能運行,多核時代會出現(xiàn)多個進程同時操作同一物理地址的情況,進而造成混亂和程序崩潰。計算機當(dāng)中很多問題的解決都是通過引入中間層,為解決物理內(nèi)存使用問題,虛擬內(nèi)存作為中間層進入了操作系統(tǒng),從此,程序不在直接操作物理內(nèi)存,只能看到虛擬內(nèi)存,通過虛擬內(nèi)存,非常優(yōu)雅的將進程環(huán)境隔離開來,每個進程都擁有自己獨立的虛擬地址空間,且所有進程地址空間范圍完全一致,也給編程帶來了很大的便利,同時也提高了物理內(nèi)存的使用率,可同時運行更多的進程。
物理內(nèi)存和虛擬內(nèi)存之間的關(guān)系
虛擬內(nèi)存以頁為單位進行劃分,每個頁對應(yīng)物理內(nèi)存上的頁框(通常大小為4KB),內(nèi)存管理單元(MMU)負責(zé)將虛擬地址轉(zhuǎn)換為物理地址,MMU中有一張頁表來存儲這些映射關(guān)系。
并非虛擬內(nèi)存中所有的頁都會分配對應(yīng)的物理內(nèi)存,為充分利用物理內(nèi)存,保證盡可能多的進程運行在操作系統(tǒng)上,因此需要提高物理內(nèi)存利用率,對于很少用到的虛擬內(nèi)存頁不分配對應(yīng)的物理內(nèi)存,只有用到的頁分配物理內(nèi)存。雖然從程序角度來看,虛擬內(nèi)存為連續(xù)地址空間,但其實,它被分隔成多個物理內(nèi)存碎片,甚至還有部分?jǐn)?shù)據(jù)并不在內(nèi)存中,而是在磁盤上。
當(dāng)訪問虛擬內(nèi)存時,通過MMU尋找與之對應(yīng)的物理內(nèi)存,如果沒有找到,操作系統(tǒng)會觸發(fā)缺頁中斷,從磁盤中取得所缺的頁并將其換入物理內(nèi)存,并在頁表中建立虛擬頁與物理頁的映射關(guān)系。
如果物理內(nèi)存滿了,操作系統(tǒng)會根據(jù)某種頁面置換算法(比如LRU算法),將物理內(nèi)存對應(yīng)的頁換出到磁盤,如果被換出的物理內(nèi)存被修改過,則必須將其寫回磁盤以更新對應(yīng)的副本。
當(dāng)進程創(chuàng)建時,內(nèi)核為進程分配4G虛擬內(nèi)存,此時,僅僅只是建立一個映射關(guān)系,程序的數(shù)據(jù)和代碼都還在磁盤中,只有當(dāng)運行時才換回物理內(nèi)存。并且,通過malloc來分配動態(tài)內(nèi)存時,也只分配了虛擬內(nèi)存,并不會直接給物理內(nèi)存,因此,理論上來說malloc可分配的內(nèi)存大小應(yīng)該是無限制的(實際當(dāng)然會有很多算法進行限制)。
多進程使用同一物理內(nèi)存圖如下:
物理內(nèi)存與虛擬內(nèi)存關(guān)系
二。 Linux內(nèi)存管理機制進程地址空間
進程地址空間分為內(nèi)核空間(3G到4G)和用戶空間(0到3G),如下圖。
進程內(nèi)存地址空間
內(nèi)核通過brk和mmap來分配(虛擬)內(nèi)存,malloc/free底層實現(xiàn)即為brk, mmap和unmmap
當(dāng)malloc內(nèi)存小于128k時采用brk,其通過將數(shù)據(jù)段(.data)的地址指針_edata往高地址推來分配內(nèi)存,brk分配的內(nèi)存需要高地址內(nèi)存全部釋放后才會釋放,當(dāng)最高地址空間空閑內(nèi)存大于128K時,執(zhí)行內(nèi)存緊縮操作。
當(dāng)malloc內(nèi)存大于128K時采用mmap,其在堆棧中間的文件映射區(qū)域(Memory Mapping Segment)找空閑虛擬內(nèi)存,mmap分配的內(nèi)存可單獨釋放。
每個進程都對應(yīng)一個mm_struct結(jié)構(gòu)體,即唯一的進程地址空間
// include/linux/mm.h
struct vm_area_struct {
struct mm_struct * vm_mm;
};
// include/linux/sched.h
struct mm_struct {
struct vm_area_struct *mmap; // vma鏈表結(jié)構(gòu)
struct rb_root mm_rb; // 紅黑樹指針
struct vm_area_struct *mmap_cache; // 指向最近找到的虛擬區(qū)間
atomic_t mm_users; // 正在使用該地址的進程數(shù)
atomic_t mm_count; // 引用計數(shù),為0時銷毀
struct list_head mmlist; // 所有mm_struct結(jié)構(gòu)體都通過mmlist連接在一個雙向鏈表中
};
linux內(nèi)核用struct page結(jié)構(gòu)體表示物理頁:
// include/linux/mm.h
struct page {
unsigned long flags; // 頁標(biāo)識符
atomic_t count; // 頁引用計數(shù)
struct list_head list; // 頁鏈表
struct address_space *mapping; // 所屬的inode
unsigned long index; // mapping中的偏移
struct list_head lru; // LRU最近最久未使用, struct slab結(jié)構(gòu)指針鏈表頭變量
void *virtual; // 頁虛擬地址
}
內(nèi)存碎片與外存碎片
內(nèi)存碎片
產(chǎn)生原因:分配的內(nèi)存空間大于請求所需的內(nèi)存空間,造成內(nèi)存碎片
解決辦法:伙伴算法,主要包括內(nèi)存分配和釋放兩步:
內(nèi)存分配:需滿足兩個條件,1) 大于請求所需內(nèi)存;2)為最小內(nèi)存塊(如64K為一頁)的倍數(shù)。比如,最小內(nèi)存塊為64K,若分配100K內(nèi)存,則應(yīng)分配64*2=128K內(nèi)存大小。
內(nèi)存釋放:包含兩步,1)釋放內(nèi)存;2)檢查是否可與相鄰塊合并,直到?jīng)]有可合并內(nèi)存塊。
接下來通過一張圖來詳細說明伙伴算法原理(From wiki),如下:
伙伴算法圖解
Step步驟詳解(注意最左側(cè)Step為步驟,ABCD申請者對應(yīng)不同的顏色):
初始化內(nèi)存,最小內(nèi)存塊為64K,分配1024KB(只截取部分進行說明)
A申請34K內(nèi)存,因此需64K內(nèi)存塊,步驟2.1 2.2 2.3 2.4都為對半操作,步驟2.5找到滿足條件的塊(64K),分配給A
B申請66K內(nèi)存,因此需要128K內(nèi)存塊,有現(xiàn)成的直接分配
C申請35K內(nèi)存,需64K內(nèi)存塊,直接分配
D申請67K內(nèi)存,需128K內(nèi)存塊,步驟5.1對半操作,步驟5.2分配
釋放B內(nèi)存塊,沒有相鄰內(nèi)存可合并
釋放D內(nèi)存塊,步驟7.1釋放內(nèi)存,步驟7.2 與相鄰塊進行內(nèi)存合并
A釋放內(nèi)存,不許合并內(nèi)存
C釋放內(nèi)存,步驟9.1釋放內(nèi)存,步驟9.2-9.5進行合并,整塊內(nèi)存恢復(fù)如初
以上為伙伴算法原理,Linux關(guān)鍵代碼在mm/page_alloc.c中,有興趣讀者可在內(nèi)核源碼中閱讀細節(jié),如下:
//mm/page_alloc.c
// 塊分配, removing an element from the buddy allocator
// 再zone中找到一個空閑塊,order(0:單頁,1:雙頁,2:4頁 2 ^ order)
static struct page * __rmqueue(struct zone *zone, unsigned int order)
{
}
// 塊釋放,處理合并邏輯
static int
free_pages_bulk(struct zone *zone, int count, struct list_head *list, unsigned int order) {
}
這里簡單介紹云風(fēng)實現(xiàn)的伙伴算法,實現(xiàn)思路:用數(shù)組實現(xiàn)完全二叉樹來管理內(nèi)存,樹節(jié)點標(biāo)記使用狀態(tài),在分配和釋放中通過節(jié)點的狀態(tài)來進行內(nèi)存塊的分離與合并,如下:
// 數(shù)組實現(xiàn)二叉樹
struct buddy {
int level; // 二叉樹深度
uint8_tree[1]; // 記錄二叉樹用來存儲內(nèi)存塊(節(jié)點)使用情況,柔性數(shù)組,不占內(nèi)存
};
// 分配大小為s的內(nèi)存
int
buddy_alloc(struct buddy * self, int s) {
// 分配大小s的內(nèi)存,返回分配內(nèi)存偏移量地址(首地址)
int size;
if (s == 0) {
size = 1;
} else {
// 獲取大于s的最小2次冪
size = (int)next_pow_of_2(s);
}
int length = 1 《《 self-》level;
if(size 》 length)
return -1;
int index = 0;
int level = 0;
while (index 》= 0) {
//具體分配細節(jié)。..
}
return -1;
}
// 釋放內(nèi)存并嘗試合并
void
buddy_free(struct buddy * self, int offset) {
// 釋放偏移量offset開始的內(nèi)存塊
int left = 0;
int length = 1 《《 self-》level;
int index;
for (;;) {
switch(self-》tree[index]) {
case NODE_USED:
_combine(self, index); // 嘗試合并
return;
case NODE_UNUSED:
return;
default:
// 。..
}
}
}
外存碎片
產(chǎn)生原因:未被分配的內(nèi)存,出現(xiàn)大量零碎不連續(xù)小內(nèi)存,無法滿足較大內(nèi)存申請,造成外部碎片
解決辦法:采用slab分配器,處理小內(nèi)存分配問題,slab分配器分配內(nèi)存以字節(jié)為單位,基于伙伴系統(tǒng)分配的大內(nèi)存進一步細分成小內(nèi)存分配
slab分三種:slabs_full(完全分配的slab),slabs_partial(部分分配的slab),slabs_empty(空slab),一個slab分配滿了之后就從slabs_partial刪除,同時插入到slab_fulls中。
slab兩個作用:1)小對象分配,不必每個小對象分配一個頁,節(jié)省空間;2)內(nèi)核中一些小對象創(chuàng)建析構(gòu)頻繁,slab對小對象緩存,可重復(fù)利用一些相同對象,減少內(nèi)存分配次數(shù)。(應(yīng)用于內(nèi)核對象的緩存)。
slab分配器基于對象(內(nèi)核中數(shù)據(jù)結(jié)構(gòu))進行管理,相同類型對象歸為一類,每當(dāng)申請這樣一個對象,slab分配器就從一個slab列表中分配一個這樣大小的單元,當(dāng)釋放時,將其重新保存到原列表中,而不是直接返還給伙伴系統(tǒng),避免內(nèi)存碎片。slab分配對象時,會使用最近釋放的對象的內(nèi)存塊,因此其駐留在cpu高速緩存中的概率會大大提高
Slab分配器
三。 Python內(nèi)存管理機制內(nèi)存管理層次結(jié)構(gòu)
Python內(nèi)存層次結(jié)構(gòu)
Layer 0:操作系統(tǒng)提供的內(nèi)存管理接口,比如malloc,free,python不能干涉這一層
Layer 1:封裝malloc,free等接口PyMem_API,提供統(tǒng)一的raw memory管理接口,為了可移植性。
Layer 2:構(gòu)建了更高抽象層次的內(nèi)存管理策略(GC藏身之處)
Layer 3:對象緩沖池
// 第1層 PyMem_Malloc通過一個宏P(guān)yMem_MALLOC實現(xiàn)
// pymem.h
PyAPI_FUNC(void *) PyMem_Malloc(size_t);
PyAPI_FUNC(void *) PyMem_Realloc(size_t);
PyAPI_FUNC(void *) PyMem_Free(size_t);
#define PyMem_MALLOC(n) ((size_t)(n) 》 (size_t)PY_SSIZE_T_MAX ? NULL
: malloc(((n) != 0) ? (n) : 1))
#define PyMem_MALLOC(n) ((size_t)(n) 》 (size_t)PY_SSIZE_T_MAX ? NULL
: realloc(((n) != 0) ? (n) : 1))
#define PyMem_FREE free
// Type-oriented memory interface 指定類型
#define PyMem_New(type, n)
( ((size_t)(n) 》 PY_SSIZE_T_MAX / sizeof(type)) ? NULL :
( (type*)PyMem_Malloc((n) * sizeof(type))) ) )
#define PyMem_NEW(type, n)
( ((size_t)(n) 》 PY_SSIZE_T_MAX / sizeof(type)) ? NULL :
( (type*)PyMem_MALLOC((n) * sizeof(type))) ) )
小塊空間的內(nèi)存池
Python內(nèi)存池可視為一個層次結(jié)構(gòu),自下而上分為四層:block,pool,arena和內(nèi)存池(概念),其中bock, pool, arena在python中都能找到實體,而內(nèi)存池是由所有這些組織起來的一個概念。
Python針對小對象(小于256字節(jié))的內(nèi)存分配采用內(nèi)存池來進行管理,大對象直接使用標(biāo)準(zhǔn)C的內(nèi)存分配器malloc。
對小對象內(nèi)存的分配器Python進行了3個等級的抽象,從上至下依次為:Arena,Pool和Block。即,Pool由Block組成,Arena由Pool組成。
Block
block內(nèi)存大小值被稱為size class, 大小為:[8, 16, 24, 32, 40, 48 。.. 256],(8*n),內(nèi)存管理器的最小單元,一個Block存儲一個Python對象。
// obmalloc.c
// 8字節(jié)對齊
#define ALIGNMENT 8
#define ALIGNMENT_SHIFT 3
#define ALIGNMENT_MASK (ALIGNMENT - 1)
// block大小上限為256,超過256KB,則交由第一層的內(nèi)存管理機制
#define SMALL_REQUEST_THRESHOLD 256
#define NB_SMALL_SIZZE_CLASSES (SMALL_REQUEST_THREASHOLD / ALIGNMENT)
// size class index 轉(zhuǎn)換到 size class
#define INDEX2SIZE(I) (((unit) (I)) + 1) 《《 ALIGMENT_SHIFT)
// sizes class 轉(zhuǎn)換到size class index
size = (uint )(nbytes - 1) 》》 ALIGMENT_SHIFT;
小于256KB的小塊內(nèi)存分配如下圖。
Block分配策略
如果申請內(nèi)存大小為28字節(jié),則PyObject_Malloc從內(nèi)存池中分配32字節(jié)的block,size class index為3的pool(參考上圖)。
Pool
Pool為一個雙向鏈表結(jié)構(gòu),一系列Block組成一個Pool,一個Pool中所有Block大小一樣;一個Pool大小通常為4K(一個虛擬/系統(tǒng)內(nèi)存頁的大?。?/p>
一個小對象被銷毀后,其內(nèi)存不會馬上歸還系統(tǒng),而是在Pool中被管理著,用于分配給后面申請的內(nèi)存對象。Pool的三種狀態(tài)
used狀態(tài):Pool中至少有一個Block已被使用,且至少還有一個Block未被使用,存在usedpools數(shù)組中。
full狀態(tài):Pool中所有的block都已經(jīng)被使用,這種狀態(tài)的Pool在Arena中,但不再Arena的freepools鏈表中
empty狀態(tài):Pool中所有的Block都未被使用,處于這個狀態(tài)的Pool的集合通過其pool_head中的nextpool構(gòu)成一個鏈表,表頭為arena_object中的freepools
// obmalloc.c
#define SYSTEM_PAGE_SIZE (4 * 1024)
#define SYSTEM_PAGE_SIZE_MASK (SYSTEM_PAGE_SIZE - 1)
// 一個pool大小
#define POOL_SIZE SYSTEM_PAGE_SIZE
#define POOL_SIZE_MASK SYSTEM_PAGE_SIZE_MASK
/*pool for small blocks*/
struct pool_header {
union {
block *_padding;
uint count; }ref; // 分配的block數(shù)量
block *freeblock; // 指向pool中可用block的單向鏈表
struct pool_header *nextpool; // 指向下一個
struct pool_header *prevpool; // 指向上一個
uint arenaindex;
// 記錄pool保存的block的大小,一個pool中所有block都是szidx大小
// 和size class index聯(lián)系在一起
uint szidx;
uint nextoffset;
uint maxnextoffset;
};
typedef struct pool_header *poolp;
擁有相同block大小的pool通過雙向鏈表連接起來,python使用一個數(shù)組usedpools來管理使用中的pool
Userpools結(jié)構(gòu)
以下為Python內(nèi)存分配部分代碼:
// obmalloc.c
typedef uchar block;
void *
PyObject_Malloc(sizes_t nbytes)
{
block *bp; // 指向從pool中取出第一塊block的指針
poolp pool; // 指向一塊4kb內(nèi)存
poolp next;
uint size;
// 小于SMALL_REQUEST_THRESHOLD 使用Python的小塊內(nèi)存的內(nèi)存池,否則走malloc
if ((nbytes - 1) 《 SMALL_REQUEST_THRESHOLD) {
// 根據(jù)申請內(nèi)存的大小獲得對應(yīng)的獲得size class index, 從usedpools中取pool
size = (uint)(nbytes - 1) 》》 ALIGNMENT_SHIFT;
pool = usedpools[size + size];
// 如果usedpools中有可用pool, 使用這個pool來分配block$
if (pool != pool-》nextpool) {
。..
}
}
}
Arena
Arena是Python直接從操作系統(tǒng)分配和申請內(nèi)存的單位,一個Arena為256KB,每個Arena包含64個Pool,Arena管理的內(nèi)存是離散的,Pool管理的內(nèi)存是連續(xù)的。同Pool,Arena也是一個雙向鏈表結(jié)構(gòu)。
Arena結(jié)構(gòu)
Python在分配Pool的時候優(yōu)先選擇可用Pool數(shù)量少的Arena進行內(nèi)存分配,這樣做的目的是為了讓Pool更為集中,避免Arena占用大量空閑內(nèi)存空間,因為Python只有在Arena中所有的Pool全為空時才會釋放Arena中的內(nèi)存。
Python中會同時存在多個Arena,由Arenas數(shù)組統(tǒng)一管理。
// obmalloc.c
#define ARENA_SIZE (256 《《 10) // 256kb
// arena包含arena_object及其管理的pool集合,就如同pool和pool_header一樣
struct arena_object {
uintptr_t address; // arena地址
block* pool_address; // 下一個pool地址
uint nfreepools;
uint ntotalpools;
struct pool_header* freepools; // 可用pool通過單鏈表連接
struct arena_object* nextarena;
struct arena_object* prearena;
};
// arenas管理著arena_object的集合
static struct arena_object* arenas = NULL;
// 未使用的arena_object鏈表
static struct arena_object * unused_arena_objects = NULL;
// 可用的arena_object鏈表
static struct arena_object * usable_arenas = NULL;
static struct arena_object * nwe_arena(void)
{
struct arena_object * arenaobj;
uint excess;
// 判斷是否需要擴充“未使用的”arena_object列表
if (unused_arena_objects == NULL) {
// 確定本次需要申請的arena_object的個數(shù),并申請內(nèi)存
numarenas = maxarenas ? maxarenas 《《 1 : INITIAL_ARENA_OBJECTS;
。..
}
// 從unused_arena_objects中取出一個未使用的arena_object
arenaobj = unused_arena_objects;
unused_arena_objects = arenaobj-》nextarena;
// 建立arena_object和pool的聯(lián)系
arenaobj-》address = (uptr)address;
。..
return arenaobj;
}
內(nèi)存池全景圖
內(nèi)存池全景圖
四。 Nginx內(nèi)存管理機制
在介紹Nginx內(nèi)存管理之前,先參照Nginx實現(xiàn)一個簡單的內(nèi)存池,結(jié)構(gòu)圖如下:
其中,mp_pool_s為內(nèi)存池的結(jié)構(gòu)體頭,包含內(nèi)存池的一些全局信息,block為小塊內(nèi)存塊,每一個block有一個mp_node_s結(jié)構(gòu)體,也即mp_pool_s通過鏈表將所有的block連接起來進行管理,而大塊內(nèi)存由mp_large_s進行分配。申明的數(shù)據(jù)結(jié)構(gòu)如下:
// 結(jié)構(gòu)體
// 大塊內(nèi)存結(jié)構(gòu)體
struct mp_large_s {
struct mp_large_s *next;
void *alloc;
};
// 小塊內(nèi)存節(jié)點,小塊內(nèi)存構(gòu)成一個鏈表
struct mp_node_s {
unsigned char *last; // 下一次內(nèi)存從此分配
unsigned char *end; // 內(nèi)存池結(jié)束位置
struct mp_node_s *next; // 指向下一個內(nèi)存塊
size_t failed; // 改內(nèi)存塊/node分配失敗的次數(shù)
};
// 內(nèi)存池結(jié)構(gòu)
struct mp_pool_s {
size_t max; // 能直接從內(nèi)存池中申請的最大內(nèi)存,超過需要走大塊內(nèi)存申請邏輯
struct mp_node_s *current; // 當(dāng)前分配的node
struct mp_large_s *large; // 大塊內(nèi)存結(jié)構(gòu)體
struct mp_node_s head[0]; // 柔性數(shù)組不占用大小,其地址為緊挨著結(jié)構(gòu)體的第一個node
};
// 需要實現(xiàn)的接口
struct mp_pool_s *mp_create_pool(size_t size); // 創(chuàng)建內(nèi)存池
void mp_destory_pool(struct mp_pool_s *pool); // 銷毀內(nèi)存池
void *mp_alloc(struct mp_pool_s *pool, size_t size); // 分配內(nèi)存 對齊
void mp_free(struct mp_pool_s *pool, void *p); // 釋放p節(jié)點內(nèi)存
接下來介紹接口實現(xiàn),先介紹一個接口函數(shù)posix_memalign,函數(shù)原型如下:
int posix_memalign(void**memptr, size_t alignment, size_t size);
/* memptr: 分配好的內(nèi)存空間的首地址
alignment: 對齊邊界,Linux中32位系統(tǒng)8字節(jié),64位系統(tǒng)16字節(jié),必須為2的冪
size: 指定分配size字節(jié)大小的內(nèi)存
*/
其功能類似malloc,不過其申請的內(nèi)存都是對齊的。
內(nèi)存池相關(guān)接口實現(xiàn)如下(只貼出部分代碼,完整代碼私信我)
// 創(chuàng)建并初始化內(nèi)存池
struct mp_pool_s *mp_create_pool(size_t size) {
struct mp_pool_s *p;
// 分配內(nèi)存池內(nèi)存:mp_pool_s + mp_node_s + size
int ret = posix_memalign((void**)&p), MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
if (ret) { return NULL; }
// 可從內(nèi)存池申請的最大內(nèi)存
p-》max = (size 《 MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
p-》current = p-》head; // 當(dāng)前可分配的第一個節(jié)點mp_node_s
//一些初始化工作
return p;
}
// 銷毀內(nèi)存池
void mp_destroy_pool(struct mp_pool_s *pool) {
struct mp_node_s *h, *n;
struct mp_large_s *l;
// 銷毀大塊內(nèi)存
for (l = pool-》large; l; l = l-》next) { /*.。.*/}
// 銷毀小塊內(nèi)存
h = pool-》head-》next;
while (h) {/*.。.*/}
free(pool);
}
// mp_alloc 分配內(nèi)存
void *mp_alloc(struct mp_pool_s *pool, size_t size) {
if (size 《= pool-》max) { // 小塊內(nèi)存分配
p = pool-》current;
do {
/*.。.不斷尋找下一個可用節(jié)點*/
p = p-》next; // 不夠則找下一個節(jié)點
} while (p);
// 內(nèi)存池中所有節(jié)點內(nèi)存都不以滿足分配size內(nèi)存,需要再次分配一個block
return mp_alloc_block(pool, size);
}
return mp_alloc_large(pool, size); // 大塊內(nèi)存分配
}
// 大塊節(jié)點內(nèi)存釋放
void mp_free(struct mp_pool_s *pool, void *p) {
struct mp_large_s *l;
for (l = pool-》large; l; l = l-》next) {
if (p == l-》alloc) {
free(l-》alloc);
//。..
}
}
}
有了上面簡化版,接下來看Nginx中內(nèi)存管理就比較清晰的,其原理跟上述內(nèi)存池一致,先上一張圖:
Nginx內(nèi)存池結(jié)構(gòu)
以下為Nginx實現(xiàn),源代碼主要在src/core/ngx_palloc.h/c兩個文件中
// 內(nèi)存塊結(jié)構(gòu)體,每個內(nèi)存塊都有,在最開頭的部分,管理本塊內(nèi)存
typedef struct {
u_char *last; // 可用內(nèi)存的起始位置,小塊內(nèi)存每次都從這里分配
u_char *end; // 可用內(nèi)存的結(jié)束位置
ngx_pool_t *next; // 寫一個內(nèi)存池節(jié)點
ngx_unit_t failed; // 本節(jié)點分配失敗次數(shù),超過4次,認(rèn)為本節(jié)點滿,不參與分配,滿的內(nèi)存塊也不會主動回收
}ngx_pool_data_t;
// 大塊內(nèi)存節(jié)點
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s {
ngx_pool_large_t *next; // 多塊大內(nèi)存串成鏈表,方便回收利用
void *alloc; // 指向malloc分配的大塊內(nèi)存
};
// nginx內(nèi)存池結(jié)構(gòu)體
// 多個節(jié)點串成的單向鏈表,每個節(jié)點分配小塊內(nèi)存
// max,current,大塊內(nèi)存鏈表旨在頭節(jié)點
// 64位系統(tǒng)大小位80字節(jié),結(jié)構(gòu)體沒有保存內(nèi)存塊大小的字段,由d.end - p得到
struct ngx_pool_s {
// 本內(nèi)存節(jié)點信息
ngx_pool_data_t d;
// 下面的字段旨在第一個塊中有意義
size_t max; // 塊最大內(nèi)存
ngx_pool_t *current; // 當(dāng)前使用的內(nèi)存池節(jié)點
ngx_chain_t *chain;
ngx_pool_large_t *large; // 大塊內(nèi)存
ngx_pool_cleanup_t *cleanup; // 清理鏈表頭指針
ngx_log_t *log;
};
// 創(chuàng)建內(nèi)存池
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
// 銷毀內(nèi)存池
// 調(diào)用清理函數(shù)鏈表,檢查大塊內(nèi)存鏈表,直接free,遍歷內(nèi)存池節(jié)點,逐個free
void ngx_destroy_pool(ngx_pool_t *pool);
// 重置內(nèi)存池,釋放內(nèi)存,但不歸還系統(tǒng)
// 之前分配的內(nèi)存塊依舊保留,重置空閑指針位置
void ngx_reset_pool(ngx_pool_t *pool);
// 分配內(nèi)存 8字節(jié)對齊,速度快,少量浪費 》4k則直接malloc分配大塊內(nèi)存
void *ngx_palloc(ngx_pool_t *pool, size_t size);
void *ngx_pnalloc(ngx_pool_t *pool, size_t size); // 不對齊
void *ngx_pcalloc(ngx_pool_t *pool, size_t size); // 對齊分配,且初始化
// 大塊內(nèi)存free
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
五。 Ringbuffer環(huán)形緩沖區(qū)機制Ringbuffer的兩個特性:1)先進先出;2)緩沖區(qū)用完,會回卷,丟棄久遠數(shù)據(jù),保存新數(shù)據(jù)。其結(jié)構(gòu)如下圖:
Ringbuffer結(jié)構(gòu)
Ringbuffer的好處:1)減少內(nèi)存分配進而減少系統(tǒng)調(diào)用開銷;2)減少內(nèi)存碎片,利于程序長期穩(wěn)定運行。
應(yīng)用場景:服務(wù)端程序收到多個客戶端網(wǎng)絡(luò)數(shù)據(jù)流時,可先暫存在Ringbuffer,等收到一個完整數(shù)據(jù)包時再讀取。
Linux 5.1合入了一個新的異步IO框架和實現(xiàn):io_uring, io_uring設(shè)計了一對共享的RingBuffer用于應(yīng)用和內(nèi)核之間的通信,其中,針對提交隊列(SQ),應(yīng)用是IO提交的生產(chǎn)者(producer),內(nèi)核是消費者(consumer);反過來,針對完成隊列(CQ),內(nèi)核是完成事件的生產(chǎn)者,應(yīng)用是消費者。
以下為一份簡單Ringbuffer實現(xiàn):
// ringbuffer.c
#define BUFFER_SIZE 16 // 緩沖區(qū)的長度
static u32 validLen; // 已使用的數(shù)據(jù)長度
static u8* pHead = NULL; // 環(huán)形存儲區(qū)的首地址
static u8* pTail = NULL; // 環(huán)形存儲區(qū)的尾地址
static u8* pValid = NULL; // 已使用的緩沖區(qū)首地址
static u8* pValidTail = NULL; // 已使用的緩沖區(qū)尾地址
// 初始化環(huán)形緩沖區(qū)
void init Ringbuffer(void) {
if (pHead == NULL) pHead = (u8*)malloc(BUFFER_SIZE);
pValid = pValidTail = pHead;
pTail = pHead + BUFFER_SIZE;
validLen = 0;
}
// 向緩沖區(qū)寫入數(shù)據(jù),buffer寫入數(shù)據(jù)指針,addLen寫入數(shù)據(jù)長度
int writeRingbuffer(u8* buffer, u32 addLen) {
// 將數(shù)據(jù)copy到pValidTail處
if (pValidTail + addLen 》 pTail) // ringbuffer回卷
{
int len1 = addLen - pValidTail;
int len2 = addLen - len1;
memcpy(pValidTail, buffer, len1);
memcpy(pHead, buffer + len1, len2);
pValidTail = pHead + len2; // 新的有效數(shù)據(jù)區(qū)結(jié)尾指針
} else {
memcpy(pValidTail, buffer, addLen);
pValidTail += addLen; // 新的有效數(shù)據(jù)結(jié)尾指針
}
// 重新計算已使用區(qū)的起始位置
if (validLen + addLen 》 BUFFER_SIZE) {
int moveLen = validLen + addLen - BUFFER_SIZE; // 有效指針將要移動的長度
if (pValid + moveLen 》 pTail) {
int len1 = pTail - pValid;
int len2 = moveLen - len1;
pValid = pHead + len2;
} else {
pValid = pValid + moveLen;
}
validLen = BUFFER_SIZE;
}else {
validLen += addLen;
}
return 0;
}
// 從緩沖區(qū)內(nèi)取出數(shù)據(jù),buffer讀取數(shù)據(jù)的buffer,len長度
int readRingBuffer(u8* buffer, u32 len)
{
if (len 》 validLen) len = validLen;
if (pValid + len 》 pTail) { // 回卷
int len1 = pTail - pValid;
int len2 = len - len1;
memcpy(buffer, pValid, len1);
memcpy(buffer + len1, pHead, len2);
pValid = pHead + len2;
} else {
memcpy(buffer, pValid, len);
pValid = pValid + len;
}
validLen -= len;
return len;
}
六。 TCMalloc(Thread-Caching Malloc)
內(nèi)存分配器以下Tcmalloc和Andriod內(nèi)存管理這兩部分只做簡單介紹。
tcmalloc優(yōu)點:內(nèi)存分配效率高,運行速度快,穩(wěn)定性強,能夠有效降低系統(tǒng)負載;
應(yīng)用場景:多核,高并發(fā),多線程
tcmalloc內(nèi)存申請流程:
ThreadCache對象不夠,就從CentralCache中批量申請
CentralCache不夠,從PageHeap申請Span
PageHeap沒有適合的Page,則向操作系統(tǒng)申請
tcmalloc釋放流程:
ThreadCache釋放對象積累到一定程度,就釋放給CentralCache
CentralCache中一個Span釋放完全了,則把這個Span歸還給PageHeap
PageHeap發(fā)現(xiàn)一批連續(xù)的Page都釋放了,則歸還給操作系統(tǒng)
多個連續(xù)的Page組成Span, Span 中記錄起始 Page 的編號,以及 Page 數(shù)量,大對象(》32k)直接分配Span,小對象(《=32k)在Span中分配Object。以下為上述結(jié)構(gòu)圖解:
ThreadCache
?。跜entralCache](/Users/zhongrunkang/Library/Application Support/typora-user-images/image-20210228203604225.png)
PageHeap
七。 Andriod內(nèi)存管理機制
Q:Andriod的Java程序為什么容易出現(xiàn)OOM?
A:因為Andriod系統(tǒng)堆Dalvik的VM HeapSize做了硬性限制,當(dāng)java進程申請的java空間超過閾值時,就會拋出OOM,這樣設(shè)計的目的是為了讓比較多的進程常駐內(nèi)存,這樣程序啟動時就不用每次都重新加載到內(nèi)存,能夠給用戶更快的響應(yīng)。
Andriod系統(tǒng)中的應(yīng)用程序基本都是Java進程。
Andriod內(nèi)存管理機制
分配機制:
為每一個進程分配一個合理大小的內(nèi)存塊,保證每個進程能夠正常運行,同時確保進程不會占用太多的內(nèi)存;Andriod系統(tǒng)需要最大限度的讓更多進程存活在內(nèi)存中,以保證用戶再次打開應(yīng)用時減少應(yīng)用的啟動時間,提高用戶體驗。
回收機制:
當(dāng)系統(tǒng)內(nèi)存不足時,需要一個合理的回收再分配機制,以保證新的進程可以正常運行?;厥諘r殺死那些正在占用內(nèi)存的進程,OS需要提供一個合理的殺死進程機制。
編輯:lyn
-
Linux
+關(guān)注
關(guān)注
87文章
11304瀏覽量
209518 -
內(nèi)存管理
+關(guān)注
關(guān)注
0文章
168瀏覽量
14141 -
python
+關(guān)注
關(guān)注
56文章
4797瀏覽量
84690 -
nginx
+關(guān)注
關(guān)注
0文章
149瀏覽量
12176
原文標(biāo)題:一文淺析內(nèi)存管理機制
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論