Linux內(nèi)核淺析-物理內(nèi)存管理

前篇文章(https://zhuanlan.zhihu.com/p/81850840/)講了進程地址空間的分配僵控,那本文會繼續(xù)講物理內(nèi)存如何管理燥爷,何時分配等問題。'

物理內(nèi)存meta管理

涉及到物理內(nèi)存的分配犁功、釋放,必然有一個數(shù)據(jù)結(jié)構(gòu)對其進行管理枷踏,同時也要考慮到性能驳规、效率揪胃、安全性等方面的問題斥赋。

前文講了缰猴,linux管理內(nèi)存大小的粒度是4K,我們叫做page疤剑。在單cpu的機器上滑绒,一般采用平臺內(nèi)存模型(flat memroy model)胰舆,就是將page平鋪開供cpu使用,但是在多cpu的情況下蹬挤,就會出現(xiàn)對page的相互競爭、搶占的情況棘幸,導(dǎo)致效率低焰扳,所以本文講NUMA(non-uniform memory access)架構(gòu)下,非一致性內(nèi)存訪問误续。


pglist_data:numa中一個cpu對應(yīng)指定的內(nèi)存節(jié)點吨悍,重要的字段如下:

mem_map數(shù)組:包含了該node上所有的struct page,包括已分配蹋嵌、未分配的育瓜,也用于物理頁號映射。

node_zones數(shù)組:存儲該節(jié)點所有的zone栽烂。

node_zonelist數(shù)組:存儲其他節(jié)點的zone躏仇,numa架構(gòu)下,當(dāng)前cpu的pglist_data -> mem_map的page用完后腺办,可以通過該數(shù)組申請其他cpu的內(nèi)存焰手。

node_zone:存儲該節(jié)點的區(qū)域,用區(qū)域來區(qū)分不同的物理內(nèi)存怀喉。

三種類型的zone:

ZONE_DMA:DMA方式可以操作的內(nèi)存區(qū)域书妻;

ZONE_NORMAL:普通的映射區(qū);

ZONE_HIGHMEM:高端內(nèi)存映射的區(qū)域躬拢。

free_area:空閑page的數(shù)組 + 鏈表的結(jié)構(gòu)躲履,伙伴系統(tǒng)依靠該數(shù)據(jù)結(jié)構(gòu)來分配物理內(nèi)存。每個數(shù)組的item都是一個鏈表聊闯,鏈表中的每個item就是待分配的單元工猜,其大小為2的n次方個頁,n為數(shù)組下標菱蔬。MAX_ORDER = 11域慷,一次可以分配2的11次方個頁,即M內(nèi)存汗销。

page:用于表示一個4k頁面的meta信息犹褒,如果內(nèi)存為4G,則有1M個page弛针,如果page占用空間太大叠骑,則用戶實際可使用的內(nèi)存就會變少,而page的使用方式有多種削茁,需要meta記錄下來宙枷,所以內(nèi)部大量使用union掉房,page目前實際大小為32byte,就是4G的內(nèi)存慰丛,需要使用32M來保存meta信息卓囚。以下是page使用方式:

整頁模式:分配內(nèi)存的單位就是頁,包括匿名映射和文件映射诅病,其重要的字段如下:

struct address_space *mapping:指向映射到該page內(nèi)容的源地址空間哪亿,可能是文件、可能是進程地址空間贤笆。

mapping == 0蝇棉,說明是swap的頁面,其指向swapper_space的地址芥永。

如果mapping != 0篡殷,第0位bit[0] = 0,說明該page屬文件映射埋涧,mapping指向文件的地址空間address_space板辽,此時pgoff_t index則是address_space指向的radix tree的頁號。(可參見文件系統(tǒng):https://zhuanlan.zhihu.com/p/61123802

如果mapping != 0棘催,第0位bit[0] != 0戳气,說明該page為匿名映射,mapping指向vm_area_struct -> anon_vma對象巧鸭。

_mapcount:被頁表引用的次數(shù)瓶您。

lru:如果page還未被分配,則處于伙伴系統(tǒng)纲仍,lru將其連接在相同階的free_area上呀袱。如果已經(jīng)分配,則連接到zone中郑叠,供回收時使用夜赵。

slab模式:類似于對象池,將一頁分配多個slot乡革,每次分配的單位就是對象大小的內(nèi)存寇僧,基本會小于4k,所以一般1個頁可以包含多個對象沸版。

s_mem:指向正在使用的slab的第一個對象嘁傀。

freelist:池子中可分配的空閑對象。

rcu_head:需要釋放對象的列表视粮。

lru:指向slab的管理結(jié)構(gòu)细办。

更詳細struct page的介紹,可以參考《深入理解Linux內(nèi)核》296頁蕾殴。

伙伴系統(tǒng) (Buddy system)

依據(jù)pgdata_list -> zone -> free_area笑撞,分配物理內(nèi)存岛啸,返回struct page鏈表,其算法如下:

1)將分配空間大小歸一化茴肥,2^n-1 < X < 2^n坚踩,此時從free_area[n]開始查找,若有瓤狐,則直接返回瞬铸。

2)若free_area[n]無法找到,則在n ~ MAX_ORDER 之間遍歷芬首,若有,則將內(nèi)存頁一拆為二逼裆,一部分用于分配郁稍,分配有剩余且大于4k,則尋找free_area掛上去胜宇,另一部分掛在上一階的空閑鏈表上耀怜。

