我們知道操作系統中內存可以分為虛擬內存、物理內存和共享內存,以L(fǎng)inux系統為例,使用free -m命令查看當前系統內存使用情況:
上圖中的total表示系統總共物理內存大小,used、free分別表示已使用、未使用的物理內存,buff表示寫(xiě)緩存,cache表示讀緩存。寫(xiě)緩存就是將將要寫(xiě)的數據寫(xiě)到頁(yè)緩存而不是直接寫(xiě)到磁盤(pán),這樣做的好處是防止進(jìn)程對頁(yè)緩存中的數據再次修改。讀緩存表示內核在讀文件時(shí),首先在已有的頁(yè)緩存中查找所讀取的數據是否已經(jīng)存在。如果該頁(yè)緩存不存在,則一個(gè)新的頁(yè)將被添加到告訴緩存中,然后用從磁盤(pán)中讀取到的數據填充它。如果當前物理內存足夠空閑,那么該頁(yè)將長(cháng)期保留在高速緩存中,使得其他進(jìn)程再使用該頁(yè)中的數據時(shí)不再訪(fǎng)問(wèn)磁盤(pán)。
而如果想查看每個(gè)進(jìn)程的內存使用情況則可以通過(guò)top命令查看:
其中,VIRT、RES、SHR分別表示進(jìn)程使用的虛擬內存、物理內存和共享內存。我們知道物理內存大小是固定的,比如說(shuō)某臺筆記本物理內存是4g,但是可以運行大于4g的游戲軟件,操作系統是如何做到的呢?答案是虛擬內存。虛擬內存就是對主存和I/O設備的抽象,也就是說(shuō)它將物理內存存放不了的內容,存放到I/O設備上,如下圖所示。同時(shí),虛擬內存為每個(gè)進(jìn)程提供了一個(gè)一致的地址空間,比如說(shuō)32位計算機,每個(gè)進(jìn)程的地址范圍是[0,4G](0x00000000 ~ 0xbfffffff分配給用戶(hù)態(tài),而0xc0000000 ~ 0xffffffff分配給核心態(tài))。此外,虛擬內存保護每個(gè)進(jìn)程的地址空間不被其他進(jìn)程破壞。
要實(shí)現虛擬內存和物理內存,以及它們之間的映射,需要通過(guò)物理地址、虛擬地址和分頁(yè)、分段等機制實(shí)現,相關(guān)內容在后續給出。
共享內存就是兩個(gè)或更多進(jìn)程訪(fǎng)問(wèn)同一塊物理內存,當一個(gè)進(jìn)程改變了這塊地址中的內容的時(shí)候,其它進(jìn)程都獲取相應的變更信息。在Linux系統中通過(guò)mmap內存共享映射、XSI共享內存和POSIX共享內存三種內存共享方式。為了幫助大家理解,我詳細介紹下mmap內存共享這種方式。
從圖中可知,每個(gè)進(jìn)程描述符(task_struct)有一個(gè)內存管理屬性(mm,memory manage)指向mm_struct結構體,而mmap就是該結構體中一個(gè)屬性,mmap通過(guò)指向一個(gè)vm_area_struct(區域結構)的鏈表來(lái)控制數據的起始地址,從而實(shí)現直接使用內存地址對文件進(jìn)行訪(fǎng)問(wèn)。
在介紹完虛擬內存、物理內存和共享內存大致情況之后,我們來(lái)看下虛擬內存和物理內存在操作系統層面是如何實(shí)現。首先看下物理地址和邏輯地址。
物理地址和邏輯地址
當未引入邏輯地址之前,如果多個(gè)程序同時(shí)對同一個(gè)物理地址進(jìn)行操作就會(huì )沖突,為了解決這種問(wèn)題我們才引入邏輯地址。同時(shí),由于程序最后調用的是物理地址,因此邏輯地址和物理地址之間就存在某種映射關(guān)系,這種映射通過(guò)一個(gè)硬件設備MMU(內存管理單元)實(shí)現,它包含一個(gè)基址寄存器,存儲每個(gè)程序的開(kāi)始地址。為了幫助大家理解,舉個(gè)簡(jiǎn)單例子,假設程序1的基址寄存器是1000,偏移量是200,那么,它的物理地址就是1200。
分頁(yè)
通過(guò)邏輯地址我們解決了地址沖突問(wèn)題,但是依然存在一個(gè)問(wèn)題:對于進(jìn)程來(lái)說(shuō),它是獨占虛擬內存空間的,比如說(shuō)32位計算機,它的地址范圍是[0,4G],但是大于4G的程序卻無(wú)法運行。為了解決這個(gè)問(wèn)題,我們想出了將部分數據加載到內存中,而不是一次性加載全部數據,其中數據的最小單位是頁(yè)(Page),這也是分頁(yè)技術(shù)名字由來(lái),它的理論基礎是局部性原理:空間局部性和時(shí)間局部性。
分頁(yè)技術(shù)強調每次加載部分數據到內存中,同時(shí),我們將虛擬內存和物理內存按照頁(yè)進(jìn)行劃分,通過(guò)頁(yè)表是維護虛擬頁(yè)和物理頁(yè)的映射關(guān)系,需要注意的是每個(gè)進(jìn)程維護一張頁(yè)表。當頁(yè)表中的虛擬頁(yè)對應的物理頁(yè)是空白時(shí),操作系統會(huì )發(fā)生缺頁(yè)中斷。
分頁(yè)具體流程。以CPU執行MOV (0x560) EAX為例,CPU內部會(huì )將邏輯地址進(jìn)行拆分成頁(yè)號和偏移量,然后將邏輯地址轉換成物理地址。
分段
我們知道分頁(yè)對于用戶(hù)來(lái)說(shuō)是沒(méi)什么邏輯意義的,分頁(yè)是為了完成離散存儲,所有的頁(yè)面大小都一樣。為了讓用戶(hù)更好的理解程序,我們又提出了分段概念,它將程序劃分為若干段部分,也就是說(shuō)分段大小是不固定的,而且每一個(gè)分段都有獨立的功能,例如:代碼段,數據段,棧,堆等。通過(guò)分段技術(shù),我們把內存空間分成一個(gè)個(gè)可以自治的段,而且把內存從一維空間變成了一個(gè)二維空間。此時(shí)虛擬地址有段號和偏移量組成。

