目录

1.为什么需要虚拟内存地址

2.页表

3.内核启动初始页表创建

获取内核在内存中起始页地址

__create_page_tables


1.为什么需要虚拟内存地址

个人理解通过创建页表实现物理地址到虚拟地址的映射,通过虚拟地址、虚拟空间实现了不同应用进程地址空间的隔离,表象上给应用空间提供了足够的内存空间;将内存的管理更多的交给底层系统进行实现,同时避免了物理内存的直接暴露一定程度上提高了系统的安全性。

2.页表

页表用时实现内存物理地址到虚拟地址的映射,实现虚拟地址到物理的转化;是虚、实内存地址转化的媒介。页表,是由物理地址向虚拟地址映射时创建的,由软件代码逻辑进行实现;虚拟地址向物理地址的转化需要借助MMU硬件单元实现。

如上是Arm64系统虚拟地址宽度39bit、(va_bit = 39)page size 为4k(page_shift = 12)、PTG level为3(3级页表)的页表,图中体现了虚拟地址转化为物理地址的实现逻辑。

虚拟地址向物理地址转化过程:

1) A1 取虚拟地址 PGD 段,做为 PGD 数组的下标;

2) B1  PGD[A1] 值为该虚拟地址对应的 PMD 的基础物理地址;

3) A2 取虚拟地址 PMD 段,做为 PMD 数组的下标;

4) B2 PMD[A2 ]值为该虚拟地址对应的 PTE 的基础物理地址;

5)  A3 取虚拟地址 PTE 段,做为 PTE 数组的下标;

6) B3  PTE[A3] 值为该虚拟地址对应的物理地址的PFN (PFN: page-frame                         number,页帧号);

7)  A4 取虚拟地址 page 段,做为该虚拟地址对应的物理地址页内偏移;

8) PFN + page 段即为该虚拟地址对应的物理地址

系统中虚拟地址转化物理地址时,还有其他的因素要考虑,如:虚拟地址是内核段的还是用户段、对应的内存属性等,但这些不是本章重点。本章的重点在于介绍虚拟地址到物理地址转换逻辑,对PGD有初步了解、为下一章内核启动过程临时页表的创建做基础准备。

备注:PFN  page-frame number 页帧号表示内存地址所在页的页编号,是针对内存的物理地址右移page_shift后的值。以4K page size系统为例PFN即保留了物理地址右移12的值。

3.内核启动初始页表创建

#define __PHYS_OFFSET    (KERNEL_START - TEXT_OFFSET)ENTRY(stext)bl  preserve_boot_argsbl    el2_setup                      // Drop to EL1, w0=cpu_boot_modeadrp    x23, __PHYS_OFFSET         //@1 获取内核的物理地址and   x23, x23, MIN_KIMG_ALIGN - 1   // KASLR offset, defaults to 0bl set_cpu_boot_mode_flagbl    __create_page_tables           //@2 创建初始页表/** The following calls CPU setup code, see arch/arm64/mm/proc.S for* details.* On return, the CPU will be ready for the MMU to be turned on and* the TCR will have been set.*/bl    __cpu_setup         // initialise processorb    __primary_switch
ENDPROC(stext)
  •        获取内核在内存中起始页地址 

      KERNEL_START 为_text 即内核头入口虚拟地址

      TEXT_OFFSET 为0x80000 (未开启CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET     时),表示kenerl image距ram起始地址的偏移。在内核启动时,ram的前0x80000内存空间用于存储uboot传递内核的启动参数

        __PHYS_OFFSET 从如上的定义可知为内核在内存起始虚拟地址;

       adrp    x23, __PHYS_OFFSET  //获取内核在内存中的起始页地址;关于adrp指令解析,可参考ARMv8汇编指令-adrp、adr、adr_l_业余程序员plus的博客-CSDN博客_adr指令 说明。

  • __create_page_tables

该函数通过调用create_pgd_entry、create_table_entry、create_block_map来创建内核启动过程中临时内存映射,会分别以 idmap_pg_dir 、swapper_pg_dir为页表空间来完成临时页表的创建。

