0x01 内存简介
对于每个程序都有其独立的 4 G 的虚拟内存空间。
Windows 在默认的情况下会将高地址的 2GB 空间分配给内核(也可以配置 1GB)。
Linux 默认情况下将高地址的 1GB 空间分配给内核。
其中的 32 位系统程序存放布局图示意如下:
对于其中的进程空间,可以进行如下的划分:
0x02 堆和栈
堆和栈是两个东西,各有不同
栈:
由系统分配与回收内存,从高地址向低地址增长;大小上限对于应用层一般为 1 M 到 10 M ,内核层一般 12 K 到 24 K 不等;由于由系统分配其效率较高;用来记录函数调用过程,也称为栈帧。
栈帧一般包含:函数参数、返回地址,旧 ebp ,函数内局部变量等。
堆:
由程序员分配回收(利用 malloc 和 free 函数),从低地址向高地址增长;大小上限由系统中有效虚拟内存决定;效率较低,且会产生内存碎片;堆在堆头部使用一个字节表示大小,剩余空间存储内容由程序员在程序中的计算决定
0x03 内存地址
地址可以分为三类:逻辑地址、线性地址、物理地址
逻辑地址:由编译器生成,也叫相对地址,即在汇编指令中的地址(操作数),所以有的进程可能有一样的逻辑地址。
线性地址:也称为虚拟地址,在分段中的段偏移地址就是线性地址, 线性地址是由分段机制从逻辑地址转化而来
物理地址:这是 CPU 在存取数据时,最终在地址总线上发出的电平信号,依靠该地址来访问对应数据。若需要得到物理地址,需将逻辑地址经过分段,分页等机制转化而来,这个过程我们称之为寻址。
x86 中常用的寻址模型有两种:实模式分段模型、保护模式扁平模型
分段模型:
对于 16 位的系统,寄存器只有 16 位寻址空间位
2
16
2^{16}
216(64 KB),而地址总线有 20 位,寻址空间为
2
20
2^{20}
220(1 MB),为了能成功寻址,将 1 M 的存储空间分为 16 个 64 KB 的逻辑段,于是地址分为两部分:段地址、偏移地址。
段地址即逻辑段在主存中的起始位置,存放在段寄存器中,20 位地址中低四位省略为 0 。
偏移地址即距离段起始位置的偏移量,存放在另一段寄存器中,由于每个段大小不超过 64 KB ,所以也可以由 16 位数据表示。
所以段地址左移 4 位再加上偏移地址即为内存地址。
扁平模型:
在 32 位系统中,寄存器和地址总线都为 32 位,故不需要分段,直接一个基地址就可以了。
实模式:
指运行在 20 位地址总线中,寻址空间 1MB,寄存器 16 位,1 M 空间分成 16 个 64 KB 的段。利用 (CS << 4 ) + IP 寻址
保护模式:
指运行在 32 位地址总线中,地址为虚拟地址,只有通过页表或者段描述符表的映射来获取物理地址。
现在的系统大多都是保护模式扁平模型,是利用页表机制寻址的。
0x04 内存泄漏
当我们在堆上利用动态分配的内存并没就有进行释放,使得系统内存资源出现浪费,这称为内存泄漏,严重的内存泄漏可能会导致系统崩溃的问题。
预防:
API 的合格使用;malloc 和 free 的配对使用;尽量做到谁分配谁释放;多方使用时,可以使用引用计数,当计数归零时自动释放