為什么要有虛擬內(nèi)存?

如何使用物理內(nèi)存?

有且僅有這一種辦法:將一個程序加載到內(nèi)存学少,PC指向程序首地址剪个, 在CPU取指執(zhí)行的過程中,內(nèi)存已經(jīng)被使用了版确。 程序是存儲在磁盤上的(先忽略加載的過程)扣囊, 那么程序該加載到內(nèi)存的哪個位置呢?

(40) _main: mov ax,[100]
             ...
(0)         call 40

如果40和0都是真實的物理地址绒疗,為了讓『call 40』好使侵歇,main必須放到物理內(nèi)存中40位置,局限性相當大吓蘑,要是其他程序也想放到40位置呢惕虑,那么先得需要找到空閑內(nèi)存。

如果恰好1000位置空閑磨镶,那么把程序加載到1000位置溃蔫,并1000賦給IP,CPU開始取值執(zhí)行琳猫, 『call 40』又跳到了物理內(nèi)存40位置伟叛,還是不好使。 僅僅修改PC初始地址是不夠的沸移, 還需要另外一個概念:重定位(修改程序中的地址)

linux何時重定位?

編譯連接時/加載時痪伦?

編譯時:編譯期就得確認內(nèi)存的位置,對于一些特殊的嵌入式軟件雹锣,可能是合理的网沾。

加載時:一旦載入內(nèi)存就不能動了,必須常駐內(nèi)存蕊爵!

swap

高級操作系統(tǒng)都應(yīng)有swap的概念辉哥,在物理內(nèi)存不夠時,將一部分睡眠狀態(tài)的進程換出
到磁盤,等到該進程重新?lián)Q入到內(nèi)存時醋旦,其實又需要重定位恒水!因為這個時候物理內(nèi)存的
布局很有可能發(fā)生了變化,這就引出了一個概念:運行時重定位饲齐。

運行時重定位

每執(zhí)行一條指令前都需要地址翻譯:從邏輯地址算出物理地址钉凌。那么采用基地址(base)+偏移量(offset)去定義邏輯地址,
base就放到PCB中好了捂人,基地址的話x86有對應(yīng)的基地址寄存器(EB)御雕,地址翻譯則有相應(yīng)的硬件MMU,那么每個進程都可
以擁有自己的內(nèi)存了滥搭。

那么整理一下思路酸纲,程序載入是整個一起載入內(nèi)存的嗎?

分段

不瑟匆,我們希望有多個分段:代碼/變量/函數(shù)庫/動態(tài)數(shù)組/棧闽坡,這樣可以獨立考慮每個段,不會
寫變量的時候不小心把代碼給改了愁溜,而且每個段擴容也變得很方便疾嗅。
如何管理不同段的段號/基地址/權(quán)限呢?那么就有了LDT表冕象,放到每個進程的PCB中宪迟,對應(yīng)的寄存器為LDTR。
段號 基址 長度 保護
0 180k 150k R
1 360k 60k R/W
比如一條指令:jmp 40交惯,那么當前的CS寄存器為40,假如當前代碼段的段號為0穿仪,
那么查詢段表找到基址加上偏移40席爽,那么就找到了程序跳轉(zhuǎn)的物理地址。
僅僅分段還是會有問題啊片。如果現(xiàn)在有多個段需要分配物理內(nèi)存只锻,那么物理內(nèi)存該怎么分呢?
很容易就能想到需要『空閑段表』和『已分配段表』去維護物理內(nèi)存紫谷。如果發(fā)起分配請求
req=100k,發(fā)現(xiàn)總空閑內(nèi)存>100k祖驱,但是沒有連續(xù)的空閑內(nèi)存>100k,如何分配捺僻?這個問題
就是內(nèi)存碎片,解決這個問題可以將空閑內(nèi)存合并(內(nèi)存縮緊),內(nèi)存縮緊需要進行段的復(fù)制匕坯,
耗費大量的時間束昵!

分頁

  • 從連續(xù)到離散
