深入理解計算機系統 過程(函式呼叫棧幀變化)

2022-09-11 12:18:14 字數 3709 閱讀 8053

一:棧幀結構

暫存器幀指標%ebp ,棧指標%erp 。

因為棧指標經常移動,所以基於位址的訪問多數是以幀指標為基礎的。而被呼叫者的棧幀一般在呼叫者下方。

實現過程

過程的實現主要就是在於資料如何在呼叫者和被呼叫者之間傳遞,以及在被呼叫者當中區域性變數記憶體的分配以及釋放。

而過程實現當中,引數傳遞以及區域性變數記憶體的分配和釋放都是通過以上介紹的棧幀來實現的,大部分情況下,我們認為過程呼叫當中做了以下幾個操作。

①、備份原來的幀指標,調整當前的幀指標到棧指標的位置,這個過程就是我們經常看到的如下兩句彙編**做的事情。

pushl   %ebp

movl %esp, %ebp

②、建立起來的棧幀就是為被呼叫者準備的,當被呼叫者使用棧幀時,需要給臨時變數分配預留記憶體,這一步一般是經過下面這樣的彙編**處理的。

1subl    $16,%esp

③、備份被呼叫者儲存的暫存器當中的值,如果有值的話,備份的方式就是壓入棧頂。因此會採用如下的彙編**處理。  

1pushl    %ebx

④、使用建立好的棧幀,比如讀取和寫入,一般使用mov,push以及pop指令等等。

⑤、恢復被呼叫者暫存器當中的值,這一過程其實是從棧幀中將備份的值再恢復到暫存器,不過此時這些值可能已經不在棧頂了。因此在恢復時,大多數會使用pop指令,但也並非一定如此。

⑥、釋放被呼叫者的棧幀,釋放就意味著將棧指標加大,而具體的做法一般是直接將棧指標指向幀指標,因此會採用類似下面的彙編**處理(也可能是addl)。

1movl    %ebp,%esp

⑦、恢復呼叫者的棧幀,恢復其實就是調整棧幀兩端,使得當前棧幀的區域又回到了原始的位置。因為棧指標已經在第六步調整好了,因此此時只需要將備份的原幀指標彈出到%ebp即可。類似的彙編**如下。  

1popl    %ebp

⑧、彈出返回位址,跳出當前過程,繼續執行呼叫者的**。此時會將棧頂的返回位址彈出到pc,然後程式將按照彈出的返回位址繼續執行。這個過程一般使用ret指令完成。

過程的實現大概就是以上八個步驟組成的,不過這些步驟並不都是必須的(大部分時候,開啟編譯器的優化會優化掉很多步驟),而且第6和第7步有時會使用le**e指令代替。

過程呼叫和返回

(1)call指令:call 指令有乙個目標,即指明被呼叫過程起始的指令位址。直接呼叫的目標可以是乙個標號,間接呼叫的目標是 * 後面跟乙個操作符。它一共做兩件事,第一件是將返回位址(也就是call指令執行時pc的值)壓入棧頂,第二件是將程式跳轉到當前呼叫的方法的起始位址。第一件事是為了為過程的返回做準備,而第二件事則是真正的指令跳轉。

(2)le**e指令:它也是一共做兩件事,第一件是將棧指標指向幀指標,第二件是彈出備份的原幀指標到%ebp。第一件事是為了釋放當前棧幀,第二件事是為了恢復呼叫者的棧幀。

(3)ret指令:它同樣也是做兩件事,第一件是將棧頂的返回位址彈出到pc,第二件事則是按照pc此時指示的指令位址繼續執行程式。這兩件事其實也可以認為是一件事,因為第二件事是系統自己保證的,系統總是按照pc的指令位址執行程式。

可以看出,除了call指令之外,le**e和ret指令都與上面8個步驟有些不可分割的關係。call指令沒有在8個步驟當中體現,是因為它發生在進入過程之前,因此在第1步發生的時候,call指令往往已經被執行了,並且已經為ret指令準備好了返回位址。

在 ia32 中,暫存器%eax,%edx和%ecx被劃分為呼叫者儲存暫存器。當過程 p 呼叫 q 時,q可以覆蓋這些暫存器,而不會破壞 p 所需的資料。

暫存器%ebx,%esi和%edi被劃分為被呼叫者儲存暫存器。這裡 q 必須在覆蓋這些暫存器的值之前,先把他們儲存到棧中,並在返回前恢復它們,因為 p(或某個更高層次的過程)可能會在今後的計算中需要這些值。上面所說的過程實現的8個步驟中第三步便是如此。

