持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第27天,点击查看活动详情
内存碎片化是内存管理中一个比较难的课题,本节介绍Linux 5.0内核在解决内存外碎片化时做的一项优化。
伙伴系统算法如何减少内存碎片
Linux内核在采用伙伴系统算法时考虑了如何减少内存碎片。在伙伴系统算法中,什么样的内存块可以成为伙伴呢?其实伙伴系统算法有如下3个基本条件。
- 两个块大小相同。
- 两个块地址连续。
- 两个块必须是从同一个大块中分离出来的。
一个有8个页面的大内存块A0,可以切割成两个小内存块B0和B1,它们都有4个页面。B0还可以继续切割成C0和C1,它们是两个页面大小的内存块。C0可以继续切割成P0和P1两个小内存块,它们有1个物理页面。
第一个条件是两个内存块大小必须相同,在图5.33中,B0内存块和B1内存块就是大小相同的。第二个条件是两个内存块地址连续,伙伴就是邻居的意思。第三个条件是两个内存块必须是从同一个大内存块中分离出来的,下面来具体解释。
0和P1为伙伴,它们都是从C0中分离出来的,P2和P3为伙伴,它们也是从C1中分离出来的。假设P1和P2合并成一个新的内存块C_new0,然后P4、P5、P6和P7合并成一个大内存块B_new0,会发现即使P0和P3变成了空闲页面,这8个页面的内存块无法继续合并成一个新的大内存块。P0和C_new0无法合并成一个大内存块,因为它们的大小不一样,同样C_new0和P3也不能继续合并。因此P0和P3就变成了“空洞”,这就是外碎片化(external fragmentation)。随着时间的推移,外碎片化会变得越来越严重,内存利用率也随之下降。
外碎片化的一个比较严重的后果是明明系统有足够的内存,但是无法分配出一大块连续的物理内存供页面分配器使用。因此,伙伴系统算法在设计时就考虑避免图5.34所示的内存外碎片化。 学术上常用的解决外碎片化的技术叫作内存规整,也就是移动页面的位置让空闲页面连成一片。但是在早期的Linux内核中,这种方法不一定有效。内核分配的物理内存有很多种用途,如内核本身使用的内存、硬件需要使用的内存,如DMA缓冲区、用户进程分配的内存(如匿名页面)等。如果从页面的迁移属性来看,用户进程分配的内存是可以迁移的,但是内核本身使用的内存页面是不能随便迁移的。假设在一大块物理内存中,中间有一小块内存被内核本身使用,但是因为这小块内存不能迁移,导致这一大块内存不能变成连续的物理内存。
为什么内核本身使用的页面不能迁移呢?因为要迁移这个页面,首先需要把物理页面的映射关系断开,然后重新建立映射关系。 在这个断开映射关系的过程中,如果内核继续访问这个物理页面,就会访问不正确的指针和内存,导致内核出现oops错误,甚至导致系统崩溃(crash)。内核是一个敏感区域,它必须保证其使用的内存是安全的。这和用户进程不太一样,用户进程使用的页面在断开映射关系之后,如果用户进程继续访问这个页面,就会产生一个缺页异常。在缺页异常处理中,会重新分配一个物理页面,然后和虚拟内存建立映射关系。这个过程对于用户进程来说是安全的。