MMU(Memory Management Unit):內(nèi)存管理單元,它將虛擬地址轉換位物理地址。
程序和進程的區(qū)別:程序是靜態(tài)的,進程是動態(tài)的。進程是程序運行時的一個過程,程序是預先編譯好的指令和數(shù)據(jù)的集合。
一般來說C語言中的指針大小與虛擬地址空間的位數(shù)相同,例如,Win32平臺下虛擬地址空間為32位,所以指針大小也是32位。
程序都是運行在虛擬地址空間中的。
一般來講,內(nèi)存空間被分成兩部分——系統(tǒng)區(qū)和用戶區(qū),我們平時所能操作的都是用戶區(qū),系統(tǒng)區(qū)是被禁止的。
32位的CPU最大支持4G內(nèi)存,這實際上說的是虛擬地址空間大小,但實際上你可以將物理地址空間擴展到4G以上。所謂的32位的操作系統(tǒng)也就是支持最大虛擬地址空間為232=4G的操作系統(tǒng)。
程序在運行時所需要的內(nèi)存往往大于物理內(nèi)存所能提供的。
程序的局部性原理就是說程序在運行時只有一部分需要放進內(nèi)存。
動態(tài)裝載的基本原理是程序的局部性原理,它的思想是程序用到哪個模塊就把哪個模塊放進內(nèi)存。
動態(tài)裝載的兩種典型的方法是覆蓋裝入和頁映射。
程序員手工編寫用于控制模塊動態(tài)裝載的管理代碼,它被稱為覆蓋管理器。
它的思想是各模塊輪流共用內(nèi)存的一片區(qū)域,程序員手動控制哪個模塊在某一時刻該進入該區(qū)域。
模塊之間往往存在依賴的關系,這使得獨一模塊無法完成功能,這種依賴關系使各模塊間形成了樹狀結構。加載一個模塊的時候也必須把存在依賴關系的模塊,換句話說就是該模塊的組成模塊,也一同加載進內(nèi)存。這也說明你不要把非組成模塊加載進內(nèi)存。
模塊覆蓋慢,因為它需要把模塊在內(nèi)存和硬盤之間換進換出,而且還需要經(jīng)過覆蓋管理器。
它是一種時間換空間的解決方案。
它把磁盤中所有的數(shù)據(jù)和指令以頁為單位進行劃分,裝載用的也是頁。
比如說現(xiàn)在頁的大小是212=4096byte,內(nèi)存大小為512M=229byte,所以在某一時刻該內(nèi)存可容納最多229/212=217個頁面,即131072頁。
6.3從操作系統(tǒng)的角度看可執(zhí)行文件的裝載
虛擬存儲的發(fā)展使得頁映射機制成為可能,如果是物理地址直接裝載的話,那么還需要重定位,好在現(xiàn)階段操作系統(tǒng)已經(jīng)接管了這一工作。
一個進程區(qū)別于其他進程的特征就是它擁有它自己獨立的虛擬地址空間。
創(chuàng)建進程通常需要做三件事:
1、創(chuàng)建獨立的虛擬地址空間。
2、讀取可執(zhí)行文件,建立虛擬地址空間與可執(zhí)行文件的映射關系。
3、將CPU的指令寄存器設置為程序執(zhí)行入口,開始執(zhí)行。
創(chuàng)建虛擬空間并不是創(chuàng)建空間而是創(chuàng)建應設函數(shù)所需要的數(shù)據(jù)結構,例如Linux只創(chuàng)建一個也目錄就完事了。它的作用是創(chuàng)建虛擬地址空間與物理地址空間的映射關系。
當程序執(zhí)行到某一處時發(fā)現(xiàn)它所需要的指令和數(shù)據(jù)不在內(nèi)存中,這時候就產(chǎn)生了缺頁中斷,操作系統(tǒng)捕獲到該中斷就磁盤中的可執(zhí)行文件中的該頁換入到內(nèi)存中。但是問題是該把可執(zhí)行文件中的哪一頁還進內(nèi)存呢?解決方案是把虛擬地址空間和可執(zhí)行文件之間建立映射關系。這種映射關系保存在操作系統(tǒng)中的一個數(shù)據(jù)結構中,在Linux下叫VMA(Virtual
Memory Area),在Windows下叫VS(Virtual
Section)。這里邊存儲著ELF的虛擬地址空間的起始和終止地址、段名、段的屬性等。
然后操作系統(tǒng)控制CPU將控制權轉交給程序,具體來講是跳轉到程序入口,自此程序開始執(zhí)行。
ELF文件中的段大小是頁的整數(shù)倍,頁比段小。
操作系統(tǒng)關心的只是段的權限,即,只讀、只寫、讀寫。
把相同權限的段合并到一起進行映射,可以頁面碎片,節(jié)省內(nèi)存空間。ELF就是這樣做的。從虛擬存儲的角度講ELF分成若干segment,每個segment又分成若干section。
ELF被劃分成3個segment,它們是可讀可執(zhí)行的VMA0、可讀可寫VMA1和調(diào)試信息+字符串表。
section是ELF的鏈接視圖而segment是ELF的執(zhí)行視圖。
在裝載時指的是ELF的執(zhí)行視圖,即,segment。
segment的信息保存在ELF文件中的程序頭表中。
由于ELF目標文件不會被裝載,因此目標文件沒有程序頭表,剩下的像可執(zhí)行文件和共享庫文件都是由程序頭表的。和前面講的結構一樣,程序頭表也是個結構體,它的名字叫Elf32_Phdr。
操作系統(tǒng)還通過VMA對進程的地址空間進行管理,比如堆和棧。
AVMA(Anonymous Virtual Memory Area)是沒有映射到文件中的VMA。幾乎每個進程都有3個AVMA,它們是堆、棧和vdso。其中,vdso是進程與內(nèi)核通信的模塊。
P192~P193之間對進程與VMA之間的關系做了簡要的總結概括。
從圖6-9可以看出內(nèi)存虛擬地址空間中從低地址到高地址依次分布著code、data、heap和stack等幾個區(qū)域。
堆主管動態(tài)空間分配,它最多能分配的空間受操作系統(tǒng)版本、程序本身大小、動態(tài)/共享庫數(shù)量、程序棧數(shù)量大小等因素影響。
這一點決定了虛擬起址必須是頁大小的整數(shù)倍,于是這就存在一個優(yōu)化的問題。即,如何優(yōu)化才能使空間利用率提高。
當一個頁面的實際大小不足一頁但卻非要占去一個頁空間的話會浪費很大的頁面。
為此UNIX提出了一個解決方案。它讓兩個段之間相鄰的部分共享一塊物理內(nèi)存區(qū)域,然后再把這塊區(qū)域映射到各段各自的虛擬地址空間中去。