int p(int

x)

(1)可以在呼叫 q 之前,將 y 的值儲存在自己的幀棧中;當 q 返回時,過程 p 就可以從棧中取出y 的值。換句話說就是呼叫者 p 自己儲存這個值。 過程 p 在呼叫 q 之前會先計算 y 的值,而且它必須保證 y 的值在 q 返回後是可用的。這裡有兩種方法實現:

(2)可以將 y 儲存在被呼叫者儲存暫存器中。如果 q ,或者其它 q 呼叫的程式想使用這個暫存器,它必須將這個暫存器的值儲存在幀棧中,並在返回前恢復該值。換句話說就是被呼叫者儲存這個值。當 q 返回到 p 時,y 的值會在被呼叫者儲存暫存器中,或者是因為暫存器根本就沒有改變,或者是因為它被儲存並恢復了。

這兩種方法在 ia32 中是都採用的。

過程的實現主要就是在於資料如何在呼叫者和被呼叫者之間傳遞,以及在被呼叫者當中區域性變數記憶體的分配以及釋放。

而過程實現當中,引數傳遞以及區域性變數記憶體的分配和釋放都是通過以上介紹的棧幀來實現的,大部分情況下,我們認為過程呼叫當中做了以下幾個操作。

①、備份原來的幀指標,調整當前的幀指標到棧指標的位置,這個過程就是我們經常看到的如下兩句彙編**做的事情。12

pushl   %ebp

movl    %esp, %ebp

②、建立起來的棧幀就是為被呼叫者準備的,當被呼叫者使用棧幀時,需要給臨時變數分配預留記憶體,這一步一般是經過下面這樣的彙編**處理的。

1subl    $16,%esp

③、備份被呼叫者儲存的暫存器當中的值,如果有值的話,備份的方式就是壓入棧頂。因此會採用如下的彙編**處理。  

1pushl    %ebx

④、使用建立好的棧幀,比如讀取和寫入,一般使用mov,push以及pop指令等等。

⑤、恢復被呼叫者暫存器當中的值,這一過程其實是從棧幀中將備份的值再恢復到暫存器,不過此時這些值可能已經不在棧頂了。因此在恢復時,大多數會使用pop指令,但也並非一定如此。

⑥、釋放被呼叫者的棧幀,釋放就意味著將棧指標加大,而具體的做法一般是直接將棧指標指向幀指標,因此會採用類似下面的彙編**處理(也可能是addl)。

1movl    %ebp,%esp

⑦、恢復呼叫者的棧幀,恢復其實就是調整棧幀兩端,使得當前棧幀的區域又回到了原始的位置。因為棧指標已經在第六步調整好了,因此此時只需要將備份的原幀指標彈出到%ebp即可。類似的彙編**如下。  

1popl    %ebp

⑧、彈出返回位址,跳出當前過程,繼續執行呼叫者的**。此時會將棧頂的返回位址彈出到pc,然後程式將按照彈出的返回位址繼續執行。這個過程一般使用ret指令完成。

過程的實現大概就是以上八個步驟組成的,不過這些步驟並不都是必須的(大部分時候,開啟編譯器的優化會優化掉很多步驟),而且第6和第7步有時會使用le**e指令代替。下面會詳細講解這些步驟。

pushl   %ebp

movl    %esp, %ebp

深入理解計算機系統

關鍵路徑是在迴圈的反覆執行中形成的資料相關鏈。迴圈展開是一種程式變換,通過增加每次迭代計算的元素的數量,減少迴圈的迭代次數。重新結合變換能夠減少計算中關鍵路徑上操作的數量,通過更好地利用功能單元的流水線能力得到更好的效能。浮點運算不保證是可結合的,通常迴圈展開和並行地累積在多個值中,是提高程式效能的...

《深入理解計算機系統》

知乎 深入理解計算機系統 這本書需要什麼水平能看懂?15 213 18 218 15 513 introduction to computer systems schedule fall 2016 鏈結失效則 cmu15 213的課程主頁,有ppt,還有錄影,主講人就是這本書的作者。備註 備註 詳細...

深入理解計算機系統

系統的硬體組成 快取記憶體 作業系統管理硬體 程序虛擬記憶體 檔案amdahl定律 併發和並行 0和1組成的位序列,又稱為位元序列,8個位被組織成一組,成為位元組。每個位元組表示程式中的某些文字字元。系統中的所有資訊 包括磁碟檔案 記憶體中的程式 記憶體中存放的的使用者資料以及網路上傳送的資料,都是...