要想寫一個啓動區代碼,就須要瞭解開機的啓動過程,由於開機過程當中一些硬件的規定決定了這段代碼應該怎麼寫,不明白不要緊,且聽我慢慢道來。html
具體過程在我上一篇文章 【自制操做系統01】硬核講解計算機的啓動過程 講述得一清二楚,這裏咱們簡單回顧一下。瞭解開機過程,並非一個簡單的問題,我總結成你須要有三個前置知識,並記住四次跳躍。ios
首先說下三個前置知識,這些必須假設你是知道的,不然我不可能從質子中字原子開始講起。這三個前置知識就是:git
有這三個前置知識後,接下來就是要記住計算機開機後的四次關鍵跳躍,由於這些都是當時 Intel 和 BIOS 等製做廠商的大叔們定下來的,沒什麼道理可言,記住就好:程序員
知道了上面這些後,咱們就能夠寫啓動區代碼了。別急,咱們先想一下咱們須要作什麼。先不說須要什麼軟件,須要什麼代碼,這些都不是核心問題,咱們先想一下,假如咱們有無所不能的上帝之手,咱們應該怎麼作才能讓開機過程順利走下去呢?架構
首先,BIOS 裏面有一段寫死的代碼,會幫咱們把啓動區的第一扇區的 512 字節的內容,原封不動複製到內存 0x7c00 這個位置,並跳轉到此處,這個是不用咱們管的。編輯器
因此咱們要作的就是,把咱們的指令,寫到硬盤(固然也能夠是光盤、軟盤、U 盤,這裏咱們就只拿硬盤舉例)的第一扇區的 512 字節,並把這部分標記爲「啓動區」(就是把最後兩個字節寫死爲 0x55, 0xaa)。工具
好了,那咱們能夠試想下,若是咱們有上帝之手,應該是這樣一個操做流程。學習
就是這麼簡單,這樣電腦就會乖乖從開機後的某一時刻,開始執行你寫的 010100111001010 的代碼了。網站
其實並不須要上帝之手,有不少工具能夠直接往磁盤中寫數據,或者你把硬盤拿出來用電路板操做也是能夠的,而後在真機上跑你的操做系統。但這樣作太麻煩了,不過就是有人會特別糾結這一點,非要在真機上跑出來才肯罷休,這種精神是值得稱讚的。但學習計算機,該有的抽象仍是要有,能用低成本的方式實現等價的事情,不去糾結這一點是很必要的。ui
因此咱們仍是用低成本的方式來作今天這個事,剛剛上面說的真機,咱們用虛擬機來實現。上面說的硬盤,咱們用虛擬磁盤映像文件來實現。剛剛說的寫硬盤這個過程,咱們也用軟件命令往這個虛擬磁盤映像裏寫。這其實和上面的效果徹底等價,只不過都虛擬化了而已。總結起來就是:
好了,有了上面的知識儲備,終於能夠實踐了。但是一上來就三個工具,聽起來仍是很恐怖,別怕,這部分咱們只用到上述的 QEMU 工具便可。
由於虛擬磁盤映像,其實就只是一個二進制文件而已,在 Windows 電腦上直接右鍵,新建一個文本文件就能夠了。而寫數據這個過程,咱們能夠直接用二進制編輯器打開這個文件,直接往裏面寫就行了,暫時也不須要什麼工具幫助。
所以總結起來,咱們這部分的開發環境,就是下面的兩個(具體安裝步驟見章節四)
此時問題已經簡化到極致了,咱們只須要編輯一個虛擬磁盤映像文件,再用 QEMU 啓動一下就行了。那麼我再簡化一下,虛擬磁盤映像有不少種格式,但其實映射到真正的硬盤中,是同樣的,只不過多種多樣的格式,適合咱們一些工具進行方便讀取和展現。
這裏咱們使用和真正硬盤中數據一一對應的無格式的格式(raw),哈哈這個對刷過 B 站的人應該很容易理解,就是生肉,不經任何加工的原汁原味的格式。這個格式是什麼樣的呢?該格式中記錄磁盤第一扇區的 512 字節的位置,就是該文件從第一個字節開始日後的 512 字節,就是這麼簡單粗暴。
ok,接下來,咱們終於能夠開始真正動筆啦!
--------------- 正片開始預警 ------------------
不廢話,直接上三部曲。
第一步:新建文件
右鍵,新建,文本文檔。隨便取一個名字和擴展名。我這裏取的是 mbr.raw。mbr 的意思是主引導記錄,raw 的意思是無格式的虛擬磁盤映像格式。但你沒必要和我同樣。
第二步:寫入數據並保存
用 Notepad++ 文本編輯工具打開它,切換到 16 進制模式,開始一個字節一個字節寫以下內容,寫好後記得保存。
第三步:QEMU 啓動
shift + 右鍵,在此處打開命令行窗口,輸入以下命令並按下回車
qemu-system-i386 mbr.raw
等一秒鐘,QEMU 啓動,並展現出以下畫面。
第四步:沒了
哈哈就是這麼簡單,若是你看《30 天自制操做系統》,也有一部分會讓你編輯這個二進制文本文件,但你幾乎要花上半小時時間敲出來,再花半小時時間檢查下到底哪裏錯了,最後放棄了,再花十分鐘時間找到這本書的源碼直接 copy 出來,我以爲徹底沒有必要。
若是你還以爲多,那你能夠試試下面這版:
但再用 QEMU 啓動時會是這個效果(注意第一個字母 h 是筆者經過指令打上去的哦):
嘿嘿,這時你是否是發現了什麼,這版比上一版少了哪部分呢?
若是你還不服,那我再來個賴皮版:
別找了,除了最後兩個字節以外,其他的都是 0,用 QEMU 啓動後是這個樣子,這些純是 QEMU 虛擬機中 BIOS 的輸出了,徹底沒有咱們的一行指令,但它卻正確地開機了。
哈哈,這回我能夠自豪地說,我這個是世界上最簡單的啓動區了麼?沒有騙你吧。相信你已經有不少疑問了,那咱們接下來就是揭祕這個二進制文件的時刻。
不廢話,先直接上開發環境。
文本編輯器:Notepad++
不要下載過高版本,彷佛運行插件會有問題,個人是 Notepad++ 7.5.4 release,能夠參考。
虛擬機:QEMU
找到 Windows 版的下載按鈕,一樣也是無腦下載,下載好後記得把目錄加入到環境變量裏。
彙編工具:NASM
官網下載地址:https://www.nasm.us/
一樣也是找到對應的 Windows 系統,選擇一個版本下載,這裏我推薦:
直接點這個鏈接選擇 nasm-2.14.02-installer-x64.exe 下載便可。
下載好後一樣也記得把 bin 目錄加入到環境變量。
虛擬磁盤映像工具:dd
下載完以後其實就是一個 dd.exe 文件,把這個文件所在的目錄,也加到環境變量裏。
OK,整個環境搭建的工做,就結束了,直接所有官方網站一鍵下載,配置下環境變量,就大功告成了,是否是很簡單。這裏又想吐槽下《30 天自制操做系統》,做者用了本身寫的彙編工具,本身寫的磁盤拷貝工具,本身寫的虛擬映像生成工具,還有本身爲 QEMU 寫的啓動腳本。雖然直接下載其代碼就能直接運行啓動,但我就是感受很不友好。
好了,如今咱們開始真正實現這個啓動區代碼了。咦,沒錯,剛剛其實已經實現過了,但沒有人會用這種方式寫操做系統。不過固然也能夠,你能夠只用一個文本編輯器,從第一個字節開始敲,敲到最後一個字節,完成一個操做系統的製做。話說在沒有彙編語言的時候,可不就是這樣作的麼,那時候虛擬機更是沒有,須要用紙帶在真機上一遍一遍試。這樣想一想看如今幸福多了。
經過上面的環境介紹不難猜出,咱們將使用 NASM 彙編語言來實現這個啓動區,最後編譯出來的二進制文件,其實和咱們上面手寫的二進制文件是同樣的,反過來講就是,上面給你們展現的二進制文件,可不是我一個個手打的哦,是我實現寫好了彙編代碼,而後編譯出來的(偷笑)。
咱們就拿剛剛的初版二進制文件來看,咱們能夠把這裏面的字節一一取出來,去查 Intel x86 指令集架構下,這些機器指令對應的 NASM 彙編代碼是什麼?若是你足夠耐心,是能夠一個個查出來的,但咱們 NASM 這個軟件自己就有現成的工具來實現這個「反編譯」的功能。
ndisasm -o0x7c00 mbr.raw
咱們看到:
也就是說,咱們按照第三列的彙編指令把代碼寫出來,再編譯,就能夠了,對吧?沒錯,不過還須要進行一些小調整,咱們直接上代碼。
;----BIOS把啓動區加載到內存的該位置,因此需設置地址偏移量 section mbr vstart=0x7c00 ;----卷屏中斷,目的是清屏 mov ax,0x0600 mov bx,0x0700 mov cx,0 mov dx,0x184f int 0x10 ;----直接往顯存中寫數據 mov ax,0xb800 mov gs,ax mov byte [gs:0x00],'h' mov byte [gs:0x02],'e' mov byte [gs:0x04],'l' mov byte [gs:0x06],'l' mov byte [gs:0x08],'o' ;----512字節的最後兩字節是啓動區標識 times 510-($-$$) db 0 db 0x55,0xaa
咱們看到,和上面反編譯出來的彙編語句,幾乎是同樣的,但仍是有幾處不一樣,你找找看。下面我來一一解釋。
首先第一行
section mbr vstart=0x7c00
其實也能夠寫成:
org 0x7c00
這個的意思用通俗的話講就是把如下代碼加載到內存 0x7c00 這個位置,但這個說法不正確,其本質是 指定一個地址,後面的程序或數據從這個地址值開始分配。好比有個跳轉指令,跳轉到某處的代碼,編譯器能夠經過該處代碼偏移了第一行代碼多少來計算出一個相對的偏移地址,但卻沒法知道其絕對地址是多少,因此有必要讓程序員來告訴編譯器這個信息。
第二部分
;----卷屏中斷,目的是清屏 mov ax,0x0600 mov bx,0x0700 mov cx,0 mov dx,0x184f int 0x10
前面幾行是設置一些寄存器的值,有點像高級語言中方法的入參。最後一行 int 0x10 是調用 BIOS 的 10 號中斷程序,有點像高級語言中調用一個方法。這個不是咱們的重點,總之它實現了一個效果就是清屏。但你不寫這一段也能夠,無非就是讓屏幕多了 QEMU 自己的輸出,難看一點罷了。
第三部分
;----直接往顯存中寫數據 mov ax,0xb800 mov gs,ax mov byte [gs:0x00],'h' mov byte [gs:0x02],'e' mov byte [gs:0x04],'l' mov byte [gs:0x06],'l' mov byte [gs:0x08],'o'
這部分的目的就是讓咱們的操做系統運行可以看出一點效果,有不少種方式,咱們選擇直接往顯存中寫數據的方式,這種方式最簡單,也最直觀。
上一節課咱們講了實模式下的內存分佈,知道 0xB8000 - 0xB8FFFF 這段內存空間是文本模式下顯存的內存映射區域,往這個區域裏寫數據,就至關於在屏幕上輸出文本了。
因此前兩行就是往 gs 段寄存器中寫入該內存區域的起始地址 0xB800(注意這裏不是少寫了一個 0,由於實模式下段寄存器還須要乘以 16,因此此處先除以 16,詳見上一節課的內容)。
後面的五條 mov 語句,就是往這片內存區域的開始的幾個字節處,分別寫入 'h'、'e'、'l'、'l'、'o'。你可能注意到上面反彙編出來的語句中後面不太同樣,其實也能夠寫成:
mov byte [gs:0x0],0x68 mov byte [gs:0x2],0x65 mov byte [gs:0x4],0x6c mov byte [gs:0x6],0x6c mov byte [gs:0x8],0x6f
這是徹底等價的,0x68 是 ‘h’ 的 ASCII 碼,你寫成 ‘h’,彙編工具會自動幫你轉換成 0x68。
第四部分
;----512字節的最後兩字節是啓動區標識 times 510-($-$$) db 0 db 0x55,0xaa
因此這兩行代碼的意思就是,後面一直補充 0,一直到 510 個字節,而後最後兩個字節寫死爲 0x55, 0xaa,一共恰好是 512 字節,完美。
咱們上一步獲得了 mbr.asm,如今執行下面的命令來編譯它,生成一個 mbr.bin:
nasm -o mbr.bin mbr.asm
執行完後能夠看到文件夾下多了一個叫 mbr.bin 的文件,用 Notepad++ 打開它,看看是否是和上面咱們看到的二進制文件同樣呢?
實際上,咱們上面獲得的 mbr.bin 文件,直接用 QEMU 啓動也是能夠的,由於它與一個 raw 格式的虛擬磁盤映像是如出一轍的。不過咱們仍是裝模做樣地建立一個虛擬磁盤映像,而後把剛剛的 bin 文件填充到磁盤的第一扇區,這樣纔像回事嘛。
執行下面的命令建立一個虛擬磁盤映像:
qemu-img create -f raw mbr.raw 1440K
咱們看到後面的數字爲 1440K,這就更像回事了,剛剛咱們本身手寫的二進制文件,只有 512 字節,沒有那個硬盤是這麼小的。這回咱們用工具直接建立一個 1440K 的映像,就更接近真實的硬盤啦。
將 mbr.bin 文件的內容,裝載到 mbr.raw 這個空磁盤映像文件的第一扇區:
dd if=mbr.bin of=mbr.raw bs=512 count=1
該命令也是可以見名知意,就是從 if=mbr.bin 這個文件,往 of=mbr.raw 這個文件拷貝數據,以 bs=512 字節大小做爲單位,拷貝 count=1 這麼多單位的數據。
hooo!到了這步,和咱們一開始的情形就是同樣的了,咱們有了一個 raw 格式的虛擬磁盤映像文件,咱們也有了 QEMU 這個虛擬機能夠進行模擬,天然接下來就是最激動人心的時刻(其實早就激動過了)。
qemu-system-i386 mbr.raw
運行效果也是見了不少次的:
好了,啓動區代碼就是這麼簡單,其實重點在於對整個過程的理解,並不在於最終運行的那一刻,因此你能夠看到大部分時間是在幫你捋順這個過程,實際的代碼量,不多不多。若是本 chat 中有不瞭解的地方,歡迎在留言區留言,或者先去看一下我上一節課的內容,好多問題可能都會找到答案:
若是你對自制一個操做系統感興趣,不妨跟隨這個系列課程看下去,甚至加入咱們,一塊兒來開發。
當你看到該文章時,代碼可能已經比文章中的又多寫了一些部分了。你能夠經過提交記錄歷史來查看歷史的代碼,我會慢慢梳理提交歷史以及項目說明文檔,爭取給每一課都準備一個可執行的代碼。固然文章中的代碼也是全的,採用複製粘貼的方式也是徹底能夠的。
若是你有興趣加入這個自制操做系統的大軍,也能夠在留言區留下您的聯繫方式,或者在 gitee 私信我您的聯繫方式。
本課程打算出系列課程,我寫到哪以爲能夠寫成一篇文章了就寫出來分享給你們,最終會完成一個功能全面的操做系統,我以爲這是最好的學習操做系統的方式了。因此中間遇到的各類坎也會寫進去,若是你能持續跟進,跟着我一塊寫,必然會有很好的收貨。即便沒有,交個朋友也是好的哈哈。