可執行文件的裝載

《程序員的自我修養——鏈接、裝載與庫》讀書筆記

        可執行文件只有裝載到內存中以後才能被CPU執行。早期的程序裝載的基本過程就是把程序從外部存儲器讀到內存中的某個位置。隨着硬件MMU的誕生,多進程、多用戶、虛擬存儲的操作系統的出現,裝載過程變得複雜起來。程序,也就是可執行文件,是一個靜態的概念,裝載到內存中以後就成爲了進程,進程是一個動態的概念,正所謂”Process is a program in execution”。

1. 虛擬地址空間

        現在的電腦和操作系統都支持虛擬內存技術,由MMU(Memory Management Unit)進行虛擬地址和物理地址的相互轉換。每個進程擁有獨立的虛擬地址空間(Virtual Address Space),它的大小由計算機的硬件平臺決定。32位的CPU下,程序中能訪問的虛擬地址空間最大爲4G,但是實際上可用的計算機的物理內存空間可以通過PAE(Physical Address Extension),AWE(Address Windowing Extensions)等方式進行擴大。

2. 裝載方式

        程序執行時所需要的指令和數據必須在內存中才能正常運行,最簡單的方法就是把程序運行需要的指令和數據全部裝入內存中,這就是靜態裝載。但是這樣會浪費寶貴的內存,並且很多情況下程序所需要的內存大小大於物理內存。根據局部性原理,我們可以將程序最常用的部分放在內存中,不常用的放在磁盤裏,用的時候再裝入,這就是動態裝載基本原理。

        覆蓋裝入(Overlay)和頁映射(Paging)是兩種典型的動態裝載的方法。覆蓋裝入在虛擬內存技術發明之前使用比較廣泛。程序員在編寫程序時將程序分割成若干塊,然後編寫一個小小的輔助代碼,也就是覆蓋管理器(Overlay Manager)來管理這些模塊何時應該駐留在內存,何時應該被替換掉。程序員需要將這些模塊依據調用關係組織成樹狀結構,以便於確定何時覆蓋和裝入某個模塊。跨模塊的調用都要經過覆蓋管理器,以確保被調用的模塊都在內存中,如果不在還要從磁盤讀取裝入,速度比較慢。頁映射是虛擬內存技術的一部分,它將程序和內存以 “頁”(Page)爲單位進行劃分,裝載的單位就是頁。將需要用到的頁從磁盤載入內存中,如果物理內存已被用完,就要由頁替換算法決定被替換的頁。幾乎目前所有的主流操作系統都採用的這種方式裝載可執行文件。

3. ELF文件的鏈接視圖和執行視圖

        一個ELF可執行文件往往由很多個節構成,在操作系統裝載可執行文件時,如果把每個節映射到一個頁,由於它們大小不一樣,肯定會產生內存浪費。對於操作系統來說,實際上在裝載時它主要關心頁的權限問題,即可讀、可寫或可執行。而ELF文件中的節的權限往往只有爲數不多的幾種組合:

  • 可讀,可執行,如代碼節
  • 可讀,可寫,如數據節和.bss節
  • 只讀,如只讀數據節

那麼對於權限相同的節,完全可以把它們合併到一起進行映射。ELF文件引入了一個概念叫做(Segment),一個段包括一個或多個屬性類似的(Section)。裝載的時候就把一個段當作一個整體進行映射。這樣可以明顯的減少頁面內部的碎片,節省內存空間。從鏈接的角度看,ELF文件是按照節存儲的,從裝載的角度看,ELF文件又可以按照段進行劃分。從節的角度來看ELF文件就是鏈接視圖(Linking View),從段的角度來看就是執行視圖(Execution View)。段的概念實際上是從裝載的角度重新劃分了ELF的各個節。在將目標文件鏈接成可執行文件時,鏈接器會盡量把權限屬性相同的節分配在同一空間。

        使用readelf -l指令可以查看ELF文件的段的信息。正如描述節的屬性的結構叫做節表,描述段的屬性的結構叫做程序頭表(Program Header Table),它描述了ELF文件如何被操作系統映射到進程的虛擬內存空間。由於ELF目標文件不需要被裝載,它沒有程序頭表,而ELF可執行文件和共享文件都有。

4. 段地址對齊

        裝載的過程一般是通過虛擬內存的頁映射機制完成的。在映射的過程中,頁是最小單位。對於Intel 80x86系列處理器來說,默認的頁大小爲4096字節。也就是說如果我們要將一段物理內存和進程的虛擬地址空間之間建立映射關係,這段內存空間的長度必須是4096的整數倍,並且這段空間在物理內存和進程虛擬地址空間中的起始地址必須是4096的整數倍。那麼可執行文件就要儘量優化自己的空間和地址安排,以節省空間。

        最簡單的做法就是每個段分開映射,長度不足一個頁的部分也佔據一個頁,也就是說段的首地址對齊到了4096的整數倍。但是這樣會造成很多內部碎片。爲了解決這種問題,有些UNIX系統採用了一種取巧的方法,就是讓那些各個段接壤的部分共享一個物理頁面,然後將該物理頁面分別映射兩次。從某種角度看,好像是整個ELF文件從文件開頭到某個點結束,被邏輯上分成了以4096字節爲單位的若干個塊,每個塊都被裝載到物理內存中去。那些包含了多個段的物理內存中的塊,將會被映射到虛擬地址空間中多次。當然不同的段還有自己的不同對齊屬性,這一點在爲ELF文件分配虛擬內存空間時也要考慮。

5. 總結

        這裏簡單的描述了ELF文件的裝載過程,但是實際上這裏我們假設了程序都是靜態鏈接的,因此只有一個可執行模塊。有了這裏的基本概念,就可以更進一步的去了解動態鏈接和動態鏈接的程序的裝載過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章