内存管理基础(Linux内核涉及与实现)

2021/7/14 7:08:33

本文主要是介绍内存管理基础(Linux内核涉及与实现),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

    • 1. 页
    • 2. 区
    • 3. 获得页
      • 3.1 获得填充为0的页
      • 3.2 释放页
    • 4. kmalloc():物理地址和虚拟地址都连续
      • 4.1 gfp_mask标志
      • 4.2 kfree()
    • 5. vmalloc():虚拟地址连续,物理地址地址无须连续
    • 6. slab层
      • 6.1 slab层的设计
      • 6.2 slab分配器的接口
    • 7. 在栈上静态分配
    • 8. 高端内存的映射
      • 8.1 永久映射
      • 8.2 临时映射
    • 9. 每个CPU的分配
    • 10. 新的每个CPU接口
      • 10.1 编译是的每个CPU数据
      • 10.2 运行时的每个CPU数据
    • 11. 使用每个CPU的原因
    • 12. 分配函数的选择
    • 13. 小结

1. 页

  • 内核把物理页作为内存管理的基本单位。
  • 大多数32为体系结构支持4KB的页,64位支持8KB的页。
  • 内核用struct page结构表示系统中的物理页: 在这里插入图片描述
    flags域用来存放页的状态,是否页是不是脏的,是否被锁定在内存中等
    _count域存页的引用计数器,为-1时,表明没有引用这一页。
    virtual域是页的虚拟地址。

2. 区

  • 内核使用区对具有相似特性的页进行分组。
区名描述
ZONE_DMA这个区的页能用来执行DMA
ZONE_DNA32和ZONE_DMA类似,但只能被32位设备访问
ZONE_NORMAL这个区包含的都是能正常映射的页
ZONE_HIGHEM这个区包含“高端内存”,其中的页不能永久地映射到内核地址空间

在这里插入图片描述
在这里插入图片描述

  • lock域是一个自旋锁,防止该结构被并发访问。
  • watermark数组持有该区的最小值、最低和最高水位值。
  • name域是一个以NULL结束的字符串表示这个区的名字,“DMA”、“Normal”和“HighMem”。

3. 获得页

所有接口都是以页为单位分配内存。
struct page * alloc_pages(gfp_t gfp_mask, unsigned int order) //分配2^order(1<<order)个连续的物理页,返回第一个页的page结构体,出错返回NULL。
把给定的页转换成它的逻辑地址:
void * page_address(struct page *page) //返回给定物理页当前所在的逻辑地址。
与alloc_pages()类似的函数:
unsigned long __get_free_pages(gfp_t gfp_mask, ungigned int order) //直接放回第一个页的逻辑地址。
只需要一页:
struct page * alloc_page(gfp_t gfp_mask)
unsigned long __get_free_page(gfp_t gfp_mask)

3.1 获得填充为0的页

unsigned long get_zeroed_page(unsigned int gfp_mask)
在这里插入图片描述

3.2 释放页

void __free_pages(struct page *page, unsigned int order)
void free_pages(unsigned long addr, unsigned int order)
void __free_pages(unsigned long addr)

#define GFP_KERNEL(__GFP_WAIT | __GFP_IO | __GFP_FS)
__GFP_WAIT : 缺内存页的时候可以睡眠;
__GFP_IO : 允许启动磁盘IO;
__GFP_FS : 允许启动文件系统IO。

4. kmalloc():物理地址和虚拟地址都连续

  • kmalloc()可以获得以字节为单位的一块内核内存。分配的内存在物理上连续的。
    void * kmalloc(size_t size, gfp_t flags) //返回至少有size大小的内存块指针。flags可以为GFP_KERNEL

4.1 gfp_mask标志

三类:行为修饰符、区修饰符及类型。
行为修饰符:表示内核应当如何分配所需的内存。
在这里插入图片描述
在这里插入图片描述
区修饰符:表示内存区应当从何处分配。
在这里插入图片描述
不能给__get_free_pages()或kalloc()指定ZONE_HIGHMEM,因为返回逻辑地址,而不是page。只有alloc_pages()才可以分配高端内存。
类型标志:指定所需的行为和区描述符。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2 kfree()

void kfree(const void *ptr)
kfree(NULL)是安全的。

5. vmalloc():虚拟地址连续,物理地址地址无须连续

  • vmalloc()分配的内存虚拟地址是连续的,物理地址则无须连续。
  • kmalloc()分配的物理地址和虚拟地址都是连续的。
  • 用户空间malloc()返回的页在进程的虚拟地址空间内是连续的,但是不保证物理RAM中也是连续的。

void * vmalloc(unsigned long size) //返回大小至少为size的逻辑连续的内存区。
````void vfree(const void *addr) //释放通过vmalloc()所获得到的内存。```

6. slab层

