環(huán)境:centos7.6,騰訊云服務(wù)器
Linux文章都放在了專欄:【 Linux 】歡迎支持訂閱 ??
相關(guān)文章推薦:
【Linux】馮.諾依曼體系結(jié)構(gòu)與操作系統(tǒng)
【Linux】進(jìn)程理解與學(xué)習(xí)Ⅰ-進(jìn)程概念
淺談Linux下的shell--BASH
【Linux】進(jìn)程理解與學(xué)習(xí)Ⅱ-進(jìn)程狀態(tài)
【Linux】進(jìn)程理解與學(xué)習(xí)Ⅲ-環(huán)境變量
前言
在C/C++階段對(duì)于內(nèi)存分布相關(guān)知識(shí)我們耳熟能詳。知道 內(nèi)存空間的劃分是為了更好的管理和使用空間。就比如說棧區(qū)存放局部變量、靜態(tài)區(qū)存放靜態(tài)全局變量等。但是,我們這里的空間真的指的是
實(shí)際的物理空間嗎?換句話來說,我們真的了解該空間嗎?本次章節(jié)將對(duì)此進(jìn)行探討。
進(jìn)程地址空間
前文回顧
首先,我們先來回顧一下,在指針階段我們學(xué)習(xí)了,內(nèi)存被劃分為一個(gè)一個(gè)內(nèi)存單元,每一個(gè)單元的大小為1字節(jié)。而每一個(gè)內(nèi)存單元都有自己的編號(hào),從0-0xFFFFFFFF。這里的編號(hào)就是我們所說的地址。而我們所說的指針就是這一個(gè)個(gè)的編號(hào),即指針就是地址。不過這里的地址真的是物理意義上的地址嗎?
進(jìn)程地址空間
我們先來看這樣一段代碼:
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
//定義全局變量
int tmp=100;
int main()
{
//fork創(chuàng)建子進(jìn)程
pid_t id=fork();
if(id == 0)
{
//child
//對(duì)全局變量做修改
tmp+=100;
printf("子進(jìn)程:tmp:%d,&tmp:%p\n",tmp,&tmp);
exit(1);
}
//father
waitpid(id,NULL,0);
printf("父進(jìn)程:tmp:%d,&tmp:%p\n",tmp,&tmp);
return 0;
}

對(duì)于此現(xiàn)象,我們?cè)谇拔囊仓懒?,這是由于進(jìn)程的獨(dú)立性,子進(jìn)程在對(duì)數(shù)據(jù)進(jìn)行修改時(shí),會(huì)觸發(fā)寫時(shí)拷貝所造成的。但是,假如這里的地址是物理地址的話,同一塊地址處卻有不同的值,這肯定是不現(xiàn)實(shí)的?!?span id="n5n3t3z" class="kdocs-fontSize" style="font-size:13pt;">因此,我們可以得出這樣的結(jié)論:
我們?cè)谡Z言層面所看到的地址(棧區(qū)、堆區(qū)、靜態(tài)區(qū)...),并不是真正意義上的物理地址(因?yàn)榧偃缡俏锢淼刂罚筒粫?huì)出現(xiàn)同一個(gè)地址卻有不同的值)。
那么這種非物理的地址叫什么呢?在Linux中我們稱之為虛擬地址/線性地址。
OS則是將虛擬地址轉(zhuǎn)化為物理地址(如何轉(zhuǎn)化后面會(huì)講到)
如何理解進(jìn)程地址空間?
首先我們要知道,什么是進(jìn)程地址空間?
實(shí)際上進(jìn)程地址空間就是操作系統(tǒng)喂給進(jìn)程的一塊“餅”,OS會(huì)跟每個(gè)進(jìn)程說,你們有4G的內(nèi)存空間(棧區(qū)、堆區(qū)、靜態(tài)區(qū)...)可以使用,但實(shí)際上,只有當(dāng)進(jìn)程需要用的時(shí)候,OS才會(huì)分配空間給進(jìn)程。
舉個(gè)例子來說,就好比一位富翁,對(duì)他的幾個(gè)兒子說,我的10億的資產(chǎn)都是你們的。此時(shí)兒子心里就會(huì)覺得:我有10億資產(chǎn)可以使用。但實(shí)際上富翁并不會(huì)直接就是給兒子10億資產(chǎn),兒子也不會(huì)直接拿到10億資產(chǎn)。但是假如說,兒子要拿1w元買東西,富翁還是會(huì)給兒子·的。此時(shí)給的1w才是真正意義上實(shí)際的。

