個人主頁 : zxctscl
如有轉(zhuǎn)載請先通知
1. 虛擬地址
1.1 虛擬地址引入
先先來一個測試代碼:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4 #include<stdlib.h>
5
6 int g_val=100;
7
8 int main()
9 {
10 printf("father is running,pid:%d,ppid:%d\n",getpid(),getppid());
11
12
13 pid_t id=fork();
14 if(id==0)
15 {
16 int cnt=0;
17 while(1)
18 {
19 printf("I am child process,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
20 sleep(1);
21 cnt++;
22 if(cnt==5)
23 {
24 g_val=300;
25 printf("I am child process,change %d->%d\n",100,300);
26 }
27 }
28 }
29 else{
30 while(1)
31 {
32 printf("I am father process,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
33 sleep(1);
34 }
35
36 }
37 }
38
編譯運行:
子進程把數(shù)據(jù)改了,父進程的數(shù)據(jù)沒有改變,但是父子地址是一樣的。
這個地址絕對不是物理地址,理論上修改了數(shù)據(jù)為300之后不可能在輸出有100,訪問一個地址怎么可能又是100也是300。這個地址在系統(tǒng)層面上稱之為虛擬地址。
1.2 虛擬地址理解
每一個進程除了要把代碼和數(shù)據(jù)加載到內(nèi)存之外,對于當(dāng)前的操作系統(tǒng)來講,系統(tǒng)當(dāng)中會為每一個進程創(chuàng)建一個地址空間。
地址空間在操作系統(tǒng)里面。在32位和64位下的地址空間大小是不一樣的,為了方便這里使用32位來表述。32位從低到高一個有4GB的地址空間范圍,實際上這個地址空間當(dāng)中打印出來的地址,是該空間內(nèi)對應(yīng)的地址。進程是可以指向這個地址空間的。
其實PCB和地址空間都是在物理內(nèi)存里面的,只不過要訪問初始化全局數(shù)據(jù)的時候,不在地址空間上保存,地址空間只會提供線性連續(xù)地址,讓用戶之后通過虛擬地址的地址空間,將虛擬地址轉(zhuǎn)化到為了物理內(nèi)存中。
此時計算機的體系結(jié)構(gòu)中還存在一個頁表,頁表它的主要功能是負責(zé)將地址空間中的虛擬地址和物理地址之間建立映射關(guān)系。未來在用進程進行訪問的時候,操作系統(tǒng)會自動用虛擬地址查頁表轉(zhuǎn)換為物理地址,然后讓用戶訪問到數(shù)據(jù)。
父進程的代碼可以通過頁表地址映射轉(zhuǎn)換到為了內(nèi)存中代碼,父進程通過連續(xù)的地址空間就可以訪問到它的代碼和數(shù)據(jù)。
假設(shè)在物理內(nèi)存上存放一個全局變量g_val,默認內(nèi)容是100,g_val在頁表在地址空間中都要被找到,所以在地址空間的初始化數(shù)據(jù)中就有它的地址虛擬地址,頁表的左側(cè)也有它的虛擬地址,在頁表右側(cè)就有它對應(yīng)的物理地址。
當(dāng)創(chuàng)建了一個子進程,本質(zhì)上是系統(tǒng)多了一個進程,它也有自己的task_truct,還有自己的虛擬地址空間,還有它所對應(yīng)的頁表。
每個進程都要有自己的虛擬的地址空間,也要有自己對應(yīng)的頁表。
每個進程都要有自己獨立的地址空間,那么操作系統(tǒng)就得管理很多個進程的地址空間,而地址空間本質(zhì)上就是內(nèi)核中的一個數(shù)據(jù)結(jié)構(gòu)對象。
子進程會把父進程的很多數(shù)據(jù)結(jié)構(gòu)全拷貝一份,基本上子進程的PCB、地址空間和頁表基本上和父進程的一致。
子進程的地址空間也會有一個虛擬地址,子進程對應(yīng)的頁表也來自父進程,所以頁表保存的地址,從而子進程也會指向那個g_val。
所以子進程和父進程看到的虛擬地址是一樣的,并且它們的頁表也一樣,指向的物理內(nèi)存也一樣,所以它們打印出來的地址也就是相同的了。
如果子進程進行寫入,也是通過頁表向物理內(nèi)存處進行寫入,寫入的時候直接找到g_val把100改為300。可子進程一旦對數(shù)據(jù)做修改了,父進程就會看到。如果子進程直接修改了數(shù)據(jù),就會導(dǎo)致程序運行本身問題。
而進程本身在運行的時候具有獨立性,所以子進程對數(shù)據(jù)進行修改,就不能影響到父進程,所以當(dāng)子進程嘗試對數(shù)據(jù)進行修改時,操作系統(tǒng)發(fā)現(xiàn)父進程也有,就在在子進程修改之前,在物理內(nèi)存中出現(xiàn)開辟一個空間,開辟完成之后。然后把修改之前的數(shù)據(jù)拷貝到新空間中,再把新的物理地址和之前的物理地址相比較,把新的物理地址放在子進程的頁表中,重新構(gòu)建映射,頁表的右側(cè)就指向新的物理地址空間,這個工作結(jié)束,才會就行讓子進程執(zhí)行寫入操作,把100改為300。
重新開辟物理內(nèi)存這些都是操作系統(tǒng)自己做,上面這個過程叫做寫時拷貝。
修改的只是子進程的物理地址和頁表,而地址空間里面的依然是虛擬地址。子進程和父進程的虛擬地址是一樣的,只是映射到物理內(nèi)存到不同區(qū)域,所以對應(yīng)看到的地址是一樣的,但內(nèi)容卻不一樣。
1.3 虛擬地址細節(jié)問題
如果父子進程不寫,未來一個全局變量,默認是被父子共享的,代碼(只讀)是共享的。
為什么會存在寫時拷貝?
因為進程具有獨立性,所以父子進程有自己的地址空間和頁表。
但是代碼是共享的,那么怎么不在創(chuàng)建子進程的時候,全部給子進程拷貝一份?
主要是在父進程中的數(shù)據(jù)子進程不一定都會修改,而這些占據(jù)的空間又很大,子進程程序拷貝一份就是在浪費空間,所以采用寫時拷貝,就是為了按需申請。必須寫時才能拷貝是為了保證進程的獨立性。
按需申請本質(zhì)是通過調(diào)整拷貝時間順序,達到有效節(jié)省空間的目的。
2. 地址空間
2.1 理解地址空間
地址空間本質(zhì)是內(nèi)核的一個struct結(jié)構(gòu)體,結(jié)構(gòu)體里面有各種各樣的區(qū)域劃分,內(nèi)部有很多的屬性都是表示start,end的范圍。
來看看源碼里面描寫這個結(jié)構(gòu)體:
并不是限定了某一個范圍,而是這個范圍之間它所對應(yīng)地址空間都可以使用。這個范圍可以根據(jù)頁表映射到物理內(nèi)存。
操作系統(tǒng)給每一個進程都劃分一塊進程地址空間。
為什么要有地址空間?
一個程序的代碼和數(shù)據(jù)放在物理內(nèi)存中,如果沒有虛擬地址空間,要直接找到程序的代碼和數(shù)據(jù),就必須讓進程的PCB把對應(yīng)的代碼和數(shù)據(jù)都記錄下來。如果當(dāng)前還有其他程序,都在物理內(nèi)存中,每一個程序都在物理內(nèi)存中加載的話,也就要求每一個進程所對應(yīng)的代碼和數(shù)據(jù)在物理內(nèi)存的哪一個位置都得記錄下來。這個記錄對應(yīng)進程而言負擔(dān)是比較大的,也就是進程直接使用物理地址。
就有可能出現(xiàn)訪問越界,或者訪問到其他進程的代碼和數(shù)據(jù)。所以用進程記錄物理地址就比較混亂,不利于做統(tǒng)一管理。
實際物理內(nèi)存中的代碼區(qū),數(shù)據(jù)區(qū)、堆區(qū)、棧區(qū)、共享區(qū)、命令行參數(shù)和環(huán)境變量,對一個進程來講可能是亂序的,那么再加載其他進程也是亂序的。
進程在申請內(nèi)存時,在地址空間上能申請就可以,在頁表對應(yīng)的左側(cè)就可以了,右側(cè)可以先不填,當(dāng)用戶真正用到的時候在申請。
地址空間和也表存在的好處就是:一、將無序變有序,讓進程以統(tǒng)一的視角來看待物理內(nèi)存以及自己運行的各個區(qū)域。
二、進程管理模塊和內(nèi)存管理模塊進行解耦
地址空間并不是百分百使用的,一般只使用一部分。比如在堆區(qū),申請了五十個字節(jié),可是遍歷的時候計數(shù)器越界了,在地址空間里面就越界了,操作系統(tǒng)就直接攔截了這個請求,所有的非法請求都不能通過地址空間到物理內(nèi)存上,也就是保護物理內(nèi)存。
攔截非法請求就是對物理內(nèi)存進行保護。
2.2 頁表和寫時拷貝
查頁表對內(nèi)存地址進行訪問是CPU,它里面包含CR3寄存器內(nèi),CPU的還有有一個叫做MMU硬件(內(nèi)存管理單元),快速把虛擬地址結(jié)合頁表轉(zhuǎn)化為物理地址。
頁表里面的一些選項來支持權(quán)限管理。就像是C語言中不能修改字符常量區(qū),是因為頁表里面沒有給修改的權(quán)限。
操作系統(tǒng)支持寫時拷貝,頁表給父進程的權(quán)限是rw。當(dāng)父進程創(chuàng)建子進程之后,子進程的頁表權(quán)限是r。當(dāng)父進程一旦創(chuàng)建子進程,父進程為了支持寫時拷貝,因為父進程走到已初始化全局區(qū)本來就是可以寫的,但創(chuàng)建子進程之后,操作系統(tǒng)會直接修改頁表中該位置的權(quán)限,都修改為r。當(dāng)父子進程中任何一個嘗試寫入時,此時系統(tǒng)就會直接識別到錯誤。
操作系統(tǒng)識別到錯誤就得判斷:1.是不是數(shù)據(jù)不在物理內(nèi)存;2.是不是數(shù)據(jù)想要寫時拷貝;3.如果都不是,才能進行異常處理。
第一種解決就是缺頁中斷,第二種就發(fā)生寫時拷貝。
上面的圖就足矣說名問題,同一個變量,地址相同,其實是虛擬地址相同,內(nèi)容不同其實是被映射到了不同的物理地址!
在最開始的時候,地址空間的頁表里面的數(shù)據(jù)從哪里來?
程序一旦加載到內(nèi)存就有地址。程序在變成二進制的時候本身就有地址。也就是說程序里面本身就有地址。
來看一下之前的代碼:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4 #include<stdlib.h>
5
6
7 int main()
8 {
9 pid_t id=fork();
10 if(id==0)
11 {
12 while(1)
13 {
14 printf("child,%d,%p\n",id,&id);
15 sleep(1);
16 }
17 }
18 else if(id>0)
19 {
20 while(1)
21 {
22 printf("father,%d,%p\n",id,&id);
23 sleep(1);
24 }
25 }
26 return 0;
27 }
28
當(dāng)fork()時候,不管是父進程還是子進程,都要return。在return時候,本質(zhì)就是對id進行寫入,而id本身是父進程定義的變量,不論是父進程還是子進程,誰先return,都得return兩次,進程在return的時候,發(fā)生寫時拷貝。所以當(dāng)父進程用id的時候,它認為id大于0;子進程在返回的時候它認為id等于0。所以虛擬地址相同而物理地址不同。
3. 進程調(diào)度
Linux中的nice值并不是能任意調(diào)度的,而是從-20到19,這40個數(shù)字之間變換。
在操作系統(tǒng)中每一個CPU都會有一個運行隊列:
來看看藍色區(qū)域的部分,這里面有queue隊列包含140項,它其實是task_struct *queue[140]
queue[140]: 一個元素就是一個進程隊列,相同優(yōu)先級的進程按照FIFO規(guī)則進行排隊調(diào)度,所以,數(shù)組下標(biāo)就是優(yōu)先級!
nr_active: 總共有多少個運行狀態(tài)的進程
從該結(jié)構(gòu)中,選擇一個最合適的進程,過程是怎么的呢?
- 從0下表開始遍歷queue[140]
- 找到第一個非空隊列,該隊列必定為優(yōu)先級最高的隊列
- 拿到選中隊列的第一個進程,開始運行,調(diào)度完成!
- 遍歷queue[140]時間復(fù)雜度是常數(shù)!但還是太低效了!
bitmap[5]:一共140個優(yōu)先級,一共140個進程隊列,為了提高查找非空隊列的效率,就可以用5*32個比特位表示隊列是否為空,這樣,便可以大大提高查找效率!
活躍進程的task_struct *queue[140]
只出不進,過期進程的task_struct *queue[140]
只進不出。
active指針和expired指針:active指針永遠指向活動隊列;expired指針永遠指向過期隊列。
可是活動隊列上的進程會越來越少,過期隊列上的進程會越來越多,因為進程時間片到期時一直都存在的。
沒關(guān)系,在合適的時候,只要能夠交換active指針和expired指針的內(nèi)容,就相當(dāng)于有具有了一批新的活動進程!文章來源:http://www.zghlxwxcb.cn/news/detail-855274.html
有問題請指出,大家一起進步?。?!文章來源地址http://www.zghlxwxcb.cn/news/detail-855274.html
到了這里,關(guān)于【Linux】地址空間&&虛擬地址的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!