slab分配器是基于对象进行管理的,所谓的对象就是内核中的数据结构(例如:task_struct,file_struct 等)。相同类型的对象归为一类,每当要申请这样一个对象时,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免内部碎片。slab分配器并不丢弃已经分配的对象,而是释放并把它们保存在内存中。slab分配对象时,会使用最近释放的对象的内存块,因此其驻留在cpu高速缓存中的概率会大大提高。
在这里插入图片描述
简要分析下这个图:kmem_cache是一个cache_chain的链表,描述了一个高速缓存,每个高速缓存包含了一个slabs的列表,这通常是一段连续的内存块。存在3种slab:slabs_full(完全分配的slab),slabs_partial(部分分配的slab),slabs_empty(空slab,或者没有对象被分配)。slab是slab分配器的最小单位,在实现上一个slab有一个货多个连续的物理页组成(通常只有一页)。单个slab可以在slab链表之间移动,例如如果一个半满slab被分配了对象后变满了,就要从slabs_partial中被删除,同时插入到slabs_full中去。
举例说明:如果有一个名叫inode_cachep的struct kmem_cache节点,它存放了一些inode对象。当内核请求分配一个新的inode对象时,slab分配器就开始工作了:

  • 首先要查看inode_cachep的slabs_partial链表,如果slabs_partial非空,就从中选中一个slab,返回一个指向已分配但未使用的inode结构的指针。完事之后,如果这个slab满了,就把它从slabs_partial中删除,插入到slabs_full中去,结束;
  • 如果slabs_partial为空,也就是没有半满的slab,就会到slabs_empty中寻找。如果slabs_empty非空,就选中一个slab,返回一个指向已分配但未使用的inode结构的指针,然后将这个slab从slabs_empty中删除,插入到slabs_partial(或者slab_full)中去,结束;
  • 如果slabs_empty也为空,那么没办法,cache内存已经不足,只能新创建一个slab了。
    https://www.cnblogs.com/wangzahngjun/p/4977425.html

slab分配器扮演了通用数据结构缓存层的角色。
在这里插入图片描述

6.1 slab层的设计

slab层把不同的对象划分为所谓告诉缓存组,其中每个缓存组都存放不同类型的对象,每种对象类型对应一个告诉缓存。
slab由一个或多个物理上连续的页组成。每个高速缓存由多个slab组成。
每个slab处于状态:满、部分满或空。
在这里插入图片描述
在这里插入图片描述

6.2 slab分配器的接口

一个新的高速缓存创建函数:
在这里插入图片描述s
name:高速缓存的名字;
size:高速缓存中每个元素的大小;
align:slab内第一个对象的偏移,确保在页内进行特定的对齐,0表示标准对齐;
flags:可选设置项,用来控制高速缓存的行为,0表示没有特殊行为。
撤销高速缓存:
在这里插入图片描述

  • 从缓存中分配:
    创建高速缓存后,调用:
    void * kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags) //返回一个指向对象的指针,flags的值传递给_get_free_pages()。
    释放一个对象:
    void kmem_cache_free(struct kmem_cache *cachep, void *objp) //将cachep中的objp对象标志空。

着色:https://www.cnblogs.com/linhaostudy/p/13184704.html

7. 在栈上静态分配

32位和64位栈一般为2页,为8KB和16KB。
内核栈,中断栈

8. 高端内存的映射

高端内存中的页不能永久映射到内核地址空间上。用alloc_pages()函数以__GFP_HIGHMEM标志获得到的页不可能有逻辑地址。

8.1 永久映射

映射一个给定的page结构到内核地址空间:
void *kmap(struct page *page) //page如果是低端内存则单纯的返回页的虚拟地址,如果是高端内存则建立永久映射地址,该函数可以睡眠,只能用于进程上下文。
解除映射:
void kunmap(struct page *page)

8.2 临时映射

当必须创建一个映射而当前的上下文又不能睡眠时,内核提供了临时映射(原子映射)。内核可以原子地把高端内存中的一个页映射到某个保留的映射中,因此临时映射可以用在不能睡眠的地方,比如中断处理程序中。
建立临时映射函数:
void * kmap_atomic(struct page *page, enum km_type type)
这个函数不会阻塞,可以用在中断上下文和其他不能重新调度的地方,也禁止内核抢占。
在这里插入图片描述
取消映射:
void kunmap_atomic(void *kvaddr, enum km_type type)

9. 每个CPU的分配

每个CPU的数据存放在一个数组中。

10. 新的每个CPU接口

10.1 编译是的每个CPU数据

10.2 运行时的每个CPU数据

11. 使用每个CPU的原因

  • 减少数据锁定,每个处理器访问每个CPU数据的逻辑,不需要任何锁;
  • 减少缓存失效。

12. 分配函数的选择

  • 连续物理页,用kmalloc();
  • 高端内存用alloc_pages();
  • 不需要连续的物理页,vmalloc()
  • 创建和撤销很多大的数据结构,用slab高速缓存,

13. 小结



这篇关于内存管理基础(Linux内核涉及与实现)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程