
程序(我們這里只討論單進程情況,存在多進程的程序如淘寶微信等不展開討論)鏡像存在磁盤中,運行時將鏡像加載至內(nèi)存RAM中,然后開始執(zhí)行。
先來看一下CPU的多級存儲結(jié)構(gòu),CPU通用寄存器訪問速度最快,其次是Cache,再次是內(nèi)存,磁盤訪問速度最慢。
CPU的多級存儲結(jié)構(gòu)
對于進程而言,可使用的地址空間為2^32=4G,那么對于只有2G內(nèi)存甚至只有256M內(nèi)存的嵌入式設(shè)備怎么辦?這個時候就需要MMU負(fù)責(zé)將進程的虛擬地址轉(zhuǎn)換為內(nèi)存的真實物理地址。如何管理這種映射關(guān)系呢?內(nèi)核中為每個進程分配了進程頁表。對應(yīng)下圖可以看到,只有正在使用的虛擬地址空間才會真正分配到物理頁框;同時對于內(nèi)核空間地址映射對于不同進程物理地址一樣。
進程地址映射
上圖中不同進程映射到物理內(nèi)存使用到了TLB表(地址變換高速緩存),流程為:CPU獲取數(shù)據(jù)或指令:獲取進程頁表,拿到物理地址;訪問內(nèi)存物理地址拿到真實數(shù)據(jù)。每次執(zhí)行指令,先從TLB表中獲取,如果未命中,則從內(nèi)存中獲取,同時根據(jù)LRU方法更新TLB表。這里提一下,LRU更新方法在很多地方都用得到,像CDN頁面緩存、CPU cache緩存等,如何消除cache顛簸的影響,是另一個話題。局部性原理:時間及空間局部性,即CPU訪問某個邏輯地址的數(shù)據(jù)時,大概率會繼續(xù)訪問該虛擬地址相鄰的地址。因此為了保證cache的命中率,一般CPU采用多級流水線設(shè)計,將預(yù)取指令及相鄰的地址空間存放至TLB表中。
每個核都有自己的TLB,由MMU內(nèi)存管理單元模塊執(zhí)行,流程為:CPU發(fā)送執(zhí)行進程的虛擬地址給MMU模塊,MMU對應(yīng)的硬件電路獲取TLB表物理地址并訪問數(shù)據(jù)。如果進程使用了4G虛擬地址,那么所需要的頁表項條目為:4G/4K=1M
每個頁表項為4Byte,因此進程頁表占據(jù)了4M物理內(nèi)存空間。這種狀態(tài)下可以使用多級頁表減少內(nèi)存占用,且可以離散存儲。
使用malloc分配16k空間,這16k不會立即占用16k真實內(nèi)存,而是采用寫時復(fù)制的方式使用。如果物理內(nèi)存已經(jīng)寫滿數(shù)據(jù)怎么辦(可能被多個進程占用)?這個時候就繼續(xù)使用上面提到的LRU方式進行頁表置換。置換過程中會將換出的頁表真實寫入磁盤中,一般由daemon守護進程完成。
前面使用malloc分配空間之后,linux內(nèi)核并未真正給該進程分配物理頁,如果對該地址進行寫動作,會由MMU觸發(fā)缺頁中斷,這時進入內(nèi)核終端處理程序,將數(shù)據(jù)從磁盤加載至內(nèi)存。

