进程地址空间

进程地址空间

请添加图片描述如果我们在32位平台上,我们程序能访问的地址空间范围是2的32次方,就比如32位平台下指针大小是4个字节32个比特位正好可以表示从全0到全1;
❓我们曾经C/C++学的程序地址空间,是内存吗?-----根本就不是物理内存!甚至说是程序地址空间也不准确,应该叫做进程地址空间!进程地址空间是操作系统上的概念。

验证地址空间–Linux

我们用代码来进行验证!

int un_g_val;
int g_val=100;
int main(int argc, char *argv[], char *env[])
{
    printf("code addr         : %p\n", main);---代表程序代码的地址--是函数的代表相当于代码区
    printf("init global addr  : %p\n", &g_val);----初始化全局数据区地址
    printf("uninit global addr: %p\n", &un_g_val);---未初始化全局数据区地址
    char *m1 = (char*)malloc(100);
    char *m2 = (char*)malloc(100);
    char *m3 = (char*)malloc(100);
    char *m4 = (char*)malloc(100);
    static int s = 100;
    printf("heap addr         : %p\n", m1);---打印堆区的地址
    printf("heap addr         : %p\n", m2);
    printf("heap addr         : %p\n", m3);
    printf("heap addr         : %p\n", m4);

    printf("stack addr        : %p\n", &m1);---打印栈区的地址(&m1就是局部变量)
    printf("stack addr        : %p\n", &m2);
    printf("stack addr        : %p\n", &m3);
    printf("stack addr        : %p\n", &m4);
    printf("s stack addr        : %p\n", &s);
    for(int i = 0; i < argc; i++)-----打印命令行参数的地址
    {
        printf("argv addr         : %p\n", argv[i]); //argv/&argc?
    }

    for(int i =0 ; env[i];i++)-----打印环境变量的地址
    {
        printf("env addr          : %p\n", env[i]);
    }
}

请添加图片描述
✳️我们看到地址排布是依次递增的,会发现堆和栈之间会出现非常大的地址镂空!
我们Linux下地址空间排布:正文、代码区、已初始化全局区、为初始化全局区、堆区、栈区、命令行参数和环境变量
堆区和栈区出现的非常大的镂空这部分区域我们后面会讲一般所加载的动静态库,我们经常要做的进程间通信等等后面会说。

如何理解static变量

如果一个变量在函数内定义,它变成static,它的作用域不变,在该函数内有效,但他的生命周期会随着程序一直存在,那为什么呢?

✳️我们做了一个实验,在函数内将变量定义成是static,发现虽然变量在函数内定义,但是他的地址不被编译在栈上了,而是放在全局变量的地址区,相当于写在函数内的全局变量,这就是为什么生命周期随着程序一直存在,因为他本来就是全局变量!

✳️函数内定义的变量用static修饰,本质是编译器会把该变量编译进全局数据区

验证堆和栈增长方向的问题

char *m1 = (char*)malloc(100);
    char *m2 = (char*)malloc(100);
    char *m3 = (char*)malloc(100);
    char *m4 = (char*)malloc(100);
    static int s = 100;
    printf("heap addr         : %p\n", m1);---打印堆区的地址
    printf("heap addr         : %p\n", m2);
    printf("heap addr         : %p\n", m3);
    printf("heap addr         : %p\n", m4);

    printf("stack addr        : %p\n", &m1);---打印栈区的地址(&m1就是局部变量)
    printf("stack addr        : %p\n", &m2);
    printf("stack addr        : %p\n", &m3);
    printf("stack addr        : %p\n", &m4);
    printf("s stack addr        : %p\n", &s);

✳️堆区是向上增长;堆区向地址增大方向增长。
栈区向地址减少方向增长。
堆,栈是相对而生的。
我们一般在C函数定义的变量,通常在栈上保存,那么先定义的一定是地址比较高的!
请添加图片描述

感知地址空间的存在–引出虚拟地址

引出虚拟地址

✳️父子进程被创建出来哪个先被调度是不清楚的,所以谁先运行是不确定的。
✳️当父子进程没有人修改全局数据的时候,父子是共享该数据的!

🌟使用一下代码做实验

int g_val=100;

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        int flag = 0;
        while(1)
        {
            printf("我是子进程:%d, ppid: %d, g_val: %d, &g_val: %p\n\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
            flag++;
            if(flag == 5)
            {
                g_val=200;
                printf("我是子进程,全局数据我已经改了,用户你注意查看!\n");
            }
        }
    }
    else 
    {
        //parent
        while(1)
        {
            printf("我是父进程:%d, ppid: %d, g_val: %d, &g_val: %p\n\n", getpid(), getppid(), g_val, &g_val);
            sleep(2);
        }
    }
}

