Skip to content

Instantly share code, notes, and snippets.

@letoh
Last active June 7, 2024 09:22
Show Gist options
  • Save letoh/2790559 to your computer and use it in GitHub Desktop.
Save letoh/2790559 to your computer and use it in GitHub Desktop.
[筆記] 為什麼在 x86,MBR 會被載入到 0x7C00?(完全版)

原文 Assembler / なぜx86ではMBRが"0x7C00"にロードされるのか?(完全版)

感謝 descent 大大分享本文,隨便看隨便譯。本文不是逐句譯,同時也不是這方面的專家 (不管是語言或技術),用語不一或缺漏錯誤在所難免,歡迎自行 fork 修正指教

誰適合看本文?

對 x86 架構與組合語言有基礎認識,以及從 MBR 到載入 OS 這一段過程有興趣的人 (還有談到中斷向量或 INT xxx 時不會一臉茫然的人)

在自學 x86 架構,特別是 OS 的啟動或保護模式時,通常從 MBR (Master Boot Record) 跨出第一步。MBR 是軟碟 (FDD) 或硬碟 (HDD) 最前面的一個 sector,也就是 512-byte block。這裡通常放有啟動 OS 的機械碼或磁碟的分割區等資訊。對於使用 Intel 80x86 系列 CPU 的電腦來說,開啟電源後會先執行 BIOS 的 POST (Power On Self Test),在偵測週邊裝置後會讀入 MBR 的內容,並開始處理 OS 的啟動程序。

BIOS 通常被寫進 ROM 晶片中,而且無法再修改 (如果使用 EEPROM 之類可以重新覆寫的 ROM,可能可以透過特別的軟體更新 BIOS)。而 MBR 因為放在 FDD/HDD 的最前面一個 sector,程式設計師有機會自己修改。

在 2010 的今天,已經有 Bochs、QEMU、VirtualBox、與 VMware 之類的方便使用的虛擬環境。利用這些虛擬環境,將機械碼寫入磁碟映像檔的 MBR 部份,可以很方便地進行實驗,不必再擔心寫到實際硬碟後,OS 會無法啟動。

在許多書或網頁文章中解釋了如下所示的 MBR 中的代碼執行過程:

  1. BIOS 執行 POST (Power On Self Test)
  2. 執行完 POST 之後,BIOS 會將 FDD 或 HDD 的 MBR 載入到 0x7C00,再接著繼續執行。(當然從 CD/DVD 也可以開機,本文只包含從 FDD/HDD 的開機程序)
  3. 在 MBR 中存放了可以載入 OS 核心並執行的機械碼,OS 的開機程序從這裡開始

除此之外,也有不從 MBR 直接載入 OS 核心,而是經過一或兩個階段的中間 loader 才載入的做法。MBR 中的機械碼,以及 MBR 載入並執行的中間 loader 可以是 OS 特有的選擇 (如 Microsoft 的 NTLDR),也有對應多種 OS 的開放原始碼軟體 (如 LILO 與 GRUB)。

說到這裡,實際研究過 MBR 的人曾經有過下面的疑問嗎?

"0x7C00" 到底是在哪一份規格書中定義的?

學習 x86 CPU 的話,會發現存在許多像 Magic Number 般的記憶體位址:

  • 64 KB 的限制

    根據 segment:offset 各 16 bit 的組合,一個 segment 的定址空間限制。

  • 0x3FF

    中斷向量可用的記憶體空間

    4 bytes ISR 位址 x 256 個中斷向量 = 1024 bytes = 0x400 bytes

  • 0xFFFFF (1MB)

    真實模式下最大的位址。這個值的依據是 80186 以前 Address Bus 有 20bit,可允許的最大值。

  • 0xFFF0

    與 IBM PC (5150) 的誕生有密切關係的 Intel 8086/8088 在通電初始化後,最早執行的位址。

話說回來,對於 "0x7C00" 的意義,調查了 CPU 相關資訊也沒有任何線索。就算研究 Bootloader 或 MBR 相關資料,只能查到載入位址是 0x7C00,至於為什麼是這個位址?到底是誰規定的?這類資訊則到處都找不到。

