亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Linux進(jìn)程地址空間詳解

 更新時(shí)間:2025年02月20日 08:58:52   作者:s_little_monster_  
文章主要介紹了進(jìn)程地址空間和頁表的工作原理,以及它們?cè)诓僮飨到y(tǒng)中的作用,通過父子進(jìn)程的示例,解釋了進(jìn)程地址空間的共享和獨(dú)立性,以及頁表如何將虛擬地址轉(zhuǎn)換為物理地址,文章還討論了頁表的其他功能,如權(quán)限控制和頁面置換算法,并簡要介紹了缺頁中斷的處理過程

一、程序地址空間

1、各內(nèi)存區(qū)域的相對(duì)位置

我記得在之前的博文中好像用編譯器粗略定位過各個(gè)類型地址空間的位置,這里我們?cè)衮?yàn)證一下它們的相對(duì)關(guān)系,這里是32位的機(jī)器,存儲(chǔ)空間為2^32byte=4GB

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int g_val_1;
int g_val_2 = 100;

int main(int argc, char *argv[], char *env[])
{
    printf("code addr: %p\n", main);//代碼段
    const char *str = "hello world";
    printf("read only string addr: %p\n", str);//只讀數(shù)據(jù)段
    printf("init global value addr: %p\n", &g_val_2);//數(shù)據(jù)段(已初始化)
    printf("uninit global value addr: %p\n", &g_val_1);//BBS段(未初始化)
    char *mem = (char*)malloc(100);
    char *mem1 = (char*)malloc(100);
    char *mem2 = (char*)malloc(100);
    //malloc在堆上開辟空間
    printf("heap addr: %p\n", mem);
    printf("heap addr: %p\n", mem1);
    printf("heap addr: %p\n", mem2);
    //臨時(shí)變量在棧上開辟空間
    printf("stack addr: %p\n", &str);
    printf("stack addr: %p\n", &mem);
    
    static int a = 0;
    int b;
    int c;
    //靜態(tài)成員變量在數(shù)據(jù)段
    printf("a = stack addr: %p\n", &a);
    //臨時(shí)變量在棧區(qū)
    printf("stack addr: %p\n", &b);
    printf("stack addr: %p\n", &c);
    //其實(shí)在棧區(qū)的最大地址處和內(nèi)核空間的最小地址處之間還有一部分
    //用來存放我們的命令行和環(huán)境變量,且環(huán)境變量在大地址處
    int i = 0;
    for(; argv[i]; i++)
        printf("argv[%d]: %p\n", i, argv[i]);

    for(i=0; env[i]; i++)
        printf("env[%d]: %p\n", i, env[i]);

    return 0;
}

從圖中我們可以看到,棧區(qū)和堆區(qū)是相對(duì)而生的,其中間有很大一部分的空間,在它們的中間還有一段內(nèi)存映射段,這里我們后面結(jié)合后面的內(nèi)容來解釋

2、引入父子進(jìn)程問題

  • test
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
 
int g_val = 0;
 
int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 0;
    }
    else if(id == 0)
    {
	 	//child
        printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }
    else
    { 
		//parent
        printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }
    sleep(1);
    return 0;
}
  • fork_test
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int g_val = 0;
 
int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 0;
    }
    else if(id == 0)
    {
	 	//child,子進(jìn)程先修改,完成之后,父進(jìn)程再讀取
        g_val=100;
        printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }
    else
    { 
		//parent
        sleep(3);
        printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }
    sleep(1);
    return 0;
}

通過test進(jìn)程現(xiàn)象我們發(fā)現(xiàn),在這里我們的父子進(jìn)程在訪問我們的g_val值時(shí)訪問同一個(gè)位置同一個(gè)值,但是在我們子進(jìn)程對(duì)gal值進(jìn)行修改然后再讓父進(jìn)程進(jìn)行讀取的時(shí)候,我們發(fā)現(xiàn)父進(jìn)程讀取的值依舊是原來的g_val值,而子進(jìn)程讀取的值已經(jīng)是修改值,但地址還是相同的,地址相同怎么會(huì)讀取到不同的值呢?首先我們可以肯定的是,這個(gè)地址一定不是物理地址!同一塊物理地址訪問到的值一定是一樣的!

結(jié)合我們?cè)谇懊嬷v到的,如果子進(jìn)程修改了數(shù)據(jù),我們會(huì)在另一塊位置重新開辟一塊空間用來存放子進(jìn)程與父進(jìn)程不同的這部分?jǐn)?shù)據(jù),這是為什么呢?這個(gè)實(shí)現(xiàn)的原理是什么呢?我們也可以肯定的是,這個(gè)變量,也就是這個(gè)數(shù)據(jù),內(nèi)容是不一樣的,這是我們觀察到的,父子進(jìn)程輸出的一定不是同一個(gè)變量!下面我們來討論一下

