【嵌入式开发】 ARM 关闭 MMU ( 存储体系 | I/D-Cache | MMU | CP15 寄存器 | C1 控制寄存器 | C7 寄存器 | 关闭 MMU )
程序员文章站
2022-07-03 16:32:52
...
本博客的参考文章及相关资料下载 :
- 1.本博客代码及参考手册下载 : https://download.csdn.net/download/han1202012/10455643
一. MMU 概念
1. ARM 存储
(1) ARM 的存储体系
ARM 存储 体系 简介 : ARM 处理器分为三个等级, 处理器寄存器 -> TCM 存储器 -> 辅助存储器, 由上到下, 处理速度依次变慢, 但是存储空间依次增加 ;
- 1.处理器内部寄存器 : 处理器内部的 通用寄存器 和 状态字寄存器 等, 这些寄存器 访问速度很快, 但是数量很少 ;
- 2.TCM 紧耦合存储器 : Cache, 内存 等存储器;
-
3.辅助存储器 : 开发板上的 NandFlash 达到 1G 大小的数量级别, SD 卡 等存储 设备; 该类型存储器 访问速度最慢, 但是数量最大;
(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 大小;
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.手册对应章节 : 3.2.7 章节 c1, Control Register;
- 2.Arm1176jzfs.pdf手册下载地址 :https://download.csdn.net/download/han1202012/10412045
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 生效;
-
3.D-Cache ( Data Cache ) 控制位 : 第 2 位 控制 D-Cache 的开启 / 关闭, 设置成 0 即 I-Cache 失效, 设置成 1 即 I-Cache 生效;
-
4.MMU 控制位 : 第 0 位 控制 MMU 生效 / 失效, 设置成 0 即 MMU 失效, 设置成 1 即 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 操作 中给出的;
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 = .;
;
-
( 1 ) 记录 BSS 段的起始地址 :
-
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
;
-
( 1 ) 依赖文件 : 产生最终目标需要依赖 start.o 文件, 使用
- 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 ;
本博客的参考文章及相关资料下载 :
- 1.本博客代码及参考手册下载 : https://download.csdn.net/download/han1202012/10455643
上一篇: ubuntu eclipse 菜单