那么可以把物理內(nèi)存分為多個頁,分配內(nèi)存時直接分配任意幾個空閑頁框葛峻,然后使用頁表來維護
內(nèi)存锹雏,頁表可以有頁號/頁框號/特權(quán)級。比如現(xiàn)在需要分配n個大小的內(nèi)存(k個連續(xù)的頁面)术奖,
并映射到具體的物理內(nèi)存頁框礁遵。由于頁表的工作方式,現(xiàn)在也不需要那么大的連續(xù)內(nèi)存了腰耙!頁框
可以隨機分配到物理內(nèi)存榛丢。

例如『mov [0x2240],%eax』
當前每頁大小為4k,算頁號需要右移12位挺庞,得到頁號為0x2偏移為0x240晰赞,很快就能找到所在
物理內(nèi)存的位置。上述計算過程选侨,為專門的硬件(MMU)完成的掖鱼。進程切換CR3寄存器跟著切換,
頁表就產(chǎn)生了援制,MMU基于頁表來進行計算戏挡。那么頁查找的全過程就完成了。
  • 多級頁表和TLB寄存器

頁表該如何設(shè)計晨仑?

1.所有的頁都有頁表:

  假如當前是32位系統(tǒng)褐墅,可以有4G的尋址空間,頁面大小通常為4k洪己,32位地址會有2^20個頁面妥凳,
如果那么多個頁表項都放在內(nèi)存中逝钥,需要4M內(nèi)存拱镐,如果系統(tǒng)并發(fā)10個進程那么就需要40M內(nèi)存沃琅,
實際上大多數(shù)的邏輯地址根本不會用到岳枷。//根據(jù)程序的『局部性』空繁。

2.用到的頁才有頁表項:

  但是這樣頁號不連續(xù)盛泡,需要用二分查找凯砍,時間復(fù)雜度O(log2n)悟衩。

既要連續(xù)又要降低空間復(fù)雜度座泳,那么就有了多級頁表挑势。

那么就抽出一個頁目錄表作為頁表的索引潮饱,一來頁目錄表保存了所有的頁目錄,可以連續(xù);
二來被使用到的頁目錄才會有對應(yīng)的頁表缕溉,降低了空間占用。

多級頁表引入了新的問題

每次尋址都需要查詢多級頁表勤晚,增加了訪存次數(shù)赐写,哪怕碰巧頁表項在l1緩存中,性能會下降一到兩個
周期端铛。最差的情況下禾蚕,每次都會去內(nèi)存中多取一次數(shù)據(jù)哗总,性能會下降幾十到幾百個周期讯屈。

為了降低這一開銷涮母,那么就有了TLB哈蝇。TLB是一組相聯(lián)快遞存儲炮赦,是一個寄存器,也被稱作快表峡眶。
TLB存儲了最近使用的頁號與其對應(yīng)頁框號峭拘,處理流程如下:

  1.CPU產(chǎn)生一個虛擬地址
  
  2.MMU查詢TLB鸡挠,取出對應(yīng)的頁表項(命中)
    
  3.MMU將這個虛擬地址翻譯成物理地址拣展,傳給內(nèi)存總線
  
  4.內(nèi)存總線將物理地址對應(yīng)的的數(shù)據(jù)返回給對應(yīng)的處理器

如果不命中姓惑,MMU將會查詢高速緩存/內(nèi)存中的頁表于毙,并將新取出的頁表項放到TLB中望众,下圖為TLB命中的情況:
有了TLB后效率呢?基于上述流程可以算一個數(shù)學期望:
  有效訪問時間 = HitR*(TLB+MA)+(1-HitR)*(TLB+2MA)甘耿,其中HitR為TLB命中率竿滨,
TLB和MA分別為訪問耗時毁葱。 
    
按命中率98%計算倾剿,有效訪問時間=98%*(20ns+100ns)+2%*(20ns+200ns)=122ns前痘,近乎訪存一次的開銷芹缔,
上述結(jié)果非常依賴TLB的命中率最欠,該命中率得益于『程序』的『局部性』窒所。截取CS:APP書上的一段話:

  |盡管在整個運行過程中程序引用的不同頁面的總數(shù)可能會超過物理內(nèi)存的總大小,但是『局部性』保證了在
