实验3-----

---恢复内容开始---

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,因为段在文件中时许多未定义的变量并没有分配空间给它们

猜你喜欢

转载自www.cnblogs.com/stankangyong/p/10527787.html