一,內存地址空間1.1,棧區存儲變量:普通局部變量、指針變量、函數參數、函數返回地址、臨時變量、寄存器變量;
函數參數:函數的參數是從右到左依次入棧的;
在vs2022上棧區并不是”向下生長的”,而是正常的棧,因此推斷結果應該是b的地址較低,a的地址比b的地址高;
函數返回地址:函數返回地址是指函數執行完畢后,程序需要跳轉回繼續執行的內存地址(即函數的地址)。當一個函數被調用時,當前程序的執行流程會轉移到該函數內部。在函數執行完成后,程序需要知道回到哪里繼續執行后續的代碼,這個“回去的位置”對應的內存地址就是函數返回地址。
1.2,堆區介紹
特點
動態分配:程序運行時可根據需要隨時在堆區申請和釋放內存空間,大小可在運行時確定,適應不同數據結構和算法的需求;
空間較大:一般來說,堆區的空間比棧區的空間更大,因為它是隨機開辟的,并不像棧區地址都是連續的,所以空間更大,方便使用;
管理復雜:需要程序員手動管理內存分配和釋放,容易引發懸空指針和由于忘了釋放內存造成的內存泄漏問題;
1.3,全局區(數據段)與常量區全局區也稱為靜態存儲區,用于存放全局變量和靜態變量;
生命周期:隨著進程結束釋放內存;即便是函數內部的局部靜態變量生命周期依舊是整個進程;
我們可以看到即便是超出了作用域,Static int a 的地址空間依舊沒有釋放;這說明靜態局部變量的生命周期不是隨作用域的,而是隨進程的;但只能在作用域中使用(在不返回值的情況下); ——>靜態局部變量相當于全局變量,唯一的區別就是作用域不同;
1.4,代碼段數據段是進程中存儲執行代碼的內存區域,它包含了程序的指令和常量(“1”,”hello world”….);
這其實是在兩個進程中看,父進程執行到fork時還沒來得及return,此時子進程就已經創建出來了,并且也會return一個值,所以有兩個值;那這么看就會產生兩個數據,對吧;接下來我們驗證一下他們的地址;
我們可以看到id的值是不一樣的,但地址確實一樣的,這是為什么呢?按道理說,如果地址相同那同一個地址怎么可能存兩個數據呢?況且還發生了寫時拷貝,地址怎么會相同呢?
其實這里我們看到的是虛擬內存地址;
先來解釋一下上面的情況:
—->父進程創建子進程時確實發生了寫時拷貝,id變量的確有兩份,不過是寫時拷貝開辟的空間是物理內存,這個時候物理內存上存在兩個id地址,一個是父進程的,一個是子進程的,而我們在程序中看到的是虛擬內存地址;物理內存無法在程序中看到;
問題一:為什么要有虛擬地址(進程地址空間)
1.統一進程視角看待內存
現在我們使用OS類比一個大富翁,3個進程類比3個他的孩子;一個大富翁(操作系統)有10億美金,而他有四個私生子,但是四個私生子(進程)都并不知道對方的存在(進程獨立性),所以他們都認為大富翁只有他唯一一個兒子,而大富翁告訴他們一旦自己去世了,就把所有的家產留給他,所以每個兒子也都信了,因此大富翁其實給每個私生子都畫了一個大餅(進程地址空間)。每個人都認為自己有十億家產。但實際上是這些私生子要多少才會給多少(進程需要多少空間操作系統就給多少空間
如果有一張虛擬內存,這樣每個進程就不需要關心當前的物理內存會不會影響到別的進程,我用的時候直接告訴OS,然后他幫我們分配,這樣可以更加方便有序的使進程運行;
2.保護內存地址,出現問題直接攔截(相當于加了一層防護)
當我們申請物理內存空間時,就會利用虛擬地址進行地址審查,在這個轉化過程中,如果虛擬內存地址出現問題,就會直接結束這個過程,就不會直接影響到物理內存;
為什么我們無法修改常量字符串?
常量字符串位于常量區域,但僅僅如此不足以說明不可修改,我們都知道不可修改是一種權限,那這個權限在哪里呢?其實這個權限是在頁表中,當通過虛擬地址訪問物理地址時,會通過頁表轉化并檢查權限,如果沒有權限就會被攔截;
三,什么是進程地址空間我們知道要管理一個對象的方法是–>先描述再組織;
四,頁表現代操作系統不做浪費時間和空間的事;
4.1,寫時拷貝,缺頁中斷,惰性加載首先,頁表中有什么呢?
答:虛擬地址、物理地址、權限位、標志位(是否將對應的代碼和數據加載到內存中)
權限位有什么用?
權限位上有該地址的讀和寫權限,如果該地址是只讀權限那么我們對地址進行修改就會被OS直接攔截,非法請求就不會發送到物理內存,對物理內存起到一定程度的保護作用;
標志位是什么?
標志位是檢查進程該地址需要的代碼是否加載到了內存中;
惰性加載:就是需要多少加載多少,操作系統對于大文件是可以實現分批加載的,也就是進程可能有時會只有PCB在內存中;
缺頁中斷:當所需的代碼和數據還沒有被加載進內存的時候,這時候就會發生缺頁中斷,中斷的意思就是暫時暫停此進程,等待代碼和數據加載進來后就會繼續執行進程;
那么為什么不一次性將代碼數據加載到內存中呢?
從空間的角度思考,一個大文件加載是需要占用很大的空間的,而且進程一開始也不會馬上用到一整塊代碼,所以這個時候有一些代碼數據是空閑的,也就占用了額外的內存空間;
從時間的角度考慮,加載一個大文件十分耗費時間;一次性加載效率不高;
因此出現了缺頁中斷,其用意就是對代碼和數據進行局部性加載,合理使用內存從而提高效率;
寫時拷貝:數據段(全局區)的數據本來是可寫的,但權限確實只讀的,這么做的目的就是為了維持寫時拷貝;當進程雙方中的一方對數據進行修改,就會觸發寫時拷貝機制,重新開辟一塊空間,存儲新的數據,并且修改頁表映射;
4.2進程地址是如何被切換的進程PCB結構體里有對應的進程地址空間指針,所以進程切換就意味著進程空間地址空間被切換,而頁表會被存儲在CPU的cr3寄存器中,這其實屬于進程的上下文信息,在進程切換的時候會被進程帶走,后面再恢復過來!
4.3進程創建的具體分析過程進程被創建的時候,優先被創建和加載的是PCB數據結構和對應的地址空間,代碼和數據等到需要的會后在加載進來;
4.4重新理解進程具有獨立性1.在PCB數據結構上,每一個進程都有自己唯一的PCB;
2.虛擬地址可以相同,但通過頁表映射的物理地址是不同的;各自有各自的區域,對于父子進程在寫時拷貝的機制下,也是擁有自己獨一份的物理地址;
五,命令行參數和環境變量在棧的上面