hnu操作系统-lab8

HNU OSLab 8

实验目的

理解和实现操作系统中的内存管理机制
理解分页原理:学习操作系统中如何使用分页技术来管理内存,包括虚拟地址到物理地址的转换过程。
页表的创建和管理:编写代码来创建和管理页表,这是实现分页内存管理的核心部分。
内存映射配置:通过定义内存映射区域(mmu_mmap_region_s结构体数组),设置不同
区域的内存属性,如缓存共享、设备属性等。)MMU 寄存器配置:学习并配置 MMU 的相关寄存器,如 TCR(Translation Control Register)、
TTBR (Translation Table Base Register)、MAIR(Memory Attribute Indirection Register)等。
缓存和 TLB 的刷新:理解并实现在内存映射更新后刷新数据缓存、指令缓存和 TLB 的过
程。
调试和测试:通过调试确保 MMU 正确启动并运行,测试不同的内存访问以验证分页机制的正确性。

前序知识

Armv8的地址转换

TTBR0指向整个虚拟空间下半部分通常用于应用程序的空间,TTBR1指向虚拟空间的上半部分通常用于内核的空间。其中TTBR0除了在EL1中存在外,也在EL2 and EL3中存在,但TTBR1只在EL1中存在。

TTBR0_ELn 和 TTBR1_ELn 是页表基地址寄存器,地址转换的过程如下所示。

单级页表地址转换机制说明​

(基于64KB粒度 & 42位虚拟地址)

​1. 页表基址选择​

  • ​TTBR1选择条件​​:当虚拟地址高位 VA[63:42] 全为1时,使用寄存器TTBR1作为一级页表基址。
  • ​TTBR0选择条件​​:当 VA[63:42] 全为0时,使用寄存器TTBR0作为一级页表基址。

​2. 页表结构与索引​

  • ​页表规模​​:一级页表包含8,192个64位页表项(PTE)。
  • ​索引方式​​:通过虚拟地址的VA[41:29](13位)索引一级页表项。

​3. 页表项检查​

MMU会验证目标页表项的以下属性:

  • ​有效性​​(Valid)
  • ​访问权限​​(Memory Access Permission)
    若检查通过,则继续转换流程。

​4. 大页映射(512MB块描述符)​

  • ​物理地址生成​​:
    • ​高19位​​:取自页表项的[47:29] → 对应PA[47:29]
    • ​低29位​​:直接使用虚拟地址的VA[28:0] → 对应PA[28:0]
  • ​输出结果​​:最终生成48位物理地址PA[47:0],并附带页表项中的附加属性(如内存属性、权限等)。

​5. 单级转换的局限性​

  • ​地址空间划分粗糙​​:仅支持512MB大页,无法实现细粒度(如4KB页)的内存管理。
  • ​扩展方案​​:实际系统中通常采用​​多级页表​​(如二级页表),通过一级页表项指向二级页表,从而支持更灵活的地址空间划分。

MMU管理实现

新建 src/bsp/mmu.c 文件

头文件与全局变量​

#include "prt_typedef.h" // 基础类型定义(如U64/U32)
#include "prt_module.h" // 模块相关定义
#include "prt_errno.h" // 错误码定义
#include "mmu.h" // MMU相关宏和结构体
#include "prt_task.h" // 任务管理相关
extern U64 g_mmu_page_begin; // 页表内存起始地址(链接脚本定义) extern U64 g_mmu_page_end; // 页表内存结束地址

内存映射配置​

