制作你的 ToyOS 系列(Ⅰ)

譯者:penghuster
作者: Krishnakumar R.
原文:http://www.lxweimin.com/p/88900eac8507



本文是構(gòu)建一個小型的內(nèi)核引導(dǎo)扇區(qū)的動手實踐教程。這個第 1 部分提供了電腦開機啟動執(zhí)行動作的背后理論,它也說明了我們的計劃。第 2 部分闡述了在進一步前進之前你應(yīng)該進行的所有動作。第 3 部分處理了這個程序。我們的小啟動程序不會真實的啟動 Linux,但是會在屏幕上打印一些消息。


1. 背景

1.1 奇幻的外衣

微處理器控制電腦。在啟動時,每個微處理器僅僅相當(dāng)于一個8086。盡管你可能有一個最新品牌的 Pentium 微處理器,他也僅能提供 8086 微處理器相同的功能。 基于這一點,我們需要利用某些軟件切換處理器到臭名昭著的保護模式。直到那時,我們才能使用處理器的全部能力。

1.2 工作概述

最初,電腦是由 BIOS 控制。BIOS 就是存儲在 ROM 中的多個程序集合。BIOS 執(zhí)行上電自檢(POST),并檢查電腦的完整性(例如周邊設(shè)備是否正常工作,鍵盤是否已經(jīng)連接等。)。這就是你聽到從電腦發(fā)出蜂鳴聲的時刻。如果一切都正常,BIOS 選定一個啟動設(shè)備,并復(fù)制該設(shè)備的第一個扇區(qū)(即引導(dǎo)扇區(qū))中的數(shù)據(jù)到內(nèi)存地址 0x7C00處。然后,系統(tǒng)控制權(quán)將轉(zhuǎn)移到該位置。啟動設(shè)備可能是一個軟盤、CD-ROM,硬盤或者其他設(shè)備。這里我們將以一個軟盤作為系統(tǒng)啟動設(shè)備。如果我們已經(jīng)寫了一些代碼到軟盤的啟動扇區(qū)中,現(xiàn)在我們的代碼將被執(zhí)行。由此,我們的工作就清晰了,即寫入某些程序到軟盤的啟動扇區(qū)中。

1.3 計劃

首先,用 8086 匯編寫一個小程序(不要害怕,我將教你如何來編寫它),然后,拷貝該程序到軟盤的啟動扇區(qū)中。關(guān)于拷貝,我們將編寫一個 c 程序。從軟盤啟動電腦,然后享受它。

2. 準(zhǔn)備

as86
這是一個匯編器,可以將我們寫的匯編代碼轉(zhuǎn)換為一個目標(biāo)文件。
ld86
這是鏈接器,將上述的目標(biāo)文件轉(zhuǎn)換為真實的機器語言。機器語言是一種能夠被 8086 處理器所能理解的形式。
gcc
這是 C 編譯器,現(xiàn)在我們需要編寫一個將 OS 轉(zhuǎn)移到軟盤中的 C 程序。
空白軟盤
該軟盤被用來存儲操作系統(tǒng),它也是系統(tǒng)的啟動設(shè)備。
Linux 開發(fā)環(huán)境
很顯然你知道其功能。

as86 和 ld86 是大部分標(biāo)準(zhǔn) Linux 發(fā)行版的預(yù)裝軟件。如果沒有,你可以通過網(wǎng)站 http://www.cix.co.uk/~mayday/ 獲得。兩者包含在一個名為 bin86 的包中。其詳細使用文檔在 www.linux.org/docs/ldp/howto/Assembly-HOWTO/as86.html

3. Start!

3.1 引導(dǎo)扇區(qū)

打開你常用的編輯器,輸入如下幾行:

entry start
start:
mov ax, #0xb800
mov es, ax
seg es
mov [0], #0x41
seg es
mov [1], #0x1f
loop1: jmp loop1

