深入相识linux体系—— 进程地点空间

张国伟  论坛元老 | 2025-5-7 09:11:43 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1640|帖子 1640|积分 4920

前言
步伐地点空间

在之前,我们学习C/C++时,多多少少都看过这样的一张图

我们现在通过下面这一段代码看一下:
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. int g_unval;
  5. int g_val = 100;
  6. int main(int argc, char *argv[], char *env[])
  7. {
  8.     const char *str = "helloworld";
  9.     printf("code addr: %p\n", main);
  10.     printf("init global addr: %p\n", &g_val);
  11.     printf("uninit global addr: %p\n", &g_unval);
  12.     static int test = 10;
  13.     char *heap_mem = (char*)malloc(10);
  14.     char *heap_mem1 = (char*)malloc(10);
  15.     char *heap_mem2 = (char*)malloc(10);
  16.     char *heap_mem3 = (char*)malloc(10);
  17.     printf("heap addr: %p\n", heap_mem); //heap_mem(0), &heap_mem(1)
  18.     printf("heap addr: %p\n", heap_mem1); //heap_mem(0), &heap_mem(1)
  19.         printf("heap addr: %p\n", heap_mem2); //heap_mem(0), &heap_mem(1)
  20.         printf("heap addr: %p\n", heap_mem3); //heap_mem(0), &heap_mem(1)
  21.     printf("test static addr: %p\n", &test); //heap_mem(0), &heap_mem(1)
  22.     printf("stack addr: %p\n", &heap_mem); //heap_mem(0), &heap_mem(1)
  23.     printf("stack addr: %p\n", &heap_mem1); //heap_mem(0), &heap_mem(1)
  24.     printf("stack addr: %p\n", &heap_mem2); //heap_mem(0), &heap_mem(1)
  25.     printf("stack addr: %p\n", &heap_mem3); //heap_mem(0), &heap_mem(1)
  26.     printf("read only string addr: %p\n", str);
  27.     for(int i = 0 ;i < argc; i++)
  28.     {
  29.             printf("argv[%d]: %p\n", i, argv[i]);
  30.     }
  31.     for(int i = 0; env[i]; i++)
  32.     {
  33.             printf("env[%d]: %p\n", i, env[i]);
  34.     }
  35.     return 0;
  36. }
复制代码

通过观察我们可以发现,我们步伐内的地点确实是符合图片中的地点地区划分的。
增补:


  • 在32为呆板下步伐地点空间的巨细为2^32字节,也就是4GB。
假造地点

这里我们思索一个问题,步伐地点空间是内存吗?
我们先来看一下代码:
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. int main()
  4. {
  5.         int x = 0;
  6.         int id = fork();
  7.         if(id < 0){
  8.                 perror("fork");
  9.                 return 1;
  10.         }
  11.         else if(id == 0){
  12.                 //子进程
  13.                 while(1){
  14.                         printf("子进程, x = %-3d, &x = %p\n",x,&x);
  15.              sleep(1);
  16.                         x+=10;
  17.                 }
  18.         }
  19.         else{
  20.                 //父进程
  21.                 while(1){
  22.                         printf("父进程, x = %-3d, &x = %p\n",x,&x);
  23.              sleep(1);
  24.                 }
  25.         }
  26.         return 0;
  27. }
复制代码
这里我们让父子进程同时实行,然后子进程每次实行x+=10;父子进程都输出x的值和x的地点。

   我们惊讶的发现,父子进程输出的x的值不一样,但是x的地点却是一样的!!!
  所以很显然,这里的步伐地点空间不是内存。(如果是内存,同一个地点为什么会输出差别的值?)
  

  • 这里变量内容不一样,所以我们父子进程输出的一定不是同一个变量
  • 地点一样,所以这里的地点一定不是指的内存(物理地点)
  • 在Linux中,这种地点称为假造地点
所以,我们之前在代码中使用&操作,取到的都是假造地点;
而物理地点,我们是看不到的,它由操作体系管理起来。
而我们的操作体系OS可以通过假造地点找到对应的物理地点。
进程地点空间

实在,我们之前的步伐地点空间,它是从语言层面去理解的;它本质上是进程地点空间。
   简朴来说,进程地点空间就是操作体系为每一个进程分配额的一个假造内存视图,它让每一个进程都以为本身独占了整个计算机的内存资源。
  用现在通俗的话来说,进程地点空间就是操作体系给每一个进程地点空间画的大饼,给每一个进程说,内存资源都是你的,让它以为它本身占用了所有的内存资源。


  • 一个进程,一个进程地点空间
  • 一个进程,一套页表
   我们知道在内存中存在非常多的进程,那么多进程,每一个进程都有一个进程地点空间;云云多的进程地点空间,操作体系是不是也要将其管理起来呢?
  答案是的,管理这些进程地点空间:先形貌、后组织
  在Linux内核中,进程地点空间本质上就是一个结构体对象mm_struct;
那也就是说,在进程当中还存在一个进程地点空间,我们在语言层面用到的地点都是假造地点,而操作体系可以通过假造地点,找到对应的物理地点。

页表