static mmu_mmap_region_s g_mem_map_info[] = {
{
.virt = 0x0, // 虚拟地址起点
.phys = 0x0, // 物理地址起点
.size = 0x40000000, // 映射大小1GB
.max_level = 0x2, // 最大页表级别(0-3)
.attrs = MMU_ATTR_DEVICE_NGNRNE | MMU_ACCESS_RWX, // 设备内存属性(非缓存、可读写执行)
}, {
.virt = 0x40000000, // 第二段映射:虚拟地址0x40000000
.phys = 0x40000000, // 映射到相同物理地址
.size = 0x40000000, // 1GB大小
.max_level = 0x2,
.attrs = MMU_ATTR_CACHE_SHARE | MMU_ACCESS_RWX, // 缓存内存属性(可读写执行)
}
};

  • ​作用​​:定义两段内存映射区域:
    • ​设备内存​​:0x0-0x40000000,非缓存。
    • ​普通内存​​:0x40000000-0x80000000,带缓存。
    • 这两段内存映射区域的存在,是为了将设备内存和普通内存分离,对他们设置不同的属性,确保了内存访问的正确性和系统的性能优化。

MMU控制结构体​

`static mmu_ctrl_s g_mmu_ctrl = { 0 }; // 全局MMU控制状态 结构体在mmu.h中定义

TCR寄存器配置​

根据内存映射配置(g_mem_map_info)和页表粒度(4K/64K),计算并返回 TCR 寄存器值,同时可选地返回物理地址位数(ips)和虚拟地址位数(va_bits)。TRC寄存器用于控制内存映射和地址转换的参数。
static U64 mmu_get_tcr(U32 *pips, U32 *pva_bits)
{
==//计算最大虚拟地址==
    U64 max_addr = 0;
    U64 ips, va_bits;
    U64 tcr;
    U32 i;
    U32 mmu_table_num = sizeof(g_mem_map_info) / sizeof(mmu_mmap_region_s);
    // 根据g_mem_map_info表计算所使用的虚拟地址的最大值
    for (i = 0; i < mmu_table_num; ++i) {
        max_addr = MAX(max_addr, g_mem_map_info[i].virt + g_mem_map_info[i].size);
          }

[!NOTE]
  遍历 g_mem_map_info 数组,找到所有内存映射区域中最大的虚拟地址(virt + size)。若 g_mem_map_info 定义了两段 1GB 的映射(0x0 和 0x40000000),则 max_addr = 0x80000000

   

   ==// 依据虚拟地址最大值(max_addr)计算虚拟地址所需的位数,==
    // 实际上应该分别计算物理地址的ips和虚拟地址的va_bits,而不是如下同时进行。
    //如果最大虚拟地址大于44位,需要5级物理地址和48位虚拟地址
    if (max_addr > (1ULL << MMU_BITS_44)) {
        ips = MMU_PHY_ADDR_LEVEL_5;//5级物理地址
        va_bits = MMU_BITS_48;//48位虚拟地址
    } else if (max_addr > (1ULL << MMU_BITS_42)) {
        ips = MMU_PHY_ADDR_LEVEL_4;//
        va_bits = MMU_BITS_44;
    } else if (max_addr > (1ULL << MMU_BITS_40)) {
        ips = MMU_PHY_ADDR_LEVEL_3;
        va_bits = MMU_BITS_42;
    } else if (max_addr > (1ULL << MMU_BITS_36)) {
        ips = MMU_PHY_ADDR_LEVEL_2;
        va_bits = MMU_BITS_40;
    } else if (max_addr > (1ULL << MMU_BITS_32)) {
        ips = MMU_PHY_ADDR_LEVEL_1;
        va_bits = MMU_BITS_36;
    } else {
        ips = MMU_PHY_ADDR_LEVEL_0;
        va_bits = MMU_BITS_32;
    }

   ==//构建Translation Control Register寄存器的值,tcr可控制TTBR0_EL1和TTBR1_EL1的影响==
    tcr = TCR_EL1_RSVD | TCR_IPS(ips);//初始化tcr 保留位+物理地址位数
    ==//页表粒度配置==
    if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
        tcr |= TCR_TG0_4K | TCR_SHARED_INNER | TCR_ORGN_WBWA | TCR_IRGN_WBWA;//页表粒度 共享内存属性,读写设置(写回,写分配)
    } else {
        tcr |= TCR_TG0_64K | TCR_SHARED_INNER | TCR_ORGN_WBWA | TCR_IRGN_WBWA;
    }

[!NOTE]

  • TCR_TG0_4K/TCR_TG0_64K​:设置页表粒度(4KB 或 64KB)。
  • TCR_SHARED_INNER​:共享内存属性(Inner Shareable)。
  • TCR_ORGN_WBWA/TCR_IRGN_WBWA​:
    • ​ORGN​​(Outer Cacheability):Write-Back, Write-Allocate。
    • ​IRGN​​(Inner Cacheability):Write-Back, Write-Allocate。

// 地址空间范围配置
    `tcr |= TCR_T0SZ(va_bits);   // Memory region 2^(64-T0SZ)

