《Linux内核设计与实现》读书笔记—内存管理

本文介绍了Linux内核中的内存管理机制,包括页、区的概念及其实现细节,页表的维护和查询过程,以及页的分配与释放。还讨论了kmalloc()、vmalloc()等内存分配函数的工作原理,Slab分配器的设计思想,并简述了高端内存的映射和每CPU分配内存的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 内核使用struct page表示一个物理页,其成员变量中:flag表示页的状态(是否为脏数据,是否被锁定在内存中等等);_count表示页的引用计数,当其为-1时,表示内核没有引用该页,于是在新的页分配中便可以使用它;virtual是页的虚拟地址。
  2. struct page结构与物理页相关,而并非与虚拟页相关。该结构表述的是物理内存本身,而不是包含在其中的数据。因此该结构对物理页的描述是暂时的,当该物理页被换出后,则该物理页与某page结构的关联将被断开。
  3. 页表查询的过程由硬件完成,但是页表的维护需要软件完成,处理器的MMU单元负责把虚拟地址转换成物理地址
  4. 当处理器发现虚拟地址无法通过页表映射到对应的物理地址时,就会触发一个缺页异常,挂起出错的进程,操作系统软件需要处理这个缺页异常。
  5. 一个页表可以由页缓存使用,可以作为私有数据,也可以作为进程页表中的映射
  6. 内核用struct page这一数据结构来知道一个页是否空闲,如果页被分配的话,谁拥有这个页,是用户空间进程、动态分配的内核数据还是静态内核代码或页高速缓存。

  1. 由于硬件限制,内核并不能对所有的页一视同仁。有些页位域内存中特定的物理地址上,所以不能将其用于一些特定的任务。由于存在这种限制,所以内核把页划分为不同的区(Zone)。这样就可以根据用途进行分配内存,例如ZONE_DMA内存池让内核有能力为MDA分配所需的内存。
  2. 区的划分没有任何物理意义,只不过是内核为了管理页而采用的一种逻辑上的分组。
  3. 某些分配可能需要从特定的区中获取页,而另外的一些分配则可以从多个区中获取页。例如一般用途的内存既能从ZONE_DMA分配,也能从ZONE_NORMAL分配,不过不能同时从两个区分配,因为分配是不能跨区的。
  4.  内核使用struct zone来表示区,其成员变量中:lock为一个自旋锁,保护结构不被并发访问;watermark持有该区的最小,最高和最低水位值;name表示该区的名字;free_area维护该区的空闲内存。

获得页

  1. struct page* alloc_pages(gfp_t gfp_mask, unsigned int order) 该函数分配2^order个连续的物理页,返回一个指针,指向第一个页的page结构体。如果申请失败返回NULL。
  2. void* page_address(struct page* page) 该函数把一个给定的页返回它的逻辑地址。
  3. unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) 这个函数与alloc_pages()作用相同,不过它直接返回所请求的第一个页的逻辑地址。
  4. void __free_pages(struct page* page, unsigned int order) 释放页
  5. void free_pages(unsigned long addr, unsigned int order) 释放页

kmalloc()

  1. void* kmalloc(size_t size, gfp_t flags) 这个函数返回一个指向内存块的指针,其内存块至少有size大小,并且其分配的内存在物理上是连续的。
  2. void* kfree(const void* ptr) 这个函数释放由kmalloc()分配出来的内存块。如果释放的内存不是kmalloc()申请的,或者已经被释放了,则调用kfree()会产生严重的后果。

gpf_mask标志

  1. gpf_mask标志共分为三大类:行为修饰符,区修饰符,类型修饰符。
  2. 行为修饰符:行为修饰符标识内核应当如何分配所需的内存,在某些情况下,只能使用某些特定的方法分配内存。例如,中断处理程序就要求内核在分配内存的过程中不能睡眠。典型的标志包括:GFP_WAIT表示分配器可以睡眠,GFP_IO表示分配器可以启动磁盘IO,GFP_FA表示分配器可以访问文件等等
  3. 区修饰符:去修饰符表示从哪儿分配内存。典型的标志包括:GFP_DMA表示分配器从ZONE_DMA中分配内存,GFP_HIGHMEM表示分配器从ZONE_HIGHMEM或ZONE_NORMAL分配内存等等
  4. 类型:类型标志组合了行为修饰符和区修饰符,将各种可能用到的类型归纳为不同的类型,简化了修饰符的使用。典型的类型包括:GFP_ATOMIC用在中断程序,下半部等不能睡眠的地方;GFP_KERNEL用在常规的内存分配当中,可能会引起阻塞,内核会尽力而为的为调用者分配内存。GFP_USER用在为用户空间分配内存的场景中。

