Linux系统编程14:进程入门之Linux进程中非常重要的概念之进程地址空间-原来我们看到的地址全部是虚拟的
文章目錄
- (1)舊知回顧
- (2)程序地址空間?
- A:同一個地址有兩個數(shù)據(jù)?
- B:物理地址和虛擬地址
- C:進(jìn)程地址空間及作用
- D:進(jìn)程地址空間如何工作
(1)舊知回顧
學(xué)習(xí)C/C++總免不了這張圖
這張圖幫助我們解決了不少C/C++問題,但是我們對它的認(rèn)識還是不夠深刻。現(xiàn)在我我們用一端C程序,深刻的取感受一下這個過程的內(nèi)存地址的變化
(2)程序地址空間?
上面的圖在學(xué)習(xí)C/C++時(shí)我們稱之為程序的地址空間分布圖,說白了就是一段程序在其運(yùn)行過程中的數(shù)據(jù)的內(nèi)存分布情況,但是接下來的敘述可能和你所認(rèn)為的有所出入
A:同一個地址有兩個數(shù)據(jù)?
如下C程序,有個全局變量val,初始值為0,使用fork創(chuàng)建一個子進(jìn)程,然后讓父進(jìn)程先睡眠三秒,先讓子進(jìn)程運(yùn)行,子進(jìn)程運(yùn)行時(shí)把val改為100,三秒后父子進(jìn)程同時(shí)運(yùn)行
#include <stdio.h> #include <stdlib.h> #include <unistd.h>int val=0; int main() {pid_t id=fork();if(id==0){val=100;while(1){printf("現(xiàn)在是子進(jìn)程:val=%d,其地址為%p\n",val,&val);sleep(1);}}else if(id>0){sleep(3);while(1){printf("############################################\n")printf("現(xiàn)在是父進(jìn)程:val=%d,其地址為%p\n",val,&val);sleep(1);}}else{exit(-1);}sleep(1);return 0;}效果如下,問題就在于同一個變量怎么會同時(shí)具有兩個不同的值?
B:物理地址和虛擬地址
我們能夠很明確一點(diǎn):同一片空間不可同時(shí)具有兩個值,就像現(xiàn)實(shí)生活中,你不可能同時(shí)再學(xué)校又同時(shí)在家。那么只有一種解釋:你所看到并不是真實(shí)的,也就是說你所看到的地址并不是真實(shí)的物理地址,而是表象上的相同,他們對應(yīng)的真實(shí)的物理地址肯定是不相同的,我們稱這種地址為虛擬地址
實(shí)際上:我們使用C/C++看到的地址,全部都是虛擬地址,真實(shí)的地址用戶看不到,而操作系統(tǒng)就負(fù)責(zé)將虛擬地址轉(zhuǎn)換為物理地址
可以這樣描述:父進(jìn)程啟動(它的內(nèi)存空間也是虛擬的不是真實(shí)的),然后遇見fork就創(chuàng)建了一個子進(jìn)程,fork就把父進(jìn)程的內(nèi)存狀態(tài)拷貝了給自己一份,如果val的值沒有改變,那么操作系統(tǒng)本著不浪費(fèi)一點(diǎn)空間原則,直接使用val這個虛擬地址所對應(yīng)的物理地址就可以了。但是好景不長,子進(jìn)程把val修改掉了,所以操作系統(tǒng)就把子進(jìn)程val的虛擬地址所對應(yīng)的物理地址進(jìn)行了修改。從表面看起來好似地址沒有變,但是真實(shí)情況早都變化了。
C:進(jìn)程地址空間及作用
1:早期直接訪問物理內(nèi)存的缺陷所在
我們知道進(jìn)程=進(jìn)程控制塊PCB+代碼+數(shù)據(jù),早期計(jì)算機(jī),也就是沒有進(jìn)程地址空間的時(shí)候進(jìn)程一旦啟動,這些東西就會被全部裝入內(nèi)存,那么此時(shí)進(jìn)程訪問的就是真實(shí)的物理內(nèi)存,如果畫一張圖,應(yīng)該就是下面這樣
但是這樣防止有很大壞處:比如說野指針,放的這么近,一旦指針訪問的別的進(jìn)程的數(shù)據(jù),這就出了大亂子了。還有進(jìn)程在運(yùn)行過程中,是會產(chǎn)生數(shù)據(jù)的,產(chǎn)生的數(shù)據(jù)一旦不能放在本進(jìn)程后面,就要另外找內(nèi)存去存放,這樣就會導(dǎo)致不連續(xù)的現(xiàn)象,也增加了異常訪問的情況
2:進(jìn)程地址空間的發(fā)明
所以計(jì)算機(jī)設(shè)計(jì)者意識到了這種模式缺陷,想到了一種方法:增加一個中間層,利用中間層映射物理內(nèi)存。程序訪問內(nèi)存時(shí)不直接訪問物理內(nèi)存,先訪問中間層,如果中間層訪問沒有問題,那么操作系統(tǒng)就會將中間層映射到物理層,完成正常執(zhí)行
一個進(jìn)程創(chuàng)建之后,操作系統(tǒng)會為這個進(jìn)程分配一個專屬于它的大小為4GB的虛擬進(jìn)程地址空間(4GB是因?yàn)?2位系統(tǒng)中,指針是4個字節(jié)),與它相對的是一片真實(shí)的物理地址空間,操作系統(tǒng)在映射虛擬內(nèi)存時(shí)只會映射到那一片物理空間,而且需要特別注意這個虛擬空間并不是真的有4GB,它只是虛擬的 。由于每一個進(jìn)程都有自己的虛擬的進(jìn)程地址空間,所以它只能訪問自己的進(jìn)程的數(shù)據(jù),這樣做實(shí)現(xiàn)了隔離,也就是進(jìn)程之間的相互獨(dú)立。并且把虛擬地址空間劃分為這樣,那樣的區(qū),這樣的話也能解決數(shù)據(jù)的連續(xù)存放
3:頁表
不管怎樣,程序運(yùn)行一定要在物理內(nèi)存上,所以如何映射成為了關(guān)鍵,,這種映射稱為頁表
映射時(shí),讓虛擬地址和物理地址一一對應(yīng),進(jìn)程在虛擬地址上的地址就對應(yīng)了物理內(nèi)存上的某個地址。這樣的話就不存在非法訪問了,因?yàn)槊總€進(jìn)程都有自己獨(dú)立的進(jìn)程地址空間,如果非法訪問,頁表上根本就找不到這樣的地址,所以操作系統(tǒng)拒絕映射。“代碼,字符串是只讀的,數(shù)據(jù)是可讀可寫的”就是基于這個原因,雖然咋頁表上能找到,但是對代碼,字符串做了權(quán)限限制,一旦監(jiān)測到這些數(shù)據(jù)類型要做一些寫入操作,那么同樣操作系統(tǒng)拒絕映射
D:進(jìn)程地址空間如何工作
還是那個觀點(diǎn)“先描述,再組織”,也就是說我們看見的那個經(jīng)典的棧,堆分布圖,其實(shí)本質(zhì)也是一個結(jié)構(gòu)體,這個結(jié)構(gòu)體隸屬于task_struct,叫做task mm_struct(文件在mm_types.h)
這張圖可以說明task_stuct和task mm_struct的關(guān)系
申請空間的本質(zhì)就是想內(nèi)存索要空間,得到物理地址,然后在特定區(qū)域申請沒有使用的虛擬地址,建立映射關(guān)系,再返回虛擬地址
總結(jié)
以上是生活随笔為你收集整理的Linux系统编程14:进程入门之Linux进程中非常重要的概念之进程地址空间-原来我们看到的地址全部是虚拟的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java中 equals() 和 ==
- 下一篇: Linux下记录所有用户操作的脚本