MMU(Memory Management Unit):內(nèi)存管理單元惨远,它將虛擬地址轉(zhuǎn)換位物理地址辫红。
程序和進(jìn)程的區(qū)別:程序是靜態(tài)的车海,進(jìn)程是動(dòng)態(tài)的。進(jìn)程是程序運(yùn)行時(shí)的一個(gè)過程彬祖,程序是預(yù)先編譯好的指令和數(shù)據(jù)的集合豁跑。
一般來說C語言中的指針大小與虛擬地址空間的位數(shù)相同慧瘤,例如宜雀,Win32平臺(tái)下虛擬地址空間為32位,所以指針大小也是32位埋凯。
程序都是運(yùn)行在虛擬地址空間中的点楼。
一般來講,內(nèi)存空間被分成兩部分——系統(tǒng)區(qū)和用戶區(qū)白对,我們平時(shí)所能操作的都是用戶區(qū)掠廓,系統(tǒng)區(qū)是被禁止的。
32位的CPU最大支持4G內(nèi)存甩恼,這實(shí)際上說的是虛擬地址空間大小蟀瞧,但實(shí)際上你可以將物理地址空間擴(kuò)展到4G以上沉颂。所謂的32位的操作系統(tǒng)也就是支持最大虛擬地址空間為232=4G的操作系統(tǒng)。
程序在運(yùn)行時(shí)所需要的內(nèi)存往往大于物理內(nèi)存所能提供的悦污。
程序的局部性原理就是說程序在運(yùn)行時(shí)只有一部分需要放進(jìn)內(nèi)存铸屉。
動(dòng)態(tài)裝載的基本原理是程序的局部性原理,它的思想是程序用到哪個(gè)模塊就把哪個(gè)模塊放進(jìn)內(nèi)存切端。
動(dòng)態(tài)裝載的兩種典型的方法是覆蓋裝入和頁映射彻坛。
程序員手工編寫用于控制模塊動(dòng)態(tài)裝載的管理代碼,它被稱為覆蓋管理器踏枣。
它的思想是各模塊輪流共用內(nèi)存的一片區(qū)域昌屉,程序員手動(dòng)控制哪個(gè)模塊在某一時(shí)刻該進(jìn)入該區(qū)域。
模塊之間往往存在依賴的關(guān)系茵瀑,這使得獨(dú)一模塊無法完成功能间驮,這種依賴關(guān)系使各模塊間形成了樹狀結(jié)構(gòu)。加載一個(gè)模塊的時(shí)候也必須把存在依賴關(guān)系的模塊马昨,換句話說就是該模塊的組成模塊蜻牢,也一同加載進(jìn)內(nèi)存。這也說明你不要把非組成模塊加載進(jìn)內(nèi)存偏陪。
模塊覆蓋慢,因?yàn)樗枰涯K在內(nèi)存和硬盤之間換進(jìn)換出煮嫌,而且還需要經(jīng)過覆蓋管理器笛谦。
它是一種時(shí)間換空間的解決方案。
它把磁盤中所有的數(shù)據(jù)和指令以頁為單位進(jìn)行劃分昌阿,裝載用的也是頁饥脑。
比如說現(xiàn)在頁的大小是212=4096byte,內(nèi)存大小為512M=229byte懦冰,所以在某一時(shí)刻該內(nèi)存可容納最多229/212=217個(gè)頁面灶轰,即131072頁。
6.3從操作系統(tǒng)的角度看可執(zhí)行文件的裝載
虛擬存儲(chǔ)的發(fā)展使得頁映射機(jī)制成為可能刷钢,如果是物理地址直接裝載的話笋颤,那么還需要重定位,好在現(xiàn)階段操作系統(tǒng)已經(jīng)接管了這一工作内地。
一個(gè)進(jìn)程區(qū)別于其他進(jìn)程的特征就是它擁有它自己獨(dú)立的虛擬地址空間伴澄。
創(chuàng)建進(jìn)程通常需要做三件事:
1、創(chuàng)建獨(dú)立的虛擬地址空間阱缓。
2非凌、讀取可執(zhí)行文件,建立虛擬地址空間與可執(zhí)行文件的映射關(guān)系荆针。
3敞嗡、將CPU的指令寄存器設(shè)置為程序執(zhí)行入口颁糟,開始執(zhí)行。
創(chuàng)建虛擬空間并不是創(chuàng)建空間而是創(chuàng)建應(yīng)設(shè)函數(shù)所需要的數(shù)據(jù)結(jié)構(gòu)喉悴,例如Linux只創(chuàng)建一個(gè)也目錄就完事了棱貌。它的作用是創(chuàng)建虛擬地址空間與物理地址空間的映射關(guān)系。
當(dāng)程序執(zhí)行到某一處時(shí)發(fā)現(xiàn)它所需要的指令和數(shù)據(jù)不在內(nèi)存中粥惧,這時(shí)候就產(chǎn)生了缺頁中斷键畴,操作系統(tǒng)捕獲到該中斷就磁盤中的可執(zhí)行文件中的該頁換入到內(nèi)存中。但是問題是該把可執(zhí)行文件中的哪一頁還進(jìn)內(nèi)存呢突雪?解決方案是把虛擬地址空間和可執(zhí)行文件之間建立映射關(guān)系起惕。這種映射關(guān)系保存在操作系統(tǒng)中的一個(gè)數(shù)據(jù)結(jié)構(gòu)中,在Linux下叫VMA(Virtual
Memory Area)咏删,在Windows下叫VS(Virtual
Section)惹想。這里邊存儲(chǔ)著ELF的虛擬地址空間的起始和終止地址、段名督函、段的屬性等嘀粱。
然后操作系統(tǒng)控制CPU將控制權(quán)轉(zhuǎn)交給程序,具體來講是跳轉(zhuǎn)到程序入口辰狡,自此程序開始執(zhí)行锋叨。
ELF文件中的段大小是頁的整數(shù)倍,頁比段小宛篇。
操作系統(tǒng)關(guān)心的只是段的權(quán)限娃磺,即,只讀叫倍、只寫偷卧、讀寫。
把相同權(quán)限的段合并到一起進(jìn)行映射吆倦,可以頁面碎片听诸,節(jié)省內(nèi)存空間。ELF就是這樣做的蚕泽。從虛擬存儲(chǔ)的角度講ELF分成若干segment晌梨,每個(gè)segment又分成若干section。
ELF被劃分成3個(gè)segment须妻,它們是可讀可執(zhí)行的VMA0派任、可讀可寫VMA1和調(diào)試信息+字符串表。
section是ELF的鏈接視圖而segment是ELF的執(zhí)行視圖璧南。
在裝載時(shí)指的是ELF的執(zhí)行視圖掌逛,即,segment司倚。
segment的信息保存在ELF文件中的程序頭表中豆混。
由于ELF目標(biāo)文件不會(huì)被裝載篓像,因此目標(biāo)文件沒有程序頭表,剩下的像可執(zhí)行文件和共享庫文件都是由程序頭表的皿伺。和前面講的結(jié)構(gòu)一樣员辩,程序頭表也是個(gè)結(jié)構(gòu)體,它的名字叫Elf32_Phdr鸵鸥。
操作系統(tǒng)還通過VMA對(duì)進(jìn)程的地址空間進(jìn)行管理奠滑,比如堆和棧。
AVMA(Anonymous Virtual Memory Area)是沒有映射到文件中的VMA妒穴。幾乎每個(gè)進(jìn)程都有3個(gè)AVMA宋税,它們是堆、棧和vdso讼油。其中杰赛,vdso是進(jìn)程與內(nèi)核通信的模塊。
P192~P193之間對(duì)進(jìn)程與VMA之間的關(guān)系做了簡(jiǎn)要的總結(jié)概括矮台。
從圖6-9可以看出內(nèi)存虛擬地址空間中從低地址到高地址依次分布著code乏屯、data、heap和stack等幾個(gè)區(qū)域瘦赫。
堆主管動(dòng)態(tài)空間分配辰晕,它最多能分配的空間受操作系統(tǒng)版本、程序本身大小确虱、動(dòng)態(tài)/共享庫數(shù)量含友、程序棧數(shù)量大小等因素影響。
這一點(diǎn)決定了虛擬起址必須是頁大小的整數(shù)倍蝉娜,于是這就存在一個(gè)優(yōu)化的問題。即扎唾,如何優(yōu)化才能使空間利用率提高召川。
當(dāng)一個(gè)頁面的實(shí)際大小不足一頁但卻非要占去一個(gè)頁空間的話會(huì)浪費(fèi)很大的頁面。
為此UNIX提出了一個(gè)解決方案胸遇。它讓兩個(gè)段之間相鄰的部分共享一塊物理內(nèi)存區(qū)域荧呐,然后再把這塊區(qū)域映射到各段各自的虛擬地址空間中去。
比如seg0和seg1是相鄰的纸镊,seg0獨(dú)立的部分被映射在5Page處倍阐,seg1和seg0相鄰的地方被映射在4Page處,seg1獨(dú)立的部分被映射在3Page處逗威。由于4Page處是共享的部分峰搪,所以它要被映射到seg0和seg1各自的虛擬空間中去。即凯旭,seg0的虛擬地址空間=seg0獨(dú)立的虛擬地址空間+seg0與seg1相鄰的虛擬地址空間概耻。而seg1的虛擬地址空間=seg1獨(dú)立的虛擬地址空間+ seg0與seg1相鄰的虛擬地址空間使套。
從這張圖也可以看出可執(zhí)行文件的各個(gè)段是先被映射成物理地址空間,然后再被映射成虛擬地址空間的鞠柄。
它是一種系統(tǒng)提供的程序運(yùn)行的環(huán)境侦高,進(jìn)程棧中保存著系統(tǒng)環(huán)境變量和運(yùn)行參數(shù),它也同樣是在虛擬地址空間中厌杜。
圖6-12展示了Linux進(jìn)程初始化棧的布局奉呛。
ESP(Extended stack pointer):棧頂指針。
bash:unix下的shell夯尽。
bash調(diào)用fork()創(chuàng)建新進(jìn)程瞧壮,新進(jìn)程再調(diào)用execve()系統(tǒng)調(diào)用執(zhí)行ELF文件。
在內(nèi)核中execve()的入口是sys_execve()呐萌,sys_execve()在進(jìn)行一些參數(shù)檢查復(fù)制后調(diào)用do_ execve()馁痴。
do_ execve()會(huì)檢查可執(zhí)行文件的前128字節(jié)的內(nèi)容以判斷該文件的格式。每種可執(zhí)行文件的前4個(gè)字節(jié)被稱為魔數(shù)肺孤,每種可執(zhí)行文件的魔術(shù)都是不同的罗晕。
然后,do_
execve()再調(diào)用search_binary_handle()查找和匹配相應(yīng)的可執(zhí)行文件的裝載處理過程赠堵,這一過程是由search_binary_handle()檢查魔數(shù)來完成的小渊。
裝過處理過程所要做的事情可參見P199所述內(nèi)容。
以上過程完成以后EIP(Extend
Instruction Pointer)寄存器就指向了ELF程序的入口茫叭,于是ELF程序開始執(zhí)行酬屉。
PE文件中的段的起始地址和長(zhǎng)度都是頁的整數(shù)倍。
PE文件中段的數(shù)量比ELF文件中的段數(shù)量少很多揍愁,一般只有代碼段呐萨、數(shù)據(jù)段、只讀數(shù)據(jù)段和BSS等幾個(gè)莽囤。
RVA(Relative Virtual Address):它是相對(duì)于PE文件裝載基址的偏移地址谬擦。
每個(gè)PE文件都會(huì)被裝載到某一特定地址,改地址被稱為目標(biāo)地址朽缎,也就是基地址惨远,這個(gè)基地址不是固定的。
PE文件的裝載過程可參見P200~P201所述部分话肖。
與PE文件裝載有關(guān)的信息都在PE擴(kuò)展頭(PE Optional Header)和段表中北秽。