頁(yè)表的一些術(shù)語(yǔ)
現(xiàn)在Linux內(nèi)核中支持四級(jí)頁(yè)表的映射,我們先看下內(nèi)核中關(guān)于頁(yè)表的一些術(shù)語(yǔ):
全局目錄項(xiàng),PGD(Page Global Directory)
上級(jí)目錄項(xiàng),PUD(Page Upper Directory)
中間目錄項(xiàng),PMD(Page Middle Directory)
頁(yè)表項(xiàng),(Page Table)
大家在看內(nèi)核代碼時(shí)會(huì)經(jīng)常看的以上術(shù)語(yǔ),但在ARM的芯片手冊(cè)中并沒(méi)有用到這些術(shù)語(yǔ),而是使用L1,L2,L3頁(yè)表這種術(shù)語(yǔ)。
ARM32 虛擬地址到物理地址的轉(zhuǎn)換
虛擬地址的32個(gè)bit位可以分為3個(gè)域,最高12bit位20~31位稱(chēng)為L(zhǎng)1索引,叫做PGD,頁(yè)面目錄。中間的8個(gè)bit位叫做L2索引,在Linux內(nèi)核中叫做PT,頁(yè)表。最低的12位叫做頁(yè)索引。
在ARM處理器中,TTBRx寄存器存放著頁(yè)表基地址,我們這里的一級(jí)頁(yè)表有4096個(gè)頁(yè)表項(xiàng)。每個(gè)表項(xiàng)中存放著二級(jí)表項(xiàng)的基地址。我們可以通過(guò)虛擬地址的L1索引訪問(wèn)一級(jí)頁(yè)表,訪問(wèn)一級(jí)頁(yè)表相當(dāng)于數(shù)組訪問(wèn)。
二級(jí)頁(yè)表通常是動(dòng)態(tài)分配的,可以通過(guò)虛擬地址的中間8bit位L2索引訪問(wèn)二級(jí)頁(yè)表,在L2索引中存放著最終物理地址的高20bit位,然后和虛擬地址的低12bit位就組成了最終的物理地址。以上就是虛擬地址轉(zhuǎn)換為物理地址的過(guò)程。
MMU訪問(wèn)頁(yè)表是硬件實(shí)現(xiàn)的,但頁(yè)表的創(chuàng)建和填充需要Linux內(nèi)核來(lái)填充。通常,一級(jí)頁(yè)表和二級(jí)頁(yè)表存放在主存儲(chǔ)器中。
ARM32 一級(jí)頁(yè)表的頁(yè)表項(xiàng)
下面這張圖來(lái)自ARMV7的手冊(cè)。
一級(jí)頁(yè)表項(xiàng)這里有三種情況:一種是無(wú)效的,第二種是一級(jí)頁(yè)表的表項(xiàng)。第三種是段映射的頁(yè)表項(xiàng)。
bit 0 ~ bit 1:用來(lái)表示這個(gè)頁(yè)表項(xiàng)是一級(jí)頁(yè)表還是段映射的表項(xiàng)。
PXN:PL1 表示是否可以執(zhí)行這段代碼,為0表示可執(zhí)行,1表示不可執(zhí)行。
NS:none-security bit,用于安全擴(kuò)展。
Domain:Domain域,指明所屬的域,Linux中只使用了3個(gè)域。
bit31:bit10:指向二級(jí)頁(yè)表基地址。
二級(jí)頁(yè)表的表項(xiàng)
bit0:禁止執(zhí)行標(biāo)志。1表示禁止執(zhí)行,0表示可執(zhí)行
bit1:區(qū)分是大頁(yè)還是小頁(yè)
C/B bit:內(nèi)存區(qū)域?qū)傩?/p>
TEX[2:0]:內(nèi)存區(qū)域?qū)傩?/p>
AP[0:1] :訪問(wèn)權(quán)限
S:是否可共享
nG:用于TLB
ARM64 頁(yè)表
ARM體系結(jié)構(gòu)從ARMV8-A開(kāi)始就支持64bit位,最大支持48根地址線(xiàn)。那為什么不支持64根地址線(xiàn)呢?主要原因是48根地址線(xiàn)時(shí)已支持最大訪問(wèn)空間為256TB(內(nèi)核空間和用戶(hù)空間分別256TB)滿(mǎn)足了大部分應(yīng)用的需求。而且,64根地址線(xiàn)時(shí),芯片的設(shè)計(jì)復(fù)雜度會(huì)急劇增加。ARMV8-A架構(gòu)中,支持4KB,16KB和64KB的頁(yè),支持3級(jí)或者4級(jí)映射。
下面我們以4KB大小頁(yè)+4級(jí)映射介紹下虛擬地址到物理地址的映射過(guò)程。
0~11 :頁(yè)索引
bit 63 :頁(yè)表基地址選擇位,ARMV8架構(gòu)中有2兩個(gè)頁(yè)表基地址,一個(gè)用于用戶(hù)空間,一個(gè)用戶(hù)內(nèi)核空間。
39~47:L0索引
30~38:L1索引
21~29:L2索引
12~20:L3 索引
假設(shè)頁(yè)表基地址為T(mén)TBRx,訪問(wèn)頁(yè)表基地址就能訪問(wèn)到L0頁(yè)表的基地址,可以使用L0索引的值作為offset去訪問(wèn)L0頁(yè)表。
L0的頁(yè)表項(xiàng)包含了下一級(jí)L1頁(yè)表的基地址,同樣的,可以使用L1索引的值作為offset去訪問(wèn)L2頁(yè)表。以此類(lèi)推。
最后通過(guò)L3的頁(yè)表項(xiàng)可以得到物理地址的bit12 ~ 47位,這個(gè)時(shí)候再將虛擬地址的頁(yè)索引位對(duì)應(yīng)到物理地址的0~11就是完整的物理地址。
Linux內(nèi)核關(guān)于頁(yè)表的函數(shù)
Linux內(nèi)核中頁(yè)表操作的宏定義
Linux內(nèi)核中封裝了很多宏來(lái)處理頁(yè)表
#define?pgd_offset_k(addr)?pgd_offset(&init_mm,addr)?//由虛擬地址來(lái)獲取內(nèi)核頁(yè)表的PGD頁(yè)表的相應(yīng)的頁(yè)表項(xiàng)?
#define?pgd_offset(mm,addr)?((mm)->pgd?+?pgd_index(addr))?//由虛擬地址來(lái)獲取用戶(hù)進(jìn)程的頁(yè)表中相應(yīng)的PGD表項(xiàng)
pgd_index(addr)?//由虛擬地址找到PGD頁(yè)表的索引
pte_index(addr)?//由虛擬地址找到PT頁(yè)表的索引
pte_offset_kernel(pmd,addr)?//查找內(nèi)核頁(yè)表中對(duì)應(yīng)的PT頁(yè)表的表項(xiàng)?
判斷頁(yè)表項(xiàng)的狀態(tài)
#define?pte_none(pte)??(!pte_val(pte))?//pte是否存在
#define?pte_present(pte)?(pte_isset((pte),?L_PTE_PRESENT))?//present比特位
#define?pte_valid(pte)??(pte_isset((pte),?L_PTE_VALID))?//pte是否有效
#define?pte_accessible(mm,?pte)?(mm_tlb_flush_pending(mm)???pte_present(pte)?:?pte_valid(pte))
#define?pte_write(pte)??(pte_isclear((pte),?L_PTE_RDONLY))?//pte是否可寫(xiě)
#define?pte_dirty(pte)??(pte_isset((pte),?L_PTE_DIRTY))?//pte是否有臟數(shù)據(jù)
#define?pte_young(pte)??(pte_isset((pte),?L_PTE_YOUNG))?//
#define?pte_exec(pte)??(pte_isclear((pte),?L_PTE_XN))
修改頁(yè)表
mk_pte()?//創(chuàng)建的相應(yīng)的頁(yè)表項(xiàng)
pte_mkdirty()?//?設(shè)置dirty標(biāo)志位
pte_mkold()?//?清除Accessed標(biāo)志位
pte_mkclean()?//清除dirty標(biāo)志位
pte_mkwrite()//?設(shè)置讀寫(xiě)標(biāo)志位
pte_wrprotect()?//清除讀寫(xiě)標(biāo)志位
pte_mkyoung()//設(shè)置Accessed標(biāo)志位
set_pte_at()//?設(shè)置頁(yè)表項(xiàng)到硬件中
例子1 內(nèi)核頁(yè)表的映射
前面我們介紹了很多關(guān)于內(nèi)核的宏,函數(shù),下面我們通過(guò)實(shí)際的例子學(xué)習(xí)如何使用這些宏
系統(tǒng)初始化時(shí)需要把kernel image區(qū)域和線(xiàn)性映射區(qū)建立頁(yè)表映射,這個(gè)時(shí)候依次調(diào)用start_kernel() --> setup_arch() --> paging_init() --> map_lowmem() --> create_mapping()
去創(chuàng)建內(nèi)核頁(yè)表。我們可以研究下內(nèi)核是如何建立內(nèi)核頁(yè)表的映射。
/*
?*?Create?the?page?directory?entries?and?any?necessary
?*?page?tables?for?the?mapping?specified?by?`md'.??We
?*?are?able?to?cope?here?with?varying?sizes?and?address
?*?offsets,?and?we?take?full?advantage?of?sections?and
?*?supersections.
?*/
static?void?__init?create_mapping(struct?map_desc?*md)
{
?if?(md->virtual?!=?vectors_base()?&&?md->virtual?<?TASK_SIZE)?{
??pr_warn("BUG:?not?creating?mapping?for?0x%08llx?at?0x%08lx?in?user?regionn",
???(long?long)__pfn_to_phys((u64)md->pfn),?md->virtual);
??return;
?}
?if?(md->type?==?MT_DEVICE?&&
?????md->virtual?>=?PAGE_OFFSET?&&?md->virtual?<?FIXADDR_START?&&
?????(md->virtual?<?VMALLOC_START?||?md->virtual?>=?VMALLOC_END))?{
??pr_warn("BUG:?mapping?for?0x%08llx?at?0x%08lx?out?of?vmalloc?spacen",
???(long?long)__pfn_to_phys((u64)md->pfn),?md->virtual);
?}
?__create_mapping(&init_mm,?md,?early_alloc,?false);
}
首先會(huì)檢查映射的虛擬地址是否在內(nèi)核向量表的基址以上,并且小于用戶(hù)空間的TASK_SIZE
。TASK_SIZE
通常被定義為0xC0000000(3GB),表示用戶(hù)空間的虛擬地址范圍從0到3GB。對(duì)于64位體系結(jié)構(gòu),TASK_SIZE
通常被定義為0x00007fffffffffff(128TB)。
接著會(huì)檢查映射的類(lèi)型是否為設(shè)備類(lèi)型,并且虛擬地址在頁(yè)偏移以上且低于FIXADDR_START
,且不在VMALLOC_START
和VMALLOC_END
之間(即不在vmalloc空間中)。
最后會(huì)調(diào)用__create_mapping
函數(shù)創(chuàng)建映射。傳入初始內(nèi)存管理結(jié)構(gòu)體init_mm
、映射描述結(jié)構(gòu)體md
、早期內(nèi)存分配函數(shù)early_alloc
,以及false
標(biāo)志。
/*
?*?Create?a?mapping?for?the?given?map?descriptor,?md.?The?function
?*?__create_mapping?is?used?for?both?kernel?and?user?mode?mappings.
?*
?*?@mm:?????????the?mm?structure?where?the?mapping?will?be?created
?*?@md:????????the?map?descriptor?with?the?details?of?the?mapping
?*?@alloc:??????a?pointer?to?a?function?used?to?allocate?pages?for?the?mapping
?*?@ng:?????????a?boolean?flag?indicating?if?the?mapping?is?non-global
?*/
static?void?__init?__create_mapping(struct?mm_struct?*mm,?struct?map_desc?*md,
????????void?*(*alloc)(unsigned?long?sz),
????????bool?ng)
{
?unsigned?long?addr,?length,?end;
?phys_addr_t?phys;
?const?struct?mem_type?*type;
?pgd_t?*pgd;
?type?=?&mem_types[md->type];
#ifndef?CONFIG_ARM_LPAE----------------------(1)
?/*
??*?Catch?36-bit?addresses
??*/
?if?(md->pfn?>=?0x100000)?{
??create_36bit_mapping(mm,?md,?type,?ng);
??return;
?}
#endif
?addr?=?md->virtual?&?PAGE_MASK;----------------------(2)
?phys?=?__pfn_to_phys(md->pfn);
?length?=?PAGE_ALIGN(md->length?+?(md->virtual?&?~PAGE_MASK));
?/*
??*?Check?if?the?mapping?can?be?made?using?pages.
??*?If?not,?print?a?warning?and?ignore?the?request.
??*/
?if?(type->prot_l1?==?0?&&?((addr?|?phys?|?length)?&?~SECTION_MASK))?{----------------------(3)
??pr_warn("BUG:?map?for?0x%08llx?at?0x%08lx?can?not?be?mapped?using?pages,?ignoring.n",
???(long?long)__pfn_to_phys(md->pfn),?addr);
??return;
?}
?pgd?=?pgd_offset(mm,?addr);
?end?=?addr?+?length;----------------------(4)
?do?{
??unsigned?long?next?=?pgd_addr_end(addr,?end);----------------------(5)
??/*
???*?Allocate?a?page?directory?entry?for?this?range.
???*?Initialize?it?with?the?appropriate?page?table
???*?and?make?the?mapping.
???*/
??alloc_init_p4d(pgd,?addr,?next,?phys,?type,?alloc,?ng);----------------------(6)
??/*
???*?Update?the?phys?value?with?the?end?of?the?last?mapped
???*?page?so?that?the?next?range?can?be?allocated?properly.
???*/
??phys?+=?next?-?addr;
??addr?=?next;----------------------(7)
?}?while?(pgd++,?addr?!=?end);
}
__create_mapping
完成中創(chuàng)建映射的功能,根據(jù)給定的映射描述結(jié)構(gòu)體,將虛擬地址與物理地址進(jìn)行映射。
(1) 系統(tǒng)沒(méi)有啟用ARM LPAE(Large Physical Address Extension),并且物理頁(yè)幀號(hào)大于等于0x100000,調(diào)用create_36bit_mapping
函數(shù)進(jìn)行處理,然后返回。
在早期階段,地址總線(xiàn)也是32位的,即4G的內(nèi)存地址空間。隨著應(yīng)用程序越來(lái)越豐富,占用的內(nèi)存總量很容易就超過(guò)了4G。但由于編程模型和地址總線(xiàn)的限制,是無(wú)法使用超過(guò)4G的物理地址的。所以PAE/LPAE這種大內(nèi)存地址方案應(yīng)運(yùn)而生。
PAE/LAPE方案其它很簡(jiǎn)單,編程視角依然還是32位(4G)的地址空間,這層是虛擬地址空間。而計(jì)算機(jī)地址總線(xiàn)卻使用超過(guò)32位的,比如X86的就使用36位(64G)的地址總線(xiàn),ARM使用的是48位(64G)的地址總線(xiàn)。中間是通過(guò)保護(hù)模式(X86架構(gòu))或者M(jìn)MU機(jī)制(ARM架構(gòu))提供的分頁(yè)技術(shù)(paging)實(shí)現(xiàn)32位虛擬地址訪問(wèn)超過(guò)4G的物理內(nèi)存空間。這項(xiàng)技術(shù)的關(guān)鍵是分頁(yè)技術(shù)中的頁(yè)表項(xiàng)使用超過(guò)4字節(jié)的映射表 (ARM在LPAE模式下,頁(yè)表項(xiàng)是8字節(jié)),因?yàn)槭褂贸^(guò)4字節(jié)映射表,就可以指示超過(guò)4G的內(nèi)存空間。
(2) 獲取虛擬地址的起始地址,因?yàn)榈刂酚成涞淖钚挝皇莗age,因此這里進(jìn)行mapping的虛擬地址需要對(duì)齊到page size,同樣的,長(zhǎng)度也需要對(duì)齊到page size。
(3) 首先檢查映射類(lèi)型的prot_l1
字段是否為0。prot_l1
表示第一級(jí)頁(yè)表(Level 1 Page Table)的保護(hù)位。如果prot_l1
為0,表示無(wú)法使用頁(yè)面進(jìn)行映射。如果地址、物理地址和長(zhǎng)度與SECTION_MASK
存在非零位,表示頁(yè)面映射要求地址和長(zhǎng)度并未按頁(yè)面大小對(duì)齊。
(4)設(shè)置了頁(yè)全局目錄(pgd
)的初始偏移,并將結(jié)束地址(end
)設(shè)置為起始地址(addr
)加上長(zhǎng)度(length
)。
(5)然后,使用pgd_addr_end
函數(shù)計(jì)算下一個(gè)地址(next
),該地址是當(dāng)前地址和結(jié)束地址之間的較小值。
(6)調(diào)用alloc_init_p4d
函數(shù),為當(dāng)前范圍內(nèi)的地址分配一個(gè)頁(yè)目錄項(xiàng),初始化它的頁(yè)表,并進(jìn)行映射。該函數(shù)使用給定的參數(shù)pgd
、addr
、next
、phys
、type
、alloc
和ng
來(lái)執(zhí)行這些操作。
(7)更新phys
的值,使其加上當(dāng)前范圍內(nèi)映射的頁(yè)面數(shù),以便正確分配下一個(gè)范圍的地址。最后,在循環(huán)的末尾,遞增pgd
的值,并檢查是否達(dá)到了結(jié)束地址。如果沒(méi)有達(dá)到,繼續(xù)循環(huán)處理下一個(gè)地址范圍。
例子2 進(jìn)程頁(yè)表的映射
remap_pfn_range
函數(shù)對(duì)于寫(xiě)過(guò)Linux驅(qū)動(dòng)的人都不陌生,很多驅(qū)動(dòng)程序的mmap函數(shù)都會(huì)調(diào)用到該函數(shù),該函數(shù)實(shí)現(xiàn)了物理空間到用戶(hù)進(jìn)程的映射。
比如我們?cè)谟脩?hù)空間讀寫(xiě)SOC的寄存器時(shí),ARM中的寄存器通常都是memory map形式的,在用戶(hù)空間都要讀寫(xiě)ARM空間的寄存器,通常都要操作/dev/mem
設(shè)備來(lái)實(shí)現(xiàn),最后都會(huì)調(diào)用到remap_pfn_range
來(lái)實(shí)現(xiàn)。
VMA:準(zhǔn)備要映射的進(jìn)程地址空間的VMA的數(shù)據(jù)結(jié)構(gòu)
addr:要映射到 用戶(hù)空間的起始地址
pfn:準(zhǔn)備要映射的物理內(nèi)存的頁(yè)幀號(hào)
size:表示要映射的大小
prot:表示要映射的屬性
接下來(lái)我們從頁(yè)表的角度看下函數(shù)的實(shí)現(xiàn)
int?remap_pfn_range(struct?vm_area_struct?*vma,?unsigned?long?addr,
??????unsigned?long?pfn,?unsigned?long?size,?pgprot_t?prot)
{
?pgd_t?*pgd;
?unsigned?long?next;
?unsigned?long?end?=?addr?+?PAGE_ALIGN(size);
?struct?mm_struct?*mm?=?vma->vm_mm;//從VMA獲取當(dāng)前進(jìn)程的mm_struct結(jié)構(gòu)
?unsigned?long?remap_pfn?=?pfn;
?int?err;
?if?(WARN_ON_ONCE(!PAGE_ALIGNED(addr)))
??return?-EINVAL;
?if?(is_cow_mapping(vma->vm_flags))?{
??if?(addr?!=?vma->vm_start?||?end?!=?vma->vm_end)
???return?-EINVAL;
??vma->vm_pgoff?=?pfn;
?}
?err?=?track_pfn_remap(vma,?&prot,?remap_pfn,?addr,?PAGE_ALIGN(size));
?if?(err)
??return?-EINVAL;
?vma->vm_flags?|=?VM_IO?|?VM_PFNMAP?|?VM_DONTEXPAND?|?VM_DONTDUMP;//設(shè)置vm_flags,remap_pfn_range直接使用物理內(nèi)存。Linux內(nèi)核對(duì)物理頁(yè)面分為兩類(lèi):normal?mapping,special?mapping。special?mapping就是內(nèi)核不希望該頁(yè)面參與到內(nèi)核的頁(yè)面回收等活動(dòng)中。
?BUG_ON(addr?>=?end);
?pfn?-=?addr?>>?PAGE_SHIFT;
?pgd?=?pgd_offset(mm,?addr);//找到頁(yè)表項(xiàng)
?flush_cache_range(vma,?addr,?end);
????//以PGD_SIZE為步長(zhǎng)遍歷頁(yè)表
?do?{
??next?=?pgd_addr_end(addr,?end);//獲取下一個(gè)PGD頁(yè)表項(xiàng)的管轄的地址范圍的起始地址
??err?=?remap_p4d_range(mm,?pgd,?addr,?next,
????pfn?+?(addr?>>?PAGE_SHIFT),?prot);//繼續(xù)遍歷下一級(jí)頁(yè)表
??if?(err)
???break;
?}?while?(pgd++,?addr?=?next,?addr?!=?end);
?if?(err)
??untrack_pfn(vma,?remap_pfn,?PAGE_ALIGN(size));
?return?err;
}
遍歷PUD頁(yè)表
static?inline?int?remap_pud_range(struct?mm_struct?*mm,?p4d_t?*p4d,
???unsigned?long?addr,?unsigned?long?end,
???unsigned?long?pfn,?pgprot_t?prot)
{
?pud_t?*pud;
?unsigned?long?next;
?int?err;
?pfn?-=?addr?>>?PAGE_SHIFT;
?pud?=?pud_alloc(mm,?p4d,?addr);//找到pud頁(yè)表項(xiàng)。對(duì)于二級(jí)頁(yè)表來(lái)說(shuō),PUD指向PGD
?if?(!pud)
??return?-ENOMEM;
????//以PUD_SIZE為步長(zhǎng)遍歷頁(yè)表
?do?{
??next?=?pud_addr_end(addr,?end);//獲取下一個(gè)PUD頁(yè)表項(xiàng)的管轄的地址范圍的起始地址
??err?=?remap_pmd_range(mm,?pud,?addr,?next,
????pfn?+?(addr?>>?PAGE_SHIFT),?prot);//繼續(xù)遍歷下一級(jí)頁(yè)表
??if?(err)
???return?err;
?}?while?(pud++,?addr?=?next,?addr?!=?end);
?return?0;
}
Linux內(nèi)核中實(shí)現(xiàn)了4級(jí)頁(yè)表,對(duì)于ARM32來(lái)說(shuō),它是如何跳過(guò)中間兩級(jí)頁(yè)表的呢?大家可以看下以下兩個(gè)宏的實(shí)現(xiàn)
/*?Find?an?entry?in?the?second-level?page?table..?*/
#ifndef?pmd_offset
static?inline?pmd_t?*pmd_offset(pud_t?*pud,?unsigned?long?address)
{
?return?(pmd_t?*)pud_page_vaddr(*pud)?+?pmd_index(address);
}
#define?pmd_offset?pmd_offset
#endif
接收指向頁(yè)上級(jí)目錄項(xiàng)的指針 pud 和線(xiàn)性地址 addr 作為參數(shù)。這個(gè)宏產(chǎn)生目錄項(xiàng) addr 在頁(yè)中間目錄中的偏移地址。在兩級(jí)或三級(jí)分頁(yè)系統(tǒng)中,它產(chǎn)生 pud ,即頁(yè)全局目錄項(xiàng)的地址。
#ifndef?pud_offset
static?inline?pud_t?*pud_offset(p4d_t?*p4d,?unsigned?long?address)
{
?return?(pud_t?*)p4d_page_vaddr(*p4d)?+?pud_index(address);
}
#define?pud_offset?pud_offset
#endif
參數(shù)為指向頁(yè)全局目錄項(xiàng)的指針 pgd 和線(xiàn)性地址 addr 。這個(gè)宏產(chǎn)生頁(yè)上級(jí)目錄中目錄項(xiàng) addr 對(duì)應(yīng)的線(xiàn)性地址。在兩級(jí)或三級(jí)分頁(yè)系統(tǒng)中,該宏產(chǎn)生 pgd ,即一個(gè)頁(yè)全局目錄項(xiàng)的地址。
遍歷PMD頁(yè)表
remap_pmd_range
函數(shù)和remap_pud_range
類(lèi)似。
static?inline?int?ioremap_pmd_range(pud_t?*pud,?unsigned?long?addr,
??unsigned?long?end,?phys_addr_t?phys_addr,?pgprot_t?prot,
??pgtbl_mod_mask?*mask)
{
?pmd_t?*pmd;
?unsigned?long?next;
?pmd?=?pmd_alloc_track(&init_mm,?pud,?addr,?mask);//找到對(duì)應(yīng)的pmd頁(yè)表項(xiàng),對(duì)于二級(jí)頁(yè)表來(lái)說(shuō),pmd指向pud
?if?(!pmd)
??return?-ENOMEM;
????//以PMD_SIZE為步長(zhǎng)遍歷頁(yè)表
?do?{
??next?=?pmd_addr_end(addr,?end);//獲取下一個(gè)PMD頁(yè)表項(xiàng)的管轄的地址范圍的起始地址
??if?(ioremap_try_huge_pmd(pmd,?addr,?next,?phys_addr,?prot))?{
???*mask?|=?PGTBL_PMD_MODIFIED;
???continue;
??}
?//繼續(xù)遍歷下一級(jí)頁(yè)表
??if?(ioremap_pte_range(pmd,?addr,?next,?phys_addr,?prot,?mask))
???return?-ENOMEM;
?}?while?(pmd++,?phys_addr?+=?(next?-?addr),?addr?=?next,?addr?!=?end);
?return?0;
}
遍歷PT頁(yè)表
/*
?*?maps?a?range?of?physical?memory?into?the?requested?pages.?the?old
?*?mappings?are?removed.?any?references?to?nonexistent?pages?results
?*?in?null?mappings?(currently?treated?as?"copy-on-access")
?*/
static?int?remap_pte_range(struct?mm_struct?*mm,?pmd_t?*pmd,
???unsigned?long?addr,?unsigned?long?end,
???unsigned?long?pfn,?pgprot_t?prot)
{
?pte_t?*pte,?*mapped_pte;
?spinlock_t?*ptl;
?int?err?=?0;
?mapped_pte?=?pte?=?pte_alloc_map_lock(mm,?pmd,?addr,?&ptl);//尋找相應(yīng)的pte頁(yè)表項(xiàng)。注意這里需要申請(qǐng)一個(gè)spinlock鎖用來(lái)保護(hù)修改pte頁(yè)表
?if?(!pte)
??return?-ENOMEM;
?arch_enter_lazy_mmu_mode();
????//以PAGE_SIZE為步長(zhǎng)遍歷PT頁(yè)表
?do?{
??BUG_ON(!pte_none(*pte));
??if?(!pfn_modify_allowed(pfn,?prot))?{
???err?=?-EACCES;
???break;
??}
????????/*
????????*pte_none()判斷這個(gè)pte是否存在
????????*pfn_pte()由頁(yè)幀號(hào)pfn得到pte
????????*pte_mkspecial()設(shè)置軟件的PTE_SPECIAL標(biāo)志位(三級(jí)頁(yè)表才會(huì)用該標(biāo)志位)
????????*set_pte_at()?把pte設(shè)置到硬件頁(yè)表中
????????*/
??set_pte_at(mm,?addr,?pte,?pte_mkspecial(pfn_pte(pfn,?prot)));
??pfn++;
?}?while?(pte++,?addr?+=?PAGE_SIZE,?addr?!=?end);
?arch_leave_lazy_mmu_mode();
?pte_unmap_unlock(mapped_pte,?ptl);//PT頁(yè)表設(shè)置完成后,需要把spinlock?釋放
?return?err;
}
缺頁(yè)中斷do_anonymous_page
在缺頁(yè)中斷處理中,匿名頁(yè)面的觸發(fā)條件為下面的兩個(gè)條件,當(dāng)滿(mǎn)足這兩個(gè)條件的時(shí)候就會(huì)調(diào)用do_anonymous_page
函數(shù)來(lái)處理匿名映射缺頁(yè)異常,代碼實(shí)現(xiàn)在mm/memory.c文件中
- 發(fā)生缺頁(yè)的地址所在頁(yè)表項(xiàng)不存在是匿名頁(yè),即是vma->vm_ops為空,即vm_operations函數(shù)指針為空
我們知道在進(jìn)程的
task_struct
結(jié)構(gòu)中包含了一個(gè)mm_struct
結(jié)構(gòu)的指針,mm_struct
用來(lái)描述一個(gè)進(jìn)程的虛擬地址空間。進(jìn)程的mm_struct
則包含裝入的可執(zhí)行映像信息以及進(jìn)程的頁(yè)目錄指針pgd。該結(jié)構(gòu)還包含有指向 ~vm_area_struct ~結(jié)構(gòu)的幾個(gè)指針,每個(gè)vm_area_struct
代表進(jìn)程的一個(gè)虛擬地址區(qū)間。vm_area_struct
結(jié)構(gòu)含有指向vm_operations_struct
結(jié)構(gòu)的一個(gè)指針,vm_operations_struct
描述了在這個(gè)區(qū)間的操作。vm_operations
結(jié)構(gòu)中包含的是函數(shù)指針;其中,open、close 分別用于虛擬區(qū)間的打開(kāi)、關(guān)閉,而nopage 用于當(dāng)虛存頁(yè)面不在物理內(nèi)存而引起的“缺頁(yè)異?!睍r(shí)所應(yīng)該調(diào)用的函數(shù)
/*
?*?We?enter?with?non-exclusive?mmap_lock?(to?exclude?vma?changes,
?*?but?allow?concurrent?faults),?and?pte?mapped?but?not?yet?locked.
?*?We?return?with?mmap_lock?still?held,?but?pte?unmapped?and?unlocked.
?*/
static?vm_fault_t?do_anonymous_page(struct?vm_fault?*vmf)
{
?struct?vm_area_struct?*vma?=?vmf->vma;
?struct?page?*page;
?vm_fault_t?ret?=?0;
?pte_t?entry;
?/*?File?mapping?without?->vm_ops???*/
?if?(vma->vm_flags?&?VM_SHARED)-----------------(1)
??return?VM_FAULT_SIGBUS;
?/*
??*?Use?pte_alloc()?instead?of?pte_alloc_map().??We?can't?run
??*?pte_offset_map()?on?pmds?where?a?huge?pmd?might?be?created
??*?from?a?different?thread.
??*
??*?pte_alloc_map()?is?safe?to?use?under?mmap_write_lock(mm)?or?when
??*?parallel?threads?are?excluded?by?other?means.
??*
??*?Here?we?only?have?mmap_read_lock(mm).
??*/
?if?(pte_alloc(vma->vm_mm,?vmf->pmd))-----------------(2)
??return?VM_FAULT_OOM;
?/*?See?the?comment?in?pte_alloc_one_map()?*/
?if?(unlikely(pmd_trans_unstable(vmf->pmd)))
??return?0;
?/*?Use?the?zero-page?for?reads?*/
?if?(!(vmf->flags?&?FAULT_FLAG_WRITE)?&&
???!mm_forbids_zeropage(vma->vm_mm))?{-----------------(3)
??entry?=?pte_mkspecial(pfn_pte(my_zero_pfn(vmf->address),-----------------(4)
??????vma->vm_page_prot));
??vmf->pte?=?pte_offset_map_lock(vma->vm_mm,?vmf->pmd,-----------------(5)
????vmf->address,?&vmf->ptl);
??if?(!pte_none(*vmf->pte))?{-----------------(6)
???update_mmu_tlb(vma,?vmf->address,?vmf->pte);
???goto?unlock;
??}
??ret?=?check_stable_address_space(vma->vm_mm);-----------------(7)
??if?(ret)
???goto?unlock;
??/*?Deliver?the?page?fault?to?userland,?check?inside?PT?lock?*/
??if?(userfaultfd_missing(vma))?{-----------------(8)
???pte_unmap_unlock(vmf->pte,?vmf->ptl);
???return?handle_userfault(vmf,?VM_UFFD_MISSING);
??}
??goto?setpte;
?}
?/*?Allocate?our?own?private?page.?*/
?if?(unlikely(anon_vma_prepare(vma)))-----------------(9)
??goto?oom;
?page?=?alloc_zeroed_user_highpage_movable(vma,?vmf->address);-----------------(10)
?if?(!page)
??goto?oom;
?if?(mem_cgroup_charge(page,?vma->vm_mm,?GFP_KERNEL))-----------------(11)
??goto?oom_free_page;
?cgroup_throttle_swaprate(page,?GFP_KERNEL);
?/*
??*?The?memory?barrier?inside?__SetPageUptodate?makes?sure?that
??*?preceding?stores?to?the?page?contents?become?visible?before
??*?the?set_pte_at()?write.
??*/
?__SetPageUptodate(page);-----------------(12)
?entry?=?mk_pte(page,?vma->vm_page_prot);-----------------(13)
?entry?=?pte_sw_mkyoung(entry);
?if?(vma->vm_flags?&?VM_WRITE)
??entry?=?pte_mkwrite(pte_mkdirty(entry));-----------------(14)
?vmf->pte?=?pte_offset_map_lock(vma->vm_mm,?vmf->pmd,?vmf->address,
???&vmf->ptl);-----------------(15)
?if?(!pte_none(*vmf->pte))?{
??update_mmu_cache(vma,?vmf->address,?vmf->pte);-----------------(16)
??goto?release;
?}
?ret?=?check_stable_address_space(vma->vm_mm);-----------------(17)
?if?(ret)
??goto?release;
?/*?Deliver?the?page?fault?to?userland,?check?inside?PT?lock?*/
?if?(userfaultfd_missing(vma))?{
??pte_unmap_unlock(vmf->pte,?vmf->ptl);
??put_page(page);
??return?handle_userfault(vmf,?VM_UFFD_MISSING);
?}
?inc_mm_counter_fast(vma->vm_mm,?MM_ANONPAGES);-----------------(18)
?page_add_new_anon_rmap(page,?vma,?vmf->address,?false);-----------------(19)
?lru_cache_add_inactive_or_unevictable(page,?vma);-----------------(20)
setpte:
?set_pte_at(vma->vm_mm,?vmf->address,?vmf->pte,?entry);-----------------(21)
?/*?No?need?to?invalidate?-?it?was?non-present?before?*/
?update_mmu_cache(vma,?vmf->address,?vmf->pte);-----------------(22)
unlock:
?pte_unmap_unlock(vmf->pte,?vmf->ptl);
?return?ret;
release:
?put_page(page);
?goto?unlock;
oom_free_page:
?put_page(page);
oom:
?return?VM_FAULT_OOM;
}
-
- 如果是共享則意味著之前以及通過(guò)mmap方式在其他進(jìn)程申請(qǐng)過(guò)物理內(nèi)存,vma應(yīng)該存在對(duì)應(yīng)物理內(nèi)存映射,不應(yīng)該再發(fā)生page fault調(diào)用pte_alloc函數(shù)來(lái)為頁(yè)面表表項(xiàng)(PTE)分配內(nèi)存,并傳遞vma->vm_mm和vmf->pmd作為參數(shù)如果頁(yè)面錯(cuò)誤不是寫(xiě)操作且內(nèi)存管理子系統(tǒng)允許使用零頁(yè),則映射到零頁(yè)面生成一個(gè)特殊頁(yè)表項(xiàng),映射到專(zhuān)有的0頁(yè),一頁(yè)大小據(jù)pmd,address找到pte表對(duì)應(yīng)的一個(gè)表項(xiàng),并且lock住如果頁(yè)表項(xiàng)不為空,則調(diào)用update_mmu_tlb
-
- 函數(shù)更新內(nèi)存管理單元(MMU)的轉(zhuǎn)換查找緩沖(TLB)并且跳unlock。檢查地址空間的穩(wěn)定性。如果發(fā)現(xiàn)userfaultfd
-
- 缺失,則解除映射并解鎖頁(yè)面表項(xiàng)(PTE)對(duì)vma進(jìn)行預(yù)處理,主要是創(chuàng)建anon_vma和anon_vma_chain,為后續(xù)反向映射做準(zhǔn)備從高端內(nèi)存區(qū)的伙伴系統(tǒng)中獲取一個(gè)頁(yè),這個(gè)頁(yè)會(huì)清0申請(qǐng)內(nèi)存成功之后,將新申請(qǐng)的page加入到mcgroup管理設(shè)置此頁(yè)的PG_uptodate標(biāo)志,表示此頁(yè)是最新的將頁(yè)面和頁(yè)面保護(hù)位(vma->vm_page_prot
-
- )組合成一個(gè) PTE 條目。如果vma區(qū)是可寫(xiě)的,則給頁(yè)表項(xiàng)添加允許寫(xiě)標(biāo)志。將 PTE 條目的Dirty
-
- 位和Young
-
- 位設(shè)置為1。鎖定pte
-
- 條目,防止同時(shí)更新和更多虛擬內(nèi)存對(duì)物理內(nèi)存映射pte條目存在的話(huà),讓mmu更新頁(yè)表項(xiàng),應(yīng)該會(huì)清除tlb檢查給定的內(nèi)存是否從用戶(hù)拷貝過(guò)來(lái)的。如果從用戶(hù)拷貝過(guò)來(lái)的內(nèi)存不穩(wěn)定,不用處理。增加mm_struct
-
- 中匿名頁(yè)的統(tǒng)計(jì)計(jì)數(shù)對(duì)這個(gè)新頁(yè)進(jìn)行反向映射,主要工作是:設(shè)置此頁(yè)的_mapcount
-
- = 0,說(shuō)明此頁(yè)正在使用,但是是非共享的(>0是共享)。設(shè)置page->mapping
-
- 最低位為1,page->mapping
-
- 指向此vma->anon_vma,
-
-
- page->index
-
- 存放此page在vma中的第幾頁(yè)。通過(guò)判斷,將頁(yè)加入到活動(dòng)lru緩存或者不能換出頁(yè)的lru鏈表將上面配置好的頁(yè)表項(xiàng)寫(xiě)入頁(yè)表更新mmu的cache
do_anonymous_page
首先判斷一下匿名頁(yè)是否是共享的,如果是共享的匿名映射,但是虛擬內(nèi)存區(qū)域沒(méi)有提供虛擬內(nèi)存操作集合
就返回錯(cuò)誤;然后判斷一下pte頁(yè)表是否存在,如果直接頁(yè)表不存在,那么分配頁(yè)表;
接下來(lái)判讀缺頁(yè)異常是由讀操作觸發(fā)的還是寫(xiě)操作觸發(fā)的,如果是讀操作觸發(fā)的,生成特殊的頁(yè)表項(xiàng),映射到專(zhuān)用的零頁(yè),設(shè)置頁(yè)表項(xiàng)后返回;如果是寫(xiě)操作觸發(fā)的,需要初始化vma中的anon_vma_chain和anon_vma,分配物理頁(yè)用于匿名映射,調(diào)用mk_pte函數(shù)生成頁(yè)表項(xiàng),設(shè)置頁(yè)表項(xiàng)的臟標(biāo)志位和寫(xiě)權(quán)限,設(shè)置頁(yè)表項(xiàng)后返回。
小結(jié)
從以上的分析中,我們可以學(xué)習(xí)到關(guān)于常用的頁(yè)表的宏的使用方法。Linux內(nèi)核就是這樣,你不光可以看到某個(gè)函數(shù)的實(shí)現(xiàn),還可以看到某個(gè)函數(shù)的調(diào)用過(guò)程。所以,大家對(duì)某個(gè)函數(shù)有疑問(wèn)的時(shí)候,可以順著這樣的思路去學(xué)習(xí)。
ARM32頁(yè)表和Linux頁(yè)表那些奇葩的地方
ARM32硬件頁(yè)表中PGD頁(yè)目錄項(xiàng)PGD是從20位開(kāi)始的,但是為何頭文件定義是從21位開(kāi)始?
歷史原因:Linux最初是基于x86的體系結(jié)構(gòu)設(shè)計(jì)的,因此Linux內(nèi)核很多的頭文件的定義都是基于x86的,特別是關(guān)于PTE頁(yè)表項(xiàng)里面的很多比特位的定義。因此ARM在移植到Linux時(shí)只能參考x86版本的Linux內(nèi)核的實(shí)現(xiàn)。
X86的PGD是從bit22 ~ bit31,總共10bit位,1024頁(yè)表項(xiàng)。PT頁(yè)表從bit12 ~ bit 21 ,總共 10 bit位,1024頁(yè)表項(xiàng)。
ARM的PGD是從bit20 ~ bit31,總共12bit, 4096頁(yè)表項(xiàng)。PT域從bit12 ~ bit 19,總共8bit,2556頁(yè)表項(xiàng)。
X86和ARM頁(yè)表最大的差異在于PTE頁(yè)表內(nèi)容的不同。
Linux內(nèi)核版本的PTE比特位的定義
/*
?*?"Linux"?PTE?definitions?for?LPAE.
?*
?*?These?bits?overlap?with?the?hardware?bits?but?the?naming?is?preserved?for
?*?consistency?with?the?classic?page?table?format.
?*/
#define?L_PTE_VALID??(_AT(pteval_t,?1)?<<?0)??/*?Valid?*/
#define?L_PTE_PRESENT??(_AT(pteval_t,?3)?<<?0)??/*?Present?*/
#define?L_PTE_USER??(_AT(pteval_t,?1)?<<?6)??/*?AP[1]?*/
#define?L_PTE_SHARED??(_AT(pteval_t,?3)?<<?8)??/*?SH[1:0],?inner?shareable?*/
#define?L_PTE_YOUNG??(_AT(pteval_t,?1)?<<?10)?/*?AF?*/
#define?L_PTE_XN??(_AT(pteval_t,?1)?<<?54)?/*?XN?*/
#define?L_PTE_DIRTY??(_AT(pteval_t,?1)?<<?55)
#define?L_PTE_SPECIAL??(_AT(pteval_t,?1)?<<?56)
#define?L_PTE_NONE??(_AT(pteval_t,?1)?<<?57)?/*?PROT_NONE?*/
#define?L_PTE_RDONLY??(_AT(pteval_t,?1)?<<?58)?/*?READ?ONLY?*/
#define?L_PMD_SECT_VALID?(_AT(pmdval_t,?1)?<<?0)
#define?L_PMD_SECT_DIRTY?(_AT(pmdval_t,?1)?<<?55)
#define?L_PMD_SECT_NONE??(_AT(pmdval_t,?1)?<<?57)
#define?L_PMD_SECT_RDONLY?(_AT(pteval_t,?1)?<<?58)
ARM32的PTE比特位的定義
/*
?*???-?extended?small?page/tiny?page
?*/
#define?PTE_EXT_XN??(_AT(pteval_t,?1)?<<?0)??/*?v6?*/
#define?PTE_EXT_AP_MASK??(_AT(pteval_t,?3)?<<?4)
#define?PTE_EXT_AP0??(_AT(pteval_t,?1)?<<?4)
#define?PTE_EXT_AP1??(_AT(pteval_t,?2)?<<?4)
#define?PTE_EXT_AP_UNO_SRO?(_AT(pteval_t,?0)?<<?4)
#define?PTE_EXT_AP_UNO_SRW?(PTE_EXT_AP0)
#define?PTE_EXT_AP_URO_SRW?(PTE_EXT_AP1)
#define?PTE_EXT_AP_URW_SRW?(PTE_EXT_AP1|PTE_EXT_AP0)
#define?PTE_EXT_TEX(x)??(_AT(pteval_t,?(x))?<<?6)?/*?v5?*/
#define?PTE_EXT_APX??(_AT(pteval_t,?1)?<<?9)??/*?v6?*/
#define?PTE_EXT_COHERENT?(_AT(pteval_t,?1)?<<?9)??/*?XScale3?*/
#define?PTE_EXT_SHARED??(_AT(pteval_t,?1)?<<?10)?/*?v6?*/
#define?PTE_EXT_NG??(_AT(pteval_t,?1)?<<?11)?/*?v6?*/
那X86和ARM的頁(yè)表差距這么大,軟件怎么設(shè)計(jì)呢?Linux內(nèi)核的內(nèi)存管理已經(jīng)適配了X86的頁(yè)表項(xiàng),我們可以通過(guò)軟件適配的辦法來(lái)解決這個(gè)問(wèn)題。因此,ARM公司在移植該方案時(shí)提出了兩套頁(yè)表的方案。一套頁(yè)表是為了迎合ARM硬件的真實(shí)頁(yè)表,另一套頁(yè)表是為了迎合Linux真實(shí)的頁(yè)表。
對(duì)于PTE頁(yè)表來(lái)說(shuō),一下子就多出了一套頁(yè)表,一套頁(yè)表256表項(xiàng),每個(gè)表項(xiàng)占用4字節(jié)。為了軟件實(shí)現(xiàn)的方便,軟件會(huì)把兩個(gè)頁(yè)表合并成一個(gè)頁(yè)表。4套頁(yè)表正好占用256 * 4 * 4 = 4K的空間。因此,Linux實(shí)現(xiàn)的時(shí)候,就分配了一個(gè)page 來(lái)存放這些頁(yè)表。
這一套方案的話(huà),相當(dāng)于每個(gè)PGD頁(yè)表項(xiàng)有8字節(jié),包含指向兩套PTE頁(yè)表項(xiàng)的entry。每4個(gè)字節(jié)指向一個(gè)物理的二級(jí)頁(yè)表。
本文參考
奔跑吧Linux內(nèi)核
http://www.wowotech.net/memory_management/mem_init_3.html
http://blog.chinaunix.net/uid-628190-id-5821835.html
https://blog.csdn.net/zhoutaopower/article/details/88940727
https://blog.csdn.net/zhoutaopower/article/details/88940727
https://zhuanlan.zhihu.com/p/543076384
https://blog.csdn.net/huyugv_830913/article/details/5884628
https://zhuanlan.zhihu.com/p/452139283
https://www.cnblogs.com/arnoldlu/p/8335508.html
https://www.cnblogs.com/tolimit/p/5398552.html
https://blog.csdn.net/weixin_42419952/article/details/124392825
https://blog.csdn.net/sinat_22338935/article/details/128899811
https://zhuanlan.zhihu.com/p/377905409
https://www.cnblogs.com/pwl999/p/15534986.html