【C++】虚拟地址空间

一、引言:
在计算机中,每台设备以及进程都被分配一个地址空间。地址空间包括物理空间以及虚拟空间。如果将物理地址暴露给进程,用户就很容易破坏操作系统,从而使系统停止;另外随着进程数量和体积的增长,内核空间变得越来越不够用,从而引入虚拟地址空间。


二、物理地址与虚拟地址的辨析:
物理地址(physical address):放在寻址总线上的地址。物理内存是以字节为单位编址的。
虚拟地址(virtual address):CPU启动保护模式后,程序运行在虚拟地址空间。CPU在启动时是运行在实模式的,内核在初始化页表之前并不是使用虚拟地址,而是直接使用物理地址。


三、虚拟地址空间展示
虚拟地址空间(Virtual Address Space),在32位模式下它是一个4GB的内存地址块。在Linux系统中, 内核进程和用户进程所占的虚拟内存比例是1:3,而Windows系统为2:2(通过设置Large-Address-Aware Executables标志也可为1:3)。这并不意味着内核使用那么多物理内存,仅表示它可支配这部分地址空间,根据需要将其映射到物理内存。
虚拟地址通过页表(Page Table)映射到物理内存,页表由操作系统维护并被处理器引用。内核空间在页表中拥有较高特权级,因此用户态程序试图访问这些页时会导致一个页错误(page fault)。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存。内核代码和数据总是可寻址,随时准备处理中断和系统调用。与此相反,用户模式地址空间的映射随进程切换的发生而不断变化。

图1 虚拟地址空间分布图

用户进程部分分段存储内容:

名称存储内容
局部变量、函数参数、返回地址等
动态分配的内存
bss段未初始化或初值为0的全局变量和静态局部变量
数据段已初始化且初值非0的全局变量和静态局部变量
代码段可执行代码、字符串字面值、只读变量

在将应用进程加载到内存空间执行时,操作系统负责代码段、数据段和bss段的加载,并在内存中为这些段分配空间。栈也由操作系统分配和管理;堆由程序员自己管理,即显式地申请和释放空间。
bss段、数据段和代码段是可执行程序编译时的分段,运行时还需要栈和堆。

  • 内核空间
    内核驻留在内存中,操作系统和驱动程序运行在内核空间。内核空间为内核保留,不允许应用程序读写该区域的内容或直接调用内核代码定义函数。

  • 环境变量和命令行参数
    是一组字符串,环境变量是操作系统传递给进程的一组字符串信息,命令行参数列表由char**argv指向,字符串个数由int argc指明,它们就是main函数的两个参数,还有一个全局变量char* envp,它指向环境表(环境字符串的集合)并以NULL空串结尾,只是我们习惯省略这些,所以我们的常写的main函数是main()。即: int main(int argc,charargv[],char*envp[])

  • 栈(stack)
    栈又称堆栈,由编译器自动分配释放,用来存储临时数据和栈帧。(特点:先进后出(first in last out,即FILO))
    作用:

  • 为函数内部声明的非静态局部变量提供存储空间

  • 记录函数调用过程相关的维护信息,称为栈帧(Stack Frame)或过程活动记录(Procedure Activation Record)。它包括函数返回地址,不适合装入寄存器的函数参数及一些寄存器值得保存。除递归调用外,栈并非必要。因为编译时可获知局部变量,参数和返回地址所需要空间,并将其分配于bss段

  • 临时存储区,用于暂存长算术表达式部分结果或alloca()函数分配得栈内内存

持续地重用栈空间有助于时活跃的栈内存保持在CPU缓存中,从而加速访问。进程中的每个线程都有属于自己的栈。向栈中不断压入数据时,若超出其容量就会出现栈溢出状况,从而触发一个页错误。此时若栈的大小低于堆栈最大值RLIMIT_STACK(通常是8M),则栈会动态增长,程序继续运行。映射的栈区扩展到所需大小后,不再收缩。
堆栈的大小在运行时由内核动态调整。

  1. 共享库
    在编写程序时,会依靠其他人已经写好的许多代码来执行例程或特殊功能。 这些代码存储在共享库中使用它们,需要将它们与自己的代码相链接,无论是在构建程序时还是在运行程序时。
  2. 堆(Heap)
    堆用于存放进程运行时动态分配的内存段,由程序员手动完成申请和释放的。 堆中内容都是匿名的,不能通过名字直接访问,只能通过指针间接访问。如果用malloc申请空间,就用free释放,如果用new申请,就用delete释放。
    分配的堆内存时经过字节对齐的空间,以适合原子操作。堆管理器通过链表管理每个申请的内存,由于堆申请和释放是无序的,最终会产生内存碎片。堆内存一般由应用程序分配和释放,回收的内存可供重新使用。若程序员不释放,程序结束时操作系统可能会自动回收。
    堆的末端由break指针标识,当堆管理器需要更多内存时,可通过系统调用brk()和sbrk()来移动break指针以扩张堆,一般由系统自动调用。
    使用堆内存要注意两个问题:
    1)内存破坏。(释放或改写仍在使用的内存)
    2)内存泄漏。(未释放不再使用的内存)
    【注意】此处堆不同于数据结构中的“堆”
  3. bss
    数据段:静态内存分配,保存未初始化的全局及静态变量(皆为0),可读可写。

