新聞中心

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

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

        作者: 時間:2016-11-09 來源:網絡 收藏
        本文講述Linux系統啟動過程中內核空間的映射。

        Linux系統內核啟動過程中,會在start_kernel() ->setup_arch() -> paging_init()函數中建立頁表,下面詳細記錄一下其中每一個重要的步驟。(下面演示的代碼經過刪減)

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

        先看函數prepare_page_table()

        [c]static inline void prepare_page_table(void){unsigned long addr;for (addr = 0; addr < MODULES_VADDR; addr  = PGDIR_SIZE) {pmd_clear(pmd_off_k(addr));}for ( ; addr < PAGE_OFFSET; addr  = PGDIR_SIZE) {pmd_clear(pmd_off_k(addr));}for (addr = __phys_to_virt(bank_phys_end(&meminfo.bank[0]));addr < VMALLOC_END; addr  = PGDIR_SIZE) {pmd_clear(pmd_off_k(addr));}}[/c] 

        函數prepare_page_table()的作用是清空內核頁表。對于我的配置來說,前兩個for循環可以合并為一個,它們的作用是清空地址區間[0x00000000, 0xC0000000)的內存映射;第三個for循環有些不一樣,它所清空的區間與前面是不連續的,它從bank0的末尾開始,直到VMALLOC結束。為什么要把bank0讓出來呢?因為bank0是內核正在運行的空間,這段區域已經在head.S中的匯編代碼里映射好了,如果在這里一并清空的話,內核就沒法運行了。
        有一個地方我一直不太理解,就是PGDIR_SIZE的定義,在這個版本的內核里,這個值被定義為:

        [c]#define PGDIR_SHIFT 21#define PGDIR_SIZE (1UL << PGDIR_SHIFT)[/c] 

        就是說,PGDIR_SIZE被定義為2M,那么為什么不能定義成1M呢?1M正好是一個section,這樣不是正好容易理解嗎?而且如果這樣定義的話,pmd方面的處理也會比較麻煩一些(在這里PMD其實就是PGD)。比如在pmd_clear()中,每次都需要設置兩項:

        [c]#define pmd_clear(pmdp) do { pmdp[0] = __pmd(0); pmdp[1] = __pmd(0); clean_pmd_entry(pmdp); } while (0)[/c] 

        接下來的一個重要函數是map_lowmem() -> map_memory_bank() -> create_mapping()

        [c]static void __init create_mapping(struct map_desc *md){unsigned long phys, addr, length, end;const struct mem_type *type;pgd_t *pgd;......pgd = pgd_offset_k(addr);end = addr   length;do {unsigned long next = pgd_addr_end(addr, end);alloc_init_section(pgd, addr, next, phys, type);phys  = next - addr;addr = next;} while (pgd  , addr != end);}[/c] 

        map_lowmem()是為低端物理內存建立映射,在我的模擬環境中,物理內存只有一個bank,共有16M。alloc_init_section()為每一個PGD建立映射。

        [c]static void __init alloc_init_section(pgd_t *pgd, unsigned long addr,unsigned long end, unsigned long phys,const struct mem_type *type){pmd_t *pmd = pmd_offset(pgd, addr);if (((addr | end | phys) & ~SECTION_MASK) == 0) {pmd_t *p = pmd;if (addr & SECTION_SIZE)pmd  ;do {*pmd = __pmd(phys | type->prot_sect);phys  = SECTION_SIZE;} while (pmd  , addr  = SECTION_SIZE, addr != end);flush_pmd_entry(p);} else {alloc_init_pte(pmd, addr, end, __phys_to_pfn(phys), type);}}[/c] 

        對于low memory的情況,條件if (((addr | end | phys) & ~SECTION_MASK) == 0)得到滿足,這一段是專門為段式映射而準備的。在接下來的do循環中,連續兩個PMD/PGD表項會被寫入新的內容,以我的系統為例,寫入的第一個表項內存是:
        pmd = 0xc0007000, *pmd = 0x5000040e, phys = 0x50000000
        即把物理地址0x50000000映射到虛擬地址0xc0000000,PMD/PGD表項的位置是在0xc0007000,寫入的內容是0x5000040e,其中高12位是段的基地址(物理地址),而低20位0x40e是段的屬性。

        如果條件if (((addr | end | phys) & ~SECTION_MASK) == 0)不滿足的話,函數alloc_init_section()的另外一半代碼是為什么設計的呢?
        答案是這段代碼用于設備內存的映射。

        接下來,內核要為設備內存建立映射,在paging_init()->devicemaps_init()->create_mapping()->alloc_init_section()->alloc_init_pte()這個調用棧中,就將用到alloc_init_section()的另外一半代碼。與用于存儲數據的一般內存不同,這里所說的設備內存往往是為訪問設備用的特定地址或者用于特定功能的小段內存(比如中斷向量表所占用的內存),而且各塊設備內存在物理上可能并不連續,如果使用段為單位來做映射的話,就會浪費很多虛擬地址空間,所以設備內存使用頁式映射,即二級映射。

        以“中斷向量表”的映射為例,在下面這段代碼中,內核使用boot memory manager為中斷向量表申請一頁(4K)內存,并將這頁內存映射到虛擬地址的0xffff0000處。對于中斷向量表的位置,ARM為操作系統提供了兩個選項,可以把它配置到內存的最低地址0x00000000處,也可以把它配置到到地址0xffff0000處,這里所說的地址都是虛擬地址,即經過MMU映射過后的地址。Linux默認選擇后者,即高地址。

        [c]static void __init devicemaps_init(struct machine_desc *mdesc) {......vectors = alloc_bootmem_low_pages(PAGE_SIZE);......map.pfn = __phys_to_pfn(virt_to_phys(vectors));map.virtual = 0xffff0000;map.length = PAGE_SIZE;map.type = MT_HIGH_VECTORS;create_mapping(&map);[/c] 

        至于為設備內存做二級映射的過程,我將另寫一篇做詳細記錄,因為內容比較多。



        評論


        技術專區

        關閉
        主站蜘蛛池模板: 郑州市| 二连浩特市| 龙陵县| 尚志市| 张家港市| 青海省| 察隅县| 阿鲁科尔沁旗| 乌兰察布市| 广汉市| 襄垣县| 连江县| 怀集县| 平塘县| 扎囊县| 新平| 同德县| 丹凤县| 北辰区| 青铜峡市| 岳普湖县| 奉贤区| 赣榆县| 芮城县| 久治县| 临沂市| 靖江市| 基隆市| 景德镇市| 建湖县| 大理市| 休宁县| 龙里县| 泗水县| 甘谷县| 鄂伦春自治旗| 勐海县| 嘉峪关市| 武邑县| 石狮市| 兴国县|