任何新技術都是在一點一點的積累中成熟并呈現在世人的面前,就像猿人程序成人也不是一簇而就的,而是在漫長的歲月中一點一點的進化與完善。還比如現代的吸塵器,目前發明吸塵器的那個人隻是用了一台風扇的電機和葉片以及一個布口袋制成的,後來也是一點一點的改善成為現在的使用友善的吸塵器。
作業系統的記憶體管理也是同樣的道理,起初的作業系統并沒有現代作業系統的虛拟記憶體管理機制,而是指令直接通路實體記憶體,并且在記憶體中同一時間隻能運作一個程序,因為如果是多個程序,程序A中的指令有可能會修改程序B的記憶體位址,造成程序B崩潰或錯誤的計算結果。
後來開始支援多任務也就是同時可以運作多個程序,為每個程序都配置設定一塊獨立的記憶體位址并寫有保護位,其它程序指令是無法通路目前程序的位址空間的,并且利用上了cpu的基址寄存器和界限寄存器來表達目前程序的實體位址通路開始端和範圍,開始支援程式指令以相對記憶體位址運作。支援多任務固然是好,讓cpu的使用率大大提高,提高了工作效率,同時讓程式以相對位址通路更加解放了程式員的工作。可是多程序帶來的問題就是記憶體空間大小的局限性,就算有1G記憶體空間,如果我的程式加上正文加上資料算100M,也就最多能運作10個,這還不算作業系統占用的,是以人們後來就發明了交換技術,當記憶體無法同時容納多個程式是,就根據政策比如很久以後才會運作的程序,就将程式的正文還有堆棧以及程式計數器這些程序映像存儲到硬碟上,當運作時再次裝入記憶體,恢複寄存器和程式計數器,這樣雖然解決了記憶體空間有限的問題,但是也帶來了新的問題,程式資料在記憶體與硬碟上的往複交換帶來了大量的io讀寫磁盤操作,大大降低了程式的運作效率。
經過以上的兩個階段的問題與整理,人們終于走上了虛拟記憶體的道路,現代的大多作業系統記憶體管理都采用分頁和分段的記憶體管理機制,是以下面我們詳細的說一下機制。
所謂虛拟記憶體,簡單的來說就是程式指令通路的記憶體位址不是真的記憶體的實體位址,而是需要一個轉換過程才能通路到實體位址,讀取或存取資料。
在作業系統中讓運作的每個程式擁有自己的空間位址,這個位址被分成多個塊,每個塊被稱為一頁或“頁面”,每一頁有連續的虛拟記憶體位址,每一個頁都對應實體記憶體中的一塊,這種塊被稱為“頁框”,頁框和頁面同等大小,頁面與頁框的對應關系被稱為頁表存儲在程序的位址空間中。當指令通路一個記憶體位址是,經過硬體(cpu中的mmu,負責虛拟位址轉換,每一個程式運作時,就會将頁表資訊設定mmu,讓mmu知道頁面與頁框的關系,進而快速計算出實際實體位址)快速計算出實際的實體位址,讀取位址擷取資料,如果發現通路的虛拟位址不存在,則有作業系統負責将缺失的頁面由磁盤裝如記憶體,并更新頁表中頁面與頁框的對應關系。
頁表中存儲着頁面與頁框的映射關系,當cpu發出一條通路記憶體的指令,mmu會根據記憶體位址的前幾位在頁表中找出對應的頁面與頁框的記錄,進而知道了頁框的位址,采用位址的後幾位作為實體位址區間頁框的偏移量,這樣找到了頁框并且知道了該頁框上的位置,就知道了通路的真實的記憶體位址。
頁表中還儲存了一些保護位。第一個是狀态位,就是頁面是否在記憶體中,如果存在則根據頁框與頁面的對應關系找出實體位址,如果不存在,則引起缺頁中斷有作業系統将缺失頁面裝入記憶體并更新頁表。第二則是保護位,記錄隻讀或可讀寫,第三是記錄頁面修改情況,以便在将頁框中的資訊置換到硬碟時寫入磁盤還是直接丢棄。第四個是禁止高速緩存位,如果作業系統一直在讀取軟碟上的資料,保證cpu讀取到的資料隻是最新的是很有必要的,是以要設定這個位。
當頁框也就是實體記憶體不夠使用的時候,作業系統會根據一些頁面置換算法将記憶體中的資料置換到磁盤上的交換空間(swap),騰出空閑的頁框來存儲需要在記憶體中運作的程式和相關資料。
在所有的頁面置換算法中,以老化算法應用最廣泛和最實用,下面大概說一下這個頁面置換算法。
在cpu剛使用的頁面很可能在後面的幾條指令中被使用,反過來說,已經很久沒有使用的頁面在未來的一段時間很可能仍然不被使用,因為程式指令是順序執行的,當然存在在jmp這樣的跳轉,可是大多數情況,程式指令還是以此來執行的。老化算法就是根據這個思想來實作的,在發生缺頁中斷時,置換很久沒有使用的頁面,這個政策被稱為LRU。
老化算法的大概實作思路是,在記憶體中存儲一個資料結構,裡面存儲頁面的使用次數,每次時鐘滴答後,将記憶體中頁面的引用次數加到資料結構裡面的數值上,每次先将數值右移一位(1000=>0100)然後将本次使用設定到數值的最左邊((使用)1100 不使用0100),然後在發生頁面替換時,則替換數值最小的那個也就是使用次數最小的.
下面說一下作業系統中發生缺頁中斷時的處理流程
1.目前程序的某條指令被放入cpu,cpu發出指令通路記憶體,該位址不存在,系統則陷入核心。
2.核心首先存儲目前程序的堆棧寄存器以及程式計數器來儲存目前程序的進度印象,為處理完缺頁中斷後繼續運作目前程式做準備。
3.作業系統根據cpu中寄存器發現發生缺頁中斷的記憶體位址。如果寄存器中沒有,則必須檢索上一部儲存的程式計數器來發現發生缺頁中斷的虛拟記憶體位址
4.發現了需要處理的位址後,系統檢查這個位址是否有效,是否越界。如果不符合檢查條件,則殺掉該程序,繼續運作别的程序。當符合條件。則系統檢查是否有空閑頁框,如果不存在空閑頁框,則調用頁面置換算法尋找一個頁面調換出去,為此頁面騰出一個頁框。
5.發現需要置換的頁面後,則檢查該頁面是否屬于髒頁面也就是資料裝入記憶體後被修改過,則需要調用程式将此資料寫回磁盤,并發生一次上下文切換,挂起目前處理缺頁中斷的程序,讓其他程序繼續運作。
6.當頁面幹淨後,則調用io程式将資料從磁盤寫入記憶體,并挂起目前程序,讓系統繼續排程其他待運作程序。
7.資料裝入頁框後,挂起程序被喚醒。系統将程式計數器以及各個寄存器以及堆棧恢複到cpu和産生缺頁中斷的程序空間位址中,繼續運作産生缺頁中斷的程序。
在作業系統中,一個程序包含資料,程式正文,還有堆棧,這些資料都放在一個程序位址空間中,堆棧會随着程式的執行變大和變小,資料也會跟着程式的進度而變化,當将這些不同類型的資料都放在一起時管理起來是很複雜的,比如堆棧的變大需要更多的記憶體空間時,是發生缺頁中斷還是增加程序位址的記憶體空間。其實作為程式員的我們沒有必要關心這個,這些完全可以交由系統去做,故現代的作業系統采用了分段技術,就是将一個程式的程序位址空間分為 資料段 程式指令正文段 堆棧段 還有全局常量,将這些不同類型的資料放入不同的空間并維護自己的頁表,這樣的幾個獨立的資料空間,空間擴容或減小都有作業系統攜帶分頁程式來管理,程式員不需要關心這些。
總結:我們大概說了一下作業系統的記憶體管理機制,其實無非就是通過各種手段去排程交換,讓有限的資源得到最大程度的利用。分頁處理或分段處理也是通過分頁程式或分段程式,将有限的記憶體空間放入最需要的資料。這也是人們在一步一步發展中總結出來的解決問題的辦法的展示。