【注意】尽管均放置于BSS段,但初值为0的全局变量是强符号,而未初始化的全局变量是弱符号。若其他地方已定义同名的强符号(初值可能非0),则弱符号与之链接时不会引起重定义错误,但运行时的初值可能并非期望值(会被强符号覆盖)。因此,定义全局变量时,若只有本文件使用,则尽量使用static关键字修饰;否则需要为全局变量定义赋初值(哪怕0值),保证该变量为强符号,以便链接时发现变量名冲突,而不是被未知值覆盖。

  1. data
    数据段:静态内存分配,保存已初始化且不为0的全局及静态变量,可读可写。

数据段和bss段的区别:
1)bss段不占用物理文件尺寸,但占用内存空间;数据段占用物理文件,也占用内存空间。
对于大型数组如int ar0[10000] = {1, 2, 3, …}和int ar1[10000],ar1放在BSS段,只记录共有10000*4个字节需要初始化为0,而不是像ar0那样记录每个数据1、2、3…,此时BSS为目标文件所节省的磁盘空间相当可观。
2)当程序读取数据段的数据时,系统会触发缺页故障,从而分配相应的物理内存;当程序读取bss段数据时,内核会将其转到一个全零页面,不会发生缺页故障,也不会为其分配相应的物理内存。
运行时数据段和bss段整个区段统称为数据区。

  1. text
    代码段:保存可执行机器码(即程序执行代码)和常量(如字符串常量),可读不可写可执行,指令指针EIP就是指向代码段。通常代码段是可共享的,因此频繁执行的程序只需要在内存中拥有一份拷贝即可。
    代码段指令根据程序设计流程依次执行,对于顺序指令,只会执行一次(每个进程);若有反复,则需要使用挑转指令;若进行递归,则需要借助栈实现。
    代码段指令中包括操作码和操作对象(或对象地址引用)。若操作对象是具体数值,将直接包含在代码中;若是局部数据,将在栈区分配空间,然后引用该数据地址;若位于bss和数据段,同样引用该数据地址。
  2. 保留区
    位于虚拟地址空间的最低部分,未赋予物理地址。任何对它的引用都是非法的,用于捕捉使用空指针和小整型值指针引用内存的异常情况。
    它并不是一个单一的内存区域,而是对地址空间中受到操作系统保护而禁止用户进程访问的地址区域的总称。

内存分段的好处:
进程运行过程中,代码指令根据流程依次执行,只需要访问一次(跳转和递归可能使代码执行多次);数据(数据段和bss段)通常需要访问多次,因此单独开辟空间以便访问和节约空间。
解释:
1)当程序被装载后,数据和指令分别映射到两个虚拟区域。数据区对于进程而言可读写,而指令区对于进程只读。两区的权限可分别设置为可读写和只读。以防止程序指令被有意或无意地改写。
2)现代CPU具有极强的缓存体系,程序必须尽量提高缓存命中率。指令区和数据区的分离有利于提高程序的局部性,故有利于提高CPU缓存命中率。
3)当系统运行多个该程序的副本时,其指令相同,故内存中必须保存一份该程序的指令部分。若系统中运行数百个进程,通过共享指令将节省大量空间(尤其是对于动态链接的系统)。其他只读数据如程序中的图标、图片、文本等资源也可共享。而每个副本进程的数据区域不同,它们是进程私有的。
4)临时数据及需要再次使用的代码在运行时放入栈区中,生命周期短。全局数据和静态数据可能在整个程序执行过程中都需要访问,因此单独存储管理。堆区由用户自由分配,以便管理


四、虚拟内存实现机制

图2 虚拟内存实现
8. 地址映射

可执行文件从磁盘映射到虚拟地址空间,
虚拟地址空间映射到物理地址空间

  • 请页

将可执行文件从磁盘调入物理内存

  • 交换机制

把内存的内容换到磁盘,把磁盘内容换到内存,需要用到文件系统

参考链接:https://www.cnblogs.com/clover-toeic/p/3754433.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值