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();
}
相關文章
相關標籤/搜索