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