❓若尝试写入呢?
发现惊人的现象:父子进程读取同一个变量(因为打印的地址一样),但是后续有人修改的情况下,父子进程读取到的内容却不一样!在一块内存里面确读取到不同变量!!------

✳️得出结论,我们在C/C++中使用的地址,绝对不是物理地址!
如果是物理地址,这种现象是绝对不可能产生的!
那这是什么??(现在无法证明,但可以告诉概念)—“虚拟地址”!也可以叫做线性地址或者逻辑地址。(那么虚拟地址是什么东西?为什么要存在它呢?它和我们物理地址会有什么关系呢?) 请添加图片描述

为什么我的操作系统不让我直接看到物理内存呢?

✳️如果能让我们看到物理内存,是不是可以让我们随便操作了?不能呀,因为有时候我越界访问,出现野指针,程序就直接崩溃了呀。
内存就是一个硬件,不能阻拦我们访问。我们之前程序崩溃,并不是直接访问物理内存,是操作系统在我们程序和内存之间加了一层软件层,它来对我们做控制,我们就是要认识到,内存是一个硬件,是不能阻止我们访问的!
我们经常听说代码只读,不能被修改,既然代码是只读的,那么代码在内存里面吗?代码如果在内存里,代码不可被修改,是不是意味着保存代码的那部分内存是不可被修改的?那这代码曾经又是如何被加入到这个内存的呢?那么这代码是如何第一次从磁盘/外设加载到内存里呢?你内存都不可被修改了,那你代码是如何写到内存里的呢?所以发现是有逻辑问题的!
我们的代码确实不可以被修改,数据可以被修改,但这并不是内存帮我们保证的,内存就是一个硬件,并不能阻拦我们的访问,只能被动的进行读取和写入。
如果系统让你直接访问到内存,那直接拿着指针就对内存当中的任何数据做修改。

进程地址空间

✳️每一个进程在启动的时候,都会让操作系统给他创建一个地址空间,该地址空间就是进程地址空间。

我们的进程被创建出来,有自己的代码和数据,同样操作系统为了管理该进程就得先为该进程构建一个task_struct,代码和数据肯定都在内存里的。以前我们说是PCB指向代码和数据。那么现在更准确的说操作系统会在PCB和放在物理内存中的代码和数据构建一个地址空间。

✳️每一个进程都会有自己的进程地址空间。
那么操作系统要不要管理这些进程地址空间呢??—要管理!那就得先描述,再组织。

✳️所谓进程地址空间就是内核的一个数据结构,struct mm_struct用来描述进程地址空间。
那么这个struct mm_struct是怎么和内存扯上关联的呢?–后面说。、请添加图片描述
❓究竟什么是进程地址空间??
我们曾经说过进程具有独立性(一个进程挂掉或者崩溃是不影响另一个进程的)!
一个进程独立性体现在哪儿呢?进程相关的数据结构是独立的;进程的代码和数据是独立的。
举例:美国富翁有很多私生子,各个私生子不知道对方的存在,以为自己都是富翁的独生子。富翁为了各个私生子的独立性,富翁就给各个儿子许下相同的财富许诺,就是每个儿子都认为自己有10个亿,这时候大儿子叫他给他1000美金,富翁给了;二儿子要一辆车,富翁给他了…只要富翁的承受范围内都会给!
富翁给自己的私生子,每个人在大脑中画了虚拟不存在的10个亿家产 ,就叫做富翁给这几个儿子建立了进程地址出空间。所以说进程地址空间说白了就是给进程画的一块大饼。
富翁:OS
三个私生子:进程
富翁给三个私生子画的大饼:进程地址空间(逻辑上抽象的概念),为了彼此根本赶不到各自的存在。
为了让每一个进程都认为自己是独占系统中的所有资源的!其实是操作系统骗了进程,资源是被所有进程共享的,你进程不会吧空间所有用完的,你用都是一点点要的,然后你一直都会这么认为。(感性认识)
另一个例子:其实我们人人都是私生子,银行是最大的富翁,我们将钱存到银行,认为自己有该有的钱,但是银行拿去做其他事情了,你并不不会认为被骗了!

✳️所谓的地址空间:其实就是OS通过软件的方式,给进程提供一个软件视角,认为自己会独占系统的所有资源(内存)。

