《現代操作系統》第三章:存儲管理

操作系統中管理分層存儲器體系的部分稱爲存儲器管理(memory manager)

3.1 無存儲器抽象

直接使用物理地址;
並行問題;
重定位問題(因爲使用的是絕對物理地址);
裝載器需要一定的方法來辨別地址和常數(地址值需要重定位,而常數不需要重定位)。

3.2 一種存儲抽象:地址空間

要保證多個應用程序同時處於內存中並且不互相影響,需要解決的問題:保護和重定位。
    保護:給內存塊標記保護鍵(IBM 360)
地址空間是一個進程可用於尋址內存的一套地址集合,每個進程都有一個自己的地址空間。
    使得一個程序中的地址28所對應的物理地址與另一個程序中的地址28對應的物理地址不同。
基址寄存器和界限寄存器
    簡單的動態重定位
    前者是程序的起始物理地址,後者是程序的長度,CPU硬件在把地址發送到內存總線前,自動把基址值加到進程發出的地址上。
    缺點:每次訪問內存都需要進行加法和比較運算,導致速度相對慢一點。
Intel 8088提供了多個基址寄存器,使程序的代碼和數據可以被獨立的重定位。但是沒有提供引用地址越界的預防機制。
把所有進程都一直保存在內存中需要巨大的內存,如果內存不夠,就做不到。
    處理內存超載的通用方法:
        1)交換(swapping)技術:進程運行一段時間,然後再存回磁盤中,週期性的被喚醒與睡眠,不運行時則不佔內存。
        2)虛擬內存(virtual memory):使程序只有一部分被調入內存的情況下運行。
交換技術:
    某個進程本次換入內存與上次換入內存時的位置可能會發生變化,則在換入的時候會通過軟件或者程序運行期間(多數是這種情況)通過硬件對其地址進行重定位。
    內存緊縮(memory compaction)操作。交換在內存中產生了多個空閒區(hole)也叫空洞,通過把所有的進程儘可能向下移動,有可能將這些小的空閒塊合併成一大塊。
    進程被創建或換入時需要分配內存的大小問題,爲可能增長的數據段預留多一個空閒空間:
        1)分配相鄰的空閒區最爲可能增長的空
        2)在堆棧之間預留可能增長的空間
動態內存分配時跟蹤內存使用情況:
    1)位圖:將內存劃分成多個單元,通過位圖來記錄每個單元是否被使用,分配操作比較耗時;分配單元的大小是一個重要的設計因素,內存的大小和分配單元的大小決定了位圖的大小。
    2)鏈表:維護一個記錄已分配內存段和空閒內存段的鏈表。鏈表中的每一個結點都包含以下域:空閒區(H)或進程(P)的指示標誌、起始地址、長度和指向下一結點的指針。
鏈表的存儲管理方法中,爲創建或者換入的進程分配內存的方法:
    1)首次適配(first fit)算法:每次都從第一個開始搜索,找第一個合適的;
    2)下次適配(next fit)算法:每次找到合適的空閒區就會記錄當時的位置,下次則在這個位置往後繼續搜索,性能略低於首次適配法;
    3)最佳適配(best fit)算法:找到合適的最小空閒區,比首次適配算法更浪費內存(會產生大量無用的小空閒區?),需要搜索整個鏈表;
    4)最差適配(worst fit)算法:總是分配最大的可用空閒區;
    5)快速適配(quick fit)算法:爲常用大小的空閒區維護單獨的鏈表,尋找指定大小的空閒塊是快速,但是進程終止或被換出時合併操作耗時。
    將進程和空閒區使用不同的鏈表保存?

3.3 虛擬內存(virtual memory)

雖然軟件大小的快速增長,可能內存無法滿足單獨一個程序的需求,並且硬盤的讀取速度會導致換入與換出一個程序很耗時。
虛擬內存的基本思想是:每個程序擁有自己的地址空間,該空間被分割成很多塊,每一塊稱作一頁或者頁面(page)。
    每一頁被映射到物理內存,不是所有的頁都必須在內存中才能運行程序。
    程序引用到一部分在物理內存中的地址空間時,由硬件執行必要的映射。當程序引用到一部分不在物理內存中的地址空間時,由操作系統負責將缺失的部分裝入物理內存並重新執行失敗的指令。
    從某種角度上講,虛擬內存是對基址寄存器和界限寄存器的一種綜合,使得整個地址空間可以用相對較小的單元映射到物理內存,而不是爲正文段和數據段分別進行重定位。
    適合多道程序設計系統,因爲許多程序的片段會同時保存在內存中,當一個程序等待它的一部分讀入內存時,可以直接把CPU交給另一個進程使用而基本不需要進行進程的換入換出操作。