其中创建页表的核心函数create_table_entry如下,在三级页表结构中此函数主要完成PGD、PMD两级页表的创建。

以创建PGD页表过程为例,

入参说明: tbl 指需要创建页表物理地址

virt 为映射到的虚拟地址

shift 为映射PGD索引在虚拟地址中的偏移,即PGDIR_SHIFT

ptrs 为PGD索引在虚拟地址中的位宽度,即PTRS_PER_PGD

tmp1 、tmp2为临时变量

创建过程: 1) 从virt中获取 PGD 页表 index

2) 获取一下级页表的基地址,即PMD页表物理基地址

3) 给PMD页表物理地址设置有效标识,表明该地址是有效的PMD                                 type table

4) 将PMD页表基地址写入PGD[index]

5) 将tbl指向一下级页表基地址,为下一级页表的创建做准备

/** Macro to create a table entry to the next page.**    tbl:    page table address,页表地址* virt:   virtual address,映射的虚拟地址* shift:  #imm page table shift, 映射位在字中的偏移量*   ptrs:   #imm pointers per table page,映射位宽度** Preserves:  virt* Corrupts: tmp1, tmp2* Returns:    tbl -> next level table page address*/.macro create_table_entry, tbl, virt, shift, ptrs, tmp1, tmp2
/* virt 向右移动shift位置,准备获取也表项索引 */lsr  \tmp1, \virt, #\shift   /* table index,按位与操作,保留了tmp1中对应的ptrs-1位数据其他位清零,即获取到页表项索引 */and \tmp1, \tmp1, #\ptrs - 1    /* tmp2 = tbl + PAGE_SIZE,idmap_pg_dir包含多级页表(每个页表占用一个page),故此处目的会获取下一级也表项入口地址 */add   \tmp2, \tbl, #PAGE_SIZE /* address of next table and entry type, 按位或操作,设置下一页表项标志位PMD_TYPE_TABLE */   orr \tmp2, \tmp2, #PMD_TYPE_TABLE   /* * 将下一级页表项基地址写入当前的页表项,* 当前页表项地址 = tbl + (tmp1 << 3) * 因每个页表项占用8byte,故对应页表相对页表基地址的偏移应为tmp1 << 3* 偏移应为tmp1 << 3
*/str   \tmp2, [\tbl, \tmp1, lsl #3]    /* next level table page ,指向下一级页表 */                             add \tbl, \tbl, #PAGE_SIZE  .endm

如上即完成了PGD页表项的填充,PMD页表项的填充过程与PGD页表项填充过程相同。PTE页表的创建通过   create_block_map 函数完成。

/** Macro to populate block entries in the page table for the start..end* virtual range (inclusive).* tbl 页表项的物理基地址* flags 需要映射页表flag* phys 需映射的物理地址* start 物理地址映射到的虚拟空间的起始地址* end 物理地址映射到的虚拟空间的结束地址* Preserves: tbl, flags* Corrupts:   phys, start, end, pstate*/.macro    create_block_map, tbl, flags, phys, start, end/* 获取需要映射的物理地址对应的PFN */lsr    \phys, \phys, #SWAPPER_BLOCK_SHIFT/* table start-index,获取start在tbl表中起始索引 */lsr  \start, \start, #SWAPPER_BLOCK_SHIFT    and \start, \start, #PTRS_PER_PTE - 1   /* table entry,获取物理地址所在的物理页地址,并给物理页地址设置页表flag */orr   \phys, \flags, \phys, lsl #SWAPPER_BLOCK_SHIFT  /* table end-index,获取start在tbl表结束索引 */lsr   \end, \end, #SWAPPER_BLOCK_SHIFTand \end, \end, #PTRS_PER_PTE - 1/* 将需要映射物理页地址 写入 虚拟地址对应的 tbl[index]*/
9999:   str \phys, [\tbl, \start, lsl #3]       // store the entryadd   \start, \start, #1          // next entryadd    \phys, \phys, #SWAPPER_BLOCK_SIZE       // next blockcmp    \start, \endb.ls    9999b.endm

PTE页表同上两级页表的创建过程比较类似,需要注意的点在于:1) 入参 ,因为是创建PTE页表项,所以需要传入要映射的物理地址phys;此时的tbl页表基地址也已经指向PTE 页表的物理基地址。2)创建过程,是将要映射的物理页地址填充到虚拟地址对应的PTE页表,要映射的空间大小通过入参中起始虚拟地址和结束虚拟地址来实现。 到此即完成三级页表的创建。

