---恢复内容开始---
1.编译过程详解
1.1实验1编译过程解析
在上述过程表示了一个bootloader经过编译最终生成在虚拟硬盘上扇区的全过程。
2.实验2新增的编译过程
在上面的基础上,新增如下构建过程
……
ld -m elf_i386 -Ttext 0x100000 -e kern_init -o bin/kernel obj/kern/init/init.o obj/kern/libs/printf.o obj/kern/driver/console.o obj/libs/printfmt.o obj/libs/string.o
……
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
这两步是生成ucore的关键。第一步把ucore涉及的各个.o目标文件链接起来,并在bin目录下形成ELF文件格式的文件kernel,这就是我们第一个ucore操作系统,而且设定ucore入口地址在0x10000,即kern_init函数的起始位置。这也就意味着bootloader需要把读出的kernel文件的代码段+数据段放置在0x10000起始的内存空间。第二步是把bin目录下的kernel文件直接覆盖到ucore.img(虚拟硬盘的文件)的bootloader所处扇区(即第一个扇区,主引导扇区)之后的扇区(第二个扇区)。如果一个扇区大小为512字节,这kernel覆盖的扇区数为上取整(kernel的大小/512字节)
2.实验代码
1.函数解析
static void readsect(void *dst, uint32_t secno) { // wait for disk to be ready waitdisk(); outb(0x1F2, 1); // count = 1 outb(0x1F3, secno & 0xFF); outb(0x1F4, (secno >> 8) & 0xFF); outb(0x1F5, (secno >> 16) & 0xFF); outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); outb(0x1F7, 0x20); // cmd 0x20 - read sectors // wait for disk to be ready waitdisk(); // read a sector insl(0x1F0, dst, SECTSIZE / 4); }
该函数功能是读取第secno扇区的数据到内存地址dst。
static void readseg(uintptr_t va, uint32_t count, uint32_t offset) { uintptr_t end_va = va + count; // round down to sector boundary va -= offset % SECTSIZE; // // translate from bytes to sectors; kernel starts at sector 1 uint32_t secno = (offset / SECTSIZE) + 1; // If this is too slow, we could read lots of sectors at a time. // We'd write more to memory than asked, but it doesn't matter -- // we load in increasing order. for (; va < end_va; va += SECTSIZE, secno ++) { readsect((void *)va, secno); } }
该函数功能是从第一个扇区起偏移offset个字节的地方,读取count个字节到起始内存地址是va的地方。需要注意,这个函数在读取时以磁盘扇区作为基本单位,所以可能多读字节。另外对于offset的处理,其为了保证从va开始存放的是我们想要的字节,比如offset是3,SECTSIZE大小是8,va地址是1000;那么va首先减少3变为997,然后读一个扇区大小的数据到997的地址。此时1000地址正好是磁盘3个offset的字节内容。
2.流程
void bootmain(void) { // read the 1st page off disk readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0); // is this a valid ELF? //ELFHDR内存地址存入的是第一个磁盘扇区的数据(即kernel的ELF文件数据) //所以ELFHDR为ELF文件头,检查其是否是正常 if (ELFHDR->e_magic != ELF_MAGIC) { goto bad; } struct proghdr *ph, *eph; // load each program segment (ignores ph flags) // 获取第一个program header表的地址,其中e_phoff为program segment到ELFHDR的偏移 // e_phnum代表了program的个数,eph最终指向program header表末 ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); eph = ph + ELFHDR->e_phnum; for (; ph < eph; ph ++) { readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); // 读取ELF文件内各program segment的到 kernel后的链接地址 } // call the entry point from the ELF header // note: does not return ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))(); bad: outw(0x8A00, 0x8A00); outw(0x8A00, 0x8E00); /* do nothing */ while (1); }
注意:for循环就是在加载所有的段到内存中。ph->paddr根据参考文献中的说法指的是这个段在内存中的线性地址(此时没开启分页只有线性地址即为物理地址)。ph->offset字段指的是这一program段的开头相对于这个elf文件开头的偏移量。ph->filesz字段指的是这个段在elf文件中的大小。ph->memsz则指的是这个段被实际装入内存后的大小。通常来说memsz一定大于等于filesz,因为段在文件中时许多未定义的变量并没有分配空间给它们