在上面形貌中,提到了一个进程,一套页表;那页表指的是什么呢?
我们知道,操作体系要能够通过假造地点找到对应的物理地点,那怎样去找呢?(就比如父子进程中x的假造地点是一样的,但值不一样就说明找到的物理地点不一样)
页表本质上就是假造地点和物理地点的映射关系,它里面存储了假造地点和其对应的物理地点(除此之外,还存储了其他内容,比方权限)
那对于差别的进程,相同的假造地点能够找到差别的物理地点,也就是说两个进程中的假造地点和物理地点对应关系是不一样的。
所以,一个进程,一套页表
写时拷贝

相识了进程地点空间和页表之后,我们再回过头看上面的问题:父子进程x的值不相同,但假造地点是相同的
我们知道一个进程一个进程地点空间,一个进程对应一套页表;
在父进程创建子进程时,子进程也会将父进程的task_struct大部分数据拷贝到子进程的task_atruct(部分数据会修改);也会将父进程的进程地点空间连同页表进行浅拷贝;这样父子进程就公共代码和数据
而当我们子进程进行修改数据时,(操作体系不让子进程在原数据上修改,而是给子进程开辟一块新的内存,然后将子进程页表的对应关系修改了,也就是将页表中对应的物理内存修改为新开辟的这一块空间)。
那这样,父进程在访问时,操作体系通过页表找到对应的物理地点与子进程在访问时操作体系通过页表找到对应的物理地点就不相同了

管理进程地点空间

我们知道了每一个进程都有对应的进程地点空间,那操作体系是怎样将进程地点空间管理起来的呢?
在Linux下,形貌进程地点空间所有信息的结构体是mm_struct;每一个进程都有一个mm_struct结构;在进程的task_struct中都存在实行进程对应mm_struct的指针。
理解地区划分

我们观察进程地点空间的图,我们可以发现有正文代码、初始化数据、未初始化数据、堆、栈等非常多的地区;那操作体系是怎样价格进程地点空间划分成云云多的地区呢?
   这里就好比我们小时间,与同桌之间画的三八线;它将桌子划分成了两个地区,站在计算机的角度去理解就是:
  整个桌子的地区[0,99],三八线将桌子划分成了两个地区[0,49]和[50,99];你的地区是[0,49],你同桌的地区是[50,99];
  我们只必要知道你地区的起始位置和结束位置,同桌的起始位置和结束位置,就可以将桌子划分;
  在这里也是一样,我们是不是只必要知道每一个地区的起始位置和停止位置,那就可以很好的将进程地点空间划分成非常多的地区。
类似于下图这样:

在Linux内核中,我们可以查看mm_struct的结构:
  1. struct mm_struct
  2. {
  3.     //...
  4.     struct vm_area_struct *mmap; /* 指向虚拟区间(VMA)链表 */
  5.     struct rb_root mm_rb; /* red_black树 */
  6.     unsigned long task_size; /*具有该结构体的进程的虚拟地址空间的⼤⼩*/
  7.     //...
  8.     // 代码段、数据段、堆栈段、参数段及环境段的起始和结束地址。
  9.     unsigned long start_code, end_code, start_data, end_data;
  10.     unsigned long start_brk, brk, start_stack;
  11.     unsigned long arg_start, arg_end, env_start, env_end;
  12. }
复制代码
通过查看我们可以发现:在其中还存在一个rb_root和struct vm_area_struct*范例的mmap:


  • 当假造区间少时采用单链表,由mmap指针指向这个链表。
  • 当假造区间多时采用红黑树管理,由mm_rt指向这颗树。
假造区间

我们在malloc时,在堆上开辟空间,那堆区不应该只有一个吧?那也就是不止存在一个假造空间起始位置吧?
所以在Linux操作体系中还存在vm_area_struct,它表示一个独立的假造内存地区,由于每个差别的假造内存地区功能和内部机制都不相同,所以要使用多个vm_area_struct来区分差别范例的假造内存地区。
上面意思呢?
   简朴来说就是,将进程地点空间中mm_struct是每一个地区再进行细致划分,划分成一个个假造区,然后将假造区间管理起来;
  在mm_struct内核源代码中,存在的mmap和mm_rb就是管理假造区的。
  那操作体系管理进程地点空间就可以形象化为下面图片所示:


固然在mm_struct中也存在每一个地区的开始和结束位置,而vm_area_struct中表示的是更加细致的地区划分。
进程地点空间的作用



  • 将无序的地点,变成有序的
这个就非常好理解了,物理内存空间它不一定是连续的,通过进程地点空间的假造地点,将这些无序的地点转化成有序的地点;然后将假造地点提供为上层用户使用


  • 地点转化的过程中,对我们的地点进行正当性判定,从而保护物理内存
当我们进行野指针访问时,操作体系在页表中查询,没有查询到,操作体系就直接告诉我们野指针访问,从而保护物理内存
还用一种就是当我们修改只有只读权限的内容时,操作体系在页表中查询到以后,发现我们只具有只读权限,而我们要进行写入数据,操作必要就会报错;
   这里写时拷贝也是云云,当我们子进程要修改数据时,操作体系会发现子进程只具有只读权限,就会报错,然后给子进程开辟新的空间,重新映射页表,然后修改权限为可写可读。
  本篇文章到这里就结束了,感谢支持
简朴总结:
   

  • 理解进程地点空间是什么
  • 理解写时拷贝
  • 地区划分和假造区间

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张国伟

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表