段頁(yè)結合
分段技術(shù)的段長(cháng)可以根據需要動(dòng)態(tài)改變,而且便于用戶(hù)理解,但是物理內存分配比較麻煩,基于此,我們提出了段頁(yè)結合,也就是說(shuō)對于邏輯地址我們采用分段管理,而物理地址我們采用分頁(yè)機制。它的流程大致如下:首先根據段表信息,將邏輯地址轉換成另一個(gè)邏輯地址,在轉換的過(guò)程中會(huì )判斷偏移量是否超過(guò)指定長(cháng)度,如果沒(méi)有超過(guò),則根據頁(yè)表將邏輯地址轉換成物理地址。

當訪(fǎng)問(wèn)的頁(yè)不在內存中時(shí)就會(huì )發(fā)生缺頁(yè)中斷,此時(shí)就需要進(jìn)行頁(yè)面置換。目前常見(jiàn)的置換算法有FIFO、LRU和Clock算法。
FIFO(先進(jìn)先出)
先進(jìn)先出算法思想很簡(jiǎn)單,當內存滿(mǎn)了,優(yōu)先置換出最先進(jìn)入內存的頁(yè)面。但是它存在一個(gè)問(wèn)題:經(jīng)常被訪(fǎng)問(wèn)的數據有可能被換入換出,下面我就舉個(gè)簡(jiǎn)單的栗子。假設只有3個(gè)物理頁(yè)面,邏輯頁(yè)面的訪(fǎng)問(wèn)次序是: 7 0 1 2 0 3 0 4。

LRU(最近最少使用)
LRU算法就是所有頁(yè)用棧組成,當棧滿(mǎn)了,且新增加元素沒(méi)有命中,則將棧底元素淘汰,新增元素放到棧頂;當棧滿(mǎn)了,且新增元素命中,則只需要將新增元素移動(dòng)到棧頂位置即可。假設只有3個(gè)物理頁(yè)面,邏輯頁(yè)面的訪(fǎng)問(wèn)次序是: 7 0 1 2 0 3 0 4。

Clock算法
Clock算法是LRU算法的近似實(shí)現,它為每個(gè)頁(yè)加一個(gè)引用位,默認值為0,無(wú)論讀還是寫(xiě),都置為1,它把所有的頁(yè)組成一個(gè)循環(huán)隊列,選擇淘汰頁(yè)的時(shí)候,掃描引用位,如果是1則改成0(相當于再給該頁(yè)面一次存活的機會(huì )),并掃描下一個(gè);如果該引用位是0,則淘汰該頁(yè),換入新的頁(yè)面。假設只有3個(gè)物理頁(yè)面,邏輯頁(yè)面的訪(fǎng)問(wèn)次序是: 3 4 2 6。

聯(lián)系客服