3)如果當(dāng)前zone -> free_area不ok,則遍歷node_zonelists中的其他zone桐愉。

如現(xiàn)在需要分配1個頁财破,free_area[0],free_area[1]都為空从诲,從free_area[2]上取出一個單元(order = 2時左痢,分配item = 4 page),在分配需要使用的1個頁后系洛,剩下的3頁中俊性,1個頁掛在free_area[0],2個頁作為一個單元掛在free_area[1]上描扯。

其函數(shù)調(diào)用鏈:alloc_pages(gfp_t gfp_mask, unsigned int order) -> alloc_pages_current -> __alloc_pages_nodemask定页。

gfp_t的枚舉值:GPF_USER、GPF_KERNAL绽诚、GPF_HIGHMEM典徊,分別對應(yīng)ZONE_NORMAL、ZONE_NORMAL恩够、ZONE_HIGHMEM的空間卒落。

頁表

前面講了進程地址空間,本文前面說了物理內(nèi)存的分配蜂桶,那進程地址空間如何和物理內(nèi)存對應(yīng)呢导绷?就是linux的頁表機制。


段地址 + 段偏移 = 邏輯地址屎飘,然后將邏輯地址拆解為頁號(20位) + 頁內(nèi)偏移(12位)妥曲。頁表維護虛擬頁號和物理頁號的映射贾费。

對于32位的系統(tǒng),支持最大物理尋址空間為4g檐盟。1 page為4k褂萧,4g的空間需要1m個page。由于是32位葵萎,每個page需要32位导犹,即4個字節(jié)來索引,所以4g空間需要4m的頁表羡忘。由于邏輯地址是按進程隔離的谎痢,所以進程之間的邏輯地址可能重合,但都映射了不同的物理地址卷雕,所以頁表也需要按進程隔離节猿。如果有機器上有1000個進程,則頁表就會占用1000 * 4m = 4g的頁表空間漫雕,32位系統(tǒng)的內(nèi)存就撐滿了滨嘱。

為節(jié)省內(nèi)存空間,對頁表進行分級: 4g空間需要4m的頁表浸间,那這4m的頁表需要1k個頁表(4k的空間)來描述太雨,具體如下圖:


有人會說新增了一級頁表,那不就從4m -> 4m + 4k了嗎魁蒜?比原來更大了囊扳。但是大部分情況,1個進程是不會用到4g的地址空間的兜看,對于沒有用到的地址空間宪拥,只要1級頁表缺失4byte,2級頁表就可以省下4k铣减。

頁表是有Linux按照x86規(guī)范構(gòu)造的她君,并將一級頁表的指針通過宏__pa()轉(zhuǎn)換為物理地址,并加載到cr3寄存器(這也是x86體系規(guī)范)葫哗,這個過程就是在context_switch -> switch_mm中發(fā)生的缔刹。