任意時刻锯厢,程序?qū)②呌谠谝粋€較小的active page集合上工作捺氢。在初始開銷剪撬,也就是將工作集頁面調(diào)度到內(nèi)存
中以后,接下來對這個active page的引用將會命中馍佑,而不會產(chǎn)生額外的磁盤流量。|

由此可以得出TLB的命中率非常高疫诽。

段頁結(jié)合 => 虛擬內(nèi)存

結(jié)合分段和分頁雏亚,總結(jié)出:

  1.應(yīng)用程序希望能分段(獨立考慮每個段);
  2.物理內(nèi)存希望能分頁(提高分配效率)罢低。

那么能不能來個東西把他們都組合起來奕短,那么就有了『虛擬內(nèi)存』,對于用戶來虛擬內(nèi)存是透明日杈,而用戶
程序可以以段的方式去訪問虛擬內(nèi)存莉擒。
那么訪問內(nèi)存的方式就變成了『段號+偏移』,用CPU取指執(zhí)行這一訪存動作舉例:

    這里邏輯地址為cs:ip麦萤,先查段表,找到cs段對應(yīng)段號的基址姻檀,再加上ip組合成虛擬地址绣版,推給MMU杂抽,
MMU根據(jù)邏輯地址查詢頁表默怨,拿到物理地址交給內(nèi)存總線匙睹,獲取到對應(yīng)的指令數(shù)據(jù)。如下圖:
  • 『為什么要有虛擬內(nèi)存』理論部分就結(jié)束了梦谜,后面是linux上虛擬內(nèi)存的源碼分析(部分)和虛擬內(nèi)存的一些拓展應(yīng)用

linux內(nèi)存管理源碼分析(linux 0.12)

