0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

Linux內(nèi)核中頁表映射的基礎知識

嵌入式與Linux那些事 ? 來源:嵌入式與Linux那些事 ? 2024-08-07 15:53 ? 次閱讀

來源:嵌入式Linux那些事公眾號

頁表的一些術語

現(xiàn)在Linux內(nèi)核中支持四級頁表的映射,我們先看下內(nèi)核中關于頁表的一些術語:

全局目錄項,PGD(Page Global Directory)

上級目錄項,PUD(Page Upper Directory)

中間目錄項,PMD(Page Middle Directory)

頁表項,(Page Table)

大家在看內(nèi)核代碼時會經(jīng)??吹囊陨闲g語,但在ARM芯片手冊中并沒有用到這些術語,而是使用L1,L2,L3頁表這種術語。

ARM32 虛擬地址到物理地址的轉換

虛擬地址的32個bit位可以分為3個域,最高12bit位20~31位稱為L1索引,叫做PGD,頁面目錄。中間的8個bit位叫做L2索引,在Linux內(nèi)核中叫做PT,頁表。最低的12位叫做頁索引。

在ARM處理器中,TTBRx寄存器存放著頁表基地址,我們這里的一級頁表有4096個頁表項。每個表項中存放著二級表項的基地址。我們可以通過虛擬地址的L1索引訪問一級頁表,訪問一級頁表相當于數(shù)組訪問。

二級頁表通常是動態(tài)分配的,可以通過虛擬地址的中間8bit位L2索引訪問二級頁表,在L2索引中存放著最終物理地址的高20bit位,然后和虛擬地址的低12bit位就組成了最終的物理地址。以上就是虛擬地址轉換為物理地址的過程。

MMU訪問頁表是硬件實現(xiàn)的,但頁表的創(chuàng)建和填充需要Linux內(nèi)核來填充。通常,一級頁表和二級頁表存放在主存儲器中。

image.png

ARM32 一級頁表的頁表項

下面這張圖來自ARMV7的手冊。

image.png

一級頁表項這里有三種情況:一種是無效的,第二種是一級頁表的表項。第三種是段映射的頁表項。

bit 0 ~ bit 1:用來表示這個頁表項是一級頁表還是段映射的表項。

PXN:PL1 表示是否可以執(zhí)行這段代碼,為0表示可執(zhí)行,1表示不可執(zhí)行。

NS:none-security bit,用于安全擴展。

Domain:Domain域,指明所屬的域,Linux中只使用了3個域。

bit31:bit10:指向二級頁表基地址。

二級頁表的表項

image.png

bit0:禁止執(zhí)行標志。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] :訪問權限

S:是否可共享

nG:用于TLB

ARM64 頁表

ARM體系結構從ARMV8-A開始就支持64bit位,最大支持48根地址線。那為什么不支持64根地址線呢?主要原因是48根地址線時已支持最大訪問空間為256TB(內(nèi)核空間和用戶空間分別256TB)滿足了大部分應用的需求。而且,64根地址線時,芯片的設計復雜度會急劇增加。ARMV8-A架構中,支持4KB,16KB和64KB的頁,支持3級或者4級映射。

下面我們以4KB大小頁+4級映射介紹下虛擬地址到物理地址的映射過程。

image.png

0~11 :頁索引

bit 63 :頁表基地址選擇位,ARMV8架構中有2兩個頁表基地址,一個用于用戶空間,一個用戶內(nèi)核空間。

39~47:L0索引

30~38:L1索引

21~29:L2索引

12~20:L3 索引

假設頁表基地址為TTBRx,訪問頁表基地址就能訪問到L0頁表的基地址,可以使用L0索引的值作為offset去訪問L0頁表。

L0的頁表項包含了下一級L1頁表的基地址,同樣的,可以使用L1索引的值作為offset去訪問L2頁表。以此類推。

最后通過L3的頁表項可以得到物理地址的bit12 ~ 47位,這個時候再將虛擬地址的頁索引位對應到物理地址的0~11就是完整的物理地址。

Linux內(nèi)核關于頁表的函數(shù)

Linux內(nèi)核中頁表操作的宏定義

Linux內(nèi)核中封裝了很多宏來處理頁表

