进程的内存布局
内存地址由高到低依次是:
- kernel space
- stack:向下增长
- dynamic libraries:共享库载入的空间
- heap:向上增长
- read/write sections(.data .bss):属于可执行文件映像
- readonly sections(.init .rodata .text):属于可执行文件映像
- reserved:内存中受到保护而禁止访问的内存区域
动态库和栈之间,动态库和堆之间仍有可使用的空间,因此栈和堆才能增长。此外,其他区域之间也并不是一定是连续的。
段错误(segment default):非法指针解引用所引起的错误。例如,指针指向不允许读写的地址,或者指针指向的地址并没有映射到实际的物理内存。
多线程下的内存布局
上面所说的内存布局是《程序员的自我修养》上讲的,在《Linux环境编程》上提到了多线程进程的内存布局:
mmap区域指的是执行系统调用mmap()所分配出来的区域,这块区域又称为文件映射区。mmap()的作用是向操作系统申请一块虚拟地址空间。可以指定某个文件来填充这块空间(文件映射),也可以不指定(分配匿名空间)。
mmap()调用后,仅仅是消耗了虚拟地址空间,创建/更改了内存映射所需的数据结构(页表),并没有映射到物理页。当进程后续访问这些虚拟地址的时候,会发现这些虚拟内存页没有对应的物理页,然后发生页错误(Page Default)。在处理页错误的过程中才分配物理页(如果调用mmap时指定了文件,就把磁盘中的文件拷贝到物理页中),并建立映射。此时分配的虚拟内存才变得真正可用。
页错误也是缺页中断。除了第一次访问mmap分配的页(因为没有映射到物理页)会引发页错误外,后续访问时,如果物理页已经换出到外存,也会引发页错误。
glibc中malloc的实现
在标准C库中,提供了malloc函数来分配一块虚拟内存空间。
glibc下malloc获取的空间的来源有两个:brk系统调用,mmap系统调用。
(有人把malloc管理的空间统称为堆,也有人只把brk系统调用分配的空间称之为堆,下面说的都取前者的含义)
- brk的作用是设置进程数据段的结束地址。如果把数据段的结束地址向高地址移动,那么扩大的那部分空间就可以拿来作为堆空间使用。
- mmap是在进程的虚拟地址空间中(brk分配的空间和栈之间的一块内存,称为文件映射区)分配一块空闲的虚拟内存。
对于小于128KB的请求,malloc会在现有的堆空间里面,按照堆分配算法分配一块空间并返回;对于大于128KB的请求,malloc会使用mmap系统调用分配一块匿名空间,然后在这块匿名空间中为用户分配空间。
这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。