[!NOTE]
TCR_T0SZ(va_bits)​:

  • 计算方式:T0SZ = 64 - va_bits
  • 作用:定义 TTBR0_EL1 管理的地址空间范围为 2^(64 - T0SZ)

// 选择性的返回输出参数ips/va_bits
    if (pips != NULL) {
        *pips = ips;
    }

    if (pva_bits != NULL) {
        *pva_bits = va_bits;
    }

    return tcr;
}

获取页表类型

static U32 mmu_get_pte_type(U64 const *pte)
{
    return (U32)(*pte & PTE_TYPE_MASK);//提取页表类型
}

根据页表项级别计算当个页表项表示的范围(位数)

static U32 mmu_level2shift(U32 level)
{
    //计算某页表项覆盖的地址范围位数(4K/64K粒度不同)
    if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
        return (U32)(MMU_BITS_12 + MMU_BITS_9 * (MMU_LEVEL_3 - level));
    } else {
        return (U32)(MMU_BITS_16 + MMU_BITS_13 * (MMU_LEVEL_3 - level));
    }
}

根据虚拟地址找到对于级别的页表项

根据虚拟地址 addr 和页表级别 level,从顶级页表开始逐级向下查找,返回目标级别的页表项指针。若中间遇到无效项或块描述符(Block Descriptor),则提前终止并返回 NULL

static U64 *mmu_find_pte(U64 addr, U32 level)
{
    U64 *pte = NULL;
    U64 idx;
    U32 i;

    if (level < g_mmu_ctrl.start_level) {
        return NULL;
    }

    pte = (U64 *)g_mmu_ctrl.tlb_addr;//初始化为顶级页表的指针

    // 从顶级页表开始,直到找到所需level级别的页表项或返回NULL
    for (i = g_mmu_ctrl.start_level; i < MMU_LEVEL_MAX; ++i) {
        // 依据级别i计算页表项在页表中的索引idx
        if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
            idx = (addr >> mmu_level2shift(i)) & 0x1FF;//使用9位索引
        } else {
            idx = (addr >> mmu_level2shift(i)) & 0x1FFF;//使用13位索引
        }

        // 找到对应的页表项
        pte += idx;

        // 如果是需要level级别的页表项则返回
        if (i == level) {
            return pte;
        }

        // 从顶级页表开始找,
        // 找到当前级别页表项不是有效的(无效或是block entry)直接返回NULL
        if (mmu_get_pte_type(pte) != PTE_TYPE_TABLE) {
            return NULL;
        }

        // 不是所需级别但pte指向有效,依据页表粒度准备访问下级页表
        if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
            pte = (U64 *)(*pte & PTE_TABLE_ADDR_MARK_4K);
        } else {
            pte = (U64 *)(*pte & PTE_TABLE_ADDR_MARK_64K);
        }
    }

    return NULL;
}

根据页表粒度在页表区域新建一个页表,返回页表起始位置