如何使用內(nèi)存,前面提到過:先把程序『放入內(nèi)存』耸棒,在CPU取指執(zhí)行的過程中就開始『使用內(nèi)存』了单山。
那么linux何時開始進行內(nèi)存管理呢米奸? 很容易想到從fork系統(tǒng)調(diào)用的內(nèi)存分配階段開始:分配段、建段表;
分配頁铡溪、建頁表佃却。

  從sys_fork開始
   
  流程: sys_fork -> copy_process ,所以直接看copy_process的實現(xiàn)
  
  int copy_process(... ){
        //申請一頁空閑物理頁面,下面會講
        struct task_struct *p = get_free_page();
        //給task_struct放一些數(shù)據(jù)
        ...
            //重點看
        copy_mem();
  }
    
  //nr為進程序號
  int copy_mem(int nr, struct task_struct * p){
        unsigned long old_data_base, new_data_base, data_limit;
        unsigned long old_code_base, new_code_base, code_limit;
    
        code_limit = get_limit(0x0f);
        data_limit = get_limit(0x17);
        old_code_base = get_base(current->ldt[1]);
        old_data_base = get_base(current->ldt[2]);
        if (old_data_base != old_code_base) {
            panic("We don't support separate I&D");
        }
        if (data_limit < code_limit) {
            panic("Bad data_limit");
        }
        //TASK_SIZE是linux上每個進程的大性畋谩(64M)赦邻,這里直接在虛擬內(nèi)存上隔離了不同進程的段
        //linux0.12只能映射4G的線性地址惶洲,每個進程大小只給64M,最多64個進程铐料。
        //所以不同進程的虛擬地址完全不重疊,所以進程切換連頁目錄表都不需要切(全局共享一個頁目錄表)篓跛!
        //注意現(xiàn)代linux每個進程都可以獨立映射4G的線性地址(32位)举塔,而且虛擬地址都是會重疊的求泰,每個進程都有獨立的頁目錄表臭墨!
        new_data_base = new_code_base = nr * TASK_SIZE;
        p->start_code = new_code_base;
        //linux 0.12實現(xiàn)比較簡單惰许,代碼段和數(shù)據(jù)段用同一個基址
        //建段表
        set_base(p->ldt[1], new_code_base); //代碼段
        set_base(p->ldt[2], new_data_base); //數(shù)據(jù)段
            
            
        //頁表相關(guān),重點看這個
        if (copy_page_tables(old_data_base, new_data_base, data_limit)) {
            free_page_tables(new_data_base, data_limit);
            return -ENOMEM;
        }
        return 0;
  }

  //復(fù)制頁表,進程的COW機制
  int copy_page_tables(unsigned long from, unsigned long to, long size){
        ...
        //這里為啥右移20位很重要痊硕,由于是32位虛擬地址:[10位頁目錄號][10位頁號][12位偏移]
        //所以需要右移動22位得到頁目錄號艾疟,每個頁目錄項4個字節(jié),為得到具體的頁目錄項指針需要(from >> 22)*4=(from >> 20)
        from_dir = (unsigned long *) ((from >> 20) & 0xffc); //源地址頁目錄項指針
        to_dir = (unsigned long *) ((to >> 20) & 0xffc);     //目標地址頁目錄項指針
        size = ((unsigned) (size + 0x3fffff)) >> 22;
        
        //開始復(fù)制頁目錄項
        for(; size-->0; from_dir++, to_dir++){
            from_page_table=(0xfffff000&*from_dir);
            //新的進程有獨立的頁表蚊俺,所以申請一頁空閑物理頁面懈涛,下面會講
            to_page_table=get_free_page();
            //復(fù)制當前頁表的nr個頁表項
            for (; nr-- > 0; from_page_table++, to_page_table++) {
                    this_page = *from_page_table;
                    if (!this_page) {
                        continue;
                    }
                    //該頁面在交換設(shè)備中,申請一頁新的內(nèi)存泳猬,然后將交換設(shè)備中的數(shù)據(jù)讀取到該頁面中
                    if (!(1 & this_page)) {
                        if (!(new_page = get_free_page())) {
                             return -1;
                        }
                        read_swap_page(this_page >> 1, (char *)new_page);
                        *to_page_table = this_page;
                        *from_page_table = new_page | (PAGE_DIRTY | 7);
                             continue;
                    }
                    this_page &= ~2; // 讓頁表項對應(yīng)的內(nèi)存頁面只讀
                    *to_page_table = this_page;
                    //物理頁面的地址在1MB以上忙上,需要在mem_map[]中增加對應(yīng)頁面的引用次數(shù)
                    if (this_page > LOW_MEM) {
                        *from_page_table = this_page; //令源頁表項也只讀
                        this_page -= LOW_MEM;
                        this_page >>= 12;
                        //增加對應(yīng)頁面的引用次數(shù)
                        mem_map[this_page]++;
                    }
            }
        }
  }
  
  //申請一頁空閑物理頁面
  unsigned long get_free_page(void){
        register unsigned long __res;
        //在內(nèi)存映射字節(jié)位圖中從尾到頭地查找值為0的字節(jié)項手形,然后把對應(yīng)物理內(nèi)存頁面清零
        repeat:
            __asm__("std ; repne ; scasb\n\t"
                "jne 1f\n\t"
                "movb $1,1(%%edi)\n\t"
                "sall $12,%%ecx\n\t"
                "addl %2,%%ecx\n\t"
                "movl %%ecx,%%edx\n\t"
                "movl $1024,%%ecx\n\t"
                "leal 4092(%%edx),%%edi\n\t"
                "rep ; stosl\n\t"
                "movl %%edx,%%eax\n"
                "1:"
                :"=a" (__res)
                :"0" (0), "i" (LOW_MEM), "c" (PAGING_PAGES),
                "D" (mem_map + PAGING_PAGES - 1)
            );
        if (__res >= HIGH_MEMORY) { // 頁面地址大于實際內(nèi)存容量,重新尋找
            goto repeat;
        }
        if (!__res && swap_out()) { // 沒有得到空閑頁面則執(zhí)行交換處理,并重新查找
            goto repeat;
        }
            return __res;
  }

總結(jié):

fork系統(tǒng)調(diào)用會去申請物理頁野建,用來映射task_struct和頁表(每個進程私有)须蜗,接著復(fù)制頁表項柿估,并將『源/新』兩個進程的所有頁表項標記為只讀,
并將兩個進程的每個區(qū)域結(jié)構(gòu)都標記為私有的寫時復(fù)制,最終媚值,fork系統(tǒng)調(diào)用返回锰扶,產(chǎn)生的新進程的地址空間看上去就和源進程一模一樣了京闰。

