Unix系統編程()進程內存布局
每個進程所分配的內存由很多部分組成,通常稱之為"段(segment)"。
文本段包含了進程運行的程序機器語言指令。文本段具有只讀屬性,以防止進程通過錯誤指針意外修改自身指令。
因為多個進程可同時運行同一程序,所以又將文本段設為可共享,這樣,一份程序代碼的拷貝可以映射到所有這些進程的虛擬地址空間中。
初始化數據段包含顯式初始化的全局變量和靜態變量。當程序加載到內存時,從可執行文件中讀取這些變量的值。
未初始化數據段包括了未進行顯式初始化的全局變量和靜態變量。
程序啟動之前,系統將本段內所有內存初始化為0。
出於歷史原因,此段常被稱為BSS段,這源於老版本的匯編語言助記符"block started by symbol"。
將經過初始化的全局變量和靜態變量與未經初始化的全局變量和靜態變量分開存放,其主要原因在於程序在磁盤上存儲時,沒有必要為未經初始化的變量分配存儲空間。
相反,可執行文件只需記錄未初始化數據段的位置及所需大小,直到運行時再由程序加載器來分配這一空間。
棧(stack)是一個動態增長和收縮的段,由棧幀(stack frames)組成。
系統會為每個當前調用的函數分配一個棧幀。
棧幀中存儲了函數的局部變量(所謂自動變量)、實參和返回值。
堆(heap)是可在運行時(為變量)動態進行內存分配的一塊區域。
堆頂端稱作program break。
對於初始化和未初始化的數據段而言,不太常用、但表述更清晰的稱謂分別是用戶初始化數據段(user-initialized data segment)和零初始化數據段(zero-initialized data segment)。
size命令可以顯示二進制可執行文件的文本段、初始化數據段、非初始化數據段(bss)的段大小。
這裏的的"段"不應該與一些硬件體系架構,比如x86-32中使用的硬件分段(segmentation)相混淆。
相反,這裏的段指的是UNIX系統中進程虛擬內存的邏輯劃分。有時,會用術語"區"(section)來代替段,因為在風行的可執行文件格式(ELF)規範中,采用的術語與"區"更趨一致。
下面的程序展示了不同類型的C語言變量,並以註釋說明了每種變量分屬於哪個段。
這些說明正確的前提是假定使用了非優化的編譯器,且在應用程序二進制接口(ABI)中,是通過棧來傳遞所有參數的。實際上,優化編譯器會將頻繁使用的變量分配於寄存器中,或者索性地徹底將變量刪除。
此外,一些ABI需要通過寄存器,而不是棧,來傳遞函數實參和結果。
但是這個例子只是意在展示C語言變量和進程各段之間的映射關系。
應用程序二進制接口(ABI)是一套規則,規定了二進制可執行文件 在運行時應該如何與某些服務(諸如內核或函數庫所提供的服務)交換信息。ABI特別規定了使用哪些寄存器和棧地址來交換信息以及所交換值的含義,一旦針對某個特定的ABI進行了編譯,其二進制可執行文件應能在ABI相同的任何系統上運行。與之相反,標準化的API僅能通過編譯源代碼來保證應用程序的可移植性。
雖然SUSv3沒有規定,但在大多數UNIX實現(包括Linux)中C語言編程環境提供了3個全局符號(sysmbol):etext,edata和end,可在程序內使用這些符號以獲取相應程序文本段、初始化數據和非初始化數據段結尾處下一字節的地址。
使用這些符號,必須顯式聲明如下:
extern char etext, edata, end;
下圖展示了各種內存段在x86-32體系結構中的布局,該圖的頂部標記為argv、environ的空間用來存儲程序命令行實參(通過C語言中的main函數的argv參數獲得)和進程環境列表(稍後討論),圖中十六進制的地址會因為內核配置和程序鏈接選項差異而有所不同。
圖中標灰的區域表示這些範圍在進程虛擬地址空間中不可用,也就是說,沒有為這些區域創建頁表(page table)。
Unix系統編程()進程內存布局