??? ??????? 歡迎來(lái)到小林的博客??!
?????????博客主頁(yè):??小林愛(ài)敲代碼
?????????博客專(zhuān)欄:??Linux之路
?????????社區(qū) :?? 進(jìn)步學(xué)堂
?????????歡迎關(guān)注:??點(diǎn)贊??收藏??留言
??進(jìn)程地址空間
我們?cè)趯W(xué)習(xí)C語(yǔ)言的時(shí)候,應(yīng)該都知道這個(gè)內(nèi)存空間圖。
但其實(shí)我們對(duì)它并不了解,為什么呢?我們用一段代碼來(lái)感受一下!
#include<stdio.h>
#include<unistd.h>
int g_val = 100;
int main()
{
int pid = fork(); //創(chuàng)建子進(jìn)程
if(pid == 0)
{
//child
int count = 5;
while(count)
{
printf("i am child , g_val = %d, &g_val = %p\n",g_val,&g_val);
if(count == 3)
{
//修改數(shù)據(jù)
printf("******開(kāi)始修改數(shù)據(jù)*******\n");
printf("i am child , g_val = %d, &g_val = %p\n",g_val,&g_val);
g_val = 200;
printf("******修改數(shù)據(jù)done*******\n");
}
count--;
sleep(1);
}
}
else if(pid > 0)
{
//parent
while(1)
{
printf("i am father , g_val = %d, &g_val = %p\n",g_val,&g_val);
sleep(1);
}
}
else
{
//erro
perror("fork:");
}
return 0;
}
我們這個(gè)代碼的主體邏輯是,創(chuàng)建一個(gè)全局變量。然后再創(chuàng)建一個(gè)子進(jìn)程,隨后打印全局變量的值和地址,在子進(jìn)程特定的時(shí)候修改這個(gè)全局變量。
那么我們來(lái)看看運(yùn)行結(jié)果吧!
我們可以發(fā)現(xiàn),g_val的值被修改了! 但是它們的地址還是一樣的。這是怎么回事?。???
我們都知道,父進(jìn)程和子進(jìn)程如果沒(méi)有發(fā)生數(shù)據(jù)修改,那么會(huì)共用同一份數(shù)據(jù)。如果有一方的數(shù)據(jù)發(fā)生了修改,那么就會(huì)寫(xiě)實(shí)拷貝一份。所以此時(shí)的父進(jìn)程和子進(jìn)程各有一份屬于自己的數(shù)據(jù),既然有2份數(shù)據(jù)那么就說(shuō)明有2個(gè)g_val。2個(gè)獨(dú)立進(jìn)程的g_val變量用了同一塊內(nèi)存空間,這合理嗎?完全不合理?。?!2個(gè)進(jìn)程用同一塊空間,這不就起沖突了。這是為什么呢??
再探索這個(gè)問(wèn)題之前,先給大家講個(gè)小故事,方便大家理解。
在美國(guó)有一個(gè)大富翁,他有三個(gè)私生子,這三個(gè)私生子互相不認(rèn)識(shí)。而這個(gè)大富翁有100億美金。
然后這個(gè)時(shí)候,大富翁對(duì)私生子小A說(shuō):等我老了,就由你來(lái)繼承我100億財(cái)產(chǎn)吧。這時(shí)小A就以為這100億是他的了。然后大富翁又對(duì)私生子小B說(shuō):等我老了,你來(lái)繼承我的100億吧。 小B聽(tīng)了高興壞了,也以為這100億是他的了。然后大富翁又對(duì)私生子小C說(shuō)了同樣的話。 所以,大富翁給他的所有私生子都花了一張大餅。告訴他們,他們未來(lái)都會(huì)繼承這100億。所以大富翁的私生子,都認(rèn)為自己有100億可以花,就可以按照這100億來(lái)為自己分配生活。
而這里面的大富翁,就是操作系統(tǒng),私生子就是進(jìn)程,大餅就是進(jìn)程地址空間,那么這100億美金,就是我們的物理內(nèi)存。而我在之前的篇章里說(shuō)過(guò),進(jìn)程的本質(zhì)其實(shí)就是 描述進(jìn)程的結(jié)構(gòu)體(PCB)+代碼數(shù)據(jù)。那么進(jìn)程地址空間是否在PCB里呢?答案當(dāng)然是的。也就是說(shuō)每個(gè)進(jìn)程都會(huì)有一個(gè)進(jìn)程地址空間,每個(gè)私生子都認(rèn)為自己獨(dú)占了大富翁的100億美金,所以每個(gè)進(jìn)程都認(rèn)為自己獨(dú)占了物理內(nèi)存。 所以,我們的**進(jìn)程地址空間,也被我們稱(chēng)之為虛擬內(nèi)存。**那么為什么會(huì)打印相同地址?我們先了解一些東西,再最侯為大家總結(jié)結(jié)論。
??進(jìn)程地址空間是什么?
那么進(jìn)程地址空間是什么呢?地址空間本質(zhì)是內(nèi)核中的一種數(shù)據(jù)類(lèi)型,在Linux內(nèi)核中,它是一個(gè)struct mm_struct
的結(jié)構(gòu)體。
也就說(shuō),我們程序的內(nèi)存劃分,本質(zhì)上是一個(gè)區(qū)域??!
進(jìn)程地址空間的劃分
那么進(jìn)程地址空間是怎么劃分的?
打個(gè)比方:
假如你現(xiàn)在是一名小學(xué)生,你的同桌是一名愛(ài)干凈的小女孩。而你一名愛(ài)流鼻涕不講衛(wèi)生的小男孩,這時(shí)侯,你的同桌嫌棄你。假設(shè)你倆的桌子長(zhǎng)100cm,此時(shí)你的同桌在50cm的地方畫(huà)了一根三八線,跟你劃清界限。那么此時(shí)你還能不能把東西放到你同桌所在的區(qū)域?當(dāng)時(shí)是不能了!假設(shè)你的區(qū)域是 0 - 50cm的地方,那么你的東西只能放在0-50區(qū)間。假設(shè)這時(shí)候有一把尺子,你想把你的橡皮擦放在第38cm的地方,于是你就拿尺子量出了38cm,把橡皮放在這個(gè)位置上。這里面呢,你和你同桌,充當(dāng)?shù)氖且粔K區(qū)域,而這把尺子,是進(jìn)程地址空間,橡皮擦,則是你的數(shù)據(jù)。你要把數(shù)據(jù)放在指定的地方,那么就需要進(jìn)程地址空間充當(dāng)尺子。為什么你知道桌子是100cm?因?yàn)橛谐咦?,所以你才知道桌子?00cm。所以你要?jiǎng)澐謪^(qū)域,也需要進(jìn)程地址空間充當(dāng)尺子來(lái)劃分區(qū)域。
struct mm_struct
{
unsigned int code_start; //代碼段起始地址
unsigned int code_end; //代碼段結(jié)束地址
unsigned int init_data_start;//初始化變量區(qū)起始地址.
unsigned int init_data_end;//初始化變量區(qū)結(jié)束地址
unsigned int uninit_data_start;//未初始化變量區(qū)起始地址.
unsigned int uninit_data_end;//未初始化變量區(qū)結(jié)束地址
unsigned int heap_start;//堆區(qū)起始地址.
unsigned int heap_end;//堆區(qū)結(jié)束地址
.....
unsigned int stack_start;//堆區(qū)起始地址.
unsigned int stack_end;//堆區(qū)結(jié)束地址
}
每個(gè)進(jìn)程都認(rèn)為地址空間的劃分是按照4GB的空間劃分的,而地址空間上進(jìn)行區(qū)域劃分的位置,是虛擬地址!雖然這里只要start和end,但是每個(gè)進(jìn)程都可以認(rèn)為mm_struct 代表整個(gè)內(nèi)存,且所有的地址為0x00000000 -> 0xFFFFFFFF。
虛擬內(nèi)存轉(zhuǎn)換成物理內(nèi)存
既然每個(gè)進(jìn)程都有一塊地址空間,而程序里面的數(shù)據(jù)和代碼都是根據(jù)進(jìn)程地址空間存放。那么我們的系統(tǒng)調(diào)用它時(shí)是如何為它分配地址的呢?如何把對(duì)應(yīng)的數(shù)據(jù)放到物理內(nèi)存的呢?
那是因?yàn)槲锢韮?nèi)存和虛擬內(nèi)存之間,有一張頁(yè)表。
而頁(yè)表的本質(zhì)就是哈希表。通過(guò)虛擬內(nèi)存來(lái)映射物理內(nèi)存。也就說(shuō),每一個(gè)進(jìn)程地址空間,都會(huì)有一張對(duì)應(yīng)的頁(yè)表。意思就是每一個(gè)進(jìn)程都會(huì)有一張頁(yè)表,通過(guò)頁(yè)表的虛擬地址,就可以找到對(duì)應(yīng)的物理內(nèi)存。從而操作系統(tǒng)對(duì)物理內(nèi)存進(jìn)行操作。
??為什么要有進(jìn)程地址空間?
1. 通過(guò)添加一層軟件層,完成有效的對(duì)進(jìn)程操作內(nèi)存進(jìn)行風(fēng)險(xiǎn)管理(權(quán)限管理),本質(zhì)的目的是為了,保護(hù)物理內(nèi)存以及各個(gè)進(jìn)程的數(shù)據(jù)安全。
比如:
先給大家放一段代碼。
int main()
{
const char* str = "hello world";
str = "HW";
return 0;
}
這段代碼會(huì)報(bào)錯(cuò),為什么呢?因?yàn)?str它所處的內(nèi)存空間是常量區(qū)。通過(guò)虛擬內(nèi)存映射到真實(shí)的物理地址之后,它的權(quán)限是只讀權(quán)限。當(dāng)你修改它時(shí),因?yàn)槟悴痪邆鋵?xiě)權(quán)限,所以操作系統(tǒng)會(huì)直接把你干掉。這也是為什么要有進(jìn)程地址空間的原因。如果沒(méi)有進(jìn)程地址空間,那么就無(wú)法進(jìn)行權(quán)限管理,那么即使是常量也可以被修改!這是非常嚴(yán)重的!而有了進(jìn)程地址空間之后,你能不能修改,全部取決于操作系統(tǒng)讓不讓你修改!
2. 將內(nèi)存申請(qǐng)和內(nèi)存使用的概念在時(shí)間上劃分清除,通過(guò)虛擬地址空間,來(lái)屏蔽底層申請(qǐng)的過(guò)程,達(dá)到進(jìn)程讀寫(xiě)內(nèi)存和OS進(jìn)行內(nèi)存管理操作,進(jìn)行軟件上面的分離!
先拋出一個(gè)問(wèn)題:假如我們申請(qǐng)5000個(gè)字節(jié),我們立馬能使用這5000字節(jié)嗎??
答案是:不一定,可能會(huì)存在暫時(shí)不會(huì)全部使用,甚至?xí)簳r(shí)不使用的情況。
因?yàn)椋贠S(操作系統(tǒng))的角度上,如果空間立馬就給你的話,是不是就意味著,整個(gè)系統(tǒng)會(huì)有一部分空間,本來(lái)可以先給其他進(jìn)程立馬使用,現(xiàn)在卻被你閑置著?說(shuō)簡(jiǎn)單點(diǎn)就是這個(gè)進(jìn)程現(xiàn)在正茅坑,但絲毫沒(méi)有要拉屎的意思。這種做法是人人恨之的,所以操作系統(tǒng)不一定會(huì)立馬給你使用。
打個(gè)比方:比如你要開(kāi)學(xué)了,你和你老爹要8000塊錢(qián)的學(xué)費(fèi)。但是你還有一星期才開(kāi)學(xué)呢,于是你老爹說(shuō):好,我知道了,開(kāi)學(xué)前一天給你。 你像你老爸要了8000塊錢(qián),你老爸對(duì)應(yīng)給你了。這就就相當(dāng)于你申請(qǐng)了8000字節(jié)的空間。但是你還有一星期才開(kāi)學(xué),也就是你這8000塊錢(qián)暫時(shí)用不上,你這8000字節(jié)也暫時(shí)用不上。所以你爸說(shuō)等你開(kāi)學(xué)前一天的時(shí)候給你,而操作系統(tǒng)也在進(jìn)程要使用的時(shí)候,給進(jìn)程真實(shí)的物理內(nèi)存。
而這和我們的寫(xiě)實(shí)拷貝非常的像,數(shù)據(jù)不改變就共用同一份數(shù)據(jù),改變就拷貝一份。
3.站在CPU和應(yīng)用層的角度,進(jìn)程統(tǒng)一可以看做統(tǒng)一使用4GB空間,而且每個(gè)空間區(qū)域的相對(duì)位置,是比較確定的!OS最終這樣設(shè)計(jì)的目的,達(dá)到了一個(gè)目標(biāo):每個(gè)進(jìn)程都認(rèn)為自己是獨(dú)占系統(tǒng)資源的!進(jìn)程具有獨(dú)立性的!
這種情況也就是我們開(kāi)頭演示的那樣,為什么2個(gè)進(jìn)程的g_val的地址是相同,而值是不同的。這是因?yàn)?*子進(jìn)程創(chuàng)建是以父進(jìn)程為模板創(chuàng)建的,所以子進(jìn)程也會(huì)繼承父進(jìn)程的頁(yè)表。**在子進(jìn)程沒(méi)有對(duì)g_val的值進(jìn)行修改時(shí),父子進(jìn)程共享一份數(shù)據(jù)。而一旦子進(jìn)程對(duì)g_val的值進(jìn)行修改,那么在OS會(huì)對(duì)g_val的數(shù)據(jù)進(jìn)行一份拷貝(寫(xiě)實(shí)拷貝)。且讓子進(jìn)程頁(yè)表映射到g_val的值映射至新拷貝后的物理地址。這樣子,即使它們的g_val的地址是相同的,但是在它們?cè)陧?yè)表 g_val的數(shù)據(jù) 是映射到不同的物理地址。
最后,為什么下面str1和str2的地址是相等的?文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-464658.html
#include<stdio.h>
int main()
{
const char* str1 = "hello world";
const char* str2 = "hello world";
printf("str1 的地址是: %p",str1);
printf("str2 的地址是: %p",str2);
}
如上代碼,我們會(huì)發(fā)現(xiàn)str1和str2的地址是一樣的,可是它們不是同一個(gè)變量啊,為什么?因?yàn)閟tr1和str2都在常量區(qū)。也就是說(shuō)操作系統(tǒng)只給了這個(gè)區(qū)域可讀權(quán)限,所以操作系統(tǒng)認(rèn)為,對(duì)于只有可讀的數(shù)據(jù),操作系統(tǒng)只需要維護(hù)一份即可。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-464658.html
到了這里,關(guān)于【Linux】進(jìn)程地址空間(帶你認(rèn)清內(nèi)存的本質(zhì))的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!