比如seg0和seg1是相鄰的,seg0獨立的部分被映射在5Page處,seg1和seg0相鄰的地方被映射在4Page處,seg1獨立的部分被映射在3Page處。由于4Page處是共享的部分,所以它要被映射到seg0和seg1各自的虛擬空間中去。即,seg0的虛擬地址空間=seg0獨立的虛擬地址空間+seg0與seg1相鄰的虛擬地址空間。而seg1的虛擬地址空間=seg1獨立的虛擬地址空間+ seg0與seg1相鄰的虛擬地址空間。
從這張圖也可以看出可執(zhí)行文件的各個段是先被映射成物理地址空間,然后再被映射成虛擬地址空間的。
它是一種系統(tǒng)提供的程序運行的環(huán)境,進程棧中保存著系統(tǒng)環(huán)境變量和運行參數(shù),它也同樣是在虛擬地址空間中。
圖6-12展示了Linux進程初始化棧的布局。
ESP(Extended stack pointer):棧頂指針。
bash:unix下的shell。
bash調(diào)用fork()創(chuàng)建新進程,新進程再調(diào)用execve()系統(tǒng)調(diào)用執(zhí)行ELF文件。
在內(nèi)核中execve()的入口是sys_execve(),sys_execve()在進行一些參數(shù)檢查復制后調(diào)用do_ execve()。
do_ execve()會檢查可執(zhí)行文件的前128字節(jié)的內(nèi)容以判斷該文件的格式。每種可執(zhí)行文件的前4個字節(jié)被稱為魔數(shù),每種可執(zhí)行文件的魔術都是不同的。
然后,do_
execve()再調(diào)用search_binary_handle()查找和匹配相應的可執(zhí)行文件的裝載處理過程,這一過程是由search_binary_handle()檢查魔數(shù)來完成的。
裝過處理過程所要做的事情可參見P199所述內(nèi)容。
以上過程完成以后EIP(Extend
Instruction Pointer)寄存器就指向了ELF程序的入口,于是ELF程序開始執(zhí)行。
PE文件中的段的起始地址和長度都是頁的整數(shù)倍。
PE文件中段的數(shù)量比ELF文件中的段數(shù)量少很多,一般只有代碼段、數(shù)據(jù)段、只讀數(shù)據(jù)段和BSS等幾個。
RVA(Relative Virtual Address):它是相對于PE文件裝載基址的偏移地址。
每個PE文件都會被裝載到某一特定地址,改地址被稱為目標地址,也就是基地址,這個基地址不是固定的。
PE文件的裝載過程可參見P200~P201所述部分。
與PE文件裝載有關的信息都在PE擴展頭(PE Optional Header)和段表中。