< Linux >:进程地址空间

目录

一、验证进程地址空间

二、感知进程地址空间的存在


一、验证进程地址空间

< Linux >:进程地址空间_第1张图片

我们之前学的 C/C++ 程序地址空间是物理内存吗?

        答:不是物理内存,甚至叫做程序地址空间都不太准确,应该叫做进程地址空间,因此根本就不是 C/C++ 上的概念,而是操作系统上的概念、 

在 Linux 操作系统下验证进程地址空间:

在 Winodws 操作系统下无法验证进程地址空间,原因:

1、与 Windows 操作系统本身的设置有关、

2、与 Windows 操作系统所使用的编译器也有关系,编译器会对代码地址空间做调整,防止代码被恶意猜测,比如:栈随机化策略、

< Linux >:进程地址空间_第2张图片

[LCC@hjmlcc ~]$ ls
a.out  hjm.c  process.c  test.c
[LCC@hjmlcc ~]$ ./a.out -a -b -c
Code addr           :0x40057d
Init global addr    :0x60103c
Uninit global addr  :0x601044
Heap addr           :0x812010
Stack addr          :0x7ffc3f1db3c0
Argv addr           :0x7ffc3f1db7c6
Argv addr           :0x7ffc3f1db7ce
Argv addr           :0x7ffc3f1db7d1
Argv addr           :0x7ffc3f1db7d4
Env addr            :0x7ffc3f1db7d7
Env addr            :0x7ffc3f1db7ed
Env addr            :0x7ffc3f1db7fd
Env addr            :0x7ffc3f1db808
Env addr            :0x7ffc3f1db818
Env addr            :0x7ffc3f1dbfe6
[LCC@hjmlcc ~]$ 

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

< Linux >:进程地址空间_第3张图片

< Linux >:进程地址空间_第4张图片


二、感知进程地址空间的存在

        注意:使用 Linux 操作系统提供的系统调用接口 fork 创建出当前进程的子进程,若启动当前进程,那么当前进程和当前进程的子进程启动的先后顺序是不确定的,到底是谁先谁后启动,取决于 CPU 先运行了谁,通过实验可知,一般都是父进程先被启动,子进程再被启动,但并代表父进程一定会先被启动,记住即可、