邏輯地址 -> 物理地址的具體轉(zhuǎn)換過程由硬件完成,比如執(zhí)行mov指令時(見前文開頭https://zhuanlan.zhihu.com/p/81850840/)劣针,其傳入的是邏輯地址校镐,硬件訪問時會轉(zhuǎn)換為物理地址訪問,并取出對應(yīng)的值捺典,這個模塊叫MMU(memory mangerment unit)鸟廓。

缺頁中斷

何時分配的物理內(nèi)存呢?是不是上層只要調(diào)用brk、mmap就分配呢引谜?顯然不是的牍陌,上文講brk、mmap時并沒有將分配物理內(nèi)存员咽。實際的物理內(nèi)存是在進程訪問時毒涧,發(fā)現(xiàn)頁表項為空會觸發(fā)缺頁異常,在缺頁異常處理程序中分配內(nèi)存贝室。缺頁異常的注冊中斷門代碼如下:

set_intr_gate(14,&page_fault);

其調(diào)用鏈路是:do_page_fault -> handle_mm_fault -> handle_pte_fault契讲,其中handle_mm_fault主要是創(chuàng)建或找到頁表項pte,handle_pte_fault是完成物理頁的分配滑频,并將物理頁號記錄到頁表項pte中捡偏。主要代碼如下:

do_page_fault(struct pt_regs *regs, unsigned long error_code)

{

unsigned long address = read_cr2();? // 缺頁中斷發(fā)生的線性地址通過cr2寄存器傳遞

......

__do_page_fault(regs, error_code, address);

......

}

/*

* This routine handles page faults.? It determines the address,

* and the problem, and then passes it off to one of the appropriate

* routines.

*/

static noinline void

__do_page_fault(struct pt_regs *regs, unsigned long error_code,

unsigned long address)

{

? ? ? ? // 判斷是否在內(nèi)核態(tài),如果是則調(diào)用內(nèi)核的分配函數(shù)

if (unlikely(fault_in_kernel_space(address))) {

if (vmalloc_fault(address) >= 0)

return;

}

......? // 找到缺頁中斷發(fā)生的線性地址描述符

vma = find_vma(mm, address);

......

fault = handle_mm_fault(vma, address, flags);

......

/*

* 根據(jù)線性地址完成頁表的查詢或分配峡迷,此處代碼是支持64位os的银伟,所以是4層頁表,比

* 前面分析32位的頁表多2層

*/

static int __handle_mm_fault(struct vm_area_struct *vma, unsigned long address,

unsigned int flags)

{

struct vm_fault vmf = {

.vma = vma,

.address = address & PAGE_MASK,

.flags = flags,

.pgoff = linear_page_index(vma, address),

.gfp_mask = __get_fault_gfp_mask(vma),

};

struct mm_struct *mm = vma->vm_mm;

pgd_t *pgd;

p4d_t *p4d;

int ret;

? ? ? ? // 尋找或分配頁表項

? ? ? ? // 全局頁表

pgd = pgd_offset(mm, address);

p4d = p4d_alloc(mm, pgd, address);

......? // 上層頁表

vmf.pud = pud_alloc(mm, p4d, address);

......? // 中間層頁表

vmf.pmd = pmd_alloc(mm, vmf.pud, address);

......

return handle_pte_fault(&vmf);

}

handle_pte_fault會完成真是物理頁的分配和pte頁表項的填充凉当。

1)如果vmf -> pte為null枣申,說明沒有分配售葡,如果是匿名映射看杭,直接調(diào)用do_anonymous_page分配。如果是文件映射挟伙,則調(diào)用do_fault進行分配楼雹,此處涉及到vfs(虛擬文件系統(tǒng)的操作,請參考https://zhuanlan.zhihu.com/p/61123802

2)如果vmf -> pte存在且不在內(nèi)存中尖阔,說明是swap到硬盤了贮缅,通過do_swap_page swap in就ok了。

static int handle_pte_fault(struct vm_fault *vmf)

{

pte_t entry;

......

vmf->pte = pte_offset_map(vmf->pmd, vmf->address);

vmf->orig_pte = *vmf->pte;

......

if (!vmf->pte) {

if (vma_is_anonymous(vmf->vma))

return do_anonymous_page(vmf);

else

return do_fault(vmf);

}

if (!pte_present(vmf->orig_pte))

return do_swap_page(vmf);

......

}

do_anonymous_page:

1)pte_alloc:分配頁表項介却。

2)alloc_zeroed_user_highpage_movable:分配一個頁谴供,此處最終調(diào)用到伙伴系統(tǒng)的__alloc_pages_nodemask,然后返回一個struct page齿坷。

