天天看點

Linux記憶體管理與C存儲空間

ELF檔案

在學習之前我們先看看ELF檔案。

ELF分為三種類型:.o 可重定位檔案(relocalble file),可執行檔案以及共享庫(shared library),三種格式基本上從結構上是一樣的,隻是具體到每一個結構不同。

下面我們就從整體上看看這3種格式從檔案内容上存儲的方式,spec上有張圖是比較經典的:如上圖: 

其實從檔案存儲的格式來說,上面的兩種view實際上是一樣的,Segment實際上就是由section組成的,将相應的一些section映射到一起就叫segment了,就是說segment是由0個或多個section組成的,實際上本質都是section。

在這裡我們首先來仔細了解一下section和segment的概念:

section就是相同或者相似資訊的集合,比如我們比較熟悉的.text .data  .bss section,.text是可執行指令的集合,.data是初始化後資料的集合,.bss是未初始化資料的集合。

實際上我們也可以将一個程式的所有内容都放在一起,就像dos一樣,但是将可執行程式分成多個section是很有好處的,比如說我們可以将.text section放在memory的隻讀空間内,将可變的.data section放在memory的可寫空間内。

從可執行檔案的角度來講,如果一個資料未被初始化那就不需要為其配置設定空間,是以.data和.bss一個重要的差別就是.bss并不占用可執行檔案的大小,它隻是記載需要多少空間來存儲這些未初始化資料,而不配置設定實際的空間。

可以通過指令 $ readelf -l a.out 檢視檔案的格式群組成。

size - list section sizes and total size是GNU Development Tools,列出目标檔案各個部分所占的位元組數,當不輸入目标檔案時,将會把a.out檔案作為預設輸入檔案名。

...$ size a.out
   text    data     bss     dec     hex filename
   9658     736       8   10402    28a2 a.out      

輸出各段說明:

  • text段:正文段位元組數大小
  • data段:包含靜态變量和已經初始化的全局變量的資料段位元組數大小
  • bss段:存放程式中未初始化的全局變量的位元組數大小(包含初始化為0的),BBS段屬于靜态記憶體配置設定
  • text段+data段+bss段=dec段(10進制),hex段為16進制表示

對輸出各段的更詳細的說明&C程式的存儲空間布局:

1.text段(正文段/代碼段),這是由CPU執行的機器指令部分,通常是可共享的,是以即使是頻繁執行的程式(如文本編輯器、C編譯器和shell等)在存儲器中也隻需要有一個副本。通常是指用來存放程式執行代碼的一塊記憶體區域。這部分區域的大小在程式運作前就已經确定,并且記憶體區域通常屬于隻讀, 某些架構也允許代碼段為可寫,即允許修改程式。在代碼段中,也有可能包含一些隻讀的常數變量,例如字元串常量等。

2.data段/資料段(初始化資料段),通常是指用來存放程式中已初始化的全局變量的一塊記憶體區域。資料段屬于靜态記憶體配置設定。

3.bss段(未初始化資料段),在程式開始執行之前,核心将此段中的資料初始化為0或空指針。由于BSS段隻儲存沒有值的變量,是以事實上它并不需要儲存這些變量的映像。運作時所需要的BSS段的大小記錄在目标檔案中,但BSS段并不占據目标檔案的任何空間。

4. 棧(Stack),又稱為堆棧,自動變量以及每次函數調用是所需儲存的資訊都存放在此段中。是使用者存放程式臨時建立的局部變量,也就是說我們函數括弧“{} ”中定義的變量(但不包括static 聲明的變量,static 意味着在資料段中存放變量)。除此以外,在函數被調用時,其參數也會被壓入發起調用的程序棧中,并且待到調用結束後,函數的傳回值也會被存放回棧中。由于棧的先進後出特點,是以棧特别友善用來儲存/ 恢複調用現場。從這個意義上講,我們可以把堆棧看成一個寄存、交換臨時資料的記憶體區。

5.堆(Heap),堆是用于存放程序運作中被動态配置設定的記憶體段,它的大小并不固定,可動态擴張或縮減。當程序調用malloc 等函數配置設定記憶體時,新配置設定的記憶體就被動态添加到堆上(堆被擴張);當利用free 等函數釋放記憶體時,被釋放的記憶體從堆中被剔除(堆被縮減)。

堆與棧的差別:

1. 堆棧空間配置設定

棧(作業系統):由作業系統自動配置設定釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于資料結構中的棧。

堆(作業系統): 一般由程式員配置設定釋放, 若程式員不釋放,程式結束時可能由OS回收,配置設定方式倒是類似于連結清單。

2. 堆棧緩存方式

棧使用的是一級緩存, 他們通常都是被調用時處于存儲空間中,調用完畢立即釋放。

堆則是存放在二級緩存中,生命周期由虛拟機的垃圾回收算法來決定(并不是一旦成為孤兒對象就能被回收)。是以調用這些對象的速度要相對來得低一些。

3. 堆棧資料結構差別

棧(資料結構):一種先進後出的資料結構。

堆(資料結構):堆可以被看成是一棵樹,如:堆排序。

不同代碼在可執行程式中的對應關系

1.一般情況下,一個可執行二進制程式(更确切的說,在Linux作業系統下為一個程序單元,在UC/OSII中被稱為任務)在存儲(沒有調入到記憶體運作)時擁有3個部分,分别是代碼段(text)、資料段(data)和BSS段。這3個部分一起組成了該可執行程式的檔案。

可執行二進制程式 = 代碼段(text)+資料段(data)+BSS段

2.而當程式被加載到記憶體單元時,則需要另外兩個域:堆域和棧域。圖1-1所示為可執行代碼存儲态和運作态的結構對照圖。一個正在運作的C程式占用的記憶體區域分為代碼段、初始化資料段、未初始化資料段(BSS)、堆、棧5個部分。

正在運作的C程式 = 代碼段+初始化資料段(data)+未初始化資料段(BSS)+堆+棧

3.在将應用程式加載到記憶體空間執行時,作業系統負責代碼段、資料段和BSS段的加載,并将在記憶體中為這些段配置設定空間。棧亦由作業系統配置設定和管理,而不需要程式員顯示地管理;堆段由程式員自己管理,即顯示地申請和釋放空間。

檔案布局在記憶體中的映射:

段可以友善地映射到連結器在運作時可以直接載入的對象中,載入器隻是取檔案中的每個段的映像,并直接将他們放入記憶體中。

下圖為可執行檔案中的段在記憶體中的布局,右邊是程序的位址空間。

注意虛拟位址空間的最低部分未被映射。也就是說,它位于程序的位址空間,但并未賦予實體位址,是以任何對它的引用都是非法的。

在考慮到共享庫時,程序的位址空間的樣子如下圖:

Linux記憶體管理與C存儲空間

參考:

https://blog.csdn.net/RHEL_admin/article/details/43055649

https://blog.csdn.net/qq_37924084/article/details/78154044

https://blog.csdn.net/zdy0_2004/article/details/42296109

https://blog.csdn.net/qq_28388835/article/details/80374518

https://blog.csdn.net/love_gaohz/article/details/41310597

C專家程式設計 6.2節