Qemu-0.5.10
一:入口:vl.c: main()main_loop()cpu_exec()…
硬件初始化:
Hw/mips_godson.c
Mips_godson_init()
cpu_register_physical_memory(0x1fbf0000, 0x10000, mips_qemu_iomemtype);
cpu_register_physical_memory_offset(start_addr, size, phys_offset, 0);
{
/* register physical memory. 'size' must be a multiple of the target
page size. If (phys_offset & ~TARGET_PAGE_MASK) != 0, then it is an
io memory page.
The address used when calling the IO function is
the offset from the start of the region, plus region_offset. Both
start_region and regon_offset are rounded down to a page boundary
before calculating this offset.
This should not be a problem unless
the low bits of start_addr and region_offset differ.
phys_page_find()
*/
phys_page_find_alloc(target_phys_addr_t index, int alloc)
这段代码为 qemu 为 target 的页表管理的实现。
其中 L1_BITS, L2_BITS, L1_SIZE, L2_SIZE 分别一级页表以及二级页表的 bits 与 size.
TARGET_PAGE_BITS 为 target 的 page 需要的 bits。
以传统的二级页表为例,他们的分配分别为:
L1_BITS = 10
L2_BITS = 10
PAGE_BITS = 12
l1_phys_map 为一级页表的首地址。
在函数开始的时候
p = (void**)l1_phys_map;
因此
lp = p + ((index >> L2_BITS) & (L1_SIZE - 1));
pd = *lp;
lp 为一级页表的 offset,pd 则为 pgd[offset]。如果 alloc==0,这个函数将返回:
return ((PhysPageDesc *)pd) + (index & (L2_SIZE - 1));
也就是二级页表的地址,也就是 pte entry 的地址;否则当搜索的 pte entry 不存在是,自动分
配内存给要求的 pte entry,并返回这个 pte entry。
需要注意的是当 TARGET_PHYS_ADDR_SPACE_BITS > 32 时,通常我们需要三级页表,如
对 ppc64,TARGET_PHYS_ADDR_SPACE_BITS == 42,因此我们需要多做一个页表搜索才
能得到 pte entry 的地址。
lp = p + ((index >> (L1_BITS + L2_BITS)) & (L1_SIZE - 1));
p = *lp;
L1_BITS,L2_BITS 这里其实相当于第二级与第三级页表的 bits。
index 传进来的时候是一个地址右移了 PAGE_BITS,经过上面的操作后就变成了三级页表的
第一级的 offset 了(由于代码里写的是 L1_BITS, L2BITS,我们可以假定第一级为 L0_BITS)。
如果不考虑特殊情况,函数 cpu_register_physical_memory()做的事情到此已经结束,即:搜
索(分配)根据 addr 与 size 相关参数确定的 pte entry。其中最重要的是我们知道 qemu 模拟
了目标架构的 MMU(softmmu).
}
Load_kernel()
Load_elf()load_elf_32()或 load_elf_64()
我们以 load_elf_32 为例(ppc heathrow),这个函数在文件 elf_ops.h 中定义;实际上这个文件
做了一点小小的手段,导致我们定义 SZ=32 或 SZ=64 是,#include 这个文件两次,我们就
生成了需要的相关_xxx_32 或是_xxx_64 函数(都是静态的)。如:
static int glue(load_elf, SZ)(int fd, int64_t address_offset,
int must_swab, uint64_t *pentry, uint64_t *lowaddr, uint64_t *highaddr)
static int load_elf_32(...) or
static int load_elf_64(...)
了解一点 loader 与 ELF 文件格式的都会知道,ELF 文件的 PT_LOAD 字段要求被载入内存,
这 通 常 包 含.text 与.data 字 段 , 对 于 我 们 比 较 了 解 的 x86 来 说 ,.text 字 段 通 常 要 求 在
0x0804_8000 加 载 , 因 此 loader 会 在 loader 这 个 ELF 文 件 (executable ) 的 时 候 请 求 在
0x0804_8000 映射内存,这个通常由一个 mmap()调用达到目的。对于 PPC 来说,这个地址
通常是 0x1000_0000,.data 字段通常在.text 字段(连续)之后,当然 ELF 文件会要求 load
的时候被对齐(如对其到系统的页面大小 4kb)。我们可以通过 readelf -l /path/to/executable
来获取相关信息,ELF entry 通常就是_start 函数开始的地址(当然是链接后的)。通过
load_symbols_32(),qemu 还加载了相应的.sym, .strtab 字段。
函 数 load_elf() (load_elf_32) 返 回 了 @lowaddr 与 @highaddr , 当 一 个 ELF 文 件 有 多 个
PT_LOAD 字段时,@lowaddr 为所有映射字段地址里最低的,同理@highaddr 为所有映射字
段里面地址最高的结束为止 (addr + mem_size)。
与普通的 loader 不同的是,qemu loader 无法将 ELF 文件要求映射的地址按其要求映射。qemu
是一个普通的进程,可能无法再将 ELF 可执行文件映射到默认的 entry 地址(如主机为
x86, 0x0804_8000 已 无 法 继 续 映 射 )。 上 面 我 们 提 到 qemu 模 拟 了 目 标 机 器 的 MMU
(softmmu, 具有分页机制),因此类似一个正常的 loader(通过 mmap 要求 os 映射到指定地
址),我们需要请求 qemu 的 MMU 将目标 ELF 文件要求映射的地址做好映射。相关的工作
由以下函数完成:void cpu_physical_memory_write_rom(target_phys_addr_t addr,
const uint8_t *buf, int len)
注意 qemu 仅仅映射了 PT_LOAD 字段,解析了符号表,返回了 entry 函数地址(以及设定
@lowaddr/highaddr),但目前为止并未执行任何动作。
函数 QEMUMachine->init (heathrow_init)在文件 vl.c:main()中被调用(qemu 0.10.5, #5599):
machine->init(ram_size, vga_ram_size, boot_devices,
kernel_filename, kernel_cmdline, initrd_filename, cpu_model);
current_machine = machine;
我们可能有点奇怪 qemu 加载的 ELF 文件(可能为 linux kernel 或是 firmware)是在哪里执
行的?ppc_oldworld.c:ppc_heathrow_init.c()中有两处调用了 load_elf(),第一次加载 firmware,
第二次加载 linux kernel(如果有-kernel 选项的话):
/* Load OpenBIOS (ELF) */
bios_size = load_elf(buf, 0, NULL, NULL, NULL);
...
kernel_size = load_elf(kernel_filename, kernel_base, NULL, &lowaddr, NULL);
两次加载的 entry_point 都没有指定(同加载一个正常的用户空间 ELF 文件不一样)。那
heathrow 是如何启动起来的?回答这个问题之前我们需要了解一点 CPU 初始化的知识。由
于不了解 heathrow(oldmac, 740?),以 Freescale MPC85xx 为例,CPU(指 MPC85xx)上电的
一刻,ip 指向的位置为 0xFFFF_FFFC(当然是虚拟地址,BookE PPC 并没有所谓的“实”模
式);CPU 默认的 ROM 地址为 0xFF80_0000,也就是 4G 的最后 8M,我们可以不全部使用
这 8M,如 u-boot 就只使用了最后的 512KB。但如果想让 firmware 工作正常,我们需要在
0xFFFF_FFFC (在 boot rom 中)放一条跳转指令。以 u-boot 为例,这个指令将跳转至
0xFFFF_E000,即最后一页。这里我们需要特别小心:由于 CPU 刚上电,几乎一切都没有
被初始化;并且对于 BookE(如 MPC85xx, PPC 44x)的 PPC,默认地址转换(即 MMU)
处于开启状态,因此 CPU 必须(默认行为)在 TLB 寄存器里在存储最后一页的映射。我们
开始能使用的地址空间(包括指令,数据)就只有这一页(4KB)。在正确的初始化完 CPU 之
前,试图访问这一页之外的数据会触发 Data TLB Error 或 Instruction TLB Error 异常,但这
时候异常向量表并未被正确的设置,结果是很显然的(更多信息会在后续的 u-boot 初始化
分析中给出)。
函 数 cpu_ppc_reset() 最 后 做 了 一 些 类 似 善 后 的 工 作 , 如 tlb flush , 注 意 这 里 并 没 有
cache flush/invalidate,这是因为 qemu 并不做 cache 模拟。