【OS學習筆記】九 真實模式:從彙編的角度理解棧結構
上一篇文章以一種更加高效的方法編寫了主引導扇區的程式碼。主要是引入了迴圈和跳轉指令。點選連結檢視上一篇文章:編寫主引導扇區程式碼-另一種更高效的寫法
本篇文章,繼續上一篇文章的學習。同樣還是編寫彙編程式碼載入到主引導扇區讓CPU直接執行。但是我們以一個簡單程式,實現1加到100,來引出8086中的棧結構。瞭解處理器訪問棧需要哪些支援。
1、回顧
還記得前幾篇文章,我們學會了編寫主引導扇區程式碼,在顯示屏顯示字串。最開始我們的做法是一個字元一個字元的傳送給視訊記憶體。後來發現可以先將所有需要傳送的字元先存放到一塊記憶體中,然後使用movsw連續傳送這些字串到視訊記憶體更加方便。
今天我們的目的是,我們將我們想要顯示的數字,先暫時存放到一種稱為棧的結構中。最後我們再從棧中取出這些數字傳送給視訊記憶體。
2、程式碼清單
與前幾篇文章一樣,我們先提供彙編程式碼。遇到彙編程式碼不要害怕,腦子裡將CPU,暫存器,記憶體這三個結構與他們的關係都勾勒出來,然後分析指令的執行,就會很清晰。
如果不懂,看後面的分析,如果你有一點基礎,就一定能夠學會!!!
;程式碼清單7-1
;檔名:c07_mbr.asm
;檔案說明:硬碟主引導扇區程式碼
;
jmp near start
message db '1+2+3+...+100='
start:
mov ax,0x7c0 ;設定資料段的段基地址
mov ds,ax
mov ax,0xb800 ;設定附加段基址到顯示緩衝區
mov es,ax
;以下顯示字串
mov si,message
mov di,0
mov cx,start-message
@g:
mov al,[si]
mov [es:di],al
inc di
mov byte [ es:di],0x07
inc di
inc si
loop @g
;以下計算1到100的和
xor ax,ax
mov cx,1
@f:
add ax,cx
inc cx
cmp cx,100
jle @f
;以下計算累加和的每個數位
xor cx,cx ;設定堆疊段的段基地址
mov ss,cx
mov sp,cx
mov bx,10
xor cx,cx
@d:
inc cx
xor dx,dx
div bx
or dl,0x30 ;實際上應該是add指令,但是這可以是or指令,因為dl高四位為0,0x30低四位位0
push dx
cmp ax,0
jne @d
;以下顯示各個數位
@a:
pop dx
mov [es:di],dl
inc di
mov byte [es:di],0x07 ;顯示字元的顏色屬性
inc di
loop @a
jmp near $
times 510-($-$$) db 0
db 0x55,0xaa
程式碼不長,大部分內容,在前幾篇文章都學過。
3、程式碼分析
強烈建議先將上一篇文章學會,再閱讀下面的程式碼解釋會更加輕鬆:點選連結檢視上一篇文章
這裡分析會比較簡潔,因為大部分程式碼的意思跟前幾篇文章內容是一個意思,無非就是設定程式碼段資料段基地址與偏移地址,設定視訊記憶體的基地址與偏移地址。然後將要顯示的字串經過計算得出結果並存起來。最後將這些字串傳送到顯示緩衝區。
那麼下面就開始分析:
-
8行:就是想要顯示‘1+2+3+…+100’,只不過這裡先要將它儲存在這裡,好方便下面的迴圈傳送。message是標號,代表它當前位置的彙編地址
-
11-14行:設定資料段基地址與附加段基地址(也就是視訊記憶體的基地址),這裡前幾篇文章已經講了很多,不懂的可以回頭看前面的文章。
-
18-28行:將字串‘1+2+3+…+100’顯示出來。這裡同樣使用了迴圈的方法將字串迴圈傳送到視訊記憶體。CX這裡代表計數器,表示要傳送的字串的位元組數。inc指令代表加1的意思。
-
31-37行:計算1-100的和。這裡將計算結果存到AX暫存器。CX每次加1是代表下一次要加的數。
-
40-53行:計算累加和的各個數位。畢竟我們要顯示這個累加和嘛,又不能直接將它傳送到顯示緩衝區直接顯示,直接將它的各個數位拆解出出來顯示。這幾行,是我們今天要重要研究的彙編程式碼。它涉及到一個新的概念----棧
得到了累加和之後,前兩篇文章,是將各個數位儲存在資料段中。現在我們將各個數位儲存在一個叫做棧的地方。
棧----是一種特殊的資料儲存結構,資料的存取只能從一端進行。這樣先進去的資料只能最後出來。後進去的資料倒是最先出來。
如下圖:
和程式碼段,資料段和附加段一樣,棧也是一種記憶體段,叫做棧段。由棧暫存器SS指向。
針對棧有兩種操作方式:push和pop。這個應該大家都理解。壓棧和出棧只能在一端進行。所以需要用棧指標暫存器SP來指示下一個資料應當壓入到什麼位置,或者資料從哪裡彈出。
定義棧需要兩個步驟。即指定SS和SP暫存器。為此40-42行,設定了SS和SP。他們都是指向0地址。
到目前為止,我們已經定義了3個段。如下圖是我們當前程式的記憶體佈局:
總記憶體容量是1MB,實體地址範圍是0x00000-0xFFFFF
其中資料段長度是64KB(實際上它的長度無關緊要)佔據的實體地址範圍是0x07C00-0x17BFF
,對應的邏輯地址為範圍為 0x07C0:0x0000-0x7C00:0xFFFF;
程式碼段和棧段是同一個段,佔據著實體地址0x00000-0x0FFFF
,對應的邏輯地址的範圍是0x0000:0x0000-0x0000:0xFFFF
。
雖然程式碼段和棧段在本質上指向同一塊記憶體區域,但是通過後面的學習我們會知道,他們互不干擾。
分解各個數位還是要靠除法來做,44行將除數10傳送給暫存器BX。
由於每次分解得到的數位都是壓棧的,所以後面再出棧的時候,我們需要記住總共有多少個。這裡用CX暫存器記錄個數。所以45行,先將CX暫存器清零。
源程式第47-53行也是一個迴圈體,沒執行一次,分解出一個數位。每次分解時,CX加1,表明數位又多了一個,這是源程式47行所做的事。其他指令較為簡單治理不再贅述。
- 57-62行:出棧,並顯示各個數位。
這幾行都比較簡單。pop指令的意思是將邏輯地址SS:SP處的一個字彈出到暫存器DX中,然後將暫存器SP的內容加上運算元的字長(2)。
-
64行:為了讓我們看到顯示屏的顯示效果,這裡是一個死迴圈,防止程式退出。
-
67-68行:填充空的位元組區間。然後最後的0x55和0xaa是主引導扇區的有效標誌。
4、進一步認識棧
上述我們從程式碼層面第一次接觸到棧這種結構。那麼下面我們就來總結一下,做幾點說明。
- push指令的運算元可以是16位暫存器或者16位記憶體單元。push指令執行後,壓入棧中的僅僅是該暫存器或者記憶體單元中的數值。
- 棧在本質上也只是普通的記憶體區域,之所以用push和pop指令來訪問,是因為你把它看成棧而已。引入棧和push、pop只是方便程式開發而已。
- 要注意保持棧平衡。push多少,pop多少。
- 在編寫程式時,必須充分估計所需的棧空間,以防止破壞有用的資料。
- 儘管不能完全阻止程式中的錯誤,但是,通過將棧定義到一個單獨的64KB記憶體段,可以使錯誤僅侷限於棧段,而不破壞其他段的有用資料。
5、執行程式
執行結果如下;
本次程式執行很順利!!!
筆記記得不是很全,像彙編的語法以及如何將程式碼寫到虛擬硬碟的主引導扇區這些都沒有寫。如果又不懂的可以加我聯絡方式一起交流。
學習探討加個人:
qq:1126137994
微信:liu1126137994