當這兩個進程的任意一個發(fā)生『寫操作』時乾翔,那么這個寫操作就會觸發(fā)一個『故障』,那么內(nèi)核中的『故障處理程序』就會在物理內(nèi)存中創(chuàng)建所寫頁面的新副本,
更新頁表指向這個新的副本而姐,接著恢復(fù)對應(yīng)虛擬頁面的寫權(quán)限风瘦。ok,『故障處理程序』可以返回了意敛,那么將ip指回剛剛的『寫操作』,CPU重新執(zhí)行重新寫
的時候就可以在新的物理頁上操作了!

這就是所謂進程的copy-on-write機制,

  • 換入
如果是32位操作系統(tǒng),每個進程擁有獨立的4g地址空間拢锹,而物理內(nèi)存大小也就4g,能做到這點也正是前面提過的swap機制。

案例:CPU提供一個邏輯地址(段號加偏移)吧史,MMU查詢頁表時發(fā)現(xiàn)沒有對應(yīng)的物理內(nèi)存頁框,那么MMU就會產(chǎn)生一個『缺頁中斷』,
缺頁中斷處理程序?qū)⒋疟P上對應(yīng)的頁文件換入。一部分邏輯由硬件(MMU)完成区岗,所以換入部分的源碼分析只需要看缺頁中斷
處理程序祭示。
中斷號 名稱 說明
12 Segment not Present 描述符指定的短不存在
14 Page fault 頁不在內(nèi)存
    //中斷處理程序初始化(未縮略)
    void trap_init(void){
        set_trap_gate(14, &page_fault); //保存14號中斷與其處理程序
    }
    #define set_trap_gate(n, addr) \
        _set_gate(&idt[n], 15, 0, addr); 
        
    //直接來看缺頁中斷處理程序(mm/page.s)
    page_fault:
    xchgl   %eax, (%esp)    // 取出錯碼到eax
    pushl   %ecx
    pushl   %edx
    push    %ds
    push    %es
    push    %fs
    
    
    movl    $0x10, %edx     // x86的保護模式下,訪問內(nèi)存需要基于段選擇器(如CS、DS、ES猿诸、SS)
                            // 而各段選擇器與基址的對應(yīng)關(guān)系存在GDT表中(GDT表的首地址位于GDTR寄存器),對應(yīng)前面的段表
                            // 而段表中的段表項在x86里叫段選擇子,組成結(jié)構(gòu):[描述符索引][TI][請求特權(quán)級],其中TI為描述符表指示器(0:要查GDT画拾,1:要查LDT)
                            // x86部分可以看看下面的圖
                            // 這里將0x10傳給edx是為了切換為內(nèi)核的段基址鲁猩,畢竟中斷處理程序是要在內(nèi)核態(tài)執(zhí)行的嘛
    mov     %dx, %ds        // 置為內(nèi)核棧對應(yīng)的數(shù)據(jù)段選擇子(為了后續(xù)查詢段描述符)
    mov     %dx, %es        // 同上
    mov     %dx, %fs        // 同上,這幾行代碼不清楚可以看看下面的圖
    
    
    movl    %cr2, %edx      // 取引起頁面異常的虛擬地址罢坝,cr2則是故障虛擬地址寄存器廓握,還有cr0,cr1,cr2,cr3,cr8
    pushl   %edx            // 將該線性地址和出錯碼壓入棧中,作為將調(diào)用函數(shù)的參數(shù)
    pushl   %eax
    testl   $1, %eax        // 測試頁存在標志P(位0)嘁酿,如果不是缺頁引起的異常則跳轉(zhuǎn)
    jne     1f
    call    do_no_page      // 調(diào)用缺頁中斷處理函數(shù)(mm/memory.c)
    jmp     2f
1:  call    do_wp_page      // 調(diào)用寫保護處理函數(shù)(mm/memory.c)
2:  addl    $8, %esp        // 丟棄壓入棧的兩個參數(shù)隙券,彈出棧中寄存器并退出中斷
    pop     %fs
    pop     %es
    pop     %ds
    popl    %edx
    popl    %ecx
    popl    %eax
    iret
