欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【嵌入式开发】 ARM 关闭 MMU ( 存储体系 | I/D-Cache | MMU | CP15 寄存器 | C1 控制寄存器 | C7 寄存器 | 关闭 MMU )

程序员文章站 2022-07-03 16:32:52
...


本博客的参考文章及相关资料下载 :




一. MMU 概念




1. ARM 存储


(1) ARM 的存储体系


ARM 存储 体系 简介 : ARM 处理器分为三个等级, 处理器寄存器 -> TCM 存储器 -> 辅助存储器, 由上到下, 处理速度依次变慢, 但是存储空间依次增加 ;

  • 1.处理器内部寄存器 : 处理器内部的 通用寄存器 和 状态字寄存器 等, 这些寄存器 访问速度很快, 但是数量很少 ;
  • 2.TCM 紧耦合存储器 : Cache, 内存 等存储器;
  • 3.辅助存储器 : 开发板上的 NandFlash 达到 1G 大小的数量级别, SD 卡 等存储 设备; 该类型存储器 访问速度最慢, 但是数量最大;

    【嵌入式开发】 ARM 关闭 MMU ( 存储体系 | I/D-Cache | MMU | CP15 寄存器 | C1 控制寄存器 | C7 寄存器 | 关闭 MMU )



(2) Cache 由来


Cache 的由来 : Cache 用于解决 处理器 与 存储器 之间 数据传输效率低下的问题;

  • 1.没有 Cache 的 情况 : 处理器直接访问主存储器, 两者之间的 处理速度差别巨大, 处理器的访问效率会被大大的拉低 ;
  • 2.有 Cache 的 情况 : Cache 位于 处理器 与 主存储器 之间, Cache 中存放主存储器的一些拷贝, 当处理器需要读取指定内容时, 先到 Cache 中去查看, 如果没有, 就 直接从主存储器中读取, 同时将数据也读取到 Cache 中, 当处理器下一次在读取该数据的时候, 就可以直接从 Cache 中获取该数据;


(3) Cache 定义


Cache 定义 :

  • 1.定义 : Cache 是 小容量 高速度 的 存储器, 其速度 低于 处理器 高于 主存储器;
  • 2.对外透明 : Cache 的功能对外是透明的, 在 Cache 中, 保存哪些数据, 覆盖哪些数据都是操作系统决定的;
  • 3.Cache 功能划分 : 分为两类, ① I-Cache 指令 Cache, 用于存放指令; ② D-Cache 数据 Cache, 用于存放数据 ;
  • 4.图示 : 下图是 S3C6410X.pdf 芯片手册 1.2 章节 中的 I-Cache 和 D-Cache 的描述, 下图红框部分, I/D-Cache 都是 16KB 大小;

【嵌入式开发】 ARM 关闭 MMU ( 存储体系 | I/D-Cache | MMU | CP15 寄存器 | C1 控制寄存器 | C7 寄存器 | 关闭 MMU )





2. MMU


(1) 虚拟地址 与 物理地址


虚拟机地址 与 物理地址 :

  • 1.虚拟地址概念 : 程序中使用的地址 是 虚拟地址 ;
  • 2.物理地址概念 : 存储器物理存储单元的实际物理地址 ;
  • 3.虚拟地址的优势 : ① 应用程序可以使用更大的存储空间, ② 解决不同程序之间的地址冲突问题; 如果没有虚拟地址, 程序中直接使用物理地址, 那么程序必须使用指定的物理地址, 会产生冲突; 同时程序中使用的存储空间也被限制 了; 因此程序中直接使用实际的物理地址 是不可行的 ;
  • 4.MMU 作用 : MMU 可以 实现 物理地址 到 虚拟地址 之间的转换 ;


(2) MMU 作用 及 关闭原因


MMU 作用 : 实现 物理地址 到 虚拟地址 的转换 ;

  • 1.MMU 与 Cache 的 位置 : ① ARM 11 之前, 处理器 -> Cache -> MMU -> 存储器, ② ARM 11 及 ARM 11 之后, 处理器 -> MMU -> Cache -> 存储器, 访问 Cache 必须通过 MMU 将虚拟地址映射成物理地址后访问;
  • 2.关闭 MMU 原因 : 使用 MMU 和 Cache 必须经过一系列的配置, 之后才能正确的使用, 在 ARM 初始化 时, 还没有配置 MMU 和 Cache, 如果不关闭会出现错误;






