一步步编写操作系统 35 内存为何要分页
一直以來我們都直接在內存分段機制下工作,目前未出問題看似良好,的確目前咱們的應用過于簡單了,就一個loader在跑,能出什么問題呢。可是想像一下,當我們物理內存不足時會怎么辦呢?比如系統里的應用程序過多、或者內存碎片過多無法容納新的進程、或者曾經被換出到硬盤中的內存段需要再次重新裝到內存,可是內存中找不到合適大小的內存區域怎么辦?也許有人會說,這簡單啊,停止想像唄…….嘿嘿,開玩笑而已,問題還是要解決的。
也許文字說得并不是很清楚,下面以圖示說明這些情況
?
上圖模擬了多個進程并行的情況。在第1步中,系統里有3個進程正在運行,進程A、B、C各占了10MB、20MB、30MB的內存空間,物理內存還是挺寬裕的,還剩下15MB可用。到了第2步就悲催了,此時進程B已經運行結束,騰出了20MB的內存,可是待加載運行的進程D需要20MB+3KB的內存空間,即20483KB。現在的運行環境是未開啟分頁功能,“段基址+段內偏移”產生的線性地址就是物理地址,程序中引用的線性地址是連續的,所以物理地址也連續。雖然總共剩下35MB內存可用,可問題是明擺著的,現在連續的內存塊只有原來進程B的20MB和最下面可用內存15MB。哪一塊都不夠進程D用的,這怎么辦呢?
兩個解決方案:
- 等待進程C運行完后騰出內存,這樣連續可用的內存就夠運行進程D了
- 將進程A的段A3或進程C的段C1換出到硬盤上,騰出一部分空間,加上鄰接的20MB,足夠容納進程D。
第一個方案比較簡單直接,但就是要等待,而且咱們也不知道程序C啥時候執行完,等個沒完沒了,用戶還以為死機了呢,說不定一氣之下就給重啟了,算啦,這個方法不好,看第二個吧。
第二個方案看上去先進很多,原理是將老進程不常用的段換出到硬盤,騰出空間給新進程用,等老進程再次需要該段時,再從硬盤上將該段載入內存。如圖:
?
看上去方案完美無懈可擊,雖然要用到低速的硬盤,但至少能干活。這就是當系統物理內存不足的情況下,硬盤燈會不停閃爍的原因。不過這一切是需要硬件的配合才能實現,咱們一會介紹下這種內存管理,不過在這之前先扯點別的。
我曾經一度搞不清楚操作系統和硬件的內在聯系,比如,某種功能是操作系統自己實現的還是硬件直接支持的?甚至在更早些時候,由于知識掌握的不足,有些問題迷惑到不知該如何表達,后來才搞清楚,操作系統和硬件之間是相互依賴、相互推動、相互促進而發展起來的。比如,起初的操作系統無法對內存段做訪問限制,有了這樣的需求后,cpu廠商決定采用段描述符來實現相關功能,在硬件一級上添加了GDTR和LDTR寄存器來支持全局描述符表和局部描述符表,并由硬件負責周邊的安全檢測。當初cpu硬件廠商可不是憑空造出這樣一個概念的,是與操作系統廠商共同協商后才有了一套硬件方面的支持。這不僅僅在計算機行業中是這樣,其它行業也一樣,比如機械制造行業,如果要生產一個精度較高的零件,而目前的車床無法加工,生產車床的廠商就要提高自身水平,制造出加工精度更高的車床,而不是讓零件去適應車床而降低精度。另外一個最典型的例子就是人類的直立行走,最早的時候是用四肢行走,人在思想上想把雙手騰出來做其它事,所以身體便給予了“硬件”支持,慢慢發展成了只用下肢行走,這是典型的軟件督促硬件發展。
雖然操作系統和cpu相互促進,但說到底,操作系統是軟件,軟件中的指令是靠cpu來執行,如果計算機是有生命的,軟件相當于思想、靈魂,而硬件才是真正的身體,思想指導身體的行為。
但并不是思想指導了所有的行為,就拿人類的運動來說,咱們的大腦產生了跑的意識后,左腿右腿就交替向前邁進。但跑起來之后,心臟會加速跳動,肺也加速了呼吸的頻率,這并不是咱們主動控制的,這些器官的行為是由身體里的植物神經控制。也就是說,咱們在跑步時,雖然大腦思想上只負責跑步的動作,不用向身體發命令:心臟加速、呼吸加速等,但這些器官的行為確實存在著,而且是在生理一級上自動完成不受意識控制。
說這些就是想告訴大家,我們所寫的代碼僅僅是完成了某件事的一部分而已,也許是大部分,還有一部分是cpu硬件上負責的,這部分咱們不用管,由cpu自動完成。比如,調用一個函數時,cpu自動將返回地址壓入棧;進入中斷時,cpu除了壓入返回地址、標志寄存器外,還要根據當前特權級決定是否壓入當前棧段寄存器及指針……這樣的例子太多了不再一一列舉。
東扯西扯地說了這么多后,開始說下例子中內存管理的原理,內存段是怎樣被換出的。
在保護模式下,段描述符是內存段的身份證。cpu在引用一個段時,都要先查看段描述符。很多時候,段描述符存在于描述符表中(GDT或LDT),但與此對應的段并不在內存中,也就是說,cpu允許在描述符表中已注冊的段不在內存中存在,這就是它提供給軟件使用的策略,我們利用它實現段式內存管理。如果該描述符中的P位為1,表示該段在內存中存在。訪問過該段后,cpu將段描述符中的A位置1,表示近來剛訪問過該段。相反,如果P位為0,說明內存中并不存在該段,這時候cpu將會拋出個NP(段不存在)異常,轉而去執行中斷描述符表中NP異常對應的中斷處理程序,此中斷處理程序是操作系統負責提供的,該程序的工作是將相應的段從外存(比如硬盤)中載入到內存,并將段描述符的P位置1,中斷處理函數結束后返回,cpu重復執行這個檢查,繼續查看該段描述符的P位,此時已經為1了,在檢查通過后,將段描述符的A位置1。
以上是cpu加載內存段的過程,內存段是何時移出到外存上的呢?
段描述符的A位是由cpu置1,但清0工作可是由操作系統來完成的。此位干嗎用的呢?如果僅僅用來表示該段被訪問過,這也意義不大啊。其實這正是軟件和硬件相互配合的體現,操作系統每發現該位為1后就將該位清0,這樣一來,在一個周期內統計該位為1的次數就知道該段的使用頻率了,從而可以找出使用頻率最低的段。當物理內存不足時,可以將使用頻率最低的段換出到硬盤,以騰出內存空間給新的進程。當段被換出到硬盤后,操作系統將該段描述符的P位置0。當下次這個進程上cpu運行后,如果訪問了這個段,這樣程序流就回到了剛開始cpu檢查出P位為0、緊接著拋出異常、執行操作系統中斷處理程序、換入內存段的循環。
另外,內存中的數據是二進制的,段被換出到硬盤上也是以二進制形式存儲,數據內容都是一樣的,只是存儲介質不同而已,不要因為陌生而覺得段的換入換出深不可測,這無非是一段二進制數據在內存和外存之間拷貝來拷貝去而已,其過程就像將一個txt文件讀到內存中修改后再保存到硬盤一樣。
第二個方法雖然解決了內存不足的問題,但也有缺陷。比如物理內存特別小,無法容納任何一個進程的段,這就沒法運行進程了,更沒法做段的換入換出。也許有人會說,這是用戶的問題,這么小的內存還拿出來用,這不是“逗比”嗎。您還別說,一會介紹的內存分頁機制,理論上只要4K內存就可以讓程序運行下去。另外一種情況是,若進程的段比較大,換出時要將整個段全部搬到外存上,這種IO操作太多了機器響應奇慢無比,用戶是無法接受的。還有沒有更好的方法呢?
想一想,出現這種問題的原因是什么?問題的本質是,在目前只分段的情況下,cpu認為線性地址等于物理地址。而線性地址是由編譯器編譯出來的,它本身是連續的,所以物理地址也必須要連續才行,但我們可用的物理地址不連續。換句話說,如果線性地址連續,而物理地址可以不連續,不就解決了嗎。
按照這種思路,我們首先要做的是解除線性地址與物理地址一一對應的關系,然后將它們的關系重新建立。通過某種映射關系,可以將線性地址映射到任意物理地址。
有很多實現映射的方法,比如可以寫個哈希算法,將線性地址做key,而value是物理地址。不過,這都是軟件實現的算法,時間復雜度再低,效率肯定不如硬件“短、平、快”,因為硬件中的操作更直接,并且已經在電路上做過優化,而軟件的效率主要取決于代碼的算法和編譯器的優化能力,即使能產生出最優的機器碼,也是被當做普通指令處理:先要到內存中取指、譯碼、再執行,不說別的,就光是取指這步就已經很慢了,畢竟內存在cpu眼里是低速設備。所以,對于地址轉換這種實時性較高的需求,cpu已經給予了我們最大的硬件支持,在cpu實現中,這種映射關系是通過一張表來實現的,該表就是我們所說的頁表,查找頁表的工作也是由硬件完成的。這張表是什么樣的呢?我們在下一節中給出答案
總結
以上是生活随笔為你收集整理的一步步编写操作系统 35 内存为何要分页的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 618为何大家不疯狂“剁手”了:揭秘背后
- 下一篇: 信用卡冻结是怎么回事 信用卡冻结后怎么解