這是一段 as86 格式匯編代碼。第一個語句指定該程序的入口點,我們規(guī)定程序應(yīng)該從 start 標(biāo)簽開始執(zhí)行。第二行描述了開始標(biāo)簽 start 的位置(不要忘記,start 后緊跟冒號)。start 標(biāo)簽后緊跟的代碼行是該程序執(zhí)行的第一句代碼 。

0xb800 是顯存地址。# 號代表立即數(shù)。執(zhí)行語句 mov ax #0xb800 將使寄存器 ax 的值改寫為 0xb800,也就是顯存地址。然后移動該值到寄存器 es 中,es 是附加段寄存器。如你所知,8086 程序是一個分段結(jié)構(gòu),其段包括代碼段、數(shù)據(jù)段、附加段等,對應(yīng)的段寄存器是 csdses等。通過執(zhí)行上述兩句代碼,我們將 es 寄存器置為顯存地址,因此任何寫到附加段的數(shù)據(jù)都將進入顯存中。

為了在屏幕上顯示字符,需要在顯存中寫入兩個字節(jié)。第一個字節(jié)表示該字符的 ascii 碼,第二個字節(jié)表示該字符的顯示屬性,屬性值應(yīng)該指定該字符的顏色、背景色、是否閃爍等。seg es 事實上是一個前綴,該前綴指明了關(guān)于附加段中接下來將要執(zhí)行的那條指令。如指令所示,將 A 的 ascii 碼值 0x40 寫入顯存的第一個字節(jié)。然后需要寫入該字符的屬性到下一個字節(jié),這里我們輸入 0x1f 屬性值,該屬性值表示該字符為白色,背景色為藍色。因此如果執(zhí)行此程序,將在屏幕的藍色背景上打印出一個白色字符 A。最后,是一個死循環(huán)。當(dāng)程序完成字符顯示后,需要終止執(zhí)行或進入一個死循環(huán)。保存該文件為 boot.s

顯存的概念可能沒有說清楚,因此我將進一步對其進行解釋。假定有一個 80 列 40 排的屏幕,因此對于每一行需要 160 字節(jié),每個字符 1 個字節(jié),對應(yīng)字符的屬性 1 個字節(jié)。如果需要寫一些字符到第 3 列,那么需要跳過0、1 兩個字節(jié)(第 1 列)和2、3 兩個字節(jié)(第 2 列);然后寫入待寫字符的 ascii 值到顯存的第 4 個字節(jié),該字符的屬性到顯存的第 5 個字節(jié)。

3.2 將引導(dǎo)扇區(qū)寫入軟盤

我們需要編寫一個 C 程序,該程序拷貝 OS 代碼到軟盤的第一個扇區(qū)。其代碼如下:

#include <sys/types.h> //unistd.h 需要
#include <unistd.h> //包含 read/write
#include <fcntl.h>
int main()
{
    char boot_buf[512];
    int floppy_desc, file_desc;
    file_desc = open("./boot", O_RDONLY);
    read(file_desc, boot_buf, 510);
    close(file_desc);
    boot_buf[510] = 0x55;
    boot_buf[511] = 0xaa;
    floppy_desc = open("/dev/fd0", O_RDWR);
    lseek(floppy_desc, 0, SEEK_CUR) ;
    write(floppy_desc, boot_buf, 512);
    close(floppy_desc);
    return 0;
}

首先,以只讀方式打開文件 boot,并將該打開文件的文件描述符賦值給變量 file_desc。讀此文件到末尾或 510 個字節(jié)到字符數(shù)組 boot_buf 中,這里 boot 很小,故前一情況正好發(fā)生。為了優(yōu)雅的代碼,及時關(guān)閉此文件。

最后 4 行代碼中,打開一個軟盤設(shè)備(該軟盤最有可能是 /dev/fd0),然后寫入 boot_buf 中的 512 個字節(jié)到該軟盤中。

readwritelseekman 頁面給出了這些函數(shù)的參數(shù)含義以及函數(shù)用法。在上述兩段代碼之間由兩行看起來有點神秘的代碼:

