logo资料库

qemu初始化代码分析.docx

第1页 / 共3页
第2页 / 共3页
第3页 / 共3页
资料共3页,全文预览结束
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 模拟。
分享到:
收藏