static U64 *mmu_create_table(void)
{
    U32 pt_len;
    U64 *new_table = (U64 *)g_mmu_ctrl.tlb_fillptr;

//确定页表长度

    if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
        pt_len = MAX_PTE_ENTRIES_4K * sizeof(U64);
    } else {
        pt_len = MAX_PTE_ENTRIES_64K * sizeof(U64);
    }

    // 根据页表粒度在页表区域新建一个页表(4K或64K)
    g_mmu_ctrl.tlb_fillptr += pt_len;

    if (g_mmu_ctrl.tlb_fillptr - g_mmu_ctrl.tlb_addr > g_mmu_ctrl.tlb_size) {
        return NULL;
    }

    // 初始化页表全为0,因此该页表所有的页表项初始都是无效页表项PTE_TYPE_FAULT
    // (void)memset_s((void *)new_table, MAX_PTE_ENTRIES_64K * sizeof(U64), 0, pt_len);
    U64 *tmp = new_table;
    for(int i = 0; i < pt_len; i+=sizeof(U64)){
        *tmp = 0;
        tmp++;
    }

    return new_table;
}

将一个页表项指向一个新的页表

1
2
3
static void mmu_set_pte_table(U64 *pte, U64 *table) {
*pte = PTE_TYPE_TABLE | (U64)table;
}
  • ​作用​​:设置页表项为​​中间表描述符​​,指向下级页表。
  • ​关键点​​:
    • PTE_TYPE_TABLE:标记该描述符类型为页表(非块或页)。
    • (U64)table:下级页表的物理地址(低48位有效,需对齐)。

依据mmu_mmap_region_s填充pte

1
2
3
4
5
6
7
8
9
10
11
12
13
static S32 mmu_add_map_pte_process(mmu_mmap_region_s const *map, U64 *pte, U64 phys, U32 level) {
if (level < map->max_level) {
// 需要创建下级页表
if (mmu_get_pte_type(pte) == PTE_TYPE_FAULT) {
new_table = mmu_create_table();//返回创建的新页表地址
mmu_set_pte_table(pte, new_table); // 指向新页表
}
} else if (level == MMU_LEVEL_3) {
*pte = phys | map->attrs | PTE_TYPE_PAGE; // 最后一级:页描述符
} else {
*pte = phys | map->attrs | PTE_TYPE_BLOCK; // 中间级别:块描述符
}
}
  • ​逻辑分支​​:
    1. ​上级页表项​​(level < max_level):创建下级页表并链接。
    2. ​最后一级(L3)​​:设置为页描述符(4KB/64KB页)。
    3. ​中间级别​​:设置为块描述符(如1GB/2MB大页)。
  • ​参数​​:
    • map:内存映射配置(属性、最大级别等)。
    • phys:当前级别的物理地址基址。

依据 mmu_mmap_region_s 的定义,生成 mmu 映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static S32 mmu_add_map(mmu_mmap_region_s const *map)
{
    U64 virt = map->virt;
    U64 phys = map->phys;
    U64 max_level = map->max_level;
    U64 start_level = g_mmu_ctrl.start_level;
    U64 block_size = 0;
    U64 map_size = 0;
    U32 level;
    U64 *pte = NULL;
    S32 ret;

    if (map->max_level <= start_level) {
        return -2;
    }

    while (map_size < map->size) {
        // 从起始级别start_level开始遍历页表。注意起始级别页表肯定存在
        for (level = start_level; level <= max_level; ++level) {
            // 找到对应level的页表项
            pte = mmu_find_pte(virt, level);
            if (pte == NULL) {
                return -3;
            }

            // 如果为上级页表项且pte指向无效,新建下级页表且pte指向该新建的页表
            // 如果为最低页表项或到达设定级别页表项,直接设置页表项的值
            ret = mmu_add_map_pte_process(map, pte, phys, level);
            if (ret) {
                return ret;
            }

            if (level != start_level) {
                block_size = 1ULL << mmu_level2shift(level);
            }
        }

        virt += block_size;
        phys += block_size;
        map_size += block_size;
    }

    return 0;
}
  • ​作用​​:按map配置逐级构建页表映射。
  • ​关键流程​​:
    1. start_level开始,逐级查找或创建页表项。
      2. 如果为上级页表项且pte指向无效,新建下级页表且pte指向该新建的页表
              3. 如果为最低页表项或到达设定级别页表项,直接设置页表项的值
    2. 更新地址和映射大小