雖然前言有點長,只是為了介紹本文的內容:整理了 MBR 載入位址 0x7C00 的來源與意義的調查結果


重新檢視 IBM PC(5150) 誕生時期的環境

先說結論,"0x7C00" 最早出現於 1981 年 8 月發表的 IBM Personal Computer (PC) 5150 的 ROM BIOS

IBM PC 5150 使用同時對應 16-bit 與 8-bit 的 Intel 8088 作為 CPU,相當於現今 x86 架構的起源。短暫的開發,以及 IBM 一改往常地採用開放架構之外,宛如歷史的轉捩點般的誕生,就像戲劇化的軼聞充滿傳奇色彩。

在這先對 IBM Personal Computer (PC) 5150 型誕生前的歷史簡單地確認一下。

從 Intel 4004 開始,通用 CPU 以 8-bit 的 Intel 8080 席捲市場 (1974 年)。1975 年的 MITS Altair 8800 也搭載了 (Intel 8080)。Digital Research 公司的 OS,CP/M 於 1974 到 1975 年左右誕生,伴隨著 8-bit 微處理器市場的蓬勃發展,CP/M 不斷被移植到相容的 CPU 或其他微處理器系統。

即使 IBM 在當時已經是 Mainframe 市場中的巨人,也無法忽視這股潮流。因此也在 1975 年發表了 "5100 Portable Computer",此外還發表了桌上型尺寸的電腦系統,如 1978 年的 "IBM 5110 Computing System"、1979 年的 "5520 Administrative System"、1980 年 2 月的 "5120 Computer System"。但是,就算能進入商業或辦公室市場或科學計算應用,仍然無法撼動微型電腦 (Microcomputer) 的地位。

1980 年,Don Estridge 帶領的 12 人團隊在一年內開發出 IBM 第一部個人電腦,以此挑戰微型電腦市場。這時使用的中央處理器是同時支援 8bit 與 16bit 的 Intel 8088。要支援軟體首先要有程式語言與作業系統。程式語言的部份是在 ROM 搭載了 Microsoft 公司開發的 BASIC;而作業系統曾一度想採用 Digital Research 公司的 CP/M,但似乎進行的不太順利,最後還是交給了 Microsoft 負責。由於 CP/M 在當時的微型電腦市場頗受歡迎,因此需要開發一款能夠執行 CP/M 對應軟體的作業系統。

接下來就是廣為人知的故事了。當時的 Microsoft 並沒有開發作業系統的經驗,想到的辦法就是買下 Seatle Computer Products (SCP) 的 Tim Paterson 為 8086 平台所開發的 "86-DOS" (早期曾被稱為 QDOS:Quick and Dirty Operating System)。這是一款相容 CM/P 的作業系統,因此以它為基礎來開發 IBM PC 5150 用的 CP/M 相容系統。

1979 年主流的電腦系統如 Altair 8800 或 IMSAI 8080 都是使用了 "S-100" 這種匯流排的微型電腦,因此當時 SCP 也想提供能插入 S-100 的 8086 處理器基板以及作業系統。其中作業系統最早似乎想用的是 Digital Research 的 8086 版本的 CP/M。到了 1979 年 11 月,SCP 雖然推出了 8086 處理器基版以及能獨立運作的 BASIC,但由於 CP/M 的 8086 移植一直沒有進展,在 1980 年 4 月決定自己開發相容 (CP/M) 的 OS。

Microsoft 在 1980 年的 8 月推出了 QDOS 0.10,且在年底也推出了 86-DOS 0.3 版。幾乎在同一時期,Digital Research 也推出了支援 8086 的 CP/M-86。

到了 1981 年 7 月,Microsoft 買下 DOS,並在 1981 年 8 月的時候,隨著 IBM PC 5150 的發表同時推出 86-DOS 以及一款非常相似的 PC-DOS。

86-DOS → PC-DOS + ROM BIOS

在現代個人電腦,BIOS 存放在主機板上的 ROM 晶片中,OS 則是存放在 HDD 之類的外部記憶磁碟中已經是常識了。這樣的分配正是從 IBM PC 5150 發展而來。

