在多道程式中,需要從磁碟中同時讀入多個進程到記憶體,我們需要對記憶體進行管理,使得能夠有條理地執行進程。
通常指令先要從記憶體中讀入,進行解碼,還要從記憶體中讀取運算元,再將結果返回給記憶體。記憶體看到的只是地址。
一個進程佔了一塊記憶體,跨度是一串聯續的地址,我們用base register和limit register來限定進程訪問的範圍。
CPU只能訪問的儲存空間是記憶體和CPU的寄存器,所以如果有指令要訪問資料,必須要提前置入到記憶體。下面會講到交換方法,和動態載入。
Cache:因為訪問CPU寄存器必訪問記憶體速度快了很多,所以我們不能老是訪問記憶體,所以要有Cache。訪問記憶體可能會導致CPU暫停。
進程訪問的地址的限定通過硬體實現:即通過base和limit寄存器的比較實現。
OS可以改limit和base的值。
磁碟有一個輸入隊列,存放準備匯入記憶體的進程。
代碼裡我們通常用一個變數代表地址,我們可以將變數綁定到可重定位地址,載入程式再將重定位地址綁定成絕對位址。
綁定的情況:
1.編譯時間:如果在編譯時間就知道記憶體的地址,那麼直接綁定絕對位址。
2.載入時:在編譯時間產生可重定位代碼,在載入時才產生絕對代碼。
3.執行時:如果執行時還要將進程從一個記憶體塊移到另一個記憶體塊,則只有執行時才綁定。
CPU產生的邏輯地址,記憶體的地址是物理地址。
程式產生的地址空間稱為邏輯地址空間。
運行時虛擬位址到物理地址通過MMU映射。通過CPU的邏輯地址+base register就得到物理地址。使用者是不知道物理地址的。
動態載入:如果一個程式必須全部載入入記憶體,那麼必須限制程式的大小,所以動態載入是將一個大程式分成多個子程式,每次將某個子程式調入記憶體,如果一個子程式需要調入另一個子程式,則再載入,加強記憶體空間的使用率。
動態連結:通過在程式內部存放一個存根引用語言庫,可以不必複製多個語言庫副本,並且如果版本號碼不同,可以在記憶體中存放多個不同版本的庫。
--------
如果CPU要執行的進程不再記憶體中,而在備份儲存空間中,則需要換入和換出,即交換。
當就進程換出後再次換入時,需要考慮位置問題:
1.當在載入時就綁定到物理地址,則換入到同一個位置。
2.在運行時綁定,則可以換入到任意位置。
交換的時間分為:轉移時間,磁碟磁頭定址時間。
換出的進程選擇方面需要注意不能選在I/O等待隊列中的進程。
因此有一個修正的交換方式,可以在記憶體吃緊的情況下交換,但是在CPU使用率降低到一定時停止交換。
為了管理記憶體,需要為每個進程分配連續的記憶體。
記憶體映射需要將CPU的邏輯地址先與limit register比較,再加上base register。
當作業系統的一部分不常用時,可以先將它換出到備份儲存,這塊空間就可以騰給使用者進程使用。
記憶體配置最簡單的是將記憶體分成多個固定大小的分區,每個分區容納一個進程。
可變分區是OS儲存一個表,記錄哪些記憶體可用,哪些記憶體已佔用。
閒置記憶體地區稱為孔。
當一個進程從輸入隊列進入記憶體,有幾種選取孔的方案:
1.最佳適應:遍曆一遍,找到合適的最小孔。
2.首次適應。
3.最差適應。(真不知道要這個幹嘛。。)
在第一第二中方法時,會存在外部片段問題。
外部片段問題:進程入孔後,還多出一點小空間,但不能放進程了。
內部片段問題:分配記憶體時按塊分配,比如一個進程3K,一個孔4K,則直接將4K分給進程,4-3=1K就是內部片段。
解決外部片段的方法是緊縮,將進程都擠一塊。但是當綁定在載入時,則不能緊縮。
還有一種方法是重點,即可以將記憶體配置為非連續。
50%規則:在可分配的記憶體中,有50%的空間是片段。
分頁:將CPU的邏輯地址分成頁,而將實體記憶體地址分成幀。備份儲存也分成大小一樣的塊。先通過頁號找到頁表對應的幀,再加上頁位移找到記憶體的位置。
將CPU邏輯地址分成頁號和頁位移方法:先將CPU邏輯地址的大小變成2^m,m就是邏輯地址的位元,設2^n為一頁的大小,那麼n則為頁位移。
m-n就為頁號長度。
分頁會導致內部片段。因為幀大小固定,一次分配就是一幀的整數倍。
進程由幾頁組成,每個頁對應一個幀。因此進程的內容就不一定要連續,每個進程對應一個頁表。OS還持有每個進程頁表的副本。
幀表:包括全部的幀以及他們是空閑還是被佔用。
一開始頁表放在PCB的寄存器中,因為速度快。但是隨著頁表的變大,必須把頁表放在記憶體,而PCB只需保留一個頁表基寄存器(PTBR),指向頁表的頭部。得到幀號再加上頁位移後再次訪問記憶體。因此要2次訪問。
由於上述方案需要2次記憶體訪問,延遲大,所以產生了(TLB)快表(感覺就像cache),保留頁表的一小部分,可以同時訪問TLB的每個元素,速度快。如果TLB中沒有,則按常規訪問,並且將這個頁表添加入TLB(局部性原理)。TLB的結構是鍵為頁號,值為幀號。
有時TLB中還會有ASID,地址空間標識符(ASID):唯一標識一個進程,一定要ASID匹配並且頁號匹配才算匹配。
如果沒有提供ASID時,則TLB儲存一個進程的頁表,每次進程切換,則TLB被flush。
TLB的命中率:訪問TLB能找到的機率。
我們可以為每個幀授予許可權,唯讀,可讀寫,可執行等。
頁表的保護方式是通過在頁表中提供一位稱為“有效無效位”,即v或i。代表該頁號是否合法。因為可能一個程式只有1-5的頁號,所以頁號為6以後的都被置為i。當然還可以通過PTLR(頁表長度寄存器)來限制頁表大小。
分頁可以共用公用代碼,但是這些公用代碼是不能修改的。想象一下為什麼能共用,因為進程只需要存放專屬的頁表,頁表對應的幀在記憶體中,所以只需要一個程式即可,但是需要獨立的資料,因此資料不能共用。
組織頁表有幾種方法:
1.多級頁表。向前映射頁表:從外級頁表逐步映射到地址。想到了多級索引。【頁表劃分出題】
由來:因為我們的地址空間越來越大,所以頁表也越來越大,因此到了我們都不能連續存放頁表的地步,所以出現了多級頁表。
2.hash頁表。邏輯地址的頁號通過hash後找到hash表對應位置,hash表每個元素對應一個鏈表,一個元素由虛擬頁碼、幀號、next組成。
群集頁表適用於64位地址空間,hash頁表的每個條目儲存多個物理頁幀的映射。
3.反向頁表。通常一個進程有一個頁表,但是佔用記憶體大,因此整個系統只有一個反向頁表,一個反向頁表條目為<pid,page_number>,虛擬位址空間的一個條目為<pid,page_number,offset>,但是缺點為遍曆時間長,但是可以結合前面提到的技術,如先TLB,再hash,再反向頁表。但是反向頁表不能共用記憶體。
分段是考慮到了使用者對於記憶體的視角來構造的。將程式看成是一個段,沒有順序,使用者通過<段號,段位移>確定地址。
類似於頁表,分段通過段表來實現地址映射,段表一個元素對應base register和limit register。