linux内核之内存管理

内存管理

在内核里分配内存不像在其他地方分配内存那么容易。造成这种情况的原因有很多,从根本上讲,是因为内核不能像用户空间那样奢侈的使用内存。比如说,内核不能够睡眠,在处理内存分配错误的问题时不是一件容易的事等等,这些限制使得内核内存分配机制不能过于复杂。

内核管理内存

内核把物理页作为内存管理的基本单位。虽然处理器的最小可寻址单位是字,但是内存管理单元(MMU,将虚拟地址转为物理地址的硬件)通常以页为单位进行处理,页的大小一般在32位系统为4k,64位系统为8k,如果页为4k,物理内存为1G,则物理内存会被划分为1G/4k=262144个页,这些页信息通过page结构体全部保存在内存中。

一个页可以由页缓存使用、或是作为私有数据,或是作为进程页表的映射等。内核中用struct page结构体表示系统中每个物理页,该结构位于<linux/mm_types.h>文件中。必须要理解的是,该结构与物理页相关,而并非与虚拟页相关,因此该结构对页的描述只是短暂的。内核仅仅用这个数据结构来描述当前时刻相关物理页中存放了东西,这种数据结构目的在于描述物理内存本身,而不是描述包含在其中的数据。

内核用这一数据结构来管理系统中所有的页,因为内核需要知道一个页是否空闲(有没有被分配),如果被分配了,是谁拥有这个页(用户空间进程、动态分配的内核数据结构、静态内核代码、页高速缓存等)。系统中每个页都需要分配一个这样的结构体。

由于硬件的限制(老硬件地址总线位数比较少,相应能够访问到的实际地址空间也少),内核并不能对所有的页一视同仁。由于这种限制,内核把具有相似特征的页划分为不同区。
linux必须处理如下两种由于硬件缺陷而引起的内存寻址问题:
(1)一些硬件只能由某些特定的内存地址来执行直接内存访问(DMA),例如,ISA总线只能对RAM前16MB地址进行寻址。
(2)一些体系结构的物理寻址范围比虚拟寻址范围大的多,比如,32位的CPU不能直接访问具有大容量的物理内存(超过4G),这样,有一些内存无法永久的映射到内核空间上。

因为这些约束,linux主要使用了四个区:
(1)ZONE_DMA,这个区包含的页只能用于DMA操作。
(2)ZONE_DMA32,和ZONE_DMA类似,不同之处在于这些页面只能被32位设备访问。
(3)ZONE_NORMAL,这个区包含的都是能够正常映射的页。
(4)ZONE_HIGHEM,高端内存区,其中的页不能永久的映射到内核空间上。

区的使用和分布与体系结构息息相关。区由结构体struct zone表示,在<linux/mmzone.h>文件中定义。

以下都是基于x86机器分析上述几种区的作用和使用方式。
例如,为了解决上述限制问题,对于x86机器,Linux内核将物理内存分为了3个管理区(zone):

区域描述物理内存地址
ZONE_DMADMA使用的页低于16MB的内存空间
ZONE_NORMAL正常可寻址的页(线性映射)16MB~895MB
ZONE_HIGHMEM动态映射的页896MB~物理内存结束

在内核或应用程序访问内存时,所操作的内存地址都为虚拟地址,需要一对一的映射来对应到真正的物理内存地址。对于应用程序,虚拟地址到物理地址的转换需要MMU(涉及到进程页表转换);而对于x86内核来说,前两个管理区的内存空间被直接映射到内核虚拟地址空间中(0-896M)。直接映射时虚拟地址0xc0000003对应的物理地址为0x00000003,0xc0000004对应的物理地址为0x00000004。虚拟地址与物理地址有如下的对应关系:

物理地址 = 虚拟地址 – 0xC0000000

值得注意的是,在进程的虚拟内存区域中,内核属于3G-4G部分,3G至(3G+896M)部分通过上述相减的方式直接映射,效率会很高效(这里可以看出来,所有进程的虚拟地址中,内核该部分其实都是同一份,映射的物理地址0-896M)。而3G+896M至4G部分则用来映射高端内存区,即ZONE_HIGHMEM。如下图(请忽视比列):
在这里插入图片描述

按照如上直接映射的方式,会发现内核只能访问1G的内存,但我们的物理内存实际上会达到8G、16G,甚至更高,所以必须要有一种灵活的方式来让内核访问到全部的物理内存,因此linux高端内存十分必要。高端内存就是通过动态映射的方式来帮我们访问896MB之外的其他内存空间