分頁(paging)技術。
    地址可以通過索引、基址寄存器、段寄存器或其他方式產生。
由程序產生的地址稱爲虛擬地址(virtual address),構成了一個虛擬地址空間(virtual address space)
    1)沒有虛擬內存的計算機上,系統直接將虛擬地址送到內存總線上。
    2)在使用虛擬內存的情況下,虛擬地址是被送到內存管理單元(Memory Management Unit,MMU),MMU把虛擬地址映射爲物理內存地址。
虛擬地址空間按照固定大小劃分爲頁面(page)的若干單元,在物理內存中對應的單元稱爲頁框(page frame)。頁面大小和頁框是一樣的。
    RAM和磁盤之間的交換總是以整個頁面大小爲單元進行。
    在實際的硬件中,用一個“在/不在”位(present/absent bit)記錄頁面在內存中的實際存在情況。
MMU注意到某個虛擬頁面沒有被映射到頁框時,會使CPU陷入到操作系統,這個陷阱稱爲缺頁中斷(page fault)。操作系統再找到一個很少使用的頁框將其內容寫入到磁盤(如果它不在磁盤上),隨後再將需要訪問的頁面讀到剛纔回收的頁框中,修改映射關係,然後重新啓動引起陷阱的指令。
    被收回的虛擬頁面要修改爲未映射。
    將需要的虛擬頁面裝入到對應頁框中,訪問該虛擬頁面的地址會被映射到對應的物理內存中。
虛擬地址到物理地址的映射可以概括如下:虛擬地址分爲虛擬頁號(高位部分)和偏移量(低位部分)兩個部分。
    例如32位系統中輸入的16位虛擬地址就被分爲4位的頁號和12位的偏移量,4位的頁號可以表示16個頁面,12位的偏移可以爲一頁內的全部4096個字節編址。
    可以用頁號作爲頁表(page table)的索引,以得出對應於該虛擬頁面的頁框號。  得到頁號 ---> 查詢頁表(如果“在/不在”標誌位爲1)---> 得到頁框號 ---> 加上偏移量 ---> 物理地址。
頁表的目的是把虛擬頁面映射爲頁框,類似一個函數,參數是虛擬頁號,結果是物理頁框號,通過這個函數可以把虛擬地址中的虛擬頁域替換成頁框域,從而形成物理地址。
每個進程都有自己的頁表?
頁表項的結構:
    1)高速緩存禁止位:保證硬件是不斷從設備中讀取數據而非訪問一箇舊的被高速緩存的副本,對映射到設備寄存器而不是常規內存的頁面而言該特性比較重要。具有獨立的I/O空間而不使用內存映射I/O的機器不需要該位。
    2)保護位:指出一個頁允許什麼類型的訪問;
    3)修改位:記錄頁面的使用情況,記錄該頁是否被修改過,由硬件自動設置修改。如果一個頁面被修改過(即它是“髒”的),則必須把它寫回磁盤,否則只要簡單丟棄即可,有時也被稱爲髒位(dirty bit);
    4)訪問位:用來幫助在發生缺頁中斷時選擇要淘汰的頁面;在多頁面置換算法中有很重要的作用。
    5)“在/不在”位:記錄該表項對應的虛擬頁面是否在內存中,是否觸發缺頁中斷;
    6)頁框號。
注意頁表並不會保存頁面的磁盤地址。
虛擬內存的實現,是將虛擬地址空間分解成頁,並將每一頁映射到物理內存的某個頁框或者(暫時)解除映射。
分頁需要考慮的問題:
    1)虛擬地址到物理地址的映射必須非常快;
    2)如果虛擬地址空間很大,頁表也會很大。
對大而快速的頁映射的需求成爲了構建計算機的重要約束。
    極端方法1:快速硬件寄存器 ==> 當啓動一個進程時,操作系統把保存在內存中的進程頁表的副本載入到寄存器中,每次上下文切換都必須裝載整個頁表,降低了性能。
    極端方法2:整個頁表都在內存中
