如何使用物理內(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)>>