linux内核设计与实现--内存管理
页(page)
内核把物理页作为内存管理的基本单位. MMU以页大小为单位来管理系统中的页表.
32位系统的页大小一般为4KB, 64位系统的页大小一般为8KB.
内核用strcut page结构来表示系统中的每个物理页. 每个物理页有自己的flag(状态标识), count(引用计数), virtual(虚拟地址)域.
区(zone)
由于硬件的限制, 只有某些页能做特定的任务, 所以内核把页划分为不同的区.
Linux主要使用的4种区:
ZONE_DMA: 可以用来执行DMA操作的页.
ZONE_DMA32: 可以被32位设备访问的用于执行DMA的页.
ZONE_NORNAL: 能正常映射的页.
ZONE_HIGHMEM: 高端内存, 不能永久映射到内核地址空间.
获得页与释放页
当我们需要的内存大小刚好是一个页或几个页的时候, 我们可以直接使用底层的页分配函数来分配内存
Linux系统提供了最低层的接口用来获得物理上连续的页, 该接口只能分配2^n个页.
Alloc_pages + page_address: 使用alloc_pages分配页之后, 用page_address获取到页对应的虚拟地址.
__get_free_pages: 直接获取页对应的虚拟地址,由于页是连续的, 所以其它的页也会紧随其后.
当只需要分配一个页的时候, 可以用alloc_page或__get_free_page来替代.
Get_zeroed_page: 只分配一页, 但是页会被填充为0.
Free_pages, free_page, __free_pages,__free_page几个函数都可以用来释放页.
kmalloc和vmalloc的区别
在内核中, 我们一般使用kmalloc和vmalloc来分配内存
小内存(128K以下)分配的时候, 使用kmalloc, 大内存分配的时候, 使用vmalloc.
Kmalloc分配的内存物理上是连续的, vmalloc则不保证这一点. 由于不用为每一页都处理映射关系, 所以kmalloc的性能比vmalloc要高.
Kmalloc加上GFP_ATOMIC标志便可以在中断上下文中使用, 加上GFP_DMA则可以分配能用于DMA的内存.
高端内存
高端内存由于没有映射虚拟地址, 因此不能直接访问. 访问之前需要建立物理地址到虚拟地址的映射关系, 然后再通过虚拟地址来访问. 我们通过kmap和kunmap这两个api来建立映射关系. 如果需要在中断上下文中调用, 则使用kmap_atomic和kunmap_atomic这两个api.
SLAB层的作用
代码中针对CPU的硬件特征(高速缓存的对齐和着色)进行优化, 可以大大提高程序的性能. 但由于这是一个比较复杂的领域, 如果每个程序员都需要做这样的研究, 将耗费大量的人力.于是linux针对内存管理模块, 单独设计出了SLAB层.
linux中的高速缓存是用所谓 SLAB层来实现的, SLAB层即内核中管理高速缓存的机制.
SLAB分配支持常用对象数据的初始化,减少了同类对象数据重复的初始化过程.
SLAB分配支持硬件缓存对齐和着色,这样不同缓存下的对象数据可以使用同样的硬件缓存行,可以提高系统的性能.
SLAB层的出现可以避免用户在编程的过程中使用空闲链表, 造成系统的内存紧缺.
SLAB层解决了频繁的内存分配和释放带来的内存碎片问题.
所以, 如果想获得性能上的优势又不想过多的去研究CPU底层的缓存原理等, 使用SLAB层吧. 它还可以让我们避免使用空闲链表预分配的方式浪费系统内存, 也避免引起了内存碎片.
伙伴系统和SLAB之间的关系
Linux系统中, 最底层的基于页的内存分配方式(如alloc_pages, free_pages)会直接通过伙伴系统来分配页, vmalloc会通过伙伴系统来分配多个页.Kmalloc则会通过slab层然后再和伙伴系统打交道. 最底层的内存管理仍然是伙伴系统来完成, slab只是在其基础上实现了一些缓存和针对cache的优化而已.