有了分頁後,因爲要訪問頁表導致更多次的訪問內存,降低一半的性能。
虛擬地址到物理地址的映射必須非常快 ==> 轉換檢測緩衝區(Translation Lookaside Buffer, TLB,快表?),又稱爲相關聯儲存器(associate memory)。
    基於現象:大多數程序總是對少量的頁面進行多次的訪問,只有很少的頁表項會被反覆讀取。
    用於將虛擬地址直接映射爲物理地址的小型硬件設備,通常在MMU中,包含少量的表項,每個表項記錄的頁面相關信息(除了虛擬頁號不是必須的)與頁表中域一一對應。
    就是部分頁表保存在硬件寄存器中。
    如果TLB中找到指定虛擬頁面對應的表項:
        訪問操作不違反保護位則返回,否則產生一個保護錯誤。
    如果TLB中沒有找到對應的頁表項才陷入到操作系統中:
        進行正常的頁表查詢,接着中TLB中淘汰一個表項,用找到的頁表項代替它。
        當一個表項被清除出TLB時,將修改爲複製到內存中的頁表項,而除了訪問位,其他值不變。
軟件TLB管理:
    正常情況下對TLB的管理和TLB的失效處理都完全由MMU硬件來實現,只有在內存中沒有找到某個頁面時纔會陷入到操作系統中。
    現在大多數都TLB失效都是生成一個TLB失效問題交給操作系統解決,TLB表項會被操作系統顯示地裝載。
    TLB大的話可以減少失效率。
    軟失效與硬失效:前者頁面在內存中而不再TLB中,而後者頁面也不再內存中需要從磁盤中裝入。
如果虛擬地址空間很大,頁表也會很大 ==> 針對大內存的頁表:
    1)多級頁表:原因之一是避免把全部頁表一直保存在內存中。
        例子:32位的虛擬地址被劃分爲10位的PT1域、10位的PT2域和12位的Offset(偏移量)域,因爲偏移量是12位,所以頁面長度是4KB,有2^20個頁面。整個4G虛擬地址空間被分成1024個4MB的塊。由索引頂級頁表得到的表項中含有二級頁表的地址或頁框號。頂級頁表的表項0指向程序正文的頁表,表項1指向數據的頁表,表項1023指向堆棧的頁表。
        存在的必要性?
        頁表的級別越多,靈活性越大,但是也越複雜。
    2)倒排頁表(inverted page table):就是從頁框中查找某個頁表是否存在。
        多級頁表不適合64位系統,頁表耗費的空間非常大。
        在實際內存中每一個頁框有一個表項,而不是每一個虛擬頁面有一個表項。節省了大量空間,特別是虛擬地址空間比物理內存大得多的時候。
倒排頁面相關的知識、處理大虛存的方法。

3.4 頁面置換算法

發生缺頁中斷時如何從內存中選擇一個頁面換出內存,爲新的頁面騰出空間。
最優頁面置換算法:不可能實現
最近未使用(NRU)頁面置換算法:LRU的很粗糙的近似
    用R位(訪問)和M位(修改)來構造一個簡單的頁面置換算法。R位被定期的(比如在每次時鐘中斷時)清零,以區別最近沒有被訪問的頁面和被訪問的頁面。所以即使是被頻繁訪問的頁面,也會定期出現缺頁中斷?
    根據R位和M位的值對所有頁面分類:  
        第0類:沒有被訪問和修改
        第1類:沒有被訪問,但被修改
        第2類:已被訪問,但沒被修改
        第3類:已被訪問和修改
    NRU隨機地從類編號最小的非空類中選擇一個頁面淘汰之。
先進先出(FIFO)頁面置換算法:可能怕拋棄重要頁面
    最新的頁面放在表尾,最久進入的頁面放在表頭
    發生缺頁中斷時,淘汰表頭的頁面並把新調入的頁面放到表尾。
第二次機會頁面置換算法:比FIFO有大的改善
    在FIFO算法的基礎上改進
    如果最老頁面的R位爲0,則將其置換掉,如果是1,則將R位清零並將其放到表尾,並且修改它的裝入時間使它像剛裝入的一樣,然後繼續搜索。
    就是尋找一個最近的時鐘間隔以來沒有被訪問過的頁面,如果所有頁面都被訪問過了,該算法就簡化爲純粹的FIFO算法。
