👹
Carlos's Tech Blog
  • 🧔ECUs
    • ZYNQ_Documents
      • [ZYNQ] 构建ZYNQ的BSP工程
      • [ZYNQ] 启动流程
      • [ZYNQ] Secure Boot Flow
      • [ZYNQ] Provisioning Guideline
      • [ZYNQ] Decrypting Partition by the Decrypt Agent Using PUF key
      • [ZYNQ] enabling the cryptsetup on ramdisk
      • [ZYNQ] Encrypt external files based on file system using PUF key
      • [ZYNQ] Loading an Encrypted Linux kernel at U-Boot with a KUP Key
      • [ZYNQ] cross-compile the cryptsetup on Xilinx ZYNQ aarch64 platform
      • [ZYNQ] Linux Linaro系统镜像制作SD卡启动
    • S32G_Documents
      • [S32G] Going through the s32g hard/soft platform
      • [S32G] S32g247's Secure Boot using HSE firmware
        • S32g2 HSE key config
        • How S32g verify secure boot image
        • S32g secure boot signature generation
        • How to download and build S32g Secure boot image
        • [S32G] OTA with Secure Boot
    • RT117x_Documents
      • [RT-117x]IMX RT1170 Provisioning Guideline
      • [RT-117x] Going through the MX-RT1170 hard/soft platform
      • [RT-117x] i.MX-RT1170's Secure Boot
        • [RT-117x]Signing image with the HSM (SignServer)
    • LS104x_Documents
      • [LS104x] bsp project
      • [LS104x] boot flow
      • [LS104x] secure boot
      • [LS104x] Application Note, Using the PKCS#11 in TCU platform
      • [LS104x] 使用ostree更新rootfs
      • [LS104x] ostree的移植
      • [LS104x] Starting with Yocto
      • [LS104x] 使用FIT的kernel格式和initramfs
    • IMX6/8_Documents
      • [IMX6] Defining A U-Boot Command
      • NXP IMX6 嵌入式板子一些笔记
      • NXP-imx6 initialization
    • Vehicle_Apps
      • [SecOC] Tree
        • [SecOC] SecOC Freshness and MAC Truncation
  • 😾TECH
    • Rust Arm OS
      • ARMv7m_Using_The_RUST_Cross_Compiler
    • ARM
      • ARM-v7-M
        • 01_ARMv7-M_处理器架构技术综述
        • 02_ARMv7-M_编程模型与模式
        • 03_ARMv7-M_存储系统结构
        • 04_ARMv7-M_异常处理及中断处理
      • ARM-v8-A
        • 02_ARMv8_基本概念
        • 03_ARMv8_指令集介绍_加载指令集和存储指令集
        • 04_ARMv8_指令集_运算指令集
        • 05_ARMv8_指令集_跳转_比较与返回指令
        • 06_ARMv8_指令集_一些重要的指令
        • 0X_ARMv8_指令集_基于汇编的UART驱动
        • 07_ARMv8_汇编器Using as
        • 08_ARMv8_链接器和链接脚本
        • 09_ARMv8_内嵌汇编(内联汇编)Inline assembly
        • 10_ARMv8_异常处理(一) - 入口与返回、栈选择、异常向量表
        • 11_ARMv8_异常处理(二)- Legacy 中断处理
        • 12_ARMv8_异常处理(三)- GICv1/v2中断处理
        • 13_ARMv8_内存管理(一)-内存管理要素
        • 14_ARMv8_内存管理(二)-ARM的MMU设计
        • 15_ARMv8_内存管理(三)-MMU恒等映射及Linux实现
        • 16_ARMv8_高速缓存(一)cache要素
        • 17_ARMv8_高速缓存(二)ARM cache设计
        • 18_ARMv8_高速缓存(三)多核与一致性要素
        • 19_ARMv8_TLB管理(Translation Lookaside buffer)
        • 20_ARMv8_barrier(一)流水线和一致性模型
        • 21_ARMv8_barrier(二)内存屏障案例
      • ARM Boot Flow
        • 01_Embedded_ARMv7/v8 non-secure Boot Flow
        • 02_Embedded_ARMv8 ATF Secure Boot Flow (BL1/BL2/BL31)
        • 03_Embedded_ARMv8 BL33 Uboot Booting Flow
      • ARM Compiler
        • Compiler optimization and the volatile keyword
      • ARM Development
        • 在MACBOOK上搭建ARMv8架构的ARM开发环境
        • Starting with JLink debugger or QEMU
    • Linux
      • Kernel
        • 0x01_LinuxKernel_内核的启动(一)之启动前准备
        • 0x02_LinuxKernel_内核的启动(二)SMP多核处理器启动过程分析
        • 0x21_LinuxKernel_内核活动(一)之系统调用
        • 0x22_LinuxKernel_内核活动(二)中断体系结构(中断上文)
        • 0x23_LinuxKernel_内核活动(三)中断体系结构(中断下文)
        • 0x24_LinuxKernel_进程(一)进程的管理(生命周期、进程表示)
        • 0x25_LinuxKernel_进程(二)进程的调度器的实现
        • 0x26_LinuxKernel_设备驱动(一)综述与文件系统关联
        • 0x27_LinuxKernel_设备驱动(二)字符设备操作
        • 0x28_LinuxKernel_设备驱动(三)块设备操作
        • 0x29_LinuxKernel_设备驱动(四)资源与总线系统
        • 0x30_LinuxKernel_设备驱动(五)模块
        • 0x31_LinuxKernel_内存管理(一)物理页面、伙伴系统和slab分配器
        • 0x32_LinuxKernel_内存管理(二)虚拟内存管理、缺页与调试工具
        • 0x33_LinuxKernel_同步管理_原子操作_内存屏障_锁机制等
        • 01_LinuxDebug_调试理论和基础综述
      • Userspace
        • Linux-用户空间-多线程与同步
        • Linux进程之间的通信-管道(上)
        • Linux进程之间的通信-管道(下)
        • Linux进程之间的通信-信号量(System V)
        • Linux进程之间的通信-内存共享(System V)
        • Linux进程之间的通信-消息队列(System V)
        • Linux应用调试(一)方法、技巧和工具 - 综述
        • Linux应用调试(二)工具之coredump
        • Linux应用调试(三)工具之Valgrind
        • Linux机制之内存池
        • Linux机制之对象管理和引用计数(kobject/ktype/kset)
        • Linux机制copy_{to, from}_user
        • Linux设备树 - DTS语法、节点、设备树解析等
        • Linux System : Managing Linux Services - inittab & init.d
        • Linux System : Managing Linux Services - initramfs
      • Kernel Examples
        • Linux Driver - GPIO键盘驱动开发记录_OMAPL138
        • 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl
        • 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(二)之cdev与read、write
        • 基于OMAPL138的字符驱动_GPIO驱动AD9833(三)之中断申请IRQ
        • Linux内核调用SPI驱动_实现OLED显示功能
        • Linux内核调用I2C驱动_驱动嵌套驱动方法MPU6050
    • OPTEE
      • 01_OPTEE-OS_基础之(一)功能综述、简要介绍
      • 02_OPTEE-OS_基础之(二)TrustZone和ATF功能综述、简要介绍
      • 03_OPTEE-OS_系统集成之(一)编译、实例、在QEMU上执行
      • 05_OPTEE-OS_系统集成之(三)ATF启动过程
      • 06_OPTEE-OS_系统集成之(四)OPTEE镜像启动过程
      • 07_OPTEE-OS_系统集成之(五)REE侧上层软件
      • 08_OPTEE-OS_系统集成之(六)TEE的驱动
      • 09_OPTEE-OS_内核之(一)ARM核安全态和非安全态的切换
      • 10_OPTEE-OS_内核之(二)对安全监控模式的调用的处理
      • 11_OPTEE-OS_内核之(三)中断与异常的处理
      • 12_OPTEE-OS_内核之(四)对TA请求的处理
      • 13_OPTEE-OS_内核之(五)内存和cache管理
      • 14_OPTEE-OS_内核之(六)线程管理与并发
      • 15_OPTEE-OS_内核之(七)系统调用及IPC机制
      • 16_OPTEE-OS_应用之(一)TA镜像的签名和加载
      • 17_OPTEE-OS_应用之(二)密码学算法和安全存储
      • 18_OPTEE-OS_应用之(三)可信应用的开发
      • 19_OPTEE-OS_应用之(四)安全驱动开发
      • 20_OPTEE-OS_应用之(五)终端密钥在线下发系统
    • Binary
      • 01_ELF文件_目标文件格式
      • 02_ELF文件结构_浅析内部文件结构
      • 03_ELF文件_静态链接
      • 04_ELF文件_加载进程虚拟地址空间
      • 05_ELF文件_动态链接
      • 06_Linux的动态共享库
      • 07_ELF文件_堆和栈调用惯例以ARMv8为例
      • 08_ELF文件_运行库(入口、库、多线程)
      • 09_ELF文件_基于ARMv7的Linux系统调用原理
      • 10_ELF文件_ARM的镜像文件(.bin/.hex/.s19)
    • Build
      • 01_Script_makefile_summary
    • Rust
      • 02_SYS_RUST_文件IO
    • Security
      • Crypto
        • 1.0_Security_计算机安全概述及安全需求
        • 2.0_Security_随机数(伪随机数)
        • 3.0_Security_对称密钥算法加解密
        • 3.1_Security_对称密钥算法之AES
        • 3.2_Security_对称密钥算法之MAC(CMAC/HMAC)
        • 3.3_Security_对称密钥算法之AEAD
        • 8.0_Security_pkcs7(CMS)_embedded
        • 9.0_Security_pkcs11(HSM)_embedded
      • Tools
        • Openssl EVP to implement RSA and SM2 en/dec sign/verify
        • 基于Mac Silicon M1 的OpenSSL 编译
        • How to compile mbedtls library on Linux/Mac/Windows
    • Embedded
      • eMMC启动介质
  • 😃Design
    • Secure Boot
      • JY Secure Boot Desgin
    • FOTA
      • [FOTA] Module of ECUs' FOTA unit design
        • [FOTA] Tech key point: OSTree Deployment
        • [FOTA] Tech key point: repositories role for onboard
        • [FOTA] Tech key point: metadata management
        • [FOTA] Tech key point: ECU verifying and Decrpting
        • [FOTA] Tech key point: time server
      • [FOTA] Local-OTA for Embedded Linux System
    • Provisioning
      • [X-Shield] Module of the Embedded Boards initialization
    • Report