也就是說:對 MS-DOS 的 原型,86-DOS (與 CP/M) 來說,BIOS 的功能也算是包含在 OS 中了

BIOS 的功能包括:

  1. 記憶體或 CPU 的檢查,與週邊裝置的偵測之類,相當於現今 POST 的功能
  2. CP/M 相容 OS 所使用的軟體中斷處理

POST 處理當然重要,OS 所使用的軟體中斷也很重要,用於在畫面上描繪文字或圖形、存取 FDD/HDD 並讀出資料、檔案處理或將程式載入記憶體等工作。當時這些功能也成為 "OS" 的一部份了。作為 86-DOS 基礎的 CP/M 也是這麼設計的。

從此奠定了 OS 與 BIOS 分離的設計,決定未來設計方式的原點就是 IBM PC 5150。

回到正題:0xFFFF0 (Intel 8086/8088 在硬體重置後,最早執行的位址)

在這裡要重新再確認一次,x86 處理器從硬體重置 (接上電源) 並經過電路的初始化後,最早取出命令的位址。

CPU CS IP 實體位址
8086/8088 F000H FFF0H FFFF0H
80286 F000H(※1) FFF0H FFFFF0H
80386以降 F000H(※2) FFF0H FFFFFFF0H

不管是哪一款處理器,參考到的位址都超出實體記憶體的範圍了,似乎是因為要放中斷向量的關係而無法使用位址 0,以及初始化程式盡量配置在高位址以免佔用一般程式能使用的定址空間的緣故。

用來儲存 BIOS 的 ROM 也在硬體規劃上調整成對應到高位址。IBM PC 5150 會將 ROM BIOS 載入到 FE000H 以後的位址,並在 FFFF0H 放一個會跳到 BIOS 開頭的機械指令 (後述)。

※1 : 80286 雖然有 24bit 的定址能力,在真實模式下只能使用其中的 20bit。A20-A23 在重置後會變成 1。也因為此 CS 的值雖然是 F000H,在高位址其實還隱藏了 4bit 全為 1 的資料。計算方法如下:

(F)F0000 H(CS) (左移 4bit)
    FFF0 H(IP)
--------------
 F FFFF0 H

"FFFFF0H" 成了最早取出指令的位址

参考資料:

  • "INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986"
    • "10.1 Processor State After Reset" (p174)
    • "10.2.3 First Instructions" (p176)

※2 : 80386 雖然有 32bit 的定址能力,但在真實模式下只能使用其中的 20bit。A20-A31 在重置後會變成 1。也因為此 CS 的值雖然是 F000H,在高位址其實還隱藏了 12bit 全為 1 的資料。計算方法如下:

(FFF)F0000 H(CS) (左移 4bit)
      FFF0 H(IP)
--------------
 FFF FFFF0 H

参考資料:

  • "INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986"
    • "10.1 Processor State After Reset" (p174)
    • "10.2.3 First Instructions" (p176)

86-DOS のブートプロセス(MBRを200Hにロード)

86-DOS 將開機碼載入 200H 的理由 (From Tim Paterson)

在 86-DOS 中會將儲存在磁碟開頭的開機碼 (bootstrap) 載入到 200H,而 200H 這個值剛好在 8086 的中斷向量所在的位址範圍內 (0H - 3FFH)。

根據 Tim Peterson 的說明,中央處理器的中斷向量所在區域在規格上屬於預定好的保留區,也就是說 0H-3FFH 這個區域是絕對不會載入作業系統的「安全地帶」,因此選擇了 200H 這個位址。

摘錄 Tim Paterson 的說明如下:

At SCP, I chose 0x200 because it was in the interrupt vector space (0 - 3FFH). This means it needed to be reserved and couldn't be in the way of an OS, no matter where it wanted to load.

整理一下:

  • 0x0 - 0x3FF 是 8086 在規格上用來放置中斷向量的區域
  • 0x400 以後的位址會載入 86-DOS 系統
  • 0x200 - 0x3FF 之間的的中斷向量在 86-DOS 時代並未被使用

根據以上的理由使用了 200H - 3FFH 之間的空間。順帶一提,100H - 200H 之間的空間似乎也被用來備份暫存器內容或作為 Monitor/開機碼所使用的堆疊空間