時鐘頁面置換算法:現實的
    將所有的頁面都保存在一個類似鐘面的環形鏈表中,一個錶針指向最老的頁面。
    遇到R位是0的則淘汰該頁面並把新頁面插入該位置,錶針向前移動一個位置,如果R位爲1則清零,並向前繼續搜索。
最近最少使用(LRU,Least Recently Used)頁面置換算法:很優秀,但很難實現
    困難的是每次訪問內存都必須更新整個鏈表
    一種方法:每個頁面都有一個計數值,如果訪問了該頁面則加1,計數值最小的則是最近最少使用的頁面。
    一種方法:用一個初值爲0的n*n位的矩陣,訪問到頁框k時,首先把k行的位都設置成1,再把k列的位都設置成0。則任何時刻,二進制數值最小的行就是最近最少使用的。 !!!!!
最不常用(NFU,Not Frequently Used)算法:LRU的相對粗略的近似
    每次時鐘中斷,都將頁面的R位加到計數器上,這個計數器大體上跟蹤了各個頁面被訪問的頻繁程度,發生缺頁中斷時則置換計數值最小的頁面。
    一個頁面被頻繁訪問後,即使很長時間沒被訪問其計數值仍被遺留下來並影響了後面的置換操作。
老化(aging)算法:非常近似LRU的有效算法,是對最不常用算法的改進:
    調整:在R位被加進之前先將計數器右移一位,並且R位加到計數器的最左端而不是最右端。  
    發生缺頁中斷時,仍是置換計數器值最小的頁面。
    與LRU算法的區別:
        1)如果兩個頁面的計數值相同,老化算法並不知道哪個頁面是先被訪問的。
        2)老化算法的計數器只有有限位數,限制了對以往頁面的記錄。假設爲8位,那如果兩個頁面的計數器都是0,有可能其中一個頁面上次被訪問是在9個時鐘滴答前,而另一個是在1000個時鐘滴答前,但我們不知道,只能隨機選一個頁面置換。
工作集頁面置換算法:一個進程當前正在使用的頁面的集合稱爲它的工作集(working set)。
    請求調頁(demand paging)策略。
    局部性訪問行爲:在進程運行的任何階段,它都只訪問較少的一部分頁面。並且大多數程序也不是均勻地訪問它們的地址空間的。
    若每執行幾條指令程序就發生一次缺頁中斷,那麼就稱這個程序發生了顛簸(thrashing)。
    不少分頁系統都會設法跟蹤進程的工作集,以確保在讓進程運行以前,它的工作集已在內存中了,該方法稱作工作集模型(working set model)。目的在於減少產生缺頁中斷的次數,在讓進程運行前預先裝入其工作集頁面也稱爲預先調頁(prepaging)。
    預先調頁就是在程序繼續運行之前預先裝入推測出的工作集的頁面。
    定義:工作集就是最近K次內存訪問所使用過的頁面的集合。
    置換算法:當發生缺頁中斷時,淘汰一個不在工作集中的頁面   ===>   需要一種精確方法確定哪些頁面在工作集中。
工作集算法:實現起來開銷很大
工作集時鐘算法:好的有效算法

3.5 分頁系統中的設計問題

如何在互相競爭的可運行進程之間分配內存:局部分配策略與全局分配策略。
	全局算法在通常情況下工作得比局部算法好,當工作集的大小隨進程運行時間發生變化時這種現象更加明顯。
	工作集的大小可能在幾微秒內就會發生改變,而老化位卻要經歷一定的時鐘滴答數纔會發生變化。
	另一種途徑:定期確定進程的數目併爲他們分配相等的份額。
		不合理,30K和300K的進程都分配一樣的份額?
		對每個進程都規定一個最小的頁框數。
		根據進程大小按比例分配頁面,但是必須在程序運行時動態更新。
	使用全局分配策略時,如何爲每個進程分配頁面的數量。
	管理動態內存的一種方法是使用PFF(Page Fault Frequency,缺頁中斷率)算法。
		測量缺頁中斷率:計算每秒的缺頁中斷數。
		金控制分配集的大小。
		PFF盡力讓每個進程的缺頁中斷控制在可接受的範圍內。
	注意一些頁面置換算法適用於局部置換算法也適用於全局置換算法,而一些則只採用局部策略纔有意義。