由 GitBook 提供支持
在本页
  • 15_ARMv8_内存管理(三)-MMU恒等映射及Linux实现
  • 1. 恒等映射
  • 1.1 boot stage MMU
  • 1.2 恒等映射的实现
  • 1.3 CPU初始化及MMU配置
  • 2. Ref
  1. TECH
  2. ARM
  3. ARM-v8-A

15_ARMv8_内存管理(三)-MMU恒等映射及Linux实现

https://github.com/carloscn/blog/issues/55

15_ARMv8_内存管理(三)-MMU恒等映射及Linux实现

  • 以ELF作为引子展开MMU的作用,为了和ELF做一些知识关联工作。[13_ARMv8_内存管理与MMU(一)-内存管理要素]

  • 以《操作系统概念》的虚拟内存管理一节,为MMU做一些理论和基础知识的储备。[13_ARMv8_内存管理与MMU(一)-内存管理要素]

  • 以《ARMv8体系架构》的MMU为例子,了解在CPU层级MMU的实现和配置。[13_ARMv8_内存管理与MMU(二) - ARMv8架构虚拟内存MMU]

  • 以《Linux内核》的内存管理为例子,来研究MMU怎么实现的。[Linux_Kernel_MMU设计(一)]

1. 恒等映射

1.1 boot stage MMU

