程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 系統棧的工作原理 詳解

系統棧的工作原理 詳解

編輯:關於JAVA
 

本篇文章著重寫的是系統中棧的工作原理,以及函數調用過程中棧幀的產生與釋放的過程,有可能名字過大,如果不合適我可以換一個名字,希望大家能夠指正,小丁虛心求教!如果有哪裡寫的不清楚的或者錯誤的地方請及時更正,小丁再次謝過了。文章裡面有錯別字,也可能會有好友說寄存器的32、16位的區別其實我感覺這裡主要講的還是些原理性的東西,後續會將文章圖片錯別字進行調整.

2.內存的不同用途

根據不同的操作系統,一個進程可能被分配到不同的內存區域去執行。但是不管什麼樣的操作系統、什麼樣的計算機架構,進程使用的內存都可以按照功能大致分為以下4個部分:

(1)代碼區:這個區域存儲著被裝入執行的二進制機器代碼,處理器會到這個區域取指並執行。

(2)數據區:用於存儲全局變量等。

(3)堆區:進程可以在堆區動態地請求一定大小的內存,並在用完之後歸還給堆區。動態分配和回收是堆區的特點。

(4)棧區:用於動態地存儲函數之間的關系,以保證被調用函數在返回時恢復到母函數中繼續執行。

Windows平台下,高級語言寫出的程序經過編譯鏈接,最終會變成PE文件。當PE文件被裝載運行後,就成了所謂的進程。

PE文件代碼段中包含的二進制級別的機器代碼會被裝入內存的代碼區(.text),處理器將到內存的這個區域一條一條地取出指令和操作數,並送入運算邏輯單元進行運算;如果代碼中請求開辟動態內存,則會在內存的堆區分配一塊大小合適的區域返回給代碼區的代碼使用;當函數調用發生時,函數的調用關系等信息會動態地保存在內存的棧區,以供處理器在執行完被調用函數的代碼時,返回母函數。

如果把計算機看成一個有條不紊的工廠,我們可以得到如下類比:

< CPU是完成工作的工人。

< 數據區、堆區、棧區等則是用來存放原料、半成品、成品等各種東西的場所。

< 存放在代碼區的指令則告訴CPU要做什麼,怎麼做,到哪裡去領原材料,用什麼工具來做,做完以後把成品放到哪個貨倉去。

< 值得一提的是,棧除了扮演存放原料、半成品的倉庫之外,它還是車間調度主任的辦公室。

3.棧與系統棧

從計算機科學的角度來看,棧指的是一種數據結構,是一種先進後出的數據表。棧的最常見操作有兩種:壓棧(PUSH)、彈棧(POP);

用於標識棧的屬性也有兩個:棧頂(TOP)、棧底(BASE)。

棧在內存中的存放是高地址是棧底(Base),低地址是棧頂(Top)。

下面來演示下棧的工作原理:

首先我們先以這段匯編指令來進行操作:

mov ax,0123H

push ax

mov bx 2244H

push bx

pop ax

pop bx

首先我們先將10000H-1000FH這段內存空間來當做棧來使用,首先執行的操作是push ax,會將0123H壓入到棧中,SP=SP-2,SS:SP指向當前棧頂當前的單元,以當前的單元為新的棧頂,將ax的數據送到SS:SP指向的內存單元中,SS:SP此時指向新的棧頂。此時ax的數值是0123H;詳細請見下圖

接來下進行第二部操作:push bx,操作同上;

接下來我們要演示的是pop操作,請注意pop操作的細節,比如到了棧底的時候指針是在哪裡?這些都是要進行關注的。

CPU執行pop ax時,SP=SP+2,SS:SP指向1000EH,pop操作棧頂元素,1000CH處的2266H依然存在,但是它在棧中不存在了,當再次push等入棧指令後,SS:SP移至1000CH,並在裡面寫入新的數據,將其覆蓋。詳細看下圖操作:

當再次進行pop給bx時,這是SP=SP+2,這時候指針就超出了棧底,就變成了SP=10H,所以我們得出一個結論就是當棧為空時,SS=1000H,SP=10H。詳細看下面操作:

內存的棧區實際上指的就是系統棧。系統棧由系統自動維護,它用於實現高級語言中函數的調用。對於類似C語言這樣的高級語言,系統棧的PUSH、POP等堆棧平衡細節是透明的。一般說來,只有在使用匯編語言開發程序的時候,才需要和它直接打交道。

4.函數調用約定與相關指令

函數調用約定描述了函數傳遞參數方式和棧幀同工作的技術細節。不同的操作系統、不同的語言、不同的編譯器在實現函數調用時的原理雖然基本相同,但具體的調用約定還是有差別的。這包括參數傳遞方式,參數入棧順序是從右向左還是從左向右,函數返回時恢復堆棧平衡的操作在子函數中進行還是在母函數中進行。
調用方式之間的差異

具體的,對於Visual C++來說,可支持以下3種函數調用約定:

