新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > ARM Linux (S3C6410架構/2.6.35內核)的內存映射(三)

        ARM Linux (S3C6410架構/2.6.35內核)的內存映射(三)

        作者: 時間:2016-11-09 來源:網絡 收藏
        這里記錄一下Linux內核做二級內存映射的過程,以中斷向量表的映射過程為例。

        S3C6410架構下,Linux采用的是粗粒度小頁內存管理方式,即內存段(section)的大小為1M,而頁(page)的大小為4K。在第一級內存映射中,每一個PGD項覆蓋1M的內存區域;如果有二級內存映射的話,每一個PTE項覆蓋4K的內存區域。

        本文引用地址:http://www.104case.com/article/201611/317853.htm

        下面我們來看一下二級內存映射表的設計。如果段的大小是1M而頁的大小是4K的話,那么每一張二級映射表即頁表中就需要有1M/4K=256個表項。而不論是PGD還是PTE,每一個表項的大小是4字節,即一個長整形數的大小。一張頁表的大小為256*4=1024/1K字節,所以,頁表的大小與頁的大小并不能對并,一張4K大小的內存頁可以存得下4張這樣的頁表。Linux采用了這樣一種設計來存放頁表:(文件arch/arm/include/asm/pgalloc.h)

        在一張4K大小的內存頁中,存放了4張不同的頁表,它們依次是:第一張頁表的ARM版本(也被叫做硬件版本),第二張(與第一張表的虛擬空間是連續的)頁表的ARM版本,第一張頁表的內核版本(也被叫做Linux版本),第二張頁表的內核版本。同一張表的內核版本與ARM版本不是連續存放,而是間隔開的。

        頁表為什么會有內核版本和硬件版本的區分呢?因為內核需要的一些信息(比如dirty、access等)在ARM需要的頁表信息中沒有,所以Linux需要另外一份滿足自己需要的映射表。

        可能正是因為頁表大小(1K)與頁大小(4K)的不匹配,也造成了內存映射計算方面的很多麻煩。直觀地來理解,既然每一個一級頁表項映射的內存空間是1M,那么在代碼中一個一級頁表項pgd_t的大小就應該定義為4字節,PGDIR_SIZE應該定義為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個字節。其實這兩個PGD仍然是互相獨立的,并沒有任何關聯。
        這給理解和計算都帶來了麻煩,但唯一的一條好處就是更好地解決了頁表大小與頁大小不匹配的問題。因為每兩個相鄰的頁表是放在一起處理的,所以干脆把兩個相鄰的PGD也定義在一起,這樣當其中的一個被映射時也要保證另一個得到映射。
        下面看一個映射中斷向量表的實際過程,通過調用棧paging_init()->devicemaps_init()->create_mapping()->alloc_init_section()->alloc_init_pte(),最后到達了函數alloc_init_pte(),這段代碼包含了我的注釋和打?。ㄒ訹Michael]開關):

        [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上,最多只支持內存的二級映射即PGD->PTE->page,所以并不存在真正的PMD,即使當它出現時,它也與PGD相同。
        這段代碼檢查一級映射項PGD是不是空,如果是空的話就說明一級映射還沒有建立過,(二級)頁表不存在,所以就先通過boomem來申請一張頁面做為頁表,有了頁表就可以填充PGD了,填充PGD的代碼__pmd_populate()在《Arm-Linux二級頁表的問題》一篇中已經講過,不再贅述。

        這一段執行完之后,頁表有了,但頁表還是空的,下面要給指定的表項填充內容:

        [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)計算出這個物理地址在頁表中對應的位置,不過需要注意的是,這里計算出的pte值指的是這個頁面所對應的頁表項在硬件頁表中的位置。接下來調用set_pte_ext(),這是一個依硬件而不同的函數,比如在S3C6410上,它的實現是armv6_set_pte_ext(),是在arch/arm/mm/proc-macros.S文件中用匯編代碼實現的。set_pte_ext()的作用是同時填充硬件頁表和內核頁表。

        看一下在skyeye模擬器上運行這個內核的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,這已經是一個虛擬地址,但我們需要把它重新映射到指定地址0xffff0000去。在alloc_init_pte()中,首先確認PGD為空,于是申請一頁內存做為頁表,得到的頁面是0xc02ab000,緊挨著中斷向量表那一樣。

        接下來填充PGD。我們要映射的的目標虛擬地址是0xffff0000,它在PGD表中的序號是0xffff0000/1M=0xfff,每個PGD占4字節,而PGD表的開始位置是0xc0004000,所以0xffff0000所對應的PGD的位置是0xc0004000 ((0xffff0000/1M) * 4) =0xc0007ffc。它是相鄰兩個PGD中的第二個,前一個PGD在0xc0007ff8。真給PGD的內容呢,先看前一項0xc0007ff8,向這里填的內容是(頁表基地址 屬性),新得到的頁表地址為0xc02ab000,物理地址為0x502ab000,加上屬性值0x21,所以向0xc0007ff8填充的PGD內容為0x502ab021。那么相應地,相鄰的下一張頁表的基地址要加上256*4=1024/1K,即0x400,所以向一個PGD(0xc0007ffc)填充的內容為0x502ab021 0x400 =0x502ab421。

        現在頁表有了,下面要做的就是填充指定的頁表項。目標虛擬地址0xffff0000在Linux頁表中表項的地址是0xc02abfc0,這是由pte_offset_kernel(pmd, addr)計算出來的,然后調用set_pte_ext()寫入頁表項,匯編代碼的細節這里先不深究,只看寫入的內容。最后兩行打印分別是調用set_pte_ext()前后硬件頁表和內核頁表的內容,可以看到兩張表里的內容都已經填好:

        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



        評論


        技術專區

        關閉
        主站蜘蛛池模板: 东方市| 临猗县| 张家口市| 桓仁| 读书| 特克斯县| 咸阳市| 武汉市| 思茅市| 宝山区| 德保县| 故城县| 阿城市| 丹江口市| 板桥市| 石河子市| 福鼎市| 兰西县| 隆昌县| 平原县| 北碚区| 平江县| 怀化市| 保康县| 龙海市| 周宁县| 湾仔区| 岳池县| 中牟县| 高安市| 花莲县| 花垣县| 历史| 阳春市| 溧水县| 慈利县| 米脂县| 保德县| 常熟市| 邹城市| 疏勒县|