buddy伙伴系统

Linux内存管理简述

Linux的内存管理不仅与处理器的架构相关,还要综合考虑性能需求。在Linux内核内存管理的框架中,最底层的page allocator是对物理内存进行管理的模块,负责管理所有的物理内存,分配和释放都是以page为单位,大小是2^N个连续的物理内存页。所有的内存管理都是以page allocator为基础,采用的算法为经典的Buddy(伙伴算法)。

对于UMA架构,比如SMP,只有一个物理内存节点,但是对于服务器的NUMA架构,会有多个物理内存节点。Linux为了管理所有内存节点的物理内存,将不同的节点称之为node,用专门的结构体进行管理。每个node又会划分成不同的区域(zone)。每个zone管理着属于自己的pages。

Buddy系统

不论是哪种架构,内存管理的基础都是page allocator。page allocator负责管理所有物理内存,采用的算法就是经典的Buddy算法(伙伴系统)。下面这幅图就是buddy系统的对物理内存管理的整个框架:

buddy系统的整体框架

在这里插入图片描述
大概的说一下上面这个框架,这其中是包含了3部分,一个是buddy系统两个主要的数据结构free_area和free_list的关系,第二个是Linux的反碎片技术,第三个就是buddy对空闲物理页的组织。

buddy的主要数据结构

buddy的主要数据结构有两个:free_areafree_list。第一个是为了组织和管理空闲的物理页面,第二个是内核的反碎片技术的具体实现。

free_area定义在struct zone中:
在这里插入图片描述
free_area是一个结构体数组。buddy将连续的空闲页面按照2^order的大小组织在一个free_area数组中,每个zone都有一个这样的数组。free_area[0]就表示所指向的空闲块链表中,每个块的大小为2 ^0=1个page。

其中,MAX_ORDER是内核规定的一个空闲页块所能包含的最大页数:
在这里插入图片描述
free_area数组最多有11个元素,free_area[10]就指向了最大空闲块的链表,这个链表中每个空闲块为2^10个页,也就是4M,所以一次能申请到的最大空闲内存块就是4M。

struct free_area结构体:
在这里插入图片描述
这里面free_list才真正指向了空闲块链表,这里面的MIGRATE_TYPES是内核中使用的反碎片技术。

内核的反碎片技术

首先按照物理页的属性分为不同的类型,也就是MIGRATE_TYPE(页面迁移类型)。内核将MIGRATE_TYPE分为五种类型:
在这里插入图片描述
对于Linux内核来说,主要是3种类型:UNMOVABLE(不可移动),RECLAIMABLE(可回收),MOVABLE(可移动)。反碎片技术就是在分配的时候考虑到这些页的属性,比如不可移动的页不能位于可移动的内存区的中间,否则就无法从该内存区域获得较大的连续物理内存。

buddy的核心函数:

不论是UMA架构,还是NUMA架构,最终都会调用到__alloc_pages_nodemask(),因为这个函数会进行实际物理页面的分配。
在这里插入图片描述
这个函数我还没有太看完,所以这里就先说一些关于这个函数细节的东西:

gfp_mask

gfp是get free page的意思,它只是一个传入的标志掩码,重要的是它的类型gfp_t

gfp_t类型

gfp_t是内核使用的数据类型,内核使用的数据通常对类型安全的要求非常严格。
在这里插入图片描述
可以看到这里使用typedef__bitwise__重新定义了一个类型就是gfp_t__bitwise__是什么东西?在内核代码里这个东西应该是很常见的,它的作用就是防止不同字节序类型的数据进行运算。
在这里插入图片描述
上面的__le16表示16位小端模式,__be16表示是16位大端模式。加上__bitwise表示这些数据类型将是字节序敏感的,绝对禁止他们之间的运算。

__ bitwise __
__bitwise__是按位的意思,这个功能是给sparse(内核代码的静态分析工具)用的,内核的一些数据类型经常会用它修饰。
在这里插入图片描述
如果定义了__CHECKER__宏,就会使能sparse的检查功能,而且sparse支持GCC的__attribute __((……))功能。bitwise用来确保不同位方式类型不会被弄混(大端模式,小端模式等)。

__alloc_pages_nodemask( )做的第一件事就是要确定在哪个zone_type中分配内存,所以它会调用gfp_zone( ),根据传入的gfp_mask标志掩码来确定zone的类型。

zone type

Linux将一个node划分为以下几种类型:
在这里插入图片描述
对于32位来说,就是ZONE_DMAZONE_NORMALZONE_HIGHMEM,对于64位,已经不需要高端内存,所以没有了ZONE_HIGHMEM区域,而是ZONE_DMAZONE_NORMAL,而且64位需要兼容32位,所以还会划分一个区域ZONE_DMA32

gfp_zone( )

gfp_zone函数可以通过传入的标志位掩码gfp_mask来确定需要分配内存的区域。
在这里插入图片描述
注意这一句:

int bit = flags & GFP_ZONEMASK;

GFP_ZONEMASK掩码标志,本质是一个宏
在这里插入图片描述
这个宏经过__GFP_DMA__GFP_HIGHMEM__GFP_DMA32__GFP_MOVABLE4个域掩码按位或得到。

__GFP_DMA__GFP_HIGHMEM__GFP_DMA32__GFP_MOVABLE的值定义如下:
在这里插入图片描述
将这4个域掩码的值按位或,即0x01 | 0x02 | 0x04 | 0x08 = 1111,所以GFP_ZONEMASK就等于1111,这个值是一个固定的值,作为参与后期运算的掩码而设计。这里,注意以下gfp_t的类型,是__force。刚才在上面说过被__biewise修饰的类型,即使强制类型转换都会被sparse警告,但是有些类型确实需要进行类型转换,总不能一棒子全打死,所以能破解这种警告的就是使用__force来标明对应的类型。
在这里插入图片描述
__bitwise一样,force也是sparse的属性,内核将force重新包装了一下,使用__force来表示这种属性。使用了__force标明一个类型后,sparse就不再报出警告。对于gcc来说,在编译的时候,__force是透明的,gcc只会看到gfp_t,而看不到前面的__force属性。

gfp_zone( )的代码中,可以看到gfp_zone( )依赖两张表:GFP_ZONE_TABLEGFP_ZONE_BAD

GFP_ZONE_TABLE

在这里插入图片描述
这个GFP_ZOEN_TABLE看着很长,实际它是一个组装后的结果,只要将它一步一步重新组装就知道它怎么来的了。前面说过掩码位标志有4种,分别是__GFP_DMA__GFP_HIGHMEM__GFP_DMA32__GFP_MOVABLE,2的4次方就有16种组合,这16种组合的结果如下:
在这里插入图片描述
内核规定__GFP_DMA__GFP_HIGHMEM__GFP_DMA32这3个标志不能两个或全部出现,其中BAD就表示这种错误的组合。将所有的BAD组合或起来就构成了GFP_ZONE_BAD表。除了BAD外的情况就是内核允许的情况,将这些组合或在一起就构成了GFP_ZONE_TABLE表。

关于这两张表有什么用,怎么组成的,还有__alloc_pages_nodemask( )函数剩余部分的分析下次周报再发,我还没有全部分析完。

猜你喜欢

转载自blog.csdn.net/weixin_44395686/article/details/106041176
今日推荐