✳️实际上我们mm_struct里面的排布就是地址空间分布图里的排布。每一个区域都要经过一个内核结构“页表”。程序在磁盘中有代码和数据,那么他一定会被加载到内存当中,加载进来后,我们需要将虚拟地址空间与物理内存空间建立映射关系,这个关系是通过页表的结构,通过页表来维护的。
页表本质上是一个映射表,它的左侧是虚拟地址,表的右侧对应的是物理地址,我们通常看到的各种物理地址实际上就是在虚拟地址空间当中给我们分配好的各种地址,这样的地址通过我们的虚拟地址转化成物理地址到达我们的物理内存。
请添加图片描述
❓那么什么叫做空间区域?
我们通常说我们的地址空间被划分为代码区,数据区等区域,这些区域如何理解呢?
在上学时一个桌子是分给两个学生一个男的一个女的,假设桌子100cm,女生在桌子上画一根线(三八线),则1-50,50-100是各个区域,则这就是区域的划分。则用计算机语言:struct desk_area
{
int start,
int end;
}
struct desk_area girl_area=[1,50]
struct desk_area boy_area=[50,100]
之间不能越界访问,并且可以调整区域大小,调整区域大小无非就是用start和end的改变。这就是空间区域。
我想啊将铅笔盒放在桌子上,无非就是给他分配好刻度。
所以每个区域范围,都是可以有对应的编号的,以cm为单位
请添加图片描述
✳️所以空间被划分为不同的区域,这里每一个区域范围之内都有一套地址,这一套地址将来会作为页表左侧的虚拟地址,同右侧的物理地址进行映射。

✳️页表建立映射是我们在,将程序加载到内存,由程序变成进程之后由操作系统构建一个页表结构。
所以页表实际上是我们进程加载到内存时自动会被构建的,自动被构建好的你想想你加载的时候一定是将你的代码加载到内存,那么你对应的所有的代码物理地址都有了,虚拟地址大家都一样,这样就能给每个进程建立一套映射关系,左侧虚拟地址,右侧物理地址建立好,此时进程就能在特定的区域当中去访问它的地址,找到地址当中的入口地址,然后去找页表,再找到对应的代码和对应的数据去访问就可以了。
🌟这个区域我们就叫做虚拟地址!

程序是如何变成进程的

❓ 程序被编译出来,没有被加载的时候,程序内部,有地址吗?
答案是:有的!还记得链接吗?链接不就是把我们的程序同库产生关联吗?如果没有地址,请问怎么产生关联?我们所说产生关联不就是把库当中的代码加载,然后库当中的printf的地址填到我自己调用printf的地方吗,这就叫做产生关联,你不能说他没有地址,实际上是有的!

❓程序被编译出来,没有被加载的时候,程序内部,有没有区域?
答案是:有的!我们用gdp调试的时候调用#readif -S myproc的时候会列出地址区域(初始化数据区,未初始化数据区等等。

❓不是说一个程序加载到内存里才会有地址,那么现在一个程序没有加载到内存怎么会有地址?那请问这两地址有啥关系?请问我的加载又是在做什么?
当我们的程序加载到内存的时候,地址编排是由全0到全f,比如说我的代码区,相对于我程序的起始地址0x1f,然后想把这部分区域加载到内存的起始位置0x100,当被加载进来的时候,实际上是吧程序那一部分加载到内存的时候,把里面所有的地址加上一个0x1f偏移量,所以此时所有地址变为0x11f,也就相当于我们已经把可执行程序的部分地址加载到内存里了,换句话说我们在可执行程序里面采用的是“相对地址”我们一般称逻辑地址。
然后我们把程序加载到内存的时候,我们是把内存想象成每个进程都是从全0到全f的,所以我们把内存的整体结构想象成一个线性地址,然后我们程序放到内存的时候,我呢相对于我程序的起始地址是一个相对地址(0x1f),我给它加上我的起始地址(0x100),最后在内存里面就成了一个全新的地址,我们称之为虚拟地址!在Linux中逻辑地址、线性地址、虚拟地址都是一个概念大家都是认为从0开始的。
一个程序在编译的时候就认为是从0开始的,所以可以理解为程序很多的虚拟地址在编译的时候就确定了 ,在加载到内存的时候,就是做了一些相关的转化。
意思就是说:跑道上有一棵树,我人放在跑道上有两种放法。一种是我们直接把人放在具体的刻度位置;还有一种表示地址的方案就是我离那棵树是5米,所以我们人到跑道的时候,人只要站在离树5米的地方,那么也是能够找到自己的地址,其实真正用到的内存地址应该是从5米加上自己的起始位置。
请添加图片描述
✳️可执行程序在编译好之后本身就有了一套自己对应的地址,可执行程序内存当中是由区域的,所有的区域最终都在磁盘上已经划分好了,所以加载到内存无非是根据区域加载到内存。
换句话说,我们加载之后,我们可以在内存里吧所有可执行程序的代码和数据所对应的地址,全部转化为我认为我是从0开始独占我们的内存的。接下来加载完后程序到内存里面了,操作系统会给每一个进程创建PCB,PCB指向那段空间,在构建所对应的列表,所以此时列表映射到物理内存,映射到物理内存,程序会去读取我们所对应的代码的数据,当读取代码时,因为物理内存当中的地址,已经被转化为虚拟地址,所以CPU读到的全部都是虚拟地址。在进行我们寻址的时候,会做页表转化,找到对应的物理内存 。如上就是加载过程