接下來談一談OS如何管理我們所說的進(jìn)程地址空間(即我們所說的棧區(qū)、堆區(qū)等)?
答:先描述,再組織。實(shí)際上我們所說的進(jìn)程地址空間本質(zhì)上是一個(gè)內(nèi)核數(shù)據(jù)結(jié)構(gòu),struct_mmstruct{}。在該結(jié)構(gòu)體里存在著大量的_start與_end。用來表示每一個(gè)區(qū)域各自的邊界值。
就比如說:堆區(qū)的區(qū)域范圍為[heap_start,heap_end]。而對(duì)進(jìn)程地址空間中各個(gè)區(qū)域的調(diào)整,實(shí)際上就是轉(zhuǎn)換為了調(diào)整各個(gè)區(qū)域?qū)?yīng)的_start與_end。

物理空間與虛擬空間
既然我們所說的地址都是虛擬地址,那么真正的物理地址在哪里呢???虛擬地址與物理地址之間又有什么關(guān)系呢?
實(shí)際上,OS會(huì)通過頁(yè)表,以及MMU的存在,將我們所謂的虛擬地址與物理地址之間建立一種映射關(guān)系,通過虛擬地址映射后的地址,可以尋到物理地址。同時(shí)可以將物理地址,經(jīng)過頁(yè)表映射虛擬地址返回給進(jìn)程。就好像下面這樣:

寫時(shí)拷貝
我們來解釋一下最開始的現(xiàn)象:為什么父子進(jìn)程的tmp地址相同,但結(jié)果不同呢?
實(shí)際上當(dāng)一方進(jìn)程想要對(duì)數(shù)據(jù)進(jìn)行修改時(shí),會(huì)觸發(fā)寫時(shí)拷貝,將物理空間原有的指向內(nèi)容拷貝出一份,在拷貝后的那里進(jìn)行對(duì)數(shù)據(jù)的修改,并將拷貝后的物理地址重新與原有的虛擬地址建立映射關(guān)系:

因此我們也可以這么來說,所謂的寫時(shí)拷貝,實(shí)際上是操作系統(tǒng)的一種賭博式行為。OS賭你不會(huì)對(duì)數(shù)據(jù)進(jìn)行修改,所以當(dāng)各個(gè)進(jìn)程不對(duì)數(shù)據(jù)進(jìn)行修改時(shí),多個(gè)進(jìn)程在此時(shí)訪問同一個(gè)數(shù)據(jù),實(shí)際上該數(shù)據(jù)所在的物理空間是同一塊。只有當(dāng)進(jìn)程對(duì)數(shù)據(jù)進(jìn)行修改時(shí),OS才會(huì)另外開辟空間,并將原物理空間的內(nèi)容拷貝進(jìn)去,重新建立一種映射關(guān)系。并滿足進(jìn)程對(duì)數(shù)據(jù)的修改。而這也是進(jìn)程獨(dú)立性的一種重要表現(xiàn),即多個(gè)進(jìn)程互不影響。
而寫時(shí)拷貝這種“賭博行為”機(jī)制的好處就在于:
1、減少了物理空間的使用(多個(gè)進(jìn)程的數(shù)據(jù)訪問的是同一塊空間)
2、減少了寫時(shí)拷貝的次數(shù)(只有需要修改數(shù)據(jù)時(shí)才會(huì)發(fā)生拷貝,否則不會(huì)),提高了運(yùn)行效率(寫時(shí)拷貝一定會(huì)調(diào)用拷貝構(gòu)造進(jìn)行深拷貝,會(huì)有一定效率的影響)。
拓展:為什么存在進(jìn)程地址空間?
一、防止地址隨意訪問,保護(hù)物理內(nèi)存與其它進(jìn)程
實(shí)際上,在最開始的時(shí)候,還沒有虛擬地址這種概念。早期的進(jìn)程是直接與物理內(nèi)存打交道。但是可能會(huì)存在野指針問題:
假如我們寫的程序中存在野指針,這就造成了對(duì)物理內(nèi)存越界訪問,就有可能會(huì)影響到其它進(jìn)程。但是現(xiàn)在有了虛擬地址,進(jìn)程不會(huì)與物理內(nèi)存直接打交道,OS就相當(dāng)于多了一道屏障,對(duì)于進(jìn)程發(fā)出的不合理的請(qǐng)求,OS可以拒絕。
(就好比富翁不會(huì)直接把10億元直接給兒子,因?yàn)閮鹤涌赡芤粫?huì)兒就敗光了,而是告訴兒子,你有10億元的資產(chǎn)可以使用,我?guī)湍惚9?,你需要時(shí)再給你。這樣當(dāng)兒子發(fā)出不合理的使用時(shí),富翁可以直接拒絕)