在讨论恒等映射之前,我们我们先来弄清楚一个问题,为什么在uboot阶段需要关闭?

uboot阶段关闭MMU是一种推荐做法,这个在uboot的文档和Linux文档上都有体现。不过,uboot阶段是可以开启MMU的,可能是为了一些测试或一些可执行文件在boot环境,也有文献表明在一些加解密安全特性下,在boot环境考虑使能MMU。

如果在boot阶段使能MMU,要从两个维度考虑,第一个,页表映射引入的问题;第二个,d-cache关闭和i-cache开启的功能。

在某些处理器里面,并不是像ARMv8体系架构一样,cache隶属于MMU下面的一个模块,而是cache和MMU是分立的两个机制,MMU的功能仅仅是页表映射。因此这就引发了一些讨论,我们从上一个内存管理(二)里面提到ARM将内存划分,device memory和normal memeory,normal memory就是一般性的存储空间,比如ROM, RAM, Flash;而device memory就是外围设备映射的空间,这个分水岭是以cacheing功能做的划分,如果此时我们并没有打开MMU,而开了D-Cache,这有可能那块内存刚好被映射到device memory上面,如果刚好是一些比如fifo读敏感的设备,那么就会发生很致命的错误。在这种情况下就必须要使能MMU,让MMU页表的映射(MMU内存有相应的属性和合法性检查),这就会被视为安全的访问。简而言之,在没有MMU的保护下,cacheing设备内存是一个非常错误的操作。