二、進(jìn)程地址空間

1、頁表

我們?cè)谥爸v到的程序地址空間的說法其實(shí)是錯(cuò)誤的,正確來說應(yīng)該叫進(jìn)程地址空間,上面我們所說的地址叫做虛擬地址,也叫做線性地址,既然叫做虛擬地址,那當(dāng)然就不是真實(shí)的物理地址了,虛擬地址和物理地址存在映射關(guān)系,而承載他們映射關(guān)系的,就是頁表

我整理了一下地址空間、頁表和物理內(nèi)存的關(guān)系如下圖

在這個(gè)圖中,我們把父子進(jìn)程以及頁表分開來畫,因?yàn)樗鼈兪莾蓚€(gè)獨(dú)立的進(jìn)程,但是地址空間的這部分內(nèi)容是共享的,也就是虛擬地址是相同的,我們不是復(fù)制出了兩個(gè)地址空間,這里需要注意

內(nèi)核空間中有父子進(jìn)程的task_struct,它們里面有指向各自頁表的指針

其中上方是父進(jìn)程的地址空間,下方是子進(jìn)程的地址空間,子進(jìn)程直接復(fù)制父進(jìn)程的地址空間,包括虛擬地址、頁表等等變量都是相同的,類似于一個(gè)淺拷貝的過程,在子進(jìn)程修改g_val變量時(shí),子進(jìn)程在物理內(nèi)存上新開辟一塊空間,用來存放與父進(jìn)程數(shù)據(jù)不同的量,這個(gè)過程類似于memcpy的過程,創(chuàng)建并復(fù)制內(nèi)容,然后再將g_val改成100,然后頁表的物理地址指向該地址,這個(gè)過程是寫時(shí)拷貝,我們前面提到過

其中MMU起到的作用是負(fù)責(zé)將進(jìn)程虛擬地址轉(zhuǎn)換為物理地址,當(dāng) CPU 需要訪問內(nèi)存時(shí),會(huì)將虛擬地址發(fā)送給 MMU,MMU 根據(jù)頁表等數(shù)據(jù)結(jié)構(gòu)進(jìn)行地址轉(zhuǎn)換,是與頁表息息相關(guān)的一個(gè)內(nèi)存管理單元

2、深入理解進(jìn)程地址空間

那看到這里有人問了,地址空間究竟是什么啊,我們?yōu)槭裁匆M(jìn)行這樣的劃分?

我們一直拿32位的計(jì)算機(jī)舉例,因?yàn)樗粩?shù)少,比64位的計(jì)算機(jī)簡單一些,這里的32位計(jì)算機(jī)又指的是什么?

在 32 位計(jì)算機(jī)里,地址總線寬度是 32 位,也就是有 32 條線路,每條線路能通過高低電平的轉(zhuǎn)換來實(shí)現(xiàn)0和1的變化,所以這 32 條線路能表示的不同地址組合數(shù)量為 2^32個(gè),因?yàn)槊總€(gè)內(nèi)存地址對(duì)應(yīng)一個(gè)字節(jié),所以 32 位計(jì)算機(jī)理論上能直接訪問的內(nèi)存空間大小就是 2 ^32字節(jié),而2 ^32字節(jié)換算后等于 4GB,這就意味著 32 位計(jì)算機(jī)的 CPU 可以通過地址總線直接訪問從 0 到 2 ^32 - 1地址范圍內(nèi)的 4GB 物理內(nèi)存

我們的進(jìn)程地址空間就在這樣一個(gè)概念中展開,而地址空間的劃分實(shí)際上是對(duì)該空間的一種組織,在正常運(yùn)行的情況下互不影響

我們計(jì)算機(jī)中最小的存儲(chǔ)單元就是字節(jié)byte,每個(gè)字節(jié)都會(huì)有一個(gè)地址,這個(gè)地址是可以直接被操作系統(tǒng)使用的,這是可以使用地址找到的最小單位,類似于bit這樣的存儲(chǔ)單元是沒有地址的概念的

所以所謂的進(jìn)程地址空間,本質(zhì)上是一個(gè)描述進(jìn)程可視范圍的大小,地址空間內(nèi)一定要存在各種區(qū)域的劃分,只要對(duì)虛擬地址(線性地址)進(jìn)行區(qū)域劃分即可

這里要注意的是,棧的start是高地址處,其他用戶空間都是start為低地址處

3、進(jìn)程地址空間這樣組織的優(yōu)勢(shì)

(一)讓進(jìn)程以一個(gè)統(tǒng)一的視角看待內(nèi)存

