浅析armlinux-setup_arch()->create_mapping()函数5-2-2
文章来源:http://gliethttp.cublog.cn
建议首先参考《浅析armlinux2_4_19启动程序[head-armv.s文件]》[http://gliethttp.cublog.cn] //---------------------------------------- //1.arch/arm/mm/Mm-armv.c->create_mapping() static void __init create_mapping(struct map_desc *md) { unsigned long virt, length; int prot_sect, prot_pte; long off; if (md->prot_read && md->prot_write && !md->cacheable && !md->bufferable) { //提示设置不合理 printk(KERN_WARNING "Security risk: creating user " "accessible mapping for 0x%08lx at 0x%08lx\n", md->physical, md->virtual); } if (md->virtual != vectors_base() && md->virtual < PAGE_OFFSET) { //仅仅能创建0xffff0000开始的用于中断向量的虚拟地址映射页目录 //请参见《浅析arm-linux中断vector向量表的建立流程》 printk(KERN_WARNING "MM: not creating mapping for " "0x%08lx at 0x%08lx in user region\n", md->physical, md->virtual); } //页表属性值 prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | (md->prot_read ? L_PTE_USER : 0) | (md->prot_write ? L_PTE_WRITE : 0) | (md->cacheable ? L_PTE_CACHEABLE : 0) | (md->bufferable ? L_PTE_BUFFERABLE : 0); //页目录属性值 prot_sect = PMD_TYPE_SECT | PMD_DOMAIN(md->domain) | (md->prot_read ? PMD_SECT_AP_READ : 0) | (md->prot_write ? PMD_SECT_AP_WRITE : 0) | (md->cacheable ? PMD_SECT_CACHEABLE : 0) | (md->bufferable ? PMD_SECT_BUFFERABLE : 0); virt = md->virtual; off = md->physical - virt; length = md->length; while ((virt & 0xfffff || (virt + off) & 0xfffff) && length >= PAGE_SIZE) { //如果虚拟地址virt或者其对应的物理地址(virt + off),只要有一个非1M页目录对齐, //那么首先调整成1M页目录对齐 //将virt和其对应的物理地址(virt + off)建立映射,如果没有设置页目录,那么alloc_init_page //将自动设置 alloc_init_page(virt, virt + off, md->domain, prot_pte); virt += PAGE_SIZE; length -= PAGE_SIZE; } while (length >= PGDIR_SIZE) { //还有至少1M的空间需要映射,那么可以使用alloc_init_section进行快速1M大小的节映射 alloc_init_section(virt, virt + off, prot_sect); virt += PGDIR_SIZE; length -= PGDIR_SIZE; } while (length >= PAGE_SIZE) { //经过1M节映射之后,仍然还有一些余项,那么继续完成4k页表映射[gliethttp] alloc_init_page(virt, virt + off, md->domain, prot_pte); virt += PAGE_SIZE; length -= PAGE_SIZE; } } //---------------------------------------- //2.rch/arm/mm/Mm-armv.c->alloc_init_page static inline void alloc_init_page(unsigned long virt, unsigned long phys, int domain, int prot) { pmd_t *pmdp; pte_t *ptep; //获取中间级目录入口,对于2级小页映射,pmdp就等于一级页目录入口,即:pmdp = &swapper_pg_dir[i] pmdp = pmd_offset(pgd_offset_k(virt), virt); //对于虚拟映射的原理问题,请参考《linux2.4.19下__ioremap函数中remap_area_pages虚拟地址映射建立函数的代码分析》 if (pmd_none(*pmdp)) { //如果1级页目录dir,是空的,那么创建之 //其中PTRS_PER_PTE=256项二级页表项 //所以首先申请2*256*4=2k空间,之所以要多申请出1K空间,是因为需要存储相应页表项对应的kernel信息 //详细说明见下面的分析.[gliethttp] pte_t *ptep = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t)); ptep += PTRS_PER_PTE;//ptep指向页表项存储区的末端 //将swapper_pg_dir[i]=__mk_pmd(ptep, PMD_TYPE_TABLE | PMD_DOMAIN(domain)), //一级页目录项,装入数值__mk_pmd(ptep, PMD_TYPE_TABLE| PMD_DOMAIN(domain)) set_pmd(pmdp, __mk_pmd(ptep, PMD_TYPE_TABLE | PMD_DOMAIN(domain))); } ptep = pte_offset(pmdp, virt);//找到virt虚拟地址对应的pte页表项入口 //ptep指向第256+(pte_t*)ptr项,即:1k~2k空间 set_pte(ptep, pfn_pte(phys >> PAGE_SHIFT, __pgprot(prot))); } static inline pmd_t __mk_pmd(pte_t *ptep, unsigned long prot) { unsigned long pte_ptr = (unsigned long)ptep; pmd_t pmd; pte_ptr -= PTRS_PER_PTE * sizeof(void *);//之前的pte_ptr指向的是页表项存储区的末端 pmd_val(pmd) = __virt_to_phys(pte_ptr) | prot; return pmd; } //include/asm-arm/Pgtable.h有如下定义 #define pte_offset(dir, addr) ((pte_t *)pmd_page(*(dir)) + __pte_offset(addr)) #define __pte_offset(addr) (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1)) #define pfn_pte(pfn,prot) (__pte(((pfn) << PAGE_SHIFT) | pgprot_val(prot))) //include/asm-arm/proc-armv/Pgtable.h有如下定义 static inline unsigned long pmd_page(pmd_t pmd) { unsigned long ptr; ptr = pmd_val(pmd) & ~(PTRS_PER_PTE * sizeof(void *) - 1); ptr += PTRS_PER_PTE * sizeof(void *);//指向第256+(pte_t*)ptr项,即:1k~2k空间[gliethttp] return __phys_to_virt(ptr); } //include/asm-arm/proc-armv/Pgtable.h有如下定义 #define set_pte(pmdp,pmd) cpu_set_pte(ptep,pte) //include/asm-arm/cpu-single.h中有如下定义 #define cpu_set_pte cpu_fn(CPU_NAME,_set_pte) 翻译之后 #define cpu_set_pte cpu_arm920_set_pte 相应的 #define cpu_set_pgd cpu_arm920_set_pgd #define cpu_set_pmd cpu_arm920_set_pmd #define cpu_set_pte cpu_arm920_set_pte 以上三个函数位于arch/arm/mm/proc-arm920.S .align 5 ENTRY(cpu_arm920_set_pte) str r1, [r0], #-1024//2*1k空间,0~1k被使用,且r0处于1k~2k之间,那么r0=r0-1k,就位于0~1k空间 eor r1, r1, #LPTE_PRESENT | LPTE_YOUNG | LPTE_WRITE | LPTE_DIRTY//异或 bic r2, r1, #0xff0 bic r2, r2, #3 orr r2, r2, #HPTE_TYPE_SMALL tst r1, #LPTE_USER | LPTE_EXEC @ User or Exec? orrne r2, r2, #HPTE_AP_READ tst r1, #LPTE_WRITE | LPTE_DIRTY @ Write and Dirty? orreq r2, r2, #HPTE_AP_WRITE tst r1, #LPTE_PRESENT | LPTE_YOUNG @ Present and Young? movne r2, #0 str r2, [r0]//对该pte在0~1k相应的页表项赋值,我想0~1k空间是有kernel使用,和硬件mmu无关 mov r0, r0 mcr p15, 0, r0, c7, c10, 1 @ clean D entry mcr p15, 0, r0, c7, c10, 4 @ drain WB mov pc, lr //所以可以看出《linux2.4.19下__ioremap函数中remap_area_pages虚拟地址映射建立函数的代码分析》中的pte是 //一个示意图,并不是实际的物理图,实际的物理图应该如下: |->pte-256->存储pte+000对应的kernel信息 |->pte-255->存储pte+001对应的kernel信息 ... |->pte-002->存储pte+254对应的kernel信息 |->pte-001->存储pte+255对应的kernel信息 dir+0000->pte+000->4k |->pte+001->4k |->pte+002->4k ... |->pte+253->4k |->pte+254->4k |->pte+255->4k
|->pte-256->存储pte+000对应的kernel信息 |->pte-255->存储pte+001对应的kernel信息 ... |->pte-002->存储pte+254对应的kernel信息 |->pte-001->存储pte+255对应的kernel信息 dir+0001->pte+000->4k |->pte+001->4k |->pte+002->4k ... |->pte+253->4k |->pte+254->4k |->pte+255->4k
...
|->pte-256->存储pte+000对应的kernel信息 |->pte-255->存储pte+001对应的kernel信息 ... |->pte-002->存储pte+254对应的kernel信息 |->pte-001->存储pte+255对应的kernel信息 dir+4094->pte+000->4k |->pte+001->4k |->pte+002->4k ... |->pte+253->4k |->pte+254->4k |->pte+255->4k
|->pte-256->存储pte+000对应的kernel信息 |->pte-255->存储pte+001对应的kernel信息 ... |->pte-002->存储pte+254对应的kernel信息 |->pte-001->存储pte+255对应的kernel信息 dir+4095->pte+000->4k |->pte+001->4k |->pte+002->4k ... |->pte+253->4k |->pte+254->4k |->pte+255->4k
|