【linux】进程地址空间

进程地址空间

  • 1.什么是地址空间
  • 2.感性理解虚拟地址空间
  • 3.理性认识虚拟地址空间
  • 4.为什么存在地址空间
    • 4.1原因1
    • 4.2原因2
    • 4.3原因3

1.什么是地址空间

【linux】进程地址空间_第1张图片

请问C/C++地址空间是内存吗?

其实不是,是虚拟地址空间!

我们见识见识虚拟地址空间。

#include
#include
 
int global_val=100;

int main()
{
    pid_t id=fork();
    if(id == 0)
    {
        int cnt=0;
        while(1)
        {
            printf("我是子进程, pid: %d, ppid: %d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_val, &global_val);
           sleep(1);
            cnt++;
            if(cnt == 10)
            {
                global_val=200;
                printf("子进程已经改变global_val的值了。。。。。\n");
            }
        }
    }
    else if(id > 0)
    {
        while(1)
        {
           printf("我是父进程, pid: %d, ppid: %d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_val, &global_val);
            sleep(2);    
       }
    }
    else                                                                                                                                                          
    {
        printf("fork error\n");
    }
 
    return 0;
 }

                                              

【linux】进程地址空间_第2张图片

fork之后,父子进程谁先运行,不确定,由调度器决定。

我们发现多线程在读取同一个地址的时候,怎么可能出现不同的结果?
地址没变----> 这里的地址,绝对不可能是物理地址。

曾经我们学习的语言的地址(指针),不是对应的物理地址,是虚拟地址(线性地址/逻辑地址)

打印出来的地址空间排布,全部都是虚拟地址。

2.感性理解虚拟地址空间

每个进程都认为自己是独占系统资源的(事实上并不是),这知识一种理念。

举个例子
【linux】进程地址空间_第3张图片

son1 工厂周转不开了,找peter要1w美金,5w美金,
son2 要2k美金,买个手表
son3 要3k美金,生活费

不可能全要,因为peter也不会给。

【linux】进程地址空间_第4张图片
peter和他每个私生子都说,等他死后,就让儿子继承全部家产。给儿子画大饼

【linux】进程地址空间_第5张图片

我们的系统就是这样做的;
【linux】进程地址空间_第6张图片

那如何画饼呢?
假设500个员工---->画饼---->员工要被管理----->饼要不要管理呢?
肯定是要的,不然许张三当总经理,把这个大饼当成许给李四了。

500个进程---->地址空间------>进程要被管理------>地址空间也要被管理
如何管理呢?
先描述,在组织

地址空间的本质:是内核的一种数据结构!mm_struct

3.理性认识虚拟地址空间

【linux】进程地址空间_第7张图片

区域划分

有了地址区间就要对地址区间进行划分;
有属于代码的起始和结束的地址,属于数据的起始和结束的地址。。。
【linux】进程地址空间_第8张图片
区域调整

进程在运行时,会有一个PCB,task_struct,操作系统结合地址空间malloc一个struct mm_struct。
【linux】进程地址空间_第9张图片
代码和数据区本质上不变的
heap和stack所谓的区域调整,本质上就是修改各个区域的start/end的值。
定义局部变量,malloc,new----->扩大栈区或者堆区
函数调用结束,free------>缩小栈区或者堆区

库代码
【linux】进程地址空间_第10张图片

进程地址空间和内存的映射

【linux】进程地址空间_第11张图片

进程地址空间和物理内存之间是通过页表来进行映射,建立映射关系的。

【linux】进程地址空间_第12张图片

每个进程到要有对应的地址空间

【linux】进程地址空间_第13张图片
进程1看不到物理内存,进程2也看不到物理内存。
每个进程都认为自己由2^32个地址空间或者内存大小,实际上操作系统不会给那么多,只会给一些,但是依旧让每个进程都认为自己独占系统空间。

4.为什么存在地址空间

4.1原因1

1.如果让进程之间访问物理内存,万一进程越界非法操作,是非常不安全的,有了操作系统提供的虚拟地址和页表,在进程越界非法操作时,页表就会提醒。所以页表不只是映射,还要拦截,所有进程都要遵守。

4.2原因2

还记得上面,写的一段代码,创建子进程,然后子进程把global_val变量改了变成200,但是父子进程的虚拟地址还是一样的,并且父进程global_val值还是100;这是为什么呢?

【linux】进程地址空间_第14张图片
创建子进程,是把父进程的PCB,地址空间,页表都给子进程一份。

还记得之前说过,进程具有独立性,如果一个进程对共享的数据做修改,影响了其他进程,这样就不能称之为独立性。

所以不管是父进程还是子进程修改了数据,为了保证进程的独立性,操作系统会在内存中申请一块空间,把要被修改的数据拷贝下来,修改其页表对应的映射关系,这时候再修改数据,就和其他进程没关系了。而打印出来的地址是一样的,是因为只修改了页表,并没有对进程地址虚拟空间进行变动。

【linux】进程地址空间_第15张图片

任何一份尝试写入,os先进行数据拷贝,更改页表映射,然后再让进程进行修改。这叫做写时拷贝。

操作系统,为了保证进程的独立性做了很多工作-------通过地址空间,页表让不同进程映射到不同的物理内存处。
【linux】进程地址空间_第16张图片

正因为有了这些东西,才保证了进程的独立性。
【linux】进程地址空间_第17张图片

2.地址空间的存在,可以更方便的进行进程与进程之间的数据和代码的解耦,保证了进程独立性这样的特征

4.3原因3

重新再一次理解地址空间

可执行程序里面,有没有地址呢?(再没有被加载到内存的时候)
是有的,早在汇编阶段就有地址,这也是逻辑地址

【linux】进程地址空间_第18张图片

虚拟地址空间,你不要认为只有操作系统会遵守对应的规则,编译器也要遵守!编译器在编译你的代码的时候,就是按照虚拟地址空间的方式,对我们的代码和数据进行编址的。
【linux】进程地址空间_第19张图片

上面说的地址,是我们程序内部使用的地址,程序加载到内存中,天然就具有了一个外部的物理地址!

【linux】进程地址空间_第20张图片

可执行程序加载到内存中,就具备了两套地址;1.标识物理内存中代码和数据的地址;2.在程序内部互相跳转的地址---->虚拟地址;

物理地址加载到页表的右侧,由于可执行程序在编译的时候,也是按照虚拟地址的方式给代码和数据编制的,所以mm_strucrt直接就可以使用这类地址,并且加载到页表左侧。

再看看CPU是如何执行这些指令的。
【linux】进程地址空间_第21张图片
找到main函数虚拟地址,通过页表去找对应的物理地址,然后往下执行代码,当读到fun函数时,取到fun的虚拟地址,然后也根据页表去找对应的物理地址。

cpu在运行进程的时候,读进来的都是指令,指令内部就有了地址(这是虚拟地址);然后在页表找对应的物理地址,去执行进程内部的指令,当跳转到别的函数或者数据时,会再次拿到地址(也是虚拟地址),然后再去找页表对应的物理地址,然后再执行指令。

CPU没有见到物理地址。

3.让进程以统一的视角,来看待进程对应的代码和数据等各个区域,编译器也以统一的视角进行编译代码,因为规则是一样的,方便了使用。

关于进程地址空间就到这了,这里比较难以理解,多看几遍。
喜欢的小伙伴点赞,评论,收藏+关注!感谢!

你可能感兴趣的:(Linux,linux,运维,服务器)