前言
現(xiàn)代處理器基本都支持虛擬內(nèi)存管理,在開啟虛存管理時,程序只能訪問到虛擬地址,處理器的內(nèi)存管理單元(MMU)會自動完成虛擬地址到物理地址的轉(zhuǎn)換。基于虛擬內(nèi)存機制,操作系統(tǒng)可以為每個運行中的進程創(chuàng)建獨享的虛擬地址空間,在這個空間中執(zhí)行的程序,無法感知系統(tǒng)中其它進程的存在,從而使得不同的進程在運行時可以互不干擾。
進程地址空間的大小
虛擬地址空間的最大長度與系統(tǒng)中實際可用的物理內(nèi)存數(shù)量無關(guān),而是取決于硬件平臺支持的尋址空間大小,即處理器的位數(shù)。32位處理器平臺下,支持的虛擬地址空間范圍為0x00000000~0xFFFFFFFF,總大小為2^32 字節(jié),即4GB;而在64位處理器平臺上,虛擬地址空間的范圍從0x0000000000000000擴展到了0xFFFFFFFFFFFFFFFF,總大小為2^64 字節(jié)。用戶進程通常無法直接訪問全部地址空間,操作系統(tǒng)會將一部分地址空間劃分給內(nèi)核程序,具體的劃分策略依賴于操作系統(tǒng)的實現(xiàn)。
進程地址空間的布局
下圖是在典型的32位和64位Linux操作系統(tǒng)上,運行時進程的地址空間布局:
基于內(nèi)核的管理策略,進程虛擬地址空間并沒有全部分配給進程使用。Linux將進程地址空間劃分成兩個部分:位于高地址部分的內(nèi)核地址空間和位于低地址部分的用戶地址空間,其中
- 內(nèi)核地址空間:內(nèi)核總是駐留在內(nèi)存中,是操作系統(tǒng)的一部分。內(nèi)核空間專為內(nèi)核保留,并且對于系統(tǒng)中所有的進程都是相同的。內(nèi)核空間只允許具備高特權(quán)級的內(nèi)核程序進行訪問;對于用戶程序,只能通過系統(tǒng)調(diào)用等方式陷入內(nèi)核空間中以訪問系統(tǒng)提供的資源;
- 用戶地址空間:進程只能訪問用戶地址空間。用戶地址空間保存了用戶程序的運行指令和數(shù)據(jù)。內(nèi)核在創(chuàng)建進程時,會依據(jù)用戶可執(zhí)行文件中提供的信息在用戶空間中創(chuàng)建必要的區(qū)域,并為用戶進程維護環(huán)境變量、堆和棧等信息。對于系統(tǒng)中的每個進程,其用戶地址空間是彼此分離,完全隔絕的,進程只能訪問屬于自己的用戶地址空間部分的數(shù)據(jù)
用戶地址空間區(qū)域
由上圖可以看出,無論是32位系統(tǒng)或是64位系統(tǒng),進程用戶地址空間除了地址空間大小不同外,內(nèi)存布局基本相同,通常都包含以下幾個部分:
- 當(dāng)前運行程序的二進制代碼,對應(yīng)于代碼段;
- 存儲只讀數(shù)據(jù)和可讀寫數(shù)據(jù)的段,如常量和全局變量;
- 用于動態(tài)分配內(nèi)存的堆;
- 存儲局部變量以及實現(xiàn)過程調(diào)用的棧;
- 保存命令行參數(shù)和環(huán)境變量的區(qū)域。
內(nèi)存映射區(qū)域
圖中有一個特殊的區(qū)域沒有畫出,就是內(nèi)存映射區(qū)域。內(nèi)存映射區(qū)域通常位于堆和棧之間,用于將磁盤中的內(nèi)容直接映射到內(nèi)存中進行訪問。Linux系統(tǒng)中的程序可以通過mmap系統(tǒng)調(diào)用來請求這種功能,相較于直接讀寫磁盤文件,內(nèi)存映射提供了一種更加高效的文件IO方式,典型的應(yīng)用場景就是動態(tài)庫的裝載。
進程地址空間訪問
盡管系統(tǒng)中每個進程都擁有巨大的虛擬地址空間,實際可使用的物理內(nèi)存確是有限的,因此內(nèi)核必須考慮如何合理地安排有限的物理地址到虛擬地址空間區(qū)域的映射。Linux內(nèi)核為每個進程維護了獨立的進程頁表,并管理著系統(tǒng)中所有的物理內(nèi)存,通過動態(tài)建立虛擬地址和物理地址的頁表映射,每個進程只會訪問所需要的那一部分物理內(nèi)存,從而實現(xiàn)了內(nèi)存的高效使用。下圖簡要顯示了進程虛擬地址空間中的地址到實際物理地址的轉(zhuǎn)換過程:
缺頁異常
內(nèi)核遵循按需分配的原則,在進程實際需要某個虛擬內(nèi)存區(qū)域的數(shù)據(jù)之前,內(nèi)核不會為其建立虛擬內(nèi)存到物理內(nèi)存的頁面映射。若進程訪問的虛擬地址空間部分沒有與具體的物理頁幀建立關(guān)聯(lián),處理器會自動觸發(fā)缺頁異常,并調(diào)用內(nèi)核設(shè)置的缺頁異常處理函數(shù)進行處理。內(nèi)核缺頁異常處理函數(shù)會檢測觸發(fā)缺頁異常的虛擬地址合法性,并在通過后,為其分配物理頁幀和建立頁面映射關(guān)系,并返回重新執(zhí)行觸發(fā)異常的指令。文章來源:http://www.zghlxwxcb.cn/news/detail-809366.html
非法內(nèi)存訪問
完整的進程地址空間被劃分成了不同的區(qū)域,對于不同的區(qū)域,其設(shè)置的訪問權(quán)限也都不相同,這同時也就意味著,用戶程序在隨意訪問了某個內(nèi)存地址時,如內(nèi)核空間部分的部分,則可能觸發(fā)不可恢復(fù)的錯誤。典型的幾種訪問了非法地址的情況如下所示:文章來源地址http://www.zghlxwxcb.cn/news/detail-809366.html
- 內(nèi)核空間區(qū)域:內(nèi)核空間區(qū)域?qū)?yīng)的頁表項,設(shè)置了高特權(quán)級訪問權(quán)限,對于運行在低特權(quán)級的用戶程序來說,是沒有資格訪問的;
- 只讀權(quán)限區(qū)域:只讀權(quán)限的區(qū)域包含代碼段、只讀數(shù)據(jù)段以及其它被設(shè)置了只讀權(quán)限的區(qū)域,如果對這類區(qū)域進行寫操作,則會觸發(fā)異常;
- 極低地址區(qū)域:大部分操作系統(tǒng)中,都不允許進程訪問極小的內(nèi)存地址,對應(yīng)于地址空間中保留未用的部分區(qū)域。也正因如此,C語言中NULL宏默認被定義成0,訪問NULL指針則會觸發(fā)空指針異常;
- 未分配的區(qū)域:堆和棧之間的區(qū)域默認情況下,內(nèi)核沒有分配給進程使用。若用戶在沒有申請該段區(qū)域的情況下進行訪問,會導(dǎo)致錯誤。
相關(guān)參考
- 《深入理解Linux內(nèi)核架構(gòu)》
- 《程序員的自我修養(yǎng)—鏈接、裝載與庫》
到了這里,關(guān)于淺析Linux進程地址空間的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!