进程的虚拟内存

进程的虚拟内存

进程属性信息的task_struct结构体,其中包含进程使用的内存信息。在32位的操作系统中,当进程创建的时候(程序运行时),系统会为每一个进程分配大小为4GB的虚拟内存空间,用于存储进程属性信息。

C语言中的变量,通常使用&运算符来获得其地址,那么,这个地址就是虚拟地址,在简单的单片机中,编写的代码编译时都需要指定物理RAM空间分布,不会有虚拟地址的概念,地址就是指在RAM中的实际物理地址

为什么需要虚拟空间机制

首先程序代码和数据必须驻留在内存中才能得以运行,然而系统内存大小是有限的,有时可能不能容纳一个完整的程序的所有代码和数据,更何况在多任务系统中,可能同时要打开子处理程序、浏览器等多种任务,想让内存驻留所有的这些程序显然不太可能。因此,首先能想到的是将程序分割成小份,只让当前系统运行它的所有需要的那部分留在内存,其他部分都留在硬盘。当系统处理完当前任务片段后,再从外存中调入下一个待运行的任务片段。老式系统就是这样处理大任务的,而且这个工作是由开发者自行完成。然而随着程序越来越丰富,由于程序的行为几乎准确预测,因此很难再靠预见性来静态分配固定大小的内存,然后再机械地轮换程序片进入内存执行。系统必须采用一种能按需分配的新技术

这种按需分配的技术就是虚拟内存机制。之所以称之为虚拟内存,说明内存只是逻辑上存在的,并非真实的物理内存,而且进程的分配的虚拟内存空间可能比实际使用物理内存要大很多。程序最终的执行,也是由CPU操作物理内存完成的。因此,虚拟内存需要与实际的物理内存建立起一定的联系,从而对于进程来说,保证访问的虚拟内存空间是有意义的,而不是访问了一个假设的地址值

物理内存与虚拟内存建立联系通过地址映射得来。所谓映射,就是一个地址转换的过程,通俗地讲,就是让虚拟地址与物理地址建立一一对应的关系。一旦这种关系建立,进程只需操作虚拟地址即可,然后通过查找这一虚拟地址与实际地址建立的关系,即可实现对实际地址的使用。当进程退出不需要内存资源释放时,将这一对应关系断开即可,此时虚拟地址就毫无意义,因为它没有和任何物理地址有关系

系统虽然为每一个进程分配了4GB的虚拟内存空间,但实际情况是进程按照当前运行对内存的需求,通过与实际的物理内存建立映射关系,获取分配的内存资源。在这一过程中,所需的地址在其生命周期中可以发生变化。同时,虚拟内存使得进程认为它的拥有连续可用的内存(一段连续完整的内存);但实际上,它通常是映射的多个不连续的物理内存分段来的

虚拟内存管理:如何实现虚拟地址与实际地址的映射

物理地址与虚拟地址建立关系,进程通过操作虚拟地址,而得到与之建立关系的实际物理地址的使用。这种地址关系建立,是通过页映射表现的

虚拟内存的规划之一是将每个程序使用的内存切割成小型的、固定大小的“页”单位(一般页面的大小为4096字节=4kb)。相应地,将RAM划分成一系列与虚拟页尺寸相同的页帧。内核需要为每一个进程维护一张页映射表。该页映射表中的每个条目指出一个虚拟“页”在RAM中的所在位置,在进程虚拟地址空间中,并非所有的地址范围都需要页表条目。由于可能存在大段的虚拟地址空间并未投入使用,故而也没有必要为其维护相应的页表条目

虚拟内存管理使进程的虚拟地址空间与RAM物理地址空间隔离开来,有优点如下

  1. 进程与进程、进程与内核相互隔离,一个进程不能读取或修改另一个进程或内核的内存,这是因为每个进程的页表条目指向RAM中的截然不同的物理页面集合
  2. 适当情况下,两个或者多个进程能够共享内存。这是因为内核可以使不同进程的页表条目指向相同的RAM页
  3. 便于实现内存保护机制:也就是说,可以对页表条目进行标记,以表示相关页面的内容是可读、可写、可执行抑或是这些保护措施的组合。多个进程共享RAM页时,允许每个进程对内存采取不同的保护措施。例如,一个进程可能以只读方式访问某RAM页,而另一个进程则以读写方式访问该页
  4. 程序员和编译器、链接器之类的工具无须关注程序在RAM中的物理布局
  5. 因为需要驻留在内存中的仅是程序的一部分,所以程序的加载和运行都很快。而且,一个进程所占用的虚拟内存大小能够超出RAM容量

进程的内存布局

Linux操作系统采用的虚拟内存管理技术,该虚拟内存空间大小为4GB的线性虚拟空间,进程只管自己的访问虚拟地址,无须知道物理地址的映射情况。利用这种虚拟地址不但更安全(用户不能直接访问物理内存),而且用户程序可以使用比实际物理内存更大的地址空间

4GB的进程地址空间会被分成两个部分--用户空间与内核空间。用户地址空间是03GB(0XC00000000),内核地址空间占据34GB。通常情况下,用户只能访问用户的虚拟地址,不能访问内核空间虚拟地址。只有用户进程使用系统调用(代表用户进程在内核态执行)时才可以访问内核空间。当进程切换时,用户空间就会跟发生变化;而内核空间是由内核负责映射的,是固定的,它并不会随着进程改变。内核空间地址有自己对应的页表,用户进程各自由不同的表,每个进程的用户空间都是完全独立、互不相干

用户空间包括以下几个功能区域

  1. 程序代码段:源代码,具有只读属性,包含程序代码(.init和.text)和只读数据(.rodata)
  2. 数据段:存放的是全局变量和静态变量。其中初始化数据段(.data)【静态数据区】存放显示初始化的全局(Global)变量和静态(Static)变量;未初始化数据段,此段通常也被称为BSS段(.bss),存放未进行显示初始化的全局变量和静态变量
  3. 栈:系统管理,由系统自动分配释放,存放函数的参数值、局部变量的值、返回地址等
  4. 堆:用户管理,存放动态分配的数据,一般由程序动态分配和释放,若程序不释放,程序结束时可能由操作系统回收。例如:使用malloc()函数申请空间
  5. 共享库的内存映射区域:这是Linux动态链接器和其他共享库代码的映射区域
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章