3)mk_pte:struct page轉(zhuǎn)換為物理頁號桂肌,并保存在pte頁表項中,這是映射物理內(nèi)存的關(guān)鍵永淌,

static int do_anonymous_page(struct vm_fault *vmf)

{

struct vm_area_struct *vma = vmf->vma;

struct mem_cgroup *memcg;

struct page *page;

int ret = 0;

pte_t entry;

......

if (pte_alloc(vma->vm_mm, vmf->pmd, vmf->address))

return VM_FAULT_OOM;

......

page = alloc_zeroed_user_highpage_movable(vma, vmf->address);

......

entry = mk_pte(page, vma->vm_page_prot);

if (vma->vm_flags & VM_WRITE)

entry = pte_mkwrite(pte_mkdirty(entry));

vmf->pte = pte_offset_map_lock(vma->vm_mm, vmf->pmd, vmf->address,

&vmf->ptl);

......

set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);

......

}

#define mk_pte(page, pgprot)? pfn_pte(page_to_pfn(page), (pgprot))

#define page_to_pfn(page) ((unsigned long) (page - vmem_map))

static inline pte_t pfn_pte(unsigned long page_nr, pgprot_t pgprot)

{

phys_addr_t pfn = (phys_addr_t)page_nr << PAGE_SHIFT;

pfn ^= protnone_mask(pgprot_val(pgprot));

pfn &= PTE_PFN_MASK;

return __pte(pfn | check_pgprot(pgprot));

}

page_to_pfn:獲取物理頁號的函數(shù)崎场。page實例是前面伙伴系統(tǒng)分配的,同時該page實例存儲在pglist_data -> mem_map中遂蛀,page - vmem_map谭跨,結(jié)構(gòu)體直接想減,得到的是兩個地址之間可以有多少個減數(shù)大小的對象,此處就表示該page是mem_map中的index螃宙,這就是物理頁號蛮瞄。pte結(jié)構(gòu)就是一個long,將該物理頁號和一些控制信息按x86要求記錄就好污呼。

很多人這里可能會困惑裕坊,物理頁號是啥?和硬件相關(guān)嗎燕酷?其實硬件沒有物理頁號這個概念籍凝。只是這個struct page就占用了這個物理頁號,其他的page不能使用苗缩。當(dāng)訪問物理內(nèi)存時饵蒂,真實的物理地址 = 物理頁號 * 4k。而釋放內(nèi)存酱讶,只需要釋放這個pte和page即可退盯,下次再次分配該page時,將物理地址上的內(nèi)存空間覆蓋就好泻肯。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渊迁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子灶挟,更是在濱河造成了極大的恐慌琉朽,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稚铣,死亡現(xiàn)場離奇詭異箱叁,居然都是意外死亡,警方通過查閱死者的電腦和手機惕医,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門耕漱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抬伺,你說我怎么就攤上這事螟够。” “怎么了峡钓?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵妓笙,是天一觀的道長。 經(jīng)常有香客問我椒楣,道長给郊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任捧灰,我火速辦了婚禮淆九,結(jié)果婚禮上统锤,老公的妹妹穿的比我還像新娘。我一直安慰自己炭庙,他們只是感情好饲窿,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著焕蹄,像睡著了一般逾雄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腻脏,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天鸦泳,我揣著相機與錄音,去河邊找鬼永品。 笑死做鹰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鼎姐。 我是一名探鬼主播钾麸,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼炕桨!你這毒婦竟也來了饭尝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤献宫,失蹤者是張志新(化名)和其女友劉穎钥平,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遵蚜,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡帖池,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年奈惑,在試婚紗的時候發(fā)現(xiàn)自己被綠了吭净。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡肴甸,死狀恐怖寂殉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情原在,我是刑警寧澤友扰,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站庶柿,受9級特大地震影響村怪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浮庐,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一甚负、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧货抄,春花似錦稚疹、人聲如沸侣滩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽居夹。三九已至嚎京,卻和暖如春诚隙,著一層夾襖步出監(jiān)牢的瞬間既穆,已是汗流浹背赎懦。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留幻工,地道東北人铲敛。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像会钝,于是被迫代替她去往敵國和親伐蒋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容