linux内核是如何实现分页机制的
来源:互联网 发布:图像阈值分割算法 编辑:程序博客网 时间:2024/04/28 08:54
注意:请使用谷歌浏览器阅读(IE浏览器排版混乱)
【摘要】
1)为低端内存创建页表:
setup_arch->paging_init->map_lowmem()->create_mapping
static void __init create_mapping(struct map_desc *md){unsigned long addr, length, end;phys_addr_t phys;const struct mem_type *type;pgd_t *pgd;if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {printk(KERN_WARNING "BUG: not creating mapping for 0x%08llx" " at 0x%08lx in user region\n", (long long)__pfn_to_phys((u64)md->pfn), md->virtual);return;}if ((md->type == MT_DEVICE || md->type == MT_ROM) && md->virtual >= PAGE_OFFSET && (md->virtual < VMALLOC_START || md->virtual >= VMALLOC_END)) {printk(KERN_WARNING "BUG: mapping for 0x%08llx" " at 0x%08lx out of vmalloc space\n", (long long)__pfn_to_phys((u64)md->pfn), md->virtual);}/* mem_type中保存了页表属性和页中间目录的属性 */type = &mem_types[md->type];addr = md->virtual & PAGE_MASK; /*phys对应物理地址,本函数实际上就是把phys映射到addr*/ phys = __pfn_to_phys(md->pfn);length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {printk(KERN_WARNING "BUG: map for 0x%08llx at 0x%08lx can not " "be mapped using pages, ignoring.\n", (long long)__pfn_to_phys(md->pfn), addr);return;}/* 页表创建过程如下:*/pgd = pgd_offset_k(addr);end = addr + length;do {unsigned long next = pgd_addr_end(addr, end);alloc_init_pud(pgd, addr, next, phys, type);phys += next - addr;addr = next;} while (pgd++, addr != end);}
static void __init create_mapping(struct map_desc *md){/*1 init_task进程的页全局目录地址:swapper_pg_dir以后每个进程都从这里拷贝页全局目录到各自的pgd里dup_mm->pgd_alloc中实现,cpu_switch_mm中切换。*/pgd = pgd_offset_k(addr);end = addr + length;do {unsigned long next = pgd_addr_end(addr, end);/* 每个页全局目录 都要初始化页二级目录 */alloc_init_pud(pgd, addr, next, phys, type);phys += next - addr;addr = next;} while (pgd++, addr != end);}->2 页二级目录初始化:static void __init alloc_init_pud(pgd_t *pgd, unsigned long addr, unsigned long end, phys_addr_t phys, const struct mem_type *type){/*页二级目录地址,因为不使用页二级目录,所以页二级目录地址等于页全局目录地址。为实现软件兼容性所以代码中还保留了页二级目录的处理流程,只不过它的地址即是页全局目录*/pud_t *pud = pud_offset(pgd, addr);unsigned long next;do {next = pud_addr_end(addr, end);/*页三级目录初始化*/alloc_init_pmd(pud, addr, next, phys, type);phys += next - addr;} while (pud++, addr = next, addr != end);}->3页三级目录初始化:static void __init alloc_init_pmd(pud_t *pud, unsigned long addr, unsigned long end, phys_addr_t phys, const struct mem_type *type){/*页三级目录地址,因为不使用页三级目录,所以页三级目录地址等于页二级目录,当然也等于页全局目录地址。为实现软件兼容性所以代码中还保留了页三级目录的处理流程,只不过它的地址即是页全局目录*/pmd_t *pmd = pmd_offset(pud, addr);unsigned long next;do {next = pmd_addr_end(addr, end);/*if;else都有可能执行到*//* 当我们创建页表项的虚拟地址区间是1M对齐时 */if (type->prot_sect && ((addr | next | phys) & ~SECTION_MASK) == 0) {__map_init_section(pmd, addr, next, phys, type);} /* 当我们创建页表项的虚拟地址区间非1M对齐时 */else {alloc_init_pte(pmd, addr, next,__phys_to_pfn(phys), type);}phys += next - addr;} while (pmd++, addr = next, addr != end);}->4 页表初始化:虚拟地址区间1M时,如下方式MMU映射:static void __init __map_init_section(pmd_t *pmd, unsigned long addr,unsigned long end, phys_addr_t phys,const struct mem_type *type){pmd_t *p = pmd;do {*pmd = __pmd(phys | type->prot_sect);phys += SECTION_SIZE;} while (pmd++, addr += SECTION_SIZE, addr != end);flush_pmd_entry(p);}虚拟地址区间非1M对齐时,如下方式MMU映射,即创建页表:static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr, unsigned long end, unsigned long pfn, const struct mem_type *type){ /* 此时申请页表的地址(一个页表项4byte,一个pmd有512个页表项)页表的基地址会赋值给pmd ,__pmd_populate中完成赋值*/pte_t *pte = early_pte_alloc(pmd, addr, type->prot_l1);do {/*为每一页配置页表属性,注意此时用到了mem_types定义的属性*/set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);pfn++;} while (pte++, addr += PAGE_SIZE, addr != end);}
ENTRY(cpu_v7_set_pte_ext)/*r0:页表项地址r1:页表属性|物理地址偏移将页表属性r1配置给页表项r0*/strr1, [r0]@ linux versionbicr3, r1, #0x000003f0bicr3, r3, #PTE_TYPE_MASKorrr3, r3, r2orrr3, r3, #PTE_EXT_AP0 | 2tstr1, #1 << 4orrner3, r3, #PTE_EXT_TEX(1)eorr1, r1, #L_PTE_DIRTYtstr1, #L_PTE_RDONLY | L_PTE_DIRTYorrner3, r3, #PTE_EXT_APXtstr1, #L_PTE_USERorrner3, r3, #PTE_EXT_AP1tstr1, #L_PTE_XNorrner3, r3, #PTE_EXT_XNtstr1, #L_PTE_YOUNGtstner1, #L_PTE_VALIDeorner1, r1, #L_PTE_NONEtstner1, #L_PTE_NONEmoveqr3, #0 ARM(strr3, [r0, #2048]! ) THUMB(addr0, r0, #2048 ) THUMB(strr3, [r0] )ALT_SMP(W(nop))ALT_UP (mcrp15, 0, r0, c7, c10, 1)@ flush_ptebxlrENDPROC(cpu_v7_set_pte_ext)
1) 可以通过配置全局变量mem_types属性修改低端内存cache的开启情况,create_mapping中真正使用该配置。内核在初始化中的会为低端内存、io地址等地址空间设置页表属性,如果要修改这一部分内存的属性包括cache开启关闭情况,mem_types中定义了初始页表项属性,形如:
prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY
其实在内核启动过程的build_mem_type_table()函数里,还会根据不同arm版本做调整。
2) 可以在映射一段物理内存之前通过系统的标志宏来修改cache使用情况。
举例:/dev/mem驱动中实现物理内存的重新映射(即mmap函数的实现过程).
mmap_mem->remap_pfn_range中:vm_page_prot = pgprot_writecombine(pVma->vm_page_prot);这段代码就是配置cache属性的。
内存映射前关闭cache的宏:
#define pgprot_noncached(prot) \
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED)
#define pgprot_writecombine(prot) \
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE)
关闭cache时使用的具体标志位:
#define L_PTE_MT_UNCACHED (_AT(pteval_t, 0) << 2) /* strongly ordered */
#define L_PTE_MT_BUFFERABLE (_AT(pteval_t, 1) << 2) /* normal non-cacheable */
【总结】
- linux内核是如何实现分页机制的
- linux内核里面writel是如何实现的
- linux内核里面writel是如何实现的?
- 内核里面writel是如何实现的
- 内核里面writel是如何实现的
- Linux内核是如何工作的
- Linux内核是如何工作的
- Linux内核是如何工作的
- Linux 内核是如何构建的
- Linux内核RCU机制的实现
- Linux内核RCU机制的实现 .
- codeigniter是如何实现钩子机制的?
- 简述Linux系统与内核是如何构成的以及 linux内核是如何构成的
- Linux内核复习之分页机制
- linux内核笔记——分页机制
- Linux的分页机制
- Linux的分页机制
- Linux分页机制之分页机制的实现详解--Linux内存管理(八)
- HP-UX 官方包下载
- 【高性能JavaScript】读书笔记
- ubuntu16.04
- jmeter测试读取 .csv 文件出现第一个字符乱码的解决方法
- android studio 2.3更新后编译报错的问题
- linux内核是如何实现分页机制的
- 二叉排序树详解
- CentOS 之 nexus 搭建
- java string 方法 toString用法
- VMware安装centos6.5
- TensorFlow学习笔记(一)入门
- GYM 101147 H.Commandos(dp)
- Win10快捷键
- Caused by: org.apache.commons.logging.LogConfigurationException: org.apache.commons.logging.LogConfi