C++:虚拟地址空间布局

  • Post author:
  • Post category:其他


对于每一个进程都会对应一个虚拟地址空间,对于32位的操作系统(其指令的位数最大为32位,因此地址码最多32位),虚拟地址空间的大小为2^{32}B即0~4GB的虚拟地址空间,其中内核空间为1GB,64位操作系统同理。

由于每个进程都不能直接访问内核空间,而是通过系统调用间接进入内核,因此,所有的进程都共享内核空间。而每个进程都拥有各自的用户空间,各个进程之间不能相互访问彼此的用户空间。因此,对每一个具体的进程而言,都拥有4GB的虚拟地址空间。

一个程序在经过编译、链接之后形成的地址空间是一个虚拟的地址空间,只有当程序运行的时候才会分配具体的物理空间。由此我们可以得知,程序的虚拟地址相对来时候是固定的,而物理地址则随着每一次程序的运行而有所不同。

对于内核空间而言,它与物理内存之间存在一个简单的线性关系,即存在3GB的偏移量。在Linux内核中,这个偏移量叫做PAGE_OFFSET。如果内核的某个物理地址为x,那么对应的内核虚拟地址就为x+PAGE_OFFSET。

对于用户空间而言,它与物理内存之间的映射远不止这么简单。与内核空间和物理空间的线性映射不同的是,分页机制将虚拟用户空间和物理地址空间分成大小相同的页,然后再通过页表将虚拟页和物理页块映射起来。


程序使用的地址空间要是连续的。


虚拟地址空间


操作系统给进程描述的一个虚假的地址空间,通过struct mm_struct结构体描述的虚拟地址空间。


使用虚拟地址空间的原因


若进程直接使用物理内存,会造成大量的内存碎片,导致资源利用率较低,并且缺乏访问的安全性。

注意:同一变量地址相同,其实是虚拟地址相同,内容不同是被映射到不同的物理地址上了。


虚拟地址空间的作用


1.方面编译器和操作系统安排程序的地址;

2.方便实现各个进程空间之间的隔离,互不干扰,因为每个进程都对应自己的虚拟地址空间;

3.实现虚拟存储,从逻辑上扩大了内存。


管理虚拟地址与物理地址的方法


分页&虚拟地址空间


虚拟地址空间映射到物理内存上的过程


首先我们使用一个虚拟地址向进程提供一个连续的完整的地址使用,通过页表映射到物理内存的各个区域,并且这些物理区域可以不用连续,实现了数据在物理内存上的离散式存储,提高了内存的利用率,并且虚拟地址空间可以对地址访问进行控制,提高访问安全性。

虚拟地址空间布局


1 保留区(受保护的地址)


保留区即为受保护的地址,大小为128M,位于虚拟地址空间的最低部分,未赋予物理地址(不会与内存地址相对应,因此其不会放任何内容)。任何对它的引用都是非法的,用于捕捉使用空指针和小整型值指针引用内存的异常情况。大多数操作系统中,极小的地址通常都是不允许访问的,如NULL。C语言将无效指针赋值为0也是出于这种考虑,因为0地址上正常情况下不会存放有效的可访问数据。将指针赋值为0,意味着该指针将永远不会被使用,从而不会出现野指针情况。#define NULL 0 与 #define NULL (void*)0 在C语言中是等效的,而在C++中,只能用#define NULL 0,后面 #define NULL (void*)0的使用会出错。


2 代码段


代码段也称正文段或文本段,通常用于存放程序执行代码(即CPU执行的机器指令)。一般C语言执行语句都编译成机器代码保存在代码段。通常代码段是可共享的,因此频繁执行的程序只需要在内存中拥有一份拷贝即可。代码段通常属于只读,以防止其他程序意外地修改其指令(对该段的写操作将导致段错误)。某些架构也允许代码段为可写,即允许修改程序。


3 数据段(.data段)


数据段通常用于存放程序中已初始化的全局变量和静态局部变量。数据段属于静态内存分配(静态存储区),可读可写。由于全局变量未初始化时,其默认值为0,因此值为0的全局变量位于.bbs段(不位于数据段)。对于未初始化的局部变量,其值是不可预测的。注意:在代码段和数据段之间还包括其它段:只读数据段和符号段等。


4 .bbs段


该段用于存放未初始化的全局变量和静态局部变量,包括值为0的全局变量。 数据段和.bbs段又称为全局数据区,前者初始化,后者未初始化。

ELF段包括:代码段、其它段(只读数据段和符号段等)、.data段(数据段)和.bbs段,都属于可执行程序部分。


5 堆空间


