http://blog.csdn.net/al_xin/article/details/38590931html
進程提供給應用程序的關鍵抽象:linux
虛擬存儲器算法
虛擬存儲器是硬件異常、硬件地址翻譯、主存、磁盤文件和內核軟件的完美交互,它爲每一個進程提供了一個大的、一致的和私有的地址空間。經過一個很清晰的機制,虛擬存儲器提供了三個重要的能力:數組
(1)它將主存當作是一個存儲在磁盤上的地址空間的高速緩存,在主存中只保存活動區域,並根據須要在磁盤和主存之間來回傳送數據,經過這種方式,它高效地使用了主存。緩存
(2)它爲每一個進程提供了一致的地址空間,從而簡化了存儲器管理。數據結構
(3)它保護了每一個進程的地址空間不被其餘進程破壞。app
物理和虛擬尋址dom
物理尋址函數
計算機系統的主存被組織成一個由M個連續的字節大小的單元組成的數組。每字節都有一個惟一的物理地址(Physical Address,PA)。第一個字節的地址爲0,接下來的字節的地址爲1,再下一個爲2,依此類推。給定這種簡單的結構,CPU訪問存儲器的最天然的方式就是使用物理地址,咱們把這種方式稱爲物理尋址。工具
虛擬尋址
使用虛擬尋址時,CPU經過生成一個虛擬地址(Virtual Address,VA)來訪問主存,這個虛擬地址在被送到存儲器以前先轉換成適當的物理地址。將一個虛擬地址轉換爲物理地址的任務叫作地址翻譯(address translation)。就像異常處理同樣,地址翻譯須要CPU硬件和操做系統之間的緊密合做。CPU芯片上叫作存儲器管理單元(Memory Management Unit,MMU)的專用硬件,利用存放在主存中的查詢表來動態翻譯虛擬地址,該表的內容是由操做系統管理。
地址空間
地址空間(adress space)是一個非整數地址的有序集合:{0,1,2,...}
若是地址空間中的整數是連續的,那麼咱們說它是一個線性地址空間(linear address space)。在一個帶虛擬存儲器的系統中,CPU從一個有N = 2 ^ n個地址空間中生成虛擬地址,這個地址空間稱爲虛擬地址空間(virtual address space):{0,1,2,3,...,N-1}
一個地址空間的大小是由表示最大地址所須要的倍數來描述的。例如,一個包含N=2^n個地址的虛擬地址空間叫作一個n位地址空間。如今系統典型地支持32位或者64位虛擬地址空間是。
一個系統還有一個物理地址空間(physical addresss space),它與系統中物理存儲器的M字節相對應:{0,1,2,...M-1}
M不要求是2的冪,可是爲了簡化討論,咱們假設M = 2 ^ m。
地址空間的概念是很重要的,由於它清楚地區分了數據對象(字節)和它們的屬性(地址)。一旦認識到了這種區別,那麼咱們就能夠將其推廣,容許每一個數據對象有多個獨立的地址,其中每一個地址都選自一個不一樣的地址空間(不連續的意思嗎?)。這就是虛擬存儲器的基本思想。主存中每一個字節都有一個選自虛擬地址空間的虛擬地址和一個選自物理地址空間的物理地址。(這段沒怎麼看懂~~)
虛擬存儲器做爲緩存的工具
概念上而言,虛擬存儲器(VM)被組織爲一個由存放在磁盤上N個連續的字節大小的單元組成的數組。每一個字節都有一個惟一的虛擬地址,這個惟一的虛擬地址是做爲到數組的索引的。磁盤上的數組的內容被緩存在主存中。和存儲器層次結構中其餘緩存同樣,磁盤(較低層)上的數據被分割成塊,這些塊做爲磁盤和主存(較高層)之間的傳輸單元。VM系統經過將虛擬存儲器分割稱爲虛擬頁(Vitual Page,VP)的大小固定的塊來處理這個問題。每一個虛擬頁的大小爲P = 2 ^ n字節。相似地,物理存儲器被分割爲物理頁(Physical Page,PP),大小也爲P字節(物理頁也稱爲頁幀(page frame))。
在任意時刻,虛擬頁面的集合都分爲三個不相交的子集:
頁表
同任何緩存同樣,虛擬存儲器系統必須有某種方法來斷定一個虛擬頁是否存放在DRAM中的某個地方。若是是,系統還必須肯定這個虛擬頁存放在哪一個物理頁中。若是不命中,系統必須判斷這個虛擬頁存放在磁盤的哪一個位置,在物理存儲器中選擇一個犧牲頁,並將虛擬頁從磁盤拷貝到DRAM中,替換這個犧牲頁。
這些功能是由許多軟硬件聯合提供的,包括操做系統軟件,MMU(存儲器管理單元)中地址翻譯硬件和一個存放在物理存儲器中叫作頁表(page table)的數據結構,頁表將虛擬頁映射到物理頁。頁表就是一個頁表條目(Page Table Entry,PTE)的數組。
Linux虛擬存儲器系統
linux爲每一個進程維持了一個單獨的虛擬地址空間。
內核虛擬存儲器包含內核中的代碼和數據結構。內核虛擬存儲器的某些區域被映射到全部進程共享的物理頁面。例如,每一個進程共享內核的代碼和全局數據結構。
一、Linux虛擬存儲器區域(Windows下也有區域的概念)
Linux將虛擬存儲器組織成一些區域(也叫作段)的集合。一個區域(area)就是已經存在着的(已分配的)虛擬存儲器的連續片(chunk),這些頁是以某種方式相關聯的。例如,代碼段、數據段、堆、共享庫段,以及用戶棧都不一樣的區域。每一個存在的虛擬頁面保存在某個區域中,而不屬於某個區域的虛擬頁是不存在的,而且不能被進程引用。區域的概念很重要,由於它容許虛擬地址空間有間隙。內核不用記錄那些不存在的虛擬頁,而這樣的頁也不佔用存儲器。磁盤或者內核自己的任何額外資源。
內核爲系統中的每一個進程維護一個單獨的任務結構(源代碼中的task_struct)。任務結構中的元素包含或者指向內核運行該進程所須要的全部信息(例如,PID,指向用戶棧的指針、可執行的目標文件的名字以及程序計數器)。
task_struct中的一個條目指向mm_struct,它描述了虛擬存儲器中的當前狀態。其中pgd指向第一級頁表(頁全局目錄)的基址,而mmap指向一個vm_area_struct(區域結構)的鏈表,其中每一個vm_area_structs都描述了當前虛擬地址空間的一個區域(area)。當內核運行這個進程時,它就將pgd存放在CR3控制寄存器中。
一個具體區域結構包含下面的字段:
共享對象的關鍵點在於即便對象被映射到了多個共享區域,物理存儲器也只須要存放共享對象的一個拷貝。
一個共享對象(注意,物理頁面不必定是連續的。)
私有對象是使用一種叫作寫時拷貝(copy-on-write)的巧妙技術被映射到虛擬存儲器中的。對於每一個映射私有對象的進程,相應私有區域的頁表條目都被標記爲只讀,而且區域結構被標記爲私有的寫時拷貝。
再看fork函數
當fork函數被當前進程調用時,內核爲新進程建立各類數據結構,並分配給它一個惟一的PID。爲了給這個新進程建立虛擬存儲器,它建立了當前進程的mm_struct、區域結構和頁表的原樣拷貝。它將兩個進程中的每一個頁面都爲標記只讀,並將兩個進程中的每一個區域結構都標記爲私有的寫時拷貝。
當fork在新進程中返回時,新進程如今的虛擬存儲器恰好和調用fork時存在的虛擬存儲器相同。當這兩個進程中的任一個後來進行寫操做時,寫時拷貝機制就會建立新頁面,所以,也就爲每一個進程保持了私有地址空間的抽象概念。
再看execve函數
假設運行在當前進程中的程序執行了以下的調用:
execve("a.out",NULL,NULL) ;
execve函數在當前進程中加載並運行包含在可執行目標文件a.out中的程序,用a.out程序有效地替代了當前程序。加載並運行a.out須要如下幾個步驟:
一、須要額外的虛擬存儲器時,使用一種動態存儲器分配器(dynamic memory allocator)。一個動態存儲器分配器維護着一個進程的虛擬存儲器區域,稱爲堆(heap)。在大多數的unix系統中,堆是一個請求二進制0的區域;對於每一個進程,內核維護着一個變量brk,它指向堆的頂部。
二、分配器將堆視爲一組不一樣大小的塊(block)的集合來維護。每一個塊就是一個連續的虛擬存儲器組塊(chunk),要麼是已分配的,要麼是未分配的。
1)顯式分配器(explicit allocator):如經過malloc,free或C++中經過new,delete來分配和釋放一個塊。
2)隱式分配器(implicit allocator):也叫作垃圾收集器(garbage collector)。自動釋放未使用的已分配的塊的過程叫作垃圾回收(garbage collection)。
三、malloc不初始化它返回的存儲器,calloc是一個基於malloc的包裝(wrapper)函數,它將分配的存儲器初始化爲0。想要改變一個之前已分配的塊的大小,可使用realloc函數。
四、分配器必須對齊塊,使得它們能夠保存任何類型的數據對象。在大多數系統中,以8字節邊界對齊。
不修改已分配的塊:分配器只能操做或者改變空閒塊。一旦被分配,就不容許修改或者移動它。
五、碎片(fragmentation)
有內部碎片(internal)和外部碎片(external)。
外部碎片:在一個已分配塊比有效載荷在時發生的。(如對齊要求,分配最小值限制等)
外部碎片:當空閒存儲器合計起來足夠知足一個分配請求,可是沒有一個單獨的空閒塊足夠大能夠來處理這個請求時發生的。
六、隱式空間鏈表
放置分配的塊的策略有:首次適配(first fit),下一次適配(next fit),和最佳適配(best fit)。
若是空閒塊已經最大程度的合併,而仍然不能生成一個足夠大的塊,來知足要求的話,分配器就會向內核請求額外的堆存儲器,要麼是經過調用nmap,要麼是經過調用sbrk函數;分配器都會將額外的(增長的)存儲器轉化成一個大的空閒塊,將這個塊插入到空閒鏈表中,而後將被請求的塊放置在這個新的空閒塊中。
七、書中對分配器的設計舉了一個小例子,10.9.12節。
八、一種流行的減小分配時間的方法,稱爲分離存儲(segregated storage),維護多個空閒鏈表,其中每一個鏈表中的塊有大體相等的大小。
關於「簡單分離存儲」、「分離適配」、「夥伴系統」等概念,10.9.14節進行了敘述。
一、垃圾收集器將存儲器視爲一張有向可達圖(reachability graph)。
二、Mark%Sweep垃圾收集器由標記(mark)階段和清除(sweep)階段組成。標記階段標記出根節點的全部可達的和已分配的後繼,然後面的清除階段釋放每一個被標記的已分配塊。典型地,塊頭部中空閒的低位中的一位來表示這個塊是否被標記了。
Note that the arrows in this example denote memory references, and not free list pointers.
三、在10.11中,講述了與存儲器相關的錯誤,值得一讀。
如:指針的算術運算是以它們指向的對象的大小爲單位來進行的。