二. 关闭 MMU 和 Cache


参考手册 : ARM核 手册 Arm1176jzfs.pdf ( 基于 6410 开发板 ARM 11 )


1. 关闭 MMU 和 Cache 的方法简介


(1) 关闭方法


关闭 MMU 和 Cache 简介 :

  • 1.关闭 Cache 和 MMU 步骤 : ① 设置 ICache 和 DCache 失效; ② 关闭 ICache 和 DCache 以及 MMU ;
  • 2.操作方法 : MMU 和 Cache 关闭操作都是通过 CP15 协处理器 控制的, ① C1 控制寄存器 控制 Cache 和 MMU 开启 / 关闭 , ② C7 寄存器 控制 Cache 的的 失效 操作 ;


(2) C1 控制寄存器 ( 打开关闭 Cache )


C1 控制寄存器简介 :

  • 1.文档位置 : Arm1176jzfs.pdf 第 3.2.7 章节 c1, Control Register ;
  • 2.I-Cache ( Instruction Cache ) 控制位 : 第 12 位 控制 I-Cache 的开启 / 关闭, 设置成 0 即 I-Cache 失效, 设置成 1 即 I-Cache 生效;
    【嵌入式开发】 ARM 关闭 MMU ( 存储体系 | I/D-Cache | MMU | CP15 寄存器 | C1 控制寄存器 | C7 寄存器 | 关闭 MMU )
  • 3.D-Cache ( Data Cache ) 控制位 : 第 2 位 控制 D-Cache 的开启 / 关闭, 设置成 0 即 I-Cache 失效, 设置成 1 即 I-Cache 生效;
    【嵌入式开发】 ARM 关闭 MMU ( 存储体系 | I/D-Cache | MMU | CP15 寄存器 | C1 控制寄存器 | C7 寄存器 | 关闭 MMU )
  • 4.MMU 控制位 : 第 0 位 控制 MMU 生效 / 失效, 设置成 0 即 MMU 失效, 设置成 1 即 MMU 生效;
    【嵌入式开发】 ARM 关闭 MMU ( 存储体系 | I/D-Cache | MMU | CP15 寄存器 | C1 控制寄存器 | C7 寄存器 | 关闭 MMU )


(3) C7 Cache 操作寄存器 ( 使 Cache 失效 )


C7 寄存器 简介 :

  • 1.文档位置 : Arm1176jzfs.pdf 第 3.2.22 章节 c7, Cache operations ;
  • 2.使 Cache 失效 的指令 : MCR p15, 0, <Rd>, c7, c7, 0, 这是 文档 中表格 3-71 Cache 操作 中给出的;
    【嵌入式开发】 ARM 关闭 MMU ( 存储体系 | I/D-Cache | MMU | CP15 寄存器 | C1 控制寄存器 | C7 寄存器 | 关闭 MMU )


2. 关闭 MMU 和 Cache 代码编写