二、將進(jìn)程管理與內(nèi)存管理進(jìn)行解耦合
我們先來談一談malloc的本質(zhì),實(shí)際上我們平常使用malloc開辟一塊空間時(shí),OS并不是說直接給我們開辟出一塊空間給我們。而是只有當(dāng)我們需要這塊空間時(shí),OS再開辟空間供我們使用。
這是因?yàn)镺S不允許任何空間的浪費(fèi)。而當(dāng)我們malloc之后,使用之前,這塊空間處于一種閑置狀態(tài),OS是絕對(duì)不允許的。這就是所謂的"缺頁(yè)中斷"。
因此對(duì)于進(jìn)程來說,我只需要通過頁(yè)表映射向內(nèi)存去要,對(duì)于內(nèi)存來說,我只需要在進(jìn)程使用空間時(shí)提供一塊沒被使用的空間。這就實(shí)現(xiàn)了進(jìn)程管理與內(nèi)存管理之間的解耦!

三、讓進(jìn)程以統(tǒng)一的視角,看待自己的代碼與數(shù)據(jù)
實(shí)際上虛擬地址的這種策略并不僅僅只有OS才有,我們的編譯器也會(huì)遵循。也就是說,我們的程序在被編譯時(shí),本身內(nèi)部已經(jīng)存在了虛擬地址。我們可以輸入指令objdump -S 可執(zhí)行程序的指令,來查看該程序的反匯編,就好像下面這樣,這些都是虛擬地址:

也就是說,我們的程序在被加載到內(nèi)存之前,本身內(nèi)部就已經(jīng)有了虛擬地址: 加載到物理內(nèi)存之后,則天然具有物理地址,然后通過 頁(yè)表映射,建立與虛擬地址之間的聯(lián)系。而當(dāng)CPU進(jìn)行調(diào)度時(shí),通過虛擬地址經(jīng)過頁(yè)表映射后,將物理地址的內(nèi)容加載到CPU運(yùn)行,此時(shí) CPU內(nèi)部全都是程序內(nèi)部已經(jīng)存在的虛擬地址,再緊接著,CPU通過虛擬地址經(jīng)過頁(yè)表尋址到物理地址,并加載到CPU運(yùn)行...循環(huán)以往,直到跑完整個(gè)程序。
因此 對(duì)于每一個(gè)進(jìn)程來說,我并不需要關(guān)心我內(nèi)部的代碼與數(shù)據(jù)被加載到物理內(nèi)存的哪一個(gè)位置,不管是否物理地址連續(xù)有序,都會(huì)經(jīng)過頁(yè)表映射建立與虛擬地址之間的聯(lián)系,將物理內(nèi)存的并不連續(xù)有序的物理地址,轉(zhuǎn)化為了虛擬內(nèi)存中有序的虛擬地址。每一個(gè)進(jìn)程都是如此,將看待物理內(nèi)存中并不有序的物理地址,經(jīng)過映射后轉(zhuǎn)化為看待虛擬內(nèi)存中的有序地址。

end.文章來源:http://www.zghlxwxcb.cn/news/detail-413107.html
生活原本沉悶,但跑起來就會(huì)有風(fēng)!??文章來源地址http://www.zghlxwxcb.cn/news/detail-413107.html
到了這里,關(guān)于【Linux】進(jìn)程理解與學(xué)習(xí)Ⅳ-進(jìn)程地址空間的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!