linux进程(进程地址空间)

目录

前言:

正文:

1.验证地址空间 

2.地址空间是指物理空间吗

3.linux内核的地址空间

4进程访问地址

4.1早期程序寻址 

4.2进程地址空间到物理内存的映射

4.3解释同一变量产生不同值

5虚拟地址空间的意义 

5.1保护物理内存 

5.2进程管理和内存管理的解耦

5.3方便管理 

6总结 


前言:

对于 C/C++ 来说,程序中的内存包括这几部分:栈区堆区静态区 等,其中各个部分功能都不相同,比如函数的栈帧位于 栈区,动态申请的空间位于 堆区,全局变量和常量位于 静态区 ,区域划分的意义是为了更好的使用和管理空间,那么 真实物理空间 也是如此划分吗?多进程运行 时,又是如何区分空间的呢?写时拷贝 机制原理是什么?本文将对这些问题进行解答

linux进程(进程地址空间)_第1张图片 

正文:

1.验证地址空间 

 我们通过代码验证一下,各部分数据处于的位置

linux进程(进程地址空间)_第2张图片

  

#include     
#include     
#include     
    
int g_unval;    
int g_val = 100;    
    
    
int main(int argc, char *argv[], char *env[])    
{    
   // int a = 10;    
    //字面常量    
   const char *str = "helloworld";    
   // 10;    
   // 'a';    
    printf("code addr: %p\n", main);    
    printf("init global addr: %p\n", &g_val);    
    printf("uninit global addr: %p\n", &g_unval);    
    
    char *heap_mem = (char*)malloc(10);    
    char *heap_mem1 = (char*)malloc(10);    
    printf("heap addr: %p\n", heap_mem); //heap_mem(0), &heap_mem(1)    
    printf("heap addr: %p\n", heap_mem1); //heap_mem(0), &heap_mem(1)    
                                                                                                                                                             
    printf("stack addr: %p\n", &heap_mem); //heap_mem(0), &heap_mem(1)    
    printf("stack addr: %p\n", &heap_mem1); //heap_mem(0), &heap_mem(1)    
    
    printf("read only string addr: %p\n", str);    
    int i;    
    for(i = 0 ;i < argc; i++)
    {
        printf("argv[%d]: %p\n", i, argv[i]);
    }
    for(i = 0; env[i]; i++)
    {
        printf("env[%d]: %p\n", i, env[i]);
    }
 
    return 0;
}

linux进程(进程地址空间)_第3张图片

2.地址空间是指物理空间吗

我们利用fork()系统调用函数创建子进程,让父子进程共同使用同一个变量

 

#include 
#include 
#include 
#include 

int main()
{
  int val = 10;
  pid_t id = fork();
  if(id == 0)
  {
  	val *= 2;	//刻意改变共享值
    printf("我是子进程,pid:%d ppid:%d 共享值:%d 共享值地址:%p\n", getpid(), getppid(), val, &val);
    exit(0);
  }

  waitpid(id, 0, 0);

  printf("我是父进程,pid:%d ppid:%d 共享值:%d 共享值地址:%p\n", getpid(), getppid(), val, &val);
  return 0;
}

 linux进程(进程地址空间)_第4张图片

怎么同一个变量,同一个地址,同时读取读到了不同内容呢!!如果地址空间是实实在在的物理地址,同一地址是不可嗯呢读取到两个值,所以结论就是地址空间是虚拟地址也叫线性地址,语言层面包括c/c++都是虚拟地址,用户无法看到真实地址,由操作系统统一管理。

3.linux内核的地址空间

地址空间本质就是一种内核数据结构,在Linux当中,叫做struct mm_struct(linux内核当中的地址空间结构体)包含了一些区域信息(先描述),能够实现区域划分(本质就是在一定的范围内定义start和end)。 

struct mm_struct
{
    unsigned long code_start;
    unsigned long code_end;
    
    unsigned long init_start;
    unsigned long init_end;
    
    unsigned long uninit_start;
    unsigned long uninit_end;
    
    unsigned long heap_start;
    unsigned long heap_end;
    
    unsigned long stack_start;
    unsigned long stack_end;
    //...等不同的区域划分
}

 每个进程都会有自己的地址空间,同时进程控制块(PCB)中也包含了 *mm_struct 指针,可使我们直接找到自己所对应的进程地址空间(后组织)。

  上述讲述的这么多,我们可以理解为进程地址空间就是操作系统给进程花了一个大饼。

  这个大饼就是指的每个进程都会有4GB的连续的空间(0x00000000~0xFFFFFFFF)。实际上呢,这4GB的的空间是虚拟内存,虚拟内存对应的实际物理内存,可能只对应的分配了一点点的物理内存,实际使用了多少内存,就会对应多少物理内存。

  这4G虚拟内存是一个连续的地址空间(这也只是进程认为),而实际上,它的数据是存储在多个物理内存碎片的,还有一部分存储在外部磁盘存储器上,在需要时将数据交换进物理内存。