負載控制:減少系統發生顛簸,將進程交換到磁盤中。
	將進程交換出去以減輕內存需求的壓力是借用了兩級調度的思想,在此過程中一些進程被放到磁盤,此時用一個短期的調度程序來調度剩餘的過程。
頁面大小
	小頁面的好處:
		進程數據如內存段、數據段等不會恰好裝滿整個頁面而導致有些頁面部分爲空的浪費稱爲內部碎片(internal fragmentation)。
		與小頁面相比,大頁面使更多沒有用的程序保留在內存中。程序的分階段運行。
	大頁面的好處:
		頁面小意味着更大的頁表。內存與磁盤的交換傳輸中的大部分時間都花在了尋道和旋轉延遲上,所以小頁面和大頁面的傳輸時間是基本相同,則小頁面會更耗時。
	開銷是頁表和內部碎片的損失。
		在頁面比較小的時候,第一項(頁表大小)大,而頁面設置比較大的時候,則第二項(內存碎片)大。
		如何選擇較優的頁面大小。根據公式的結果選擇出來。
分離的指令空間和數據空間:就是分成了代碼段(I空間)和數據段(D空間)等。
	地址空間小使得程序員對地址空間的使用出現困難。
	當硬件進行取指令操作時,要知道使用I空間和I空間頁表,類似的,對數據的訪問必須通過D空間頁表。
共享頁面:指令空間的頁面共享,但是需要對應的數據結構記錄這些共享頁面。
	多個不同的用戶運行同一個程序是很常見的,通過共享頁面避免在內存中有一個頁面的兩份副本。
	I空間和D空間的分離使多個進程來共享程序變得簡單。
	進程使用相同的I空間頁表和不同的D空間頁表。
		每個進程在它的進程表中都有兩個指針:一個指向I空間,一個指向D空間頁表。
	多個進程共享代碼時,需要考察一個頁面是否被共享,爲了防止A需要的頁面被B替換了。
	共享數據,如fork後父進程和子進程都共享程序文本和數據:
		讓這些進程分別擁有它們自己的頁表,但都指向同一個頁面集合,這些頁面被共享並且被標記爲只讀。
		寫時複製:如果兩個進程只讀,則不作更改,若其中一個進程更新寫操作,則會觸發只讀保護並引發操作系統陷阱,然後生成一個自己的專用副本。(所以前面有講到第一次寫操作也會觸發陷阱)。
共享庫:
	現代操作系統中有很多大型庫被衆多進程使用,如果用上面共享頁面的粒度實現共享,將庫靜態與磁盤上每個程序綁定,將會使他們變得更加龐大。
	未定義外部函數(undefined externals):任何在目標文件中被調用但是沒有被定義的函數。
		任何被未定義外部函數調用但是不存在的函數也會成爲未定義外部函數。
	傳統(靜態庫)的鏈接方式:
		鏈接器會在某些庫如/usr/lib/libc.a中尋找未定義函數,如果找到了則加載到可執行二進制文件中。
		當鏈接器完成任務後,一個可執行二進制文件被寫到磁盤,包括所有需要的函數,而庫中定義但是沒被調用的函數則不會被加載進去。
		還是會存在磁盤空間和內存空間的大量浪費。
	共享庫的鏈接方式:
		鏈接器沒有加載加載被調用的函數,而是加載了一小段能夠在運行時綁定被調用函數的存根例程(stub routine)。
	共享庫不是一次性讀入內存,而是根據需要以頁面爲單位進行加載的。
		如果其他進程已經裝載了某個共享庫,則不會再重新裝載。
	共享庫具有使可執行文件更小、節省內存空間外,共享庫修改後不需要重新編譯調用對應函數的程序。
		商業軟件就可以使用共享庫,而不需要分發源碼給客戶。
	位置無關代碼(position-independent code):只使用相對偏移量的代碼。
		共享庫帶來問題:同個共享庫可能被不同的進程定位在不同的地址上,導致進程1和進程2調用同一個庫函數時,重定位得到的需要跳轉的地址不一樣(注意這裏說的是虛擬地址)。
		在編譯共享庫的時候,通過特殊編譯選項讓編譯器不要產生使用絕對地址的指令,而只使用相對地址。