#definepgd_offset_k(addr)pgd_offset(&init_mm,addr)//由虛擬地址來獲取內(nèi)核頁表的PGD頁表的相應的頁表項
#definepgd_offset(mm,addr)((mm)->pgd+pgd_index(addr))//由虛擬地址來獲取用戶進程的頁表中相應的PGD表項
pgd_index(addr)//由虛擬地址找到PGD頁表的索引
pte_index(addr)//由虛擬地址找到PT頁表的索引
pte_offset_kernel(pmd,addr)//查找內(nèi)核頁表中對應的PT頁表的表項

判斷頁表項的狀態(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)建的相應的頁表項
pte_mkdirty()//設置dirty標志位
pte_mkold()//清除Accessed標志位
pte_mkclean()//清除dirty標志位
pte_mkwrite()//設置讀寫標志位
pte_wrprotect()//清除讀寫標志位
pte_mkyoung()//設置Accessed標志位
set_pte_at()//設置頁表項到硬件中

例子1 內(nèi)核頁表的映射

前面我們介紹了很多關于內(nèi)核的宏,函數(shù),下面我們通過實際的例子學習如何使用這些宏

系統(tǒng)初始化時需要把kernel image區(qū)域和線性映射區(qū)建立頁表映射,這個時候依次調(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);
}

首先會檢查映射的虛擬地址是否在內(nèi)核向量表的基址以上,并且小于用戶空間的TASK_SIZE。TASK_SIZE通常被定義為0xC0000000(3GB),表示用戶空間的虛擬地址范圍從0到3GB。對于64位體系結構,TASK_SIZE通常被定義為0x00007fffffffffff(128TB)。

接著會檢查映射的類型是否為設備類型,并且虛擬地址在頁偏移以上且低于FIXADDR_START,且不在VMALLOC_START和VMALLOC_END之間(即不在vmalloc空間中)。

最后會調(diào)用__create_mapping函數(shù)創(chuàng)建映射。傳入初始內(nèi)存管理結構體init_mm、映射描述結構體md、早期內(nèi)存分配函數(shù)early_alloc,以及false標志。