关闭 MMU 和 Cache 代码编写 :

  • 1.设置标号 : 为本段代码设置一个标号, 让程序可以跳转到该处执行以下代码, disable_mmu : ;
  • 2.设置 I-Cache 和 D-Cache 失效 : 使 两个 Cache 都失效, 文档中 Arm1176jzfs.pdf 第 3.2.22 章节 给出的代码格式为 MCR p15, 0, <Rd>, c7, c7, 0, 其中 Rd 通用寄存器 设置为 R0, 最终代码为 MCR p15, 0, R0, c7, c7, 0 ;
  • 3.关闭 I-Cache 和 D-Cache 及 MMU :
    • ① 修改方式 : C1 控制寄存器中的 [0] 位 控制 MMU 开启/关闭, [2] 位控制 D-Cache 开启/关闭, [12] 位控制 I-Cache 开启/关闭; 上述位 设置为 0 关闭, 设置为 1 开启;
    • ② C1 寄存器读写方式 : CP15 寄存器不能直接读取, 需要使用 MRC 来将协处理器中的内容读取到通用寄存器中, 语法格式为 MRC{cond} P15,<Opcode_1>,<Rd>,<CRn>,<CRm>,<Opcode_2> , 使用 MCR 将 Rd 寄存器中的值传送到 CP15 协处理器中, 语法格式为 MCR{cond} P15,<Opcode_1>,<Rd>,<CRn>,<CRm>,<Opcode_2> ;
    • ③ 位计算 : 关闭 I/D-Cache 和 MMU 需要将 C1 寄存器的 [0](MMU), [2](D-Cache), [12] (I-Cache) 三位 设置为0; 其中 I-Cache 可以关闭, 也可以开启, 不是必须的; 但是 D-Cache 和 MMU 必须关闭, Bootloader 主要作用是将 Linux 内核下载到内存中, 如果下载的过程中 D-Cache 没有配置, 可能就将数据下载到了 Cache 中, 这样就会出现问题, 影响内核运行; 因此这里我们只需要将 第 [0] 位 和 第 [1] 位 设置成 0, 将 MMU 和 D-Cache 关闭, I-Cache 不作设置;
    • ④ 读取 C1 寄存器的值 : 使用 MRC p15, 0, R0, c1, c0, 0 将 c1 寄存器中的值 读取到 R0 通用寄存器中;
    • ⑤ 将指定位设置为 0 : 使用 bic 位清除指令, 将 R0 寄存器中的 第 0, 1, 2 三位 设置成0, 这里 第 1 位选择性设置, 为了方便计算 顺便将 第 1 位 也设置成 0, 代码为 bic r0, r0, #0x7 ;
    • ⑥ 将 R0 寄存器中的值写回到 C1 寄存器中 : 使用 MRC p15, 0, r0, c1, c0, 0 指令, 将 R0 寄存器中的值 写回到 C1 寄存器中;
  • 4.设置程序跳转到返回点继续执行 : 使用 BL 指令跳转到 disable_mmu 标号处执行, 同时将返回地址存储到了 LR 寄存器中, 返回时跳转到 LR 寄存器中的地址执行即可, 使用 mov pc, lr 指令, 执行 lr 中地址指向的位置的代码;
  • 5.代码示例 :
disable_mmu : 
    mcr p15,0,r0,c7,c7,0                            @ 设置 I-Cache 和 D-Cache 失效
    mrc p15,0,r0,c1,c0,0                            @ 将 c1 寄存器中的值 读取到 R0 通用寄存器中
    bic r0, r0, #0x00000007                         @ 使用 bic 位清除指令, 将 R0 寄存器中的 第 0, 1, 2 三位 设置成0, 代表 关闭 MMU 和 D-Cache
    mcr p15,0,r0,c1,c0,0                            @ 将 R0 寄存器中的值写回到 C1 寄存器中
    mov pc, lr                                      @ 返回到 返回点处 继续执行后面的代码






三. 关闭 MMU 和 Cache 完整可编译执行代码




1. 汇编代码



汇编代码示例 : Bootloader 流程 : ① 初始化异常向量表 , ② 设置 svc 模式 , ③ 关闭看门狗, ④ 关闭中断, ⑤ 关闭 MMU ;

@****************************  
@File:start.S  
@  
@BootLoader 初始化代码 
@****************************  

.text                                   @ 宏 指明代码段  
.global _start                          @ 伪指令声明全局开始符号  
_start:                                 @ 程序入口标志  
        b   reset                       @ reset 复位异常  
        ldr pc, _undefined_instruction  @ 未定义异常, 将 _undefined_instruction 值装载到 pc 指针中  
        ldr pc, _software_interrupt     @ 软中断异常  
        ldr pc, _prefetch_abort         @ 预取指令异常  
        ldr pc, _data_abort             @ 数据读取异常  
        ldr pc, _not_used               @ 占用 0x00000014 地址                            
        ldr pc, _irq                    @ 普通中断异常  
        ldr pc, _fiq                    @ 软中断异常  

_undefined_instruction: .word undefined_instruction @ _undefined_instruction 标号存放了一个值, 该值是 32 位地址 undefined_instruction, undefined_instruction 是一个地址  
_software_interrupt:    .word software_interrupt    @ 软中断异常  
_prefetch_abort:    .word prefetch_abort            @ 预取指令异常 处理  
_data_abort:        .word data_abort                @ 数据读取异常  
_not_used:      .word not_used                      @ 空位处理  
_irq:           .word irq                           @ 普通中断处理  
_fiq:           .word fiq                           @ 快速中断处理  

undefined_instruction:                              @ undefined_instruction 地址存放要执行的内容  
        nop  

software_interrupt:                                 @ software_interrupt 地址存放要执行的内容  
        nop  

prefetch_abort:                                     @ prefetch_abort 地址存放要执行的内容  
        nop  

data_abort:                                         @ data_abort 地址存放要执行的内容  
        nop  

