内存管理
进程编译过程
一个c程序,编译时分为三步:预处理,编译,汇编,链接
- 预处理,展开头文件、宏替换,去掉注释,条件编译等 gcc -E hello.c -o hello.i
- 编译,使用编译器将预处理文件hello.i编译成汇编文件hello.s gcc -S hello.i -o hello.s
- 汇编,使用汇编器将hello.s编译成目标文件hello.o,二进制文件 gcc -c hello.s -o hello.o
- 链接,使用链接器将目标文件与其它目标文件,库文件等连接为可执行文件 gcc hello.o -o hello.out
c程序在虚拟地址空间布局
- 代码段,cpu执行机器指令部分,可共享,只读。
- 数据段,初始化后的数据段,如int a = 1;
- bss段,未初始化数据段,如int a[1];
- 栈,临时变量,每次函数调用时保存的临时变量,函数调用返回地址及调用者环境信息。递归函数每次调用自己会使用新的栈帧,不会影响另一次调用的变量。
堆,动态分配的变量。
内存地址空间概念
逻辑地址,程序产生的段内偏移地址部分,程序员所能见到的。
虚拟地址,由段选择符和段内偏移地址两部分组成的地址,也有书指出这就是逻辑地址。
1.表示最大虚拟地址空间:2^13*2*4G=64T,段选择符的13位索引,区分GDT和LDT的1个比特,32位逻辑地址 2.linux0.11内核中,给每个进程划分了64MB的虚拟内存空间。如果是现在Linux内核,32位系统则分配4G的虚拟内存。
- 线性地址,由段选择符索引的段描述符所映射的段基址和段内偏移组成。如果未开启分页机制,则线性地址(80386是4G)就是物理地址。
物理地址,地址总线能够寻址的物理内存地址
分段机制
多进程保护,防止一个任务访问另一个任务或操作系统的内存区域。通过给与每个进程独立的段表,每个进程就会有不同的地址变换。
不同段之间的保护,如代码段是只读,其他段不能修改
把虚拟地址空间中的虚拟内存组织成一些长度可变的段。如数据段,代码段等。
把逻辑地址转换成线性地址。
段描述符表
段描述符的一个数组。包括GDT(全局描述符表)和LDT(局部描述符表)
GDT 系统中所有进程共享的系统代码段或数据段由GDT映射,GDT的基址地址和长度保存在GDTR寄存器中。并且包含所有LDT的段描述符。
LDT 各个进程自己的段表。LDT的基址,段限长及段选择符存放在LDTR寄存器 一个进程就是一个任务,任务A和B除了能访问自身的代码段和数据段,还能访问共享的os代码段,os数据段。通过虚拟地址空间隔离每个任务。 发生任务切换时,LDTR更换成新任务的LDT,但GDT不变。
段选择符
或称为段选择子,是选择段的一个16位标识符。
- 描述符索引 指向段描述符表中的索引,用于找出段描述符。
- TI 0表示在GDT中查找,1表示在LDT中查找。
- 通常由加载程序进行设置和修改,不是应用程序。
访问某个段时,必须已经把段选择符加载到一个段寄存器中。处理器最多提供6个段选择符的寄存器,CS(代码段),DS(数据段),SS(堆栈段),还有ES,FS,GS3个辅助的数据段寄存器。
段描述符
长度是8字节,是GDT和LDT表中用于描述段的数据结构项,包括段基址,段限长,段属性。由编译器,连接器,加载器或者OS来创建。 4G线性地址空间中段基址由3个分离的基地址字段组合形成。
分页机制
分段机制把逻辑地址转换成线性地址,分页机制把线性地址转换成物理地址。CR0寄存器的PG位决定是否开启分页机制。
80X86使用4K(2^12)字节固定大小的内存页,每个页面4KB,对齐与4K地址边界处。4G线性地址空间划分成了2^20(1M)个页面。
由于是线性地址空间4K大小的页面作为一个单元映射,并且对齐于4K边界,因此线性地址的低12位可作为页内便宜量直接映射,线性地址的高20位转换到对应的物理地址的高20位。
提高内存空间利用率,如果用单级表来映射4G内存,则每个进程需要4M,用分页机制可以大大减少页表占用空间。 1.如果一个进程12M,则只需要3个页目录项表示,1个页目录表,3个页表,只需要16KB的空间。因为有很多逻辑空间不需要用到。 2.如果用1级页表映射,则需要4M。
页表结构
页表中每个页表项大小为32位,4字节,每个页4KB,则需要2^20(1M)个表项,页表占用4M。
一个进程的虚拟地址需要先通过LDT中的段描述符变换为CPU整个线性地址空间中的地址,再使用分页机制。
Linux0.11每个进程最大虚拟地址空间为64MB,因此每个进程的逻辑地址通过(任务号)*64MB,即可转换为线性空间中的地址
页目录表
一级表。具有2^10(1K)个4字节长度的页目录项,页目录项指向对应的二级表。页目录表占用4KB,一页大小。
Linux0.1x系统中内核和所有进程任务都共用同一个页目录表。为了让他们互不重叠,必须从虚拟地址空间映射到线性地址空间的不同位置,占用不同的线性地址空间。
页表
- 二级表。长度也是1个内存页,每个页表含有1K个页表项,每个占4字节。每个表项含有20位的物理基址。
- 二级页表分散在内存各个页面中,不需要保存在连续的4MB内存块中。且可以在需要时再分配。
- 每个进程有自己的页表,如果每个进程能映射4G内存地址空间,则需要4M存放页表。10个进程则40M。
页表项格式
P 位表示表项对地址转换是否有效。如果不存在,处理器会通过缺页中断通知OS把缺少的页表从磁盘加载进物理内存。 这就是涉及到内存管理的换入换出,页面置换算法LRU,当物理内存不够时,可以把最近最少使用的内存页置换到磁盘上的swap分区,当需要时再置换回来。
总结
段页结合的方式管理内存,能够保证程序分成多段,符合人的习惯,每段分成多个页,能高效的利用内存,减少内存碎片。 Linux采用的是内存管理方式是段页结合的管理方式,总览图如下:
- 原文作者:niep
- 原文链接:http://www.fdgggy.com/2019/10/17/mem/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。