ARM Linux (S3C6410架構(gòu)/2.6.35內(nèi)核)的內(nèi)存映射(三)
在S3C6410架構(gòu)下,Linux采用的是粗粒度小頁內(nèi)存管理方式,即內(nèi)存段(section)的大小為1M,而頁(page)的大小為4K。在第一級內(nèi)存映射中,每一個PGD項覆蓋1M的內(nèi)存區(qū)域;如果有二級內(nèi)存映射的話,每一個PTE項覆蓋4K的內(nèi)存區(qū)域。
本文引用地址:http://www.ex-cimer.com/article/201611/317853.htm下面我們來看一下二級內(nèi)存映射表的設(shè)計。如果段的大小是1M而頁的大小是4K的話,那么每一張二級映射表即頁表中就需要有1M/4K=256個表項。而不論是PGD還是PTE,每一個表項的大小是4字節(jié),即一個長整形數(shù)的大小。一張頁表的大小為256*4=1024/1K字節(jié),所以,頁表的大小與頁的大小并不能對并,一張4K大小的內(nèi)存頁可以存得下4張這樣的頁表。Linux采用了這樣一種設(shè)計來存放頁表:(文件arch/arm/include/asm/pgalloc.h)
在一張4K大小的內(nèi)存頁中,存放了4張不同的頁表,它們依次是:第一張頁表的ARM版本(也被叫做硬件版本),第二張(與第一張表的虛擬空間是連續(xù)的)頁表的ARM版本,第一張頁表的內(nèi)核版本(也被叫做Linux版本),第二張頁表的內(nèi)核版本。同一張表的內(nèi)核版本與ARM版本不是連續(xù)存放,而是間隔開的。
頁表為什么會有內(nèi)核版本和硬件版本的區(qū)分呢?因為內(nèi)核需要的一些信息(比如dirty、access等)在ARM需要的頁表信息中沒有,所以Linux需要另外一份滿足自己需要的映射表。
可能正是因為頁表大小(1K)與頁大小(4K)的不匹配,也造成了內(nèi)存映射計算方面的很多麻煩。直觀地來理解,既然每一個一級頁表項映射的內(nèi)存空間是1M,那么在代碼中一個一級頁表項pgd_t的大小就應(yīng)該定義為4字節(jié),PGDIR_SIZE應(yīng)該定義為1M,但事實不是這樣:
[c]#define PGDIR_SHIFT 21#define PGDIR_SIZE (1UL << PGDIR_SHIFT)typedef unsigned long pgd_t[2];[/c]
PGDIR_AIZE被定義為2M,而pgd_t被定義為8個字節(jié)。其實這兩個PGD仍然是互相獨立的,并沒有任何關(guān)聯(lián)。
這給理解和計算都帶來了麻煩,但唯一的一條好處就是更好地解決了頁表大小與頁大小不匹配的問題。因為每兩個相鄰的頁表是放在一起處理的,所以干脆把兩個相鄰的PGD也定義在一起,這樣當(dāng)其中的一個被映射時也要保證另一個得到映射。
下面看一個映射中斷向量表的實際過程,通過調(diào)用棧paging_init()->devicemaps_init()->create_mapping()->alloc_init_section()->alloc_init_pte(),最后到達(dá)了函數(shù)alloc_init_pte(),這段代碼包含了我的注釋和打?。ㄒ訹Michael]開關(guān)):
[c]static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,unsigned long end, unsigned long pfn,const struct mem_type *type){pte_t *pte;printk(MICHAEL_DBG "alloc_init_pte()n");if (pmd_none(*pmd)) {pte = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t));printk(MICHAEL_DBG "pmd is still blank, pte = 0x%xn", pte);printk(MICHAEL_DBG "will populate pmdn");__pmd_populate(pmd, __pa(pte) | type->prot_l1);}pte = pte_offset_kernel(pmd, addr);do {void *linux_pte = (void *)pte;void *hw_pte = linux_pte - 2048;printk(MICHAEL_DBG "pmd has been populated, pte = 0x%x, pfn = 0x%x, pfn_pte = 0x%xn", pte, pfn, pfn_pte(pfn, __pgprot(type->prot_pte)));printk(MICHAEL_DBG "before set_pte_ext(): hw_pte = 0x%x, *hw_pte = 0x%x, linux_pte = 0x%x, *linux_pte = 0x%xn", hw_pte, *((unsigned int*)hw_pte), linux_pte, *((unsigned int *)linux_pte));set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);printk(MICHAEL_DBG "after set_pte_ext(): hw_pte = 0x%x, *hw_pte = 0x%x, linux_pte = 0x%x, *linux_pte = 0x%xn", hw_pte, *((unsigned int*)hw_pte), linux_pte, *((unsigned int *)linux_pte));pfn ;} while (pte , addr = PAGE_SIZE, addr != end);}[/c]
先看前面一段(去掉了注釋和打印):
[c]if (pmd_none(*pmd)) {pte = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t));__pmd_populate(pmd, __pa(pte) | type->prot_l1);}[/c]
先說明一下,因為在S3C6410上,最多只支持內(nèi)存的二級映射即PGD->PTE->page,所以并不存在真正的PMD,即使當(dāng)它出現(xiàn)時,它也與PGD相同。
這段代碼檢查一級映射項PGD是不是空,如果是空的話就說明一級映射還沒有建立過,(二級)頁表不存在,所以就先通過boomem來申請一張頁面做為頁表,有了頁表就可以填充PGD了,填充PGD的代碼__pmd_populate()在《Arm-Linux二級頁表的問題》一篇中已經(jīng)講過,不再贅述。
這一段執(zhí)行完之后,頁表有了,但頁表還是空的,下面要給指定的表項填充內(nèi)容:
[c]pte = pte_offset_kernel(pmd, addr);do {set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);pfn ;} while (pte , addr = PAGE_SIZE, addr != end);}[/c]
addr是需要映射的頁面的物理地址,pte_offset_kernel(pmd, addr)計算出這個物理地址在頁表中對應(yīng)的位置,不過需要注意的是,這里計算出的pte值指的是這個頁面所對應(yīng)的頁表項在硬件頁表中的位置。接下來調(diào)用set_pte_ext(),這是一個依硬件而不同的函數(shù),比如在S3C6410上,它的實現(xiàn)是armv6_set_pte_ext(),是在arch/arm/mm/proc-macros.S文件中用匯編代碼實現(xiàn)的。set_pte_ext()的作用是同時填充硬件頁表和內(nèi)核頁表。
看一下在skyeye模擬器上運行這個內(nèi)核的log:
vectors = 0xc02aa000
init_mm.pgd = 0xc0004000, addr = 0xffff0000, pgd_index() = 0x7ff, PGDIR_SHIFT = 21
alloc_init_pte()
pmd is still blank, pte = 0xc02ab000
will populate pmd
__pmd_populate():
&pmdp[0] = 0xc0007ff8, pmdp[0] = 0x502ab021
&pmdp[1] = 0xc0007ffc, pmdp[1] = 0x502ab421
pmd has been populated, pte = 0xc02abfc0, pfn = 0x502aa, pfn_pte = 0x502aa34b
before set_pte_ext(): hw_pte = 0xc02ab7c0, *hw_pte = 0x0, linux_pte = 0xc02abfc0, *linux_pte = 0x0
after set_pte_ext(): hw_pte = 0xc02ab7c0, *hw_pte = 0x502aa02a, linux_pte = 0xc02abfc0, *linux_pte = 0x502aa34b
我們通過bootmem申請到的中斷向量表頁的位置是0xc02aa000,這已經(jīng)是一個虛擬地址,但我們需要把它重新映射到指定地址0xffff0000去。在alloc_init_pte()中,首先確認(rèn)PGD為空,于是申請一頁內(nèi)存做為頁表,得到的頁面是0xc02ab000,緊挨著中斷向量表那一樣。
接下來填充PGD。我們要映射的的目標(biāo)虛擬地址是0xffff0000,它在PGD表中的序號是0xffff0000/1M=0xfff,每個PGD占4字節(jié),而PGD表的開始位置是0xc0004000,所以0xffff0000所對應(yīng)的PGD的位置是0xc0004000 ((0xffff0000/1M) * 4) =
現(xiàn)在頁表有了,下面要做的就是填充指定的頁表項。目標(biāo)虛擬地址0xffff0000在Linux頁表中表項的地址是0xc02abfc0,這是由pte_offset_kernel(pmd, addr)計算出來的,然后調(diào)用set_pte_ext()寫入頁表項,匯編代碼的細(xì)節(jié)這里先不深究,只看寫入的內(nèi)容。最后兩行打印分別是調(diào)用set_pte_ext()前后硬件頁表和內(nèi)核頁表的內(nèi)容,可以看到兩張表里的內(nèi)容都已經(jīng)填好:
before set_pte_ext(): hw_pte = 0xc02ab7c0, *hw_pte = 0x0, linux_pte = 0xc02abfc0, *linux_pte = 0x0
after set_pte_ext(): hw_pte = 0xc02ab7c0, *hw_pte = 0x502aa02a, linux_pte = 0xc02abfc0, *linux_pte = 0x502aa34b
評論