發布時間:2024-09-03閱讀(18)

本文主要根據 “Operating Systems: Three Easy Pieces” 第15章總結而來。
在本頭條號之前的文章中有介紹,操作系統為了實現CPU虛擬化,采用的策略是:大部分情況下,讓進程直接在CPU上運行,這樣效率最高。但是為了不喪失對CPU的控制權,進程在進行系統調用時,或者操作系統設置的定時中斷被觸發時,操作系統又回重新奪回控制權。從這個層面看,高效和可控是現代操作系統設計的核心。內存虛擬化也不例外。
為了實現內存虛擬化,高效和可控也是首先需要考慮的因素。高效意味著,進程能很容易地在硬件的幫助下訪問內存;可控意味著沒有一個進程能隨意訪問別的進程的內存,從而對進程進行保護,也對操作系統進行了保護。除了高效和可控,內存虛擬化還有一個額外的要求就是靈活,靈活指的是我們希望進程能按照它想要的方式隨意使用它地址空間內的進程,從而讓編程變得簡單。
代碼很簡單,先加載內存中的值,然后加3,最后把計算后的值保存到內存中。從進程的角度看,代碼和數據在它的地址空間內如下圖所示:

可以看到,加3的代碼從地址128開始(靠近代碼段的頂端),變量x的值保存在地址15k的位置(靠近棧的底端),初始值是3000。當指令運行之后,從進程的角度看它會這樣使用內存:
從128獲取指令
執行指令(從15k的位置加載數據)
從132獲取指令
執行指令(不用讀取內存)
從135獲取指令
執行指令(把數據寫入15k的位置)
也就是說,進程認為它的地址空間是從0開始,最大是16k,所有的內存訪問都必須在0-16k之間。然而,為了實現內存虛擬化,操作系統不一定會把進程放到物理內存0開始的位置。一個可能的視圖如下圖所示:

從圖片來看,進程被放在了物理內存32k-48k的位置,16k-32k和48k-64k的位置還未被使用。那么問題來了,如何在進程無感知的情況下,把進程地址空間放到物理內存的指定位置呢?
基于硬件的動態重定位(或者是動態遷移)
前面的介紹都是背景,下面具體來看操作系統到底是怎么實現地址翻譯的。這里就引出了內存管理單元(memory management unit, MMU)中最基本的兩個寄存器:基地址寄存機和上界寄存器,base and bounds。后續會使用英文,因為英文更能表達含義。
當進程最初開始運行的時候,操作系統會根據目前系統可用的內存情況,設置base寄存器的值。比如上面的例子進程的物理地址是從32k開始的,那么base寄存器會設置成32k。當進程想獲取地址128的指令時,它會被翻譯成 32k(32768) 128=32896,這樣就能從物理地址里面取到真正的指令了。這就是所謂的地址翻譯!重復一下公式: 物理地址 = 虛擬地址 + 基地址
那么bounds寄存器什么時候用呢?也是在進程最初運行的時候,操作系統預分配一定大小的內存給進程,bounds寄存器就會被設置成最大的值,比如上面的例子,被設置成16k。當進程試圖引用大于16k的地址空間時,系統會拋出一個異常,這個異常就是因為檢查了bounds寄存器中的值。
從上面的流程可以看到,操作系統是在進程運行起來之后,根據系統可用的內存狀況來設置base and bounds,這個過程被稱作動態重定位。與它相對的也有靜態重定位,靜態重定位發生在編譯器,也就是說,程序編譯完了就知道基地址是什么了。可想而知,靜態重定位不具有移植性,在不同內存大小的系統就需要重新編譯,就算內存大小相同也要根據系統可用的內存而定。所以,目前基本上操作系統都是基于MMU中的 base and bounds寄存器,使用動態重定向的技術。
操作系統的角色
上面說到的是硬件,特別是MMU起的作用,那么操作系統起了什么作用呢?
首先,系統可用的內存列表是由操作系統維護的。有很多數據結構可以完成這個任務,隨著我們的深入研究會不斷介紹新的數據結構。這里我們先看最簡單的數據結構叫空閑鏈表,free list。Free List很簡單,把可用的內存分成很多塊,每一塊當作一個節點放到鏈表中。當新的進程來的時候,從列表中取一段內存給它,并把這段內存刪掉。當進程退出后,再把內存塊放到free list里面來。
其次,在CPU做上下文切換的時候,操作系統必須要把base and bounds的數據也存儲到PCB中。這樣當進程重新被調度運行的時候,它依然能找到自己的代碼和數據。
第三,既然進程可以切換,base and bounds可以被保存和讀取,那么在適當的時候,操作系統也可以移動進程在內存中的位置,只需要移動之后把base and bounds更新就可以了。這其實也是動態重定位的體現。
最后,操作系統必須在啟動的時候,額外注冊一個異常句柄,也就是bounds溢出的處理函數,保證進程訪問界限之外的內存區域被操作系統拒絕。
總結
基于之前我們做的假設,內存空間的大小是一定的,而且小于物理內存的大小,所以free list能滿足我們的需求。但是我們應該看到,這些假設可能造成的問題。畢竟,并不是所有的進程用的地址空間都是一樣的,我們系統地址空間的大小是可變的。另外,如果有些內存塊太小它就沒辦法被分配給進程,這塊內存就會被浪費掉,也就是我們說的內存碎片。
解決這些問題就需要引入內存分頁和分段的技術,這是后面會繼續的內容。歡迎大家訂閱我的頭條號,第一時間收到更新,謝謝!
歡迎分享轉載→http://m.avcorse.com/read-398613.html
Copyright ? 2024 有趣生活 All Rights Reserve吉ICP備19000289號-5 TXT地圖HTML地圖XML地圖