我們帶著問題去探討new之后的底層原理.
我們對此代碼解讀咙轩。
- new之后的流程是什么
- 內存如何被讀寫
- 地址如何產生
- 地址如何表示
1.吐槽
不得不吐槽一下蘑志,簡書的markdown真摳腳显熏,竟然沒法畫流程圖散怖。
2.各種概念
2.1.什么是虛擬內存(virtual memory)
??在我看來喷兼,虛擬內存更像是對內存的管理篮绰,請看下圖:
??每一個進程的虛擬地址都是從0開始,包含放置代碼區(qū)褒搔,堆阶牍,棧,和內核映射區(qū)星瘾。在這里的地址都是連續(xù)的走孽,會被分成很多虛擬頁,每一個虛擬頁為4K大小琳状,頁會映射到物理內存磕瓷,物理內存的地址不會重復,當一個進程開始執(zhí)行或者請求堆內存,會在物理內存尋找空余的物理頁(也是一頁4K大欣场)存放边翁。虛擬內存保證了每個進程的地址獨立,不會被其他進程的訪問到硕盹。
??引進虛擬內存這個概念后符匾,會使得訪問物理內存變得慢一些了,因為會有兩次尋址瘩例,先去頁表找物理地址啊胶,找到物理地址后才能去物理內存上讀取數(shù)據(jù)。虛擬內存這個概念垛贤,也算是不錯的設計吧焰坪。每個進程的地址空間都是獨立的,這使得每個進程地址都是相同的基本格式(進程虛擬地址起始為0聘惦,堆某饰,棧也是固定的起始地址),不需要知道真正的物理內存放在什么地方善绎。由于我們編寫的程序沒有權限直接訪問硬件黔漂,這其中的訪問物理內存,會從用戶態(tài)到內核態(tài)的轉換涂邀,分配好內存后再從內核態(tài)轉換為用戶態(tài)瘟仿。系統(tǒng)不信任我們這群坑貨,才如此設計的比勉,流程大概是這樣劳较,用戶態(tài)請求內存分配 -> 系統(tǒng)中斷 -> 內核態(tài)分配好內存 -> 系統(tǒng)中斷 -> 返回用戶態(tài) :
2.2.什么是虛擬頁(virtual page)
??虛擬內存由虛擬頁構成,每個虛擬頁4K大小浩聋。這一摞概念都是抽象的观蜗,在腦子里聯(lián)想吧,你可以把虛擬內存聯(lián)想成一把梯子衣洁,梯級之間的空間是虛擬頁大小墓捻。
2.3.什么是物理頁(physical page)
??物理頁也被稱為頁幀(page frame),4K大小坊夫,和虛擬頁類似砖第,只不過物理頁是構成物理內存的,你把它想象成另一把梯子和梯級吧环凿。物理頁會產生內部內存碎片梧兼,但不會產生外部內存碎片,這與分段相反(還有一個種內存劃分是分段的智听,產生外部內存碎片羽杰,但不產生內部內存碎片)渡紫,這種分頁的方式,類似內存對齊考赛,關于內存對齊的理解惕澎,我寫了另外一篇文章⊙罩瑁總的來說唧喉,物理頁構成了RAM(Random Access Memory,包含主存Main Memory等)复哆。
2.4.什么是頁表(page table)
??頁表存在RAM欣喧。操作系統(tǒng)為每個進程提供一個頁表,多個進程對應多個頁表梯找。頁表的作用是干翻譯這行的,虛擬內存的地址能夠找到物理內存的地址益涧,就是通過頁表映射的锈锤,頁表保存了物理地址。
2.5.什么是快表(TLB闲询,Translation Lookaside Buffer)和cpu高速緩存
??畢竟頁表是存在RAM久免,并且比較大,查表慢扭弧,有損性能阎姥,快表就是為了解決這個問題的「肽恚快表可以看作是頁表的一個緩存呼巴,當CPU訪問某一塊物理內存后,把物理頁存到CPU高速緩存御蒲,物理地址存到TLB衣赶。每次CPU提著虛擬地址來查表,先去TLB看看能不能找到對應的物理地址厚满,如果找到了再去緩存找數(shù)據(jù)府瞄。如果在TLB找不到(稱為TLB不命中,奇葩的命名)碘箍,再去頁表查找遵馆。快表本身也存在cpu緩存中丰榴,內存占用小訪問速度快货邓。unity的ECS運行速度快的其中一個原因,ECS把數(shù)據(jù)都放在一起了多艇,當CPU訪問其中一項數(shù)據(jù)逻恐,會把4K的一個物理頁都存到CPU緩存中,下次使用鄰近的數(shù)據(jù)時候,很可能已經被存到CPU緩存中了复隆,所以訪問速度快一些拨匆。因此,我們寫代碼時候挽拂,可以考慮把數(shù)據(jù)寫在一起惭每,或者函數(shù)之間調用挨著的。
2.6.VPO亏栈,VPN台腥,PPO,PPN
- VPN :虛擬頁號(Virtual Page Number )绒北,一頁通常是4K黎侈,VPN表示在虛擬內存的第幾頁。
- VPO :虛擬頁偏移(Virtual Page Offset)闷游,虛擬內存的頁內偏移峻汉,一頁中的第幾個字節(jié)。
- PPN :物理頁號(Physical Page Number)脐往,一頁通常是4K休吠,VPN表示在物理內存的第幾頁。
- PPO :物理頁偏移(Physical Page Offset)业簿,物理內存的頁內偏移瘤礁,一頁中的第幾個字節(jié)。
??當我們要訪問某一塊內存時候梅尤,會使用虛擬頁號(VPN)去頁表找到真實的物理頁號(PPN)柜思,通過虛擬頁偏移(VPO)找到真實的物理地址。這里的尋址克饶,為什么會與PPN酝蜒,VPO有關呢?一個物理地址矾湃,實際上是物理頁號+偏移值構成的亡脑,一個物理頁占4K大小(虛擬頁也是4K)邀跃,頁內偏移是指在這物理頁號之后位移多少字節(jié)霉咨。我們用二維數(shù)組來比喻吧 ,每一行有4K列拍屑,現(xiàn)在要找9K途戒,它在2行1K列(相當于它在2物理頁號1K頁內偏移)。請看地址計算公式僵驰,其中2^s表示一頁占用的內存大小(一般為4K):
??為什么此處使用VPO而非PPO喷斋,其實都一樣的唁毒,頁內偏移相等,VPO=PPO星爪。
3.對整體流程的講解
??鋪墊完概念浆西,下面開始討論一下上述代碼的執(zhí)行流程。
- 應用程序編譯成指令顽腾,編譯時候近零,每塊數(shù)據(jù)都有相對地址了。
- 程序開始執(zhí)行抄肖,代碼被加載到主存中久信。
- 指令被送至cpu的指令寄存器。
- cpu從指令寄存器取出指令漓摩,執(zhí)行new int(0)裙士;代碼。
- 在主存中分配好內存幌甘,并且映射到虛擬內存的堆中潮售,物理地址和虛擬地址在頁表中關聯(lián)。
- 與此同時锅风,把p所在的物理頁緩存到CPU高速緩存中,地址放進TLB鞍泉。
- int* p = new int(0)皱埠;返回p的虛擬地址。
- 讀p的值咖驮。先去TLB查看是否存在p的地址边器,如果存在,去高速緩存查看值在不在托修。
- 如果TLB不存在對應的地址忘巧,去頁表找。
??有幾點要說明的睦刃。
- 當我們要訪問p地址對應的內存時候砚嘴,操作系統(tǒng)會檢查地址是否在虛擬內存的當前進程中,操作系統(tǒng)不允許訪問其他進程的私有內存涩拙。
- 存在多級頁表际长。
- 頁表有操作系統(tǒng)維護,分配內存時填寫響應的頁表項兴泥,釋放內存清除響應的頁表項工育,程序退出釋放它的頁表。
- 有一種特殊情況搓彻,高速緩存和主存都沒有找到如绸,那么此時說明物理頁在磁盤中嘱朽。一般這種情況是爆內存了,主存不足以存放得下怔接,那么把一部分不常用的內存放進磁盤搪泳。由于磁盤讀寫比較慢的原因,導致訪問會卡頓蜕提。舉個例子森书,當我們運行一個占用內存比較大的軟件,內存占用為100%了谎势,但是軟件卻不會閃退凛膏,原因是有一部分內存被移動到磁盤上了,把磁盤當成了主存的一部分脏榆,虛擬內存會映射到磁盤的扇區(qū)地址猖毫,去磁盤讀寫時候就顯得比較慢了。本人曾經做過一個測試须喂,在unity中不斷加載資源到場景中吁断,并且引用這些資源不給GC釋放,觀察內存占用情況坞生,先是內存被占用為100%仔役,然后發(fā)現(xiàn)E盤的可用大小變得越來越小。
??老實說是己,學這些對你的代碼能力沒多大幫助又兵,僅僅只是個人想了解一下而已。
Author : SunnyDecember
Date : 2019.11.9
原文