mmu寄存器配置

通过内联汇编直接操作 ARMv8-A 的系统寄存器,完成以下操作:

  1. ​设置页表基址寄存器​​(TTBR0_EL1)。
  2. ​配置地址转换控制寄存器​​(TCR_EL1)。
  3. ​定义内存属性寄存器​​(MAIR_EL1)。
  4. ​插入内存屏障指令​​,确保配置顺序性和一致性。
1
2
3
4
5
6
7
8
9
10
11
static inline void mmu_set_ttbr_tcr_mair(U64 table, U64 tcr, U64 attr)
{
    OS_EMBED_ASM("dsb sy"); //数据同步屏障

    OS_EMBED_ASM("msr ttbr0_el1, %0" : : "r" (table) : "memory"); //设置页表基址寄存器
    // OS_EMBED_ASM("msr ttbr1_el1, %0" : : "r" (table) : "memory");
    OS_EMBED_ASM("msr tcr_el1, %0" : : "r" (tcr) : "memory"); //设置地址转换tcr寄存器
    OS_EMBED_ASM("msr mair_el1, %0" : : "r" (attr) : "memory"); //设置内存属性(设备内存或者普通内存)寄存器

    OS_EMBED_ASM("isb");//指令同步屏障
}

MMU初始化核心函数

static U32 mmu_setup_pgtables(mmu_mmap_region_s *mem_map, U32 mem_region_num, U64 tlb_addr, U64 tlb_len, U32 granule)
{
    U32 i;
    U32 ret;
    U64 tcr;
    U64 *new_table = NULL;

    //初始化页表控制结构
    g_mmu_ctrl.tlb_addr = tlb_addr;     // 页表内存池起始地址
    g_mmu_ctrl.tlb_size = tlb_len;      // 页表内存池大小
    g_mmu_ctrl.tlb_fillptr = tlb_addr;  // 当前页表分配指针(初始指向起始地址)
    g_mmu_ctrl.granule = granule;       // 设置页表粒度(4K/64K)
    g_mmu_ctrl.start_level = 0;         // 临时初始化起始级别(后续计算)

    //获取tcr寄存器和虚拟地址位数
    tcr = mmu_get_tcr(NULL, &g_mmu_ctrl.va_bits);

    // 依据页表粒度和虚拟地址位数计算地址转换起始级别
    if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
        if (g_mmu_ctrl.va_bits < MMU_BITS_39) {
            g_mmu_ctrl.start_level = MMU_LEVEL_1;
        } else {
            g_mmu_ctrl.start_level = MMU_LEVEL_0;
        }
    } else {
        if (g_mmu_ctrl.va_bits <= MMU_BITS_36) {
            g_mmu_ctrl.start_level = MMU_LEVEL_2;
        } else {
            g_mmu_ctrl.start_level = MMU_LEVEL_1;
            return 3;
        }
    }

    // 创建一个顶级页表,不一定是L0
    new_table = mmu_create_table();
    if (new_table == NULL) {
        return 1;//若内存池耗尽,则返回1
    }
    //构建内存映射
    for (i = 0; i < mem_region_num; ++i) {
        //根据mem_map的配置逐级填充页表项
        ret = mmu_add_map(&mem_map[i]);
        if (ret) {
            return ret;
        }
    }
    //配置mmu寄存器:ttbr0_el1,tcr_el1,mair_el1
    mmu_set_ttbr_tcr_mair(g_mmu_ctrl.tlb_addr, tcr, MEMORY_ATTRIBUTES);

    return 0;
}