not_used:                                           @ not_used 地址存放要执行的内容  
        nop  

irq:                                                @ irq 地址存放要执行的内容  
        nop  

fiq:                                                @ fiq 地址存放要执行的内容  
        nop  

reset:                                              @ reset 地址存放要执行的内容  
        bl set_svc                                  @ 跳转到 set_svc 标号处执行
        bl disable_watchdog                         @ 跳转到 disable_watchdog 标号执行, 关闭看门狗
        bl disable_interrupt                        @ 跳转到 disable_interrupt 标号执行, 关闭中断
        bl disable_mmu                              @ 跳转到 disable_mmu 标号执行, 关闭 MMU 

set_svc:
        mrs r0, cpsr                                @ 将 CPSR 寄存器中的值 导出到 R0 寄存器中
        bic r0, r0, #0x1f                           @ 将 R0 寄存器中的值 与 #0x1f 立即数 进行与操作, 并将结果保存到 R0 寄存器中, 实际是将寄存器的 0 ~ 4 位 置 0
        orr r0, r0, #0xd3                           @ 将 R0 寄存器中的值 与 #0xd3 立即数 进行或操作, 并将结果保存到 R0 寄存器中, 实际是设置 0 ~ 4 位 寄存器值 的处理器工作模式代码
        msr cpsr, r0                                @ 将 R0 寄存器中的值 保存到 CPSR 寄存器中
        mov pc, lr                                  @ 返回到 返回点处 继续执行后面的代码

#define pWTCON 0x7e004000                           @ 定义看门狗控制寄存器 地址 ( 6410开发板 )
disable_watchdog:                                 
        ldr r0, =pWTCON                             @ 先将控制寄存器地址保存到通用寄存器中
        mov r1, #0x0                                @ 准备一个 0 值, 看门狗控制寄存器都设置为0 , 即看门狗也关闭了
        str r1, [r0]                                @ 将 0 值 设置到 看门狗控制寄存器中 
        mov pc, lr                                  @ 返回到 返回点处 继续执行后面的代码

disable_interrupt:
    mvn r1,#0x0                                     @ 将 0x0 按位取反, 获取 全 1 的数据, 设置到 R1 寄存器中
    ldr r0,=0x71200014                              @ 设置第一个中断屏蔽寄存器, 先将 寄存器 地址装载到 通用寄存器 R0 中 
    str r1,[r0]                                     @ 再将 全 1 的值设置到 寄存器中, 该寄存器的内存地址已经装载到了 R0 通用寄存器中

    ldr r0,=0x71300014                              @ 设置第二个中断屏蔽寄存器, 先将 寄存器 地址装载到 通用寄存器 R0 中 
    str r1,[r0]                                     @ 再将 全 1 的值设置到 寄存器中, 该寄存器的内存地址已经装载到了 R0 通用寄存器中
    mov pc, lr                                      @ 返回到 返回点处 继续执行后面的代码

disable_mmu : 
    mcr p15,0,r0,c7,c7,0                            @ 设置 I-Cache 和 D-Cache 失效
    mrc p15,0,r0,c1,c0,0                            @ 将 c1 寄存器中的值 读取到 R0 通用寄存器中
    bic r0, r0, #0x00000007                         @ 使用 bic 位清除指令, 将 R0 寄存器中的 第 0, 1, 2 三位 设置成0, 代表 关闭 MMU 和 D-Cache
    mcr p15,0,r0,c1,c0,0                            @ 将 R0 寄存器中的值写回到 C1 寄存器中
    mov pc, lr                                      @ 返回到 返回点处 继续执行后面的代码





2. 链接器脚本


gboot.lds 链接器脚本 代码解析 :

  • 1.指明输出格式 ( 处理器架构 ) : 使用 OUTPUT_ARCH(架构名称) 指明输出格式, 即处理器的架构, 这里是 arm 架构的, OUTPUT_ARCH(arm) ;
  • 2.指明输出程序的入口 : 设置编译输出的程序入口位置, 语法为 ENTRY(入口位置), 在上面的 Start.S 中设置的程序入口是 _start, 代码为 ENTRY(_start) ;
  • 3.设置代码段 : 使用 .text : 设置代码段;
  • 4.设置数据段 : 使用 .data : 设置数据段;
  • 5.设置 BSS 段 : 使用 .bss : 设置 BSS 段;
    • ( 1 ) 记录 BSS 段的起始地址 : bss_start = .; ;
    • ( 2 ) 记录 BSS 段的结束地址 : bss_end = .; ;
  • 6.对齐 : 每个段都需要设置内存的对齐格式, 使用 . = ALIGN(4); 设置四字节对齐即可;
  • 7.代码示例 :
