在第一節<(1)彙編寫入引導區,虛擬機啓動步驟>中講解到一個簡單屏幕顯示一川字符串,第二節講到BIOS啓動過程!c++
第一節中基本原理就是將那個彙編代碼用nasm彙編器進行彙編成二進制,而後把這二進制文件寫入模擬的軟盤system.img[磁盤]的第0面0磁道第1扇區中!而後虛擬機加載此映射文件。編程
BIOS讀取硬盤0盤面0磁道1扇區[0磁頭0柱面1扇區](C0-H0-S1)的MBR(主引導記錄)到內存中指定區域(具體是BIOS提供的int 19中斷例程加載MBR到RAM的0X00007C00H開始處),設置程序計數器到指定區域(EIP=0X00007C00),而後CPU開始執行MBR的指令(即CPU使用權交由MBR來掌控)。函數
因爲如今系統內核通常都很大很大,MBR根本存儲不下,因而人們想到了用boot loader(加載別處指定硬盤位置數據到內存指定位置,而後跳轉CPU到內存指定位置在執行指令,這樣就跳出了512限制),而後CPU就能夠執行命令了。oop
簡單的說,整個開機流程到操做系統以前的動做應該是這樣的:測試
1.BIOS:開機主動執行的韌體,會認識第一個開機的設備this
2.MBR:第一個可開機設備的第一個扇區內的主引導分區塊,內含引導加載程序。spa
3.引導加載程序:一個可讀取內核文件來執行的軟件。操作系統
4.內核文件:開始操做系統的功能。.net
BIOS與MBR都是硬件自己會支持的功能,至於Boot loader(引導加載程序)則是操做系統安裝在MBR上面的一套軟件。因爲MBR僅有466bytes而已,所以這個引導程序是很是小而完美的。這個boot loader的主要任務有下面幾項:code
1)那麼首先開始寫主引導boot程序(做用:將硬盤的第0面0磁道2扇區[0磁頭0柱面2扇區](C0-H0-S2)讀取1個扇區的內容到內存中0X8000位置,而後跳轉到這個位置執行指令)
實例1、
org 0x7c00 ;指定起始位置 LOAD_ADDR EQU 0X8000 ;加載地址位置 entry: mov ax, 0 ;清0 mov ss, ax mov ds, ax mov es, ax mov si, ax readFloppy: mov CH, 0 ;CH 用來存儲柱面號 mov DH, 0 ;DH 用來存儲磁頭號 mov CL, 2 ;CL 用來存儲扇區號 mov BX, LOAD_ADDR ; ES:BX 數據存儲緩衝區 mov AH, 0x02 ; AH = 02 表示要作的是讀盤操做 mov AL, 1 ; AL 表示要練習讀取幾個扇區 mov DL, 0 ;驅動器編號,通常咱們只有一個軟盤驅動器,因此寫死爲0 INT 0x13 ;調用BIOS中斷實現磁盤讀取功能 JC fin ;出錯 跳轉到fin jmp LOAD_ADDR ;CPU-EIP跳轉到0x8000位置 fin: HLT jmp fin
實例2、
org 0x7c00; LOAD_ADDR EQU 0X8000 ;MBR BIOS加載此二進制代碼到內存0X8000地址上,CPU運行 entry: mov ax, 0 mov ss, ax mov ds, ax mov es, ax mov ss, ax mov si, ax mov sp, 0x7c00 mov ax, 0xb800 mov gs, ax ;設置顯示器配置 mov ax, 0600h mov bx, 0700h mov cx, 0 ; 左上角: (0, 0) mov dx, 184fh ; 右下角: (80,25), ; 由於VGA文本模式中,一行只能容納80個字符,共25行。 ; 下標從0開始,因此0x18=24,0x4f=79 int 10h ; int 10h readSection1: ;讀取0磁頭0柱面2扇區數據到內存的BX地址處 mov BX, LOAD_ADDR ; BX = 0X8000 ES:BX數據存儲緩衝區的地址 mov AH, 0x02 ; AH = 02表示要作的是讀盤操做 mov AL, 1 ; AL表示要連續讀取幾個扇區 mov CH, 0 ;CH 用來存儲柱面號 mov CL, 2 ;CL 用來存儲扇區號 mov DH, 0 ;DH 用來存儲磁頭號 mov DL, 0 ;驅動器編號,通常咱們只有一個軟盤驅動器,因此寫死爲0 INT 0x13 ;調用BIOS中斷實現磁盤讀取功能 JC fin jmp LOAD_ADDR ;EIP跳轉到地址0x8000處 fin: jmp $ ; 使程序懸停在此
2)CPU-EIP跳轉到0x8000位置上(這裏設置0x8000H上,固然 你能夠本身設置未使用的內存地址上都是能夠的),這時候咱們能夠寫簡單的一些系統內核,下面進行測試
該段彙編主要是向顯卡循環顯示一個一個字符,最後取值爲0就跳轉fin執行HLT讓CPU睡眠,死循環!
要顯示一個字符,int 0x10則知足條件
AH=0X0E;AL=須要顯示的字符code;BH=0;BL=顏色code
實例1、利用BIOS中斷int 0x10來顯示文本
org 0x8000 ;起始地址0x8000 entry: mov ax, 0 ;清理 mov ss, ax mov ds, ax mov es, ax mov si, msg ;將msg地址給si putloop: mov al, [si] ;取si值給al add si, 1 cmp al, 0 ;al與0比較 je fin ;al爲0時跳轉fin ; 如下三行是爲了顯示AL中保存的字符 mov ah, 0x0e ;AH必須爲0x0e;在Teletype模式下顯示字符 mov bx, 15 ;BH = 0, BL = 15,合起來就是BX=15,這個15是指顏色的編號爲15 int 0x10 ;執行BIOS中段,簡單理解一個函數,該函數的地址是0x10,該函數的做用是顯示一個字符 jmp putloop fin: HLT jmp fin msg: DB "jadeshu create OS kernel!"
實例2、利用BIOS中斷int 0x10來顯示文本
org 0x8000 entry: mov ax, 0 mov ss, ax mov ds, ax mov es, ax mov ss, ax ;在光標位置處打印字符. mov ah, 3 ; 輸入: 3號子功能是獲取光標位置,須要存入ah寄存器 mov bh, 0 ; bh寄存器存儲的是待獲取光標的頁號 int 0x10 ; 輸出: ch=光標開始行,cl=光標結束行 ; dh=光標所在行號,dl=光標所在列號 ;; 打印字符串 ;; mov ax, msg mov bp, ax ; es:bp 爲串首地址, es此時同cs一致, ; 開頭時已經爲sreg初始化 ; 光標位置要用到dx寄存器中內容,cx中的光標位置可忽略 mov cx, 25 ; cx 爲串長度,不包括結束符0的字符個數 mov ax, 0x1301 ; 子功能號13是顯示字符及屬性,要存入ah寄存器, ; al設置寫字符方式 ah=01: 顯示字符串,光標跟隨移動 mov bx, 0x2 ; bh存儲要顯示的頁號,此處是第0頁, ; bl中是字符屬性, 屬性黑底綠字(bl = 02h) int 0x10 ; 執行BIOS 0x10 號中斷 jmp $ ; 使程序懸停在此 msg: DB "jadeshu create OS kernel!"
分別將上面的兩個彙編程序用nasm進行彙編成二進制!生成後命令爲boot和kernel!
雖然咱們將引導區MBR和內核都簡單寫出來了,可是咱們還沒法運行,那麼接下來就須要將這兩個二進制文件寫人咱們本身用軟件模擬的軟盤內。
下面開始創建一個模擬軟盤文件,模擬硬盤一個盤面(該盤面有兩面,80個柱面[磁道],每一個柱面有18個扇區)512*18*2*80=1474560約等於1.4M硬盤
HardDisk.h
#ifndef __HARDDISK_H__ #define __HARDDISK_H__ #define SECTOR_SIZE 512 #define CYLINDER_COUNT 80 #define SECTORS_COUNT 18 #include <string> class CHardDisk { public: CHardDisk(); ~CHardDisk(); // 設置硬盤盤面、柱面、扇區 void setMagneticHead(unsigned int head) { this->head = head; } void setCylinder(int cylinder) { this->current_cylinder = cylinder; } void setSector(int sector) { this->current_sector = sector; } // 獲取扇區數據 char* getDiskBuffer(unsigned int head, int cylinder_num, int sector_num); // 將buf數據寫入指定扇區 void setDiskBuffer(unsigned int head, int cylinder_num, int sector_num, char* buf); // 製做映像文件 void makeVirtualDisk(const char* name = "system.img"); void writeFileToDisk(const char* fileName, bool bootable, int cylinder, int beginSec); private: unsigned int head = 0; // 默認盤面 int current_cylinder = 0; // 當前磁道號 int current_sector = 0; // 當前扇區號 char* disk0[CYLINDER_COUNT][SECTORS_COUNT+1]; char* disk1[CYLINDER_COUNT][SECTORS_COUNT+1]; }; #endif
HardDisk.cpp
#include "HardDisk.h" CHardDisk::CHardDisk() { char* buf = nullptr; // 初始化硬盤,在分配和初始化內存中的數據 for (int i = 0; i < CYLINDER_COUNT; i++) { for (int j = 1; j < SECTORS_COUNT+1; j++) { buf = new char[SECTOR_SIZE]; // 效率低下 memset(buf, 0, SECTOR_SIZE); this->disk0[i][j] = buf; buf = new char[SECTOR_SIZE]; memset(buf, 0, SECTOR_SIZE); this->disk1[i][j] = buf; } } } CHardDisk::~CHardDisk() { // 釋放分配的內存 for (int i = 0; i < CYLINDER_COUNT; i++) { for (int j = 1; j < (SECTORS_COUNT + 1); j++) { delete[] this->disk0[i][j]; delete[] this->disk1[i][j]; } } } char* CHardDisk::getDiskBuffer(unsigned int head, int cylinder_num, int sector_num) { this->setMagneticHead(head); this->setCylinder(cylinder_num); this->setSector(sector_num); if (head == 0) { return this->disk0[cylinder_num][sector_num]; } else { return this->disk1[cylinder_num][sector_num]; } } void CHardDisk::setDiskBuffer(unsigned int head, int cylinder_num, int sector_num, char* buf) { char* bufTmp = getDiskBuffer(head, cylinder_num, sector_num); //memcpy_s(bufTmp, SECTOR_SIZE, buf, SECTOR_SIZE); memcpy(bufTmp, buf, SECTOR_SIZE); } void CHardDisk::makeVirtualDisk(const char* name) { printf("準備開始寫入......\r\n"); FILE* file = nullptr; fopen_s(&file, name, "wb"); for (int cylinder = 0; cylinder < CYLINDER_COUNT; cylinder++) { // 讀完0面就讀同一位置的1面數據 for (int head = 0; head <= 1; head++) { for (int sector = 1; sector < (SECTORS_COUNT+1); sector++) { char* buf = getDiskBuffer(head, cylinder, sector); // 將軟件模擬的磁盤內容寫入指定文件內 fwrite(buf, 1, SECTOR_SIZE, file); } } } fclose(file); printf("寫入成功\r\n"); } void CHardDisk::writeFileToDisk(const char* fileName, bool bootable, int cylinder, int beginSec) { FILE* file = nullptr; fopen_s(&file, fileName, "rb"); if (!file) { printf("讀取文件不存在\r\n"); } char* buf = new char[512]; memset(buf, 0, 512); if (bootable) { buf[510] = 0x55; buf[511] = 0xaa; } //求得文件的大小 fseek(file, 0, SEEK_END); int size = ftell(file); rewind(file); if (size > SECTOR_SIZE) { // 文件數據大於512字節,另做處理 //while (fread(buf, 1, size, file) == size) //{ // setDiskBuffer(0, cylinder, beginSec, buf); // beginSec++; // if (beginSec > 18) { // beginSec = 1; // cylinder++; // } //} } else { fread(buf, 1, size, file); setDiskBuffer(0, cylinder, beginSec, buf); } fclose(file); file = nullptr; }
main.cpp
#include "HardDisk.h" int main() { CHardDisk disk; // 章節一案例 //disk.writeFileToDisk("test", true, 0, 1); //disk.makeVirtualDisk("system01.img"); // 章節二案例 // 將boot二進制文件寫入0柱面1扇區 disk.writeFileToDisk("boot", true, 0, 1); // 將kernel二進制文件寫入1柱面2扇區 disk.writeFileToDisk("kernel", false, 0, 2); disk.makeVirtualDisk("system02.img"); system("pause"); return 0; }
最後生成system02.img文件
用虛擬機打開,詳情見第一節
實例一顯示結果:
實例二顯示結果:(黑底藍字)