您的位置 首页 电路

ARM Linux (S3C6410架构/2.6.35内核)的内存映射(二)

本文讲述Linux系统启动过程中内核空间的映射。Linux系统内核启动过程中,会在start_kernel()->setup_arch()->paging_init()函数中建…

本文叙述Linux体系发动进程中内核空间的映射。

Linux体系内核发动进程中,会在start_kernel() ->setup_arch() -> paging_init()函数中树立页表,下面具体记载一下其间每一个重要的进程。(下面演示的代码通过删减)

先看函数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] 

至于为设备内存做二级映射的进程,我将另写一篇做具体记载,由于内容比较多。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/fangan/dianlu/277025.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部