在考慮操作系統(tǒng)如何管理內(nèi)存之前掀淘,你先要知道:
- 程序是存在硬盤中的二進制文件烙荷,需要運行時會經(jīng)過CPU調(diào)度程序?qū)⑵鋍opy到內(nèi)存后成為進程
cpu可以直接訪問的大容量存儲只有內(nèi)存
內(nèi)存管理的目的在于快速訪問內(nèi)存钱磅,第二在于內(nèi)存的安全訪問(各個進程間的內(nèi)存不可以隨意互相訪問)
注:程序是如何形成的
源程序經(jīng)過編譯器變成一個個模塊,這些模塊和經(jīng)過 鏈接器式撼,經(jīng)過加載模塊之后,再和系統(tǒng)庫一起經(jīng)過加載器加載告匠,最后在和動態(tài)鏈接的系統(tǒng)庫鏈接成為二進制程序
這期間有
動態(tài)加載技術(shù):不是將所有模塊一起加載到內(nèi)存中戈抄,而是按需加載,一個子程序只有當被其他子程序調(diào)用時才會被加載后专。提高了空間利用率
動態(tài)鏈接于共享庫:不同程序程序中很多功能其實都是相似的划鸽,而這些重復(fù)的功能模塊加載到內(nèi)存中無疑要
占用很大一部分物理內(nèi)存,于是我們把他們抽調(diào)出來戚哎,形成專用的共享庫裸诽。這樣不僅節(jié)省空間,更利于程序的更新
對于1: 根據(jù)內(nèi)存管理方案型凳,調(diào)入內(nèi)存中的進程在執(zhí)行時可以再內(nèi)存和磁盤間移動丈冬,等待調(diào)入內(nèi)存的進程形成一個輸入隊列
編譯器將符號地址綁定在了可重新定位的的地址(比如本模塊開始第14字節(jié))。鏈接程序或者加載程序再將這些可重定位的地址綁定位絕對地址(如74104)甘畅。每次綁定都是一個地址空間到另一個的映射
對于2 :CPU只能訪問內(nèi)存殷蛇,那么CPU發(fā)出的指令肯定都是基于內(nèi)存來編址的实夹。再考慮到,編程時總不可能指定實際內(nèi)存地址粒梦,這樣在的程序經(jīng)不起環(huán)境改變的考驗。
為了解決這樣的問題荸实,計算機采用了 邏輯地址 和 * 物理地址* 的設(shè)計:
cpu 產(chǎn)生的地址通常稱為邏輯地址匀们,而內(nèi)存單元所看到的地址通常稱為物理地址
可以這么想:邏輯地址就是操作系統(tǒng)用戶編程時無視不同機器物理地址的不同所設(shè)置的屏蔽,編程時不必考慮實際的物理內(nèi)存分配是很happy的...... 實際使用時會有內(nèi)存管理單元來把邏輯內(nèi)存映射為物理內(nèi)存
對于3 使用高速緩存catch可以增加訪存速度准给,增加基地址和界限地址寄存器泄朴,一個可用的方案就是邏輯地址+基地址寄存器地址,如果這個值不大于界限地址寄存器里的值露氮,那么這就可作為物理地址祖灰,而基地址寄存器和界限地址寄存器是由操作系統(tǒng)初始化和指定的,用戶沒有權(quán)限去操作畔规。這樣就保證了第二點
只要我們使用內(nèi)存局扶,我們就必然會面臨一個問題,內(nèi)存不夠用HāH琛!
這一點是很頭疼的莫绣,尤其是對于大型單機游戲來說畴蒲,這就需要我們用到另一個技術(shù)交換
交換就是在某個進程在不被使用的時候被交換到備份存儲中(一般是磁盤)這樣的過程叫滾出.....
需要他的時候再將它加載回來..這樣的過程叫...滾入....而且這樣的交換總是在相同的物理空間上,通俗的講就是從哪滾出的对室,再從哪滾入...
這樣的交換其實對效率沒有什么良好的幫助模燥,現(xiàn)在UNIX系統(tǒng)就是只要當內(nèi)存被填滿了才會啟動交換,平時是不會的
內(nèi)存必須容納操作系統(tǒng)和用戶的一系列進程掩宜,那么怎么才能有效的分配這一濟源呢蔫骂?
這里將提到常用的幾個方法
連續(xù)內(nèi)存分配
多分區(qū)方法,將內(nèi)存分配成許多個大小相同的模塊锭亏,這種不考慮具體使用情況而異想天開的平均分配主義已經(jīng)不再使用了纠吴,因為這樣造成的浪費是巨大的..其一,進程的大小可能相差甚遠慧瘤,單位塊大小不好決定啊戴已。
可變分區(qū):操作系統(tǒng)有一個表,用于記錄哪些內(nèi)存可用和哪些內(nèi)存已被占用锅减√抢埽可用內(nèi)存稱為孔,進程進來之后怔匣,內(nèi)存管理單元會幫其找到合適的孔(注1)握联,如果孔還有剩余桦沉,那么這個孔..還是孔,變成了小孔而已...
當內(nèi)存被釋放時金闽,被釋放出的內(nèi)存重新成為孔..如果相鄰的內(nèi)存也是孔..那么纯露,這兩個孔就合體成為大孔..
注1:這樣的策略有最先適應(yīng),最佳適應(yīng)代芜,最差適應(yīng):
最先適應(yīng)埠褪,找到的一個適應(yīng)的孔
最佳適應(yīng);遍歷所有孔挤庇,找到滿足要求的最小孔
最差適應(yīng):遍歷所有孔钞速,分配最大孔
效率上看 最先》最佳》最差
這兩種方案都是常用的連續(xù)內(nèi)存分配方法,但是連續(xù)內(nèi)存分配必然會面臨一個問題:碎片問題嫡秕,每次分配內(nèi)存渴语,都有部分內(nèi)存沒有被真正使用,如多分區(qū)方法中一個分區(qū)大小為4KB 昆咽,而一個內(nèi)存是 4KB +1B這樣的狀態(tài)驾凶,這種情況下必須要給這個進程分配兩個內(nèi)存塊,但是第二個內(nèi)存塊明顯存在內(nèi)存的浪費潮改!這樣的碎片稱為內(nèi)部碎片 狭郑,而可變分區(qū)方案中,總有一些孔被很小汇在,沒有進程能被分配進去翰萨,這樣的內(nèi)存稱為:內(nèi)部碎片
那么這樣的碎片多嗎?...有這樣的一個原則--50%規(guī)則糕殉,有N的內(nèi)存空間將有0.5N的外部碎片..
解決的方法有
緊縮:把所有隨便集中亩鬼,但是這樣先不考慮 重定位的問題。這樣的開銷也是不可接受的
使用非連續(xù)的內(nèi)存分配
非連續(xù)內(nèi)存分配
分頁:將物理內(nèi)存分為大小固定的塊阿蝶,稱為幀( frame)邏輯地址分為同樣大小的塊陨亡,稱為頁(page)
由CPU生成的地址分為兩部分浪慌,頁號和頁偏移谣沸,頁號作為頁表的索引苛蒲,頁表內(nèi)存入每頁所在物理地址的基地址,基地址于頁偏移組合就成為了物理地址筑煮,這樣就可以避免外部碎片的問題(但內(nèi)存碎片仍然存在)
當程序需要執(zhí)行的時候辛蚊,他將檢查該進程的大小,進程的每一頁都需要一幀真仲,如果幀夠袋马,那么分配,并將幀號放入進程的頁表中
操作系統(tǒng)管理內(nèi)存秸应,所以它必須知道哪些幀已經(jīng)使用了虑凛,哪些沒被使用碑宴。這個信息存儲在幀表中
硬件支持:絕大多數(shù)操作系統(tǒng)為每一個進程維護一個頁表。頁表的指針與其他信息一起存入進程控制塊中桑谍。
訪存操作可能是最頻繁的操作了延柠,所以效率是十分關(guān)鍵的÷嗯基于效率考慮捕仔,頁表最好不要存在一般內(nèi)存中,這樣會使得一次訪存操作變成消耗兩次訪存的時間盈罐,效率就很低了。對此闪唆,我們可以采用專用高速寄存器來存儲頁表盅粪,對于頁表比較小的情況下這樣做是可以的。但現(xiàn)代操作系統(tǒng)頁表通常很大...之后采取的方法是將頁表存入內(nèi)存中悄蕾,然后將其基地址存入寄存器票顾,之后按照偏移地址來存...這樣會很慢...于是我們就采用了比較小但很快的專用緩存來存頁表 ,稱為轉(zhuǎn)換表緩沖區(qū)
TLB相當于一個哈希表帆调,用KEY(頁號)可以快速找到VALUE(幀號)奠骄,找到了之后訪存,找不到就去內(nèi)存中找真正的頁表番刊,找到之后用一定的算法(最近最少使用)替換到TLB中方便下次尋找
保護: 通過頁表中的保護位來做含鳞,確定進程對其是可讀還是可寫的
采用這樣的結(jié)構(gòu),還可以共享某些頁芹务,這樣又節(jié)省了內(nèi)存
- 頁表結(jié)構(gòu)
層次頁表
現(xiàn)代操作系統(tǒng)中的邏輯空間地址十分大.后果是頁表也十分大蝉绷,怎么樣在內(nèi)存中存儲頁表呢?一種方法是兩級分頁算法枣抱,將頁再分頁熔吗,對于64位的系統(tǒng)甚至可以再分頁哈希頁表
每個元素有三個域 1. 虛擬頁碼 2. 所映射的幀號 3. 指向鏈表中下一個元素的指針
虛擬地址中的虛擬頁號轉(zhuǎn)換到哈希表中,用虛擬頁號與鏈表中的每一個元素的第一個域相比較佳晶。如果匹配桅狠,那么相應(yīng)的幀號就用來形成物理地址;如果不匹配轿秧,那么就去對鏈表中下一個節(jié)點進行比較
* 反向頁表
分段
這是為了方便用戶角度來看的中跌,將一維的地址空間二維化,原先是這個程序的第XX字節(jié)淤刃,這樣的一維查找晒他,而分段使得 這個程序 XX段的 YY字節(jié) 成為二維查找
當然這樣的方式需要特別的結(jié)構(gòu)..段表的支持