內存:是現代計算機運行中心。內存由很大一組字或字節組成,每個字或字節都有他們自己的地址。CPU根據程序計數器(PC)值從內存中提取指令,這些指令可能會引起進一步對特定內存地址的讀取和寫入。
CPU所能訪問的存儲器只有內存和處理器內的寄存器;
保證物理內存的相對速度:高速緩存(cache)[k??]:CPU和內存之間增加高速內存; 確保操作系統不被用戶進程所訪問,以及確保用戶進程不被其他用戶進程所訪問。其可通過硬件來實現。首先確保每個進程都有獨立的內存空間,需確定進程可訪問的合法地址的范圍並確保進程只訪問其合法地址。
基地址寄存器(base register):含有最小的合法物理內存地址;
界限地址寄存器(limit register):j決定范圍的大小;
內存空間保護的實現,是通過CPU硬件對用戶模式所產生的每一個地址與寄存器地址進行比較來來完成的。
只有操作系統可以通過特殊的特權指令來加載基地址寄存器和界限地址寄存器。(在內核模式下運行)
用戶程序在執行前的處理步驟:源程序–>編譯器和匯編器(編譯時間)–>目標模塊–>連接器(其他模塊)–>加載模塊—>加載器(系統庫)(加載時間)–>二進制內存鏡像(動態鏈接)(執行時間,運行時間);
在這些步驟中,地址可能有不同的表現形式。源程序中的地址通常是符號來表示。編譯器通常將這些符號地址綁定(bind)在可重定位的地址。鏈接程序或加載程序再講這些可重定位的地址綁定成絕對地址。每次綁定都是一個地址空間到另外一個地址空間的映射。
通常將指令與數據綁定到內存地址有一下情況:
編譯時(compile time):如果在編譯時就知道進程將在內存中的駐留地址,那麼就可以生成絕對代碼(absolute code)。 加載時(load time):如果編譯時不知道進程將駐留在內存的什麼地方,那麼編譯器必須生成可重定位代碼(relocatable code)。對於這種情況,最後綁定會延遲到加載時才進行。 執行時(execution time):如果進程在執行時可以從一個內存段移到另一個內存段,那麼綁定必須延遲到執行時才進行。CPU所生成的地址通常稱為邏輯地址(logical adresss),而內存單元所看到的地址(即加載到內存地址寄存器(memory-adress register)中的地址) 通常稱為物理地址(physical adress)。
編譯和加載時的地址綁定方法生成相同的邏輯地址和物理地址,但執行時的地址綁定方案導致不同的邏輯地址和物理地址。對於這種情況,通常稱邏輯地址為虛擬地址(virtual adress)。
運行時從虛擬地址到物理地址的映射是由被稱為內存單元(memory-management unit,MMU)的硬件設備來完成的。
用戶進程所生成的地址在交送內存之前,在將加上重定位寄存器的值才得到物理地址。
用戶處理邏輯地址,內存映射硬件將邏輯地址轉變為物理地址。
邏輯地址(范圍為0~max),物理地址為(R+0~R+max,其中R為基地址)。
動態加載(dynamic loading):采用動態加載時,一個子程序只有在調用時才被加載。所有子程序都以可重定位的形式保存在磁盤上。主程序裝入內存並執行。當一個子程序需要調用另一個子程序時,調用子程序首先檢查另一個子程序是否已加載。如果沒有,可重定位的鏈接程序將用來加載所需的子程序,並更新程序的地址表以反映這一變換。緊接著,控制傳遞給新加載的子程序。
動態加載的優點是不用的子程序絕不會加載。如果大多數的代碼用來處理異常情況,如錯誤處理,那麼這種方法特別有用。
動態鏈接庫(dynamic linked library):此時系統語言庫和其他目標模塊一樣,由加載程序合並到二進制程序鏡像中。動態鏈接庫的概念與動態加載相似,只是這裡不是將加載延遲到運行時,而是將鏈接延遲到運行時,其不需要復制庫的副本,節省空間。
共享庫:在程序的鏈接時候並不像靜態庫那樣在拷貝使用函數的代碼,而只是作些標記。然後在程序開始啟動運行的時候,動態地加載所需模塊。所以,應用程序在運行的時候仍然需要共享庫的支持。 共享庫鏈接出來的文件比靜態庫要小得多。
進程需要在內存中以便執行,不過,進程可以短暫從內存中交換(swap)到備份存儲(backing store)上,當需要再次執行時再調回內存中。
內存通常分為兩個區:一個駐留操作系統(一般位於低內存),另一個用於用戶進程。
通過重定位寄存器和界限地址寄存器實現。
內存分配方法:
多分區方法(multile-partion method):將內存分為多個固定大小的分區。 可變分區(variable-partition):操作系統有一個表,用於記錄那些內存可用。操作系統根據所有進程的內存需要和現有可用內存情況來決定哪些進程可分配內存。即是一種通用動態存儲分配問題(根據一組空閒孔來分配大小為n的請求)。方法有:首次適應、最佳適應、最差適應;首次適應和最佳適應都有外部碎片問題(external fragmentation):隨著進程裝入和移出內存,空閒內存空間被分為小片段。當所有總的可用內存之和可以滿足請求,但並不連續時,這時就出現了外部碎片問題。
內部碎片:通常將內存以固定大小的塊為單元來分配。進程所分配內存可能比所要的要大。這兩者數字只差為內部碎片,這部分內存在分區內,但又不能用。
外部碎片解決辦法:
緊縮(compaction):緊縮的目的是移動內存內容,以便所有空閒空間合並成一整塊。但緊縮僅在重定位是動態並在運行時可采用。首先移動程序和數據,然後再根據新基地址的值來改變基地址寄存器。 允許物理地址非連續:分頁和分段。分頁(paging):內存管理方案允許進程的物理地址空間可以是非連續的。
實現分頁的基本方法:將物理內存分為固定大小的塊,稱為幀(frame);而將邏輯內存也分為同樣大小的塊,稱為頁(page)。當需要執行進程時,其頁從備份存儲中調入到可用的內存幀中。備份內存也分為固定大小的塊,其大小與內存幀一樣。
分頁硬件支持:由cpu生成的每個地址分為兩部分:頁號(p)和頁偏移(d)。頁號作為頁表的索引。頁表包含煤業所在物理內存的基地址,這些基地址與頁偏移的組合就形成了物理地址,就可送交物理地址。
也大小(與幀的大小一樣)是由硬件來決定的。頁的大小通常為2的冪。512B~16MB大小不等。
分頁是一種動態重定位。每個邏輯地址有分頁硬件綁定為一定的物理地址。
采用分頁技術不會產生外部碎片:每個幀都可以分配給需要它的進程。不過,分頁有內部碎片。
分頁的一個重要特點是:用戶視角的內存和實際的物理內存的分離。
8.4.2 硬件支持
絕大多數操作系統都為每個進程分配一個頁表。頁表的指針域其他寄存器的值一起存入進程控制塊中。
頁表的硬件實現:將頁表作為一組專用寄存器(register)來實現。這些寄存器的應用高速邏輯電路來構造,以便有效的進行分頁地址的轉換。
頁表比較大時需將頁表保存在內存中,並將頁表基寄存器(page-table base),PTBR):指向頁表。但問題是訪問用戶內存需要一些時間,即采用小但專業且快速的硬件緩沖,這種緩沖稱為轉換表緩沖區(translation look-aside buffer,TLB)。TLB是關聯的快速內存。
TLB:TLB只包含頁表中的一部分條目。當CPU產生邏輯地址時,其頁號提交給TLB.如果找到頁號,那麼也就找到幀號,並可用來訪問內存。如果頁碼不再TLB中(TLB失效),那麼久需要訪問頁表。當得到幀號後就可以訪問內存。同時將頁號和幀號增加到TLB中,這樣下次再用就可以很快查找到。
在分頁的環境下,內存保護是通過與每個幀相關聯的保護位來實現的。通常這些位保存在頁表中。
分頁的優點之一就是可以共享公共代碼(可重入代碼),則可共享。
分段(segmentation):支持用戶視角的內存管理方案。邏輯地址空間是由一組段組成的。每個段都有名稱和長度。地址指定了段名稱和段內偏移(用戶指定)。
段表(segment table):將二維的用戶定義地址映射為一維物理地址。段表的每個條目都有段基地址和段界限。
分頁和分段的區別:
頁是信息的物理單位;段是信息的邏輯單位。 頁的大小固定且由系統確定;段的長度不固定(取決於用戶所編寫的程序,通常由編譯程序在對源程序編譯時,根據信息的性質來劃分)。 分頁的作業地址空間是一維的,只需一個記憶符,就可表示一個地址;分段的作業地址空間是二維的,程序員在標識一個地址時,既需給出段名,又需給出段內地址。 分段物理空間不連續,但段內是連續的;分頁物理空間不連續。虛擬內存技術允許執行進程不必完全在內存中,其顯著優點是程序可以比物理內存大。而且,虛擬內存將內存抽象成一個巨大的,統一的存儲數組,進而將用戶看到的邏輯內存與物理內存分開。這種技術允許程序員不受內存存儲的限制。
虛擬內存(virtual memory):是計算機系統內存管理的一種技術。它使得應用程序認為它擁有連續的可用的內存(一個連續完整的地址空間),而實際上,它通常是被分隔成多個物理內存碎片,還有部分暫時存儲在外部磁盤存儲器上,在需要時進行數據交換。
按需調頁(demand paging):只有程序執行需要時才才載入頁。
調頁程序將那些必須的頁調入內存,避免讀入那些不使用的頁,也減少了交換時間和所需的物理內存空間。
需要硬件支持來區分那些頁在內存裡,哪些頁在磁盤上。
支持按需調頁的硬件:
頁表 次級存儲器寫時復制(copy-on-write):允許父進程與子進程開始時共享同一頁面。且這些頁面標記為寫時復制頁,即如果任何一個進程需要對頁進程寫操作,那麼久創建一個共享的副本。
當確定一個頁要采用寫時復制是,許多操作系統為這類請求提供了空閒緩沖池。通常采用按需填零的技術以分配這些頁。
UNIX :vfork(虛擬內存);vfork()會將父進程掛起,子進程使用父進程的地址空間。由於vfork()不使用寫時復制,因此子進程修改父進程的地址空間的任何頁,那麼修改過的頁在父進程重啟時是可見的。vfork()主要用於在子進程被創建後立即調用exec()的情況。由於沒有出現復制頁面,vforK()是一種非常有效的進程創建方法,有時用於實現UNIX命令行shell的接口。
頁置換:如果沒有空閒幀,那麼就查找當前沒有使用的幀,並將其釋放。可將其內容寫到交換空間,並改變頁表,以表示該頁不在內存中。
頁置換是按需調頁的基礎。它分開了邏輯內存與物理內存。采用這種機制,小的物理內存能為程序員提供巨大的虛擬內存。
為實現按需調頁,必須解決兩個問題:幀分配算法(frame-allocation algorithm)和頁置換算法(page-replacement algorithm)。
頁置換方法:
FIFO頁置換 最優頁置換(optimal page-replacement algorithm):產生頁錯誤率最低,它會置換最長時間不會使用的頁; LRU頁置換(最近最少使用算法 least-recently-used(LRU) algorithm):FIFO算法使用的是頁調入內存的時間,OPT算法使用的是頁將來使用的時間。如果使用的是離過去最近作為不遠將來的近似,那麼可置換最長時間沒有使用的頁。 基於計數的頁置換:最不經常使用頁置換算法(least frequently used(LFU) page-replacement algorithm):要求置換計數最小的頁; 頁緩沖算法:幀的最小數量,全局置換、局部置換;
顛簸(thrashing):頻繁的頁調度行為;
內存映射(memory mapping):使用虛擬內存技術獎文件I/O作為普通內存訪問。
文件的內存映射可將一磁盤塊映射成內存的一頁(或多頁)。開始的文件訪問按普通頁面調度來進行,會產生頁錯誤。這樣,一頁大小的部分文件從文件系統讀入物理頁。以後文件的讀寫就按通常的內存訪問來處理,由於是通過內存操作文件而不是使用系統調用read()和write(),從而簡化了文件的訪問。
通常,每個I/O控制器包括存放命令及傳遞數據的寄存器。專用I/O指令允許寄存器和系統內存之間進行數據傳遞。為了更方便的訪問I/O設備,許多計算機都提供內存映射I/O。這樣一組內存地址就專門映射到設備寄存器。對這些內存地址讀寫就如同對設備寄存器的讀寫。
內核內存的分配通常是從空閒內存池中獲取的,而不是從滿足普通用戶模式進程的內存鏈表中獲取的。主要原因如下:
內核需要為不同大小的數據結構分配內存,其中有的不到一頁。因此必須謹慎使用內存,並試圖減低碎片浪費。 用戶進程所分配的頁不必要在連續的物理內存。而有的硬件要直接與物理內存打交道,而不需要經過虛擬內存接口,因此需要內存常住在連續的物理頁中。對內核進程進行內存管理的兩個方法:
Buddy系統Buddy系統:從物理上連續的大小固定的段上進行分配。內存按2的冪的大小來進行分配。及4KB,8KB,16KB等。如果請求大小不為2的冪,那麼需要調整到下一個更大的2的冪。Buddy系統的一個優點是可通過合並而迅速形成更大的段。缺點是由於調整到下一個2的冪容易產生碎片。
slab分配Linux的slab可有三種狀態:
滿的 空的 部分slab分配器的優點:
沒有內存碎片:每個內核數據結構都有相應的cache,而每個cache都由若干slab組成,而每個slab又分為若干個與對象大小相同的部分。因此,當內核請求對象內存時,slab分配器可以返回剛好可以表示對象所需的內存。 內存請求可以快速滿足:由於對象預先創建,所以可以從cache上快速分配。另外,當用完對象並釋放時,只需要標記為空閒並返回給cache,以便下次再用。