启动过程中要针对哪些物理地址创建虚拟映射?这就要说到提到的idmap_pg_dir 、swapper_pg_dir页表空间。

__create_page_tables:/** Create the identity mapping.*/adrp  x0, idmap_pg_dir    // __pa(idmap_pg_dir)adrp   x3, __idmap_text_start      // __pa(__idmap_text_start)create_pgd_entry x0, x3, x5, x6mov   x5, x3              // __pa(__idmap_text_start) @1adr_l    x6, __idmap_text_end        // __pa(__idmap_text_end)create_block_map x0, x7, x3, x5, x6/** Map the kernel image (starting with PHYS_OFFSET).*/adrp x0, swapper_pg_dir    // __pa(swapper_pg_dir)mov_q  x5, KIMAGE_VADDR + TEXT_OFFSET // compile time __va(_text)create_pgd_entry x0, x5, x3, x6adrp  x6, _end            // runtime __pa(_end)adrp   x3, _text           // runtime __pa(_text)sub   x6, x6, x3          // _end - _textadd  x6, x6, x5          // runtime __va(_end)create_block_map x0, x7, x3, x5, x6

如下是简化后的 idmap  和 kernel 映射代码,从代码逻辑可知:   

       idmap_pg_dir 是 __idmap_text_start 到 __idmap_text_end 段内核代码物理地址映射到虚拟地址的页表项空间,因 x5 = x3 = __pa(__idmap_text_start) 故该页表空间映射的虚拟地址与物理地址一致。__idmap_text_start 到 __idmap_text_end内存空间保存了section ".idmap.text" 代码,该代码函数主要实现MMU的开关。

swapper_pg_dir 是 _text 到 _end 内核代码运行的物理地址创建映射的页表空间,该页表空间的虚拟起始地址为 KIMAGE_VADDR + TEXT_OFFSET,虚拟空间大小为内核空间大小(_text -_end),物理起始地址为内核加载到物理内存_text的地址。

经此地址映射并开启MMU后, CPU访问的内核代码的虚拟地址与vmlinux.lds的内核链接地址及system.map符号表地址一致(vmlinux.lds地址为虚拟地址)。

备注:1) idmap_pg_dir 、swapper_pg_dir 保存的页表怎么使用?

在enable_mmu函数中idmap_pg_dir 、swapper_pg_dir的地址分别被存进了ttbr0_el1 和 ttbr1_el1, Arm64会根据其他状态寄存来决定使用哪个ttbr(Translation Table Base Register)。

2)__idmap_text_start 到__idmap_text_end空间通过idmap_pg_dir 、swapper_pg_dir两个页表都可以访问?

个人观点:是可以,idmap_pg_dir页表对应的物理空间为__idmap_text_start 到__idmap_text_end。 swapper_pg_dir页表项对应的物理空间为整个内核段,而__idmap_text_start 到__idmap_text_end段包含在内核段。

