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