我們以頁表這樣的形式用來過渡,保證了我們所訪問的虛擬地址(線性地址)是線性的,我們的進(jìn)程不管要做什么,我們只要知道它做的事情的性質(zhì),我們就知道它大概存儲(chǔ)在哪個(gè)線性地址區(qū)域,并且因?yàn)橛辛隧摫淼拇嬖?,我?strong>不必再關(guān)心物理內(nèi)存的實(shí)際布局以及其他進(jìn)程的存在,我們本進(jìn)程只做好本進(jìn)程自己的事情就好了,其他的我并不關(guān)心

不同進(jìn)程的虛擬地址空間是相互隔離的,一個(gè)進(jìn)程無法直接訪問另一個(gè)進(jìn)程的虛擬地址空間,這就保證了進(jìn)程之間的獨(dú)立性和安全性,一個(gè)進(jìn)程的錯(cuò)誤或惡意操作不會(huì)影響到其他進(jìn)程的正常運(yùn)行

(二)保護(hù)物理內(nèi)存

增加進(jìn)程虛擬地址空間可以讓我們?cè)L問內(nèi)存的時(shí)候,增加一個(gè)轉(zhuǎn)換的過程,在這個(gè)轉(zhuǎn)換的過程中,可以對(duì)我們的尋址請(qǐng)求進(jìn)行審查,所以如果訪問異常,就可以直接攔截,請(qǐng)求不會(huì)到達(dá)物理內(nèi)存,從而很好的保護(hù)了物理內(nèi)存不被攻擊

(三)進(jìn)程管理模塊和內(nèi)存管理模塊低耦合

我們通過頁表這個(gè)結(jié)構(gòu),很好地將進(jìn)程管理和內(nèi)存管理解耦合,互不影響,我們進(jìn)程所看到的只有虛擬地址,并不在乎物理地址如何如何,而我們的內(nèi)存也不需要在乎有多少進(jìn)程,進(jìn)程的作用是什么,而是只在需要的時(shí)候開辟和回收空間就可以了,這樣我們?cè)谶M(jìn)程出現(xiàn)問題的時(shí)候不會(huì)影響到內(nèi)存管理,很好地阻斷了可能出現(xiàn)的一系列崩盤的問題

4、頁表的其他內(nèi)容

頁表除了我們上面提到的作用以外,還存在類似讀寫權(quán)限這樣的功能,我們?cè)谥皩W(xué)習(xí)的時(shí)候,我們知道在只讀數(shù)據(jù)段中的數(shù)據(jù)是只可讀不可寫的,那么它相對(duì)應(yīng)的映射到物理內(nèi)存上,物理內(nèi)存上又沒有限制條件,它是怎么實(shí)現(xiàn)的只讀呢?其實(shí)是頁表的某一項(xiàng)屬性控制了該變量的讀寫,分為不可讀寫、可讀不可寫、可寫不可讀、可讀可寫,在映射的同時(shí)將該性質(zhì)傳遞回去,就只可讀了

其他的還有對(duì)應(yīng)代碼和數(shù)據(jù)是否已經(jīng)加載到內(nèi)存等等一系列的其他屬性

頁表的本質(zhì)屬于進(jìn)程的硬件上下文,在進(jìn)程切換的時(shí)候會(huì)帶走這些信息,被存儲(chǔ)在CPU寄存器中,task_struct中有指向頁表地址的指針

缺頁中斷

在虛擬內(nèi)存系統(tǒng)里,程序運(yùn)行時(shí)使用的是虛擬地址,虛擬地址空間會(huì)被劃分為多個(gè)頁面。物理內(nèi)存則被劃分為與虛擬頁大小相同的頁框。當(dāng)程序訪問一個(gè)虛擬地址,而該地址對(duì)應(yīng)的頁面不在物理內(nèi)存中,也就是沒有被加載到物理內(nèi)存的頁框里時(shí),就會(huì)觸發(fā)缺頁中斷,這是一種特殊的中斷,它會(huì)暫停當(dāng)前程序的執(zhí)行,轉(zhuǎn)而去處理頁面加載的問題

進(jìn)程剛開始運(yùn)行時(shí),它的代碼和數(shù)據(jù)所在的頁面可能都還沒有被加載到物理內(nèi)存中,當(dāng)進(jìn)程第一次訪問某個(gè)頁面時(shí),就會(huì)因?yàn)樵擁撁娌辉趦?nèi)存而產(chǎn)生缺頁中斷;或者由于物理內(nèi)存資源有限,操作系統(tǒng)會(huì)使用頁面置換算法將一些暫時(shí)不用的頁面從物理內(nèi)存換出到磁盤的交換空間,當(dāng)進(jìn)程后續(xù)又需要訪問這些被換出的頁面時(shí),就會(huì)觸發(fā)缺頁中斷