[!流程图]
开始

├─ 初始化页表内存池参数

├─ 计算TCR和VA位数

├─ 确定起始级别(L0/L1/L2)
│ ├─ 4K粒度:VA<39 → L1,否则L0
│ └─ 64K粒度:VA≤36 → L2,否则报错

├─ 创建顶级页表

├─ 遍历mem_map构建映射
│ ├─ 逐级填充页表项
│ └─ 失败则终止

├─ 配置TTBR0/TCR/MAIR

└─ 返回成功(0)或错误码(1/2/3)

mmu初始化的封装函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static S32 mmu_setup(void) {
// 1. 获取页表内存池的物理地址和长度(链接脚本定义)
page_addr = (U64)&g_mmu_page_begin; // 页表起始地址
page_len = (U64)&g_mmu_page_end - (U64)&g_mmu_page_begin; // 页表区域大小
// 2. 调用核心页表构建函数
ret = mmu_setup_pgtables(
g_mem_map_info, // 内存映射配置数组
(sizeof(g_mem_map_info) / sizeof(mmu_mmap_region_s)), // 映射区域数量
page_addr, // 页表内存池起始地址
page_len, // 页表内存池大小
MMU_GRANULE_4K // 页表粒度(4KB)
);
// 3. 错误处理
if (ret) {
return ret; // 返回错误码(1~3)
}
return 0; // 成功
}

mmu初始化的调用接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
S32 mmu_init(void) {
// 1. 构建页表
ret = mmu_setup();
if (ret) {
return ret; // 失败时返回错误码
}
// 2. 失效缓存和TLB(关键!)
os_asm_invalidate_dcache_all(); // 清空数据缓存
os_asm_invalidate_icache_all(); // 清空指令缓存
os_asm_invalidate_tlb_all(); // 清空TLB
// 3. 启用MMU和缓存
set_sctlr(get_sctlr() | CR_C | CR_M | CR_I);
// CR_M: 启用MMU
// CR_C: 启用数据缓存
// CR_I: 启用指令缓存
return 0; // 成功
}

新建 src/bsp/mmu.h, 该文件可从 这里 下载

关键部分解析

mair 寄存器编码

1
#define MEMORY_ATTRIBUTES ((0x00 << (MT_DEVICE_NGNRNE*8)) | (0x04 << (MT_DEVICE_NGNRE*8)) | ...)

预定义 5 种内存类型(如 MT_DEVICE_NGNRNE 非缓存设备内存,MT_NORMAL 缓存内存)

页表项属性标记

  • 权限控制​​:PTE_BLOCK_AP_R/RW(读/读写)、PTE_BLOCK_PXN/UXN(执行权限)。
  • ​缓存策略​​:PTE_BLOCK_MEMTYPE(MT_NORMAL) 配合 PTE_BLOCK_INNER_SHARE 定义缓存共享域。

页表描述符格式

类型标识

1
#define PTE_TYPE_BLOCK (1 << 0) // 块描述符(大页) #define PTE_TYPE_PAGE (3 << 0) // 页描述符(4K/64K页) #define PTE_TYPE_TABLE (3 << 0) // 表描述符(指向下级页表)

地址对齐掩码

1
#define PTE_TABLE_ADDR_MARK_4K  0x0000FFFFFFFFF000ULL  // 4K页表地址对齐

地址转换控制(tcr寄存器)

1
2
3
4
5
6
7
//物理地址位数
#define TCR_IPS(x) ((x) << 32) // 支持 32-48 位物理地址
//页表粒度
#define TCR_TG0_4K (0 << 14) // 4KB粒度
#define TCR_TG0_64K (1 << 14) // 64KB粒度
//缓存属性
#define TCR_IRGN_WBWA (1 << 8) // Inner Write-Back, Write-Allocate

多级页表管理

级别定义

