ARM Linux (S3C6410架構/2.6.35內核)的內存映射(二)
Linux系統內核啟動(dòng)過(guò)程中,會(huì )在start_kernel() ->
先看函數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()的作用是清空內核頁(yè)表。對于我的配置來(lái)說(shuō),前兩個(gè)for循環(huán)可以合并為一個(gè),它們的作用是清空地址區間[0x00000000, 0xC0000000)的內存映射;第三個(gè)for循環(huán)有些不一樣,它所清空的區間與前面是不連續的,它從bank0的末尾開(kāi)始,直到VMALLOC結束。為什么要把bank0讓出來(lái)呢?因為bank0是內核正在運行的空間,這段區域已經(jīng)在head.S中的匯編代碼里映射好了,如果在這里一并清空的話(huà),內核就沒(méi)法運行了。
有一個(gè)地方我一直不太理解,就是PGDIR_SIZE的定義,在這個(gè)版本的內核里,這個(gè)值被定義為:
[c]#define PGDIR_SHIFT 21#define PGDIR_SIZE (1UL << PGDIR_SHIFT)[/c]
就是說(shuō),PGDIR_SIZE被定義為2M,那么為什么不能定義成1M呢?1M正好是一個(gè)section,這樣不是正好容易理解嗎?而且如果這樣定義的話(huà),pmd方面的處理也會(huì )比較麻煩一些(在這里PMD其實(shí)就是PGD)。比如在pmd_clear()中,每次都需要設置兩項:
[c]#define pmd_clear(pmdp) do { pmdp[0] = __pmd(0); pmdp[1] = __pmd(0); clean_pmd_entry(pmdp); } while (0)[/c]
接下來(lái)的一個(gè)重要函數是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()是為低端物理內存建立映射,在我的模擬環(huán)境中,物理內存只有一個(gè)bank,共有16M。alloc_init_section()為每一個(gè)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)得到滿(mǎn)足,這一段是專(zhuān)門(mén)為段式映射而準備的。在接下來(lái)的do循環(huán)中,連續兩個(gè)PMD/PGD表項會(huì )被寫(xiě)入新的內容,以我的系統為例,寫(xiě)入的第一個(gè)表項內存是:
pmd = 0xc0007000, *pmd = 0x5000040e, phys = 0x50000000
即把物理地址0x50000000映射到虛擬地址0xc0000000,PMD/PGD表項的位置是在0xc0007000,寫(xiě)入的內容是0x5000040e,其中高12位是段的基地址(物理地址),而低20位0x40e是段的屬性。
如果條件if (((addr | end | phys) & ~SECTION_MASK) == 0)不滿(mǎn)足的話(huà),函數alloc_init_section()的另外一半代碼是為什么設計的呢?
答案是這段代碼用于設備內存的映射。
接下來(lái),內核要為設備內存建立映射,在paging_init()->devicemaps_init()->create_mapping()->alloc_init_section()->alloc_init_pte()這個(gè)調用棧中,就將用到alloc_init_section()的另外一半代碼。與用于存儲數據的一般內存不同,這里所說(shuō)的設備內存往往是為訪(fǎng)問(wèn)設備用的特定地址或者用于特定功能的小段內存(比如中斷向量表所占用的內存),而且各塊設備內存在物理上可能并不連續,如果使用段為單位來(lái)做映射的話(huà),就會(huì )浪費很多虛擬地址空間,所以設備內存使用頁(yè)式映射,即二級映射。
以“中斷向量表”的映射為例,在下面這段代碼中,內核使用boot memory manager為中斷向量表申請一頁(yè)(4K)內存,并將這頁(yè)內存映射到虛擬地址的0xffff0000處。對于中斷向量表的位置,ARM為操作系統提供了兩個(gè)選項,可以把它配置到內存的最低地址0x00000000處,也可以把它配置到到地址0xffff0000處,這里所說(shuō)的地址都是虛擬地址,即經(jīng)過(guò)MMU映射過(guò)后的地址。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]
至于為設備內存做二級映射的過(guò)程,我將另寫(xiě)一篇做詳細記錄,因為內容比較多。
評論