✳️主要是要区分:程序内部的地址
内存的地址,是没有关系的
自己的程序加载到内存之后,你调用了printf,相比较于你的程序在那个位置,所以你是在内部找到这个位置,是采用相对地址的方案,而我们加载到内存之后,在物理上有地址,但这个地址是在页表右侧维护起来,能够找到你就行。实际上CPU读到你程序里面的地址加载的时候就早已转化为虚拟地址,然后我们读到的都是虚拟地址,所以最终我们此时才能够再进行列表转化找到物理内存。

✳️我们编译程序的时候,就认为是程序按照000-fff进行编址的,就相当于我们程序编译的时候,它的所有地址就已经按2的32次方就编译好了,每个区域在什么地方我们都是确定好的,在磁盘当中每个代码他的地址都全部编好了,当加载到内存的时候,它的地址呢我们可以认为它的起始偏移量是从0开始的 。我们就相当于磁盘当中的保存的虚拟地址和我们加载到内存时的地址是不变的,至于加载到物理内存的什么位置,由页表去映射,但是程序内部的地址,全部都已经是以地址空间的方式给我们排好的,它可以在磁盘的任意位置去加,因为页表可以随意去映射,但是程序当中代码的函数跳转,我们所谓的变量的地址已经以虚拟地址的方式呈现好,CPU通过页表找到对应的代码之后,读取指令里面包含的地址就是虚拟地址,然后经过页表再转化找到你再内存当中的地址,进而找到你的代码继续执行。 (若还是不了解可以放下,作为了解就行)
举例子:教室里面排队,在进入教室之前先把队排好,相当于是逻辑地址(虚拟地址),当你进入到教室里面的时候根据你的编号坐到你教室的座位上,那么这叫做物理地址,但是你自己永远记住你的编号是1234当我执行的时候我用的就是所谓的1234。
或者换个例子:教室里面有座位,每个座位有自己的编号,我作为老师,手里面有名单,名单是一对映射关系,同学们现在在门口排队,排好队呢,这个同学是1号同学,这个是2号…然后我作为老师,我以后提问我不根据座位号来提问,而是根据名单上面的编号来提问,1号同学坐在编号为5的座位上(相当于物理内存),2号同学在我们的7号位置,3号同学在我们的6号位置…其中点名的时候喊1号同学,则5号桌子上的同学站起来。则1号2号3号…编号的同学就相当于逻辑地址,而教室里面的桌子编号相当于物理地址。当点名的时候1号同学知道自己桌子编号位置为5,但是代码内部心里记住的编号为1。
Linux做法就是吧桌子编号从0,1,2…一直开始,然后自己在门外编号的学生也是从0,1,2,3…编号开始,所以我们就可以理解成天然的当我们进行编译的时候,所有的区域已经是按虚拟地址的方案全部都编好了,最后在代码里面是以这样的地址呈现的,而我加载到内存的时候(教室里的桌子)可以随便去加载,但是当我们在点名的时候依旧从0,1,2…开始点,当识别到0.,1,2…号这样方式去寻址,也就是我以地址空间的方式全0-全f,最后我在找的时候,找0号同学我就直接去他的8号位置去找。我们老师只看0,1,2…同学们的编号,而不看教室里面桌子上的编号。就是说名单上面左侧是同学的编号,右侧是对应的桌子编号。
✳️每一个进程都会创建task_struct,每个进程都会维护一个mm_struct,他自己有自己对应的区域,当我们程序加载到内存的时候,程序有自己加载到物理内存的地址,我们将虚拟地址与物理地址建立映射,我们最终进程访问的时候,它访问的是某个区域当中的地址的时候,经过页表映射到物理内存,找到对应的代码,当我找到对应的代码和数据时候,就要吧代码和数据加载到CPU里面,当我们吧代码加载到CPU里面时候,代码也有自己的地址,这个代码的地址早已经在我们加载的时候转化为虚拟地址,所以CPU可以继续照着这个逻辑地址向下运行。
虚拟地址空间,不仅仅是我们操作系统会考虑,编译器会考虑。

