转自:https://www.jianshu.com/p/eecbb1506eee
Linux 内存管理
1 页的概念
linux 内核中把物理页作为内存分配的最小单位,32位CPU 页的大小通常为4K,64位的CPU通常支持8K的也。内存管理单元MMU 同样以页为大小分配内存。
2 内核虚拟地址分区和物理内存分区
在32位内核中,内核虚拟地址空间为0-4G,其中用户态为1-3G空间,内核态为3G-4G,内核空间根据物理地址的特性大概可以分为三个区:
区 | 描述 | 32位系统物理内存大小 |
---|---|---|
ZONE_DMA | 和硬件操作相关的内存区域 | < 16M |
ZONE_NORMAL | 内核正常映射的物理页 | 16 - 896M |
ZONE_HIGH | 高端内存,由于内核空间大小的原理部分页不能永久的映射到内核,需要动态映射的 | > 896M |
下面的图描述了内核地址空间和物理内存的映射关系:
Linux 内核启动后的mm 的初始化过程:
/*
* Set up kernel memory allocators
*/
static void __init mm_init(void)
{
/*
* page_ext requires contiguous pages,
* bigger than MAX_ORDER unless SPARSEMEM.
*/
page_ext_init_flatmem();
mem_init();
kmem_cache_init();
percpu_init_late();
pgtable_init();
vmalloc_init();
ioremap_huge_init();
}
3伙伴系统算法
3.1 简介
在实际应用中,经常需要分配一组连续的页,而频繁地申请和释放不同大小的连续页,必然导致在已分配页框的内存块中分散了许多小块的空闲页框。这样,即使这些页框是空闲的,其他需要分配连续页框的应用也很难得到满足。为了避免出现这种情况,Linux内核中引入了伙伴系统算法(buddy system)。把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。最大可以申请1024个连续页框,对应4MB大小的连续内存。每个页框块的第一个页框的物理地址是该块大小的整数倍。
假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,如果没有,就去512个页框的链表中找,找到了则将页框块分为2个256个页框的块,一个分配给应用,另外一个移到256个页框的链表中。如果512个页框的链表中仍没有空闲块,继续向1024个页框的链表查找,如果仍然没有,则返回错误。页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块。
mem_init() 函数中会把内核启动后的空闲内存用buddy 系统管理。
3.2 伙伴系统算法分配函数
mem_init 初始化完伙伴系统后通过 alloc_page(s) 函数分配伙伴系统内存池的内存。
函数 | 描述 |
---|---|
struct page * alloc_page(unsigned int gfp_mask) | 分配一页物理内存并返回该页物理内存的page结构指针 |
struct page * alloc_pages(unsigned int gfp_mask, unsigned int order) | 分配2的order次方连续的物理页并返回分配的第一个物理页的page结构指针 |
unsigned long get_free_page(unsigned int gfp_mask) | 只分配一页,返回页的逻辑地址 |
unsigned long __get_free_pages(unsigned int gfp_mask, unsigned int order) | 分配 2的order页,返回也是逻辑地址 |
3.3 get_free_page(s)与alloc_page(s)的差异
alloc_page alloc_pages 分配后还不能直接使用, 需要得到该页对应的虚拟地址
- void *page_address(struct page *page);
- 低端内存的映射方式:__va((unsigned long)(page - mem_map) << 12)
- 高端内存到映射方式:struct page_address_map分配一个动态结构来管理高端内存。(内核是访问不到vma的3G以下的虚拟地址的) 具体映射由kmap / kmap_atomic执行。
get_free_page(s)与alloc_page(s)系列最大的区别是无法申请高端内存,因为它返回到是一个逻辑地址,而高端内存是需要额外映射才可以
Android x86 的buffyinfo.
以Normal区域进行分析,第二列值为459,表示当前系统中normal区域,可用的连续两页的内存大小为459*2^1*PAGE_SIZE;第三列值为52,表示当前系统中normal区域,可用的连续四页的内存大小为52*2^2*PAGE_SIZE
generic_x86:/ # cat /proc/buddyinfo
Node 0, zone DMA 4 1 2 2 3 2 3 1 2 0 1
Node 0, zone Normal 1186 459 220 142 25 13 2 0 1 2 138
Node 0, zone HighMem 87 74 12 9 0 1 1 0 0 0 0
4 Slab 内存分配算法
Slab 内存分配算法 和Java中的对象池是一个概念。采用buddy算法,解决了外碎片问题,这种方法适合大块内存请求,不适合小内存区请求
4.1 Slab 内存分配算法
slab分配器源于 Solaris 2.4 的分配算法,工作于物理内存页框分配器之上,管理特定大小对象的缓存,进行快速而高效的内存分配。slab分配器为每种使用的内核对象建立单独的缓冲区。Linux 内核已经采用了伙伴系统管理物理内存页框,因此 slab分配器直接工作于伙伴系统之上。每种缓冲区由多个 slab 组成,每个 slab就是一组连续的物理内存页框,被划分成了固定数目的对象。根据对象大小的不同,缺省情况下一个 slab 最多可以由 1024个页框构成。出于对齐等其它方面的要求,slab 中分配给对象的内存可能大于用户要求的对象实际大小,这会造成一定的内存浪费。
Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为SunOS 操作系统首次引入的一种算法。Jeff的分配器是围绕对象缓存进行的。在内核中,会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存。Jeff发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。因此他的结论是不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。例如,如果内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可。后续的内存分配不需要执行这个初始化函数,因为从上次释放和调用析构之后,它已经处于所需的状态中了。
4.2 Slab 内存结构
kmem_cache_alloc 分配的所有的内存块在内核中以链表的形式组织。
kmem_cache_alloc 从buddy系统分配到内存后,在内部被分为 slab 单元,这是一段连续的内存块(通常都是页面)。所有的对象都分配在这些slab 单元上,这些slab 单元被组织为三个链表:
- slabs_full 完全分配的 slab
- slabs_partial 部分分配的 slab
- slabs_free 可以回收的 slab
4.3 slab 着色区和slab 结构
每个Slab的首部都有一个小小的区域是不用的,称为“着色区(coloring area)”。着色区的大小使Slab中的每个对象的起始地址都按高速缓存中的”缓存行(cache line)”大小进行对齐(80386的一级高速缓存行大小为16字节,Pentium为32字节)。因为Slab是由1个页面或多个页面(最多为32)组成,因此,每个Slab都是从一个页面边界开始的,它自然按高速缓存的缓冲行对齐。但是,Slab中的对象大小不确定,设置着色区的目的就是将Slab中第一个对象的起始地址往后推到与缓冲行对齐的位置。每个Slab上最后一个对象以后也有个小小的区是不用的,这是对着色区大小的补偿,其大小取决于着色区的大小,以及Slab与其每个对象的相对大小。
4.4 Slab 内存函数
mm_init --> kmem_cache_init(); kernel 初始化
- kmem_cache_t* xx_cache; // 链表头
- 创建: xx_cache = kmem_cache_create("name", sizeof(struct xx), SLAB_HWCACHE_ALIGN, NULL, NULL);
- 分配: kmem_cache_alloc(xx_cache, GFP_KERNEL);
- 释放: kmem_cache_free(xx_cache, addr);
slab 内存用结构体 kmem_cache_t 表示:
4.5 slabinfo对象
从 /proc/slabinfo 中看一看出,内核为大结构体使用了slab 缓存。如ext4_inode_cache vm_area_struct task_struct等。
generic_x86:/ # cat /proc/slabinfo
slabinfo - version: 2.1
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
...
ext4_inode_cache 2025 2025 632 25 4 : tunables 0 0 0 : slabdata 81 81 0
ext4_allocation_context 156 156 104 39 1 : tunables 0 0 0 : slabdata 4 4 0
ext4_prealloc_space 224 224 72 56 1 : tunables 0 0 0 : slabdata 4 4 0
ext4_io_end 408 408 40 102 1 : tunables 0 0 0 : slabdata 4 4 0
ext4_extent_status 2048 2048 32 128 1 : tunables 0 0 0 : slabdata 16 16 0
...
vm_area_struct 20791 22402 88 46 1 : tunables 0 0 0 : slabdata 487 487 0
mm_struct 85 85 480 17 2 : tunables 0 0 0 : slabdata 5 5 0
...
task_struct 621 621 1184 27 8 : tunables 0 0 0 : slabdata 23 23 0
...
kmalloc-8192 28 28 8192 4 8 : tunables 0 0 0 : slabdata 7 7 0
kmalloc-4096 96 104 4096 8 8 : tunables 0 0 0 : slabdata 13 13 0
kmalloc-2048 128 128 2048 16 8 : tunables 0 0 0 : slabdata 8 8 0
kmalloc-1024 336 336 1024 16 4 : tunables 0 0 0 : slabdata 21 21 0
kmalloc-512 752 752 512 16 2 : tunables 0 0 0 : slabdata 47 47 0
kmalloc-256 698 752 256 16 1 : tunables 0 0 0 : slabdata 47 47 0
kmalloc-192 903 903 192 21 1 : tunables 0 0 0 : slabdata 43 43 0
kmalloc-128 1760 1760 128 32 1 : tunables 0 0 0 : slabdata 55 55