頁表的一些術(shù)語
現(xiàn)在Linux內(nèi)核中支持四級(jí)頁表的映射,我們先看下內(nèi)核中關(guān)于頁表的一些術(shù)語:
全局目錄項(xiàng),PGD(Page Global Directory)
上級(jí)目錄項(xiàng),PUD(Page Upper Directory)
中間目錄項(xiàng),PMD(Page Middle Directory)
頁表項(xiàng),(Page Table)
大家在看內(nèi)核代碼時(shí)會(huì)經(jīng)??吹囊陨闲g(shù)語,但在ARM的芯片手冊中并沒有用到這些術(shù)語,而是使用L1,L2,L3頁表這種術(shù)語。
ARM32 虛擬地址到物理地址的轉(zhuǎn)換
虛擬地址的32個(gè)bit位可以分為3個(gè)域,最高12bit位20~31位稱為L1索引,叫做PGD,頁面目錄。中間的8個(gè)bit位叫做L2索引,在Linux內(nèi)核中叫做PT,頁表。最低的12位叫做頁索引。
在ARM處理器中,TTBRx寄存器存放著頁表基地址,我們這里的一級(jí)頁表有4096個(gè)頁表項(xiàng)。每個(gè)表項(xiàng)中存放著二級(jí)表項(xiàng)的基地址。我們可以通過虛擬地址的L1索引訪問一級(jí)頁表,訪問一級(jí)頁表相當(dāng)于數(shù)組訪問。
二級(jí)頁表通常是動(dòng)態(tài)分配的,可以通過虛擬地址的中間8bit位L2索引訪問二級(jí)頁表,在L2索引中存放著最終物理地址的高20bit位,然后和虛擬地址的低12bit位就組成了最終的物理地址。以上就是虛擬地址轉(zhuǎn)換為物理地址的過程。
MMU訪問頁表是硬件實(shí)現(xiàn)的,但頁表的創(chuàng)建和填充需要Linux內(nèi)核來填充。通常,一級(jí)頁表和二級(jí)頁表存放在主存儲(chǔ)器中。
ARM32 一級(jí)頁表的頁表項(xiàng)
下面這張圖來自ARMV7的手冊。
一級(jí)頁表項(xiàng)這里有三種情況:一種是無效的,第二種是一級(jí)頁表的表項(xiàng)。第三種是段映射的頁表項(xiàng)。
bit 0 ~ bit 1:用來表示這個(gè)頁表項(xiàng)是一級(jí)頁表還是段映射的表項(xiàng)。
PXN:PL1 表示是否可以執(zhí)行這段代碼,為0表示可執(zhí)行,1表示不可執(zhí)行。
NS:none-security bit,用于安全擴(kuò)展。
Domain:Domain域,指明所屬的域,Linux中只使用了3個(gè)域。
bit31:bit10:指向二級(jí)頁表基地址。
二級(jí)頁表的表項(xiàng)
bit0:禁止執(zhí)行標(biāo)志。1表示禁止執(zhí)行,0表示可執(zhí)行
bit1:區(qū)分是大頁還是小頁
C/B bit:內(nèi)存區(qū)域?qū)傩?/p>
TEX[2:0]:內(nèi)存區(qū)域?qū)傩?/p>
AP[0:1] :訪問權(quán)限
S:是否可共享
nG:用于TLB
ARM64 頁表
ARM體系結(jié)構(gòu)從ARMV8-A開始就支持64bit位,最大支持48根地址線。那為什么不支持64根地址線呢?主要原因是48根地址線時(shí)已支持最大訪問空間為256TB(內(nèi)核空間和用戶空間分別256TB)滿足了大部分應(yīng)用的需求。而且,64根地址線時(shí),芯片的設(shè)計(jì)復(fù)雜度會(huì)急劇增加。ARMV8-A架構(gòu)中,支持4KB,16KB和64KB的頁,支持3級(jí)或者4級(jí)映射。
下面我們以4KB大小頁+4級(jí)映射介紹下虛擬地址到物理地址的映射過程。
0~11 :頁索引
bit 63 :頁表基地址選擇位,ARMV8架構(gòu)中有2兩個(gè)頁表基地址,一個(gè)用于用戶空間,一個(gè)用戶內(nèi)核空間。
39~47:L0索引
30~38:L1索引
21~29:L2索引
12~20:L3 索引
假設(shè)頁表基地址為TTBRx,訪問頁表基地址就能訪問到L0頁表的基地址,可以使用L0索引的值作為offset去訪問L0頁表。
L0的頁表項(xiàng)包含了下一級(jí)L1頁表的基地址,同樣的,可以使用L1索引的值作為offset去訪問L2頁表。以此類推。
最后通過L3的頁表項(xiàng)可以得到物理地址的bit12 ~ 47位,這個(gè)時(shí)候再將虛擬地址的頁索引位對應(yīng)到物理地址的0~11就是完整的物理地址。
Linux內(nèi)核關(guān)于頁表的函數(shù)
Linux內(nèi)核中頁表操作的宏定義
Linux內(nèi)核中封裝了很多宏來處理頁表
#definepgd_offset_k(addr)pgd_offset(&init_mm,addr)//由虛擬地址來獲取內(nèi)核頁表的PGD頁表的相應(yīng)的頁表項(xiàng) #definepgd_offset(mm,addr)((mm)->pgd+pgd_index(addr))//由虛擬地址來獲取用戶進(jìn)程的頁表中相應(yīng)的PGD表項(xiàng) pgd_index(addr)//由虛擬地址找到PGD頁表的索引 pte_index(addr)//由虛擬地址找到PT頁表的索引 pte_offset_kernel(pmd,addr)//查找內(nèi)核頁表中對應(yīng)的PT頁表的表項(xiàng)
判斷頁表項(xiàng)的狀態(tài)
#definepte_none(pte)(!pte_val(pte))//pte是否存在 #definepte_present(pte)(pte_isset((pte),L_PTE_PRESENT))//present比特位 #definepte_valid(pte)(pte_isset((pte),L_PTE_VALID))//pte是否有效 #definepte_accessible(mm,pte)(mm_tlb_flush_pending(mm)?pte_present(pte):pte_valid(pte)) #definepte_write(pte)(pte_isclear((pte),L_PTE_RDONLY))//pte是否可寫 #definepte_dirty(pte)(pte_isset((pte),L_PTE_DIRTY))//pte是否有臟數(shù)據(jù) #definepte_young(pte)(pte_isset((pte),L_PTE_YOUNG))// #definepte_exec(pte)(pte_isclear((pte),L_PTE_XN))
修改頁表
mk_pte()//創(chuàng)建的相應(yīng)的頁表項(xiàng) pte_mkdirty()//設(shè)置dirty標(biāo)志位 pte_mkold()//清除Accessed標(biāo)志位 pte_mkclean()//清除dirty標(biāo)志位 pte_mkwrite()//設(shè)置讀寫標(biāo)志位 pte_wrprotect()//清除讀寫標(biāo)志位 pte_mkyoung()//設(shè)置Accessed標(biāo)志位 set_pte_at()//設(shè)置頁表項(xiàng)到硬件中
例子1 內(nèi)核頁表的映射
前面我們介紹了很多關(guān)于內(nèi)核的宏,函數(shù),下面我們通過實(shí)際的例子學(xué)習(xí)如何使用這些宏
系統(tǒng)初始化時(shí)需要把kernel image區(qū)域和線性映射區(qū)建立頁表映射,這個(gè)時(shí)候依次調(diào)用start_kernel() --> setup_arch() --> paging_init() --> map_lowmem() --> create_mapping()去創(chuàng)建內(nèi)核頁表。我們可以研究下內(nèi)核是如何建立內(nèi)核頁表的映射。
/* *Createthepagedirectoryentriesandanynecessary *pagetablesforthemappingspecifiedby`md'.We *areabletocopeherewithvaryingsizesandaddress *offsets,andwetakefulladvantageofsectionsand *supersections. */ staticvoid__initcreate_mapping(structmap_desc*md) { if(md->virtual!=vectors_base()&&md->virtualpfn),md->virtual); return; } if(md->type==MT_DEVICE&& md->virtual>=PAGE_OFFSET&&md->virtualvirtualvirtual>=VMALLOC_END)){ pr_warn("BUG:mappingfor0x%08llxat0x%08lxoutofvmallocspace ", (longlong)__pfn_to_phys((u64)md->pfn),md->virtual); } __create_mapping(&init_mm,md,early_alloc,false); }
首先會(huì)檢查映射的虛擬地址是否在內(nèi)核向量表的基址以上,并且小于用戶空間的TASK_SIZE。TASK_SIZE通常被定義為0xC0000000(3GB),表示用戶空間的虛擬地址范圍從0到3GB。對于64位體系結(jié)構(gòu),TASK_SIZE通常被定義為0x00007fffffffffff(128TB)。
接著會(huì)檢查映射的類型是否為設(shè)備類型,并且虛擬地址在頁偏移以上且低于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)志。
/* *Createamappingforthegivenmapdescriptor,md.Thefunction *__create_mappingisusedforbothkernelandusermodemappings. * *@mm:themmstructurewherethemappingwillbecreated *@md:themapdescriptorwiththedetailsofthemapping *@alloc:apointertoafunctionusedtoallocatepagesforthemapping *@ng:abooleanflagindicatingifthemappingisnon-global */ staticvoid__init__create_mapping(structmm_struct*mm,structmap_desc*md, void*(*alloc)(unsignedlongsz), boolng) { unsignedlongaddr,length,end; phys_addr_tphys; conststructmem_type*type; pgd_t*pgd; type=&mem_types[md->type]; #ifndefCONFIG_ARM_LPAE----------------------(1) /* *Catch36-bitaddresses */ 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)); /* *Checkifthemappingcanbemadeusingpages. *Ifnot,printawarningandignoretherequest. */ if(type->prot_l1==0&&((addr|phys|length)&~SECTION_MASK)){----------------------(3) pr_warn("BUG:mapfor0x%08llxat0x%08lxcannotbemappedusingpages,ignoring. ", (longlong)__pfn_to_phys(md->pfn),addr); return; } pgd=pgd_offset(mm,addr); end=addr+length;----------------------(4) do{ unsignedlongnext=pgd_addr_end(addr,end);----------------------(5) /* *Allocateapagedirectoryentryforthisrange. *Initializeitwiththeappropriatepagetable *andmakethemapping. */ alloc_init_p4d(pgd,addr,next,phys,type,alloc,ng);----------------------(6) /* *Updatethephysvaluewiththeendofthelastmapped *pagesothatthenextrangecanbeallocatedproperly. */ phys+=next-addr; addr=next;----------------------(7) }while(pgd++,addr!=end); }
__create_mapping完成中創(chuàng)建映射的功能,根據(jù)給定的映射描述結(jié)構(gòu)體,將虛擬地址與物理地址進(jìn)行映射。
(1) 系統(tǒng)沒有啟用ARM LPAE(Large Physical Address Extension),并且物理頁幀號(hào)大于等于0x100000,調(diào)用create_36bit_mapping函數(shù)進(jìn)行處理,然后返回。
在早期階段,地址總線也是32位的,即4G的內(nèi)存地址空間。隨著應(yīng)用程序越來越豐富,占用的內(nèi)存總量很容易就超過了4G。但由于編程模型和地址總線的限制,是無法使用超過4G的物理地址的。所以PAE/LPAE這種大內(nèi)存地址方案應(yīng)運(yùn)而生。
PAE/LAPE方案其它很簡單,編程視角依然還是32位(4G)的地址空間,這層是虛擬地址空間。而計(jì)算機(jī)地址總線卻使用超過32位的,比如X86的就使用36位(64G)的地址總線,ARM使用的是48位(64G)的地址總線。中間是通過保護(hù)模式(X86架構(gòu))或者M(jìn)MU機(jī)制(ARM架構(gòu))提供的分頁技術(shù)(paging)實(shí)現(xiàn)32位虛擬地址訪問超過4G的物理內(nèi)存空間。這項(xiàng)技術(shù)的關(guān)鍵是分頁技術(shù)中的頁表項(xiàng)使用超過4字節(jié)的映射表 (ARM在LPAE模式下,頁表項(xiàng)是8字節(jié)),因?yàn)槭褂贸^4字節(jié)映射表,就可以指示超過4G的內(nèi)存空間。
(2) 獲取虛擬地址的起始地址,因?yàn)榈刂酚成涞淖钚挝皇莗age,因此這里進(jìn)行mapping的虛擬地址需要對齊到page size,同樣的,長度也需要對齊到page size。
(3) 首先檢查映射類型的prot_l1字段是否為0。prot_l1表示第一級(jí)頁表(Level 1 Page Table)的保護(hù)位。如果prot_l1為0,表示無法使用頁面進(jìn)行映射。如果地址、物理地址和長度與SECTION_MASK存在非零位,表示頁面映射要求地址和長度并未按頁面大小對齊。
(4)設(shè)置了頁全局目錄(pgd)的初始偏移,并將結(jié)束地址(end)設(shè)置為起始地址(addr)加上長度(length)。
(5)然后,使用pgd_addr_end函數(shù)計(jì)算下一個(gè)地址(next),該地址是當(dāng)前地址和結(jié)束地址之間的較小值。
(6)調(diào)用alloc_init_p4d函數(shù),為當(dāng)前范圍內(nèi)的地址分配一個(gè)頁目錄項(xiàng),初始化它的頁表,并進(jìn)行映射。該函數(shù)使用給定的參數(shù)pgd、addr、next、phys、type、alloc和ng來執(zhí)行這些操作。
(7)更新phys的值,使其加上當(dāng)前范圍內(nèi)映射的頁面數(shù),以便正確分配下一個(gè)范圍的地址。最后,在循環(huán)的末尾,遞增pgd的值,并檢查是否達(dá)到了結(jié)束地址。如果沒有達(dá)到,繼續(xù)循環(huán)處理下一個(gè)地址范圍。
例子2 進(jìn)程頁表的映射
remap_pfn_range函數(shù)對于寫過Linux驅(qū)動(dòng)的人都不陌生,很多驅(qū)動(dòng)程序的mmap函數(shù)都會(huì)調(diào)用到該函數(shù),該函數(shù)實(shí)現(xiàn)了物理空間到用戶進(jìn)程的映射。
比如我們在用戶空間讀寫SOC的寄存器時(shí),ARM中的寄存器通常都是memory map形式的,在用戶空間都要讀寫ARM空間的寄存器,通常都要操作/dev/mem設(shè)備來實(shí)現(xiàn),最后都會(huì)調(diào)用到remap_pfn_range來實(shí)現(xiàn)。
VMA:準(zhǔn)備要映射的進(jìn)程地址空間的VMA的數(shù)據(jù)結(jié)構(gòu)
addr:要映射到 用戶空間的起始地址
pfn:準(zhǔn)備要映射的物理內(nèi)存的頁幀號(hào)
size:表示要映射的大小
prot:表示要映射的屬性
接下來我們從頁表的角度看下函數(shù)的實(shí)現(xiàn)
intremap_pfn_range(structvm_area_struct*vma,unsignedlongaddr, unsignedlongpfn,unsignedlongsize,pgprot_tprot) { pgd_t*pgd; unsignedlongnext; unsignedlongend=addr+PAGE_ALIGN(size); structmm_struct*mm=vma->vm_mm;//從VMA獲取當(dāng)前進(jìn)程的mm_struct結(jié)構(gòu) unsignedlongremap_pfn=pfn; interr; 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)核對物理頁面分為兩類:normalmapping,specialmapping。specialmapping就是內(nèi)核不希望該頁面參與到內(nèi)核的頁面回收等活動(dòng)中。 BUG_ON(addr>=end); pfn-=addr>>PAGE_SHIFT; pgd=pgd_offset(mm,addr);//找到頁表項(xiàng) flush_cache_range(vma,addr,end); //以PGD_SIZE為步長遍歷頁表 do{ next=pgd_addr_end(addr,end);//獲取下一個(gè)PGD頁表項(xiàng)的管轄的地址范圍的起始地址 err=remap_p4d_range(mm,pgd,addr,next, pfn+(addr>>PAGE_SHIFT),prot);//繼續(xù)遍歷下一級(jí)頁表 if(err) break; }while(pgd++,addr=next,addr!=end); if(err) untrack_pfn(vma,remap_pfn,PAGE_ALIGN(size)); returnerr; }
遍歷PUD頁表
staticinlineintremap_pud_range(structmm_struct*mm,p4d_t*p4d, unsignedlongaddr,unsignedlongend, unsignedlongpfn,pgprot_tprot) { pud_t*pud; unsignedlongnext; interr; pfn-=addr>>PAGE_SHIFT; pud=pud_alloc(mm,p4d,addr);//找到pud頁表項(xiàng)。對于二級(jí)頁表來說,PUD指向PGD if(!pud) return-ENOMEM; //以PUD_SIZE為步長遍歷頁表 do{ next=pud_addr_end(addr,end);//獲取下一個(gè)PUD頁表項(xiàng)的管轄的地址范圍的起始地址 err=remap_pmd_range(mm,pud,addr,next, pfn+(addr>>PAGE_SHIFT),prot);//繼續(xù)遍歷下一級(jí)頁表 if(err) returnerr; }while(pud++,addr=next,addr!=end); return0; }
Linux內(nèi)核中實(shí)現(xiàn)了4級(jí)頁表,對于ARM32來說,它是如何跳過中間兩級(jí)頁表的呢?大家可以看下以下兩個(gè)宏的實(shí)現(xiàn)
/*Findanentryinthesecond-levelpagetable..*/ #ifndefpmd_offset staticinlinepmd_t*pmd_offset(pud_t*pud,unsignedlongaddress) { return(pmd_t*)pud_page_vaddr(*pud)+pmd_index(address); } #definepmd_offsetpmd_offset #endif
接收指向頁上級(jí)目錄項(xiàng)的指針 pud 和線性地址 addr 作為參數(shù)。這個(gè)宏產(chǎn)生目錄項(xiàng) addr 在頁中間目錄中的偏移地址。在兩級(jí)或三級(jí)分頁系統(tǒng)中,它產(chǎn)生 pud ,即頁全局目錄項(xiàng)的地址。
#ifndefpud_offset staticinlinepud_t*pud_offset(p4d_t*p4d,unsignedlongaddress) { return(pud_t*)p4d_page_vaddr(*p4d)+pud_index(address); } #definepud_offsetpud_offset #endif
參數(shù)為指向頁全局目錄項(xiàng)的指針 pgd 和線性地址 addr 。這個(gè)宏產(chǎn)生頁上級(jí)目錄中目錄項(xiàng) addr 對應(yīng)的線性地址。在兩級(jí)或三級(jí)分頁系統(tǒng)中,該宏產(chǎn)生 pgd ,即一個(gè)頁全局目錄項(xiàng)的地址。
遍歷PMD頁表
remap_pmd_range函數(shù)和remap_pud_range類似。
staticinlineintioremap_pmd_range(pud_t*pud,unsignedlongaddr, unsignedlongend,phys_addr_tphys_addr,pgprot_tprot, pgtbl_mod_mask*mask) { pmd_t*pmd; unsignedlongnext; pmd=pmd_alloc_track(&init_mm,pud,addr,mask);//找到對應(yīng)的pmd頁表項(xiàng),對于二級(jí)頁表來說,pmd指向pud if(!pmd) return-ENOMEM; //以PMD_SIZE為步長遍歷頁表 do{ next=pmd_addr_end(addr,end);//獲取下一個(gè)PMD頁表項(xiàng)的管轄的地址范圍的起始地址 if(ioremap_try_huge_pmd(pmd,addr,next,phys_addr,prot)){ *mask|=PGTBL_PMD_MODIFIED; continue; } //繼續(xù)遍歷下一級(jí)頁表 if(ioremap_pte_range(pmd,addr,next,phys_addr,prot,mask)) return-ENOMEM; }while(pmd++,phys_addr+=(next-addr),addr=next,addr!=end); return0; }
遍歷PT頁表
/* *mapsarangeofphysicalmemoryintotherequestedpages.theold *mappingsareremoved.anyreferencestononexistentpagesresults *innullmappings(currentlytreatedas"copy-on-access") */ staticintremap_pte_range(structmm_struct*mm,pmd_t*pmd, unsignedlongaddr,unsignedlongend, unsignedlongpfn,pgprot_tprot) { pte_t*pte,*mapped_pte; spinlock_t*ptl; interr=0; mapped_pte=pte=pte_alloc_map_lock(mm,pmd,addr,&ptl);//尋找相應(yīng)的pte頁表項(xiàng)。注意這里需要申請一個(gè)spinlock鎖用來保護(hù)修改pte頁表 if(!pte) return-ENOMEM; arch_enter_lazy_mmu_mode(); //以PAGE_SIZE為步長遍歷PT頁表 do{ BUG_ON(!pte_none(*pte)); if(!pfn_modify_allowed(pfn,prot)){ err=-EACCES; break; } /* *pte_none()判斷這個(gè)pte是否存在 *pfn_pte()由頁幀號(hào)pfn得到pte *pte_mkspecial()設(shè)置軟件的PTE_SPECIAL標(biāo)志位(三級(jí)頁表才會(huì)用該標(biāo)志位) *set_pte_at()把pte設(shè)置到硬件頁表中 */ 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頁表設(shè)置完成后,需要把spinlock釋放 returnerr; }
缺頁中斷do_anonymous_page
在缺頁中斷處理中,匿名頁面的觸發(fā)條件為下面的兩個(gè)條件,當(dāng)滿足這兩個(gè)條件的時(shí)候就會(huì)調(diào)用do_anonymous_page函數(shù)來處理匿名映射缺頁異常,代碼實(shí)現(xiàn)在mm/memory.c文件中
發(fā)生缺頁的地址所在頁表項(xiàng)不存在
是匿名頁,即是vma->vm_ops為空,即vm_operations函數(shù)指針為空
我們知道在進(jìn)程的task_struct結(jié)構(gòu)中包含了一個(gè)mm_struct結(jié)構(gòu)的指針,mm_struct用來描述一個(gè)進(jìn)程的虛擬地址空間。進(jìn)程的 mm_struct 則包含裝入的可執(zhí)行映像信息以及進(jìn)程的頁目錄指針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ū)間的打開、關(guān)閉,而nopage 用于當(dāng)虛存頁面不在物理內(nèi)存而引起的“缺頁異?!睍r(shí)所應(yīng)該調(diào)用的函數(shù)
/* *Weenterwithnon-exclusivemmap_lock(toexcludevmachanges, *butallowconcurrentfaults),andptemappedbutnotyetlocked. *Wereturnwithmmap_lockstillheld,butpteunmappedandunlocked. */ staticvm_fault_tdo_anonymous_page(structvm_fault*vmf) { structvm_area_struct*vma=vmf->vma; structpage*page; vm_fault_tret=0; pte_tentry; /*Filemappingwithout->vm_ops?*/ if(vma->vm_flags&VM_SHARED)-----------------(1) returnVM_FAULT_SIGBUS; /* *Usepte_alloc()insteadofpte_alloc_map().Wecan'trun *pte_offset_map()onpmdswhereahugepmdmightbecreated *fromadifferentthread. * *pte_alloc_map()issafetouseundermmap_write_lock(mm)orwhen *parallelthreadsareexcludedbyothermeans. * *Hereweonlyhavemmap_read_lock(mm). */ if(pte_alloc(vma->vm_mm,vmf->pmd))-----------------(2) returnVM_FAULT_OOM; /*Seethecommentinpte_alloc_one_map()*/ if(unlikely(pmd_trans_unstable(vmf->pmd))) return0; /*Usethezero-pageforreads*/ 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); gotounlock; } ret=check_stable_address_space(vma->vm_mm);-----------------(7) if(ret) gotounlock; /*Deliverthepagefaulttouserland,checkinsidePTlock*/ if(userfaultfd_missing(vma)){-----------------(8) pte_unmap_unlock(vmf->pte,vmf->ptl); returnhandle_userfault(vmf,VM_UFFD_MISSING); } gotosetpte; } /*Allocateourownprivatepage.*/ if(unlikely(anon_vma_prepare(vma)))-----------------(9) gotooom; page=alloc_zeroed_user_highpage_movable(vma,vmf->address);-----------------(10) if(!page) gotooom; if(mem_cgroup_charge(page,vma->vm_mm,GFP_KERNEL))-----------------(11) gotooom_free_page; cgroup_throttle_swaprate(page,GFP_KERNEL); /* *Thememorybarrierinside__SetPageUptodatemakessurethat *precedingstorestothepagecontentsbecomevisiblebefore *theset_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) gotorelease; } ret=check_stable_address_space(vma->vm_mm);-----------------(17) if(ret) gotorelease; /*Deliverthepagefaulttouserland,checkinsidePTlock*/ if(userfaultfd_missing(vma)){ pte_unmap_unlock(vmf->pte,vmf->ptl); put_page(page); returnhandle_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) /*Noneedtoinvalidate-itwasnon-presentbefore*/ update_mmu_cache(vma,vmf->address,vmf->pte);-----------------(22) unlock: pte_unmap_unlock(vmf->pte,vmf->ptl); returnret; release: put_page(page); gotounlock; oom_free_page: put_page(page); oom: returnVM_FAULT_OOM; }
如果是共享則意味著之前以及通過mmap方式在其他進(jìn)程申請過物理內(nèi)存,vma應(yīng)該存在對應(yīng)物理內(nèi)存映射,不應(yīng)該再發(fā)生page fault
調(diào)用pte_alloc函數(shù)來為頁面表表項(xiàng)(PTE)分配內(nèi)存,并傳遞vma->vm_mm和vmf->pmd作為參數(shù)
如果頁面錯(cuò)誤不是寫操作且內(nèi)存管理子系統(tǒng)允許使用零頁,則映射到零頁面
生成一個(gè)特殊頁表項(xiàng),映射到專有的0頁,一頁大小
據(jù)pmd,address找到pte表對應(yīng)的一個(gè)表項(xiàng),并且lock住
如果頁表項(xiàng)不為空,則調(diào)用update_mmu_tlb函數(shù)更新內(nèi)存管理單元(MMU)的轉(zhuǎn)換查找緩沖(TLB)并且跳unlock。
檢查地址空間的穩(wěn)定性。
如果發(fā)現(xiàn)userfaultfd缺失,則解除映射并解鎖頁面表項(xiàng)(PTE)
對vma進(jìn)行預(yù)處理,主要是創(chuàng)建anon_vma和anon_vma_chain,為后續(xù)反向映射做準(zhǔn)備
從高端內(nèi)存區(qū)的伙伴系統(tǒng)中獲取一個(gè)頁,這個(gè)頁會(huì)清0
申請內(nèi)存成功之后,將新申請的page加入到mcgroup管理
設(shè)置此頁的PG_uptodate標(biāo)志,表示此頁是最新的
將頁面和頁面保護(hù)位(vma->vm_page_prot)組合成一個(gè) PTE 條目。
如果vma區(qū)是可寫的,則給頁表項(xiàng)添加允許寫標(biāo)志。將 PTE 條目的 Dirty 位和 Young 位設(shè)置為1。
鎖定 pte 條目,防止同時(shí)更新和更多虛擬內(nèi)存對物理內(nèi)存映射
pte條目存在的話,讓mmu更新頁表項(xiàng),應(yīng)該會(huì)清除tlb
檢查給定的內(nèi)存是否從用戶拷貝過來的。如果從用戶拷貝過來的內(nèi)存不穩(wěn)定,不用處理。
增加mm_struct中匿名頁的統(tǒng)計(jì)計(jì)數(shù)
對這個(gè)新頁進(jìn)行反向映射,主要工作是:設(shè)置此頁的_mapcount = 0,說明此頁正在使用,但是是非共享的(>0是共享)。設(shè)置page->mapping最低位為1,page->mapping指向此vma->anon_vma,page->index存放此page在vma中的第幾頁。
通過判斷,將頁加入到活動(dòng)lru緩存或者不能換出頁的lru鏈表
將上面配置好的頁表項(xiàng)寫入頁表
更新mmu的cache
do_anonymous_page首先判斷一下匿名頁是否是共享的,如果是共享的匿名映射,但是虛擬內(nèi)存區(qū)域沒有提供虛擬內(nèi)存操作集合就返回錯(cuò)誤;然后判斷一下pte頁表是否存在,如果直接頁表不存在,那么分配頁表;
接下來判讀缺頁異常是由讀操作觸發(fā)的還是寫操作觸發(fā)的,如果是讀操作觸發(fā)的,生成特殊的頁表項(xiàng),映射到專用的零頁,設(shè)置頁表項(xiàng)后返回;如果是寫操作觸發(fā)的,需要初始化vma中的anon_vma_chain和anon_vma,分配物理頁用于匿名映射,調(diào)用mk_pte函數(shù)生成頁表項(xiàng),設(shè)置頁表項(xiàng)的臟標(biāo)志位和寫權(quán)限,設(shè)置頁表項(xiàng)后返回。
小結(jié)
從以上的分析中,我們可以學(xué)習(xí)到關(guān)于常用的頁表的宏的使用方法。Linux內(nèi)核就是這樣,你不光可以看到某個(gè)函數(shù)的實(shí)現(xiàn),還可以看到某個(gè)函數(shù)的調(diào)用過程。所以,大家對某個(gè)函數(shù)有疑問的時(shí)候,可以順著這樣的思路去學(xué)習(xí)。
ARM32頁表和Linux頁表那些奇葩的地方
ARM32硬件頁表中PGD頁目錄項(xiàng)PGD是從20位開始的,但是為何頭文件定義是從21位開始?
歷史原因:Linux最初是基于x86的體系結(jié)構(gòu)設(shè)計(jì)的,因此Linux內(nèi)核很多的頭文件的定義都是基于x86的,特別是關(guān)于PTE頁表項(xiàng)里面的很多比特位的定義。因此ARM在移植到Linux時(shí)只能參考x86版本的Linux內(nèi)核的實(shí)現(xiàn)。
X86的PGD是從bit22 ~ bit31,總共10bit位,1024頁表項(xiàng)。PT頁表從bit12 ~ bit 21 ,總共 10 bit位,1024頁表項(xiàng)。
ARM的PGD是從bit20 ~ bit31,總共12bit, 4096頁表項(xiàng)。PT域從bit12 ~ bit 19,總共8bit,2556頁表項(xiàng)。
X86和ARM頁表最大的差異在于PTE頁表內(nèi)容的不同。
Linux內(nèi)核版本的PTE比特位的定義
/* *"Linux"PTEdefinitionsforLPAE. * *Thesebitsoverlapwiththehardwarebitsbutthenamingispreservedfor *consistencywiththeclassicpagetableformat. */ #defineL_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比特位的定義
/* *-extendedsmallpage/tinypage */ #definePTE_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的頁表差距這么大,軟件怎么設(shè)計(jì)呢?Linux內(nèi)核的內(nèi)存管理已經(jīng)適配了X86的頁表項(xiàng),我們可以通過軟件適配的辦法來解決這個(gè)問題。因此,ARM公司在移植該方案時(shí)提出了兩套頁表的方案。一套頁表是為了迎合ARM硬件的真實(shí)頁表,另一套頁表是為了迎合Linux真實(shí)的頁表。
對于PTE頁表來說,一下子就多出了一套頁表,一套頁表256表項(xiàng),每個(gè)表項(xiàng)占用4字節(jié)。為了軟件實(shí)現(xiàn)的方便,軟件會(huì)把兩個(gè)頁表合并成一個(gè)頁表。4套頁表正好占用256 * 4 * 4 = 4K的空間。因此,Linux實(shí)現(xiàn)的時(shí)候,就分配了一個(gè)page 來存放這些頁表。
這一套方案的話,相當(dāng)于每個(gè)PGD頁表項(xiàng)有8字節(jié),包含指向兩套PTE頁表項(xiàng)的entry。每4個(gè)字節(jié)指向一個(gè)物理的二級(jí)頁表。
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1416瀏覽量
41424 -
Linux
+關(guān)注
關(guān)注
87文章
11511瀏覽量
213779 -
內(nèi)存管理
+關(guān)注
關(guān)注
0文章
168瀏覽量
14563
原文標(biāo)題:【內(nèi)存管理】頁表映射基礎(chǔ)知識(shí)
文章出處:【微信號(hào):嵌入式與Linux那些事,微信公眾號(hào):嵌入式與Linux那些事】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
詳解Linux系統(tǒng)文件頁表目錄和Linux系統(tǒng)頁表結(jié)構(gòu)

Linux內(nèi)核之內(nèi)存映射原理分析
Linux內(nèi)核地址映射模型與Linux內(nèi)核高端內(nèi)存詳解

linux系統(tǒng)內(nèi)核中ioremap映射分析
Linux設(shè)備驅(qū)動(dòng)程序基礎(chǔ)知識(shí)的了解
Linux驅(qū)動(dòng)編程基礎(chǔ)知識(shí)講解
Linux內(nèi)核反向映射基礎(chǔ)知識(shí)詳解
ARM64 Linux內(nèi)核頁表的塊映射

解析Linux內(nèi)核頁表管理中那些鮮為人知的秘密
Linux用戶態(tài)開發(fā)驅(qū)動(dòng)教程及基礎(chǔ)知識(shí)
Linux中內(nèi)核搶占相關(guān)的基礎(chǔ)知識(shí)
Linux內(nèi)核pwn基礎(chǔ)知識(shí)

MMU多級(jí)頁表映射過程

評論