内核的内存管理不同于用户空间的内存管理,首先来讲内核本身的限制点就比较多,比如内核一般不能睡眠,因此处理内存错误一般来讲是件很困难的事情,再加上其他限制以及内核内存机制不能太复杂导致想获取内核内存变成了一件难事。
一、页
在说内核的内存管理机制之前,首先我们得明白内核内存管理的基本单位才行,一般来讲内核都是以页为单位对内核进行内存管理的,正因为如此MMU(管理内存并把虚拟地址转换为物理地址的硬件)以页为单位来管理系统中的页表,从虚拟内存的角度来看,页就是最小单位。
内核中一般用struct page这个结构体来表示系统中的每个物理页,
这里我只说其中两个域的作用,_count域,相当于页的引用计数,当改计数不为0时则证明该页被引用了多少次,当其值为0时说明当前内核并未使用改页,该页就变为了空闲页。virtual域,这是个很有意思个人感觉也很重要的域,因为它就是在虚拟内存中该页的地址,换句话说就是记录了映射关系的东西。但在有些情况下有些内存并不永久的映射到内核的地址空间上,这个域的值为NULL,需要的时候必须动态映射的这些页。这指的就是内核中的高端内存。
内核系统正是靠着这样的一个结构体去管理者内核内存的,通过这个结构系统可以知道那个页被分配了,以及拥有者是谁,拥有者可能是用户空间进程、动态分配的内核数据、静态内核代码、页高速缓存等(这个是重点xxxxx)。
二、区
分区,这个内核内存管理的另一种机制。那已经有页管理方式了为什么要分区呢?因为在内核中有些页位于特定的物理地址上,具有局限性。所以内核就不得不把页分为不同的区。因为Linux上存在两种由于硬件存在的缺陷引起的内存寻址。
1.一些硬件只能用某些特定的内存来执行DMA(直接内存访问)
2.一些体系结构其内存的物理寻址范围比虚拟内存寻址范围大的多,这样就有一些虚拟内存不能永久地映射到内核空间上。
因此Linux使用了三种区:
ZONE_DMA——这个区包含的页能用来执行DMA操作
ZONE_NORMAL——这个区包含的都是能正常映射操作
ZONE_HIGHMEM——这个区包含“高端内存”,其中的页并不能永久映射到内核地址空间。
这样设计的主要原因是为了适应不同的体系结构而已,因为不同的机器支持的机制都是不一样的。要明确一点,区的划分是逻辑划分,若某个区的内存不够了,是可以从其他区拿的。这样做分区是为了更好的适应不同的机器但并不是绝对的。
三、kmalloc()、vmalloc()
既然已经知道了内核内存的基本存在的形式了,那么就应该了解下内核内存的获取了。
我想大多人都知道malloc()函数,那么kmalloc()和vmalloc()其实和它差不多,都属于malloc一族的函数,但是后两者属于系统调用(简单来讲,就是系统自带的函数,在内核中使用的),而malloc属于库函数(简单来讲,就是用户空间使用的函数),虽然他们三个都是用来获取内存的,但是是有着本质区别的。
kmalloc()分配内核内存空间,其分配的内存空间是保证物理内存上是连续的,物理内存上是连续的那么逻辑内存上当然也是连续的。
vmalloc()分配内核内存空间,其分配的内存空间是保证逻辑内存上是连续的,物理内存不一定连续,后续通过“修正”页表保证映射到逻辑地址空间的连续区域中。
malloc()分配用户态的内存,其分配的内存同vmalloc()相似,保证虚拟地址空间是连续的。
释放的时候其都有对应的释放函数,kfree(),vfree(),free()。
需要注意的是,在内核中一般使用的都是kmalloc(),因为性能上来讲vmalloc()要差很多,因为要把不连续的物理内存转为连续的虚拟内存就必须要去建立页表,而且还需要去把页拿着一个一个的去进行映射,这会造成TLB(一种硬件缓冲区,很多体系结构用它来缓存虚拟地址到物理地址的映射关系,极大的提升了系统性能)抖动,所以vmalloc()不得已才使用。
四、slab层
重点来了,对于内核来讲这是个很重要的机制。分配和释放数据结构是所有内核中最普遍的操作之一。为了变为数据的频繁分配和回收,编程者经常会用到一个空闲链表,该空闲链表包含有可提供使用的、已经分配好的数据结构块。这样就放便了结构的分配与回收。
但是在内核中,空闲链表是不能全局控制的,当内存紧缺时,内核无法通知空闲链表让其腾出空间来,事实上内核根本都不知道有这个东西,所以Linux就设计了slab层,用来帮助结构的分配和回收。
slab层的设计
slab把不同的对象划分为所谓的高速缓存(cache)组,其中每个高速缓存都存放在不同类型的对象,每种对象类型对应一个高速缓存。例如一个高速缓存用于存放task_struct,一个用来存放struct inode。
然后把这些高速缓存又划分为slab,slab由一个或多个物理上连续的页组成,一般情况下slab页就仅仅由一个页组成,每个高速缓存可以由多个slab组成。
每个slab都包含了一些对象,对象指的是被缓存的数据结构,每个slab都由三种状态,满,空,部分满。怎么理解呢?就是你需要该结构时,从一个部分满的slab中拿,若没有部分满就从空的slab中拿,该slab就被标记为部分满,当你把其中的结构拿完时,该slab就被标记为空,释放的时候就归还该slab。这种策略可以对比空闲链表可以有效的减少内存碎片。