作業系統——初識MBR(三)
作業系統——初識MBR(三)
2020-09-1114:50:02 hawk
概述
因為上一節我們已經簡單學習了組合語言中訪問記憶體以及一些跳轉的基礎指令,因此這節中我們學習通過CPU直接與外設進行通訊,從而避免通過使用BIOS的終端功能來進行互動。
IO介面
隨著計算機的不斷髮展,當前的外部裝置種類繁多、原理各異、特性不同、資料格式有差異,因此讓CPU直接與這些外部裝置進行通訊是十分不現實的。因此解決這個問題同樣依靠計算機方面的優秀傳統之一——新增抽象層來解決問題。即通過在外部裝置和CPU之間新增一個抽象層,即IO介面,通過這個IO介面來在CPU和外設中完成資料的協調轉換工作,從而確保CPU可以高效、方便的和外部裝置進行通訊。而為了簡化CPU訪問外設的工作,使其能夠輕鬆的同其他任何外設進行通訊,大家約定好了IO介面的功能,基本如下所示
1. 設定資料緩衝,解決CPU與外設之間的速度不匹配問題;
2. 設定訊號電平轉換電路,解決CPU使用TTL電平無法驅動大部分機電裝置型別的外設;
3. 設定資料格式轉換,解決CPU和外設資料格式、字長和串並型等匹配問題;
4. 設定時許控制電路來同步CPU和外設;
5. 提供地址譯碼,使CPU可以選擇IO介面上的具體某一個埠,即IO介面上的某一個暫存器,使其可以訪問資料匯流排。
這裡再介紹一下CPU如何與IO介面進行通訊。CPU和IO介面的通訊,實際上是CPU和IO介面的埠,即暫存器,通過匯流排相連,修改IO介面的埠的過程。因此往往CPU訪問IO介面就相當於訪問IO介面上的埠。這個訪問大體可以分為兩種情況——記憶體對映和獨立變址。對於記憶體對映來說,也就是將埠內容直接對映到記憶體中,因此CPU想要讀寫IO介面的埠,只需要讀寫對應的記憶體即可,通過mov命令介面實現;而對於獨立編址來說,CPU需要通過in和out指令來單獨處理,從而訪問IO介面的埠。對於in指令,其一般形式如下所示
in al, dx in ax, dx
其中dx中的值是獨立編址的埠號,也就是暫存器被獨立編譯址的地址,而al/ax暫存器用來儲存指定IO介面的埠的資料。對於out指令是類似的,其一般形式如下所示
out dx, al out dx, ax out 立即數, al out 立即數, ax
其中立即數/dx的值為獨立編址的埠號,也就是暫存器被獨立編址的地址,而al/ax暫存器用來儲存待寫入IO介面的埠的資料。
視訊記憶體、顯示卡和顯示器
由於前面實現的MBR程式的功能就是列印一個字串,但是是通過呼叫BIOS的終端實現的。這裡通過直接讀寫視訊記憶體,不借助任何的軟體依賴,同樣實現字串的顯示功能。我們將分別介紹一下有關視訊記憶體、顯示卡和顯示器的相關資訊,從而實現這個MBR程式。
CPU、視訊記憶體、顯示卡和顯示器之間的關係
實際上,為了能從計算機上顯示影象,我們需要顯示器這個外部裝置來幫忙。而無論是那種顯示器,其都需要由顯示卡來進行控制。而顯示卡實際上就是我們前面講到的外設和CPU之間的抽象層,其上包含IO介面和視訊記憶體。因此,CPU只要通過與IO介面和視訊記憶體進行通訊,即可實現與顯示器的外部裝置的通訊。
視訊記憶體與輸出
實際上,只要我們向對應的視訊記憶體中輸出資料,則顯示卡會處理這些輸入的輸出,然後顯示在顯示器上。而結合前面分析的真實模式下記憶體的佈局,與顯示卡的通訊我認為是屬於記憶體對映型的,因此我們直接通過訪問記憶體即可完成與顯示卡的通訊,從而完成顯示器的顯示。下面的問題在於顯示卡是如何處理這些資料的,從而我們通過輸入設定好的內容,經顯示卡處理後,在顯示器上顯示處我們想要的結果。
實際上根據真實模式下記憶體佈局我們可以看出來,顯示卡支援三種模式,如下表所示
起始地址 | 結束地址 | 大小 | 用途 |
0xC0000 |
0xC7FFF |
32KB | 顯示介面卡的BIOS |
0xB8000 | 0xBFFFF | 32KB | 用於文字模式顯示介面卡 |
0xB0000 | 0xB7FFF | 32KB | 用於黑白顯示介面卡 |
0xA0000 | 0xAFFFF | 64KB | 用於彩色顯示介面卡 |
可以看到,其支援文字模式、黑白模式以及彩色模式。由於之後我們實現的作業系統是文字模式的終端介面,因此我們主要關注文字模式即可。
因此可以看到,根據記憶體對映,只要我們想0xB8000到0xBFFFF這片32KB大小的記憶體中輸出資料,則相當於向視訊記憶體輸出資料,則顯示卡會將這些資料處理後按規則顯示在顯示介面卡上。這裡我們簡單介紹一下文字模式下顯示卡處理資料的規則,首先每一個字元通過2位元組的資料進行表示,其每一位的內容如下圖所示
這裡我們列表說明一下不同的R、G、B和I所組合出來的最終顏色,如下所示
R | G | B | 顏色 |
|
I=0/背景色 | I=1 | |||
0 | 0 | 0 | 黑 | 灰 |
0 | 0 | 1 | 藍 | 淺藍 |
0 | 1 | 0 | 綠 | 淺綠 |
0 | 1 | 1 | 青 | 淺青 |
1 | 0 | 0 | 紅 | 淺紅 |
1 | 0 | 1 | 品紅 | 淺品紅 |
1 | 1 | 0 | 棕 | 黃 |
1 | 1 | 1 | 白 | 亮白 |
這裡我們基本就完成了顯示卡處理文字模式的規則。下面還要說明一下,顯示卡的文字模式也是分為多種模式的,用“列數 * 行數”來進行表明,如80 * 25等。一般情況下,在顯示卡加電後,預設的模式就為80 * 25,即2000個字元,也就是需要4000B的記憶體。這樣基本介紹完了視訊記憶體與輸出的關係。
實驗
本次實驗的全部程式碼已經放置在我的倉庫中,點此進行跳轉
這次我們要實現的功能,是直接通過與視訊記憶體進行互動,使其在藍色的背景色下,閃爍淺品紅色的“Hawk’s MBR”字串即可。下面同樣給出對應的原始碼,如下所示
; 簡單的主載入程式,但並沒有實現引導的功能,僅僅實現輸出字串 ; 但是其並不需要藉助任何庫等,這次通過直接訪問視訊記憶體記憶體,從而輸出字串 ;------------------------------------------------------------------------ SECTION MBR vstart=0x7c00 ;這個地址表示將起始地址設定為0x7c00——因為BIOS會將MBR程式載入到0x7c00處 mov ax, cs mov ds, ax ;由於BIOS跳轉到MBR時,使用指令jmp 0:0x7c00,因此cs段暫存器為0,這裡將ds段暫存器也設定為了0 mov sp, 0x7c00 ;根據已知,至少0x500-0x7DFF為可用區域,則將其當用作棧即可 mov ax, 0xB800 mov es, ax ;根據已知,真實模式1MB記憶體中0xB8000-0xBFFFF為文字模式的顯示介面卡,方便之後通過直接定址讀寫記憶體 ; 下面首先清空螢幕,這裡使用BIOS提供的中斷即可, ;------------------------------------------------------------------------ ; INT 0x10; 功能號:0x06 功能描述:上卷視窗 ;------------------------------------------------------------------------ ; 輸入: ; AH--功能號: 0x06 ; AL--上卷的行數(如果為0,表示全部) ; BH--上卷行屬性 ; (CL, CH)--視窗左上角(X, Y)位置 ; (DL, DH)--視窗右下角(X, Y)位置 ; 輸出: ; 空 mov ax, 0x600 ;AH = 0x06; AL = 0x0; mov bx, 0x700 ;BH = 0x07; BL = 0x0; mov cx, 0x0 ;CH = 0x0; CL = 0x0; mov dx, 0x184f ;DH = 0x18; DL = 0x4f; int 0x10 ;------------------------------------------------------------------------ ; 我們將上面的系統呼叫分析一下 ; 輸入: AH--0x06; AL--0x0; BH--0x7; CL--0x0;CH--0x0; DL--0x4f;DH--0x18; ; 也就是我們呼叫了功能號為0x6的BIOS中斷,視窗左上角為(0x0/0, 0x0/0),視窗右上角座標為(0x4f/80, 0x18/25),上卷所有的視窗 ; 在VGA文字模式中,一般一行容納80個字元,共25行,也就相當於清空了整個螢幕 ; 向1MB記憶體中的文字模式的顯示介面卡區域寫入資料 ;------------------------------------------------------------------------ ; 每個字元2位元組,其低位元組為字元對應的ASCII碼,高位元組為字元的屬性 ; 由於其為背景白色,前景黑色,並且閃爍,其高位元組值為 11110000b ;------------------------------------------------------------------------ mov cx, 0x0 mov byte al, [format]; 初始化計數器cx,。由於前面已經設定了ds段暫存器為0,該指令相當於將字元屬性位元組讀入ax暫存器中 LOOP: mov di, cx mov byte dl, [di + string]; 這裡通過變址定址訪問記憶體,由於前面設定了ds段暫存器為0,這裡直接獲取字串中的對應字元 sub dl, 0 jz LOOPEND; 判斷字串是否結束。有條件跳轉,因此僅僅修改段偏移地址,由於cs始終為0,自然跳轉到LOOPEND對應的位置 add di, di mov byte [es:di], dl; 這裡通過變址定址訪問記憶體 add di, 1 mov byte [es:di], al; 這裡通過變址定址訪問記憶體 add cx, 1 jmp LOOP; 計數器+1後,無條件相對近跳轉,會重新跳轉到LOOP處執行迴圈 LOOPEND: ;------------------------------------------------------------------------ ; 我們將上面的指令分析一下 ; 可以看到,對於記憶體定址來說,這裡通過直接定址進行定址 ; 我們每一次輸入兩個位元組資訊,其中低位元組是上面分析的字元的屬性 ; 高位元組是字元對應的ascii碼,從而完成了記憶體的寫入。 ; 下面進行迴圈,確保程式懸停在該處,從而觀察輸出 ;------------------------------------------------------------------------ jmp $ ;------------------------------------------------------------------------ ; 我們將上面的指令分析一下 ; $表示當前行的地址,這樣子相當於始終執行這一行指令,從而使程式懸停 ; 下面進行常量設定 ;------------------------------------------------------------------------ string db "This is Hawk's MBR", 0; 即偽操作指令,表示每一個元素大小為1位元組, 並且在結尾為\x00表明字串結束 format db 10011101b; 這裡是視訊記憶體中的字元屬性,表明其為背景藍色,前景色淺品紅色,並且閃爍 ; 下面進行空白填充,確保最後程式為512位元組 ;------------------------------------------------------------------------ times 510 - ($ - $$) db 0 ;------------------------------------------------------------------------ ; 我們將上面的彙編語句分析一下 ; $表示當前行的地址,$$表示當前SECTION的起始地址,times也是偽操作指令,相當於將後面的資料重複指定次數 ; 這個指令確保了將程式填充至512位元組,中間部分以0填充 ; 下面我們最後填充該512位元組刪除的最後兩個位元組,為0x55,0xaa,從而使BIOS成功識別MBR ;------------------------------------------------------------------------ db 0x55, 0xaa
實際上上面的註釋已經很詳細了,這裡在具體說明一下
1. 前面仍然通過BIOS中斷呼叫清空螢幕,然後通過jmp實現迴圈,向指定的記憶體中寫入對應的值。
2. 這次程式碼涉及真實模式下的記憶體訪問,這裡可以參考一下上一篇部落格,其中簡單介紹了一下真實模式下的彙編相關的知識。這裡重要使用了變址定址和直接定址。
這裡還需要說明一下,不知道什麼原因,通過qemu執行無法顯示顏色的閃爍現象,這裡使用bochs執行該機器,其中bochs的配置可點選連結檢視,也可以檢視《作業系統,真象還原》書籍中的介紹,這裡我們正常編譯,如圖所示
下面我們使用配置好的bochs軟體進行啟動,檢視最後的結果(如果使用qemu的話,無法觀察到字元的閃爍),結果如下所示