U-Boot内存管理
如《Linux内核内存管理架构》一文中提到,linux内核中的内存管理支持内存地址映射、内存分配、内存回收、内存碎片管理、页面缓存等众多功能。但U-Boot做为启动引导程序,其核心功能就是引导内核镜像,所以其内存管理功能并不用像Linux内核中的内存管理一样功能齐全。U-Boot中没有内存分配、回收、缓存等功能,内存管理其实只做一件事:虚实地址映射,而且是固定映射。
为了提高效率,现代处理器的内存管理都由MMU(Memory Management Unit)硬件单元实现(示意图如下),其核心模块主要有TLB和page table。
不同的CPU体系的MMU架构差异很大,有的MMU可以跳过,有的不可以;有的MMU分为L1MMU、L2MMU;有的TLB分为页式TLB、段式TLB;有的page table分为page entry、block entry;有的TLB reload和page table 查找由MMU硬件实现(ARM、x86, PowerPC),有的则是由软件实现(MIPS, Alpha)等。U-Boot的虚实地址映射,一般能跳过MMU就跳过,能不使用页表就不用页表,总之,怎么简单怎么来。
可见,U-Boot中的内存管理的实现与CPU架构和MMU强相关,本文挑选了PowerPC e500,MIPS,ARMv8三款处理器并对其MMU架构进行分析,并讨论它们在U-Boot中的内存虚实地址映射实现。

U-Boot PowerPC内存管理
下图是PowerPC e500的MMU结构框图。

MMU地址映射过程中涉及到3种地址形式:
- 32位有效地址EA:软件可以直接访问的地址;
- 41位虚拟地址VA:经过段映射的过渡地址,由32位EA和AS以及8位PID位组成;
- 36位物理地址RA:也称为实地址,由36位地址总线访问的地址空间。
PID0-2 用来存放当前进程有效地址的进程ID号,主要作用是在进程上下文切换时,提高TLB刷新的精准性。
LAW(Local Access Window) 用于描述PowerPC处理器物理地址空间的划分,其中LAWBAR用于指定基址,LAWAR用于指定此空间用作PCI、Local Bus还是DDR等设备的空间。
e500支持2种形式的TLB:TLB0和TLB1。
TLB0支持固定4K页大小映射,512个entry最大可以映射512*4K=2M的物理地址空间,需要动态更新,会产生TLB miss异常。TLB0灵活,可以满足复杂系统应用的要求。
TLB1 是一种段式映射,有效地址和物理地址之间是一一对应的关系。TLB1支持可变页大小映射,16个entry 支持4K~4G页大小的映射,最大可映射16*4G =64G的物理地址空间,不需要动态更新,不会产生TLB miss异常。TLB1不够灵活,无法满足复杂系统应用的要求。
PowerPC e500核心的MMU是无法跳过的,所以只能通过MMU来映射地址空间。虽然MMU中的TLB1段式映射不够灵活,但是简单,可以满足U-Boot中的内存固定映射需求。PowerPC对内存虚实地址映射的处理是先设置DDR内存的物理地址(LAW),再把虚拟地址到物理地址的映射关系写到TLB1中。
相关代码实现:
phys_size_t fixed_sdram(void)
{
// ... 初始化DDR配置参数ddr_cfg_regs
ddr_size = (phys_size_t) CONFIG_SYS_SDRAM_SIZE * * ;
fsl_ddr_set_memctl_regs(&ddr_cfg_regs, ); if (set_ddr_laws(CONFIG_SYS_DDR_SDRAM_BASE, ddr_size,
LAW_TRGT_IF_DDR_1) < ) {
printf("ERROR setting Local Access Windows for DDR\n");
return ;
} return ddr_size;
} unsigned int
setup_ddr_tlbs_phys(phys_addr_t p_addr, unsigned int memsize_in_meg)
{
/// ... 计算size for (i = ; size && i < ; i++) {
/// ... 计算ram_tlb_address, p_addr, ram_tlb_index, tlb_size set_tlb(, ram_tlb_address, p_addr,
MAS3_SX|MAS3_SW|MAS3_SR, wimge,
, ram_tlb_index, tlb_size, );
} return memsize_in_meg;
} unsigned int setup_ddr_tlbs(unsigned int memsize_in_meg)
{
return
setup_ddr_tlbs_phys(CONFIG_SYS_DDR_SDRAM_BASE, memsize_in_meg);
}
U-Boot MIPS内存管理
MIPS的虚拟地址空间分为多个段,即Kseg0-3和Kuseg。其中kseg0/kseg1可以跳过MMU,支持直接映射,各512MB。kseg2/3和Kuseg必须经过MMU。
因为U-Boot阶段对内存的需求量很小,512MB内存空间已足够满足需要,所以MIPS对内存虚实地址映射的处理是先设置好DDR内存的物理地址BAR,把其虚拟地址设置到kseg0/1即可。没有必要经过MMU和TLB。换句话说,如果CPU需要访问高于512M的DDR内存物理地址空间,必须通过MMU地址转换。

