• 【Linux】进程地址空间



    需要云服务器等云产品来学习Linux的同学可以移步/-->腾讯云<--/-->阿里云<--/-->华为云<--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。


     目录

    一、虚拟地址

    二、对进程地址空间的理解

    三、32位下的进程地址空间

    那么进程如何找到内存中的数据呢?

    四、为什么要通过虚拟地址映射的方式访问物理地址


    一、虚拟地址

    先看一段父子进程共存的程序,由子进程对全局变量grobal_val进行修改:

    1. #include
    2. #include
    3. int grobal_val=10;
    4. int main()
    5. {
    6. pid_t id=fork();
    7. if(id==0)
    8. {
    9. int cnt=0;
    10. while(1)
    11. {
    12. printf("子进程:pid=%d,ppid=%d | grobal_val=%d,&grobal_val=%p\n",getpid(),getppid(),grobal_val,&grobal_val);
    13. sleep(1);
    14. ++cnt;
    15. if(cnt==10)
    16. {
    17. grobal_val=200;
    18. printf("子进程已更改全局变量grobal_val\n");
    19. }
    20. }
    21. }
    22. else if(id>0)
    23. {
    24. while(1)
    25. {
    26. printf("父进程:pid=%d,ppid=%d | grobal_val=%d,&grobal_val=%p\n",getpid(),getppid(),grobal_val,&grobal_val);
    27. sleep(1);
    28. }
    29. }
    30. else
    31. {
    32. printf("fork error\n");
    33. return 1;
    34. }
    35. return 0;
    36. }

    父子进程谁先执行不确定,由系统进行调度。

    当子进程将全局变量grobal_val由10改为200,我们可以看到,父子进程的grobal_val的地址相同,但是父子进程从这个地址中获取的值却并不相同!

    从同一块物理地址中取出的值是相同的,所以这个程序取出的地址(指针)并不是物理地址,而是虚拟地址(线性地址、逻辑地址)。注:逻辑地址指可执行程序编译完成后内部函数、变量的地址。逻辑地址有两种表示方法,一种是各个区域地址递增,另一种是每个区域的地址都从零偏移量开始(这种是比较老的表示方式)。

    在Linux中的逻辑地址是第一种表示方式,所以Linux中逻辑地址就是虚拟地址。

    之前学习的C/C++内存区域,是一块虚拟内存空间,每个进程有它自己的虚拟内存空间,即进程地址空间。所以上面的代码用fork创建子进程,因为子进程是父进程的拷贝,父子进程的grobal_val虽然虚拟地址一样,但会被映射到不同的物理地址上。

    当grobal_val未被改变时,父子进程映射同一块grobal_val的物理地址,一旦父子进程的一方对共享数据进行修改,由于进程的独立性,操作系统会在物理内存中再开辟一块空间,并拷贝原数据,提出修改的进程的页表映射关系将会被改变,然后再让进程对数据进行修改,所以我们看到父子进程的数据并不一样。这种技术称为写时拷贝对不同进程的数据进行分离

    二、对进程地址空间的理解

    1、进程它自己会认为它独占CPU资源,但其实并不是。因为进程以时间片轮转的形式占用CPU资源,时间一到,马上从运行状态进入休眠状态,实质上是通过虚拟地址空间,让进程认为它独占CPU资源。

    2、进程地址空间是操作系统给进程开辟的一块虚拟内存空间,这块空间用内核的一种数据结构来描述、组织。

    操作系统给每个进程一块4GB的虚拟内存,进程每次想使用,按需申请即可,但不会全部给进程。(注意这里给的是虚拟内存,就像老板给员工画饼一样)

    对Linux操作系统中进程的理解中提到过,进程使用进程控制块task_struct结构体进行管理,同样的,每个进程地址空间也需要被管理,管理进程地址空间的结构体叫mm_struct,task_struct中有一个指针指向自己的mm_struct。

    mm_struct伪代码:

    1. struct mm_struct
    2. {
    3. uint32_t code_start,code_end;
    4. uint32_t data_start,data_end;
    5. uint32_t heap_start,heap_end;
    6. uint32_t stack_start,stack_end;
    7. ······//存储进程地址空间各区域的起始位置
    8. };

    三、32位下的进程地址空间

    地址空间中的最小单元是字节,所以在32位系统下共有2^(32)个地址空间,也就是4GB。

    mm_struct结构体对象中存放各个区域的起始位置,栈区堆区的动态调整,本质上是修改各个区域的起始地址。

    那么进程如何找到内存中的数据呢?

    操作系统将进程中的虚拟地址通过页表映射到内存,找到对应的物理地址。

    四、为什么要通过虚拟地址映射的方式访问物理地址

    1、直接访问物理内存是非常不安全的,例如越界操作、恶意进程读取等。

    2、页表会拦截不合理的请求,可以保护物理内存,防止恶意进程的访问 。

    所以写代码出现野指针、内存越界等情况并不会造成操作系统的崩溃。

    3、进程地址空间的存在,可以让进程和进程间的代码进行解耦(互不干扰),保证了进程独立性的特征。

    4、进程和编译器均遵守进程地址空间这一套规则,编完即可使用。

    编译器也遵守进程地址空间这一套规则:

    我们的代码在磁盘时,程序的函数、变量等通过虚拟地址建立联系,满足程序间的互相跳转;

    当程序由磁盘被加载到内存中时,就具备了物理地址。函数、变量等通过页表映射至虚拟地址。

    根据可执行程序的虚拟地址初始化mm_struct结构体中每个虚拟内存中的边界。

    当程序在CPU中跑起来时,CPU根据虚拟地址运行完程序后,通过页表映射至物理地址。

  • 相关阅读:
    锐龙r7 6800u和i5 12500h选哪个好 r76800u和i512500h对比
    双节前把我的网站重构了一遍
    多维时序 | Matlab实现GRU-Adaboost和GRU多变量时间序列预测对比
    windows升级新版本mysql
    P1886 滑动窗口 /【模板】单调队列
    为什么OpenCV计算的帧率是错误的?
    想掌握vue模板语法?两篇就够了!(上)
    Since Maven 3.8.1 http repositories are blocked
    优化器算法
    QT+SQLite数据库配置和使用
  • 原文地址:https://blog.csdn.net/gfdxx/article/details/127930915