内存管理

来源:互联网 发布:苹果电脑游戏知乎 编辑:程序博客网 时间:2024/06/01 09:49

内存管理
计算机的内存是主要的资源, 处理它所用的策略对系统性能是至关重要的. 内核为所有进程的每一个都在有限的可用资源上建立了一个虚拟地址空间. 内核的不同部分与内存管理子系统通过一套函数调用交互, 从简单的 malloc/free 对到更多更复杂的功能.
1.内存管理子系统的功能
1)分配内存
2)回收内存
3)实现逻辑地址-->物理地址-->虚拟地址(线性地址)的转换
注意:内存泄漏:malloc,kmalloc使用后要free,kfree
2.地址类型:物理地址,虚拟地址,逻辑地址
1)物理地址:物理地址是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果。
2)逻辑地址:程序代码经过编译后,在汇编程序中使用的地址
3)线性地址:线性地址又名虚拟地址,在32位CPU架构下,可以表示4G的地址空间,用16进制表示就是0x00000000到0xffffffff。
 
 代码区                |0G
 文字常量区          |用户
 数据区                |空间
 堆区                   |
 栈区                   |
-----------------------------|3G
 物理地址映射区              |
 vmalloc(动态映射区)  |内核
 专用映射                      |空间
 固定映射区                   |
 保留区                         |
3.地址转换
CPU要将一个逻辑地址转换为物理地址,需要两步:首先CPU利用段式内存管理单元,将逻辑地址转换成线性地址,再利用页式内存管理单元,把线性地址最终转换为物理地址
4.段式管理(16位CPU)
16位CPU内部拥有20位的地址线,它的寻址范围就是2的20次方,也就是1M的内存空间。但是16位CPU用于存放地址的寄存器(IP,SP……)只有16位,因此只能访问65536个存储单元,64K。
由于段地址必须是16的倍数,所以值的一般形式为XXXX0H,即前16位二进制位是变化的,后四位是固定的0,鉴于段地址的这种特性,可以只保存前16位二进制位来保存整个段基地址,所以每次使用时要用段寄存器左移补4个0(乘以16)来得到实际的段地址。
在确定了某个存储单元所属的段后,只是知道了该存储单元所属的范围(段地址? -> 段地址+65536),如果想确定该内存单元的具体位置,还必须知道该单元在段内的偏移。有了段地址和偏移量,就可以唯一的确定内存单元在存储器中的具体位置。
由逻辑地址得到物理地址的公式为:  PA = 段寄存器的值 * 16 + 逻辑地址
5.分页管理
从管理和效率的角度出发,线性地址被分为固定长度的组,称为页(page),例如32位的机器,线性地址最大可为4G,如果用4KB为一个页来划分,这样整个线性地址就被划分为2的20次方个页。
另一类“页”,称之为物理页,或者是页框、页桢。分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与线性地址页是相同的。
6.Linux内存管理
Linux内核的设计并没有全部采用Intel所提供的段机制,仅仅是有限度地使用了分段机制。这不仅简化了Linux内核的设计,而且为把Linux移植到其他平台创造了条件,因为很多RISC处理器并不支持段机制。
所有段的基地址均为,由此可以得出,每个段的逻辑地址空间范围为0-4GB。因为每个段的基地址为0,因此,逻辑地址到线性地址映射保持不变,在Linux中所提到的逻辑地址和线性地址(或虚拟地址)指的也就是同一地址。看来,Linux巧妙地把段机制给绕过去了,而完全利用了分页机制。
Linux 2.6.29内核为每种CPU提供统一的界面,采用了四级页管理架构,来兼容二级、三级、四级管理架构的CPU。
7.Linux进程地址空间
1)虚拟内存
Linux操作系统采用虚拟内存管理技术,使得每个进程都有独立的进程地址空间,该空间是大小为3G,用户看到和接触的都是虚拟地址,无法看到实际的物理地址。利用这种虚拟地址不但能起到保护操作系统的作用,而且更重要的是用户程序可使用比实际物理内存更大的地址空间。
Linux将4G的虚拟地址空间划分为两个部分——用户空间与内核空间。用户空间从0到0xbfffffff,内核空间从3G到4G。用户进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间。例外情况是用户进程通过系统调用访问内核空间。
2)进程空间:用户空间对应进程,所以每当进程切换,用户空间就会跟着变化
每个进程的用户空间都是完全独立、互不相干的。把同一个程序同时运行10次(为了能同时运行,让它们在返回前睡眠100秒),会看到10个进程使用的线性地址一模一样。(cat /proc/<pid>/maps )
创建进程fork()、程序载入execve()、动态内存分配malloc()等进程相关操作都需要分配内存给进程。这时进程申请和获得的不是物理地址,仅仅是虚拟地址。
★实际的物理内存只有当进程真的去访问新获取的虚拟地址时,才会由“请页机制”产生“缺页”异常,从而进入分配实际页框的程序,当真正需要时,才把虚拟地址映射到物理地址
8.内核内存分配
在应用程序中,常使用malloc函数进行动态内存分配,而在Linux内核中,通常使用kmalloc来动态分配内存。
kmalloc 原型是:#include <linux/slab.h>void *kmalloc(size_t size, int flags)参数: 
size:要分配的内存大小。
flags:分配标志, 它控制 kmalloc 的行为。最常用的标志是GFP_KERNEL,它的意思是该内存分配是由运行在内核态的进程调用的。也就是说,调用它的函数属于某个进程的,当空闲内存太少时,kmalloc函数会使当前进程进入睡眠,等待空闲页的出现。
如果kmalloc是在进程上下文之外调用,比如在中断处理,任务队列处理和内核定时器处理中。这些情况属于中断上下文,不能进入睡眠,这时应该使用优先权GFP_ATOMIC。
9.按页分配
如果模块需要分配大块的内存,那使用面向页的分配技术会更好
get_zeroed_page(unsigned int flags)
返回指向新页面的指针,并将页面清零。
__get_free_page(unsigned int flags) 和get_zeroed_page类似,但不清零页面。
__get_free_pages(unsigned int flags,unsigned int order )
分配若干个连续的页面,返回指向该内存区域的指针,但也不清零这段内存区域。10.释放内存
当程序用完这些页, 可以使用下列函数之一来释放它们:
void free_page(unsigned long addr)
void free_pages(unsigned long addr, unsigned long order)
**如果释放的和先前分配数目不等的页面,会导致系统错误**
11.Linux内核地址空间
内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。
高端内存:物理内存896MB以上的部分称之为高端内存。
1)直接内存映射区
从3G开始,最大896M的线性地址区间,我们称作直接内存映射区,这是因为该区域的线性地址和物理地址之间存在线性转换关系
 线性地址=3G + 物理地址
2)动态内存映射区该区域的地址由内核函数vmalloc来进行分配,其特点是线性空间连续,但对应的物理空间不一定连续。vmalloc分配的线性地址所对应的物理页可能处于低端内存,也可能处于高端内存。
3)永久内存映射区
对于896MB以上的高端内存,可使用该区域来访问,访问方法:使用alloc_page(__GFP_HIGHMEM)分配高端内存页
使用kmap函数将分配到的高端内存映射到该区域
4)固定映射区PKMap区上面,有4M的线性空间,被称作固定映射区,它和4G顶端只有4K的隔离带。固定映射区中每个地址项都服务于特定的用途,如ACPI_BASE等。

 

 

 

 

 

 

 

 

 

 

 

 

 

原创粉丝点击