boot_buf[510] = 0x55;
boot_buf[511] = 0xaa;

此信息是用于 bios 識別可啟動設(shè)備。若在設(shè)備的第一個扇區(qū)的第 510 和 511 個字節(jié)位置分別是 0x55 和 0xaa,則表明該設(shè)備為可啟動設(shè)備。現(xiàn)在我們已經(jīng)完成,將程序從 boot 中讀入 boot_buf 的緩存中,并將第 510 和 511 個字節(jié)改為可啟動設(shè)備標(biāo)識,并將 boot_buf 寫入了軟盤設(shè)備。如果執(zhí)行該程序,軟盤的前 512 個字節(jié)將包含該引導(dǎo)程序。保存該文件為 write.c。

3.3 動手實踐

為了生成可執(zhí)行文件,需要在 Linux 的 shell 中執(zhí)行如下命令:

as86 boot.s -o boot.o
ld86 -d boot.o -o boot
cc write.c -o write

首先,編譯 boot.s 文件到一個目標(biāo)文件 boot.o。然后鏈接該目標(biāo)文件到最終文件 boot-d 選項表示移除所有的頭信息僅處理純凈的二進制。as86 和 ld86 的 man 頁面講說明此用法。接下來將 write.c 編譯輸出 C 程序 write

插入一個空白的軟盤到電腦上,執(zhí)行 ./write。然后重啟該電腦,進入 BIOS 啟動頁面,并選擇此軟盤為第一啟動設(shè)備。

電腦啟動完成,屏幕將顯示一個字符 A(其前景色為白色,背景色為藍色)。這意味著系統(tǒng)已經(jīng)從軟盤啟動,并執(zhí)行了寫入引導(dǎo)扇區(qū)的程序。如程序代碼中,執(zhí)行完顯示字符,電腦進入了死循環(huán)。故必須重啟電腦,并移除插入的軟盤,重新引導(dǎo)系統(tǒng)啟動 Linux。

從這里開始我們將要插入更多的代碼到引導(dǎo)扇區(qū)程序,使它能夠做更多復(fù)雜的事情(例如使用 BIOS 中斷,保護模式切換等)。后面文章(二、三)將知道你進一步的探索,直到該系列完結(jié)。


譯者注:
文中需要用到真實的軟盤設(shè)備,但大部分閱讀者應(yīng)該都不會有軟盤,故考慮用虛擬軟盤代替。

我們知道借助 dd 命令可以制造虛擬網(wǎng)盤文件,那么有兩種思路可供選擇:一是先產(chǎn)生與啟動扇區(qū)內(nèi)容一致的二進制文件, 然后借助該文件直接生成可啟動虛擬軟盤。二是先制造好空白虛擬軟盤,然后借助文中 C 程序?qū)懭雽?yīng)引導(dǎo)程序代碼。

方式 1

  • boot.s 文件進行修改,再起第 510和 511 個字節(jié),寫入可啟動標(biāo)志。更改文件如下:
entry start
start:
mov ax, #0xb800
mov es, ax
seg es
mov [0], #0x41
seg es
mov [1], #0x1f
loop1: jmp loop1

.org 510
boot_flag:
    .word 0xAA55
  • 然后按文中步驟編譯完成,生成二進制文件 boot。
  • 最后,執(zhí)行命令:dd if=boot of=Image 生成虛擬軟盤設(shè)備 Image。

方式 2

  • 執(zhí)行命令:dd if=/dev/zero of=Image bs=512 count=1 生成512字節(jié)大小的空白虛擬軟盤設(shè)備 Image。
  • write.c 文件中 floppy_desc = open("/dev/fd0", O_RDWR); 更改為 floppy_desc = open("Image", O_RDWR);。此處Image是第一步生成的空白軟盤。

執(zhí)行效果

用 bochs 模擬啟動效果如圖:


版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名創(chuàng)意共享3.0許可證

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容