Linux页表 - - 启动过程临时页表创建过程相关推荐

  1. linux 直接映射 页表大小,linux 启动过程临时页表到底映射了多大内存?

    从linux-2.4内核开始,在建立临时页表的时候,一般的教科书都说是映射了8M的物理内存,但是为什么是映射8M呢?当时网上有资料说,8M足够了,但为什么就足够了,一直没有彻底搞清楚,今天又重新分析这 ...

  2. Linux内核启动及文件系统加载过程

    当u-boot开始执行bootcmd命令,就进入linux内核启动阶段 与 u-boot 类似,普通 Linux 内核的启动过程也可以分为两个阶段,但针对压缩了的内核如 uImage 就要包括内核自解 ...

  3. 从应用启动看Activity的创建过程

    2019独角兽企业重金招聘Python工程师标准>>> 本篇暂未完成. /* *Acitivity的创建过程: *应用程序的入口为ActivityThread的main函数. *在m ...

  4. 对象创建的过程 java_Java 对象创建过程

    构造器是静态方法. 1. Java中,用new()创建一个对象的时候,Java虚拟机首先去检查new指令的参数是否能够在方法区的常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否被加载. ...

  5. linux内存管理笔记(八)---内核临时页表的创建

    前面几节,我们已经看到了x86的分段和分页硬件单元把逻辑地址转换为线性地址,再由线性地址转换到物理地址的基本原理,那么这几章我们来主要是内核是怎么实现页表的创建,本章基于imx6ull和qemu来学习 ...

  6. linux内核启动以及文件系统的加载过程

    Linux 内核启动及文件系统加载过程 当u-boot 开始执行 bootcmd 命令,就进入 Linux 内核启动阶段.普通 Linux 内核的启动过程也可以分为两个阶段.本文以项目中使用的 lin ...

  7. 通过从代码层面分析Linux内核启动来探知操作系统的启动过程

    通过从代码层面分析Linux内核启动来探知操作系统的启动过程 前言说明 本篇为网易云课堂Linux内核分析课程的第三周作业,我将围绕Linux 3.18的内核中的start_kernel到init进程 ...

  8. JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配

    文章目录 前言 零.排序规范 1.happens-before原则 2.找文档位置 一.一线互联网企业关于对象面试题: (后面回答的就是这几个问题) 二.对象创建过程 三.对象在内存中的存储布局 1. ...

  9. mybatis都有哪些executor执行器_Mybatis的SqlSession创建过程详解

    前面mybatis的初始化过程分析完成,接下来是第二步SqlSession的创建. 创建过程总览 SqlSession创建过程如下图:创建过程还是比较简单的,首先是之前分析的SqlSessionFac ...

最新文章

  1. 有道翻译蛋的进阶:丁磊为其加冕称“王”
  2. 二叉树的建造、递归与非递归遍历
  3. Gridview Master/Detail JS
  4. python基础教程:数值与字符串类型
  5. python保存快捷键_超详细的Sublime Text配置python教程
  6. C语言梳排序Comb sort算法(附完整源码)
  7. 字母异位词分组—leetcode49
  8. 静态主席树总结(静态区间的k大)
  9. 【Envi风暴】ENVI中求两幅遥感影像的相关性(相关系数)
  10. 选项卡,下拉菜单操做时的页面数据更新,highcharts,d3 结合。
  11. 相关系数矩阵计算_corrplot包:相关性矩阵可视化
  12. Linux Shell高级技巧(二)
  13. c++ mysql ctime_CTime::Format
  14. MySQL CONCAT函数:字符串拼接
  15. 历史上的今天:阿帕网退役;Quintus 收购 Mustang;同步电流磁芯存储器获得专利...
  16. sql [Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause
  17. Express框架学习笔记-app.locals对象
  18. 【dede】列表页调用二三级导航栏
  19. 【电脑小白】提高ppt矢量图导出分辨率
  20. vue 引入 element-ui 报 es2015 的错

热门文章

  1. 【Python茴香豆系列】之 拍扁列表
  2. 如何用python计算年龄_python根据出生日期返回年龄的方法
  3. ryzen linux 搭配显卡,锐龙CPU搭配什么显卡好
  4. 教程 | 阿克曼结构移动机器人的gazebo仿真(九)
  5. cartographer导航的时候更新地图
  6. 双非计算机硕士好找工作吗,“双非”女硕士:找工作没人要,还不如211本科生,985/211真吃香...
  7. Python实例:3D旋转图片
  8. linux网络yum仓库
  9. android悬浮窗只只点击按钮,只有安卓才能体验的悬浮窗!果粉看了默默收起手机...
  10. 干货实操:微服务Spring Cloud 系列(二) Eureka服务发现与服务注册(strand alone)