Linux 进程地址空间

Source

---引言----

在学习 C/C++ 的时候,我们知道内存会分为几个区域:栈区、堆区、全局/静态区、代码区、字符常量区 … 

如下图


但是如果操作系统不是这样分配的呢(没错,我就是传说的中的杠精),那我们通过程序验证一下不就知道了. 

代码样例:

结果:

结论:嗯,在Linux下,我们学习过的内存分布是正确的;但是我们结合下图来看一个有趣的现象。

这段代码很明显是利用了fork,创建子进程修改了子父进程的一个变量;

按照我们在C/C++学习当中预期的结果应该是子进程修改成功,但是父进程的值不变;

好,那么观察代码运行结果可以发现值的确变了,但为啥,两个进程的count变量对应的地址却是相同的呢?难道说其实一个地址可以对应两个值吗?根据我们C/C++的知识明显推断出这个结论很明显是错误的。

那换一个角度想,一个地址既然不能对应两个值,那有没有可能是地址是假的。

恭喜你小伙砸,你猜对了,这就是今天要讨论的问题------进程地址空间(也称作:虚拟空间)



一. 进程地址空间是什么

  a.   定义:
  1.   进程地址空间是操作系统为每个进程分配的一块独立的虚拟地址范围,用于存储程序代码、数据和栈等运行所需的内容。(它是一个抽象的概念,用于操作系统描述进程如何看待内存。)

{

重点是虚拟地址,它为进程访问物理地址加上一道锁;让进程无法直接访问到物理地址,也让操作系统有机会控制进程访问地址空间。

}

 b.  特点:虚拟化、独立性和统一性。
         1.每个进程的地址空间是独立的虚拟地址,互不干扰;

  独立性是进程最重要的性质之一,进程地址空间保障了进程在存储上的独立。


        2. 一个进程不能直接访问另一个进程的地址空间,提供了安全性;    
         3.操作系统通过页表(下面有专门的讲解)将虚拟地址映射到物理地址,所有的进程都是如此。


二. 进程地址空间的结构

   - 代码段:存储可执行代码的指令,只读,通常不可修改。
   
   - 常量(数据)区:存储已初始化的全局变量和静态变量,具有读写权限。
   
   - BSS段:存储未初始化的全局变量和静态变量,初始值默认为0,占用物理内存时才分配。
   
   - 堆(Heap):动态分配的内存区域,如`malloc`分配的内存,向高地址增长。
   
   - 栈(Stack):存储函数调用相关的局部变量、返回地址等,向低地址增长。
   
   - 内核空间:操作系统内核相关的代码和数据,用户态无法直接访问。


     代码示例:如何通过代码获取不同段(代码段、全局变量、未初始化变量、局部变量、堆变量)的地址空间位置。
       代码可以直接看引言的;

{

建议:内存分布无论是在C/C++,还是Linux当中都是相当重要的一部分,同时公司的面试笔试都喜欢考察,建议对内存分布不清楚的同学,可以去复习一下;

}


三.操作系统如何实现进程地址空间

   - 虚拟内存与地址映射:Linux使用虚拟内存技术(主要是利用了页表),将进程的虚拟地址空间映射到物理内存。内核通过页表实现虚拟地址到物理地址的映射。

操作系统正是通过中间的页表结构来修改变量的值

这也解释了为啥在引言的代码中,相同地址的变量为啥会有不同的值,原因正是:我们在子进程修改了变量的值,但是在操作系统当中,它仅仅只是修改了对应变量的映射关系(******),所以才有我们一开始的现象.

3.1补充一下:页表
{
  由于页表也属于是一个较为复杂的数据结构,在此仅是对其的简单介绍,如有感兴趣的同学可以自行查阅相关资料;

简单的说,页表就是一个存储物理地址的表(类似与哈希表的K---Val结构),我们现在知道了,进程使用的都是虚拟内存CPU 在取指令或者取数据的时候使用的是虚拟地址,为了能够从内存中取得数据,需要将虚拟地址转换为物理地址,而虚拟地址和物理地址之间的映射关系就保存在页表中。

在页表当中,操作系统会在其中存储各种与进程存储相关的数据。
}

3.2 缺页中断

{

  在谈论这个话题前,我们应该知晓一个共识,操作系统为了减少进程之间的开销,当父进程创建子进程的时候,子进程会继承了父进程的代码与数据,换句话说,子进程与父进程一开始在页表的映射关系是一样的;但是如果像引言一样,子进程或父进程改变数据了,怎么办???

此时,操作系统会以一种类似计数排序的方式对进程进行修改,会将父子进程的修改数据重新在页表当中生成一份映射关系,这个过程就叫做缺页中断。

}


四.为什么要存在地址空间

a.保护物理内存:如果进程直接访问物理内存,可能会导致进程间的相互干扰和恶意程序的攻击。虚拟地址的存在可以保护物理内存,防止直接访问,从而增强安全性。

解释1:

{

  如果不存在地址空间,那么每个进程都可以访问到物理内存

    那不是很可怕吗?随意的一个进程就可以改变物理内存上的数据,那别有用心的人不就可以对计算机进行修改吗?

    所以要存在进程地址空间,隔开进程与物理地址空间的关系,把管理权交于操作系统,保护计算机;

}

b.解耦内存管理和进程管理:虚拟地址空间使得内存管理和进程管理可以独立进行,提高了系统的灵活性和效率。

解释2:

   操作系统可以通过mm_struct与task_struct去管理内存与进程,实现进程管理与内存管理的分离解耦

}

c.统一访问方式:虚拟地址空间让每个进程以相同的方式看待代码和数据,简化了编程和系统设计。

解释3:

{

   进程地址空间为程序员提供方便,在编写程序的时候不需要关系进程的物理内存的分布;

每个进程都可以用相同的方式访问自己的内存;

}



五 总结
 进程地址空间是操作系统管理内存的核心概念,通过将地址空间划分为代码段、数据段、堆、栈等区域,提供了独立的运行环境。Linux通过虚拟内存技术(页表)实现了地址空间的隔离和映射。理解进程地址空间对操作系统学习、程序优化、内存调试等实际问题的解决具有重要意义。


结语:本人第一次画图,画的不好请见谅