x86 彙編/引導載入程式
當計算機開啟時,會發出一些嗶嗶聲,閃爍一些燈光,然後出現一個載入螢幕。然後,作業系統神奇地載入到記憶體中。隨之而來的問題是,作業系統是如何載入的?是什麼啟動了它?答案是 引導載入程式。
引導載入程式是小型軟體片段,在計算機開機時發揮作用,將作業系統載入並準備好執行。這種過程在不同的計算機設計之間有所不同(早期計算機需要使用者在每次開機時手動設定),並且在引導載入過程中通常會存在多個階段。
重要的是要理解,“引導載入程式”只是一個軟體分類(而且有時是一個模糊的分類)。對處理器來說,引導載入程式只是它盲目執行的另一段程式碼。存在許多不同型別的引導載入程式。有些很小,有些很大;有些遵循非常簡單的規則,而另一些則顯示漂亮的螢幕並提供使用者選擇。
在 IBM PC 相容機上,第一個載入的程式是基本輸入輸出系統 (BIOS)。BIOS 執行許多測試和初始化,如果一切正常,BIOS 的引導載入程式就開始執行。它的目的是載入另一個引導載入程式!它從一個磁碟(或其他儲存介質)中選擇一個磁碟,並從該磁碟中載入一個二級引導載入程式。
在某些情況下,這個引導載入程式會載入足夠的作業系統以開始執行它。在其他情況下,它會從其他地方載入另一個引導載入程式。這通常發生在同一臺計算機上安裝了多個作業系統的情況下;每個作業系統可能都有自己特定的引導載入程式,以及一個“中心”引導載入程式,根據使用者的選擇載入其中一個特定的引導載入程式。
大多數引導載入程式都是專門用匯編語言(甚至機器程式碼)編寫的,因為它們需要緊湊,它們沒有訪問其他語言可能需要的作業系統例程(例如記憶體分配),它們需要遵循一些特殊的規則,並且它們頻繁地使用底層功能。但是,一些引導載入程式,特別是那些具有許多功能並允許使用者輸入的引導載入程式,相當重量級。這些通常是用匯編語言和 C 語言的組合編寫的。例如,GRand Unified Bootloader (GRUB) 就是一個例子。
一些引導載入程式高度特定於作業系統,而另一些則不那麼特定 - 當然,BIOS 引導載入程式不特定於作業系統。MS-DOS 引導載入程式(它被放置在所有 MS-DOS 格式化的軟盤上)只檢查檔案 IO.SYS 和 MSDOS.SYS 是否存在;如果不存在,它會顯示錯誤訊息“非系統磁碟或磁碟錯誤”,否則它會載入並開始執行 IO.SYS。
最終階段的引導載入程式可能需要(由作業系統)以某種方式準備計算機,例如,將處理器置於保護模式並將中斷控制器程式設計。雖然可以在作業系統的初始化過程中執行這些操作,但將它們移到引導載入程式中可以簡化作業系統設計。一些作業系統要求它們的引導載入程式設定一個小的基本 GDT (全域性描述符表) 並進入保護模式,以消除作業系統需要任何 16 位程式碼的需要。但是,作業系統可能很快用自己的複雜 GDT 替換它。
磁碟的前 512 位元組稱為 引導扇區 或 主引導記錄。引導扇區是磁碟上為引導目的保留的區域。如果磁碟的引導扇區包含有效的引導扇區(扇區的最後一個字必須包含簽名 0xAA55),則 BIOS 將磁碟視為可引導的。
當開啟或重置時,x86 處理器開始執行它在地址 FFFF:0000 處找到的指令(在此階段它正在 真實模式 下執行)(Intel 軟體開發人員手冊卷 3 第 9 章與該資訊相矛盾:執行從物理地址 0xFFFFFFF0 開始,等等)。在 IBM PC 相容處理器中,該地址對映到包含計算機基本輸入輸出系統 (BIOS) 程式碼的 ROM 晶片。BIOS 負責許多測試和初始化;例如,BIOS 可能會執行記憶體測試,初始化中斷控制器和系統計時器,並測試這些裝置是否正常工作。
最終,實際的引導載入開始。首先,BIOS 搜尋並初始化可用的儲存介質(例如軟盤驅動器、硬碟驅動器、CD 驅動器),然後它決定將嘗試從哪個介質引導。它檢查每個裝置的可用性(例如,確保軟盤驅動器包含一個磁碟),然後檢查 0xAA55 簽名,以某種預定義的順序(通常,順序可以使用 BIOS 設定工具配置)。它將第一個可引導裝置的第一個扇區載入到 RAM 中,並啟動執行。
理想情況下,這將是另一個引導載入程式,它將繼續工作,進行一些準備,然後將控制權傳遞給其他內容。
雖然 BIOS 保持與 20 年前的軟體相容,但它們也隨著時間的推移變得越來越複雜。早期的 BIOS 無法從 CD 驅動器引導,但現在 CD 甚至 DVD 引導都是標準的 BIOS 功能。從 USB 儲存裝置引導也是可能的,一些系統可以從網路引導。為了實現這種高階功能,BIOS 有時會進入保護模式等,但隨後會返回到真實模式,以便與舊版引導載入程式相容。這造成了一個先有雞還是先有蛋的問題:引導載入程式被編寫為與無處不在的 BIOS 協同工作,而 BIOS 被編寫為支援所有這些引導載入程式,從而阻止了太多新的引導載入功能。
但是,一種新的引導技術,UEFI,正在開始獲得發展勢頭。它更復雜,本文不會對此進行討論。
還要注意,其他計算機系統 - 即使是使用 x86 處理器的系統 - 也可能以不同的方式引導。事實上,一些嵌入式系統,其軟體足夠緊湊以至於可以儲存在 ROM 晶片上,可能根本不需要引導載入程式。
引導載入程式在某些條件下執行,程式設計師必須瞭解這些條件才能建立一個成功的引導載入程式。以下內容適用於由 PC BIOS 啟動的引導載入程式。
- 驅動器的第一個扇區包含它的引導載入程式。
- 一個扇區是 512 位元組 - 其中必須是 0xAA55(即 0x55 後跟 0xAA),否則 BIOS 將驅動器視為不可引導的。
- 如果一切正常,第一個扇區將被放置在 RAM 地址 0000:7C00,而 BIOS 的作用就結束了,因為它將控制權轉移到 0000:7C00(也就是說,它跳轉到該地址)。
- DL 暫存器將包含正在從其引導的驅動器號,如果您想從驅動器上的其他位置讀取更多資料,這很有用。
- BIOS 會留下大量程式碼,這些程式碼用於處理硬體中斷(例如按鍵)併為引導載入程式和作業系統提供服務(例如鍵盤輸入、磁碟讀取和寫入螢幕)。您必須瞭解中斷向量表 (IVT) 的目的,並注意不要干擾您所依賴的 BIOS 部分。大多數作業系統會用自己的程式碼替換 BIOS 程式碼,但引導載入程式只能使用自己的程式碼和 BIOS 提供的程式碼。有用的 BIOS 服務包括
int 10h(用於顯示文字/圖形)、int 13h(磁碟功能)和int 16h(鍵盤輸入)。 - 這意味著引導載入程式需要的任何程式碼或資料都必須包含在第一個扇區中(注意不要意外地執行資料)或手動從磁碟的另一個扇區載入到 RAM 中的某個位置。因為作業系統尚未執行,所以大多數 RAM 將處於未使用狀態。但是,您必須注意不要干擾上述 BIOS 中斷處理程式和服務所需的 RAM。
- 作業系統程式碼本身(或下一個引導載入程式)也需要載入到 RAM 中。
- BIOS 將堆疊指標放在引導扇區末尾的 512 位元組之外,這意味著堆疊不能超過 512 位元組。可能需要將堆疊移動到更大的區域。
- 如果要使磁碟在主流作業系統下可讀,需要遵循一些約定。例如,您可能希望在軟盤上包含一個 BIOS 引數塊,以使磁碟在大多數 PC 作業系統下可讀。
大多數彙編程式將有一個類似於 ORG 7C00h 的命令或指令,用於通知彙編程式程式碼將從偏移量 7C00h 開始載入。彙編程式將在計算指令和資料地址時考慮到這一點。如果您省略了這一點,彙編程式會假設程式碼從地址 0 載入,並且必須在程式碼中手動進行補償。
通常,引導載入程式會將核心載入到記憶體中,然後跳轉到核心。 核心將能夠回收引導載入程式使用的記憶體(因為它已經完成了其工作)。 但是,可以在引導扇區中包含作業系統程式碼,並在作業系統啟動後將其保留在記憶體中。
這是一個為 NASM 設計的簡單引導載入程式演示
org 7C00h
jmp short Start ;Jump over the data (the 'short' keyword makes the jmp instruction smaller)
Msg: db "Hello World! "
EndMsg:
Start: mov bx, 000Fh ;Page 0, colour attribute 15 (white) for the int 10 calls below
mov cx, 1 ;We will want to write 1 character
xor dx, dx ;Start at top left corner
mov ds, dx ;Ensure ds = 0 (to let us load the message)
cld ;Ensure direction flag is cleared (for LODSB)
Print: mov si, Msg ;Loads the address of the first byte of the message, 7C02h in this case
;PC BIOS Interrupt 10 Subfunction 2 - Set cursor position
;AH = 2
Char: mov ah, 2 ;BH = page, DH = row, DL = column
int 10h
lodsb ;Load a byte of the message into AL.
;Remember that DS is 0 and SI holds the
;offset of one of the bytes of the message.
;PC BIOS Interrupt 10 Subfunction 9 - Write character and colour
;AH = 9
mov ah, 9 ;BH = page, AL = character, BL = attribute, CX = character count
int 10h
inc dl ;Advance cursor
cmp dl, 80 ;Wrap around edge of screen if necessary
jne Skip
xor dl, dl
inc dh
cmp dh, 25 ;Wrap around bottom of screen if necessary
jne Skip
xor dh, dh
Skip: cmp si, EndMsg ;If we're not at end of message,
jne Char ;continue loading characters
jmp Print ;otherwise restart from the beginning of the message
times 0200h - 2 - ($ - $$) db 0 ;Zerofill up to 510 bytes
dw 0AA55h ;Boot Sector signature
;OPTIONAL:
;To zerofill up to the size of a standard 1.44MB, 3.5" floppy disk
;times 1474560 - ($ - $$) db 0
要編譯上面的檔案,假設它被稱為'floppy.asm',你可以使用以下命令
nasm -f bin -o floppy.img floppy.asm
雖然嚴格來說這不是一個引導載入程式,但它是可啟動的,並且展示了一些內容
- 如何在引導扇區中包含和訪問資料
- 如何跳過包含的資料(這對於 BIOS 引數塊是必需的)
- 如何在扇區末尾放置 0xAA55 簽名(如果程式碼太多無法容納在一個扇區中,NASM 會發出錯誤)
- BIOS 中斷的使用
在 Linux 上,你可以發出類似的命令
cat floppy.img > /dev/fd0
將映像寫入軟盤(映像可能小於磁碟大小,在這種情況下,只有映像中的資訊會被寫入磁碟)。 另一種更復雜的選擇是使用 dd 實用程式
dd if=floppy.img of=/dev/fd0
在 Windows 下,你可以使用諸如 RAWRITE 之類的軟體。
硬碟
[edit | edit source]硬碟通常在此過程中新增一層額外的內容,因為它們可能會被分割槽。 硬碟的第一個扇區稱為主引導記錄 (MBR)。 按照慣例,硬碟的分割槽資訊包含在 MBR 的末尾,就在 0xAA55 簽名之前。
BIOS 的作用與之前相同:將磁碟的第一個扇區(即 MBR)讀入 RAM,並將執行權轉移到該扇區的第一個位元組。 BIOS 不知道分割槽方案 - 它只檢查 0xAA55 簽名的存在。
雖然這意味著可以使用任何想要的方式使用 MBR(例如,省略或擴充套件分割槽表),但很少這樣做。 儘管分割槽表設計非常老舊且有限 - 它僅限於四個分割槽 - 幾乎所有為 IBM PC 相容機設計的作業系統都假設 MBR 將以這種方式格式化。 因此,違反慣例將使你的磁碟無法操作,除非針對專門使用它的作業系統。
實際上,MBR 通常包含一個引導載入程式,其目的是載入另一個引導載入程式 - 位於其中一個分割槽的開頭。 這通常是一個非常簡單的程式,它找到第一個標記為活動的分割槽,將它的第一個扇區載入到 RAM 中,並開始執行。 由於按照慣例,新的引導載入程式也被載入到地址 7C00h,因此舊的載入程式可能需要在執行此操作之前將自己的一部分或全部重新定位到不同的位置。 此外,ES:SI 預計包含分割槽表在 RAM 中的地址,而 DL 則是引導驅動器號。 違反這些慣例可能會使引導載入程式與其他引導載入程式不相容。
但是,許多引導管理器(允許使用者選擇要啟動的分割槽,有時甚至核心的軟體)使用自定義 MBR 程式碼,它從磁碟上的某個位置載入引導管理器程式碼的其餘部分,然後向用戶提供有關如何繼續引導過程的選項。 引導管理器也可以駐留在分割槽內,在這種情況下,它必須首先由另一個引導載入程式載入。
大多數引導管理器支援鏈式載入(即透過通常的將分割槽第一個扇區載入到地址 7C00 的過程啟動另一個引導載入程式),這通常用於 DOS 和 Windows 等系統。 但是,一些引導管理器(特別是 GRUB)支援載入使用者選擇的核心映像。 這可以與 GNU/Linux 和 Solaris 等系統一起使用,從而在啟動系統時提供更大的靈活性。 機制可能與鏈式載入略有不同。
顯然,分割槽表提出了一個先有雞還是先有蛋的問題,這對分割槽方案造成了不合理的限制。 一個正在流行的解決方案是 GUID 分割槽表;它使用一個虛擬 MBR 分割槽表,以便傳統的作業系統不會干擾 GPT,而更新的作業系統則可以利用該系統提供的眾多改進。
GNU GRUB
[edit | edit source]The GRand Unified Bootloader 支援靈活的 multiboot 引導協議。 該協議旨在透過為各種作業系統提供單一、靈活的協議來簡化引導過程。 許多免費作業系統可以使用 multiboot 啟動。
GRUB 非常強大,實際上是一個小型作業系統。 它可以讀取各種檔案系統,因此允許你按檔名指定核心映像以及核心可能使用的單獨模組檔案。 命令列引數也可以傳遞給核心 - 這是以維護模式、"安全模式"或使用 VGA 圖形等方式啟動作業系統的不錯方法。 GRUB 可以為使用者提供一個選單供選擇,以及允許輸入自定義載入引數。
顯然,這種功能不可能在 512 位元組的程式碼中提供。 這就是為什麼 GRUB 被分成兩到三個"階段"的原因
- 階段 1 - 這是一個 512 位元組的塊,其中包含階段 1.5 或階段 2 的位置硬編碼到其中。 它載入下一階段。
- 階段 1.5 - 一個可選的階段,它瞭解階段 2 所在的檔案系統(例如 FAT32 或 ext3)。 它將找出階段 2 的位置並載入它。 這個階段非常小,位於固定區域,通常位於階段 1 之後。
- 階段 2 - 這是一個更大的映像,包含所有 GRUB 功能。
請注意,階段 1 可以安裝到硬碟的主引導記錄,也可以安裝到其中一個分割槽中並由另一個引導載入程式鏈式載入。
Windows 不能使用 multiboot 載入,但 Windows 引導載入程式(就像其他非 multiboot 作業系統的引導載入程式一樣)可以從 GRUB 鏈式載入,這並不是很好,但確實可以讓你啟動這些系統。
引導載入程式示例 - Linux 核心 v0.01
[edit | edit source]SYSSIZE=0x8000
|
| boot.s
|
| boot.s is loaded at 0x7c00 by the bios-startup routines, and moves itself
| out of the way to address 0x90000, and jumps there.
|
| It then loads the system at 0x10000, using BIOS interrupts. Thereafter
| it disables all interrupts, moves the system down to 0x0000, changes
| to protected mode, and calls the start of system. System then must
| RE-initialize the protected mode in it's own tables, and enable
| interrupts as needed.
|
| NOTE! currently system is at most 8*65536 bytes long. This should be no
| problem, even in the future. I want to keep it simple. This 512 kB
| kernel size should be enough - in fact more would mean we'd have to move
| not just these start-up routines, but also do something about the cache-
| memory (block IO devices). The area left over in the lower 640 kB is meant
| for these. No other memory is assumed to be "physical", i.e. all memory
| over 1Mb is demand-paging. All addresses under 1Mb are guaranteed to match
| their physical addresses.
|
| NOTE1 above is no longer valid in it's entirety. cache-memory is allocated
| above the 1Mb mark as well as below. Otherwise it is mainly correct.
|
| NOTE 2! The boot disk type must be set at compile-time, by setting
| the following equ. Having the boot-up procedure hunt for the right
| disk type is severe brain-damage.
| The loader has been made as simple as possible (had to, to get it
| in 512 bytes with the code to move to protected mode), and continuous
| read errors will result in a unbreakable loop. Reboot by hand. It
| loads pretty fast by getting whole sectors at a time whenever possible.
| 1.44Mb disks:
sectors = 18
| 1.2Mb disks:
| sectors = 15
| 720kB disks:
| sectors = 9
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
BOOTSEG = 0x07c0
INITSEG = 0x9000
SYSSEG = 0x1000 | system loaded at 0x10000 (65536).
ENDSEG = SYSSEG + SYSSIZE
entry start
start:
mov ax,#BOOTSEG
mov ds,ax
mov ax,#INITSEG
mov es,ax
mov cx,#256
sub si,si
sub di,di
rep
movw
jmpi go,INITSEG
go: mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,#0x400 | arbitrary value >>512
mov ah,#0x03 | read cursor pos
xor bh,bh
int 0x10
mov cx,#24
mov bx,#0x0007 | page 0, attribute 7 (normal)
mov bp,#msg1
mov ax,#0x1301 | write string, move cursor
int 0x10
| ok, we've written the message, now
| we want to load the system (at 0x10000)
mov ax,#SYSSEG
mov es,ax | segment of 0x010000
call read_it
call kill_motor
| if the read went well we get current cursor position ans save it for
| posterity.
mov ah,#0x03 | read cursor pos
xor bh,bh
int 0x10 | save it in known place, con_init fetches
mov [510],dx | it from 0x90510.
| now we want to move to protected mode ...
cli | no interrupts allowed !
| first we move the system to it's rightful place
mov ax,#0x0000
cld | 'direction'=0, movs moves forward
do_move:
mov es,ax | destination segment
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax | source segment
sub di,di
sub si,si
mov cx,#0x8000
rep
movsw
j do_move
| then we load the segment descriptors
end_move:
mov ax,cs | right, forgot this at first. didn't work :-)
mov ds,ax
lidt idt_48 | load idt with 0,0
lgdt gdt_48 | load gdt with whatever appropriate
| that was painless, now we enable A20
call empty_8042
mov al,#0xD1 | command write
out #0x64,al
call empty_8042
mov al,#0xDF | A20 on
out #0x60,al
call empty_8042
| well, that went ok, I hope. Now we have to reprogram the interrupts :-(
| we put them right after the intel-reserved hardware interrupts, at
| int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
| messed this up with the original PC, and they haven't been able to
| rectify it afterwards. Thus the BIOS puts interrupts at 0x08-0x0f,
| which is used for the internal hardware interrupts as well. We just
| have to reprogram the 8259's, and it isn't fun.
mov al,#0x11 | initialization sequence
out #0x20,al | send it to 8259A-1
.word 0x00eb,0x00eb | jmp $+2, jmp $+2
out #0xA0,al | and to 8259A-2
.word 0x00eb,0x00eb
mov al,#0x20 | start of hardware int's (0x20)
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x28 | start of hardware int's 2 (0x28)
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x04 | 8259-1 is master
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x02 | 8259-2 is slave
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x01 | 8086 mode for both
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0xFF | mask off all interrupts for now
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
| well, that certainly wasn't fun :-(. Hopefully it works, and we don't
| need no steenking BIOS anyway (except for the initial loading :-).
| The BIOS-routine wants lots of unnecessary data, and it's less
| "interesting" anyway. This is how REAL programmers do it.
|
| Well, now's the time to actually move into protected mode. To make
| things as simple as possible, we do no register set-up or anything,
| we let the gnu-compiled 32-bit programs do that. We just jump to
| absolute address 0x00000, in 32-bit protected mode.
mov ax,#0x0001 | protected mode (PE) bit
lmsw ax | This is it!
jmpi 0,8 | jmp offset 0 of segment 8 (cs)
| This routine checks that the keyboard command queue is empty
| No timeout is used - if this hangs there is something wrong with
| the machine, and we probably couldn't proceed anyway.
empty_8042:
.word 0x00eb,0x00eb
in al,#0x64 | 8042 status port
test al,#2 | is input buffer full?
jnz empty_8042 | yes - loop
ret
| This routine loads the system at address 0x10000, making sure
| no 64kB boundaries are crossed. We try to load it as fast as
| possible, loading whole tracks whenever we can.
|
| in: es - starting address segment (normally 0x1000)
|
| This routine has to be recompiled to fit another drive type,
| just change the "sectors" variable at the start of the file
| (originally 18, for a 1.44Mb drive)
|
sread: .word 1 | sectors read of current track
head: .word 0 | current head
track: .word 0 | current track
read_it:
mov ax,es
test ax,#0x0fff
die: jne die | es must be at 64kB boundary
xor bx,bx | bx is starting address within segment
rp_read:
mov ax,es
cmp ax,#ENDSEG | have we loaded all yet?
jb ok1_read
ret
ok1_read:
mov ax,#sectors
sub ax,sread
mov cx,ax
shl cx,#9
add cx,bx
jnc ok2_read
je ok2_read
xor ax,ax
sub ax,bx
shr ax,#9
ok2_read:
call read_track
mov cx,ax
add ax,sread
cmp ax,#sectors
jne ok3_read
mov ax,#1
sub ax,head
jne ok4_read
inc track
ok4_read:
mov head,ax
xor ax,ax
ok3_read:
mov sread,ax
shl cx,#9
add bx,cx
jnc rp_read
mov ax,es
add ax,#0x1000
mov es,ax
xor bx,bx
jmp rp_read
read_track:
push ax
push bx
push cx
push dx
mov dx,track
mov cx,sread
inc cx
mov ch,dl
mov dx,head
mov dh,dl
mov dl,#0
and dx,#0x0100
mov ah,#2
int 0x13
jc bad_rt
pop dx
pop cx
pop bx
pop ax
ret
bad_rt: mov ax,#0
mov dx,#0
int 0x13
pop dx
pop cx
pop bx
pop ax
jmp read_track
/*
* This procedure turns off the floppy drive motor, so
* that we enter the kernel in a known state, and
* don't have to worry about it later.
*/
kill_motor:
push dx
mov dx,#0x3f2
mov al,#0
outb
pop dx
ret
gdt:
.word 0,0,0,0 | dummy
.word 0x07FF | 8Mb - limit=2047 (2048*4096=8Mb)
.word 0x0000 | base address=0
.word 0x9A00 | code read/exec
.word 0x00C0 | granularity=4096, 386
.word 0x07FF | 8Mb - limit=2047 (2048*4096=8Mb)
.word 0x0000 | base address=0
.word 0x9200 | data read/write
.word 0x00C0 | granularity=4096, 386
idt_48:
.word 0 | idt limit=0
.word 0,0 | idt base=0L
gdt_48:
.word 0x800 | gdt limit=2048, 256 GDT entries
.word gdt,0x9 | gdt base = 0X9xxxx
msg1:
.byte 13,10
.ascii "Loading system ..."
.byte 13,10,13,10
.text
endtext:
.data
enddata:
.bss
endbss:
測試引導載入程式
[edit | edit source]也許測試引導載入程式的最簡單方法是在虛擬機器中,例如 VirtualBox 或 VMware。[1]
有時,如果引導載入程式支援 GDB 遠端除錯協議,這將很有用。[2]
進一步閱讀
[edit | edit source]- ↑ "如何開發你自己的引導載入程式" by Alex Kolesnyk 2009
- ↑ "RedBoot 除錯和引導韌體"
- 嵌入式系統/引導載入程式和引導扇區 描述了各種嵌入式系統的引導載入程式。(大多數嵌入式系統沒有 x86 處理器)。