圖中段描述符的段基地址和段界限不是連續(xù)的,而是分成了好幾段痹仙,非常不科學是尔,這是為了兼容80286處理器,
算是一個后遺癥吧开仰。

經(jīng)過一頓折騰拟枚,終于可以看『換入』是咋實現(xiàn)的了!

    //do_no_page众弓,縮略
    void do_no_page(unsigned long error_code , unsigned long address) {
        ...
        address& = 0xfffff000; //頁面地址
        tmp = address–current->start_code; //頁面對應(yīng)的偏移
        if(!current->executable||tmp>=current->end_data){
            get_empty_page(address); 
            return; 
        }
        ...
        page = get_free_page(); //獲取物理頁
        bread_page(page, current->executable->i_dev, nr);//讀塊設(shè)備
        put_page(page, address); //物理頁和地址建立映射(修改頁表)
    }
    
    //修改頁表
    unsigned long put_page(unsigned long page, unsigned long address){
        unsigned long tmp, *page_table;
        //前面提到過為什么要>>20
        //得到頁目錄項的地址
        page_table=(unsigned long *)((address>>20)&ffc);
        if( (*page_table)&1 ){
            page_table=(unsigned long*)(0xfffff000&*page_table);
        } else {
            tmp=get_free_page(); 
            *page_table=tmp|7;
            page_table=(unsigned long*)tmp;
        }
        //最終建立映射
        page_table[(address>>12)&0x3ff] = page|7;
        return page; 
    }
  • 換出

內(nèi)存是有限的恩溅,不可能總是獲取到新的頁,那么物理內(nèi)存不夠的時候需要把一部分頁面換出谓娃。關(guān)于頁面換出涉及好幾個頁面置換算法脚乡,
由于篇幅問題這里只詳細介紹最有效的(較少的缺頁次數(shù))。

FIFO:先入先出滨达。如果是常駐內(nèi)存的數(shù)據(jù)呢奶稠,這塊數(shù)據(jù)在內(nèi)存中停留最久,但是最常使用捡遍,結(jié)果他們因為"先入”卻要被換出了锌订。

MIN:淘汰將來最遠使用的頁,最優(yōu)的方案画株。但沒法預(yù)估將來的事情辆飘。

LRU:用歷史來預(yù)測將來,淘汰最近最長一段時間內(nèi)沒有使用過的頁谓传。前面提到過程序具有『局部性』蜈项,所以這種方案在實際的操作系統(tǒng)中最為有效。

LRU實現(xiàn):

1.很容易想到每個頁維護一個時間戳续挟,這樣就能精準的判斷要淘汰哪個頁面紧卒。但每次訪問物理頁都要去維護時間戳,這是一個很大的開銷诗祸!而且
頁的數(shù)量很大常侦,每次查找最小的時間戳也是一個很大開銷浇冰!這種方式不可行。
2. 維護一個頁碼棧聋亡,每次將最近使用的頁上浮肘习,這樣最少使用的頁會在棧底,但是實現(xiàn)代價還是很大坡倔,每次地址訪問都需要修改棧漂佩,涉及到多次
拷貝。

其實LRU算法是一個很理想化的算法罪塔,對于實際操作系統(tǒng)只能『近似實現(xiàn)』投蝉。紙上得來終覺淺啊征堪!

下面介紹實際操作系統(tǒng)的LRU近似實現(xiàn):

