自己动手写操作系统(一)
本系列文章將一步步實現一個簡單的操作系統。實驗環境是在Linux系統下通過Bochs虛擬機運行我們自己寫的操作系統。
一、實驗環境搭建
1. Ubuntu的安裝,Windows用戶可以選擇在虛擬機中安裝Ubuntu,具體安裝教程可自行搜索。
2. Bochs虛擬機的安裝
在學習編寫操作系統的過程中,我們需要一個虛擬機來模擬出一個虛擬的計算機硬件環境,比如cpu、內存、硬盤等,并且能夠運行并且調試我們寫的代碼。Bochs很好的提供了以上所有功能,在它面前我們就像上帝一樣隨時可以讓時間停止,”鉆“到計算機內部,查看這個虛擬電腦的一切信息,這正是開發操作系統所需要的。
Ubuntu下我們可以直接運行以下命令以源碼方式安裝Bochs(注意我們之所以選擇以源碼方式安裝,是因為通過apt install安裝的Bochs是沒有調試功能的)
$ sudo apt update $ sudo apt install build-essential libx11-dev xorg-dev libgtk2.0-dev $ wget https://sourceforge.net/projects/bochs/files/bochs/2.7/bochs-2.7.tar.gz $ tar zxvf bochs-2.7.tar.gz $ cd bochs-2.7/ $ ./configure --enable-debugger --enable-disasm --enable-debugger-gui $ make $ sudo make install./configure后面的參數便是打開調試功能的開關
到此實驗環境搭建完畢。
二、簡單的引導扇區匯編代碼
先簡單講一下計算機的啟動流程,詳細啟動過程可參考我的另一篇博客 操作系統啟動過程
按下開機鍵后,計算機首先會運行BIOS中的代碼,BIOS在進行硬件檢查和初始化后,會按照設置好的啟動順序(我們在使用U盤安裝系統時,經常要進入BIOS設置這個啟動順序),依次尋找啟動設備(比如硬盤、U盤等)。然后將第一個可用的啟動設備的第一個扇區載入內存0x7c00處,并把執行權限交給它。
啟動設備的第一個扇區我們稱之為引導扇區(MBR),共512個字節,必須以數值0x55及0xaa結尾。包括三部分內容:引導加載程序(Boot Loader)(前446個字節,如GRUB等)、磁盤分區表(DPT,Disk Partition Table)、分區有效性標志(55AA)。其中的引導加載程序負責加載啟動硬盤分區中的操作系統。
在BIOS向引導程序移交執行權之前,BIOS會對處理器進行初始化,這其中就包括處理器的代碼段寄存器CS和指令指針寄存器IP。當BIOS跳轉至引導程序時,CS和IP的值分別為0x0000和0x7c00。此時的處理器處于實模式下,物理地址必須經過CS寄存器和IP寄存器轉換才能得到。轉換公式為:物理地址=CS<<4+IP,也就是物理地址0x7c00處。
BIOS由Bochs虛擬機提供,我們接下來寫的就是這個512字節的引導扇區(MBR)的匯編代碼。目前它并不用加載操作系統,我們只讓它在屏幕上打印出經典的“hello world”即可。
首先看一下匯編代碼:
org 0x07c00mov ax,csmov ds,axmov es,axmov ax,Messagemov bp,axmov cx, 13mov ax,0x1301mov bx,0x0002mov dh,0mov dl,0int 0x10jmp $ Message: db "Hello, world!"times 510-($-$$) db 0dw 0xaa55代碼和數據是按匯編程序的編寫順序依次連續存放到內存的,即上面的程序在0x7c00處開始存放的是org 0x07c00的機器指令,在512字節最后放的是0xaa55數據。
BIOS程序在把引導程序加載到內存時,同時還創建了中斷系統,在物理內存的前1KB空間初始化中斷向量表,在物理內存最后256KB物理地址空間內保存中斷處理程序。cpu運行完BIOS后,物理內存的布局如下:
本程序就是調用0x10號中斷,在屏幕上打印字符串。在調用0x10號中斷處理程序往顯示器的屏幕上打印字符串時,所有的參數都是通過cpu中的寄存器傳遞的,各參數的含義如下:
- 寄存器ah:0x13表示向屏幕打印字符串;
- 寄存器al:指定光標和字符的屬性
0,表示字符的屬性值保存在寄存器bl中,光標停留在字符串的首字符
1,表示字符的屬性值保存在寄存器bl中,光標停留在字符串的尾字符
2,表示字符的屬性值緊跟在字符之后,光標停留在字符串的首字符
3,表示字符的屬性值緊跟在字符之后,光標停留在字符串的尾字符 - 寄存器bl:若寄存器al的值為0或者1時,保存字符的屬性值。如圖所示,字符屬性由一個字節大小的數據表示。
- 寄存器[es:bp]:保存字符串的首字符在數據段中的邏輯地址
- 寄存器cx:保存字符串的長度
- 寄存器dh,dl:字符串在屏幕上的起始坐標,其中寄存器dh為行號,寄存器dl為列號。顯示器的屏幕只能顯示25行字符,并且每行只能顯示80個字符,因此dh的取指范圍為0~24,dl的取指范圍為0~79。
代碼解析:
第1行,告訴編譯器程序加載到內存的0x7c00處。
第2~4行,統一數據段寄存器DS和附加段寄存器ES的值和代碼段寄存器CS一致,即不論數據段還是代碼段,段起始地址都是0x7c00。
第5~6行,將字符串"Hello, world!"的首地址傳遞給寄存器bp。注意任何不被方括號[ ]括起來的標簽或變量名都被認為是地址,訪問標簽或變量中的內容必須使用[ ]
第7行,將字符串的長度傳遞給寄存器cx
第8~11行,字符串屬性設置
第12行,調用0x10號中斷處理程序,在屏幕上顯示字符串
第13行,使cpu進入死循環
因為cpu會不停的根據寄存器[cs:ip]中的邏輯地址轉換后的物理地址,從物理內存讀取機器指令,然后對其解析、執行。其中,當運行完一條機器指令后,cpu自動將該機器指令的下一條機器指令的偏移地址賦值給ip。當cpu運行完可執行文件中的最后一個機器指令后,若不采取任何措施,則cpu會將下面的數據看做機器指令,進行取指、解析、執行。因此,需要一條讓cpu進入死循環的機器指令作為可執行文件的最后一條機器指令。
$表示當前行被匯編后的地址,$$表示程序被匯編后的開始地址,也就是0x7c00.
第15、17行,分別以字節和字的形式存放的數據
第16行,表示將0這個字節重復510-($-$$)遍,也就是在剩下的空間不停填充0,直到第510個字節為止。這樣加上結束標志0xaa55占用的兩個字節,恰好是512個字節。
在運行代碼之前我們需要將其轉換成計算機能讀懂的機器指令形式,這就需要編譯器。我們編譯c代碼使用GCC,編譯匯編程序使用nasm編譯器。
把上面的代碼保存成boot.asm,然后使用nasm編譯一下,生成二進制可執行文件boot.bin
$ nasm boot.asm -o boot.bin三、虛擬硬盤的制作
下面我們將制作一個虛擬硬盤并將已經生成的可執行文件boot.bin放到虛擬硬盤的第一個磁盤塊(引導扇區MBR)中。Bochs虛擬機將使用這塊“硬盤”引導啟動。
首先選擇合適的地方創建一個工程目錄
$ mkdir projectest $ cd projectest將可執行文件boot.bin拷貝到該工程目錄中
然后在本層目錄中創建一個大小為1MB的硬盤鏡像文件b.img的命令如下:
$ dd if=/dev/zero of=b.img bs=512 count=2048dd是文件拷貝命令,其中:
- if=/dev/zero:表示拷貝的源文件的路徑,"/dev/zero"是一個特殊的文件,可以提供n個0(n的值等于bs和count參數的積)
- of=b.img:表示拷貝的目標文件路徑。若不存在,則創建該文件
- bs=512:表示塊大小,單位為B
- count=2048:表示拷貝的文件的塊的數量。
由bs和count參數可知,硬盤鏡像文件的大小為:2048*512B=1MB,硬盤鏡像文件制作好后,將可執行文件boot.bin拷貝到硬盤鏡像文件b.img(硬盤)的引導扇區的命令如下:
$ dd if=boot.bin of=b.img bs=512 seek=0 conv=notrunc其中:
- seek=0:表示把可執行文件boot.bin拷貝到硬盤鏡像文件b.img的引導扇區(扇區號為0)。
- conv=notrunc:表示不改變目標文件的大小,若沒有該選項,則硬盤鏡像文件b.img的大小會由1MB變為可執行文件boot.bin的大小512B。
這樣一個寫入了引導程序的“硬盤”就制作好了。
四、Bochs的使用
1. 啟動Bochs
"硬盤”制作好后,要想啟動bochs還需要一個配置文件——bochsrc.bxrc。為什么需要配置文件呢?因為你需要告訴bochs你希望的虛擬機是什么樣的,比如,內存多大,使用哪個硬盤啟動等等。在下載bochs的源碼包中有一個.bochsrc,就是官方提供的配置文件示例,我們可以根據這個更改。
下面是本實驗用到的bochs配置文件代碼
romimage: file=/usr/local/share/bochs/BIOS-bochs-latest vgaromimage: file=/usr/local/share/bochs/VGABIOS-lgpl-latest ata0-master: type-disk, path="b.img" megs: 16 cpu: count=1 boot: disk其中:
- romimage:指定bochs運行過程中使用的ROM-BIOS的路徑。
- vgaromimage:指定bochs運行過程中使用的VGA的ROM-BIOS的路徑。
- ata0-master:指定硬盤鏡像文件b.img的路徑。
- megs:指定物理內存的大小,單位為MB。
- cpu:指定cpu的個數,1個。
- boot:指定啟動方式,從硬盤啟動。
將上面的代碼保存為bochsrc.bxrc,也存到工程目錄下。
現在一切準備就緒,啟動bochs的命令如下:
$ bochs -q -f bochsrc.bxrc其中:
- -q: 跳過bochs啟動后的配置界面。
- -f : bochsrc.bxrc:指定配置文件的路徑。
- 如果不指定路徑,那么Bochs將按照如下順序在當前目錄中尋找配置文件:
.bochsrc
bochsrc
bochsrc.txt
bochsrc.bxrc (windows only)
/home/.bochsrc (Unix only)
/etc/bochsrc(Unix only)
運行bochs后會在終端出現bochs調試命令行,等待我們輸入調試命令,這里輸入c繼續執行
可以看到在虛擬機中我們的引導程序已成功運行,在屏幕上打印出了hello world。
2. Bochs調試
| b | 使用物理地址打斷點 |
| vb | 使用邏輯地址打斷點 |
| blist | 查看所有斷點信息 |
| n | 單步執行(遇到函數跳過) |
| s | 單步執行(遇到函數進入函數內部) |
| c | 繼續執行 |
| r | 查看所有通用寄存器的值 (eax、ebx、ecx、edx、esp、ebp、esi、edi、eip、eflags) |
| sreg | 查看所有段寄存器的值 |
| u /5 | 打印CPU接下來將執行的5條指令 |
| xp | 查看物理內存中指定物理地址的內容 xp /2bx 物理地址:打印兩個字節,以十六進制格式輸出。 xp /13c 物理地址:打印13個字節,以ASCII碼對應的字符顯示 |
| watch 變量名 | 運行時,若某一行代碼修改了變量,則中斷,并打印修改前后的值。 |
| q | 退出調試繼續執行 |
總結
以上是生活随笔為你收集整理的自己动手写操作系统(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BackTrack4安装中文语言包
- 下一篇: Windows电脑生成iOS证书p12及