同一个id会有两个不同数据?

❓为什么我们今天在运行的时候,父子进程刚开始,它们指向同一块地址,访问的同一个数据,但一个修改之后,但是地址是一样的,却打印出来的内容却不一样???

✳️解答:因为父进程被创建,会被创建task_struct,也有自己的mm_struct(虚拟地址进程空间),也会由操作系统给他构建一个页表。我们自己定义的变量有了自己的虚拟地址地址,最后经过页表映射会映射到物理内存地址当中;
当fork创建子进程的时候,fork之后就是相当于系统多了一个进程,多了一个进程也要给他创建相应的task_struct,创建对应的进程地址空间(mm_struct),给子进程维护页表,我们曾经说过创建子进程的时候,子进程的相关数据结构内容的初始化:task_struct会以父进程为模版,当创建好后,子进程也有自己的mm_struct虚拟地址,也有自己的页表,但这个页表呢会以父进程为模版。当虚拟地址映射到页表当中,页表的物理地址映射到的依然是父进程映射到物理内存的位置,所以我们一开始打印的值是一样的。
但是当我们发生写入的时候,无论是父进程,还是子进程。因为进程具有独立性!进程也有自己的代码,一开始父进程和子进程指向同一份代码,所以能够感受出来fork之后父子进程是共享的,它们虚拟地址到物理地址映射到页表是一样的,所以它们指向的代码和数据都是一样的,所以我们两个打印出来的虚拟地址一样并且内容一样。
因为进程具有独立性,如果子进程将变量改了,就会导致父进程识别变量出现问题,若父进程用该值是做全局判断呢?若子进程将变量内容改了,会影响到父进程的逻辑,则就叫做子进程影响了父进程。
因为进程具有独立性的,就要做到互不影响,显然你现在子进程要对变量做修改了,当识别到子进程修改时,操作系统会 重新给子进程开辟一段空间,并且把修改的变量拷贝下来,拷贝下来后重新给子进程建立映射关系,所以子进程的页表就不再指向父进程页表所指向的那个变量了,而是指向新的变量的空间。
换而言之,在改的时候永远只改变页表的右侧,而左侧不变。所以最终看到的是虚拟地址一样,但实际上经过页表映射 ,已经被映射到不同的物理内存,所以读到的值不相同。

✳️我们操作系统当父子对数据做修改的时候,操作系统会给子进程或修改的一方重新开辟一段空间,并且把原始数据拷贝到新空间当中,这种行为我们叫做**“写实拷贝”**。
当父子有任何一个进程尝试去修改变量的时候,有一个人想修改,那么此时就会发生写实拷贝,拷贝到新的物理内存,然后重新构建页表的映射关系,但是虚拟地址是不发生任何变化的,所以看到同一个id,却有不同的值。

✳️通过页表,将父子进程的数据就可以通过写时拷贝的方式,进行分离!做到父子进程具有独立性!请添加图片描述

❓曾经说过fork有两个返回值(我们曾经说他一定是return了两次),pid_t id,同一个变量怎么会有不同的值??
✳️解答:pid_t id是属于父进程的栈空间定义的变量,fork内部,return会被执行两次(因为执行fork代码的时候子进程创建了则要和父进程代码共享,所以当执行到fork内部的return的时候子进程也要执行!)return的本质,就是通过寄存器写入到接受返回值的变量中!!
当id = fork()的时候,谁先返回,谁就要发生写时拷贝,所以,同一个变量,会有不同的内容值,本质是因为大家的虚拟地址是一样的,但是大家对应的物理地址是不一样的!

为什么要有虚拟地址空间?

好处一:保护内存

一个进程被加载的时候一定会为其建立维护PCB,将它代码加载到内存,CPU在执行的时候会调度进程1和进程2。可是如果我们自己写的代码,出现野指针发生越界了,比如*p = 110其为野指针,它指向进程2的代码了甚至是操作系统的代码!当进行访问的时候,就会把其他进程的数据改了!所以直接用裸的物理内存方案是不安全的!

