内存映射过程之paging_init
内存映射过程之paging_init
导读
之前已经逐步整理完成了idmap、fixup、memblock等内容,在此阶段已经可以:
- 访问kernel image的地址空间;
- 访问FDT地址空间,即已经获取device tree相关数据;
- 可以通过memblock提供的接口申请内存;
则接下来需要做的处理,是要把从fdt中获取的mem layout关系建立实际的映射;
所谓建立映射,实质很简单,即根据页表大小和地址空间大小,填充PUD PMD PTE等数据;
1. 页表转换关系
这部分之前已经整理过了,在这里制作简单说明:
1.1 涉及页表层级相关宏
宏(Config) | 转换后 | 值 | 描述 |
---|---|---|---|
CONFIG_ARM64_VA_BITS_39=y | NA | NA | 决策逻辑地址的范围,以40~63位标记kernel or user,user + kernel共 1T |
CONFIG_ARM64_VA_BITS=39 | NA | 39 | 同上 |
CONFIG_ARM64_4K_PAGES=y | NA | NA | 决策页表的大小,当前被配置为4K |
CONFIG_ARM64_PAGE_SHIFT=12 | NA | 12 | 同上 |
CONFIG_PGTABLE_LEVELS=3 | NA | 3 | 决策页表层级,即当前平台支持3级页表:PGD PMD PTE |
PTRS_PER_PTE | 1 << (PAGE_SHIFT - 3) | 1<<9 | PTE/PMD/PUD中表项的多少 |
PGDIR_SHIFT | ARM64_HW_PGTABLE_LEVEL_SHIFT(4 - CONFIG_PGTABLE_LEVELS) | 30 | PGD 的偏移位 |
PUD_SHIFT | ARM64_HW_PGTABLE_LEVEL_SHIFT(1) | NA | PUD的偏移位,level = 3 无PUD |
PMD_SHIFT | ARM64_HW_PGTABLE_LEVEL_SHIFT(2) | 21 | PMD的偏移位 |
ARM64_HW_PGTABLE_LEVEL_SHIFT(n) | (PAGE_SHIFT - 3) * (4 - (n)) + 3 | NA | 根据level决策该层的shift |
1.2 页表转换
一次地址的翻译过程基本如上图所示,相关的level、page size、VA size等根据平台不同,上述Config和宏都已经配置完成,即上述转换过程是固定的
则我们本部分要介绍的paging_init实质上就是在构造上述转换关系:
- 确认PGD的访问入口(实质为swapper的地址)
- 根据PGD和 offset 计算到PMD的页表地址,并建立映射:
- 申请物理页表;
- 与上述计算出来的地址建立映射;
- 使用同样方法得到PTE的页表地址并建立映射关系;
- 使用同样方法得到PAGE的页表地址并建立映射关系;
则上述过程中:
- 页表入口地址PGD,在此时已经可以访问到了;
- 核心功能为建立映射关系,即可以通过该线性地址读取到实际的物理地址;
1.3 页表构建过程
上述部分的核心目录:
目录 | 描述 |
---|---|
./arch/arm64/mm/mmu.c | MMU地址转换相关函数的实现 |
./kernel-4.9/arch/arm64/include/asm/pgtable.h | 页表实现相关接口 |
./kernel-4.9/arch/arm64/include/asm/pgtable-hwdef.h | 页表各个level中shift、size等实现 |
2. 功能描述
直接上图:
3. code 阅读
3.1 Paging_init
paging_init这个函数并不长,注释已经说明的比较清晰了:
- set up page table;
- 初始化zone memory映射;
- 建立第一个页面;
/** paging_init() sets up the page tables, initialises the zone memory* maps and sets up the zero page.*/
void __init paging_init(void)
{phys_addr_t pgd_phys = early_pgtable_alloc(); //分配一个phy 页面pgd_t *pgd = pgd_set_fixmap(pgd_phys); //将其放在fixmap中,建立映射关系,方便访问map_kernel(pgd); //映射内核中各个段的关系,即我们之前看的vmlinux.lds.S中的内容;map_mem(pgd); //这里是映射之前memblock中add的部分//这里复用swapper_pg_dir的空间,将当前新建的页表放进去,转换为VA哦;cpu_replace_ttbr1(__va(pgd_phys));memcpy(swapper_pg_dir, pgd, PAGE_SIZE);cpu_replace_ttbr1(swapper_pg_dir);pgd_clear_fixmap();//上述已经映射完成,由于PGD复用了swapper的空间的PGD,事实上整个系统就只用一个PGD,在这里释放掉pgdmemblock_free(pgd_phys, PAGE_SIZE);
//释放掉swapper_pg_dir中的pud pmd,这里之前是在kernel 汇编汇中填充的,目的是为了访问kernel空间,memblock_free(__pa(swapper_pg_dir) + PAGE_SIZE, SWAPPER_DIR_SIZE - PAGE_SIZE);
}
逐步分析就是做了这几件事情:
- 获取pgd_phys地址,即页表地址
- 在fixmap中建立映射关系,方便访问
- 映射kernel中各个段
- 映射memblock中添加的部分
- 复用swapper_pg_dir的地址,将pgd添加过去
- 释放掉没有使用的资源
3.2 申请物理页面
early_pgtable_alloc 申请一个page:
static phys_addr_t __init early_pgtable_alloc(void)
{phys_addr_t phys;void *ptr;phys = memblock_alloc(PAGE_SIZE, PAGE_SIZE); // 通过memblock 申请一个page大小的地址,注意这里是物理地址ptr = pte_set_fixmap(phys);//这里就是将申请的物理页面放到fixmap地址中的pte部分,这里还没用过,则现在可以通过fixmap中PTE的虚拟地址访问当前申请的这块物理地址;memset(ptr, 0, PAGE_SIZE);//将其清空pte_clear_fixmap();//去除这块的映射return phys;//将物理地址返回
}
上述函数的核心目的就是申请一块物理内存,借用fixmap中的pte映射将其清空;
3.3 借用fixmap建立映射
具体来看下fixmap的映射操作,这个在fixmap那一部分分析过,这里翻译下这个宏:
pgd_t *pgd = pgd_set_fixmap(pgd_phys);
- #define pgd_set_fixmap(addr) ((pgd_t *)set_fixmap_offset(FIX_PGD, addr))
pgd_t *pgd = ((pgd_t *)set_fixmap_offset(FIX_PGD, pgd_phys))- #define set_fixmap_offset(idx, phys) __set_fixmap_offset(idx, phys, FIXMAP_PAGE_NORMAL)
pgd_t *pgd = ((pgd_t *)__set_fixmap_offset(FIX_PGD, pgd_phys, FIXMAP_PAGE_NORMAL))- #define __set_fixmap_offset(idx, phys, flags) ({unsigned long ________addr; __set_fixmap(idx, phys, flags);________addr = fix_to_virt(idx) + ((phys) & (PAGE_SIZE - 1)); ________addr;})
pgd_t *pgd = ((pgd_t *)({ unsigned long ________addr; __set_fixmap(FIX_PGD, pgd_phys, FIXMAP_PAGE_NORMAL);________addr = fix_to_virt(FIX_PGD) + ((pgd_phys) & (PAGE_SIZE - 1)); ________addr;}))
这个函数实际在做什么呢:
- 之前申请到了4K大小的物理内存;
- 获取到fix_pgd的虚拟地址,再通过物理地址计算偏移,也就是说这里放在FIX_PGD之后,即fix_to_virt(FIX_PGD) + ((pgd_phys) & (PAGE_SIZE - 1));
- 通过__set_fixmap建立物理地址与虚拟地址的映射;
具体来看建立映射的过程:
void __set_fixmap(enum fixed_addresses idx, phys_addr_t phys, pgprot_t flags)
{unsigned long addr = __fix_to_virt(idx);//根据fixmap的idx获取其虚拟地址;pte_t *pte;BUG_ON(idx <= FIX_HOLE || idx >= __end_of_fixed_addresses);pte = fixmap_pte(addr);// 将addr作为pte_index计算pte的偏移;bm_pte是在early_fixmap_init时添加的链接if (pgprot_val(flags)) {//FIXMAP_PAGE_NORMALset_pte(pte, pfn_pte(phys >> PAGE_SHIFT, flags));//set_pte就是把物理地址放到bm_pte对应的这个pte;} else {pte_clear(&init_mm, addr, pte);flush_tlb_kernel_range(addr, addr+PAGE_SIZE);}
}
fixmap映射的过程,由于其线性地址是固定的,所以需要做的就是把对应的物理地址放到pte中;
而pte是根据虚拟地址中pmd的偏移计算出来的,pmd是根据pud的偏移计算出来,pud是根据pgd的偏移计算出来,实质都是通过idx的虚拟地址可以找到bm_pte以及其pte_index;
3.4 kernel映射
本函数核心为建立kernel中各个段到pgd页表中,通过如下几个步骤:
- map_kernel_segment映射各个段
- pgd_val链接到FIXADDR_START
/** Create fine-grained mappings for the kernel.*/
static void __init map_kernel(pgd_t *pgd)
{static struct vm_struct vmlinux_text, vmlinux_rodata, vmlinux_init, vmlinux_data;map_kernel_segment(pgd, _text, _etext, PAGE_KERNEL_EXEC, &vmlinux_text); // text段map_kernel_segment(pgd, __start_rodata, __init_begin, PAGE_KERNEL, &vmlinux_rodata); //rodata段map_kernel_segment(pgd, __init_begin, __init_end, PAGE_KERNEL_EXEC,&vmlinux_init); //init 段map_kernel_segment(pgd, _data, _end, PAGE_KERNEL, &vmlinux_data); // data段if (!pgd_val(*pgd_offset_raw(pgd, FIXADDR_START))) {/** The fixmap falls in a separate pgd to the kernel, and doesn't* live in the carveout for the swapper_pg_dir. We can simply* re-use the existing dir for the fixmap.*/set_pgd(pgd_offset_raw(pgd, FIXADDR_START), *pgd_offset_k(FIXADDR_START));} else if (CONFIG_PGTABLE_LEVELS > 3) {/** The fixmap shares its top level pgd entry with the kernel* mapping. This can really only occur when we are running* with 16k/4 levels, so we can simply reuse the pud level* entry instead.*/BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));set_pud(pud_set_fixmap_offset(pgd, FIXADDR_START), __pud(__pa(bm_pmd) | PUD_TYPE_TABLE));pud_clear_fixmap();} else {BUG();}kasan_copy_shadow(pgd);//kasan这个东西目前还没研究过,先不care
}
// map_kernel_segment( pgd, _text, _etext, PAGE_KERNEL_EXEC, &vmlinux_text);
static void __init map_kernel_segment(pgd_t *pgd, void *va_start, void *va_end, pgprot_t prot, struct vm_struct *vma)
{phys_addr_t pa_start = __pa(va_start); // va转换为paunsigned long size = va_end - va_start; //计算 sizeBUG_ON(!PAGE_ALIGNED(pa_start));BUG_ON(!PAGE_ALIGNED(size));__create_pgd_mapping(pgd, pa_start, (unsigned long)va_start, size, prot, early_pgtable_alloc, !debug_pagealloc_enabled());//建立映射关系// !debug_pagealloc_enabled() 根据CONFIG_DEBUG_PAGEALLOC is not set决策,当前平台这里是默认false,即传入参数为true,也就是allow_block_mappings//将上述我们计算的虚拟地址和物理地址添加到vm_struct中,并在后续使用vma->addr = va_start;vma->phys_addr = pa_start;vma->size = size;vma->flags = VM_MAP;vma->caller = __builtin_return_address(0);vm_area_add_early(vma);
}//建立映射关系
static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys,unsigned long virt, phys_addr_t size,pgprot_t prot,phys_addr_t (*pgtable_alloc)(void),bool allow_block_mappings)
{unsigned long addr, length, end, next;pgd_t *pgd = pgd_offset_raw(pgdir, virt);/** If the virtual and physical address don't have the same offset* within a page, we cannot map the region as the caller expects.*/if (WARN_ON((phys ^ virt) & ~PAGE_MASK))return;phys &= PAGE_MASK;addr = virt & PAGE_MASK;length = PAGE_ALIGN(size + (virt & ~PAGE_MASK));end = addr + length;do {next = pgd_addr_end(addr, end);alloc_init_pud(pgd, addr, next, phys, prot, pgtable_alloc, allow_block_mappings);phys += next - addr;} while (pgd++, addr = next, addr != end);
}//把我们映射完成的vm_struct添加到vmlist管理,需要注意的是这个函数是在vmalloc_init之前使用的
/*** vm_area_add_early - add vmap area early during boot* @vm: vm_struct to add** This function is used to add fixed kernel vm area to vmlist before* vmalloc_init() is called. @vm->addr, @vm->size, and @vm->flags* should contain proper values and the other fields should be zero.** DO NOT USE THIS FUNCTION UNLESS YOU KNOW WHAT YOU'RE DOING.*/
void __init vm_area_add_early(struct vm_struct *vm)
{struct vm_struct *tmp, **p;BUG_ON(vmap_initialized);for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) {if (tmp->addr >= vm->addr) {BUG_ON(tmp->addr < vm->addr + vm->size);break;} elseBUG_ON(tmp->addr + tmp->size > vm->addr);}vm->next = *p;*p = vm;
}
3.5 memblock 映射
这里是将我们在memblock中添加的region,逐个遍历,通过__map_memblock做物理地址与线性地址的映射;
static void __init map_mem(pgd_t *pgd)
{struct memblock_region *reg;/* map all the memory banks */for_each_memblock(memory, reg) {phys_addr_t start = reg->base;phys_addr_t end = start + reg->size;if (start >= end) break;if (memblock_is_nomap(reg)) continue;__map_memblock(pgd, start, end);}
}//实质map的动作也很简单,就是通过__create_pgd_mapping建立映射关系,只是需要做些判断,确认映射位置;
static void __init __map_memblock(pgd_t *pgd, phys_addr_t start, phys_addr_t end)
{unsigned long kernel_start = __pa(_text);unsigned long kernel_end = __pa(__init_begin);/** Take care not to create a writable alias for the* read-only text and rodata sections of the kernel image.*//* No overlap with the kernel text/rodata */if (end < kernel_start || start >= kernel_end) {__create_pgd_mapping(pgd, start, __phys_to_virt(start), end - start, PAGE_KERNEL, early_pgtable_alloc, !debug_pagealloc_enabled());return;}/** This block overlaps the kernel text/rodata mappings.* Map the portion(s) which don't overlap.*/if (start < kernel_start)__create_pgd_mapping(pgd, start, __phys_to_virt(start), kernel_start - start, PAGE_KERNEL, early_pgtable_alloc, !debug_pagealloc_enabled());if (kernel_end < end)__create_pgd_mapping(pgd, kernel_end, __phys_to_virt(kernel_end), end - kernel_end, PAGE_KERNEL, early_pgtable_alloc, !debug_pagealloc_enabled());/** Map the linear alias of the [_text, __init_begin) interval as* read-only/non-executable. This makes the contents of the* region accessible to subsystems such as hibernate, but* protects it from inadvertent modification or execution.*/__create_pgd_mapping(pgd, kernel_start, __phys_to_virt(kernel_start), kernel_end - kernel_start, PAGE_KERNEL_RO, early_pgtable_alloc, !debug_pagealloc_enabled());
}
4. 总结
4.1 页表建立过程
本部分整理不是很到位,算是仓促结束吧,之后抽时间再来重新梳理一下
4.2 描述地址映射过程
内存映射过程之paging_init相关推荐
- ARM Linux (S3C6410架构/2.6.35内核)的内存映射(三)
这里记录一下Linux内核做二级内存映射的过程,以中断向量表的映射过程为例. 在S3C6410架构下,Linux采用的是粗粒度小页内存管理方式,即内存段(section)的大小为1M,而页(page) ...
- 深入理解Linux内存映射机制
Author: wzt EMail: [email]wzt@xsec.org[/email] Site: [url]http://www.xsec.org[/url] Date: 2008-6-13 ...
- jvm 堆外内存_NIO效率高的原理之零拷贝与直接内存映射
更多内容,欢迎关注微信公众号:全菜工程师小辉~ 前言 在笔者上一篇博客,详解了NIO,并总结NIO相比BIO的效率要高的三个原因,彻底搞懂NIO效率高的原理. 这篇博客将针对第三个原因,进行更详细的讲 ...
- linux 文件IO与内存映射:内存映射
前言 前面几篇我们学习了用户空间的IO缓冲区,以及IO缓冲区的分散聚合IO技术. 为了减少系统调用的次数,提升系统性能,操作系统开发者门提出了这么多的缓存技术. 但是到这里这些技术同样有不足的地方:不 ...
- java大文件读写操作,java nio 之MappedByteBuffer,高效文件/内存映射
http://langgufu.iteye.com/blog/2107023 java处理大文件,一般用BufferedReader,BufferedInputStream这类带缓冲的Io类,不过如果 ...
- 使用内存映射文件在进程之间共享数据
数据共享方法是通过让两个或多个进程映射同一个文件映射对象的视图来实现的,这意味着它们将共享物理存储器的同一个页面.因此,当一个进程将数据写入一个共享文件映射对象的视图时,其他进程可以立即看到它们视图中 ...
- linux kernel内存映射实例分析
作者:JHJ(jianghuijun211@gmail.com) 日期:2012/08/24 欢迎转载,请注明出处 引子 现在android智能手机市场异常火热,硬件升级非常迅猛,arm cortex ...
- 底板芯片组与内存映射(Motherboard Chipsets and the Memory Map) 【转】
转自:http://blog.chinaunix.net/uid-25909619-id-4194650.html 底板芯片组与内存映射 我打算写一些关于计算机内部构造(computer intern ...
- 使用内存映射文件来提高你程序的性能
本人在学习<WINDOWS核心编程>的时候对JEFFREY大师提到的一个小程序写了两个版本来比较性能,该程序的原始需求是这样的:对一个大文件进行倒序,也就是将一个文件头变成尾,尾变成头. ...
- mmap映射大于4g的文件_iOS文件内存映射——MMAP
前言 最近一段项目上总是出现一些因为文件没有及时保存而产生的问题,因此小编就在网上寻找到了这个文件存储方法mmap,这里为大家进行下简单的介绍. 简介 首先我们需要对iOS中各App的运行环境进行了解 ...
最新文章
- 终于,我读懂了所有Java集合——queue篇
- 差分进化算法matlab代码_差分进化算法
- 学习总结 java基础
- webstorm小程序插件和中文插件
- linux教程第六章,linux教程第六章.ppt
- SplitContainer控件的理解
- java日历数据_JAVA 常用数据类型 之日历类
- 通达信标记符号_通达信添加标记符号
- 菜鸟的Django配置
- 新库上线 | CnOpenData境外投资企业(机构)名录数据
- 对标苹果开“旧机发布会”?罗永浩出任转转品牌推广大使
- 微信小程序学习笔记——环境准备 【注册账号】【获取APPID】
- 单页面网站如何高效做SEO优化?
- android 打开短信应用,通过短信打开手机应用
- sql中按照指定字段排序失效
- 国产剧《恋爱先生》中的科技牛人
- 通过路由器绕过DDoS防御攻击web服务器总结
- 电化学: 电解池,原电池
- 服务器绑定网站域名,服务器网站绑定域名
- 输入某年某月某日,判断这一天是这一年的第几天?(Python)
热门文章
- 【BZOJ1966】[AHOI2005]病毒检测(动态规划)
- [bzoj2115][Wc2011] Xor
- Oracle补习班第五天
- “同样的”约束,不同的位置
- BZOJ4060 : [Cerc2012]Word equations
- Android NDK 如何缩减库的大小
- 在cygwin下使用VC编译器
- 学会使用Trace和Debug
- 什么是.NET应用程序域
- JavaScript—call, apply, bind 函数(20)