vmalloc()

  1. void* vmalloc(unsigned long size) 这个函数与kmalloc()相比,分配的内存虚拟地址是连续的,物理地址则无需连续。而kmalloc()分配的内存在虚拟地址和物理地址上都是连续的。
  2. 用户空间分配内存的方式通常和vmalloc()一样。
  3. vmalloc()由于映射的物理地址不连续,因此需要专门建立页表项,并且产生严重的TLB抖动。因此从系统的性能考虑,内存申请通常使用的都是kmalloc()。
  4. vmalloc()通常用于大块内存的申请,例如:当模块被动态的插入到内存当中时,就会把模块装载到vmalloc()申请的内存当中。
  5. void* vfree(const void* addr) 这个函数释放由vmalloc()分配出来的内存块。
  6. vmalloc()会引起睡眠,因此不能再中断上下文,下半部中使用。

Slab

  1. Slab分配器在内核中扮演者通用数据结构缓存层的角色。Slab分配器试图在几个基本原则之间寻求一种平衡:
  • 频繁使用的数据结构也会频繁分配和释放,因此应当缓存他们。
  • 频繁分配和回收必然会导致内存碎片,为了避免这种表象,空闲链表的缓存会连续地存放。
  • 回收的对象可以立即投入下一次分配,因此,对于频繁的分配和释放,空闲链表能够提高其性能。
  • 如果分配器知道对象大小、页大小和总的高速缓存的大小这样的概念,它会做出更明智的决策。
  • 如果让部分缓存专属于单个处理器,那么分配和释放就可以在不加锁的情况下进行。
  • 如果分配器与NUMA相关的,它就可以从相同的内存节点为请求者分配。
  • 对存放的对象进行着色,以防止多个对象映射到相同的高速缓存行
  1. Slab分配器把不同的对象划分为所谓的高速缓存组,每个高速缓存组存放不同类型的对象,每种对象类型对应一个高速缓存。高速缓存又被划分为slab,slab由一个或多个物理上连续的页组成。

             2. 每个slab都处于三种状态之一(满,空,部分满)。内核需要分配对象时,首先会从部分满的slab中分配内存,若没有部分满的slab,则从空的slab中分配内存,若也没有空的slab,则会创建一个新的slab。

栈上静态分配内存

  1. 与用户空间可以动态增长的栈不同,内核空间的栈大小是固定的。通常,内核空间里的每个进程都有一个页或者两个页的栈空间。
  2. 中断栈为每个进程提供一个用于中断处理程序的栈,有了这个选项,中断处理程序不用再和被中断进程共享一个内核栈。

高端内存的映射

  1. 高端内存中的页不能永久地映射到内核的地址空间上。因此,从高端内存获得的页不能有逻辑地址。
  2. void* kmap(struct page* page) 这个函数返回该页对应的虚拟地址。如果这个页来自于低端内存,则单纯返回虚拟地址,如果这个页来自于高端内存,则先建立一个永久映射,再返回地址。
  3. void* kunmap(struct page* page)  这个函数解除建立的永久映射。
  4. void* kmap_atomic(struct page* page, enum km_type type) 这个函数建立一个临时映射,该处理为原子操作,不会被阻塞。
  5. void* kunmap_atomic(void* kvaddr, enum km_type type) 这个函数解除建立的临时映射。

每CPU分配内存

  1. 使用get_cpu()可以返回当前cpu的处理器编号,因此可以定义一份大小为NR_CPUS的数组,每个处理器访问对应位置的内存。
  2. get_cpu()除了返回当前cpu的处理器编号之外,还会禁止内核抢占。put_cpu()用来恢复内核抢占操作。
  3. 使用每PCU数据有两个显而易见的好处为:减少了数据锁定和大大减少缓存失效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值