/*
*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ù)給定的映射描述結構體,將虛擬地址與物理地址進行映射。

(1) 系統(tǒng)沒有啟用ARM LPAE(Large Physical Address Extension),并且物理頁幀號大于等于0x100000,調(diào)用create_36bit_mapping函數(shù)進行處理,然后返回。

在早期階段,地址總線也是32位的,即4G的內(nèi)存地址空間。隨著應用程序越來越豐富,占用的內(nèi)存總量很容易就超過了4G。但由于編程模型和地址總線的限制,是無法使用超過4G的物理地址的。所以PAE/LPAE這種大內(nèi)存地址方案應運而生。

PAE/LAPE方案其它很簡單,編程視角依然還是32位(4G)的地址空間,這層是虛擬地址空間。而計算機地址總線卻使用超過32位的,比如X86的就使用36位(64G)的地址總線,ARM使用的是48位(64G)的地址總線。中間是通過保護模式(X86架構)或者MMU機制(ARM架構)提供的分頁技術(paging)實現(xiàn)32位虛擬地址訪問超過4G的物理內(nèi)存空間。這項技術的關鍵是分頁技術中的頁表項使用超過4字節(jié)的映射表 (ARM在LPAE模式下,頁表項是8字節(jié)),因為使用超過4字節(jié)映射表,就可以指示超過4G的內(nèi)存空間。

(2) 獲取虛擬地址的起始地址,因為地址映射的最小單位是page,因此這里進行mapping的虛擬地址需要對齊到page size,同樣的,長度也需要對齊到page size。

(3) 首先檢查映射類型的prot_l1字段是否為0。prot_l1表示第一級頁表(Level 1 Page Table)的保護位。如果prot_l1為0,表示無法使用頁面進行映射。如果地址、物理地址和長度與SECTION_MASK存在非零位,表示頁面映射要求地址和長度并未按頁面大小對齊。

(4)設置了頁全局目錄(pgd)的初始偏移,并將結束地址(end)設置為起始地址(addr)加上長度(length)。

(5)然后,使用pgd_addr_end函數(shù)計算下一個地址(next),該地址是當前地址和結束地址之間的較小值。

(6)調(diào)用alloc_init_p4d函數(shù),為當前范圍內(nèi)的地址分配一個頁目錄項,初始化它的頁表,并進行映射。該函數(shù)使用給定的參數(shù)pgd、addr、next、phys、type、alloc和ng來執(zhí)行這些操作。

(7)更新phys的值,使其加上當前范圍內(nèi)映射的頁面數(shù),以便正確分配下一個范圍的地址。最后,在循環(huán)的末尾,遞增pgd的值,并檢查是否達到了結束地址。如果沒有達到,繼續(xù)循環(huán)處理下一個地址范圍。

例子2 進程頁表的映射

remap_pfn_range函數(shù)對于寫過Linux驅(qū)動的人都不陌生,很多驅(qū)動程序的mmap函數(shù)都會調(diào)用到該函數(shù),該函數(shù)實現(xiàn)了物理空間到用戶進程的映射。

比如我們在用戶空間讀寫SOC的寄存器時,ARM中的寄存器通常都是memory map形式的,在用戶空間都要讀寫ARM空間的寄存器,通常都要操作/dev/mem設備來實現(xiàn),最后都會調(diào)用到remap_pfn_range來實現(xiàn)。

VMA:準備要映射的進程地址空間的VMA的數(shù)據(jù)結構

addr:要映射到 用戶空間的起始地址

pfn:準備要映射的物理內(nèi)存的頁幀號

size:表示要映射的大小

prot:表示要映射的屬性

接下來我們從頁表的角度看下函數(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獲取當前進程的mm_struct結構
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;//設置vm_flags,remap_pfn_range直接使用物理內(nèi)存。Linux內(nèi)核對物理頁面分為兩類:normalmapping,specialmapping。specialmapping就是內(nèi)核不希望該頁面參與到內(nèi)核的頁面回收等活動中。

BUG_ON(addr>=end);
pfn-=addr>>PAGE_SHIFT;
pgd=pgd_offset(mm,addr);//找到頁表項
flush_cache_range(vma,addr,end);
//以PGD_SIZE為步長遍歷頁表
do{
next=pgd_addr_end(addr,end);//獲取下一個PGD頁表項的管轄的地址范圍的起始地址
err=remap_p4d_range(mm,pgd,addr,next,
pfn+(addr>>PAGE_SHIFT),prot);//繼續(xù)遍歷下一級頁表
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頁表項。對于二級頁表來說,PUD指向PGD
if(!pud)
return-ENOMEM;
//以PUD_SIZE為步長遍歷頁表
do{
next=pud_addr_end(addr,end);//獲取下一個PUD頁表項的管轄的地址范圍的起始地址
err=remap_pmd_range(mm,pud,addr,next,
pfn+(addr>>PAGE_SHIFT),prot);//繼續(xù)遍歷下一級頁表
if(err)
returnerr;
}while(pud++,addr=next,addr!=end);
return0;
}

Linux內(nèi)核中實現(xiàn)了4級頁表,對于ARM32來說,它是如何跳過中間兩級頁表的呢?大家可以看下以下兩個宏的實現(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

接收指向頁上級目錄項的指針 pud 和線性地址 addr 作為參數(shù)。這個宏產(chǎn)生目錄項 addr 在頁中間目錄中的偏移地址。在兩級或三級分頁系統(tǒng)中,它產(chǎn)生 pud ,即頁全局目錄項的地址。

#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ù)為指向頁全局目錄項的指針 pgd 和線性地址 addr 。這個宏產(chǎn)生頁上級目錄中目錄項 addr 對應的線性地址。在兩級或三級分頁系統(tǒng)中,該宏產(chǎn)生 pgd ,即一個頁全局目錄項的地址。

遍歷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);//找到對應的pmd頁表項,對于二級頁表來說,pmd指向pud
if(!pmd)
return-ENOMEM;
//以PMD_SIZE為步長遍歷頁表
do{
next=pmd_addr_end(addr,end);//獲取下一個PMD頁表項的管轄的地址范圍的起始地址

if(ioremap_try_huge_pmd(pmd,addr,next,phys_addr,prot)){
*mask|=PGTBL_PMD_MODIFIED;
continue;
}
//繼續(xù)遍歷下一級頁表
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);//尋找相應的pte頁表項。注意這里需要申請一個spinlock鎖用來保護修改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()判斷這個pte是否存在
*pfn_pte()由頁幀號pfn得到pte
*pte_mkspecial()設置軟件的PTE_SPECIAL標志位(三級頁表才會用該標志位)
*set_pte_at()把pte設置到硬件頁表中
*/
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頁表設置完成后,需要把spinlock釋放
returnerr;
}

缺頁中斷do_anonymous_page