I-Cache打开没有问题,因为指令部分不会从device memory这个位置去取指,而是从normal memory去取指,就算是没有MMU的属性保护,也不会发生错误。开启I-Cache会提高boot程序的执行效率。如果MMU在boot阶段关闭,且开了I-Cache,这里需要注意的是,当使能MMU的时候一定要对cache内的数据进行invalidate操作,否则cache内部可能会记录在MMU没有映射之前VMA和PHY恒等映射的地址,而不是MMU映射后的地址。还要注意在JTAG的情境下,我们会把程序直接load到ram里面,此时i-cache没有周期性的从ram刷新数据,所以我们运行程序的时候,可能就出现,程序可能完全是ram中的,可能是ram的也可能是cache旧的,或者完全是cache中旧的,因此要注意i-cache使能情况下,jtag的cache一致性。

所以,综上考虑, MMU及cache在boot内部的推荐配置通常是:

  • MMU关闭

  • D-Cache关闭

  • I-Cache开启

1.2 恒等映射的实现

在ARMv8有个问题,无法达到我们之前说的那种MMU配置和cache分离的,使能cache功能的前置条件是必须使能MMU,我们这部分主要来讨论一下,如何来配置armv8的MMU,这部分会被应用于操作系统中。

现在处理器大多数是多级流水线体系结构,处理器会预先取多条指令到流水中。这里有个MMU的生效的上文和下文的切换需要注意,我们在配置MMU的时候,是有一些指令的,而且这些指令以物理地址的方式被预取到流水线中;当MMU生效之后,预取的指令会使用虚拟地址来访问,这时候会到MMU中找到对应的物理地址。为了保证处理器在开启MMU后,能够完成从物理地址到虚拟地址的平滑过渡,首先会创建恒等映射(VA==PA,identity mapping)。如图所示,在内存底部建立一个小范围的映射关系,让VA=PA。

我们这部分就在cortex-a72上面建立一个恒等映射,低512MB的内存恒等映射到虚拟地址0~512MB的地址空间,采用4KB页帧粒度,4级页表,48位的地址宽度创建该映射。(借鉴Linux内核的页表实现)。整个流程如下:

flowchart TD
    A[在ld中映射idmap_pg_dir] --> B[memset with idmap_pg_dir, 0, PAGE_SIZE]
    B --> C[create_identical_mapping]
    C --> D[cpu_init]
    D --> E[enable_mmu]    

页表定义

在Linux内核里面页表定义和我们ARM64上面有点出入:

arm64
Linux kernel

L0

页全局目录(Page Global Directory, PGD)

L1

页上级目录(Page Upper Directory, PUD)

L2

页中间目录(Page Middle DIrectory, PMD)

L3

页表(Page Table, PT)

上面4个页表名称,对应arm64的l0-l3我已经画出来了。

### 区域映射

    +--------+--------+--------+--------+--------+--------+--------+--------+
    |63    56|55    48|47    40|39    32|31    24|23    16|15     8|7      0|
    +--------+--------+--------+--------+--------+--------+--------+--------+
     |                 |         |         |         |         |
     |                 |         |         |         |         v
     |                 |         |         |         |   [11:0]  in-page offset
     |                 |         |         |         +-> [20:12] L3 index
     |                 |         |         +-----------> [29:21] L2 index
     |                 |         +---------------------> [38:30] L1 index
     |                 +-------------------------------> [47:39] L0 index
     +-------------------------------------------------> [63] TTBR0/1

我们用宏定义来描述PGD、PUD、PMD、PT的属性,VA_BIS代表是48位宽度的虚拟地址。

#define     VA_BIS          (48UL)
#define     PGDIR_SHIFT     (39UL)
#define     PGDIR_SIZE      (1UL << PGDIR_SHIFT)
#define     PGDIR_MASK      (~(PGDIR_SIZE - 1))
#define     PTRS_PER_PGD    (1 << (VA_BITS - PGDIR_SHIFT))

#define     PUD_SHIFT       (30UL)
#define     PUD_SIZE        (1UL << PUD_SHIFT)
#define     PUD_MASK        (~(PUD_SIZE - 1))
#define     PTRS_PER_PUD    (1 << (PGDIR_SHIFT - PUD_SHIFT))

#define     PMD_SHIFT       (21UL)
#define     PMD_SIZE        (1UL << PMD_SHIFT)
#define     PMD_MASK        (~(PMD_SIZE - 1))
#define     PTRS_PER_PMD    (1 << (PUD_SHIFT - PMD_SHIFT))

#define     PTE_SHIFT       (12UL)
#define     PTE_SIZE        (1UL << PTE_SHIFT)
#define     PTE_MASK        (~(PTE_SIZE - 1))
#define     PTRS_PER_PTE    (1 << (PMD_SHIFT - PTE_SHIFT))

除此之外还有一种section的映射方式,ARMv8体系页表结构还支持2MB大小块类型的映射。

1、PGD(9 bits)--->PMD(9 bits)--->PTE(9 bits)--->PAGE(12 bits) 2、PGD(9 bits)--->PMD(9 bits)--->SECTION(21 bits)

#define     SECTION_SHIFT   (21UL)
#define     SECTION_SIZE    (1UL << SECTION_SHIFT)
#define     SECTION_MASK    (~(SECTION_SIZE - 1))

PTE(L3)页表属性

这个图在内存管理(二)描述地址属性的有,现在需要定义出关于属性的宏:

+---+--------+-----+-----+---+------------------------+---+----+----+----+----+------+----+----+
| R |   SW   | UXN | PXN | R | Output address [47:12] | R | AF | SH | AP | NS | INDX | TB | VB |
+---+--------+-----+-----+---+------------------------+---+----+----+----+----+------+----+----+
 63  58    55 54    53    52  47                    12 11  10   9  8 7  6 5    4    2 1    0