值得注意的是,在64位系统中不存在高端内存,64位系统内核无需通过动态映射就能访问所有内存。

内存分配

获取页函数

内核提供了一种请求内存的底层机制,并提供了对它访问的几个接口,所有这些接口都是以页为单位分配内存的。如下:
在这里插入图片描述

kmalloc函数

同用户空间的malloc类似,用它可以获取一块以字节为单位的连续物理地址的内存块。该函数返回一个指向内存块的指针(逻辑地址)。

void* kmalloc(size_t size , gfp_t flags);

值得注意的是,kmalloc只能分配非ZONE_HIGHMEM区的内存。

gfp_mask标志

通过前面几个函数,我们发现不管是低级页分配函数,还是kmalloc函数,都用到了gtp_mask分配标志,这些分配器标志可大致分为三种:行为修饰符、区修饰符以及类型。

行为修饰符
表示内核应当如何分配内存。例如,中断处理程序中要求内核分配内存时不能够睡眠。
在这里插入图片描述
区修饰符
表示从哪儿分配内存,默认从ZONE_NORMAL区进行分配。
在这里插入图片描述

注意:不能给_get_free_pages()或kmalloc指定_GFP_HGHMEM标志,因为它们返回的都是逻辑地址,这两个函数分配的内存当前可能还没有映射到内核的虚拟地址空间来,因此,可能根本连逻辑地址都没有(高端地址的分配首先由页分配函数获取高端区的页,然后通过映射函数将页映射到内核的虚拟地址空间,即分配逻辑地址)。只有alloc_pages()才能分配高端内存,它是内核内存分配中最核心的接口。

类型标志
组合了行为修饰符和区修饰符来完成特殊类型的处理。一般只需要指定类型标志即可达到我们的目标,类型标志本身就是前两个标识符的组合。
在这里插入图片描述

vmalloc

vmalloc函数的工作方式与kmalloc类似,不同之处在于,前者返回的内存虚拟地址是连续的,而物理地址无须连续。这也是用户空间分配方式。它通过分配非连续的物理内存块,再修正页表,把内存映射到逻辑地址空间的连续区域中。
一般只有在申请大块内存时才会用到vamlloc函数,因为vmalloc函数为了把不连续的物理页转换为连续的虚拟地址,必须建立页表项,而通过vmalloc获取的页必须一个一个的进行映射,这就导致会比直接内存映射大得多的TLB抖动,会影响到系统性能。

void * vmalloc(unsigned long size);
slab层

分配和释放是所有内核中最普遍的操作之一。为了便于数据的频繁分配和回收,编程人员通常会使用空闲链表的方式。当代码需要一个新的数据结构实例时,可以用空闲链表中取一个,释放实例时,将其放回空闲链表即可,而不用释放它的内存。这就是slab层的原理,使用一个对象高速缓存。

slab层把不同结构类型的对象划分为不同的高速缓存组,每种对象类型对应一个高速缓存,例如一个高速缓存放进程描述符,而另一个高速缓存放索引节点对象。有意思的,kmalloc也是建立在slab层之上的,使用了一组通用高速缓存。

这些高速缓存又被划为为slab。slab由一个或多个连续的页组成,每个高速缓存由多个slab组成。每个slab都包含一些对象成员(被缓存的数据结构)。
在这里插入图片描述

在栈上静态分配

与用户空间相比,内核在栈上分配的内存要小得多,因为内核栈本身就很小而且固定。当给每个进程分配一个固定大小的小栈后,不但可以减少内存消耗,而且内核也无须负担太重的栈管理任务。
每个进程的内核栈大小既依赖体系结构,也与编译时选项有关。通常内核栈的大小为4~16KB。

高端内存映射

根据定义,在高端内存的页不能永久的映射到内核地址空间上,因此,通过alloc_pages()函数以_GFP_HIGHMEM标志获得的页不可能有逻辑地址。

要映射一个给定的page结构到内核地址空间,可以使用定义在文件<linux/highmem.h>中的函数:

void * kmap(struct page *page)

这个函数对高端内存和低端内存都有效,如果page结构对应的是低端内存的页,这个函数只会单纯的返回该页的虚拟地址。如果页位于高端内存,则会建立一个永久的映射,在返回映射地址。该函数可以睡眠。
因为允许永久映射的内存数量是有限的,因此在不使用时需要使用下列函数解除映射:

void kunmap(struct page *page);

不会睡眠的临时映射函数:

void  *kmap_atomic(struct page *page , enum km_tyep type);
void kunmap_atomic(void *kvaddr, enum km_type type);

注:高端映射的概念已经在之前的章节中做了图解了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值