在上兩期中(自己動手寫操作系統
)
我向大家講述了如何使用Linux提供的開發工具在軟盤的啟動扇區寫一些代碼
以及如何調用BIOS的問題
現在
這個操作系統已經越來越接近當年Linus Torvalds的那個具有
歷史意義
的Linux內核了
因此
要馬上把這個系統切換到保護模式之下
什麼是保護模式
自從
年推出第一個微處理器以來
Intel處理器就在不斷地更新換代
從
到
奔騰
奔騰Ⅱ
奔騰
等
其體系結構也在不斷變化
以後
提供了一些新的功能
彌補了
的一些缺陷
這其中包括內存保護
多任務及使用
KB以上的內存等
並仍然保持和
家族的兼容性
也就是說
仍然具備了
和
的所有功能
但是在功能上有了很大的增強
早期的處理器是工作在實模式之下的
以後引入了保護模式
而在
以後保護模式又進行了很大的改進
在
中
保護模式為程序員提供了更好的保護
提供了更多的內存
事實上
保護模式的目的不是為了保護程序
而是要保護程序以外的所有程序(包括操作系統)
簡言之
保護模式是處理器的一種最自然的模式
在這種模式下
處理器的所有指令及體系結構的所有特色都是可用的
並且能夠達到最高的性能
保護模式和實模式
從表面上看
保護模式和實模式並沒有太大的區別
二者都使用了內存段
中斷和設備驅動來處理硬件
但二者有很多不同之處
我們知道
在實模式中內存被劃分成段
每個段的大小為
KB
而這樣的段地址可以用
位來表示
內存段的處理是通過和段寄存器相關聯的內部機制來處理的
這些段寄存器(CS
DS
SS和ES)的內容形成了物理地址的一部分
具體來說
最終的物理地址是由
位的段地址和
位的段內偏移地址組成的
用公式表示為
物理地址=左移
位的段地址+偏移地址
在保護模式下
段是通過一系列被稱之為
描述符表
的表所定義的
段寄存器存儲的是指向這些表的指針
用於定義內存段的表有兩種
全局描述符表(GDT)和局部描述符表(LDT)
GDT是一個段描述符數組
其中包含所有應用程序都可以使用的基本描述符
在實模式中
段長是固定的(為
KB)
而在保護模式中
段長是可變的
其最大可達
GB
LDT也是段描述符的一個數組
與GDT不同
LDT是一個段
其中存放的是局部的
不需要全局共享的段描述符
每一個操作系統都必須定義一個GDT
而每一個正在運行的任務都會有一個相應的LDT
每一個描述符的長度是
個字節
格式如圖
所示
當段寄存器被加載的時候
段基地址就會從相應的表入口獲得
描述符的內容會被存儲在一個程序員不可見的影像寄存器(shadow register)之中
以便下一次同一個段可以使用該信息而不用每次都到表中提取
物理地址由
位或者
位的偏移加上影像寄存器中的基址組成
實模式和保護模式的不同可以從圖
和圖
中很清楚地看出來
圖
實模式的尋址
圖
保護模式下的尋址
圖
段描述俯的格式
此外
還有一個中斷描述符表(IDT)
這些中斷描述符會告訴處理器到那裡可以找到中斷處理程序
和實模式一樣
每一個中斷都有一個入口
但是這些入口的格式卻完全不同
因為在切換到保護模式的過程中沒有使用到IDT
所以在此就不多做介紹了
進入保護模式
有
個
位控制寄存器
名字分別為CR
CR
CR
和CR
CR
是保留在未來處理器中使用的
在
中沒有定義
CR
包含系統的控制標志
用於控制處理器的操作模式和狀態
CR
和CR
是用於控制分頁機制的
在此
我們關注的是CR
寄存器的PE位控制
它負責實模式和保護模式之間的切換
當PE=
時
說明處理器運行於保護模式之下
其采用的段機制和前面所述的相應內容對應
如果PE=
那麼處理器就工作在實模式之下
切換到保護模式
實際就是把PE位置為
為了把系統切換到保護模式
還要做一些其它的事情
程序必須要對系統的段寄存器和控制寄存器進行初始化
把PE位置
後
還要執行跳轉指令
過程簡述如下
創建GDT表;
通過置PE位為
進入保護模式;
執行跳轉以清除在實模式下讀取的任何指令
下面使用代碼來實現這個切換過程
需要的東西
◆ 一張空白軟盤
◆ NASM編譯器
下面是整個程序的源代碼
org
x
c
; 起始地址是
:
c
jmp short begin_boot ; 跳過其它的數據
跳轉到引導程序的開始處
bootmesg db
Our OS boot sector loading
pm_mesg db
Switching to protected mode
dw
; 每一扇區的字節數
db
; 每一簇的扇區數
dw
; 保留的扇區號
db
dw
x
e
dw
x
b
db
x
f
dw
dw
dw
; 讀寫扇區號
dw
; 隱藏扇區號
print_mesg :
mov ah
x
; 使用中斷
h的功能
在屏幕上寫一個字符串
mov al
x
; 決定調用函數後光標所處的位置
mov bx
x
; 設置顯示屬性
mov cx
x
; 在此字符串長度為
mov dx
x
; 光標的起始行和列
int
x
; 調用BIOS的中斷
h
ret ; 返回調用程序
get_key :
mov ah
x
int
x
; Get_key使用中斷
h的功能
讀取下一個字符
ret
clrscr :
mov ax
x
; 使用中斷
h的功能
實現卷屏
如果al=
則清屏
mov cx
x
; 清屏
mov dx
x
f ; 卷屏至
mov bh
; 使用顏色
來填充
int
x
; 調用
h中斷
ret
begin_boot :
call clrscr ; 先清屏
mov bp
bootmesg ; 提供串地址
call print_mesg ; 輸出信息
call get_key ; 等待用戶按下任一鍵
bits
call clrscr ; 清屏
mov ax
xb
; 使gs指向顯示內存
mov gs
ax ; 在實模式下顯示一個棕色的A
mov word [gs:
]
x
; 顯示
call get_key ; 調用Get_key等待用戶按下任一鍵
mov bp
pm_mesg ; 設置串指針
call print_mesg ; 調用print_mesg子程序
call get_key ; 等待按鍵
call clrscr ; 清屏
cli ; 關中斷
lgdt[gdtr] ; 加載GDT
mov eax
cr
or al
x
; 設置保護模式位
mov cr
eax ; 將更改後的字送至控制寄存器中
jmp codesel:go_pm
bits
go_pm :
mov ax
datasel
mov ds
ax ; 初始化ds和es
使其指向數據段
mov es
ax
mov ax
videosel ; 初始化gs
使其指向顯示內存
mov gs
ax
mov word [gs:
]
x
; 在保護模式下顯示一個白色的字符A
spin : jmp spin ; 循環
bits
gdtr :
dw gdt_end
gdt
; gdt的長度
dd gdt ; gdt的物理地址
gdt
nullsel equ $
gdt ; $指向當前位置
所以nullsel =
h
gdt
; 空描述符
dd
dd
; 所有的段描述符都是
位的
codesel equ $
gdt ; 這是
h也就是gdt的第二個描述符
code_gdt
dw
x
ffff ; 段描述符的界限是
Gb
dw
x
db
x
db
x
a
db
x
cf
db
x
datasel equ $
gdt
data_gdt
dw
x
ffff
dw
x
db
x
db
x
db
x
cf
db
x
videosel equ $
gdt
dw
dw
x
; 基址是
xb
db
x
b
db
x
db
x
db
x
gdt_end
times
($
$$) db
dw
x
aa
把上面的代碼存在一個名為abc
asm的文件之中
使用命令nasm abc
asm
將得出一個名為abc的文件
然後插入軟盤
輸入命令
dd if=abc of=/dev/fd
該命令將把文件abc寫入到軟盤的第一扇區之中
然後重新啟動系統
就會看到如下的信息
*Our os booting
* A (棕色)
* Switching to protected mode
* A (白色)
對代碼的解釋
上面給出了所有的代碼
下面我對上述代碼做一些解釋
◆ 使用的函數
下面是代碼中一些函數的說明
print_mesg 該子程序使用了BIOS中斷
h的功能
h
即向屏幕寫一字符串
屬性控制是通過向一些寄存器中送入不同的值來實現的
中斷
h是用於各種字符串操作
我們把子功能號
h送到ah中
用於指明要打印一個字符串
al寄存器中的
說明了光標返回的起始位置
表示調用函數後光標返回到下一行的行首
如果al為
則表示光標位於最後一個字符處
顯存被分成了幾頁
在同一時
From:http://tw.wingwit.com/Article/program/Oracle/201311/16515.html