.NET?Core內(nèi)存結構體系(Windows環(huán)境)底層原理解析
物理內(nèi)存與虛擬內(nèi)存物理內(nèi)存
- 物理內(nèi)存(Physical Memory)
定義:物理內(nèi)存是計算機硬件中的實際RAM(如DDR5內(nèi)存條),直接通過總線與CPU連接,用于臨時存儲運行中的程序和數(shù)據(jù)。 - 虛擬內(nèi)存(Virtual Memory)
定義:由操作系統(tǒng)管理的抽象內(nèi)存層,通過結合物理內(nèi)存和磁盤空間(如頁面文件或交換分區(qū)),為程序提供連續(xù)且獨立的內(nèi)存空間。
用戶只需要與虛擬內(nèi)存地址打交道,而無需關心數(shù)據(jù)到底分配在哪里
眼見為實
物理頁4K對齊
在Windows系統(tǒng)下,以4K為最小粒度,這個單位叫做物理頁
,并以4K的整數(shù)倍分配內(nèi)存。比如申請1k分配4k,申請5k分配8k
眼見為實
void page4k() { for (int i = 0; i < 200; i++) { //1k 的占用 LPVOID ptr = VirtualAlloc(NULL, 1024 * 1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); printf("i=%d, 1k, address:%#0.8x \n", i + 1, ptr); } for (int i = 200; i < 400; i++) { //5k 的占用 LPVOID ptr = VirtualAlloc(NULL, 1024 * 5, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); printf("i=%d, 5k, address:%#0.8x \n", i + 1, ptr); } getchar(); }
申請1k分配4k
申請5k分配8k
物理內(nèi)存與虛擬內(nèi)存如何映射?
Windows系統(tǒng)采用二叉樹結構
(5層)來實現(xiàn)高效映射。
舉個例子,某個32bit的內(nèi)存地址為:0x77b01a42,其二進制為:01110,11110,11000,00001,101001000010
前20位
用來構建頁表樹
,實現(xiàn)物理頁的的高效映射后12位
映射物理頁的偏移量
操作系統(tǒng)以4K為一個單位對內(nèi)存進行分組,4G內(nèi)存=102410241024*4/(4/1024)=1048576物理頁,如此龐大的物理頁,,采用5層二叉樹來提高索引效率
眼見為實:以notepad為例
任務管理:
Windbg:
可以看到非常明顯的不同,任務管理器顯示占用44.6mb內(nèi)存,而windbg顯示占用489.531mb內(nèi)存,這是為什么呢?答:顯示邏輯不同,任務管理器顯示的是Private WorkingSet,指的是物理內(nèi)存的地址,即內(nèi)存條上的內(nèi)存
,而Windbg是顯示映射到的物理頁,Commit指的是虛擬內(nèi)存地址,這包括內(nèi)存條上的內(nèi)存,pagefile,image
三種
眼見為實:可視化觀察 虛擬地址=>物理地址
使用windbg進入內(nèi)核態(tài),這很重要,大家可以猜猜原因。
隨便找一個字符串的內(nèi)存地址
- 使用dp觀察虛擬地址
- 使用!vtop 觀察映射信息
- 使用!db觀察物理地址
眼見為實:空指針區(qū)與用戶態(tài)區(qū)
windows/linux在默認情況下,會開啟ASLR,需要關閉此技術才能復現(xiàn)。ASLR 是一種針對緩沖區(qū)溢出攻擊等內(nèi)存攻擊技術而設計的安全特性。在沒有 ASLR 的情況下,程序加載到內(nèi)存中的位置通常是固定的,攻擊者可以預測程序中各種模塊(如可執(zhí)行文件、動態(tài)鏈接庫等)的加載地址,進而利用這些固定地址來構造惡意代碼進行攻擊,比如在緩沖區(qū)溢出攻擊中精準定位跳轉地址來執(zhí)行惡意指令。而啟用 ASLR 后,操作系統(tǒng)在每次啟動程序時會隨機化程序的內(nèi)存布局,包括可執(zhí)行文件、動態(tài)鏈接庫、堆、棧等的加載地址,使得攻擊者難以準確預測內(nèi)存地址,大大增加了攻擊的難度。
Reserved與Commit
- Reserved
在虛擬地址上申請一段內(nèi)存空間,此時操作系統(tǒng)也會同步創(chuàng)建頁表樹
,但此時并未映射到物理內(nèi)存
,此時對該虛擬內(nèi)存的讀寫會拋異常 - Commit
給頁表樹
調(diào)配真實的物理內(nèi)存
,此時才能正常寫入
眼見為實:Reserved
void mem_reserved() { LPVOID ptr = VirtualAlloc(NULL, 4 * 1024, MEM_RESERVE, PAGE_READWRITE); *(int*)(ptr) = 10; //在首地址上寫入內(nèi)容。 printf("num=%d", *(int*)ptr); }
眼見為實:Commit
void mem_commit() { LPVOID ptr = VirtualAlloc(NULL, 4 * 1024, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); *(int*)(ptr) = 10; //在首地址上寫入內(nèi)容。 printf("num=%d", *(int*)ptr); }
NT堆
NT堆是 Windows NT 內(nèi)核引入的內(nèi)存管理組件,主要負責進程內(nèi)的堆內(nèi)存分配與釋放。在 Windows 系統(tǒng)里,進程可以使用 NT 堆來動態(tài)分配和管理內(nèi)存,比如程序中使用 malloc()(C 語言)、new(C++) 等函數(shù)進行內(nèi)存分配時,底層通常就依賴 NT 堆機制。
上面說到,VirtualAlloc方法它會一次性分配 64k 整數(shù)倍的內(nèi)存段,內(nèi)部對象按4k的內(nèi)存頁對齊.如果讓application直接操作VirtualAlloc,難免會造成大量的內(nèi)存浪費。為了提高內(nèi)存性能與使用效率,Windows又提供了一層抽象
,以提供更細顆粒度的內(nèi)存管理。它的名字叫做NT堆
- 在32bit平臺上:8byte為一個分配粒度
- 在64bit平臺上:16btye為一個分配粒度
- CRT堆:C運行時使用的堆,默認是對NT堆的簡單封裝
- 托管堆:用作特殊用途的,自行實現(xiàn)的一套內(nèi)存池管理機制。比如GC堆
從圖中可以看出,使用NT與否取決于程序員本身。完全可以繞過NT堆,直接使用VirtualAlloc來分配內(nèi)存,只要你接收內(nèi)存浪費。
眼見為實:GC堆,底層使用VirtualAlloc分配內(nèi)存
static void Main(string[] args) { var rand = new Random(); List<string> list = new List<string>(); for (int i = 0; i < 100000; i++) { var str = string.Join(",", Enumerable.Range(0, rand.Next(1, 1000))); list.Add(str); Console.WriteLine($"i={i},length={str.Length}"); } Console.ReadLine(); }
在bp KERNELBASE!VirtualAlloc 下斷點
眼見為實:CRT堆/NT堆,底層使用VirtualAlloc分配內(nèi)存
? #include <iostream> #include <Windows.h> void crt_c() { for (int i = 0; i < 10000000; i++) { int* ptr = (int*)malloc(sizeof(int) * 1000); *(ptr) = 10; printf("第 %d 次分配 \n", i); } } ?
在 bp ntdll!NtAllocateVirtualMemory 下斷點
到此這篇關于.NET Core內(nèi)存結構體系(Windows環(huán)境)底層原理淺談的文章就介紹到這了,更多相關.NET Core內(nèi)存結構體系內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
.NET一行代碼實現(xiàn)GC調(diào)優(yōu),讓程序不再占用內(nèi)存
這篇文章主要介紹了NET一行代碼實現(xiàn)GC調(diào)優(yōu),讓程序不再占用內(nèi)存的相關資料,需要的朋友可以參考下2022-11-11ASP.NET.4.5.1+MVC5.0設置系統(tǒng)角色與權限(二)
這篇文章主要介紹了使用ASP.NET.4.5.1+MVC5.0構建項目中設置系統(tǒng)角色的全部過程,十分的詳細,附上全部源碼,推薦給想學習.net+mvc的小伙伴們2015-01-01