存儲unix程式設計
在《UNIX環境進階程式設計》一書的第八章中,有一道課後習題如下:
回憶圖7-3典型的存儲空間布局。由于對應于每個函數調用的棧幀通常存儲在棧中,并在調用 vfork後,子程序運作在父程序的位址空間中,如果不是在main函數中而是在另一個函數中調用vfork,以後子程序從該函數傳回時,将會發生什麼情況?
作者Rich Stevens是一位大師,留下這麼一題必有其深意,于是結合《深入了解計算機系統》中的知識,寫了個程式驗證了下,受益良多。
首先回憶下程式運作的棧幀結構(見下圖):

從圖中可知,如果一個函數調用用了另外一個函數,那麼被調用者的棧幀則會被壓入棧頂被設定為“目前幀”,首先執行被調用者,執行完成後,調用者的棧幀被彈出程式棧,然後從“傳回位址”傳回到調用者的位址空間中。
于是猜想,如果在main函數中,調用了一個函數foo,則“目前幀”為foo的棧幀,這時,若調用vfork建立一個子程序,那麼根據vfork的語義,子程序不會完全複制父程序的位址空間,它會在父程序的位址空間中運作(這也是為什麼vfork能保證子程序先運作,而fork不能保證。因為vfork建立的子程序是與父程序共享位址空間,為了避免競争,是以就讓子程序先運作,而父程序後運作;而fork建立的子程序是父程序的副本,是以不會帶來競争問題,誰先誰後也就無所謂了),是以它共享的是“目前幀”的位址空間,是以當子程序傳回時,隻會改變foo的資料,而不會改變main棧幀中的資料。
下面就來寫個程式驗證一下:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int glob = 88; //a global var
void foo(int);
int main(int argc,char *arg[])
{
int var = 100; //a local var in main
foo(var);
if(printf("In main var:%d glob:%d pid:%d/n",var,glob,getpid())<0)
perror("main printf");
exit(0);
}
void foo(int var)
{
pid_t pid;
int loc = 66; //a local var in foo
printf("Before vfork/n");
if((pid = vfork())<0)
perror("vfork");
else if(pid == 0) //child process
{
loc++;
var++;
glob++;
printf("pid:%d/n",getpid());
exit(0);
}
/*parent process continues here*/
printf("In foo var:%d glob:%d loc:%d pid:%d/n",var,glob,loc,getpid());
}
運作此程式,得到結果為(見下圖):
果然,可以看到在foo和main中,程序号都是一樣的,也就說明foo和main在同一程序中。但是各個變量的值卻有差異:在foo傳回後mian函數中的局部變量var依然是初始值,并沒有增加,推其原因,就是因為子程序共享的是foo的棧幀資料,而非main函數的棧幀,是以自然也就不會改變main棧幀中的資料。(var 是值傳遞!)
書中正文中說:子程序不會完全複制父程序的位址空間,它會在父程序的位址空間中運作。是以可以進一步得出一個結論:vfork建立的子程序,共享的是父程序目前棧幀的位址空間。
(1)、堆棧幀到底是什麼
堆棧幀(stack frame)(或活動記錄(activation record))是一塊堆棧保留區域,用于存放被傳遞的實際參數、子程式的傳回值、局部變量以及被儲存的寄存器。
實際上堆棧幀就相當于子函數的緩存,當子函數使用的堆棧個數最大時,其所擁有的所有部分構成了這個函數的堆棧幀。
esp是棧指針,是cpu機制決定的,push、pop指令會自動調整esp的值;
ebp隻是存取某時刻的esp,這個時刻就是進入一個函數内後,cpu會将esp的值賦給ebp,此時就可以通過ebp對棧進行操作,比如擷取函數參數,局部變量等,實際上使用esp也可以;
既然使用esp也可以,那麼為什麼要設定ebp呢?
答案是為了友善程式員。
因為esp在函數運作時會不斷的變化,是以儲存一個一進入某個函數的esp到ebp中會友善程式員通路參數和局部變量,而且還友善調試器分析函數調用過程中的堆棧情況。前面說了,這個ebp不是必須要有的,你非要使用esp來通路函數參數和局部變量也是可行的,隻不過這樣會麻煩一些。
是以:
esp是棧頂指針,隻要有push,esp就減小,pop,esp就增加;
ebp是對于一個棧幀來說,即一次函數調用,ebp指向該函數參數,位址,局部變量壓棧後最後的esp,友善通路這個函數調用裡面的這些變量。