內存映射文件(memory-mapped file):
	共享庫實際上是一種更爲通用的機制————內存映射文件的一個特例。
	機制思想:進程一個通過發起一個系統調用,將一個文件映射到進程的虛擬地址空間的一部分。
	提供了一種I/O的可選模型:這可以把文件當內存中的大字符數組來使用,而不需要通過讀寫操作來訪問這個文件。
	如果兩個或兩個以上的進程同時映射了同一個文件,則它們可以通過共享內存來通信。
清除策略:如何清除系統中被使用的頁框,從而保存一定數量的空閒頁框。分頁守護進程(paging daemon)。保存一定數目的頁框。清除策略方法之一是使用一個雙指針時鐘,前指針用於將髒指針協會磁盤,後指針用於頁面置換。
虛擬內存接口。
	允許程序員對內存映射進行控制,並可以通過非常規的方法來增強程序的行爲。
	共享內存實現高性能的消息通信系統。進程控制頁面映射。
	分佈式共享內存。

3.6 有關實現的問題

與分頁有關的工作,操作系統要在下面四個時間段裏做與分頁相關的工作
	進程創建時
		要確定程序和數據在初始化時有多大,併爲它們創建一個頁表,要在內存中爲頁表分配空間並對其進行初始化。也就是說頁表是一開始就得分配好的,這也就是爲什麼二級頁表可以節省內存的問題了,如果使用二級頁表則不需要一次性先分配那麼多的空間?
		頁表在進程運行時必須在內存中。
		操作系統要在磁盤交換區中分配空間。
		操作系統要用程序正文和數據對交換區進行初始化,這樣當新進程發生缺頁中斷時,可以調入需要的頁面。
	進程執行時
		必須爲新進程設置MMU,刷新TLB,以清除以前的進程遺留的痕跡。
		PC(程序計數器)所指的頁面是必須裝入內存的。
	缺頁中斷時
		操作系統必須通過讀硬件寄存器來確定是哪個虛擬地址造成了缺頁中斷。
		計算需要的頁面並在磁盤上定位,找到合適頁框存放新頁面,置換新老頁面。
		備份程序計數器,重新執行引起缺頁中斷的指令。
	進程終止時
		釋放進程的頁表、頁面和頁面在硬盤上所佔用的空間。
		如果某些頁面是與其它進程共享的,則最後一個使用它們的進程終止時纔可以釋放內存和硬盤上的頁面。
缺頁中斷處理。缺頁中斷髮生時的事件順序如下:
	1)硬件陷入內核,在堆棧中保存程序計數器。大多數機器將當前指令的各種狀態信息保存在特殊的CPU寄存器中。
	2)啓動一個彙編代碼例程保存通用寄存器和其他易失的信息,以免被操作系統破壞。這個例程將操作系統作爲一個函數來調用。
	3)當操作系統發現一個缺頁中斷時,嘗試發現需要哪個虛擬頁面。通常一個硬件寄存器包含了這一信息,如果沒有的話,操作系統必須檢索程序計數器,取出這條指令,用軟件分析這條指令看看它在缺頁中斷時正在做什麼。
	4)知道發生缺頁中斷的虛擬地址後,操作系統檢查地址是否有效並檢查存取與保護是否一致。不一致則向進程發出信號或殺死該進程。如果地址有效且沒有保護錯誤發生則檢查是否有空閒頁框,沒有的話則執行頁面置換算法尋找一個頁面來淘汰。
	5)如果選擇的頁框“髒”了,安排該頁寫回磁盤,併發生一次上下文切換,掛起產生缺頁中斷的進程,讓其他進程運行直至磁盤傳輸結束。無論如何,該頁框被標記爲忙,以免因爲其他原因而被其他進程佔用。
	6)一旦頁框“乾淨”後(無論是立刻還是在寫回磁盤後),操作系統查找所需頁面在磁盤上的地址,通過磁盤操作將其裝入。該頁面被裝入後,產生缺頁中斷的進程仍然被掛起,並且如果有其他可運行的用戶進程,則選擇另一個用戶進程運行。
	7)當磁盤中斷髮生時,表明該頁已經被裝入,頁表已經更新可以反映它的位置,頁框也被標記爲正常狀態。
	8)恢復發生缺頁中斷指令以前的狀態,程序計數器重新指向這條指令。
	9)調度引發缺頁中斷的進程,操作系統返回調用它的彙編語言例程。
	10)該例程恢復寄存器和其他狀態信息,返回到用戶控件繼續執行,就好像缺頁中斷沒有發生過一樣。
