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