R    - reserve
SW   - reserved for software use
UXN  - unprivileged execute never
PXN  - privileged execute never
AF   - access flag
SH   - shareable attribute
AP   - access permission
NS   - security bit
INDX - index into MAIR register
TB   - table descriptor bit
VB   - validity descriptor bit
  • PTE_TYPE_PAGE : page类型: 11B

  • PTE_USER : 访问权限access permission: AP[6]: user

  • PTE_RDONLY : 访问权限access permission: AP[7]: readonly

  • PTE_SHARED : 共享shareable: SH[9:8]

  • PTE_AF : 访问表示access flag : AF[10]

  • PTE_PXN : 执行权限privileged execute: pxn[53]

  • PTE_UXN : 执行权限unprivileged execute: uxn[54]

// level 3 page attribute config (PTE)
#define     PTE_TYPE_MASK   (3UL << 0)
#define     PTE_TYPE_FAULT  (0UL << 0)
#define     PTE_TYPE_PAGE   (3UL << 0)
#define     PTE_TABLE_BIT   (1UL << 1)
#define     PTE_USER        (1UL << 6)
#define     PTE_RDONLY      (1UL << 7)
#define     PTE_SHARED      (1UL << 8)
#define     PTE_AF          (1UL << 10)
#define     PTE_NG          (1UL << 11)
#define     PTE_DBM         (1UL << 51)
#define     PTE_CONT        (1UL << 52)
#define     PTE_PXN         (1UL << 53)
#define     PTE_UXN         (1UL << 54)
#define     PTE_HYP_XN      (1UL << 54)

以上描述的是地址属性,下面将描述内存属性,内存属性是描述device memory与normal memory的,内存属性没有放在页表中,而是放在MAIR_ELn寄存器中,这个寄存器把64位的内存分为了8个段,这8个段中在页表结构中第11位(AttrIndex[2:0],占3位正好表示0-7)来索引MAIR_ELn的段号,进而就能拿到内存属性的信息。

#define     PTE_ATTRINDEX(t) ((t) << 2)
#define     PTE_ATTRINDEX_MASK (7 << 2)

内存属性四种:

  • Device-nGnRnE

  • Device-nGnRE

  • Device-nGRE

  • Device-GRE

#define PTE_VALID		(1UL << 0)
#define PTE_WRITE		(PTE_DBM)		 /* same as DBM (51) */
#define PTE_DIRTY		(1UL << 55)
#define PTE_SPECIAL		(1UL << 56)
#define PTE_PROT_NONE		(1UL << 58) /* only when !PTE_VALID */

#define _PROT_DEFAULT	(PTE_TYPE_PAGE | PTE_AF | PTE_SHARED)
#define PROT_DEFAULT (_PROT_DEFAULT)

#define PAGE_KERNEL_RO		((PROT_NORMAL & ~PTE_WRITE) | PTE_RDONLY)
#define PAGE_KERNEL_ROX		((PROT_NORMAL & ~(PTE_WRITE | PTE_PXN)) | PTE_RDONLY)
#define PAGE_KERNEL_EXEC	(PROT_NORMAL & ~PTE_PXN)

#define PROT_DEVICE_nGnRnE	(PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_DIRTY | PTE_WRITE | PTE_ATTRINDX(MT_DEVICE_nGnRnE))
#define PROT_DEVICE_nGnRE	(PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_DIRTY | PTE_WRITE | PTE_ATTRINDX(MT_DEVICE_nGnRE))
#define PROT_NORMAL_NC		(PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_DIRTY | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL_NC))
#define PROT_NORMAL_WT		(PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_DIRTY | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL_WT))
#define PROT_NORMAL (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_DIRTY | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL))

页表数据结构

建立页表项目pte_t, pmd_t, pud_t, pgd_t。

typedef unsigned long long u64;
typedef u64 pteval_t;
typedef u64 pmdval_t;
typedef u64 pudval_t;
typedef u64 pgdval_t;

typedef struct {
    pteval_t pte;
} pte_t;
#define pte_val(x) ((x).pte)
#define __pte(x) ((pte_t) {(x)})

