XV6 - bootman.c

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


你可能感兴趣的:(XV6 - bootman.c)