IBM PC(5150) ROM BIOS INT19h

INT 19h までの流れ(MBRを7C00Hにロード)

IBM PC 5150 的 ROM BIOS INT 19h 將 MBR 載入到 7C00H (0x7C00) 的理由 (From Dr. David Bradley)

終於要解開 7C00H 之謎了。

但在這之前,想再確認一下關於 7C00H 的一些疑問。

首先,所謂的 7C00H 就是:

32KB (8000H) - 1024B

最前面的 32KB 到剛好 1024 Bytes 的位置,就是 7C00H 了。首先是第一個疑問:這裡的 "32KB - 1024 = 7C00" 是有意義的,為什麼要訂在這個位址呢?

第二個疑問是:IBM PC 5150 發表時,搭載最少記憶體的機種只有 16KB (4000H) 的 RAM。換句話說,只有 16KB 的 RAM 是無法將 MBR 載入到 7C00H 這個位址的。這樣說來,16KB 的機種不就沒辦法執行 DOS 了嗎?

整理一下後是以下兩點:

  1. 0x7C00 剛好是 32KB - 1024B 的位置,為什麼是這個位置?
  2. 5150 搭載最少記憶體的機種只有 16KB 的 RAM,那不就無法啟動 OS 了嗎?(無法存取到 0x7C00)

針對這兩個疑問,試著寫信詢問了 IBM PC 5150 的 ROM BIOS 的開發者,David Bradley。同時他也因為設計了以 "Ctrl-Alt-Delete" 重置系統而為人所知。

另外,在信件中也問了「86-DOS 會載入到 200H,為什麼要改變呢?」

I don't know anything about 86-DOS.

得到這樣的回應。也就是說,David 沒看過 86-DOS 的程式,在不曉得「86-DOS 將開機碼載入 200H」這件事的情況下開發了 ROM BIOS。

那麼,先從 David Bradley 的回信中解答關於 16KB 機型的疑問:

It had to boot on a 32KB machine. DOS 1.0 required a minimum of 32KB, so we weren't concerned about attempting a boot in 16KB.

要執行 DOS 1.0 至少需要 32KB,因此沒考慮過 16KB 機型的問題。

接著是「為什麼是 32KB - 1024B?」的回答:

We wanted to leave as much room as possible for the OS to load itself within the 32KB. The 808x Intel architecture used up the first portion of the memory range for software interrupts, and the BIOS data area was after it. So we put the bootstrap load at 0x7C00 (32KB-1KB) to leave all the room in between for the OS to load. The boot sector was 512 bytes, and when it executes it'll need some room for data and a stack, so that's the other 512 bytes. So the memory map looks like this after INT 19H executes:

  1. 考慮到 OS 需要的最少記憶體,希望能在 32KB 中盡量空出空間
  2. 從位址 0 開始,一直到 3FFH 為止規劃為中斷向量,因此無法使用
  3. 所以,只能載入到 32KB 的後半段了
  4. MBR 中的開機碼需要空間以便存放資料或作為堆疊使用,要多保留 512 bytes
  5. 根據上述,32KB - 512B (MBR) - 512B (資料與堆疊) 就得到了 7C00H

根據以上的說明,執行 INT 19h 後的記憶體規劃如下圖:

+--------------------- 0x0
| Interrupts vectors
+--------------------- 0x400
| BIOS data area
+--------------------- 0x5??
| OS load area
+--------------------- 0x7C00
| Boot sector
+--------------------- 0x7E00
| Boot data/stack
+--------------------- 0x7FFF
| (not used)
+--------------------- (...)

以上就是超過 25 年,一直控制著 x86 IBM PC/AT 相容機上的 OS 開機碼的 0x7C00、7C00H 的由來。

参考資料

@alanduan
Copy link

Great, now for the first I got the point. thanks man!

@namjkee
Copy link

namjkee commented Jul 28, 2015

this helps a lot. tks

@wangyung
Copy link

wangyung commented Aug 3, 2015

awesome!

@chuang76
Copy link

Great post, thanks for sharing.

@madeinfree
Copy link

Great!!

@zhongshur
Copy link

suddenly see the light

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment