//这个文件跟bootasm.S将一起连接成bootblock #include "types.h" #include "elf.h" #include "x86.h" #include "memlayout.h" #define SECTSIZE 512 void readseg(uchar*, uint, uint); void bootmain(void) { struct elfhdr *elf; // 在elf.h中, elf文件头(File header)的结构体 struct proghdr *ph, *eph; // 在elf.h中, 程序段头(Program section header)的结构体 void (*entry)(void); // 声明函数, 函数的内存地址在entry处 uchar* pa; elf = (struct elfhdr*)0x10000; // scratch space // Read 1st page off disk readseg((uchar*)elf, 4096, 0);
// pa是Kernel文件将要复制到的地方, count是复制多少个字节, // Kernel文件可能在一个硬盘扇区中的某个位置开始写的, offset表示Kernel文件开始的位置相对于1号扇区开始处(LBA的排列方式, 从零开始, 0号扇区(引导扇区)已经有bootblock了)偏移多少个字节 // offset=0, 也就是意味着kernel文件存储起点从硬盘1号扇区开始处 void readseg(uchar* pa, uint count, uint offset) { uchar* epa; epa = pa + count; //pa = 0x0001 0000, count = 0x1000 // 因为从硬盘读数据是按扇区(512字节)的, 如果kernel文件的开始位置不是扇区的开头时, 因为我们要将kernel文件放入内存0x0001 0000处, // 所以要将扇区开头不是kernel文件的部分放入到0x0001 0000以前, pa -= offset % SECTSIZE; // 根据偏移, 计算kernel文件在硬盘哪个扇区开始存放 // 从现在开始, offset就变成要读的扇区号了, 不在是kernel文件的偏移量了 // 合理利用变量, 这代码真漂亮 offset = (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. //每次读一个扇区(512字节) // 直到读够0x1000个字节, 即读8次, 每次0x200个 for(; pa < epa; pa += SECTSIZE, offset++) readsect(pa, offset); }
void waitdisk(void) { // 端口1F7H 0号硬盘状态寄存器(读时)、0号硬盘命令寄存器(写时) //0 ERR,错误(ERROR),该位为1表示在结束前次的命令执行时发生了无法恢复的错误。在错误寄存器中保存了更多的错误信息。 //1 IDX,反映从驱动器读入的索引信号。 //2 CORR,该位为1时,表示已按ECC算法校正硬盘的读数据。 //3 DRQ,为1表示请求主机进行数据传输(读或写)。 //4 DSC,为1表示磁头完成寻道操作,已停留在该道上。 //5 DF,为1时,表示驱动器发生写故障。 //6 DRDY,为1时表示驱动器准备好,可以接受命令。 //7 BSY,为1时表示驱动器忙(BSY),正在执行命令。在发送命令前先判断该位 // 0xC0 = 1100 0000 // 0x40 = 0100 0000 // 当6号位是1, 7号位是0时, 跳出循环 while((inb(0x1F7) & 0xC0) != 0x40) ; } // Read a single sector at offset into dst. void readsect(void *dst, uint offset) { // 等待硬盘能够执行命令 waitdisk(); // 0x1F2是8位端口, 设置要读取的扇区数 outb(0x1F2, 1); // 读取一个扇区 //1F3H 扇区号寄存器或LBA块地址0~7 outb(0x1F3, offset); //1F4H 磁道数低8位或LBA块地址8~15 outb(0x1F4, offset >> 8); //1F5H 磁道数高8位或LBA块地址16~23 outb(0x1F5, offset >> 16); //1F6H 驱动器/磁头寄存器:指定硬盘驱动器号与磁头号和寻址方式 // 0-3号位, 磁头或LBA块地址24~27 // 4号位, 设备选择, 为0时, 代表master, 为1时, 代表slave // 5号位和7号位, 恒为一 // 6号位, 为1时,代表LBA方式寻址, 为0时, 代表CHS方式寻址 // 0xE0 = 1110 0000代表LBA寻址, 并且使用master outb(0x1F6, (offset >> 24) | 0xE0); // 0x20代表读扇区命令(带重试) outb(0x1F7, 0x20); // cmd 0x20 - read sectors // 等待硬盘能够执行命令 waitdisk(); // 在x86.h中, 使用一个嵌入汇编的内联函数 // 从端口1F0H中读取数据到dst中, 每次4字节, 读取0x80次 insl(0x1F0, dst, SECTSIZE/4); }
static inline void insl(int port, void *addr, int cnt) { asm volatile("cld; rep insl" : "=D" (addr), "=c" (cnt) : "d" (port), "0" (addr), "1" (cnt) : "memory", "cc"); } //执行后的结果是: //将port 放入EDX中 //将addr放入EDI中 //将cnt放入ECX中 // 然后执行 // cld //rep insl (%dx), %es:(%edi) #每次从dx代表的端口中,读取4字节, 放入es:edi中, 重复ECX次 // ret
//上面代码已经将kernel加载到0x0001 0000 处 //判断是不是ELF可执行文件 if(elf->magic != ELF_MAGIC) return; // let bootasm.S handle error // 加载程序段,通过文件头中的phoff ph = (struct proghdr*)((uchar*)elf + elf->phoff); // 根据elf头中的phnum,可以知道有几个程序段 eph = ph + elf->phnum; // 将ELF中的程序段,根据程序头表的描述, 载入到内存相应的位置中 for(; ph < eph; ph++){ pa = (uchar*)ph->paddr; readseg(pa, ph->filesz, ph->off); if(ph->memsz > ph->filesz) stosb(pa + ph->filesz, 0, ph->memsz - ph->filesz); } // 根据ELF文件头中的入口点, 执行kernel文件 entry = (void(*)(void))(elf->entry); entry(); }