4进程访问地址

4.1早期程序寻址 

在虚拟地址出现之前,程序的寻址都是直接寻找的物理地址。但是这样会有很多的不足:

直接访问物理内存不安全。例如我们假如使用了野指针,对内存中的数据进行了修改,那么这个时就会影响到其他的进程;
因为物理内存是有限的,当有多个进程要执行的时候,对每个进程都要分配4G内存,很显然你内存若小一点,这很快就分配完了,于是没有得到分配资源的进程就只能等待。当一个进程执行完后,再将等待的进程装入内存。这种频繁的装入内存的操作是很没效率的。
因为内存是随机分配的,所以程序运行的地址也是不正确的。例如下面的代码就会出现野指针的问题

linux进程(进程地址空间)_第5张图片

4.2进程地址空间到物理内存的映射

为了解决各种问题,大佬们提出了 虚拟地址空间 这个概念,有了 虚拟空间 后,当进程创建时,系统会为其分配属于自己的 虚拟空间需要使用内存时,通过 寻址 的方式,使用物理地址上的空间即可。如下图所示:

linux进程(进程地址空间)_第6张图片

 当有多个进程,具体情况就如下图所示:

linux进程(进程地址空间)_第7张图片

        地址空间和页表是每个进程都独有的一份,只要保证每一个进程的页表,能够映射到不同区域的物理内存,就能够做到进程之间互不干扰。这就是我们所说的进程所具有独立性。

          映射是由谁来完成的呢?答案是操作系统!操作系统通过地址转换机制将虚拟地址映射到物理地址,以实现对内存的访问。这种映射通常在页表或段表等数据结构上实现,其中存储了虚拟地址与物理地址之间的映射关系。 

4.3解释同一变量产生不同值

 相同的地址打印出不同的数据。注意,我们所访问到的地址都是虚拟地址。并不是物理地址。我们创建了一个子进程,子进程本身是继承了父进程的数据和代码。在没有对数据进行修改之前,子进程和父进程共享了一份数据。一但对子进程或者父进程的数据进行修改,就会发生写时拷贝。对修改的数据进行深拷贝,从而达到对彼此不产生干扰,实现进程独立性。

  那就对相同地址打印出不同数据的现象不难理解了。当我们对父进程的数据进行修改时,父进程发生了写时拷贝,在内存中开辟了空间。但他们都有自己的地址空间(虚拟地址),所以地址相同也是正常现象(子进程继承父进程的代码和数据)。即使虚拟地址一样,但是可通过页表映射到不同的物理内存中。具体如下图:

linux进程(进程地址空间)_第8张图片

5虚拟地址空间的意义 

5.1保护物理内存 

 

可能还有一些疑惑:即使有了虚拟地址,那我要是对野指针进行了访问修改,页表对野指针映射后,还不是对物理内存进行了非法的访问修改吗?地址空间的设置不就多此一举了吗?

  事实并非上述一样。凡是非法的访问或者映射,操作系统都会识别到的。一但你进行了非法的访问或者映射,操作系统就会终止掉你的程序。举个例子,当我们对野指针进行访问修改时,你的程序就会崩溃,这不就是程序终止退出吗!!!

  地址空间有效的保护了物理内存。因为地址空间和页表是操作系统创建并且维护的。这也就意味着地址空间和页表进行映射时需要操作系统进行监管!

5.2进程管理和内存管理的解耦

 

因为有了地址空间和页表,所以我们的数据可以在物理内存中的任何合法位置加载。因为他们之间有映射。物理内存的分配和进程的管理可以做到没有关系!

  所以进程模块和内存模块只需要各自完成各自的事情,最后通过页表的映射将他们连接起来,产生关系。降低了他们之间互相的影响度。

linux进程(进程地址空间)_第9张图片

5.3方便管理 

都可以从ox0000开始,同喜视角看待内存结构,让无序的物理地址有序化方便管理。

6总结 

 

我们平常所访问到的地址均为虚拟地址。地址空间并不是物理地址,而是虚拟地址。通过页表映射访问物理地址。 每个进程都有自己的地址空间和页表。

  页表是一种数据结构,它存储了虚拟地址与物理地址之间的映射关系。在进行地址转换时,操作系统根据进程的页表查找对应的物理地址,然后将虚拟地址转换为物理地址,以便进行实际的内存访问。

  通过使用虚拟地址,操作系统可以为每个进程提供独立的地址空间,使得多个进程可以并发运行,彼此之间相互隔离,互不干扰。虚拟地址还提供了更高的灵活性和保护性,使得操作系统可以有效地管理和分配内存资源,提高系统的性能和安全性。

你可能感兴趣的:(linux,linux,算法,运维)