相关代码实现:
#define mem_map(x) (void *)(CAC_BASE + (x))
U-Boot ARMv8内存管理
ARMv8的MMU结构如下图,其支持:
- L1指令TLB,全相连,48个entry,支持4KB, 64KB和1MB页面大小;
- L1数据TLB,全相连,32个entry,支持4KB, 64KB和1MB页面大小;
- L2 TLB,4路组相连,1024个entry,支持4KB, 64KB和1MB页面大小;
- page table 查找由MMU中的Translation Control Unit (TCU) 硬件实现;

ARMv8的page table 结构如下。其分为4级,每级页表有512个条目,支持如下3种表条目描述符。
- Table descriptor,指向下一级table;
- Page descriptor,指向一个4KB(或64KB、1MB)大小page size的页面;
- Block descriptor,指向一个block size的内存区域;在1级页表中block size是1GB,2级页表中block size是2MB。

与上面提到的PowerPC、MIPS不同,ARMv8的MMU即无法跳过,TLB也不支持段式映射。所以只能通过page table完成虚实地址映射。这就带来一个问题:在DDR内存准备好之前,page table又放在哪里?
ARMv8核心的处理器片内包含一块2MB大小的OCRAM(On Chip RAM),在DDR RAM准备好之前,MMU table就放在CONFIG_SYS_FSL_OCRAM_BASE处,最大支持EARLY_PGTABLE_SIZE(0x5000)个条目。2MB大小的页表在Linux内核中是远远不够的,但在U--Boot中已足够,因为:
- page table支持块映射(1GB,2MB),对于连续的大块地址空间映射,block entry能极大简化page table的大小;
- U-Boot中的虚实地址映射都是固定映射;
- 只有和启动相关的设备和地址空间才需要映射,其数量有限。
等DDR内存初始化好后,再将table放到DRAM中。
相关代码实现:
void setup_pgtables(void)
{
int i; if (!gd->arch.tlb_fillptr || !gd->arch.tlb_addr)
panic("Page table pointer not setup."); create_table(); /* Now add all MMU table entries one after another to the table */
for (i = ; mem_map[i].size || mem_map[i].attrs; i++)
add_map(&mem_map[i]);
} static void add_map(struct mm_region *map)
{
u64 *pte;
u64 virt = map->virt;
u64 phys = map->phys;
u64 size = map->size;
u64 attrs = map->attrs | PTE_TYPE_BLOCK | PTE_BLOCK_AF;
u64 blocksize;
int level;
u64 *new_table; while (size) {
pte = find_pte(virt, );
if (pte && (pte_type(pte) == PTE_TYPE_FAULT)) {
debug("Creating table for virt 0x%llx\n", virt);
new_table = create_table();
set_pte_table(pte, new_table);
} for (level = ; level < ; level++) {
pte = find_pte(virt, level);
if (!pte)
panic("pte not found\n"); blocksize = 1ULL << level2shift(level);
if (size >= blocksize && !(virt & (blocksize - ))) {
/* Page fits, create block PTE */
*pte = phys | attrs;
virt += blocksize;
phys += blocksize;
size -= blocksize;
break;
} else if (pte_type(pte) == PTE_TYPE_FAULT) {
/* Page doesn't fit, create subpages */
new_table = create_table();
set_pte_table(pte, new_table);
} else if (pte_type(pte) == PTE_TYPE_BLOCK) {
split_block(pte, level);
}
}
}
}
U-Boot内存管理的更多相关文章
- 程序员必读:Linux内存管理剖析
现在的服务器大部分都是运行在Linux上面的,所以作为一个程序员有必要简单地了解一下系统是如何运行的. 对于内存部分需要知道: 地址映射 内存管理的方式 缺页异常 先来看一些基本的知识,在进程看来,内 ...
- Linux内存管理学习笔记 转
https://yq.aliyun.com/articles/11192?spm=0.0.0.0.hq1MsD 随着要维护的服务器增多,遇到的各种稀奇古怪的问题也会增多,要想彻底解决这些“小”问题往往 ...
- Linux 内存管理知识学习总结
现在的服务器大部分都是运行在Linux上面的,所以,作为一个程序员有必要简单地了解一下系统是如何运行的.对于内存部分需要知道: 地址映射 内存管理的方式 缺页异常 先来看一些基本的知识,在进程看来,内 ...
- linux内存管理初始化
内存管理子系统是linux内核最核心最重要的一部分,内核的其他部分都需要在内存管理子系统的基础上运行.而对其初始化是了解整个内存管理子系统的基础.对相关数据结构的初始化是从全局启动例程start_ke ...
- Linux内存管理专题
Linux的内存管理涉及到的内容非常庞杂,而且与内核的方方面面耦合在一起,想要理解透彻非常困难. 在开始学习之前进行了一些准备工作<如何展开Linux Memory Management学习?& ...
- 伙伴系统之避免碎片--Linux内存管理(十六)
1 前景提要 1.1 碎片化问题 分页与分段 页是信息的物理单位, 分页是为了实现非连续分配, 以便解决内存碎片问题, 或者说分页是由于系统管理的需要. 段是信息的逻辑单位,它含有一组意义相对完整的信 ...
- 伙伴系统之伙伴系统概述--Linux内存管理(十五)
在内核初始化完成之后, 内存管理的责任就由伙伴系统来承担. 伙伴系统基于一种相对简单然而令人吃惊的强大算法. Linux内核使用二进制伙伴算法来管理和分配物理内存页面, 该算法由Knowlton设计, ...
- 启动期间的内存管理之build_zonelists初始化备用内存域列表zonelists--Linux内存管理(十三)
1. 今日内容(第二阶段(二)–初始化备用内存域列表zonelists) 我们之前讲了在memblock完成之后, 内存初始化开始进入第二阶段, 第二阶段是一个漫长的过程, 它执行了一系列复杂的操作, ...
- 启动期间的内存管理之引导分配器bootmem--Linux内存管理(十)
在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到保护模式, 然后内核才能检 ...
- 启动期间的内存管理之初始化过程概述----Linux内存管理(九)
在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到保护模式, 然后内核才能检 ...
随机推荐
- 五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理
55.1 TCP 连接和关闭过程 55.1.1 介绍 建立连接的过程就是三次握手的过程:客户端发送 SYN 报文给服务器,服务器回复 SYN+ACK 报文,客户机再发送 ACK 报文. 关闭连接的过程 ...
- robot framework
一.步骤 1.创建工程和测试套件 2.创建测试案例 3.写脚本,运行案例 4.查看运行结果 report报告主要是概括性的报告,总结案例执行情况.log报告是更为详细的案例步骤的报告. 二.RIDE工 ...
- 开发一个项目之代码规范ESLint
ESLint{ "rules": { "semi": ["error", "always"], } }error lev ...
- python 中获取列表的索引
1.index方法 list_a= [12,213,22,2,32]for a in list_a: print(list_a.index(a)) 结果: 0 1 2 3 4 如果列表的没有重复的话那 ...
- 2018-2019-2 网络对抗技术 20165337 Exp4 恶意代码分析
1.实践目标 1.1是监控你自己系统的运行状态,看有没有可疑的程序在运行. 1.2是分析一个恶意软件,就分析Exp2或Exp3中生成后门软件:分析工具尽量使用原生指令或sysinternals,sys ...
- verdi\debussy的使用技巧
verdi\debussy的使用技巧 转载from 大西瓜FPGA 大西瓜FPGA-->https://daxiguafpga.taobao.com fsdb display Debussy本身 ...
- $HOME
Ubuntu中 命令行输入$HOME bash: /home/XXX: Is a directory $HOME 是用户主目录
- day11 函数的参数列表
""" 今日内容: 1.函数参数的分类 2.形参是对实参的值拷贝 3.实参的分类 4.形参的分类 5.打散机制 附1:字符串的比较 """ ...
- 2018-2019-2 20175204 张湲祯 实验二《Java面向对象程序设计》实验报告
2018-2019-2-20175204 张湲祯 实验二 <Java开发环境的熟悉>实验报告 实验二 Java面向对象程序设计 一.实验内容: 初步掌握单元测试和TDD 理解并掌握面向对象 ...
- # 20175333曹雅坤《Java程序设计》第四周学习总结
教材学习内容总结 第五章:子类与继承 5.1子类与父类:关键字extends 5.2子类的继承性:如果子类与父类在一个包中,除了private其他都可以继承:如果不在一个包中,则private和友好都 ...