ARM Linux (S3C6410架構(gòu)/2.6.35內(nèi)核)的內(nèi)存映射(三)
在S3C6410架構(gòu)下,Linux采用的是粗粒度小頁內(nèi)存管理方式,即內(nèi)存段(section)的大小為1M,而頁(page)的大小為4K。在第一級(jí)內(nèi)存映射中,每一個(gè)PGD項(xiàng)覆蓋1M的內(nèi)存區(qū)域;如果有二級(jí)內(nèi)存映射的話,每一個(gè)PTE項(xiàng)覆蓋4K的內(nèi)存區(qū)域。
本文引用地址:http://m.butianyuan.cn/article/201611/317853.htm下面我們來看一下二級(jí)內(nèi)存映射表的設(shè)計(jì)。如果段的大小是1M而頁的大小是4K的話,那么每一張二級(jí)映射表即頁表中就需要有1M/4K=256個(gè)表項(xiàng)。而不論是PGD還是PTE,每一個(gè)表項(xiàng)的大小是4字節(jié),即一個(gè)長整形數(shù)的大小。一張頁表的大小為256*4=1024/1K字節(jié),所以,頁表的大小與頁的大小并不能對(duì)并,一張4K大小的內(nèi)存頁可以存得下4張這樣的頁表。Linux采用了這樣一種設(shè)計(jì)來存放頁表:(文件arch/arm/include/asm/pgalloc.h)
在一張4K大小的內(nèi)存頁中,存放了4張不同的頁表,它們依次是:第一張頁表的ARM版本(也被叫做硬件版本),第二張(與第一張表的虛擬空間是連續(xù)的)頁表的ARM版本,第一張頁表的內(nèi)核版本(也被叫做Linux版本),第二張頁表的內(nèi)核版本。同一張表的內(nèi)核版本與ARM版本不是連續(xù)存放,而是間隔開的。
頁表為什么會(huì)有內(nèi)核版本和硬件版本的區(qū)分呢?因?yàn)閮?nèi)核需要的一些信息(比如dirty、access等)在ARM需要的頁表信息中沒有,所以Linux需要另外一份滿足自己需要的映射表。
可能正是因?yàn)轫摫泶笮?1K)與頁大小(4K)的不匹配,也造成了內(nèi)存映射計(jì)算方面的很多麻煩。直觀地來理解,既然每一個(gè)一級(jí)頁表項(xiàng)映射的內(nèi)存空間是1M,那么在代碼中一個(gè)一級(jí)頁表項(xiàng)pgd_t的大小就應(yīng)該定義為4字節(jié),PGDIR_SIZE應(yīng)該定義為1M,但事實(shí)不是這樣:
[c]#define PGDIR_SHIFT 21#define PGDIR_SIZE (1UL << PGDIR_SHIFT)typedef unsigned long pgd_t[2];[/c]
PGDIR_AIZE被定義為2M,而pgd_t被定義為8個(gè)字節(jié)。其實(shí)這兩個(gè)PGD仍然是互相獨(dú)立的,并沒有任何關(guān)聯(lián)。
這給理解和計(jì)算都帶來了麻煩,但唯一的一條好處就是更好地解決了頁表大小與頁大小不匹配的問題。因?yàn)槊績蓚€(gè)相鄰的頁表是放在一起處理的,所以干脆把兩個(gè)相鄰的PGD也定義在一起,這樣當(dāng)其中的一個(gè)被映射時(shí)也要保證另一個(gè)得到映射。
下面看一個(gè)映射中斷向量表的實(shí)際過程,通過調(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]
先說明一下,因?yàn)樵赟3C6410上,最多只支持內(nèi)存的二級(jí)映射即PGD->PTE->page,所以并不存在真正的PMD,即使當(dāng)它出現(xiàn)時(shí),它也與PGD相同。
這段代碼檢查一級(jí)映射項(xiàng)PGD是不是空,如果是空的話就說明一級(jí)映射還沒有建立過,(二級(jí))頁表不存在,所以就先通過boomem來申請(qǐng)一張頁面做為頁表,有了頁表就可以填充PGD了,填充PGD的代碼__pmd_populate()在《Arm-Linux二級(jí)頁表的問題》一篇中已經(jīng)講過,不再贅述。
這一段執(zhí)行完之后,頁表有了,但頁表還是空的,下面要給指定的表項(xiàng)填充內(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)計(jì)算出這個(gè)物理地址在頁表中對(duì)應(yīng)的位置,不過需要注意的是,這里計(jì)算出的pte值指的是這個(gè)頁面所對(duì)應(yīng)的頁表項(xiàng)在硬件頁表中的位置。接下來調(diào)用set_pte_ext(),這是一個(gè)依硬件而不同的函數(shù),比如在S3C6410上,它的實(shí)現(xiàn)是armv6_set_pte_ext(),是在arch/arm/mm/proc-macros.S文件中用匯編代碼實(shí)現(xiàn)的。set_pte_ext()的作用是同時(shí)填充硬件頁表和內(nèi)核頁表。
看一下在skyeye模擬器上運(yùn)行這個(gè)內(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申請(qǐng)到的中斷向量表頁的位置是0xc02aa000,這已經(jīng)是一個(gè)虛擬地址,但我們需要把它重新映射到指定地址0xffff0000去。在alloc_init_pte()中,首先確認(rèn)PGD為空,于是申請(qǐng)一頁內(nèi)存做為頁表,得到的頁面是0xc02ab000,緊挨著中斷向量表那一樣。
接下來填充PGD。我們要映射的的目標(biāo)虛擬地址是0xffff0000,它在PGD表中的序號(hào)是0xffff0000/1M=0xfff,每個(gè)PGD占4字節(jié),而PGD表的開始位置是0xc0004000,所以0xffff0000所對(duì)應(yīng)的PGD的位置是0xc0004000 ((0xffff0000/1M) * 4) =
現(xiàn)在頁表有了,下面要做的就是填充指定的頁表項(xiàng)。目標(biāo)虛擬地址0xffff0000在Linux頁表中表項(xiàng)的地址是0xc02abfc0,這是由pte_offset_kernel(pmd, addr)計(jì)算出來的,然后調(diào)用set_pte_ext()寫入頁表項(xiàng),匯編代碼的細(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
評(píng)論