typedef struct {
	pteval_t pte;
} pte_t;
#define pte_val(x) ((x).pte)
#define __pte(x) ((pte_t) { (x) })

typedef struct {
	pmdval_t pmd;
} pmd_t;
#define pmd_val(x) ((x).pmd)
#define __pmd(x) ((pmd_t) { (x) })

typedef struct {
	pudval_t pud;
} pud_t;
#define pud_val(x) ((x).pud)
#define __pud(x) ((pud_t) { (x) })

typedef struct {
	pgdval_t pgd;
} pgd_t;
#define pgd_val(x) ((x).pgd)
#define __pgd(x) ((pgd_t) { (x) })

创建页表

我们要建立一个代码段、数据段还有外设段的恒等映射。

static void create_identical_mapping(void)
{
    unsigned long start;
    unsigned long end;
    // code section identical mapping
    start = (unsigned long)_text_boot;
    end = (unsigned long)_etext;
    __create_pdg_mapping((pdg_t *)idmap_pg_dir,
                         start,
                         start,
                         end - start,
                         PAGE_KERNEL_ROX,
                         early_pgtable_alloc,
                         0);
    // data section identical mapping.
    start = PAGE_ALIGN((unsigned long)_etext);
    end = TOTAL_MEMORY;
    __create_pdg_mapping((pdg_t *)idmap_pg_dir,
                         start,
                         start,
                         end - start,
                         PAGE_KERNEL,
                         early_pgtable_alloc,
                         0);
    // mmio section identical mapping
    start = PBASE;
    end = 0x2000000;
    __create_pdg_mapping((pdg_t *)idmap_pg_dir,
                         start,
                         start,
                         end,
                         PROT_DEVICE_nGnRnE,
                         early_pgtable_alloc,
                         0);
}

首先在链接文件中为pgd页表预留出4KB的内存空间:

	/*
	 * 分配一page的空间,用来存放页表
	 *
	 * 起始地址需要以page对齐
	 */
	. = ALIGN(4096);
	idmap_pg_dir = .;
	. += 4096;

我们还需要制作一个函数,将ld文件中idmap_pg_dir和我们页表的数据结构联系(映射起来),这部分可以参考https://elixir.bootlin.com/linux/v4.2/source/arch/arm64/mm/mmu.c#L242 。

static void __create_pdg_mapping(pgd_t *pgdir,
                                 unsigned long phys,
                                 unsigned long virt,
                                 unsigned long size,
                                 unsigned long prot,
                                 unsigned long (*alloc_pgtable)(void),
                                 unsigned long flags);
参数
意义

pgdir

页全局目录(Page Global Directory, PGD)的基地址

phys

表示要映射的物理地址的起始地址

virt

表示要映射的虚拟地址的起始地址

size

映射大小

prot

内存映射属性

alloc_pgtable

用来分配下一级页表的函数,PGD页表的是在ld文件中分配的,但是后面的页表是要在动态过程中分配。

flags

传递给页表创建过程中的标识位。

在这个函数中,会递进式的创建次一级的页表空间:

  • __create_pdg_mapping

    • alloc_init_pud -> early_pgtable_alloc

      • alloc_init_pmd -> early_pgtable_alloc

        • alloc_init_pte -> early_pgtable_alloc

每一级页表都通过调用early_pgtable_alloc来分配页表4KB的空间。

static unsigned long early_pgtable_alloc(void)
{
    unsigned long phys;
    phys = get_free_page();
    memset((void *)phys, 0, PAGE_SIZE);
    return phys;
}

这里面有个get_free_page(),用于获取当前操作系统的空闲页表,在内存管理里面,这部分就会用到伙伴系统和slab机制。这里我们做一个比较简陋的线性数组实现。

#define NR_PAGES    (TOTAL_MEMORY/PAGE_SIZE)
static unsigned short mem_map[NR_PAGES] = {0,};

