虚拟地址空间

        大多数计算机使用8位的块,或者叫做字节(Byte),来作为最小的可寻址的存储器单元,而不是访问存储器中单独的位。 机器级程序将存储器视为一个非常大的字节数组,称为虚拟存储器。存储器的一个字节都由唯一的数字来标识,称为它的地址(虚拟地址)。所有可能虚拟地址的集合就称作虚拟地址空间。

                                                                                                ——引自《深入理解计算机系统》P23

  • 基于32位的操作系统,其虚拟地址空间是2^32 即4G,因为32位操作系统的地址总线(用来寻址)有32条 。
  • 程序运行在虚拟地址空间,但虚拟地址空间其实并不存在,在他背后有着真实的内存与之相对应。进程启动后,才会有虚拟地址空间,通过虚拟地址映射将信息存到内存中。内存分为物理内存和虚拟内存,当物理内存存不下的时候将他替换到虚拟内存中(磁盘),需要时再替换回来。

以下是Linux32位操作系统,4G虚拟地址空间分布图:

容器虚拟地址_虚拟地址

  • 当一个进程启动后,就会产生虚拟地址空间,包括3G的用户空间和1G的内核空间。用户空间所有进程自身独有,而内核空间所有进程共享。在将应用程序加载到内存空间执行时,操作系统负责代码段、数据段和BSS段的加载,并在内存中为这些段分配空间。栈也由操作系统分配和管理;堆由程序员自己管理,即显式地申请和释放空间。
  • BSS段、数据段和代码段是可执行程序编译时的分段,运行时还需要栈和堆。
  • 用户进程可执行程序一般从虚拟地址空间0x0804 8000开始加载。该加载地址由ELF文件头决定。

以下详细的介绍各个分段的含义(按地址递增顺序)

1、保留区(低地址,128M,禁止访问)

      位于虚拟地址空间的最低部分,未赋予物理地址。任何对它的引用都是非法的,用于捕捉使用空指针和小整型值指针引用内存的异常情况。它并不是一个单一的内存区域,而是对地址空间中受到操作系统保护而禁止用户进程访问的地址区域的总称。大多数操作系统中,极小的地址通常都是不允许访问的,如NULL。C语言将无效指针赋值为0也是出于这种考虑,因为0地址上正常情况下不会存放有效的可访问数据。

2、.text段:指令段/代码段,数据以外都是指令,可执行代码、字符串字面值、只读变量

3、.data段:数据段,存储初始化且初始化不为0的全局变量和静态局部变量。注意:在 data 段上面有一个 rodata 段(rodata 段的位置在.o 文件时去观察它是在.bss 段的下方),它的属性是只可读。所以当我们这样定义字符串时:char *p = “helloworld”; *p = ‘a’;会报错的原因就是这里的”hello world”在 rodata 段存放,它只可读,不可修改。

4、.bss段:数据段,存储未初始化或者初始化为0的全局变量和静态局部变量。

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

6、共享库的存储映射区 lib.so

7、栈区:从高地址到低地址,存储局部变量、函数参数、返回地址等。在运行时负责函数的形参,局部变量的管理,主要由esp和ebp寄存器记录当前栈顶和当前栈低的函数调用管理。栈的起始地址和堆的起始地址不固定:防止栈溢出或者是程序的攻击。

8、命令行参数。main()函数的参数列表 :argc参数个数 argv参数内容 envp环境变量  int main(int argc,char * argv[],char * envp[])

例如:./run "hellp" "world"

9、环境变量envp

10、内核空间: 内核总是驻留在内存中,是操作系统的一部分。内核空间为内核保留,不允许应用程序读写该区域的内容或直接调用内核代码定义的函数。

ZONE_DMA:可直接访问内存(加快磁盘内存数据的读写)

ZONE_NORMAL

ZONE_HIGHMEN:高端内存(在内存中映射高于1G的物理内存)