當(dāng)缺頁中斷發(fā)生時(shí),CPU 會(huì)保存當(dāng)前進(jìn)程的現(xiàn)場信息,包括程序計(jì)數(shù)器、寄存器等內(nèi)容,以便在中斷處理完成后能恢復(fù)進(jìn)程的執(zhí)行,操作系統(tǒng)根據(jù)引發(fā)缺頁中斷的虛擬地址,查找該頁面在磁盤上的位置,這通常需要借助頁表等數(shù)據(jù)結(jié)構(gòu)來確定頁面的磁盤地址,如果物理內(nèi)存中有空閑的頁框,操作系統(tǒng)會(huì)直接分配一個(gè)頁框;若沒有空閑頁框,就需要使用頁面置換算法選擇一個(gè)當(dāng)前在物理內(nèi)存中的頁面換出到磁盤,為即將要加載的頁面騰出空間,然后發(fā)出磁盤 I/O 請(qǐng)求,將所需的頁面從磁盤讀取到分配好的物理頁框中,頁面加載完成后,操作系統(tǒng)會(huì)更新頁表,將該虛擬頁與新分配的物理頁框建立映射關(guān)系,并設(shè)置相應(yīng)的標(biāo)志位,表示該頁面現(xiàn)在已經(jīng)在物理內(nèi)存中,最后,操作系統(tǒng)恢復(fù)之前保存的進(jìn)程現(xiàn)場,讓進(jìn)程從產(chǎn)生缺頁中斷的指令處繼續(xù)執(zhí)行

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 詳解Linux中PostgreSQL和PostGIS的安裝和使用

    詳解Linux中PostgreSQL和PostGIS的安裝和使用

    這篇文章主要介紹了詳解Linux中PostgreSQL和PostGIS的安裝和使用,并把需要注意點(diǎn)做了分析和解釋,需要的朋友學(xué)習(xí)下。
    2018-02-02
  • 基于ubuntu16 Python3 tensorflow(TensorFlow環(huán)境搭建)

    基于ubuntu16 Python3 tensorflow(TensorFlow環(huán)境搭建)

    這篇文章主要介紹了基于ubuntu16 Python3 tensorflow(TensorFlow環(huán)境搭建),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-01-01
  • linux系統(tǒng)下使用tcpdump進(jìn)行抓包方法

    linux系統(tǒng)下使用tcpdump進(jìn)行抓包方法

    在本篇文章中小編給大家分享了關(guān)于linux系統(tǒng)下使用tcpdump進(jìn)行抓包的方法和相關(guān)知識(shí)點(diǎn),需要的朋友們學(xué)習(xí)下。
    2019-04-04
  • Linux中將txt導(dǎo)入到mysql的方法教程

    Linux中將txt導(dǎo)入到mysql的方法教程

    這篇文章主要給大家介紹了關(guān)于在Linux中將txt導(dǎo)入到mysql的方法教程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • Linux?LVM邏輯卷相關(guān)管理方式

    Linux?LVM邏輯卷相關(guān)管理方式

    這篇文章主要介紹了Linux?LVM邏輯卷相關(guān)管理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • Linux lsof命令使用詳解

    Linux lsof命令使用詳解

    這篇文章主要介紹了Linux lsof命令使用詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • Ubuntu18.04.2下安裝 RTX2080 Nvidia顯卡驅(qū)動(dòng)的方法

    Ubuntu18.04.2下安裝 RTX2080 Nvidia顯卡驅(qū)動(dòng)的方法

    這篇文章主要介紹了Ubuntu18.04.2下安裝 RTX2080 Nvidia顯卡驅(qū)動(dòng)的方法,本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下
    2019-07-07
  • ubuntu16.04下vim安裝失敗的原因分析及解決方案

    ubuntu16.04下vim安裝失敗的原因分析及解決方案

    重裝了ubuntu系統(tǒng),安裝vim出現(xiàn)了很多奇葩問題,今天百度查閱資料才順利解決。今天小編特此把解決思路分享到腳本之家平臺(tái),需要的朋友參考下吧
    2016-11-11
  • Linux低電量自動(dòng)關(guān)機(jī)的實(shí)現(xiàn)方法

    Linux低電量自動(dòng)關(guān)機(jī)的實(shí)現(xiàn)方法

    這篇文章主要給大家介紹了關(guān)于Linux低電量自動(dòng)關(guān)機(jī)的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用linux具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • Linux YUM倉庫及NFS共享服務(wù)方式

    Linux YUM倉庫及NFS共享服務(wù)方式

    YUM(Yellowdog Updater Modified)是基于RPM包的軟件包管理器,專門用于解決軟件包的依賴關(guān)系,支持通過FTP、HTTP服務(wù)或本地目錄從集中的YUM軟件倉庫獲取軟件包,YUM能夠自動(dòng)處理包依賴問題,簡化了軟件安裝和更新過程
    2024-09-09

最新評(píng)論