在缺頁中斷處理中,匿名頁面的觸發(fā)條件為下面的兩個條件,當滿足這兩個條件的時候就會調(diào)用do_anonymous_page函數(shù)來處理匿名映射缺頁異常,代碼實現(xiàn)在mm/memory.c文件中

發(fā)生缺頁的地址所在頁表項不存在

是匿名頁,即是vma->vm_ops為空,即vm_operations函數(shù)指針為空

我們知道在進程的task_struct結構中包含了一個mm_struct結構的指針,mm_struct用來描述一個進程的虛擬地址空間。進程的 mm_struct 則包含裝入的可執(zhí)行映像信息以及進程的頁目錄指針pgd。該結構還包含有指向 ~vm_area_struct ~結構的幾個指針,每個 vm_area_struct 代表進程的一個虛擬地址區(qū)間。vm_area_struct 結構含有指向vm_operations_struct 結構的一個指針,vm_operations_struct 描述了在這個區(qū)間的操作。vm_operations 結構中包含的是函數(shù)指針;其中,open、close 分別用于虛擬區(qū)間的打開、關閉,而nopage 用于當虛存頁面不在物理內(nèi)存而引起的“缺頁異常”時所應該調(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方式在其他進程申請過物理內(nèi)存,vma應該存在對應物理內(nèi)存映射,不應該再發(fā)生page fault

調(diào)用pte_alloc函數(shù)來為頁面表表項(PTE)分配內(nèi)存,并傳遞vma->vm_mm和vmf->pmd作為參數(shù)

如果頁面錯誤不是寫操作且內(nèi)存管理子系統(tǒng)允許使用零頁,則映射到零頁面

生成一個特殊頁表項,映射到專有的0頁,一頁大小

據(jù)pmd,address找到pte表對應的一個表項,并且lock住

如果頁表項不為空,則調(diào)用update_mmu_tlb函數(shù)更新內(nèi)存管理單元(MMU)的轉換查找緩沖(TLB)并且跳unlock。

檢查地址空間的穩(wěn)定性。

如果發(fā)現(xiàn)userfaultfd缺失,則解除映射并解鎖頁面表項(PTE)

對vma進行預處理,主要是創(chuàng)建anon_vma和anon_vma_chain,為后續(xù)反向映射做準備

從高端內(nèi)存區(qū)的伙伴系統(tǒng)中獲取一個頁,這個頁會清0

申請內(nèi)存成功之后,將新申請的page加入到mcgroup管理

設置此頁的PG_uptodate標志,表示此頁是最新

將頁面和頁面保護位(vma->vm_page_prot)組合成一個 PTE 條目。

如果vma區(qū)是可寫的,則給頁表項添加允許寫標志。將 PTE 條目的 Dirty 位和 Young 位設置為1。

鎖定 pte 條目,防止同時更新和更多虛擬內(nèi)存對物理內(nèi)存映射

pte條目存在的話,讓mmu更新頁表項,應該會清除tlb

檢查給定的內(nèi)存是否從用戶拷貝過來的。如果從用戶拷貝過來的內(nèi)存不穩(wěn)定,不用處理。

增加mm_struct中匿名頁的統(tǒng)計計數(shù)

對這個新頁進行反向映射,主要工作是:設置此頁的_mapcount = 0,說明此頁正在使用,但是是非共享的(>0是共享)。設置page->mapping最低位為1,page->mapping指向此vma->anon_vma,page->index存放此page在vma中的第幾頁。

通過判斷,將頁加入到活動lru緩存或者不能換出頁的lru鏈表

將上面配置好的頁表項寫入頁表

更新mmu的cache

do_anonymous_page首先判斷一下匿名頁是否是共享的,如果是共享的匿名映射,但是虛擬內(nèi)存區(qū)域沒有提供虛擬內(nèi)存操作集合就返回錯誤;然后判斷一下pte頁表是否存在,如果直接頁表不存在,那么分配頁表;

接下來判讀缺頁異常是由讀操作觸發(fā)的還是寫操作觸發(fā)的,如果是讀操作觸發(fā)的,生成特殊的頁表項,映射到專用的零頁,設置頁表項后返回;如果是寫操作觸發(fā)的,需要初始化vma中的anon_vma_chain和anon_vma,分配物理頁用于匿名映射,調(diào)用mk_pte函數(shù)生成頁表項,設置頁表項的臟標志位和寫權限,設置頁表項后返回。

小結

從以上的分析中,我們可以學習到關于常用的頁表的宏的使用方法。Linux內(nèi)核就是這樣,你不光可以看到某個函數(shù)的實現(xiàn),還可以看到某個函數(shù)的調(diào)用過程。所以,大家對某個函數(shù)有疑問的時候,可以順著這樣的思路去學習。

ARM32頁表和Linux頁表那些奇葩的地方

ARM32硬件頁表中PGD頁目錄項PGD是從20位開始的,但是為何頭文件定義是從21位開始?

歷史原因:Linux最初是基于x86的體系結構設計的,因此Linux內(nèi)核很多的頭文件的定義都是基于x86的,特別是關于PTE頁表項里面的很多比特位的定義。因此ARM在移植到Linux時只能參考x86版本的Linux內(nèi)核的實現(xiàn)。

X86的PGD是從bit22 ~ bit31,總共10bit位,1024頁表項。PT頁表從bit12 ~ bit 21 ,總共 10 bit位,1024頁表項。

ARM的PGD是從bit20 ~ bit31,總共12bit, 4096頁表項。PT域從bit12 ~ bit 19,總共8bit,2556頁表項。

X86和ARM頁表最大的差異在于PTE頁表內(nèi)容的不同。

5475ebde-27a8-11ef-91d2-92fbcf53809c.png

Linux內(nèi)核版本的PTE比特位的定義

/*
*"Linux"PTEdefinitionsforLPAE.
*
*Thesebitsoverlapwiththehardwarebitsbutthenamingispreservedfor
*consistencywiththeclassicpagetableformat.
*/
#defineL_PTE_VALID(_AT(pteval_t,1)<

ARM32的PTE比特位的定義

/*
*-extendedsmallpage/tinypage
*/
#definePTE_EXT_XN(_AT(pteval_t,1)<

那X86和ARM的頁表差距這么大,軟件怎么設計呢?Linux內(nèi)核的內(nèi)存管理已經(jīng)適配了X86的頁表項,我們可以通過軟件適配的辦法來解決這個問題。因此,ARM公司在移植該方案時提出了兩套頁表的方案。一套頁表是為了迎合ARM硬件的真實頁表,另一套頁表是為了迎合Linux真實的頁表。

對于PTE頁表來說,一下子就多出了一套頁表,一套頁表256表項,每個表項占用4字節(jié)。為了軟件實現(xiàn)的方便,軟件會把兩個頁表合并成一個頁表。4套頁表正好占用256 * 4 * 4 = 4K的空間。因此,Linux實現(xiàn)的時候,就分配了一個page 來存放這些頁表。

這一套方案的話,相當于每個PGD頁表項有8字節(jié),包含指向兩套PTE頁表項的entry。每4個字節(jié)指向一個物理的二級頁表。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)核
    +關注

    關注

    3

    文章

    1378

    瀏覽量

    40345
  • Linux
    +關注

    關注

    87

    文章

    11329

    瀏覽量

    209969
  • 內(nèi)存管理

    關注

    0

    文章

    168

    瀏覽量

    14162

