Linux頁框分配器之內(nèi)存碎片化整理
本文轉(zhuǎn)載自微信公眾號「人人都是極客」,作者布道師Peter 。轉(zhuǎn)載本文請聯(lián)系人人都是極客公眾號。
頁框分配器在慢速分配中包括內(nèi)存碎片化整理和內(nèi)存回收,代碼如下:
- static inline struct page *
- __alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
- struct alloc_context *ac)
- {
- page = __alloc_pages_direct_compact(gfp_mask, order,
- alloc_flags, ac,
- INIT_COMPACT_PRIORITY,
- &compact_result);
- ......
- page = __alloc_pages_direct_reclaim(gfp_mask, order, alloc_flags, ac,
- &did_some_progress);
- ......
- }
出于篇幅設計,這次我們只講內(nèi)存的碎片化整理,下文再講內(nèi)存回收。
什么是內(nèi)存碎片化
Linux物理內(nèi)存碎片化包括兩種:內(nèi)部碎片化和外部碎片化。
內(nèi)部碎片化:
指分配給用戶的內(nèi)存空間中未被使用的部分。例如進程需要使用3K bytes物理內(nèi)存,于是向系統(tǒng)申請了大小等于3Kbytes的內(nèi)存,但是由于Linux內(nèi)核伙伴系統(tǒng)算法最小顆粒是4K bytes,所以分配的是4Kbytes內(nèi)存,那么其中1K bytes未被使用的內(nèi)存就是內(nèi)存內(nèi)碎片。
外部碎片化:
指系統(tǒng)中無法利用的小內(nèi)存塊。例如系統(tǒng)剩余內(nèi)存為16K bytes,但是這16K bytes內(nèi)存是由4個4K bytes的頁面組成,即16K內(nèi)存物理頁幀號#1不連續(xù)。在系統(tǒng)剩余16K bytes內(nèi)存的情況下,系統(tǒng)卻無法成功分配大于4K的連續(xù)物理內(nèi)存,該情況就是內(nèi)存外碎片導致。
碎片化整理算法
Linux內(nèi)存對碎片化的整理算法主要應用了內(nèi)核的頁面遷移機制,是一種將可移動頁面進行遷移后騰出連續(xù)物理內(nèi)存的方法。
假設存在一個非常小的內(nèi)存域如下:
藍色表示空閑的頁面,白色表示已經(jīng)被分配的頁面,可以看到如上內(nèi)存域的空閑頁面(藍色)非常零散,無法分配大于兩頁的連續(xù)物理內(nèi)存。
下面演示一下內(nèi)存規(guī)整的簡化工作原理,內(nèi)核會運行兩個獨立的掃描動作:第一個掃描從內(nèi)存域的底部開始,一邊掃描一邊將已分配的可移動(MOVABLE)頁面記錄到一個列表中:
另外第二掃描是從內(nèi)存域的頂部開始,掃描可以作為頁面遷移目標的空閑頁面位置,然后也記錄到一個列表里面:
等兩個掃描在域中間相遇,意味著掃描結(jié)束,然后將左邊掃描得到的已分配的頁面遷移到右邊空閑的頁面中,左邊就形成了一段連續(xù)的物理內(nèi)存,完成頁面規(guī)整。
碎片化整理的三種方式
- static struct page *
- __alloc_pages_direct_compact(gfp_t gfp_mask, unsigned int order,
- unsigned int alloc_flags, const struct alloc_context *ac,
- enum compact_priority prio, enum compact_result *compact_result)
- {
- struct page *page;
- unsigned int noreclaim_flag;
- if (!order)
- return NULL;
- noreclaim_flag = memalloc_noreclaim_save();
- *compact_result = try_to_compact_pages(gfp_mask, order, alloc_flags, ac,
- prio);
- memalloc_noreclaim_restore(noreclaim_flag);
- if (*compact_result <= COMPACT_INACTIVE)
- return NULL;
- count_vm_event(COMPACTSTALL);
- page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);
- if (page) {
- struct zone *zone = page_zone(page);
- zone->compact_blockskip_flush = false;
- compaction_defer_reset(zone, order, true);
- count_vm_event(COMPACTSUCCESS);
- return page;
- }
- count_vm_event(COMPACTFAIL);
- cond_resched();
- return NULL;
- }
這也是上面memory compaction算法的代碼實現(xiàn)。
在linux內(nèi)核里一共有3種方式可以碎片化整理,我們總結(jié)如下:
這里就不展開源碼的解析了,有了宏觀的理解然后再去網(wǎng)上搜下具體實現(xiàn)細節(jié)相信不是什么難事,OK,我們進入下面的文章內(nèi)容:內(nèi)存回收(memory reclaim)。