//这个文件跟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();
}