指令備份。如何重新啓動引起陷阱的指令。
	情況一:一條指令通常涉及多次內存訪問,則操作系統需要弄清發生缺頁中斷時的程序計數器值對應位置的字是內存地址還是指令的操作碼。
		引起陷阱的可能是指令的操作數,而這時候需要記錄的是操作數前面的操作指令地址。
	情況二:更糟糕的情況是有些體系結構的尋址方式採用自動增量,增量可能在內存訪問前完成也可能在訪問之後完成。則需要在重啓該指令時考慮是否要將軟件中的寄存器減量從而得到正確的值。
	一種解決方法:使用一個隱藏的內部寄存器,在每條指令執行之前,把程序計數器的內容複製到該寄存器。
	操作系統的設計者也需要考慮解決這個問題。
鎖定內存中的頁面。
	分頁算法是全局算法則有很小機會包含緩衝區的頁面被選中換出內存,如果一個I/O設備正處在對頁面進行DMA傳輸的過程中將該頁面移出,會導致部分緩衝數據被寫入到最新裝入的頁面中。
	應當避免將包含I/O緩衝區的頁面選中換出內存。
	解決方法:
		1)鎖住正在做I/O操作的內存中的頁面以保證它不會被移出內存。鎖住一個頁面通常稱爲在內存中釘住(pinning)頁面;
		2)另一箇中方法是在內核緩衝區中完成所有的I/O操作,然後再將數據複製到用戶頁面。(現在似乎都是這種方法?)
後備存儲。
	頁面被換出時存放在磁盤上的哪個位置。
	在磁盤上分配頁面空間的最簡單算法是在磁盤上設置特殊的交換分區,甚至從文件系統劃分一塊獨立的磁盤(以平衡I/O負載)
		分區中沒有普通的文件系統,始終使用相應分區的起始塊號。
	方法一:新進程啓動後,在交換分區中被分配與其核心映像同等大小的交換分區,與每個進程對應的是其交換區的磁盤地址,即進程映像所保存的地方,這一信息記錄在進程表裏。
		寫回頁面時計算寫會地址簡單,虛擬地址空間中頁面偏移量加到交換區開始地址即可。
		進程啓動前必須初始化交換區,也可以將整個進程映像複製到交換區。
		同樣存在進程啓動後可能增大的問題:爲正文、數據和堆棧區分別保留交換區,並且允許這些交換去在磁盤上多於一個塊。
	方法二:實現什麼也不分配,在頁面換出時爲其分配磁盤空間,並在換入時回收磁盤空間。
		缺點是內存中每個頁面都要記錄相應的磁盤地址,即每個進程都必須有一張表,記錄每一個頁面在磁盤上的位置。
		在磁盤映射表項中包含一個非法的磁盤地址或者一個表示它們未被使用的標記位。
	不能保證總能夠實現固定的交換分區,沒有磁盤分區可用時怎麼處理。
		可以利用正常文件系統中的一個或多個較大的、事前定位的文件。
		程序正文通常是隻讀的,當內存資源進程時可以丟棄它們,需要的時候再從可執行文件中讀入。
策略和機制的分離。
	控制系統複雜度的一種重要方法就是把策略從機制中分離出來。通過使大多數存儲管理器作爲用戶級進程運行,就可以把該原則應用到存儲管理中。
	例子,存儲管理系統被分爲三個部分:
		1)一個底層MMU處理程序
			封裝所有MMU工作的細節,與機器相關。
		2)一個作爲內核一部分的缺頁中斷處理程序
			進程運行出現缺頁中斷時由缺頁中斷程序找出需要哪個虛擬頁面,發送消息給外部頁面調度程序通知發生了什麼問題,然後再由外部頁面調度程序從磁盤中讀入所需頁面。
		3)一個運行在用戶空間中的外部頁面調度程序
			一個進程啓動時,需要通知外部頁面調度程序以便建立進程頁面映射,如果需要的話還要在磁盤上分配後備存儲。
			進程運行時可能要把新對象映射到它的地址空間,還需要再次通知外部頁面調度程序。
			原則:無法訪問所有頁面的R位和M位,由缺頁中斷告訴它淘汰頁面及對應的信息。
	更多的模塊化代碼和更好的適應性,但由於多次交叉“用戶-內核”邊界引起額外的開銷,以及系統模塊間消息傳遞所造成的額外開銷。

3.7 分段

待添加。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章