linux2.4.19下__ioremap函数中remap_area_pages虚拟地址映射建立函数的代码分析
文章来源:http://gliethttp.cublog.cn
接续前一篇文章《linux2.4.19下__ioremap和get_vm_area的粗略理解》未完成的部分-remap_area_pages,进行进一步阐述: 虚拟内存vm技术,采用分页机制,使得进程可以使用比实际物理内存大甚多的空间;linux的地址有三种: <1>物理地址 就是没有启用mmu之前cpu操作的地址,如:sdram的总线地址等. <2>线性地址: 不采用分页机制,可以通过简单的加、减一个基地址方式,完成vm地址和物理地址的转换,如:物理内存sdram的虚拟地址位于3G~vmalloc_start,可以 通过virt_to_phys和phys_to_virt两个函数实现虚拟地址和sdram物理地址的直接线性转换,另一例子就是at91rm9200控制寄存器的控制,同样直接使用 AT91_IO_P2V(AT91C_BASE_SYS)将物理寄存器AT91C_BASE_SYS线性的映射成mmu下的虚拟地址(AT91_IO_P2V在include/asm-arm/arch-at91rm9200/Hardware.h中定义). <3>逻辑地址,也被称为虚拟地址:逻辑地址和物理地址都被linux进行了分页管理,两者的页大小可以不同,但是为了管理上的方便,一般at91rm9200处理器下的linux系统将逻 辑地址和物理地址均划分大小为4k的页面,逻辑地址经过vm技术的2级(at91rm9200处理器对应的linux系统采用二级mmu映射机制)表转换后最终映射到的真正使用的物理地 址空间,at91rm9200处理器最终操作的还是总线地址,也就是物理地址,所以linux系统的工作之一就是将线性地址和逻辑地址,于是linux需要首先建立vm的2级转换表;
这之前我们先来了解一下mmu的分页机制:mmu可以对4种大小进行映射管理, 1)微页:构成1k的存储区 2)小页:构成4k的存储区 3)大页:构成64k的存储区 4)节:构成1M的存储区(arch/arm/kernel/head-armv.S启动代码中就暂用4个节映射,完成mmu的正常启动) at91rm9200处理器采用了2级小页mmu管理方式,因此存在dir页目录4k[虚拟地址的31~20共12位],每个dir页目录项存放着第2级pte页表的基地址 dir页目录项内容如下: bit 31 ... 10 9 ... 0 | 页表基地址 | mmu控制域 | pte页表项内容如下: bit 31 ... 12 11 ... 0 | 小页基地址 | mmu控制域 | 经过pte之后,pte表项内容的31~12位和虚拟地址的低12位组合成最终32位物理地址. 虚拟地址的结构: bit 31 ... 20 19 ... 12 11 ... 0 | 第一级表索引 | 第二级表索引 | 页索引[偏移量] | 举个例子来说明2级小页mmu的结构,先假设虚拟地址为addr,那么dir目录项的索引号为:addr>>20;该dir目录索引号对应的目录项的内容31~10位作为pte的 基地址,addr的19~12位作为相对该pte基址的偏移,即:pte页表项,将pte页表项的31~12位和addr的11~0位偏移量组合成最终的物理地址,所以通过以上分析我们可以 总结出如下:dir共4k个,每个挂带1M数据,该1M数据又由256个pte分开管理,每个pte对应1个页4k,也就是1个dir页目录含有1个pte页表基地址对应256个pte页表项,1个页表项对应1个4k空间,可以用如下示意图简易的表示: dir+0000->pte+000->4k |->pte+001->4k |->pte+002->4k ... |->pte+253->4k |->pte+254->4k |->pte+255->4k dir+0001->pte+000->4k |->pte+001->4k |->pte+002->4k ... |->pte+253->4k |->pte+254->4k |->pte+255->4k ... dir+4094->pte+000->4k |->pte+001->4k |->pte+002->4k ... |->pte+253->4k |->pte+254->4k |->pte+255->4k dir+4095->pte+000->4k |->pte+001->4k |->pte+002->4k ... |->pte+253->4k |->pte+254->4k |->pte+255->4k
所以通过上述的2级小页mmu就轻松的实现了4k*256*4k=4G虚拟空间到物理空间的无缝转换.[注意:2级小页mmu结构中,dir和pte表项中的地址均为物理地址] 通过上面的mmu的转换路径分析,我们大体掌握了mmu的机理,现在转入正题,来看看__ioremap函数中remap_area_page子函数体源码:
static int remap_area_pages(unsigned long address, unsigned long pfn, unsigned long size, unsigned long flags) { int error; pgd_t * dir; unsigned long end = address + size;
pfn -= address >> PAGE_SHIFT; //#define PGDIR_SHIFT 20 //#define pgd_index(addr) ((addr) >> PGDIR_SHIFT) //#define pgd_offset(mm, addr) ((mm)->pgd+pgd_index(addr)) //dir=address对应的高12值[0~4095] dir = pgd_offset(&init_mm, address); flush_cache_all(); BUG_ON(address >= end); spin_lock(&init_mm.page_table_lock); do { pmd_t *pmd; //at91rm9200处理器采用2级小页mmu方式,所以pmd_alloc将直接返回dir,即:pmd = dir; //对于采用3级页表的系统来说,pmd_alloc将会向dir目录项填入pmd的物理基地址 pmd = pmd_alloc(&init_mm, dir, address); error = -ENOMEM; if (!pmd) break; //remap_area_pmd函数将中间页pmd填入pte内容,因为采用的是2级小页mmu方式,所以pmd=dir; if (remap_area_pmd(pmd, address, end - address, pfn + (address >> PAGE_SHIFT), flags)) break; error = 0; //address+1M,如果size=5.2M,那么会连续占用接下的5个dir区域 address = (address + PGDIR_SIZE) & PGDIR_MASK; dir++; } while (address && (address < end)); spin_unlock(&init_mm.page_table_lock); flush_tlb_all(); return error; }
static inline int remap_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size, unsigned long pfn, unsigned long flags) { unsigned long end; pgprot_t pgprot; //只对address的低1M地址空间进行pte创建 address &= ~PGDIR_MASK; end = address + size; //#define PGDIR_SHIFT 20 //#define PGDIR_SIZE (1UL << PGDIR_SHIFT) 1级目录以1M字节为dir++自加单位 if (end > PGDIR_SIZE) end = PGDIR_SIZE; pfn -= address >> PAGE_SHIFT; BUG_ON(address >= end); pgprot = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_WRITE | flags); do { //pte_alloc将获取256个连续pte的空间,并将地址指针指向这256个pte地址空间的最后一个地址处 pte_t * pte = pte_alloc(&init_mm, pmd, address); if (!pte) return -ENOMEM; //remap_area_pte为256个连续的pte填充pfn物理地址 remap_area_pte(pte, address, end - address, pfn + (address >> PAGE_SHIFT), pgprot); //因为是2级小页mmu方式,所以PMD_SIZE = PGD_SIZE = 1M //又因为外层dir已经做了size总大小的处理,所以pmd这里,将只对1M空间进行pte内容填充,也就是while循环仅仅执行一次 address = (address + PMD_SIZE) & PMD_MASK; pmd++; } while (address && (address < end)); return 0; }
以下为一些宏函数的出处和简单定义: #define pmd_populate(mm,pmdp,pte) \ do { \ unsigned long __prot; \ if (mm == &init_mm) \ __prot = _PAGE_KERNEL_TABLE; \ else \ __prot = _PAGE_USER_TABLE; \ set_pmd(pmdp, __mk_pmd(pte, __prot)); \ } while (0)
//函数__mk_pmd用来把pte数值赋给pmd单元 //函数pte_alloc static inline pmd_t __mk_pmd(pte_t *ptep, unsigned long prot) { unsigned long pte_ptr = (unsigned long)ptep; pmd_t pmd; //#define PTRS_PER_PTE 256 pte_ptr -= PTRS_PER_PTE * sizeof(void *); pmd_val(pmd) = __virt_to_phys(pte_ptr) | prot; return pmd; } include/asm-arm/cpu-multi32.h:# include/asm-arm/cpu-single.h中有如下定义: #define cpu_set_pgd cpu_fn(CPU_NAME,_set_pgd) #define cpu_set_pmd cpu_fn(CPU_NAME,_set_pmd) #define cpu_set_pte cpu_fn(CPU_NAME,_set_pte) 而cpu_fn定义如下: #ifdef __STDC__ #define __cpu_fn(name,x) cpu_##name##x #else #define __cpu_fn(name,x) cpu_/**/name/**/x #endif #define cpu_fn(name,x) __cpu_fn(name,x) 所以最后at91rm9200处理器cpu_set_pgd,cpu_set_pmd和cpu_set_pte对应的函数原型为: cpu_set_pgd -- cpu_arm920_set_pgd cpu_set_pmd -- cpu_arm920_set_pmd cpu_set_pte -- cpu_arm920_set_pte 以上三个函数位于arch/arm/mm/proc-arm920.S
include/asm-arm/proc-armv/Pgtable.h #define PTRS_PER_PTE 256 //第二级,表示有256个表项,每个表项描述4K的二级页面 #define PTRS_PER_PMD 1 //因为at91rm9200处理器不需要该级,所以此处忽略 #define PTRS_PER_PGD 4096 //第一级,表示有4K个表项,每个表项描述1M的一级页面 include/asm-arm/pgtable.h中 extern pgd_t swapper_pg_dir[PTRS_PER_PGD];
|