new( )和malloc( )函数分配的空间就属于对空间,用于内存空间的分配,其从下往上。 堆用于存放进程运行时动态分配的内存段,可动态扩张或缩减。堆中内容是匿名的,不能按名字直接访问,只能通过指针间接访问。当进程调用malloc© 和new (C++)等函数分配内存时,新分配的内存动态添加到堆上(扩张);当调用free©/delete(C++)等函数释放内存时,被释放的内存从堆中剔除(缩减) 。


6 内存映射段(共享库)


此处,内核将硬盘文件的内容直接映射到内存, 任何应用程序都可通过Linux的mmap()系统调用请求这种映射。内存映射是一种方便高效的文件I/O方式, 因而被用于装载动态共享库。如C标准库函数(fread、fwrite、fopen等)和Linux系统I/O函数,它们都是动态库函数,其中C标准库函数都被封装在了/lib/libc.so库文件中,都是二进制文件。这些动态库函数都是与位置无关的代码,即每次被加载进入内存映射区时的位置都是不一样的,因此使用的是其本身的逻辑地址,经过变换成线性地址(虚拟地址),然后再映射到内存。而静态库不一样,由于静态库被链接到可执行文件中,因此其位于代码段,每次在地址空间中的位置都是固定的。


7 栈空间


用于存放局部变量(非静态局部变量,C语言称为自动变量),分配存储空间时从上往下。栈和堆都是后进先出的数据结构。


8 命令行参数


该段用于存放命令行参数的内容:argc和argv。


9 环境变量


用于存放当前的环境变量,在Linux中用env命令可以查看其值。

代码段(.text段)与只读数据段和符号段(.rodata段),都属于只读部分,在链接的时候这两部分会链接成为一个整体;而.data段和.bbs段属于可读可写RW的部分。这四个部分都是以页(每页4KB)的形式存放在内存中。

进程控制块PCB(又叫进程描述符)放于内核空间。

多个进程在并发执行时,这些进程的用户空间都是彼此独立的,因此各个进程的用户空间在映射为内存空间使都是独立的,互不干扰,这是MMU地址变换必须要能够保证的。例如,各个进程的.text段、只读数据段和符号段、.data段和.bbs段等在用户空间中使用到的其它数据信息,都会与页为基本单位放在内存中,各个进程的映射是独立的。

而对于

内核空间

,由于只有一个操作系统,内核空间主要是 机器指令、操作系统内核的各个模块等,它们是公用的,因此每个进程的映射方式一样。

**ps:**每个进程用到或即将用到的数据才会调入内存,其余都在磁盘上。但是各个进程内核空间的进程控制块(进程描述符)映射的地点是不一样的,也是相互独立的。共用的模块才是一样的。 这些都是MMU的实现机制所决定的。

内核使用mm_struct来描述一个进程的虚拟地址空间:

struct mm_struct{
    struct vm_area_struct* mmap;  //指向虚拟区域(VMA)链表
    rb_root_t mm_rb;  //指向red_black树
    struct vm_area_struct* mmap_cache;  //指向最近找到的虚拟区间
    pgd_t* pgd;  //指向进程的页目录
    atomic_t mm_users;  //用户空间中的有多少用户
    atomic_t mm_count;  //对“struct mm_struct"有多少引用
    int map_count;  //虚拟区间的个数
    struct list_head mmlist;  //所有活动(active)mm的链表
    unsigned long start_code,end_code,start_data,end_data;  
    unsigned long start_brk,brk,start_stack;
    unsigned long arg_start,arg_end,env_start,env_end;
};

struct vm_area_struct:用来描述一个虚拟内存区域(VMA)。内核将每个内存区域作为一个单独的内存对象管理,每个内存对象管理,每个内存区域都有一致的属性,比如权限等。

我们程序的代码段、数据段和bss段在内核里都分别有一个struct vm_area_struct结构体来描述:

struct vm_area_struct{
    struct mm_struct* vm_mm;  //虚拟内存区域所在的地址空间
    unsigned long vm_start;  //在虚拟内存中的起始地址
    unsigned long vm_end;  //在虚拟内存中的结束地址
    struct vm_area_struct* vm_next;  
    pgprot_t vm_page_prot;  //对这个虚拟内存区域的存取权限
    unsigned long vm_flags;  //虚拟内存区域的标志
    rb_node_t vm_rb;  //对这个虚拟内存区域进行操作的函数
    struct vm_operations_struct* vm_ops;
    struct file* vm_file;
};


1、虚拟地址空间理解



2、虚拟地址空间



3、虚拟地址空间



版权声明:本文为veneno_one原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。