第12章 内存管理

内核不支持简单快捷的内存分配方式。

一、页

内核把无力页作为内存管理的基本单位。内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址的硬件)通常以页为单元进行处理。体系结构不同,页大小不尽相同。大多数32位体系结构支持4KB的页,64位支持8KB的页。

物理页的结构体:

struct page {
  unsigned long          flags;//页的状态
  atomic_t               _count;//引用计数
  atomic_t               _mapcount;
  unsigned long          private;
  struct address_space   *mapping;
  pgoff_t                index;
  struct                 list_head;
  void                   *virtual;//页的虚拟地址
};

page结构与物理页相关,而并非与虚拟页相关。

二、区

内核使用区对具有相似特性的页进行分组。Linux主要使用四种区:

  • ZONE_DMA:包含的页用来执行DMA(直接内存访问)操作
  • ZONE_DMA32:类似ZONE_DMA,但只能被32位设备访问
  • ZONE_NORMAL:包含能正常映射的页
  • ZONE_HIGHEM:高端内存,其中的页并不能永久地映射到内核地址空间

除了ZONE_HIGHEM,其余的内存就是低端内存。


image.png

Linux把系统的页划分为区,形成不通的内存池,以根据用途进行分配。区的划分并没有任何物理意义,只不过是内核为了管理页而采取的一种逻辑上的分组。分配可以从一个或多个区获取页,但不可能同时从两个区分配。

三、获得页

内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口,以页为单位进行分配:


image.png

alloc_pages分配的是连续的物理页,可以通过void * page_address(struct page *page);把物理页转化为逻辑地址。

四、kmalloc

void * kmalloc(size_t size, gft_t flags);

kmalloc以字节为单位进行分配,分配的内存区在物理上是连续的。

gft_t标志可分为:

  • 行为修饰符:表示内核应该如何分配所需的内存
  • 区修饰符:表示从哪儿分配内存
  • 类型修饰符:组合了行为修饰符和区修饰符
image.png
void kfree(const void *ptr);

释放kmalloc分配的内存块。kfree(NULL)是安全的。

五、vmalloc

vmalloc与kmalloc类似,但vmalloc分配的内存虚拟地址是连续的,而物理地址则无需连续。

大多数情况下,只有硬件设备需要得到物理地址连续的内存。在很多体系结构上,硬件设备存在于内存管理单元之外,不理解虚拟地址,因此硬件设备用的内存区都必须是物理上连续。仅供软件使用的内存块就可以使用只有虚拟地址连续的内存块。

void * vmalloc(unsigned long size);//分配
void vfree(const void *addr);//释放

以上两个函数都可能休眠,因此不能从中断上下文进行调用,也不能从其他不允许阻塞的情况下进行调用。

六、slab层

slab层把不同对象划分为高速缓存组,其中每个高速缓存组都存放不同类型的对象。每种对象对应一个高速缓存。

高速缓存又划分为一个或多个slab,slab由一个或多个物理上连续的页组成。每个slab都包含一些对象成员,处于满、部分满或空三种状态之一。当内核的某一部分需要一个新的对象时,先从部分满的slab中进行分配。如果没有部分满的slab,就从空的slab中进行分配。如果没有空的slab,就要创建一个slab。

image.png
//创建新的高速缓存:
struct kmem_cache * kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *));
//撤销高速缓存:
int kmem_cache_destroy(struct kmem_cache *cachep);

//获取对象
void * kmem_cache_alloc(struct kmem_cache *cachep, gft_t flags);
//释放对象
void kmem_cache_free(struct kmem_cache *cachep, void *objp);

七、在栈上的静态分配

内核栈小且固定。每个进程的内核栈大小既依赖体系结构,也与编译时的选项有关。历史上每个进程都有两页内核栈(中断处理程序与被中断进程共享一个内核栈),但2.6内核引入一个单页内核栈,这样中断处理程序就使用中断栈了。

八、高端内存的映射

高端内存中的页不能永久地映射到内核地址空间上,一旦这些页被分配,就必须映射到内核的逻辑地址空间上。

//永久映射:
void *kmap(struct page *page);
void kunmap(struct page *page);


//临时映射,不会阻塞
void *kmap_atomic(struct page *page, enum km_type type);
void kunmap_atomic(void *kvaddr, enum km_type type);

九、每个CPU的分配

一般来说,每个CPU的数据存放在一个数组中,数组中每一项对应着系统上一个存在的处理器,按当前处理器号确定这个数组的当前元素。

十、新的每个CPU接口

编译时:

//定义
DEFINE_PER_CPU(type, name);
//声明
DECLARE_PER_CPU(type, name);

//操作变量例程
get_cpu_var(name)//会禁止抢占
put_cpu_var(name)//重新激活抢占

per_cpu(name, cpu)//不禁止内核抢占,也不提供任何形式的锁保护

运行时:

void *alloc_percpu(type);
void *__alloc_percpu(size_t size, size_t align);

void free_percpu(const void *);

你可能感兴趣的:(第12章 内存管理)