虚拟内存
由于内存使用的局部性原理,操作系统使用虚拟内存来隔离进程和物理内存,同时进程可以独占虚拟内存,操作系统管理着虚拟内存和物理内存的映射。
虚拟地址到物理内存的映射有MMU(内存管理单元)完成,虚拟空间中的每一页都有页表PTE与之对应,每页为4k,虚拟地址到物理地址的映射分为4步:
1、确定页目录基址
2、定位页目录项
3、定位页表项
4、根据页表项找到物理地址
当进程真正要使用内存的时候,会产生缺页中断,MMU负责把对应的物理内存换入虚拟地址空间中。
fork调用产生子进程时,子进程也需要复制父进程的PTE,这一步的时间对着PTE的增多也就是内存占用的增多而增大。
虚拟空间又分为内核空间和用户空间,内核空间在高地址,是所有进程共享。用户空间在低地址,进程独享;以32位系统为例,虚拟空间共4G,内核空间1G,用户空间3G。
进程内存空间布局
从低地址开始,分别是:
1、Text Segment: 代码段,存放编译好的二进制代码
2、Data Segment: 数据段,存放已经初始化的静态常量
3、BSS Segment: 存放未初始化的全局常量,在运行时会置0
4、Heap: 堆,动态内存分配区域
5、Memory Mapping Segment: 内存映射区,用来进行文件映射,以及动态库的加载
6、Stack:栈,进程函数调用
动态内存申请
动态内存从堆上申请,内核维护一个brk变量,指向堆的顶部,brk的位置就决定了堆的大小,Linux提供了两个系统调用来修改堆的大小:sbrk、mmap
sbrk: 直接改变brk的大小来申请和释放内存
mmap: 功能强大,不仅可以申请内存,还可以创建共享内存,创建文件映射
glibc中的malloc和free就是通过sbrk和mmap两个系统调用来申请和释放内存。
栈
栈是进程执行函数的地方,每次函数调用就会在栈上形成一个栈帧,当函数执行完后,栈帧就会销毁
线程切换时,线程的栈会作为上下文被保存下来,等到下次该线程调度时,再被重新加载。