日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

详解操作系统中虚拟内存与物理内存的关系

發(fā)布時(shí)間:2025/3/15 windows 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 详解操作系统中虚拟内存与物理内存的关系 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

點(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 代碼啟動(dòng)過程的內(nèi)存管理

3.2.2 運(yùn)行過程中鏈接動(dòng)態(tài)鏈接庫與編譯過程中鏈接動(dòng)態(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ì)算機(jī)軟硬件之間的內(nèi)存管理技術(shù),它將程序使用到的內(nèi)存地址(虛擬地址)映射到計(jì)算機(jī)內(nèi)存中的物理地址,虛擬內(nèi)存使得應(yīng)用程序從繁瑣的管理內(nèi)存空間任務(wù)中解放出來,提高了內(nèi)存隔離帶來的安全性,虛擬內(nèi)存地址通常是連續(xù)的地址空間,由操作系統(tǒng)的內(nèi)存管理模塊控制,在觸發(fā)缺頁中斷時(shí)利用分頁技術(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)存分配模型

記住這幾個(gè)關(guān)鍵點(diǎn):

  • 每個(gè)進(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個(gè)存儲(chǔ)區(qū):

  • 棧(Stack):位于函數(shù)內(nèi)的局部變量(包括函數(shù)實(shí)參),由編譯器負(fù)責(zé)分配釋放,函數(shù)結(jié)束,棧變量失效。(先進(jìn)后出)
  • 堆(Heap):由程序員用malloc/calloc/realloc分配,free釋放。如果程序員忘記free了,則會(huì)造成內(nèi)存泄露,程序結(jié)束時(shí)該片內(nèi)存會(huì)由OS回收,但程序只要不結(jié)束,就有可能造成內(nèi)存泄露。注意它與數(shù)據(jù)結(jié)構(gòu)中堆是兩回事,分配方式倒是類似于鏈表。
  • 全局區(qū)/靜態(tài)區(qū)(Global Static Area): 全局變量和靜態(tài)變量存放區(qū),程序一經(jīng)編譯好,該區(qū)域便存在。在C語言中初始化的全局變量和靜態(tài)變量和未初始化的放在相鄰的兩個(gè)區(qū)域(在C++中,由于全局變量和靜態(tài)變量編譯器會(huì)給這些變量自動(dòng)初始化賦值,所以沒有區(qū)分了),程序結(jié)束后由系統(tǒng)釋放。
  • C風(fēng)格字符串常量存儲(chǔ)區(qū): 專門存放字符串常量的地方,程序結(jié)束后由系統(tǒng)釋放。
  • 程序代碼區(qū):存放程序二進(jìn)制代碼的區(qū)域。
  • 2.2 C++語言中內(nèi)存分配模型

    在C++語言中,與C類似,不過也有所不同,內(nèi)存主要分為如下5個(gè)存儲(chǔ)區(qū):

  • 棧(Stack):位于函數(shù)內(nèi)的局部變量(包括函數(shù)實(shí)參),由編譯器負(fù)責(zé)分配釋放,函數(shù)結(jié)束,棧變量失效。(先進(jìn)后出)
  • 堆(Heap):這里與C不同的是,該堆是由new申請的內(nèi)存,由delete或delete[]負(fù)責(zé)釋放。
  • 自由存儲(chǔ)區(qū)(Free Storage):由程序員用malloc/calloc/realloc分配,free釋放。如果程序員忘記free了,則會(huì)造成內(nèi)存泄露,程序結(jié)束時(shí)該片內(nèi)存會(huì)由OS回收。
  • 全局區(qū)/靜態(tài)區(qū)(Global Static Area): 全局變量和靜態(tài)變量存放區(qū),程序一經(jīng)編譯好,該區(qū)域便存在。在C++中,由于全局變量和靜態(tài)變量編譯器會(huì)給這些變量自動(dòng)初始化賦值,所以沒有區(qū)分了初始化變量和未初始化變量了(c中區(qū)分了),程序結(jié)束后由系統(tǒng)釋放。
  • 常量區(qū): 這是一塊比較特殊的存儲(chǔ)區(qū),專門存儲(chǔ)不能修改的常量(一般是const修飾的變量,或是一些常量字符串),程序結(jié)束后由系統(tǒng)釋放。
  • 注:c++中代碼還是存在代碼區(qū)的。

    所以我們平時(shí)所說的代碼的運(yùn)行,分配,操作等,都是指的虛擬內(nèi)存!!!!!!!!

    三、程序占用的內(nèi)存是虛擬內(nèi)存還是物理內(nèi)存

    要討論這個(gè)問題,先看看一些基本的內(nèi)存相關(guān)知識(shí)。

    3.1 內(nèi)存管理

    3.1.1 內(nèi)存管理概念

    一提到內(nèi)存管理,我們頭腦中閃出的兩個(gè)概念,就是虛擬內(nèi)存與物理內(nèi)存。這兩個(gè)概念主要來自于linux內(nèi)核的支持。

    Linux在內(nèi)存管理上份為兩級,一是線性區(qū),類似于00c73000-00c88000,對應(yīng)于虛擬內(nèi)存,它實(shí)際上不占用實(shí)際物理內(nèi)存;二是具體的物理頁面,它對應(yīng)我們機(jī)器上的物理內(nèi)存。

    這里要提到一個(gè)很重要的概念,內(nèi)存的延遲分配。Linux內(nèi)核在用戶申請內(nèi)存的時(shí)候,只是給它分配了一個(gè)線性區(qū)(也就是虛存),并沒有分配實(shí)際物理內(nèi)存;只有當(dāng)用戶使用這塊內(nèi)存的時(shí)候,內(nèi)核才會(huì)分配具體的物理頁面給用戶,這時(shí)候才占用寶貴的物理內(nèi)存。內(nèi)核釋放物理頁面是通過釋放線性區(qū)(也就是虛存),找到其所對應(yīng)的物理頁面,將其全部釋放的過程。

    char *p=malloc(2048) //這里只是分配了虛擬內(nèi)存2048,并不占用實(shí)際內(nèi)存。 strcpy(p,"123") //分配了物理頁面,雖然只是使用了3個(gè)字節(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這幾個(gè)系統(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)用呢?

    答案是否定的,這里我要引入一個(gè)新的概念,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)存時(shí),glibc就從自己獲得的內(nèi)存中取出一塊給進(jìn)程。

    3.1.3 內(nèi)存管理器面臨的困難

    我們在寫程序的時(shí)候,每次申請的內(nèi)存塊大小不規(guī)律,而且存在頻繁的申請和釋放,這樣不可避免的就會(huì)產(chǎn)生內(nèi)存碎塊。而內(nèi)存碎塊,直接會(huì)導(dǎo)致大塊內(nèi)存申請無法滿足,從而更多的占用系統(tǒng)資源;如果進(jìn)行碎塊整理的話,又會(huì)增加cpu的負(fù)荷,很多都是互相矛盾的指標(biāo),這里我就不細(xì)說了。

    我們在寫程序時(shí),涉及內(nèi)存時(shí),有兩個(gè)概念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)存釋放時(shí),使用unmmap系統(tǒng)調(diào)用將這塊內(nèi)存釋放,這個(gè)過程中間不會(huì)產(chǎn)生內(nèi)存碎塊等問題。

    針對小塊內(nèi)存的申請,在程序啟動(dòng)之后,進(jìn)程會(huì)獲得一個(gè)heap底端的地址,進(jìn)程每次進(jìn)行內(nèi)存申請時(shí),glibc會(huì)將堆頂向上增長來擴(kuò)展內(nèi)存空間,也就是我們所說的堆地址向上增長。在對這些小塊內(nèi)存進(jìn)行操作時(shí),便會(huì)產(chǎn)生內(nèi)存碎塊的問題。實(shí)際上brk和sbrk系統(tǒng)調(diào)用,就是調(diào)整heap頂?shù)刂分羔槨?/p>

    那么heap堆的內(nèi)存是什么時(shí)候釋放呢?

    當(dāng)glibc發(fā)現(xiàn)堆頂有連續(xù)的128k的空間是空閑的時(shí)候,它就會(huì)通過brk或sbrk系統(tǒng)調(diào)用,來調(diào)整heap頂?shù)奈恢?#xff0c;將占用的內(nèi)存返回給系統(tǒng)。這時(shí),內(nèi)核會(huì)通過刪除相應(yīng)的線性區(qū),來釋放占用的物理內(nèi)存。

    下面我要講一個(gè)內(nèi)存空洞的問題:

    一個(gè)場景,堆頂有一塊正在使用的內(nèi)存,而下面有很大的連續(xù)內(nèi)存已經(jīng)被釋放掉了,那么這塊內(nèi)存是否能夠被釋放?其對應(yīng)的物理內(nèi)存是否能夠被釋放?

    很遺憾,不能。

    這也就是說,只要堆頂?shù)牟糠稚暾垉?nèi)存還在占用,我在下面釋放的內(nèi)存再多,都不會(huì)被返回到系統(tǒng)中,仍然占用著物理內(nèi)存。為什么會(huì)這樣呢?

    這主要是與內(nèi)核在處理堆的時(shí)候,過于簡單,它只能通過調(diào)整堆頂指針的方式來調(diào)整調(diào)整程序占用的線性區(qū)(虛擬內(nèi)存);而又只能通過調(diào)整線性區(qū)(虛擬內(nèi)存)的方式,來釋放內(nèi)存。所以只要堆頂不減小,占用的內(nèi)存就不會(huì)釋放。

    提一個(gè)問題:

    char *p=malloc(2); free(p)

    為什么申請內(nèi)存的時(shí)候,需要兩個(gè)參數(shù),一個(gè)是內(nèi)存大小,一個(gè)是返回的指針;而釋放內(nèi)存的時(shí)候,卻只要內(nèi)存的指針呢?

    這主要是和glibc的內(nèi)存管理機(jī)制有關(guān)。glibc中,為每一塊內(nèi)存維護(hù)了一個(gè)chunk的結(jié)構(gòu)。glibc在分配內(nèi)存時(shí),glibc先填寫chunk結(jié)構(gòu)中內(nèi)存塊的大小,然后是分配給進(jìn)程的內(nèi)存。

    chunk ------size p------------ content

    在進(jìn)程釋放內(nèi)存時(shí),只要?指針減去4?便可以找到該塊內(nèi)存的大小,從而釋放掉。

    注:glibc在做內(nèi)存申請時(shí),最少分配16個(gè)字節(jié),以便能夠維護(hù)chunk結(jié)構(gòu)。

    3.2 代碼占用的內(nèi)存

    3.2.1 代碼啟動(dòng)過程的內(nèi)存管理

    數(shù)據(jù)部分占用內(nèi)存,那么我們寫的程序是不是也占用內(nèi)存呢?

    在linux中,程序的加載,涉及到兩個(gè)工具,linker 和loader。Linker主要涉及動(dòng)態(tài)鏈接庫的使用,loader主要涉及軟件的加載。

  • exec執(zhí)行一個(gè)程序
  • elf為現(xiàn)在非常流行的可執(zhí)行文件的格式,它為程序運(yùn)行劃分了兩個(gè)段,一個(gè)段是可以執(zhí)行的代碼段,它是只讀,可執(zhí)行;另一個(gè)段是數(shù)據(jù)段,它是可讀寫,不能執(zhí)行。
  • loader會(huì)啟動(dòng),通過mmap系統(tǒng)調(diào)用,將代碼端和數(shù)據(jù)段映射到內(nèi)存中,其實(shí)也就是為其分配了虛擬內(nèi)存,注意這時(shí)候,還不占用物理內(nèi)存;只有程序執(zhí)行到了相應(yīng)的地方,內(nèi)核才會(huì)為其分配物理內(nèi)存。
  • ?loader會(huì)去查找該程序依賴的鏈接庫,首先看該鏈接庫是否被映射進(jìn)內(nèi)存中,如果沒有使用mmap,將代碼段與數(shù)據(jù)段映射到內(nèi)存中,否則只是將其加入進(jìn)程的地址空間。這樣比如glibc等庫的內(nèi)存地址空間是完全一樣。
  • 因此一個(gè)2M的程序,執(zhí)行時(shí),并不意味著為其分配了2M的物理內(nèi)存,這與其運(yùn)行了的代碼量,與其所依賴的動(dòng)態(tài)鏈接庫有關(guān)。

    3.2.2 運(yùn)行過程中鏈接動(dòng)態(tài)鏈接庫與編譯過程中鏈接動(dòng)態(tài)庫的區(qū)別

    我們調(diào)用動(dòng)態(tài)鏈接庫有兩種方法:一種是編譯的時(shí)候,指明所依賴的動(dòng)態(tài)鏈接庫,這樣loader可以在程序啟動(dòng)的時(shí)候,將所有的動(dòng)態(tài)鏈接映射到內(nèi)存中;一種是在運(yùn)行過程中,通過dlopen和dlfree的方式加載動(dòng)態(tài)鏈接庫,動(dòng)態(tài)將動(dòng)態(tài)鏈接庫加載到內(nèi)存中。

    從編程角度來講,第一種是最方便的,效率上影響也不大。

    在內(nèi)存使用上有些差別:

    第一種方式,一個(gè)庫的代碼,只要運(yùn)行過一次,便會(huì)占用物理內(nèi)存,之后即使再也不使用,也會(huì)占用物理內(nèi)存,直到進(jìn)程的終止。

    第二中方式,庫代碼占用的內(nèi)存,可以通過dlfree的方式,釋放掉,返回給物理內(nèi)存。

    對于那些壽命很長,但又會(huì)偶爾調(diào)用各種庫的進(jìn)程有關(guān)。如果是這類進(jìn)程,建議采用第二種方式調(diào)用動(dòng)態(tài)鏈接庫。

    3.3 總結(jié)

    從前所述可知,每一個(gè)程序運(yùn)行都會(huì)構(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)存中的地址

    一般來說,指針是不是邏輯地址根本就不重要。在這些高級的編程語言中,所有的地址在寫的時(shí)候都是邏輯地址,最終都會(huì)映射到物理地址,不然沒法運(yùn)行。

    對應(yīng)用程序來說,不需要關(guān)心這個(gè)??梢哉J(rèn)為是虛擬內(nèi)存的地址,程序加載/運(yùn)行時(shí)操作系統(tǒng)/硬件會(huì)進(jìn)行正確的轉(zhuǎn)換。如果在沒有MMU的系統(tǒng)中,通常虛擬內(nèi)存地址和物理地址是一回事。所以具體情況得根據(jù)實(shí)際情況具體分析。

    不過既然是探討,那就具體說一說。

    首先我們要知道,不同進(jìn)程地址空間是相互隔離的,不同進(jìn)程兩個(gè)值相同的指針對應(yīng)的真實(shí)內(nèi)存是不同的。

    指針變量存儲(chǔ)的地址應(yīng)該是指虛擬地址,也就是在程序中能通過那個(gè)地址訪問變量的地址。而普通指針是指"物理地址"。

    指向類成員的指針是 經(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)存分配方式詳解(堆、棧、自由存儲(chǔ)區(qū)、全局/靜態(tài)存儲(chǔ)區(qū)和常量存儲(chǔ)區(qū))_行者三個(gè)石的博客-CSDN博客

    指針變量是存儲(chǔ)的物理地址還是有效地址即偏移量?-CSDN論壇

    C++內(nèi)存模型_TemetNosce的博客-CSDN博客_c++ 內(nèi)存模型

    ?

    總結(jié)

    以上是生活随笔為你收集整理的详解操作系统中虚拟内存与物理内存的关系的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。