進程是如何使用內存的?

程序運行概述linux

程序(咱們這裏只討論單進程狀況,存在多進程的程序如淘寶微信等不展開討論)鏡像存在磁盤中,運行時將鏡像加載至內存RAM中,而後開始執行。算法

先來看一下CPU的多級存儲結構,CPU通用寄存器訪問速度最快,其次是Cache,再次是內存,磁盤訪問速度最慢。緩存

CPU的多級存儲結構微信

對於進程而言,可以使用的地址空間爲2^32=4G,那麼對於只有2G內存甚至只有256M內存的嵌入式設備怎麼辦?這個時候就須要MMU負責將進程的虛擬地址轉換爲內存的真實物理地址。如何管理這種映射關係呢?內核中爲每一個進程分配了進程頁表。對應下圖能夠看到,只有正在使用的虛擬地址空間纔會真正分配到物理頁框;同時對於內核空間地址映射對於不一樣進程物理地址同樣。app

                                                 進程地址映射url

TLB工做原理spa

上圖中不一樣進程映射到物理內存使用到了TLB表(地址變換高速緩存),流程爲:CPU獲取數據或指令:獲取進程頁表,拿到物理地址;訪問內存物理地址拿到真實數據。.net

每次執行指令,先從TLB表中獲取,若是未命中,則從內存中獲取,同時根據LRU方法更新TLB表。這裏提一下,LRU更新方法在不少地方都用獲得,像CDN頁面緩存、CPU cache緩存等,如何消除cache顛簸的影響,是另外一個話題。設計

局部性原理:時間及空間局部性,即CPU訪問某個邏輯地址的數據時,大機率會繼續訪問該虛擬地址相鄰的地址。所以爲了保證cache的命中率,通常CPU採用多級流水線設計,將預取指令及相鄰的地址空間存放至TLB表中。code

每一個核都有本身的TLB,由MMU內存管理單元模塊執行,流程爲:CPU發送執行進程的虛擬地址給MMU模塊,MMU對應的硬件電路獲取TLB表物理地址並訪問數據。

若是進程使用了4G虛擬地址,那麼所須要的頁表項條目爲:

4G/4K=1M

每一個頁表項爲4Byte,所以進程頁表佔據了4M物理內存空間。這種狀態下可使用多級頁表減小內存佔用,且能夠離散存儲。

 

從malloc提及

使用malloc分配16k空間,這16k不會當即佔用16k真實內存,而是採用寫時複製的方式使用。若是物理內存已經寫滿數據怎麼辦(可能被多個進程佔用)?這個時候就繼續使用上面提到的LRU方式進行頁表置換。置換過程當中會將換出的頁表真實寫入磁盤中,通常由daemon守護進程完成。

前面使用malloc分配空間以後,linux內核並未真正給該進程分配物理頁,若是對該地址進行寫動做,會由MMU觸發缺頁中斷,這時進入內核終端處理程序,將數據從磁盤加載至內存。

 

                                                    CPU尋址流程

下面總結一下CPU尋址流程:CPU將進程虛擬地址經過地址總線發送給MMU,由MMU硬件電路轉換成物理地址,而後經過數據總線訪問內存獲取數據。

CPU獲取物理地址流程以下:

一、CPU發送虛擬地址,MMU查詢自身TLB表,若是命中:根據物理地址訪問數據頁;若是不命中,經過cr3寄存器取出進程頁表物理地址訪問進程頁表。

       二、這裏訪問進程頁表先是從Cache中獲取緩存頁框,若是Cache命中:從Cache中獲取獲得物理地址,更新TLB表。若是不命中,直接訪問內存頁表。

三、進程頁表保存了進程使用過程當中全部的虛擬地址對應的表項,所以經過頁表地址偏移可直接獲取到頁表項,並更新至Cache中。

四、繼續第一步,MMU從Cache中獲取頁表項,並查看虛擬地址是否已分配物理頁框。若分配,則使用LRU算法更新TLB表並經過內存物理地址訪問數據;若沒分配物理頁框,則觸發Page Fault缺頁中斷,進入並執行中斷接管程序。

五、中斷處理程序爲發送的虛擬地址分配真實物理頁框,若是內存數據已滿,根據LRU算法淘汰最久未用的頁面,置換磁盤的進程映像至物理頁框,並更新進程頁表。(用戶空間的缺頁中斷還會判斷是否非法訪問等權限校驗,這裏不展開)

六、中斷處理程序返回,CPU獲取執行權,繼續執行指令。

獲取到了物理地址,根據物理地址獲取實際數據流程爲:MMU經過物理地址查詢Cache,若是緩存命中,CPU直接獲取Cache中數據並繼續執行;若是不命中,那麼根據物理地址獲取內存中的數據,硬件電路將物理頁框存入Cache中,CPU從Cache中獲取數據。

 

CPU獲取數據流程

理想狀態下,當進程局部性較高時,如執行while循環,MMU獲取TLB表命中拿到物理地址,經過物理地址訪問Cache命中拿到真實數據。

 

具體示例代碼分析代碼數據流及執行流

char* ptr =malloc(1*1024*1024); //一、分配內存memcpy(ptr, 'a',10); //二、寫入數據ptr[100] = 'b'; //三、賦值


第一行:char* ptr =malloc(1*1024*1024);

假定malloc分配的虛擬地址是0x00000040,表示【0x00000040-0x00100040】這1M進程虛擬空間被分配成功。接下來咱們看一下進程頁表狀況。

0x00000040  ->  64

0x00100040  ->  1048640

也就是這段虛擬空間佔了1048576塊頁表項。這裏注意,若是是一級頁表,無論有沒有執行malloc,這些頁表項都存在,但若是是多級頁表,只有真實訪問數據的時候這些頁表纔會佔用物理內存空間。接下來計算頁號和頁內偏移:

起始地址虛擬頁號:64/(4*1024)= 0,頁內偏移爲64%(4*1024)= 0x40;

結束地址虛擬頁號:1048640/(4*1024)= 256,頁內偏移爲1048640%(4*1024)= 0x40;

第二行:memcpy (ptr,'g', 10);

根據ptr虛擬地址找到頁表項,發現並無分配物理頁框,觸發缺頁中斷後分配獲得第100頁框。而後CPU將ptr對應虛擬地址日後的10字節空間寫爲‘a’。這裏說一下,雖然進程頁號對應的物理頁框序號不必定相同,由於頁框大小爲4k,因此虛擬地址在進程頁號中的偏移等於映射的物理地址在物理頁框中的偏移

CPU根據虛擬地址按照上一章的流程找到對應物理地址,將第100物理頁框加載至Cache中,CPU將10字節’a’寫入Cache。

第三行:ptr[100] = 'b';

根據局部性原理,這裏訪問的就是同一個虛擬地址附近的數據,所以TLB和Cache都命中。

    能夠看到,進程在執行malloc時是將物理頁框直接分配給虛擬地址,裏面的初始數據有內存的電氣特性決定,是隨機值,所以maoolc出來的空間使用前須要賦值或者執行初始化操做。


本文分享自微信公衆號 - 機械猿(on_ourway)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索