操作系統之-頁面置換算法(java代碼模擬)

一:頁面置換算法簡介

在進程運行過程中,若其所要訪問的頁面不在內存而需把它們調入內存,但內存已無

空閒空間時,爲了保證該進程能正常運行,系統必須從內存中調出一頁程序或數據送磁盤

的對換區中。但應將哪個頁面調出,須根據一定的算法來確定。通常,把選擇換出頁面的

算法稱爲頁面置換算法(Page-Replacement Algorithms)。置換算法的好壞,將直接影響到系統

的性能。

一個好的頁面置換算法,應具有較低的頁面更換頻率。從理論上講,應將那些以後不

再會訪問的頁面換出,或把那些在較長時間內不會再訪問的頁面調出。目前存在着許多種

置換算法,它們都試圖更接近於理論上的目標。

二:常用到的頁面置換算法

1、最佳置換算法(Optimal)) (該算法主要是用來度量其他算法)

最佳置換算法是由Belady於1966年提出的一種理論上的算法。其所選擇的被淘汰頁面,

將是以後永不使用的,或許是在最長(未來)時間內不再被訪問的頁面。採用最佳置換算法,

通常可保證獲得最低的缺頁率。但由於人們目前還無法預知一個進程在內存的若干個頁面

中,哪一個頁面是未來最長時間內不再被訪問的,因而該算法是無法實現的,但可以利用

該算法去評價其它算法。

2、先進先出置換算法(FIFO)

這是最早出現的置換算法。該算法總是淘汰最先進入內存的頁面,即選擇在內存中駐

留時間最久的頁面予以淘汰。先進先出這個特性會想到的是隊列,算法思想就是先寫入到同一個隊列

裏面,然後設置一個指針(替換指針),讓它指向最老的頁面。

下面就是Java代碼模擬FIFO置換算法:

使用隊列記錄對應內存空間位置以及頁面數據:

PrintUtils這個工具類主要輸出的是過程數據
/** * 沒有用計數方法,使用的隊列記錄對應位置 * @param numbers * @param memorySize */public static void FIFOPages(int[] numbers, int memorySize) {    // 模擬內存空間    int[] pages = new int[memorySize];    Arrays.fill(pages, -1);    String[] ways = new String[memorySize];    Queue<Page> queue = new LinkedList<>();    // 缺頁次數    int sum = 0;    // 缺頁記錄    boolean[] sums = new boolean[numbers.length];    for (int i = 0; i < numbers.length; i++) {        int number = numbers[i];        long count = queue.stream().filter(page -> page.getData().equals(number)).count();        if (count <= 0) {            int size = queue.size();            if (size >= memorySize) {                size = queue.poll().getMemoryPosition();            }            queue.add(new Page(number, size));            pages[size] = numbers[i];            sum++;            sums[i] = true;        }        PrintUtils.setWays(ways, pages);    }    PrintUtils.printResult(numbers, sums, memorySize, sum, ways);}@Setter@Getter@AllArgsConstructor@ToStringpublic class Page {    private Integer data;    private Integer memoryPosition;}

3、最近最久未使用置換算法(LRU:Least Recently Used)

LRU 置換算法雖然是一種比較好的算法,但要求系統有較多的支持硬件。爲了瞭解一

個進程在內存中的各個頁面各有多少時間未被進程訪問,以及如何快速地知道哪一頁是最

近最久未使用的頁面

模擬代碼:

public static void URLPage(int[] numbers, int memorySize) {    // 模擬內存空間    int[] pages = new int[memorySize];    Arrays.fill(pages, -1);    // 頁面計數    int[] counts = new int[memorySize];    String[] ways = new String[memorySize];    // 缺頁次數    int sum = 0;    // 缺頁記錄    boolean[] sums = new boolean[numbers.length];    for (int i = 0; i < numbers.length; i++) {        if (!havePage(pages, numbers[i], counts)) {            int maxCount = getMaxCount(counts);            pages[maxCount] = numbers[i];            counts[maxCount] = 1;            sum++;            sums[i] = true;        }        PrintUtils.setWays(ways, pages);    }    PrintUtils.printResult(numbers, sums, memorySize, sum, ways);}

/** * 獲取最先進入的數據,如果計數最大說明是最先進來的,並且每一個對應計數+1 * * @param counts * @return */private static int getMaxCount(int[] counts) {    int index = 0;    int max = 0;    for (int i = 0; i < counts.length; i++) {        if (counts[i] == -1) {            index = i;            break;        }        if (counts[i] > max) {            max = counts[i];            index = i;        }        counts[i]++;    }    return index;}
/** * 查看當前內存塊中是否有當前頁,如果沒有就是要置換掉最先進入的頁,並且計數+1,有就計數變爲1,因爲已經被訪問了 * * @param pages * @param page * @param counts * @return */private static boolean havePage(int[] pages, int page, int[] counts) {    boolean isHave = false;    for (int i = 0; i < pages.length; i++) {        counts[i]++;        if (pages[i] == page) {            isHave = true;            counts[i] = 1;        }    }    return isHave;}

4、Clock置換算法

  (1)簡單的Clock置換算法

當採用簡單 Clock 算法時,只需爲每頁設置一位訪問位,再將內存中的所有頁面都通過

鏈接指針鏈接成一個循環隊列。當某頁被訪問時,其訪問位被置 1。置換算法在選擇一頁淘

汰時,只需檢查頁的訪問位。如果是 0,就選擇該頁換出;若爲 1,則重新將它置 0,暫不

換出,而給該頁第二次駐留內存的機會,再按照 FIFO 算法檢查下一個頁面。當檢查到隊列

中的最後一個頁面時,若其訪問位仍爲 1,則再返回到隊首去檢查第一個頁面。圖 4-31 示

出了該算法的流程和示例。由於該算法是循環地檢查各頁面的使用情況,故稱爲 Clock 算法。

但因該算法只有一位訪問位,只能用它表示該頁是否已經使用過,而置換時是將未使用過

的頁面換出去,故又把該算法稱爲最近未用算法 NRU(Not Recently Used)

模擬代碼如下:

public static void ClocksPage(int[] numbers, int memorySize) {    // 模擬內存空間    int[] pages = new int[memorySize];    Arrays.fill(pages, -1);    // 頁面計數    int[] counts = new int[memorySize];    int index = 0;    String[] ways = new String[memorySize];    // 缺頁次數    int sum = 0;    // 缺頁記錄    boolean[] sums = new boolean[numbers.length];    for (int i = 0; i < numbers.length; i++) {        int number = numbers[i];        if (havePage(pages, number, counts)) {            index = (index + 1) % memorySize;        } else {            while (true) {                int indexs = (index + 1) % memorySize;                if (pages[index] == -1 || (counts[index] == 0 && pages[index] != number)) {                    pages[index] = number;                    counts[index] = 1;                    sum++;                    sums[i] = true;                    index = indexs;                    break;                }                if (pages[index] != number) {                    counts[index] = 0;                    index = indexs;                }            }        }        PrintUtils.setWays(ways, pages);    }    PrintUtils.printResult(numbers, sums, memorySize, sum, ways);}
/** * 查看當前內存塊中是否有當前頁 * * @param pages * @param page * @param counts * @return */private static boolean havePage(int[] pages, int page, int[] counts) {    boolean isHave = false;    for (int i = 0; i < pages.length; i++) {        if (pages[i] == page) {            isHave = true;            counts[i] = 1;        }    }    return isHave;}

   (2)改進型Clock置換算法

簡單時鐘只有一個標記位,但是改進型是多加了一個標記位,簡單時鐘最多遍歷2遍,改進型的最多比遍歷4遍。

算法書裏面是這樣描述的:

假設 search標識訪問標記位,update表示修改標記位,下面就有這樣的一個規則

  •  

  • 1 類( search  =0, update  =0):表示該頁最近既未被訪問,又未被修改,是最佳淘汰頁。

  • 2 類( search  =0, update  =1):表示該頁最近未被訪問,但已被修改,並不是很好的淘汰頁。

  • 3 類( search  =1, update  =0):表示該頁最近已被訪問,但未被修改,該頁有可能再被訪問。

  • 4 類( search  =1, update  =1):表示該頁最近已被訪問且被修改,該頁可能再被訪問。 

(1) 從指針所指示的當前位置開始,掃描循環隊列,尋找 search  =0 且  update  =0 的第一類頁面,

將所遇到的第一個頁面作爲所選中的淘汰頁。在第一次掃描期間不改變訪問位  search  。

(2) 如果第一步失敗,即查找一週後未遇到第一類頁面,則開始第二輪掃描,尋找  search  =0

且  update  =1 的第二類頁面,將所遇到的第一個這類頁面作爲淘汰頁。在第二輪掃描期間,將所

有掃描過的頁面的訪問位都置 0。

(3) 如果第二步也失敗,亦即未找到第二類頁面,則將指針返回到開始的位置,並將所

有的訪問位復 0。然後重複第一步,如果仍失敗,必要時再重複第二步,此時就一定能找到

被淘汰的頁。

該算法與簡單 Clock 算法比較,可減少磁盤的 I/O 操作次數。但爲了找到一個可置換的

頁,可能須經過幾輪掃描。換言之,實現該算法本身的開銷將有所增加。

模擬代碼如下:

public static void ClocksPage(int[] numbers, int memorySize) {    // 模擬內存空間    int[] pages = new int[memorySize];    Arrays.fill(pages, -1);    // 記錄訪問狀態    int[] search = new int[memorySize];    // 記錄修改狀態    int[] update = new int[memorySize];    int index = 0;    String[] ways = new String[memorySize];    // 缺頁次數    int sum = 0;    // 缺頁記錄    boolean[] sums = new boolean[numbers.length];    for (int i = 0; i < numbers.length; i++) {        int number = numbers[i];        boolean isHave = false;        for (int page : pages) {            if (page == number) {                isHave = true;                break;            }        }        if (!isHave) {            sums[i] = true;            sum++;            int count = 0;            int start = index;            while (true) {                int j = (index + 1) % memorySize;                if (j == start) {                    ++count;                }
                if (count == 0 || count == 2) {                    if (search[index] == 0 && update[index] == 0) {                        pages[index] = number;                        search[index] = 1;                        update[index] = 1;                        index = j;                        break;                    }                } else {                    if (search[index] == 0 && update[index] == 1) {                        pages[index] = number;                        search[index] = 1;                        update[index] = 1;                        index = j;                        break;                    }                }                if (count != 0) {                    search[index] = 0;                }                index = j;            }        }        PrintUtils.setWays(ways, pages);    }    PrintUtils.printResult(numbers, sums, memorySize, sum, ways);}

 

5、最少使用置換算法(LFU:Least Frequently Used)

在採用最少使用置換算法時,應爲在內存中的每個頁面設置一個移位寄存器,用來記

錄該頁面被訪問的頻率。該置換算法選擇在最近時期使用最少的頁面作爲淘汰頁。由於存

儲器具有較高的訪問速度,例如 100 ns,在 1 ms 時間內可能對某頁面連續訪問成千上萬次,

因此,通常不能直接利用計數器來記錄某頁被訪問的次數,而是採用移位寄存器方式。每

次訪問某頁時,便將該移位寄存器的最高位置 1,再每隔一定時間(例如 100 ms)右移一次。

這樣,在最近一段時間使用最少的頁面將是∑Ri 最小的頁。

LFU 置換算法的頁面訪問圖與 LRU 置換算法的訪問圖完全相同;或者說,利用這樣一

套硬件既可實現 LRU 算法,又可實現 LFU 算法。應該指出,LFU 算法並不能真正反映出

頁面的使用情況,因爲在每一時間間隔內,只是用寄存器的一位來記錄頁的使用情況,因

此,訪問一次和訪問 10 000 次是等效的。

6、頁面緩衝算法(PBA:Page Buffering Algorithm)

雖然 LRU 和 Clock 置換算法都比 FIFO 算法好,但它們都需要一定的硬件支持,並需

付出較多的開銷,而且,置換一個已修改的頁比置換未修改頁的開銷要大。而頁面緩衝算

法(PBA)則既可改善分頁系統的性能,又可採用一種較簡單的置換策略。VAX/VMS 操作系

統便是使用頁面緩衝算法。它採用了前述的可變分配和局部置換方式,置換算法採用的是

FIFO。該算法規定將一個被淘汰的頁放入兩個鏈表中的一個,即如果頁面未被修改,就將

它直接放入空閒鏈表中;否則,便放入已修改頁面的鏈表中。須注意的是,這時頁面在內

存中並不做物理上的移動,而只是將頁表中的表項移到上述兩個鏈表之一中。

空閒頁面鏈表,實際上是一個空閒物理塊鏈表,其中的每個物理塊都是空閒的,因此,

可在其中裝入程序或數據。當需要讀入一個頁面時,便可利用空閒物理塊鏈表中的第一個

物理塊來裝入該頁。當有一個未被修改的頁要換出時,實際上並不將它換出內存,而是把

該未被修改的頁所在的物理塊掛在自由頁鏈表的末尾。類似地,在置換一個已修改的頁面

時,也將其所在的物理塊掛在修改頁面鏈表的末尾。利用這種方式可使已被修改的頁面和

未被修改的頁面都仍然保留在內存中。當該進程以後再次訪問這些頁面時,只需花費較小

的開銷,使這些頁面又返回到該進程的駐留集中。當被修改的頁面數目達到一定值時,例

如 64 個頁面,再將它們一起寫回到磁盤上,從而顯著地減少了磁盤 I/O 的操作次數。一個

較簡單的頁面緩衝算法已在 MACH 操作系統中實現了,只是它沒有區分已修改頁面和未修

改頁面。

參考書籍:《計算機操作系統第三版》湯小丹等著

完整代碼地址:

https://gitee.com/yh128/SpringDemoProject/tree/master/blog-code

 

 

                                            文章同時會更新到公衆號,覺得對你有幫助或者有用的可以關注一下哦 

 

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