✳️直接让我们进程去访问物理内存是不安全的!-----保护内存!
❓那虚拟地址空间是如何保证安全的呢?
比如当我们的地址空间想要访问我某一个区域,因为有页表建立的映射关系,能够指向自己的内存空间。如果我们进程出现野指针的问题,在我们的页表当中没有建立它的映射关系,那么就不会让你访问到物理内存!如果有映射关系的话,一定是曾经经过加载进来的页表映射关系,要么就是自己定义的栈空间或malloc空间开辟的空间,如果是野指针那么页表一定是没有你的映射关系,那么页表转化就会失败,那么进程想访问内存就不可靠,我们操作系统就会直接把你的进程杀掉,杀掉之后你的物理没有经历过任何的写入。

✳️所以因为有虚拟地址空间的存在,就相当于访问内存添加了一层软硬件层,可以对转化过程进行审核,非法的访问,就可以直接拦截了。

好处二 :内存管理

我们进程说想要malloc一段空间,申请一段空间的时候,地址空间说你想什么时候用呢?用户可能就说我也不知道什么时候用,但就是想提前申请一下。那么我们的地址空间就会说:行!我给你把内存申请上。所谓的申请上就是把地址空间的堆区给你向上移动(前面讲过区域划分:用start和end两个指针移动)若你想要100字节,那么地址空间把那end加上100,但是内存还没有给你申请,可你认为你已经有了。
可是为什么操作系统说不立马把空间给我们呢?你赶紧把堆区给我申请好,在内存给我开辟一段空间,在页表上把映射关系给我建立好。
但是操作系统不会这么干!因为操作系统认为提前把空间给你申请好,把物理内存给你开辟好了,可是你现在又不用,就相当于你占着内存还不用,那么你不用就是说你不用,别人还不能用,所以说这一段内存在一段时间内是属于浪费的状态!

✳️一个进程在申请内存的时候,本质只要在地址空间把相应的空间开辟好,后半部分内存申请,暂时不给你申请,然后当你想要的时候,我们的操作系统,比如当你正在申请空间,后来你代码过了一百行代码之后,在第101行开始访问这段空间,操作系统说这货要读取地址空间了,地址空间已经给他了怎么办?那么操作系统会把访问工作放一放,给他先赶紧申请一段内存,建立好映射关系,然后把进程访问空间的过程恢复过来,让它继续运行。也就是说当用的时候再给你内存申请。
当你在需要的时候给你申请内存的最大好处就是:即便你现在申请了,但你不用,那么这段空间就可以给别人拿走去用,无形当中就可以提高操作系统的运行效率。 -----这叫做linux的内存管理

✳️Linux有虚拟地址空间的存在,它可以吧对进程的调度 ,执行代码和Linux的内存管理通过页表就彻底进行解藕了
申请空间时,我操作系统只是把你地址空间扩大了,当真正想访问时,系统才会做内存管理的申请,填充我们的页表。
所以通过地址空间,进行功能模块的解藕!!

好处三:让进程或者程序可以以一种统一视角看待内存

✳️每个进程都可以认为自己有初始化区、未初始化区、堆区、栈区;
每个进程都可以认为自己起始地址是同一个确定的地址开始;
方便以统一的方式编译和加载所有的可执行程序,就相当于有了同一的地址空间,将来编译的时候,进程无论是一个单链表增删查改,还是模拟容器的实现,无论是什么代码,我们都认为main()函数,代码区、数据区…各种区域,在地址空间的那一部分我们都认为是确定的,这样编译器就可以以统一的方式去给所有可执行程序当中的代码和数据进行编址。当变成进程之后,所有进程都有这样的区域,我们就可以直接去特定的代码区和数据区去找到我们对应的代码和数据,这样就可以简化我们进程的设计,以统一的方式去处理它。
如果没有虚拟地址空间,今天要把可执行程序加载到内存里,每个程序内部有自己代码和数据都有自己的地址,请问你上面的地址加载到内存的时候,是不是就要想办法把所有地址都要变化成我们的物理地址,每次加载的时候,你的程序地址都会发生变化,因为你会被加载到不同物理地址处,但在我看来,我以统一的视角,当将来我的进程 加载到物理内存的任何位置,我都不怕,因为我的虚拟地址空间,我看到的虚拟地址空间一点都不变,我可以被加载到物理内存任意位置,因为虚拟地址映射到物理内存,改的永远是右侧,而左侧代码区,数据去,各种乱七八糟的区域完全没有任何变化,代码区永远在那个区域。请添加图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值