OUTPUT_ARCH(arm)        /*指明处理器结构*/  
ENTRY(_start)           /*指明程序入口 在 _start 标号处*/  
SECTIONS {                
    . = 0x50008000;     /*整个程序链接的起始位置, 根据开发板确定, 不同开发板地址不一致*/  

    . = ALIGN(4);       /*对齐处理, 每段开始之前进行 4 字节对齐*/  
    .text :             /*代码段*/  
    {  
    start.o (.text)     /*start.S 转化来的代码段*/  
    *(.text)            /*其它代码段*/  
    }  

    . = ALIGN(4);       /*对齐处理, 每段开始之前进行 4 字节对齐*/  
    .data :             /*数据段*/  
    {  
    *(.data)  
    }  

    . = ALIGN(4);       /*对齐处理, 每段开始之前进行 4 字节对齐*/  
    bss_start = .;      /*记录 bss 段起始位置*/  
    .bss :              /*bss 段*/  
    {  
    *(.bss)   
    }  
    bss_end = .;        /*记录 bss 段结束位置*/  
} 




3. Makefile 编译脚本


makefile 文件编写 :

  • 1.通用规则 ( 汇编文件编译规则 ) : 汇编文件 编译 成同名的 .o 文件, 文件名称相同, 后缀不同, %.o : %.S, 产生过程是 arm-linux-gcc -g -c $^ , 其中 ^ 标识是所有的依赖文件, 在该规则下 start.S 会被变异成 start.o ;
  • 2.通用规则 ( C 文件编译规则 ) : C 代码编译成同名的 .o 文件, %.o : %.c , 产生过程是 arm-linux-gcc -g -c $^ ;
  • 3.设置最终目标 : 使用 all: 设置最终编译目标;
    • ( 1 ) 依赖文件 : 产生最终目标需要依赖 start.o 文件, 使用 all: start.o 表示最终目标需要依赖该文件;
    • ( 2 ) 链接过程 : arm-linux-ld -Tgboot.lds -o gboot.elf $^, 需要使用链接器脚本进行连接, ①链接工具是 arm-linux-ld 工具, ②使用 -Tgboot.lds 设置链接器脚本 是刚写的 gboot.lds 链接器脚本, ③输出文件是 gboot.elf 这是个中间文件, ④ 依赖文件是 $^ 代表所有的依赖;
    • ( 3 ) 转换成可执行二进制文件 : arm-linux-objcopy -O binary gboot.elf gboot.bin, 使用 -O binary 设置输出二进制文件, 依赖文件是 gboot.elf, 输出的可执行二进制文件 即 结果是 gboot.bin ;
  • 4.makefile 文件内容 :
all: start.o #依赖于 start.o  
    arm-linux-ld -Tgboot.lds -o gboot.elf $^    #使用链接器脚本, 将 start.o 转为 gboot.elf  
    arm-linux-objcopy -O binary gboot.elf gboot.bin #将 gboot.elf 转化为可以直接在板子上执行的 gboot.bin 文件  

%.o : %.S   #通用规则, 如 start.o 是由 start.S 编译来的, -c 是只编译不链接  
    arm-linux-gcc -g -c $^  

%.o : %.c   #通用规则, 如 start.o 是由 start.c 编译来的, -c 是只编译不链接  
    arm-linux-gcc -g -c $^  

.PHONY: clean     
clean:              #清除编译信息  
    rm *.o *.elf *.bin  




4. 编译输出可执行文件


编译过程 :

  • 1.文件准备 : 将 汇编代码 ( start.S ) 链接器脚本 ( gboot.lds ) makefile 文件 拷贝到编译目录 ;
  • 2.执行编译命令 : make ;
  • 3.编译结果 : 可以看到 生成了 编译目标文件 start.o, 链接文件 gboot.elf, 可执行的二进制文件 gboot.bin ;
    【嵌入式开发】 ARM 关闭 MMU ( 存储体系 | I/D-Cache | MMU | CP15 寄存器 | C1 控制寄存器 | C7 寄存器 | 关闭 MMU )

本博客的参考文章及相关资料下载 :

相关标签: MMU