將上面提到的時間戳換成0/1兩種狀態(tài)瘩缆,將所有的頁框維護成一個環(huán)形隊列。
那么當缺頁發(fā)生時佃蚜,如果掃描到的頁的狀態(tài)為0庸娱,就將該頁換出,如果掃描到的頁狀態(tài)為1谐算,那就使用非常接地氣的
『再給一次機會』算法熟尉,把頁狀態(tài)置成0,那么這個頁下次掃描到才會被換出洲脂。當使用該頁的時候?qū)⑵錉顟B(tài)置1斤儿。
本質(zhì)上還是LRU,只不過把『最近最少使用』近似為『最近沒有使用』恐锦。如下圖:
上面提到的置換算法也叫Clock算法往果,但是上面對LRU實現(xiàn)的還是不夠精準。
如果缺頁極少發(fā)生呢一铅,會發(fā)生什么陕贮?所有的頁狀態(tài)都是1,這種情況下一旦發(fā)生缺頁馅闽,指針會scan一圈飘蚯,回到原點的時候
發(fā)現(xiàn)這個頁的狀態(tài)還是0(轉(zhuǎn)一圈的時間內(nèi)還是沒有被使用)馍迄,OK福也,把這頁置換出去。如果一直都是極少發(fā)生缺頁呢(缺頁一段時間后攀圈,
所有頁都被使用過了暴凑,都置成了1),那就變成了一直淘汰下一個赘来,退化成了FIFO现喳。如下圖:
歸根結(jié)底還是狀態(tài)為1的頁太多凯傲,導致每次scan花的時間太長,使LRU失去了『最近』嗦篱,那么需要改進為『CLOCK』算法冰单,
再加一個快指針定時將1置成0(交給時鐘中斷處理),原來的慢指針用來scan沒有使用的頁(缺頁時)灸促。有了快指針诫欠,『最近』就有了,這是一個較為完善的LRU實現(xiàn)浴栽。
如下圖:

那么每個進程分配多少個頁框呢荒叼,換句話說,每個進程分配多大的物理內(nèi)存呢典鸡,這是一個非常實際的問題被廓。分配多確實會減少swap發(fā)生,
但是物理內(nèi)存上限在那萝玷,分配少會出什么問題呢嫁乘?
先看上面這張圖:系統(tǒng)內(nèi)進程增多 -> 每個進程的缺頁率增加 -> 增加到一定程度 -> 進程總是在等待頁swap
->CPU的利用率降低 -> 進程數(shù)持續(xù)增加 -> CPU利用率急劇降低。這就產(chǎn)生了『抖動』间护,這時候系統(tǒng)基本上很卡了亦渗。

不管是進程數(shù)太多,還是物理內(nèi)存小汁尺,導致系統(tǒng)發(fā)生歸根結(jié)底還是每個進程分配的頁框太少法精,前面提到過程序的『局部性』。
可以得到一個結(jié)論痴突,頁框數(shù)一定要完整的覆蓋程序的『局部』搂蜓。這就涉及到了工作集分析。后續(xù)補充...
  • 虛擬內(nèi)存的一些拓展應(yīng)用

    //TODO

  • 參考資料

1. https://pdos.csail.mit.edu/6.828/2018/xv6/book-rev11.pdf

2. <<x86匯編語言:從實模式到保護模式>>

3. <<深入理解計算機系統(tǒng)>>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辽装,一起剝皮案震驚了整個濱河市帮碰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拾积,老刑警劉巖殉挽,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拓巧,居然都是意外死亡斯碌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門肛度,熙熙樓的掌柜王于貴愁眉苦臉地迎上來傻唾,“玉大人,你說我怎么就攤上這事承耿」诮荆” “怎么了伪煤?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凛辣。 經(jīng)常有香客問我抱既,道長,這世上最難降的妖魔是什么扁誓? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任蝙砌,我火速辦了婚禮,結(jié)果婚禮上跋理,老公的妹妹穿的比我還像新娘择克。我一直安慰自己,他們只是感情好前普,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布肚邢。 她就那樣靜靜地躺著,像睡著了一般拭卿。 火紅的嫁衣襯著肌膚如雪骡湖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天峻厚,我揣著相機與錄音响蕴,去河邊找鬼。 笑死惠桃,一個胖子當著我的面吹牛浦夷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辜王,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼劈狐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了呐馆?” 一聲冷哼從身側(cè)響起肥缔,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎汹来,沒想到半個月后续膳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡收班,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年坟岔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闺阱。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡炮车,死狀恐怖舵变,靈堂內(nèi)的尸體忽然破棺而出酣溃,到底是詐尸還是另有隱情瘦穆,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布赊豌,位于F島的核電站扛或,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碘饼。R本人自食惡果不足惜熙兔,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望艾恼。 院中可真熱鬧住涉,春花似錦、人聲如沸钠绍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柳爽。三九已至媳握,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間磷脯,已是汗流浹背蛾找。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赵誓,地道東北人打毛。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像俩功,于是被迫代替她去往敵國和親隘冲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容