#include
#include
int g_val=100;
int main()
{
  pid_t id=fork();//不考虑创建当前进程子进程失败的情况、
  if(id == 0)
  {
    //子进程
    while(1)
    {
      printf("我是子进程:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
      sleep(1);
    }
  }
  else{
    //父进程
    while(1)
    {
      printf("我是父进程:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
      sleep(2);                                                                                                                
    }                                                     
  }                                   
   return 0;                                          
}    

< Linux >:进程地址空间_第5张图片

注意:此时父子进程读取到的普通全局变量是 同一个 变量、

现做如下修改:

  1 #include
  2 #include
  3 //普通全局变量、
  4 int g_val=100;//以普通局部变量举例也是可以的、
  5 int main()
  6 {
  7   pid_t id=fork();//不考虑创建当前进程子进程失败的情况、
  8   if(id == 0)
  9   {
 10     //子进程
 11     int flag=0;
 12     while(1)
 13     {
 14       printf("我是子进程:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
 15       sleep(1);
 16       flag++;
 17       if(flag==5)
 18       {
 19         g_val=200;
 20         printf("我是子进程,普通全局变量的值已被修改,请注意\n");
 21       }
 22     }
 23   }                                                                                                                            
 24   else{
 25     //父进程
 26     while(1)
 27     {
 28       printf("我是父进程:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
 29       sleep(2);
 30     }
 31   }
 32   return 0;
 33 }

< Linux >:进程地址空间_第6张图片

        由上图可知,父子进程读取到的普通全局变量的地址相同,但是读取到的普通全局变量的值不同,因此我们可知,此处普通全局变量的地址一定不是物理地址,不然肯定不会出现这种情况,虽然上述父子进程读取到的普通全局变量的地址相同,但父子进程各自读取到的普通全局变量并不是 同一个 变量,所以,此处的普通全局变量的地址一定是虚拟地址、

        我们之前在 C/C++ 中所谓的地址,都不是物理地址,都是虚拟地址;虚拟地址在 Linux 操作系统下又被称为:线性地址、逻辑地址;这三个概念在 Linux 操作系统下是一样的概念,但是不在Linux 操作系统下,是三个完全不一样的概念,这和 Linux 操作系统本身的空间布局有关、

        本节所讲的进程地址空间,本质上就是虚拟地址空间,因此本小结也可被称为:感知虚拟地址空间的存在、

        操作系统不让我们直接看到或访问物理内存的原因:不安全;内存(物理内存,不存在虚拟内存的概念,物理地址对应物理内存)是一个硬件,不能阻拦我们进行访问,只能被动的进行读取和写入;由野指针或越界等原因造成程序崩溃的情况并不是由内存(物理内存)造成的,而是由操作系统在程序与内存(物理内存)之间添加的软件层造成的、

        每一个进程在启动时,操作系统都会给其创建一个进程地址空间(虚拟地址空间),操作系统会对这若干个进程地址空间进行管理(先描述再组织);所谓的进程地址空间,其实本质上就是内核(操作系统)的一个数据结构,也存在于内存(物理内存)中,在 Linux 操作系统中,也是一个描述进程地址空间的 struct 结构体( struct mm_struct )、        

        所谓的进程地址空间其实就是操作系统通过软件的方式,给进程提供一个软件视角,使得每一个进程都认为其独占操作系统的所有资源(物理内存)、

< Linux >:进程地址空间_第7张图片

        前面我们知道进程是具有独立性的,体现在相关的内核数据结构是独立的和进程的代码和数据是独立的;类比于一位海王同时撩三个女的(广撒网,钓大鱼),并对每一个女的说我只中意你,且画大饼说以后对你怎么怎么好……,从而使得每个女的都天真的认为我是不可替代的那个人,这个例子中,海王充当的就是操作系统OS,三个女的就是三个进程,海王画的大饼就是进程地址空间,海王画大饼的原因在于为了维护这三个女(三个进程)的独立性,互补干扰,若有交集则必然乱套、

        所谓的进程就是:进程等于加载到内存(物理内存)中的可执行程序(代码及数据)加上该进程对应的内核数据结构(该进程对应的描述该进程所使用的 struct 结构体(task_struct),其也存在于内存(物理内存)之中)的组合、

        在 Linux 内核中,每个进程都有 task_struct 结构体,该结构体中有个指针指向一个结构mm_struct (进程地址空间),我们假设磁盘上的一个可执行程序被加载到物理内存,之后,操作系统会给每一个进程构建一个页表结构(映射表),我们需要将虚拟地址空间(进程地址空间)和物理内存之间建立映射关系,这种映射关系是通过页表(映射表)的结构完成的,如下图:

< Linux >:进程地址空间_第8张图片

// Linux 内核中的进程地址空间、
struct mm_struct
{
    struct vm_area_struct* mmap;  // list of VMAs
    ...
    ...
}

strcut vm_area_struct
{
    struct mm_struct* vm_mm;

    unsigned long vm_start;
    unsigned long vm_end;
    ...
    ...
}

磁盘中的可执行程序里是有地址的,因为,在链接阶段就是把多个目标文件和链接库通过地址链接起来,从而生成可执行程序的,因此可执行程序里面是由地址的,在该可执行程序中,包括代码区,已初始化全局数据区,未初始化全局数据区等等,这些区域在划分时,采用的不是在物理内存中的地址,采用的是相对地址(相对于整个可执行程序最开始的地址),当该可执行程序被加载到物理内存中(0x00000000 - 0xFFFFFFFF)时,

而堆区和栈区是等该可执行程序被加载到物理内存中才会存在的,

也是有区域的,在 Linux 命令行中输入:readelf -S a.out 即可查看、

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