一個進程最關鍵的特徵是它擁有獨立的虛擬地址空間,大小由CPU的位數決定。從建立進程到可執行文件裝載的過程以下:程序員
在 i386 的 Linux 下,建立虛擬地址空間實際上只是分配一個頁目錄,至關於提早劃分了一個目錄,但裏面並無實質性的內容。可執行文件被操做系統裝載正是經過這種頁映射的機制,至關於爲目錄填充信息。頁映射是虛擬存儲機制重要的一部分,內存中和磁盤中的數據按照「頁」爲單位劃分紅若干個頁,在 iOS 操做系統中,一頁的大小爲 16kb。markdown
當操做系統去內存讀取可執行文件時,若是內存並未加載該頁,它是個空白頁,程序會發生頁錯誤,系統將從物理內存中分配一個物理頁,而後將該「缺頁」從磁盤中讀取到內存中,再設置缺頁的虛擬頁和物理頁的映射關係。當系統捕捉到缺頁錯誤時,它應當知道當前所須要的頁在可執行文件中的哪一個位置,這就須要創建虛擬空間與可執行文件的映射關係。app
因爲可執行文件在裝載時其實是被映射到虛擬空間,因此可執行文件不少時候又被叫作映像文件(image)。函數
其中一個 Segment 映射到虛擬空間中後被稱做爲虛擬內存區域 VMA(Virtual Memory Area)。例如只讀權限的 Section 被集合到一塊兒組合成只讀的 Segment,它被映射到虛擬內存空間中多是一個頁或多個頁,它們被統稱爲一個 VMA。關於Segment 和 Section 細節參考程序員的自我修養(二)-- Mach-O裏面有什麼。post
這一步能夠簡單的認爲操做系統執行了一條跳轉指令,直接跳轉到可執行文件的入口地址。spa
上面的步驟只是經過可執行文件頭部的信息創建起可執行文件和進程虛擬內存之間的映射關係,並無將真正的指令和數據裝入到內存中。假設 CPU 打算開始從入口地址執行時發如今內存上是個空頁時,就認爲這是一個「頁錯誤」,而後操做系統會去第二部創建的映射關係中查詢這個空頁面所在的位置,計算出相應的頁面在可執行文件中的偏移,而後在物理內存中分配一個物理頁面,將進程中虛擬頁與分配的物理頁之間創建映射關係,而後把控制權還給進程,進程從剛纔頁錯誤的位置從新開始執行。操作系統
前段時間很火的二進制重排能夠節省啓動時間的緣由正是基於此,它將 app 啓動過程當中所須要的函數集中到一塊兒能夠減小啓動過程當中須要加載頁的數量,從而減小發生Page Fault 的次數。調試
操做系統分配內存空間並不是以段爲單位(例如前文提到的 text 段、data 段等),由於每一個段在映射的時候都應該是頁的整數倍,若是不是,多餘的部分將會佔用一個頁,每每一個可執行文件都有十幾個段甚至幾十個段(指 Section),會浪費大量內存空間,因此操做系統並不關心段的內容,而是關心段的權限,可讀、可寫、可執行等,這樣會將多個權限相同的段合併到一塊加載,這個合併後的段就是 Mach-O裏面有什麼 講到的 「Segment」。「Segment」 和 「Section」 從不一樣的角度劃分了同一個可執行文件,這在可執行文件中被稱爲不一樣的視圖,從 「Section」 角度來看是連接視圖,從 「Segment」 角度來看就是執行視圖。code
進程在執行的時候除了須要二進制文件外,還須要用到堆棧空間,事實上它們在進程的虛擬空間中也是以 VMA 形式存在的,一個進程中的堆和棧分別都對應着一個 VMA。一個進程上基本分爲以下幾種 VMA 區域:orm
有無映像文件指的是是否映射到了可執行文件中。
摘自《程序員的自我修養》167頁。
上圖能夠看出,這個可執行文件被從新的劃分爲了三個部分:有一些段被納入到了可讀可執行,它們被統一映射到 CODE VMA,另一部分是可讀可寫的,它們被映射到了DATA VMA,還有一部分在程序裝載時沒有被映射,它們是一些包含調試信息和字符串表等段,這些段在程序執行時沒有用,因此不須要被映射。另外虛擬內存空間還會有兩個多出來的 VMA,分別表明着堆棧。
可執行文件最終要被操做系統裝載運行,通常是經過虛擬內存頁映射機制完成的,在 iOS 操做系統中,頁的大小爲 16kb,就是說物理內存和虛擬內存之間創建映射關係,大小必須是 16kb 的整數倍。可是這種對齊方式在內存上會產生不少碎片,爲了解決這個問題操做系統內部讓各個段接壤的部分共享一個段(這個段指的是 Segment 非 Section)。從某種角度來看,整個可執行文件從文件的開始到結束被分紅了 16kb 大小的若干塊,每一個塊都被裝入到物理內存中,對於兩個段接壤的部分,系統會再映射一次,保證物理內存沒有碎片的狀況下,虛擬內存中的頁仍然只會包含一種段。
《程序員的自我修養》