欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux進程地址空間詳解

 更新時間:2025年02月20日 08:58:52   作者:s_little_monster_  
文章主要介紹了進程地址空間和頁表的工作原理,以及它們在操作系統(tǒng)中的作用,通過父子進程的示例,解釋了進程地址空間的共享和獨立性,以及頁表如何將虛擬地址轉換為物理地址,文章還討論了頁表的其他功能,如權限控制和頁面置換算法,并簡要介紹了缺頁中斷的處理過程

一、程序地址空間

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

我記得在之前的博文中好像用編譯器粗略定位過各個類型地址空間的位置,這里我們再驗證一下它們的相對關系,這里是32位的機器,存儲空間為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);
    //臨時變量在棧上開辟空間
    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);
    //臨時變量在棧區(qū)
    printf("stack addr: %p\n", &b);
    printf("stack addr: %p\n", &c);
    //其實在棧區(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ū)是相對而生的,其中間有很大一部分的空間,在它們的中間還有一段內(nèi)存映射段,這里我們后面結合后面的內(nèi)容來解釋

2、引入父子進程問題

  • 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,子進程先修改,完成之后,父進程再讀取
        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進程現(xiàn)象我們發(fā)現(xiàn),在這里我們的父子進程在訪問我們的g_val值時訪問同一個位置同一個值,但是在我們子進程對gal值進行修改然后再讓父進程進行讀取的時候,我們發(fā)現(xiàn)父進程讀取的值依舊是原來的g_val值,而子進程讀取的值已經(jīng)是修改值,但地址還是相同的,地址相同怎么會讀取到不同的值呢?首先我們可以肯定的是,這個地址一定不是物理地址!同一塊物理地址訪問到的值一定是一樣的!

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

二、進程地址空間

1、頁表

我們在之前講到的程序地址空間的說法其實是錯誤的,正確來說應該叫進程地址空間,上面我們所說的地址叫做虛擬地址,也叫做線性地址,既然叫做虛擬地址,那當然就不是真實的物理地址了,虛擬地址和物理地址存在映射關系,而承載他們映射關系的,就是頁表

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

在這個圖中,我們把父子進程以及頁表分開來畫,因為它們是兩個獨立的進程,但是地址空間的這部分內(nèi)容是共享的,也就是虛擬地址是相同的,我們不是復制出了兩個地址空間,這里需要注意

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

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

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

2、深入理解進程地址空間

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

我們一直拿32位的計算機舉例,因為它位數(shù)少,比64位的計算機簡單一些,這里的32位計算機又指的是什么?

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

我們的進程地址空間就在這樣一個概念中展開,而地址空間的劃分實際上是對該空間的一種組織,在正常運行的情況下互不影響

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

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

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

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

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

我們以頁表這樣的形式用來過渡,保證了我們所訪問的虛擬地址(線性地址)是線性的,我們的進程不管要做什么,我們只要知道它做的事情的性質(zhì),我們就知道它大概存儲在哪個線性地址區(qū)域,并且因為有了頁表的存在,我們不必再關心物理內(nèi)存的實際布局以及其他進程的存在,我們本進程只做好本進程自己的事情就好了,其他的我并不關心

不同進程的虛擬地址空間是相互隔離的,一個進程無法直接訪問另一個進程的虛擬地址空間,這就保證了進程之間的獨立性和安全性,一個進程的錯誤或惡意操作不會影響到其他進程的正常運行

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

增加進程虛擬地址空間可以讓我們訪問內(nèi)存的時候,增加一個轉換的過程,在這個轉換的過程中,可以對我們的尋址請求進行審查,所以如果訪問異常,就可以直接攔截,請求不會到達物理內(nèi)存,從而很好的保護了物理內(nèi)存不被攻擊

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

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

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

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

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

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

缺頁中斷

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

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

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

總結

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

相關文章

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

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

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

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

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

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

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

    Linux中將txt導入到mysql的方法教程

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

    Linux?LVM邏輯卷相關管理方式

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

    Linux lsof命令使用詳解

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

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

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

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

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

    Linux低電量自動關機的實現(xiàn)方法

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

    Linux YUM倉庫及NFS共享服務方式

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

最新評論