0 概述
进程是操作系统进行资源分配的最小单位,而内存是进程运行必不可少的资源。
现代操作系统为每个进程分配独享的内存空间,这个独享的内存空间只是虚拟内存空间。
每次访问内存空间的某个地址(虚拟地址),都需要把地址翻译成实际物理内存地址。
进程要知道哪些虚拟地址上的数据在物理内存上,哪些不在;还有存放在物理内存上的位置,需要用页表来记录。
正因为每个进程都有一个自己的页表,使得相同的虚拟地址映射到不同的物理内存。每当切换到另一个进程时,就要通过设置MMU的某些寄存器来设置这个进程的页表,然后MMU就可以把CPU发出的虚拟地址转化到物理地址了。
1 虚拟内存地址空间布局介绍
32位模式下它是一个4GB的内存地址块
- 每个进程看到的地址空间都是一样的
- .text 都是从 0x80048000开始
- 内核地址空间都是 0xC0000000 ~ 0xFFFFFFFF
- 用户栈都是向低地址增长
- 堆都是向高地址扩展
1.1 0x00000000 ~ 0x80048000
不能给用户访问,这里面是一些C运行库的内容,访问会报 segment fault 错误。
1.2 0xC0000000 ~ 0xFFFFFFFF
内核的逻辑地址,在用户态访问会出错,权限不够;如果向访问,需要切换到内核态,可以通过系统调用等方式;系统调用代表某个进程运行于内核,此时,相当于该进程可以访问这段内存虚拟地址(实际上只能访问该进程的某个8KB的内核栈)。
1.3 Random stack offset、Random mmap offset、Random brk offset
这些随机值意在防止恶意程序.这是一种安全机制ASLR(Address Space Layout Randomization),主要防止缓冲区溢出攻击。
Linux通过对栈、内存映射段、堆的起始地址加上随机偏移量来打乱布局,以免恶意程序通过计算获取访问栈、库函数等地址。
1.4 Stack
内核空间下面就是用户栈 Stack 地址段,栈的最大范围可以通过 prlimit 命令看到,默认情况下是8MB。
1.5 Memory Mapping Segment
这块地址是用来分配内存区域的,一般是用来把文件映射进内存用的,但是你也可以在这里申请内存空间来使用。
- mmap()系统调用把一个文件映射到 Memory Mapping Segment 内存地址空间中
- 也可以匿名直接申请一段内存空间使用,mmap()不一定要在 Memory Mapping Segment
进行申请,你也可以指定任意的内存地址,当然只要不跟已有的虚拟地址冲突就好;这个地址也一定要是000结尾,才能使得页对齐(1KB)。
1.6 start_brk和brk(program break)
分别标识了堆的起始地址和结束地址。
在Linux中可以通过 brk()和 sbrk()这两个函数来改变 program break 的位置。
- 当我们在程序中调用 malloc()的时候,一般就是在内部调用 sbrk()来调整途中 brk 标识的位置向上移动。
- 当调用 free()来释放内存空间的时候,传递给 sbrk()一个负值来使堆的 brk 标识向下移动。当然, brk()和
sbrk()所做的工作远不是简单地移动 brk 标识,还要处理将虚拟内存映射到物理内存地址等工作。 - glibc 中当申请的内存空间不大于 MMAP_THRESHOLD的时候,malloc()使用 brk()/ sbrk()来调整 brk
标识的位置,这个时候所申请到的空间确实位于图中的 start_brk 和 brk 之间;当所申请的空间大于这个阈值的时候,
malloc()改用 mmap()来分配空间,这个时候所申请到的空间就位于图中的 Memory Mapping Segment 这一段内。
1.7 BSS Segment、Data Segment、Text Segment
- BSS Segment存放未初始化的静态变量,所以也就是可以随意读写;
- Text Segment 其实就是存放二进制可执行代码的位置,所以它的权限是读与可执行;
- Data Segment 存放的是静态常量,所以该地址段权限是只读。
新的进程内存布局(默认进程内存布局)导致了栈空间的固定,而堆区域和MMAP区域共用一个空间,这在很大程度上增长了堆区域的大小。
2 存放内容说明
2.1 内存空间划分
首先内存空间分为用户内存空间 和 内核内存空间;
用户内存空间也称为进程的地址空间,即对于每个用户进程可见;
用户内存空间由低地址到高地址依次为: 代码段、数据段、bbs段、内存堆区、内存映射段、内存栈区。
2.2 内核空间(Kernel space)
- 内核空间大小1G,专门给操作系统使用,用户没有操作权限。
- 内核空间通过对内存化分不同的区域来管理内存;
- 通常划分的区域为ZONE_DMA,ZONE_NORMAL,ZONE_HIGHEM,在每个区域中又通过slab层来管理内存;
- 内核管理内存的最小单位是物理页,通常在32位体系结构中支持4kb的物理页,内核直接管理物理页;
- 内核管理的内存是不会出现换出的情况的。
2.3 栈区(Stack)
存放内容:函数体的局部变量、函数调用期间的所有参数压栈、函数的返回值;
说明:栈区内存由操作系统维护,函数结束,在栈上的空间会由操作系统自己回收。
2.4 内存映射段(Memory Mapping Segment)
存放内容:动态库/静态库、文件映射、匿名映射;
说明:一切有依赖的东西都在这段区域。
2.5 堆区(Heap)
存放内容:进程运行中被动态分配的内存段,大小并不固定,malloc/calloc/realloc/new 申请堆上的空间;
说明:需要手动释放,不然会造成内存泄漏。
2.6 bss段(BSS Segment)
存放内容:未初始化的全局变量、未初始化的静态数据;
说明:bss段属于数据段
区分:
- bss段存放的数据未初始化,数据段存放的数据已初始化;
- bss段的数据未被划分空间,数据段的数据已被划分空间。
2.7 数据段(Data Segment)
存放内容:全局变量、静态类型的变量;
说明:
- 当代码编译完成后,在可执行程序这个文件中已经把这些数据的空间划分完毕;
- 在程序运行以前,操作系统就已将数据段中的数据加载到内存;
- 也就是说在进入main函数之前,该部分数据已划分好空间。
2.8 代码段(Text Segment ELF)
存放内容:可执行代码、只读常量(字符串常量等);
说明:这段内存是只读的。