1
2
3
4
5
6
7
typedef enum {
    MMU_LEVEL_0 = 0,
    MMU_LEVEL_1,
    MMU_LEVEL_2,
    MMU_LEVEL_3,
    MMU_LEVEL_MAX
} mmu_level_e;

内存映射配置

1
2
3
4
5
6
7
typedef struct {
U64 virt; // 虚拟地址起点
U64 phys; // 物理地址起点
U64 size; // 映射区域大小
U64 max_level; // 最大页表级别(如L2表示映射为1GB大页)
U64 attrs; // 内存属性(如MMU_ATTR_CACHE_SHARE
} mmu_mmap_region_s;

mmu控制状态

1
2
3
4
5
6
7
8
9
typedef struct {
    U64 tlb_addr;//内存起始地址
    U64 tlb_size;
    U64 tlb_fillptr;
    U32 granule;//页表粒度
    U32 start_level;//页表起始级别
    U32 va_bits;

} mmu_ctrl_s;

内存映射区域控制

1
2
3
4
5
6
7
typedef struct {
    U64 virt;//虚拟地址起始
    U64 phys;//物理地址起始
    U64 size;//映射区域大小
    U64 max_level;//最大页表级别
    U64 attrs;//内存属性
} mmu_mmap_region_s;

新建 src/bsp/cache_asm.S, 该文件可从 这里 下载

用于处理缓存和tlb(translation lookaside buffer)的操作

1.os_asm_dcache_level:这是一个内部函数,用于清空指定级别的数据缓存。它通过设置 CSS
(Cache Size Selection)寄存器选择指定的缓存级别,并循环遍历每个缓存行,逐行清空缓存。
2.os_asm_dcache_all:这个函数用于清空所有数据缓存。它会循环遍历系统中的每个缓存级别,并调用 os_asm_dcache_level 函数来清空每个级别的缓存。
3.os_asm_invalidate_dcache_all:这个函数用于使所有数据缓存无效。它通过将os_asm_dcache_all 函数的参数设置为0x1,实现清空所有数据缓存。
4.os_asm_flush_dcache_all:这个函数用于刷新所有数据缓存。与清空缓存不同,刷新缓存会将缓存中的数据写回到主存。它通过将 os_asm_dcache_all函数的参数设置为 Ox0,实现刷新所有数据缓存。
5.os_asm_clean_dcache_all:这个函数用于清洁所有数据缓存。清洁缓存会将缓存中的脏数据写回主存,并将缓存行置为无效状态。它通过将os_asm_dcache_all 函数的参数设置为0x2,实现清洁所有数据缓存。
6.os_asm_invalidate_icache_all:这个函数用于使所有指令缓存无效。它通过调用 ic ialluis 汇编指令来实现,该指令用于使指令缓存失效。
7.os_asm_invalidate_tlb_all:这个函数用于使 TLB(Translation Lookaside Buffer)无效。它通过调用 tlbi vmalle1 汇编指令来实现,该指令用于使 TLB 中的所有条目无效。

启用mmu

start.S 中在 B OsEnterMain 之前启用 MMU

将新建文件加入构建系统

调试确保已经启动mmu

在start.S里打上合适的断点,可以看到,逐步调试到mmu_init后

跳转到mmu.c中,对mmu进行一系列的初始化操作
我们可以看到有关寄存器成功被初始化


mmu成功启动^-^

作业

  1. 在printf.c中修改宏定义
  2. 在hwi.init.C中修改宏定义
  3. 启动ttbr1,确保mmu寄存器被正确初始化
  4. 选择页表颗粒度4kb,则高位的f不用更改
  5. 根据宏定义修改映射信息
  6. 修改get_tcr函数并在mmu.h中 添加相关宏定义
  7. 修改mmu_setup_pgtables 为ttbr0 ttbr1创建独立页表
  8. 修改mmu_set_ttbr_tcr_mair
  9. 程序运行成功