#define LOW_MEMORY (0x400000)       /*4MB*/
#define TOTAL_MEMORY (512 * 0x100000) /*512MB*/

unsigned long get_free_page(void)
{
    int i;
    for (i = 0; i < NR_PAGE; i ++) {
        if (mem_map[i] == 0) {
            mem_map[i] = 1;
            return LOW_MEMORY + i * PAGE_SIZE;
        }
    }
    return 0;
}

void free_page(unsigned long p)
{
    mem_map[(p - LOW_MEMORY)/PAGE_SIZE] = 0;
}

1.3 CPU初始化及MMU配置

1.3.1 CPU初始化

CPU的初始化主要是对MAIR和TCR寄存器的配置。

static void cpu_init(void)
{
	unsigned long mair = 0;
	unsigned long tcr = 0;
	unsigned long tmp;
	unsigned long parang;
    // VMID以及EL1中所有的TLB表项失效
	asm("tlbi vmalle1");
    // 保证指令执行完毕。
	dsb(nsh);

	write_sysreg(3UL << 20, cpacr_el1);
	write_sysreg(1 << 12, mdscr_el1);
    // 配置MAIR寄存器
	mair = MAIR(0x00UL, MT_DEVICE_nGnRnE) |
	       MAIR(0x04UL, MT_DEVICE_nGnRE) |
	       MAIR(0x0cUL, MT_DEVICE_GRE) |
	       MAIR(0x44UL, MT_NORMAL_NC) |
	       MAIR(0xffUL, MT_NORMAL) |
	       MAIR(0xbbUL, MT_NORMAL_WT);
	write_sysreg(mair, mair_el1);
    // 配置TCR寄存器。
	tcr = TCR_TxSZ(VA_BITS) | TCR_TG_FLAGS;

	tmp = read_sysreg(ID_AA64MMFR0_EL1);
	parang = tmp & 0xf;
	if (parang > ID_AA64MMFR0_PARANGE_48)
		parang = ID_AA64MMFR0_PARANGE_48;

	tcr |= parang << TCR_IPS_SHIFT;

	write_sysreg(tcr, tcr_el1);
}

1.3.2 开启MMU

static int enable_mmu(void)
{
	unsigned long tmp;
	int tgran4;
    // 检查是否支持4K页面粒度
	tmp = read_sysreg(ID_AA64MMFR0_EL1);
	tgran4 = (tmp >> ID_AA64MMFR0_TGRAN4_SHIFT) & 0xf;
	if (tgran4 != ID_AA64MMFR0_TGRAN4_SUPPORTED)
		return -1;
    // 写pgd页地址到 ttbr0_el1中
	write_sysreg(idmap_pg_dir, ttbr0_el1);
	isb();
    // 写完这个mmu就打开了
	write_sysreg(SCTLR_ELx_M, sctlr_el1);
	isb();
    // 让icache指令失效
	asm("ic iallu");
	dsb(nsh);
	isb();

	return 0;
}

可以写一个测试用例去测试:

int test_access_map_address(void)
{
    unsigned long address = TOTAL_MEMORY - 4096;
    *(unsigned long *)address = 0x55;
    return 0;
}

int test_access_unmap_address(void)
{
    unsigned long address = TOTAL_MEMORY + 4096;
    *(unsigned long *)address = 0x55;
    return 0;
}

2. Ref

  1. PATCH 1/2] ARM: allow booting with MMU enabled

  2. ARM Bootloader: Disable MMU and Caches- second comments form artless noise

  3. linux内存管理笔记(三十七)----临时页表映射过程

  4. AArch64 MMU Programming

上一页14_ARMv8_内存管理(二)-ARM的MMU设计下一页16_ARMv8_高速缓存(一)cache要素

最后更新于1年前

image-20220506140125504
image-20220506140125504
image-20220506154035570
image-20220506154035570
image-20220506154035570
image-20220506154035570
😾