详解操作系统中虚拟内存与物理内存的关系
點(diǎn)擊鏈接:
一文理解虛擬內(nèi)存、物理內(nèi)存、內(nèi)存分配、內(nèi)存管理 - 知乎
目錄
一、虛擬內(nèi)存與物理內(nèi)存
1.1 虛擬內(nèi)存
1.2 虛擬內(nèi)存與物理內(nèi)存
二、C/C++中虛擬內(nèi)存分配模型
2.1 C語言中內(nèi)存分配模型
2.2 C++語言中內(nèi)存分配模型
三、程序占用的內(nèi)存是虛擬內(nèi)存還是物理內(nèi)存
3.1 內(nèi)存管理
3.1.1 內(nèi)存管理概念
3.1.2 glibc內(nèi)存管理器
3.1.3 內(nèi)存管理器面臨的困難
3.1.4 以堆為例講解內(nèi)存的申請與釋放
3.2 代碼占用的內(nèi)存
3.2.1 代碼啟動過程的內(nèi)存管理
3.2.2 運(yùn)行過程中鏈接動態(tài)鏈接庫與編譯過程中鏈接動態(tài)庫的區(qū)別
3.3 總結(jié)
四、通過指針獲取到的地址是虛擬內(nèi)存中的地址還是物理內(nèi)存中的地址
這篇文章主要講述:
1.虛擬內(nèi)存與物理內(nèi)存的概念及關(guān)系
2.C/C++虛擬內(nèi)存分配模型
3.程序占用的內(nèi)存是虛擬內(nèi)存還是物理內(nèi)存
4.通過指針獲取到的地址是虛擬內(nèi)存中的地址還是物理內(nèi)存中的地址
一、虛擬內(nèi)存與物理內(nèi)存
1.1 虛擬內(nèi)存
虛擬內(nèi)存是一種實(shí)現(xiàn)在計算機(jī)軟硬件之間的內(nèi)存管理技術(shù),它將程序使用到的內(nèi)存地址(虛擬地址)映射到計算機(jī)內(nèi)存中的物理地址,虛擬內(nèi)存使得應(yīng)用程序從繁瑣的管理內(nèi)存空間任務(wù)中解放出來,提高了內(nèi)存隔離帶來的安全性,虛擬內(nèi)存地址通常是連續(xù)的地址空間,由操作系統(tǒng)的內(nèi)存管理模塊控制,在觸發(fā)缺頁中斷時利用分頁技術(shù)將實(shí)際的物理內(nèi)存分配給虛擬內(nèi)存,而且64位機(jī)器虛擬內(nèi)存的空間大小遠(yuǎn)超出實(shí)際物理內(nèi)存的大小,使得進(jìn)程可以使用比物理內(nèi)存大小更多的內(nèi)存空間。
1.2 虛擬內(nèi)存與物理內(nèi)存
關(guān)于虛擬內(nèi)存和物理內(nèi)存的關(guān)系可以看看這篇文章。
https://blog.csdn.net/qq_41687938/article/details/119112003?spm=1001.2014.3001.5501
二、C/C++中虛擬內(nèi)存分配模型
記住這幾個關(guān)鍵點(diǎn):
-
每個進(jìn)程都有它自己的虛擬內(nèi)存
-
虛擬內(nèi)存的大小取決于系統(tǒng)的體系結(jié)構(gòu)
-
不同操作管理有著不同的管理虛擬內(nèi)存的方式,但大多數(shù)操作系統(tǒng)的虛擬內(nèi)存結(jié)構(gòu)如下圖:
?virtual_memory?虛擬內(nèi)存結(jié)構(gòu)圖
按照地址從高到低:(注意堆、棧的地址走向)
2.1 C語言中內(nèi)存分配模型
在C語言中,內(nèi)存主要分為如下5個存儲區(qū):
2.2 C++語言中內(nèi)存分配模型
在C++語言中,與C類似,不過也有所不同,內(nèi)存主要分為如下5個存儲區(qū):
注:c++中代碼還是存在代碼區(qū)的。
所以我們平時所說的代碼的運(yùn)行,分配,操作等,都是指的虛擬內(nèi)存!!!!!!!!
三、程序占用的內(nèi)存是虛擬內(nèi)存還是物理內(nèi)存
要討論這個問題,先看看一些基本的內(nèi)存相關(guān)知識。
3.1 內(nèi)存管理
3.1.1 內(nèi)存管理概念
一提到內(nèi)存管理,我們頭腦中閃出的兩個概念,就是虛擬內(nèi)存與物理內(nèi)存。這兩個概念主要來自于linux內(nèi)核的支持。
Linux在內(nèi)存管理上份為兩級,一是線性區(qū),類似于00c73000-00c88000,對應(yīng)于虛擬內(nèi)存,它實(shí)際上不占用實(shí)際物理內(nèi)存;二是具體的物理頁面,它對應(yīng)我們機(jī)器上的物理內(nèi)存。
這里要提到一個很重要的概念,內(nèi)存的延遲分配。Linux內(nèi)核在用戶申請內(nèi)存的時候,只是給它分配了一個線性區(qū)(也就是虛存),并沒有分配實(shí)際物理內(nèi)存;只有當(dāng)用戶使用這塊內(nèi)存的時候,內(nèi)核才會分配具體的物理頁面給用戶,這時候才占用寶貴的物理內(nèi)存。內(nèi)核釋放物理頁面是通過釋放線性區(qū)(也就是虛存),找到其所對應(yīng)的物理頁面,將其全部釋放的過程。
char *p=malloc(2048) //這里只是分配了虛擬內(nèi)存2048,并不占用實(shí)際內(nèi)存。 strcpy(p,"123") //分配了物理頁面,雖然只是使用了3個字節(jié),但內(nèi)存還是為它分配了2048字節(jié)的物理內(nèi)存。 free(p) //通過虛擬地址,找到其所對應(yīng)的物理頁面,釋放物理頁面,釋放虛擬內(nèi)存(線性區(qū))。我們知道用戶的進(jìn)程和內(nèi)核是運(yùn)行在不同的級別,進(jìn)程與內(nèi)核之間的通訊是通過系統(tǒng)調(diào)用來完成的。進(jìn)程在申請和釋放內(nèi)存,主要通過brk,sbrk,mmap,unmmap這幾個系統(tǒng)調(diào)用,傳遞的參數(shù)主要是對應(yīng)的虛擬內(nèi)存。
注意一點(diǎn),在進(jìn)程只能訪問虛擬內(nèi)存,它實(shí)際上是看不到內(nèi)核物理內(nèi)存的使用,這對于進(jìn)程是完全透明的。
也就是說,程序申請和操作的內(nèi)存都是在虛擬內(nèi)存上的,包括堆(heap)、棧(stack)等。
3.1.2 glibc內(nèi)存管理器
那么我們每次調(diào)用malloc來分配一塊內(nèi)存,都進(jìn)行相應(yīng)的系統(tǒng)調(diào)用呢?
答案是否定的,這里我要引入一個新的概念,glibc的內(nèi)存管理器。glibc是GNU發(fā)布的libc庫,即c運(yùn)行庫。
我們知道m(xù)alloc和free等函數(shù)都是包含在glibc庫里面的庫函數(shù),我們試想一下,每做一次內(nèi)存操作,都要調(diào)用系統(tǒng)調(diào)用的話,那么程序?qū)⒍嗝吹牡托А?/p>
實(shí)際上glibc采用了一種批發(fā)和零售的方式來管理內(nèi)存。glibc每次通過系統(tǒng)調(diào)用的方式申請一大塊內(nèi)存(虛擬內(nèi)存),當(dāng)進(jìn)程申請內(nèi)存時,glibc就從自己獲得的內(nèi)存中取出一塊給進(jìn)程。
3.1.3 內(nèi)存管理器面臨的困難
我們在寫程序的時候,每次申請的內(nèi)存塊大小不規(guī)律,而且存在頻繁的申請和釋放,這樣不可避免的就會產(chǎn)生內(nèi)存碎塊。而內(nèi)存碎塊,直接會導(dǎo)致大塊內(nèi)存申請無法滿足,從而更多的占用系統(tǒng)資源;如果進(jìn)行碎塊整理的話,又會增加cpu的負(fù)荷,很多都是互相矛盾的指標(biāo),這里我就不細(xì)說了。
我們在寫程序時,涉及內(nèi)存時,有兩個概念heap和stack。傳統(tǒng)的說法stack的內(nèi)存地址是向下增長的,heap的內(nèi)存地址是向上增長的。
3.1.4 以堆為例講解內(nèi)存的申請與釋放
函數(shù)malloc和free,主要是針對heap進(jìn)行操作,由程序員自主控制內(nèi)存的訪問。
在這里heap的內(nèi)存地址向上增長,這句話不完全正確。
heap堆的申請
glibc對于heap內(nèi)存申請大于128k的內(nèi)存申請,glibc采用mmap的方式向內(nèi)核申請內(nèi)存,這不能保證內(nèi)存地址向上增長;小于128k的則采用brk,對于它來講是正確的。128k的閥值,可以通過glibc的庫函數(shù)進(jìn)行設(shè)置。
對于大塊內(nèi)存申請,glibc直接使用mmap系統(tǒng)調(diào)用為其劃分出另一塊虛擬地址,供進(jìn)程單獨(dú)使用;在該塊內(nèi)存釋放時,使用unmmap系統(tǒng)調(diào)用將這塊內(nèi)存釋放,這個過程中間不會產(chǎn)生內(nèi)存碎塊等問題。
針對小塊內(nèi)存的申請,在程序啟動之后,進(jìn)程會獲得一個heap底端的地址,進(jìn)程每次進(jìn)行內(nèi)存申請時,glibc會將堆頂向上增長來擴(kuò)展內(nèi)存空間,也就是我們所說的堆地址向上增長。在對這些小塊內(nèi)存進(jìn)行操作時,便會產(chǎn)生內(nèi)存碎塊的問題。實(shí)際上brk和sbrk系統(tǒng)調(diào)用,就是調(diào)整heap頂?shù)刂分羔槨?/p>
那么heap堆的內(nèi)存是什么時候釋放呢?
當(dāng)glibc發(fā)現(xiàn)堆頂有連續(xù)的128k的空間是空閑的時候,它就會通過brk或sbrk系統(tǒng)調(diào)用,來調(diào)整heap頂?shù)奈恢?#xff0c;將占用的內(nèi)存返回給系統(tǒng)。這時,內(nèi)核會通過刪除相應(yīng)的線性區(qū),來釋放占用的物理內(nèi)存。
下面我要講一個內(nèi)存空洞的問題:
一個場景,堆頂有一塊正在使用的內(nèi)存,而下面有很大的連續(xù)內(nèi)存已經(jīng)被釋放掉了,那么這塊內(nèi)存是否能夠被釋放?其對應(yīng)的物理內(nèi)存是否能夠被釋放?
很遺憾,不能。
這也就是說,只要堆頂?shù)牟糠稚暾垉?nèi)存還在占用,我在下面釋放的內(nèi)存再多,都不會被返回到系統(tǒng)中,仍然占用著物理內(nèi)存。為什么會這樣呢?
這主要是與內(nèi)核在處理堆的時候,過于簡單,它只能通過調(diào)整堆頂指針的方式來調(diào)整調(diào)整程序占用的線性區(qū)(虛擬內(nèi)存);而又只能通過調(diào)整線性區(qū)(虛擬內(nèi)存)的方式,來釋放內(nèi)存。所以只要堆頂不減小,占用的內(nèi)存就不會釋放。
提一個問題:
char *p=malloc(2); free(p)為什么申請內(nèi)存的時候,需要兩個參數(shù),一個是內(nèi)存大小,一個是返回的指針;而釋放內(nèi)存的時候,卻只要內(nèi)存的指針呢?
這主要是和glibc的內(nèi)存管理機(jī)制有關(guān)。glibc中,為每一塊內(nèi)存維護(hù)了一個chunk的結(jié)構(gòu)。glibc在分配內(nèi)存時,glibc先填寫chunk結(jié)構(gòu)中內(nèi)存塊的大小,然后是分配給進(jìn)程的內(nèi)存。
chunk ------size p------------ content在進(jìn)程釋放內(nèi)存時,只要?指針減去4?便可以找到該塊內(nèi)存的大小,從而釋放掉。
注:glibc在做內(nèi)存申請時,最少分配16個字節(jié),以便能夠維護(hù)chunk結(jié)構(gòu)。
3.2 代碼占用的內(nèi)存
3.2.1 代碼啟動過程的內(nèi)存管理
數(shù)據(jù)部分占用內(nèi)存,那么我們寫的程序是不是也占用內(nèi)存呢?
在linux中,程序的加載,涉及到兩個工具,linker 和loader。Linker主要涉及動態(tài)鏈接庫的使用,loader主要涉及軟件的加載。
因此一個2M的程序,執(zhí)行時,并不意味著為其分配了2M的物理內(nèi)存,這與其運(yùn)行了的代碼量,與其所依賴的動態(tài)鏈接庫有關(guān)。
3.2.2 運(yùn)行過程中鏈接動態(tài)鏈接庫與編譯過程中鏈接動態(tài)庫的區(qū)別
我們調(diào)用動態(tài)鏈接庫有兩種方法:一種是編譯的時候,指明所依賴的動態(tài)鏈接庫,這樣loader可以在程序啟動的時候,將所有的動態(tài)鏈接映射到內(nèi)存中;一種是在運(yùn)行過程中,通過dlopen和dlfree的方式加載動態(tài)鏈接庫,動態(tài)將動態(tài)鏈接庫加載到內(nèi)存中。
從編程角度來講,第一種是最方便的,效率上影響也不大。
在內(nèi)存使用上有些差別:
第一種方式,一個庫的代碼,只要運(yùn)行過一次,便會占用物理內(nèi)存,之后即使再也不使用,也會占用物理內(nèi)存,直到進(jìn)程的終止。
第二中方式,庫代碼占用的內(nèi)存,可以通過dlfree的方式,釋放掉,返回給物理內(nèi)存。
對于那些壽命很長,但又會偶爾調(diào)用各種庫的進(jìn)程有關(guān)。如果是這類進(jìn)程,建議采用第二種方式調(diào)用動態(tài)鏈接庫。
3.3 總結(jié)
從前所述可知,每一個程序運(yùn)行都會構(gòu)建相應(yīng)的進(jìn)程,那也就是說,程序占用內(nèi)存以及使用內(nèi)存都是從虛擬內(nèi)存的角度上進(jìn)行分析。程序只能看到虛擬內(nèi)存,具體使用的物理內(nèi)存需要操作系統(tǒng)內(nèi)核進(jìn)行決定。
四、通過指針獲取到的地址是虛擬內(nèi)存中的地址還是物理內(nèi)存中的地址
一般來說,指針是不是邏輯地址根本就不重要。在這些高級的編程語言中,所有的地址在寫的時候都是邏輯地址,最終都會映射到物理地址,不然沒法運(yùn)行。
對應(yīng)用程序來說,不需要關(guān)心這個。可以認(rèn)為是虛擬內(nèi)存的地址,程序加載/運(yùn)行時操作系統(tǒng)/硬件會進(jìn)行正確的轉(zhuǎn)換。如果在沒有MMU的系統(tǒng)中,通常虛擬內(nèi)存地址和物理地址是一回事。所以具體情況得根據(jù)實(shí)際情況具體分析。
不過既然是探討,那就具體說一說。
首先我們要知道,不同進(jìn)程地址空間是相互隔離的,不同進(jìn)程兩個值相同的指針對應(yīng)的真實(shí)內(nèi)存是不同的。
指針變量存儲的地址應(yīng)該是指虛擬地址,也就是在程序中能通過那個地址訪問變量的地址。而普通指針是指"物理地址"。
指向類成員的指針是 經(jīng)過處理的"偏移量" (一般是實(shí)際偏移量-1,多繼承就復(fù)雜鳥).
參考:
https://blog.csdn.net/qq_41687938/article/details/119112003?spm=1001.2014.3001.5501
10張圖22段代碼,萬字長文帶你搞懂虛擬內(nèi)存模型和malloc內(nèi)部原理_Peter的專欄-CSDN博客
內(nèi)存分配方式詳解(堆、棧、自由存儲區(qū)、全局/靜態(tài)存儲區(qū)和常量存儲區(qū))_行者三個石的博客-CSDN博客
指針變量是存儲的物理地址還是有效地址即偏移量?-CSDN論壇
C++內(nèi)存模型_TemetNosce的博客-CSDN博客_c++ 內(nèi)存模型
?
總結(jié)
以上是生活随笔為你收集整理的详解操作系统中虚拟内存与物理内存的关系的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: gui 设计的简单计算器 java,编写
- 下一篇: linux发行版_7款颜值当道的Linu