ucore练习四

练习四 分析bootloader加载ELF格式的OS的过程

(1)、bootloader是如何读取硬盘扇区的?
(2)、bootloader是如何加载ELF格式的OS的?

首先要介绍一下 对于bootloader访问硬盘时都是LBA模式的PIO方式,也就是说所有的I/O操作都是通过CPU访问硬盘的I/O地址寄存器完成。操作系统位于第一个硬盘上,而访问第一个硬盘的扇区可以设置I/O端口0x1f0~0x1f7来改变地址寄存器实现。下述表格所显示的即为0x1f0~0x1f7所对应的功能:

I/o地址功能
0x1f0读数据,当0x1f7不为忙状态时,可以读
0x1f1可获得详细的错误信息
0x1f2与读写的扇区数量,每次读写前,都需要表明要读写几个扇区
0x1f3如果是LBA格式,就是读LBA参数的0~7位
0x1f4如果是LBA格式,就是读LBA参数的8~15位
0x1f5如果是LBA格式,就是读LBA参数的16~23位
0x1f6第0~3位:如果是LBA模式就是24-27位 第4位:为0主盘;为1从盘
0x1f7状态和命令寄存器。操作时先给命令,再读取,如果不是忙状态就从0x1f0端口读数据

硬盘的数据存储在硬盘的扇区中,每个扇区的大小为512B,读取一个扇区的流程如下:
1、等待磁盘准备好
2、发出读取扇区的命令
3、等待磁盘准备好
4、把磁盘扇区读取到内存

#define SECTSIZE        512  //表示一个扇区的大小
#define ELFHDR          ((struct elfhdr *)0x10000)  //表示虚拟地址的起始地址

下述函数实现的是将一个扇区读取到内存的代码段

static void
waitdisk(void) {
    /*
    *0x1F7表示0号硬盘的状态寄存器,当状态寄存器的最高两位是01时,表示空闲状态
    *inb(0x1F7) & 0xC0 表示将0x1F7端口所代表的状态寄存器的值和0xC0做与操作 观察0x1F7的最高两位是否是01
    *如果是01,表示空闲,跳出循环,如果不是,则继续循环。
    */
    while ((inb(0x1F7) & 0xC0) != 0x40);  
}

//从secno指定的扇区读取数据到dst位置
static void
readsect(void *dst, uint32_t secno) {
    waitdisk();              //等待磁盘准备好
    //outb表示只读取一个字节的数据到I/O端口中
    outb(0x1F2, 1);                         // 设置读取扇区的数目为1
    outb(0x1F3, secno & 0xFF);              //将要读取的扇区编号写入到0x1F3端口所代表额寄存器中
    outb(0x1F4, (secno >> 8) & 0xFF);       //用来存放读写柱面的低八位字节
    outb(0x1F5, (secno >> 16) & 0xFF);      //用来存放读写柱面的高两位字节
    outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); //用来存放要读写的磁盘号及磁头号
    outb(0x1F7, 0x20);                      //发出读取磁盘扇区的命令

    waitdisk();            //等待磁盘准备好

    insl(0x1F0, dst, SECTSIZE / 4);   //将磁盘扇区的数据读到dst内存中    
}

下面的程序段表示将第一个硬盘的8个扇区依次读入到内存中

static void
/*
*下面的第一个参数表示的虚拟地址的起始地址
*第二个参数表示读取数据的总大小
*第三个表示偏移量
*/
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
    uintptr_t end_va = va + count;    //end_va表示读取数据的结束地址

    va -= offset % SECTSIZE;  //SECTSIZE表示的是一个扇区的长度,这里用起始地址减去偏移地址 得到的是块的首地址

    uint32_t secno = (offset / SECTSIZE) + 1; //存取我们需要读取的磁盘的位置

    //通过for循环将end_va和va地址之间的数据读取到内存中
    for (; va < end_va; va += SECTSIZE, secno ++) {
        readsect((void *)va, secno); //每次循环将secno读取到va内存的位置
    }
}

下述函数是bootloader的入口函数
该函数用于加载elf格式的os

void
bootmain(void) {
    // read the 1st page off disk
    readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);//将硬盘的8个扇区大小的数据读取到ELFHDR所开始的内存地址中  ELF的头占据8个扇区的大小

    //读取完毕之后,在加载操作之前首先需要对ELFHDR进行判断,观察是否是一个合法的ELF头部
    if (ELFHDR->e_magic != ELF_MAGIC) {
        goto bad;       //如果ELF头部不合法 则跳转到bad标号处
    }

    struct proghdr *ph, *eph;  //proghdr表示存储程序块的结构体 在elf.h中定义 ph在这里定义的是指向elf的首地址 eph在这里表示指向elf的尾地址

    // load each program segment (ignores ph flags)
    ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); //e_phoff表示elf程序头表的偏移地址 ph为基地址加上偏移地址 指的是首地址
    eph = ph + ELFHDR->e_phnum; //ph为首地址 e_phnum表示program head表的入口数目 eph指向elf文件的尾地址 
    for (; ph < eph; ph ++) {    //通过for循环 讲elf文件的内容读取到p_va的虚存中
        readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
    }

    //最后调用ELF header表头中的内核入口地址, 实现 内核链接地址 转化为 加载地址
    ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();

bad:
    outw(0x8A00, 0x8A00);
    outw(0x8A00, 0x8E00);

    /* do nothing */
    while (1);
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值