CPU尋址流程
下面總結(jié)一下CPU尋址流程:CPU將進程虛擬地址通過地址總線發(fā)送給MMU,由MMU硬件電路轉(zhuǎn)換成物理地址,然后通過數(shù)據(jù)總線訪問內(nèi)存獲取數(shù)據(jù)。
1、CPU發(fā)送虛擬地址,MMU查詢自身TLB表,如果命中:根據(jù)物理地址訪問數(shù)據(jù)頁;如果不命中,通過cr3寄存器取出進程頁表物理地址訪問進程頁表。2、這里訪問進程頁表先是從Cache中獲取緩存頁框,如果Cache命中:從Cache中獲取得到物理地址,更新TLB表。如果不命中,直接訪問內(nèi)存頁表。3、進程頁表保存了進程使用過程中所有的虛擬地址對應(yīng)的表項,因此通過頁表地址偏移可直接獲取到頁表項,并更新至Cache中。4、繼續(xù)第一步,MMU從Cache中獲取頁表項,并查看虛擬地址是否已分配物理頁框。若分配,則使用LRU算法更新TLB表并通過內(nèi)存物理地址訪問數(shù)據(jù);若沒分配物理頁框,則觸發(fā)Page Fault缺頁中斷,進入并執(zhí)行中斷接管程序。5、中斷處理程序為發(fā)送的虛擬地址分配真實物理頁框,如果內(nèi)存數(shù)據(jù)已滿,根據(jù)LRU算法淘汰最久未用的頁面,置換磁盤的進程映像至物理頁框,并更新進程頁表。(用戶空間的缺頁中斷還會判斷是否非法訪問等權(quán)限校驗,這里不展開)6、中斷處理程序返回,CPU獲取執(zhí)行權(quán),繼續(xù)執(zhí)行指令。獲取到了物理地址,根據(jù)物理地址獲取實際數(shù)據(jù)流程為:MMU通過物理地址查詢Cache,如果緩存命中,CPU直接獲取Cache中數(shù)據(jù)并繼續(xù)執(zhí)行;如果不命中,那么根據(jù)物理地址獲取內(nèi)存中的數(shù)據(jù),硬件電路將物理頁框存入Cache中,CPU從Cache中獲取數(shù)據(jù)。

理想狀態(tài)下,當(dāng)進程局部性較高時,如執(zhí)行while循環(huán),MMU獲取TLB表命中拿到物理地址,通過物理地址訪問Cache命中拿到真實數(shù)據(jù)。
具體示例代碼分析代碼數(shù)據(jù)流及執(zhí)行流char* ptr =malloc(1*1024*1024); //1、分配內(nèi)存memcpy(ptr, 'a',10); //2、寫入數(shù)據(jù)ptr[100] = 'b'; //3、賦值
第一行:char* ptr =malloc(1*1024*1024);假定malloc分配的虛擬地址是0x00000040,表示【0x00000040-0x00100040】這1M進程虛擬空間被分配成功。接下來我們看一下進程頁表情況。0x00000040 -> 64
0x00100040 -> 1048640
也就是這段虛擬空間占了1048576塊頁表項。這里注意,如果是一級頁表,不管有沒有執(zhí)行malloc,這些頁表項都存在,但如果是多級頁表,只有真實訪問數(shù)據(jù)的時候這些頁表才會占用物理內(nèi)存空間。接下來計算頁號和頁內(nèi)偏移:起始地址虛擬頁號:64/(4*1024)= 0,頁內(nèi)偏移為64%(4*1024)= 0x40;結(jié)束地址虛擬頁號:1048640/(4*1024)= 256,頁內(nèi)偏移為1048640%(4*1024)= 0x40;
第二行:memcpy (ptr,'g', 10);根據(jù)ptr虛擬地址找到頁表項,發(fā)現(xiàn)并沒有分配物理頁框,觸發(fā)缺頁中斷后分配得到第100頁框。然后CPU將ptr對應(yīng)虛擬地址往后的10字節(jié)空間寫為‘a(chǎn)’。這里說一下,雖然進程頁號對應(yīng)的物理頁框序號不一定相同,因為頁框大小為4k,所以虛擬地址在進程頁號中的偏移等于映射的物理地址在物理頁框中的偏移。
CPU根據(jù)虛擬地址按照上一章的流程找到對應(yīng)物理地址,將第100物理頁框加載至Cache中,CPU將10字節(jié)’a’寫入Cache。
根據(jù)局部性原理,這里訪問的就是同一個虛擬地址附近的數(shù)據(jù),因此TLB和Cache都命中。可以看到,進程在執(zhí)行malloc時是將物理頁框直接分配給虛擬地址,里面的初始數(shù)據(jù)有內(nèi)存的電氣特性決定,是隨機值,因此maoolc出來的空間使用前需要賦值或者執(zhí)行初始化操作。
關(guān)注公眾號,后臺回復(fù)「1024」獲取學(xué)習(xí)資料網(wǎng)盤鏈接。歡迎點贊,關(guān)注,轉(zhuǎn)發(fā),在看,您的每一次鼓勵,我都將銘記于心~