原文標題:【內(nèi)存管理】頁表映射基礎知識

文章出處:【微信號:嵌入式與Linux那些事,微信公眾號:嵌入式與Linux那些事】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    詳解Linux系統(tǒng)文件目錄和Linux系統(tǒng)頁表結構

    :是一種特殊的數(shù)據(jù)結構,記錄著頁面和框的對應關系。(映射表) 的作用:是內(nèi)存非連續(xù)分
    的頭像 發(fā)表于 05-11 09:22 ?5263次閱讀
    詳解<b class='flag-5'>Linux</b>系統(tǒng)文件<b class='flag-5'>頁</b><b class='flag-5'>表</b>目錄和<b class='flag-5'>Linux</b>系統(tǒng)<b class='flag-5'>頁表</b>結構

    Linux內(nèi)核之內(nèi)存映射原理分析

    Linux 內(nèi)核采用延遲分配物理內(nèi)存的策略,在進程第一次訪問虛擬的時候,產(chǎn)生缺頁異常。如果是文件映射,那么分配物理,把文件指定區(qū)間的數(shù)據(jù)
    發(fā)表于 07-21 17:06 ?2370次閱讀

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

    的數(shù)據(jù)可能不在內(nèi)存Linux內(nèi)核地址映射模型 x86 CPU采用了段式地址映射模型。進程
    發(fā)表于 05-08 10:33 ?3474次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>地址<b class='flag-5'>映射</b>模型與<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>高端內(nèi)存詳解

    linux系統(tǒng)內(nèi)核ioremap映射分析

    linux系統(tǒng)內(nèi)核ioremap映射分析 幾乎每一種外設都是通過讀寫設備上的寄存器來進行的,通常包括控制寄存器、狀態(tài)寄存器和數(shù)據(jù)寄存器三大類,外設的寄存器通常被連續(xù)地編址。根據(jù)CPU
    發(fā)表于 08-05 09:49

    arm內(nèi)核基礎知識介紹

    arm內(nèi)核基礎知識介紹,有需要的朋友下來看看。
    發(fā)表于 01-08 14:44 ?12次下載

    linux Android基礎知識總結

    linux Android基礎知識總結
    發(fā)表于 10-24 09:00 ?6次下載
    <b class='flag-5'>linux</b> Android<b class='flag-5'>基礎知識</b>總結

    Linux設備驅(qū)動程序基礎知識的了解

    了解Linux設備驅(qū)動程序的基礎知識,重點關注設備節(jié)點,內(nèi)核框架,虛擬文件??系統(tǒng)和內(nèi)核模塊。 提出了一個簡單的內(nèi)核模塊實現(xiàn)。
    的頭像 發(fā)表于 11-26 06:51 ?3129次閱讀

    Linux驅(qū)動編程基礎知識講解

    由于Linux驅(qū)動編程的本質(zhì)屬于Linux內(nèi)核編程,因此我們非常有必要熟悉Linux內(nèi)核以及Linux
    的頭像 發(fā)表于 03-01 08:27 ?3868次閱讀

    Linux內(nèi)核反向映射基礎知識詳解

    和工作深入理解內(nèi)存管理,進程調(diào)度,文件系統(tǒng),設備驅(qū)動等內(nèi)核子系統(tǒng)。 為了系統(tǒng)的安全性,Linux內(nèi)核將各個用戶進程運行在各自獨立的虛擬地址空間,用戶進程之間通過虛擬地址空間相互隔離,
    的頭像 發(fā)表于 11-26 14:42 ?2120次閱讀

    ARM64 Linux內(nèi)核的塊映射

    內(nèi)核文檔Documentation/arm64/memory.rst描述了ARM64 Linux內(nèi)核空間的內(nèi)存映射情況,應該是此方面最權威文檔。 以典型的4K
    的頭像 發(fā)表于 01-04 13:37 ?2624次閱讀
    ARM64 <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>頁</b><b class='flag-5'>表</b>的塊<b class='flag-5'>映射</b>

    解析Linux內(nèi)核管理那些鮮為人知的秘密

    虛擬內(nèi)存管理,而管理是在虛擬內(nèi)存管理尤為重要,本文主要以回答幾個管理關鍵性問題來解析
    的頭像 發(fā)表于 06-11 16:32 ?1681次閱讀

    Linux用戶態(tài)開發(fā)驅(qū)動教程及基礎知識

    Linux用戶態(tài)開發(fā)驅(qū)動教程及基礎知識
    發(fā)表于 07-14 10:06 ?7次下載

    Linux內(nèi)核搶占相關的基礎知識

    今天要分享的是搶占相關的基礎知識。本文以內(nèi)核搶占為引子,概述一下 Linux 搶占的圖景。我盡量避開細節(jié)問題和源碼分析。 什么是內(nèi)核搶占? 別急,咱們慢慢來。 先理解搶占 (preem
    的頭像 發(fā)表于 11-09 16:48 ?2065次閱讀

    Linux內(nèi)核pwn基礎知識

    Linux內(nèi)核pwn之基礎rop提權 1. linux kernel pwn kernel 也是一個程序,用來管理軟件發(fā)出的數(shù)據(jù) I/O 要求,將這些要求轉義為指令,交給 CPU 和計算機
    的頭像 發(fā)表于 02-01 17:53 ?1810次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>pwn<b class='flag-5'>基礎知識</b>

    MMU多級映射過程

    空間,也有相應的負責虛擬地址到物理地址之間的轉換。MMU查詢的過程,用戶進程的一級的基址存放在TTBR0。操作系統(tǒng)的
    的頭像 發(fā)表于 11-26 16:28 ?993次閱讀
    MMU多級<b class='flag-5'>頁</b><b class='flag-5'>表</b><b class='flag-5'>映射</b>過程