如果要明確使用某一種調用約定,只需要在函數前加上調用約定的聲明即可,否則默認情況下,VC會使用_stdcall的調用方式。 除了參數入棧方向和恢復棧平衡操作位置的不同之外,參數傳遞有時也會有所不同。例如,每一個C++類成員函數都有一個this指針,在Windows平台中,這個指針一般是用ECX寄存器來傳遞的,但如果用GCC編譯器來編譯,這個指針會作為最後一個參數壓入棧中。

注意:同一段代碼用不同的編譯選項、不同的編譯器編譯鏈接後,得到的可執行文件會有很多不同。

函數調用大概包括以下幾個步驟:

(1)參數入棧:將參數從右向左依次壓入系統棧中。

(2)返回地址入棧:將當前代碼區調用指令的下一條指令地址壓入棧中,供函數返回時繼續執行。

(3)代碼區跳轉:處理器從當前代碼區跳轉到被調用函數的入口處。

(4)棧幀調整:具體包括:

<1>保存當前棧幀狀態值,已備後面恢復本棧幀時使用(EBP入棧)。

<2>將當前棧幀切換到新棧幀(將ESP值裝入EBP,更新棧幀底部)。

<3>給新棧幀分配空間(把ESP減去所需空間的大小,抬高棧頂)。

<4>對於_stdcall調用約定,函數調用時用到的指令序列大致如下:

push 參數3      ;假設該函數有3個參數,將從右向做依次入棧

push 參數2

push 參數1

call 函數地址   ;call指令將同時完成兩項工作:a)向棧中壓入當前指令地址的下一個指令地址,即保存返回地址。 b)跳轉到所調用函數的入口處。

push  ebp        ;保存舊棧幀的底部

mov  ebp,esp     ;設置新棧幀的底部 (棧幀切換)

sub   esp,xxx     ;設置新棧幀的頂部 (抬高棧頂,為新棧幀開辟空間)

函數返回的步驟如下:

<1>保存返回值,通常將函數的返回值保存在寄存器EAX中。

<2>彈出當前幀,恢復上一個棧幀。具體包括:

(1)在堆棧平衡的基礎上,給ESP加上棧幀的大小,降低棧頂,回收當前棧幀的空間。

(2)將當前棧幀底部保存的前棧幀EBP值彈入EBP寄存器,恢復出上一個棧幀。

(3)將函數返回地址彈給EIP寄存器。

<3>跳轉:按照函數返回地址跳回母函數中繼續執行。

還是以C語言和Win32平台為例,函數返回時的相關的指令序列如下:

add esp,xxx     ;降低棧頂,回收當前的棧幀

pop ebp         ;將上一個棧幀底部位置恢復到ebp

retn            ;a)彈出當前棧頂元素,即彈出棧幀中的返回地址,至此,棧幀恢復到上一個棧幀工作完成。b)讓處理器跳轉到彈出的返回地址,恢復調用前代碼區

5.寄存器與函數棧幀

每一個函數獨占自己的棧幀空間。當前正在運行的函數的棧幀總是在棧頂。Win32系統提供兩個特殊的寄存器用於標識位於系統棧頂端的棧幀。

(1)ESP:棧指針寄存器(extended stack pointer),其內存放著一個指針,該指針永遠指向系統棧最上面一個棧幀的棧頂。

(2)EBP:基址指針寄存器(extended base pointer),其內存放著一個指針,該指針永遠指向系統棧最上面一個棧幀的底部。

【寄存器對棧的標識作用見(圖1)】

函數棧幀:ESP和EBP之間的內存空間為當前棧幀,EBP標識了當前棧幀的底部,ESP標識了當前棧幀的頂部。

在函數棧幀中,一般包含以下幾類重要信息。

(1)局部變量:為函數局部變量開辟的內存空間。

(2)棧幀狀態值:保存前棧幀的頂部和底部(實際上只保存前棧幀的底部,前棧幀的頂部可以通過棧幀平衡計算得到),用於在本棧被彈出後恢復出上一個棧幀。

(3)函數返回地址:保存當前函數調用前的“斷點”信息,也就是函數調用前的指令位置,以便在函數返回時能夠恢復到函數被調用前的代碼區中繼續執行指令。

注:函數棧幀的大小並不固定,一般與其對應函數的局部變量多少有關。函數運行過程中,其棧幀大小也是在不停變化的。除了與棧相關的寄存器外,我們還需要記住另一個至關重要的寄存器。

EIP:指令寄存器(extended instruction pointer),其內存放著一個指針,該指針永遠指向下一條等待執行的指令地址。 可以說如果控制了EIP寄存器的內容,就控制了進程——我們讓EIP指向哪裡,CPU就會去執行哪裡的指令。這裡不多說EIP的作用,我個人認為王爽老是的匯編裡面講EIP講的已經是挺好的了~這裡不想多寫關於EIP的事情。

6.結束語

本文是針對上面兩篇文章的一個基礎性的補充~希望大家能夠喜歡和指正其中的不足之處,小丁虛心學習於請教~不知道名字叫啥~

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved