通用和专用缓存
通过命令sudo cat /proc/slabinfo可查看系统当前 slab 使用情况。以vm_area_struct结构体为例,当前系统已分配了 13014 个vm_area_struct缓存,每个大小为 216 字节,其中 active 的有 12392 个。
[root@VM-8-9-centos]# 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>
...
vm_area_struct 12392 13014 216 18 1 : tunables 0 0 0 : slabdata 723 723 0
mm_struct 184 200 1600 20 8 : tunables 0 0 0 : slabdata 10 10 0
shared_policy_node 5015 5015 48 85 1 : tunables 0 0 0 : slabdata 59 59 0
numa_policy 15 15 264 15 1 : tunables 0 0 0 : slabdata 1 1 0
radix_tree_node 15392 17234 584 14 2 : tunables 0 0 0 : slabdata 1231 1231 0
idr_layer_cache 240 240 2112 15 8 : tunables 0 0 0 : slabdata 16 16 0
kmalloc-8192 39 44 8192 4 8 : tunables 0 0 0 : slabdata 11 11 0
kmalloc-4096 117 144 4096 8 8 : tunables 0 0 0 : slabdata 18 18 0
kmalloc-2048 458 528 2048 16 8 : tunables 0 0 0 : slabdata 33 33 0
kmalloc-1024 1399 1424 1024 16 4 : tunables 0 0 0 : slabdata 89 89 0
kmalloc-512 763 800 512 16 2 : tunables 0 0 0 : slabdata 50 50 0
kmalloc-256 3132 3376 256 16 1 : tunables 0 0 0 : slabdata 211 211 0
kmalloc-192 2300 2352 192 21 1 : tunables 0 0 0 : slabdata 112 112 0
kmalloc-128 1376 1376 128 32 1 : tunables 0 0 0 : slabdata 43 43 0
kmalloc-96 1596 1596 96 42 1 : tunables 0 0 0 : slabdata 38 38 0
kmalloc-64 16632 20800 64 64 1 : tunables 0 0 0 : slabdata 325 325 0
kmalloc-32 1664 1664 32 128 1 : tunables 0 0 0 : slabdata 13 13 0
kmalloc-16 4608 4608 16 256 1 : tunables 0 0 0 : slabdata 18 18 0
kmalloc-8 4096 4096 8 512 1 : tunables 0 0 0 : slabdata 8 8 0
...
可以看到,系统中存在的含有具体名字的 slab 我们称其为 专用 slab,用来为特定结构体分配内存,如 vm_area_struct、mm_struct 等。而其他形如 kmalloc-xxx 的 slab,我们称其为通用型 slab,用来满足分配通用内存。
kmalloc 是基于slab 分配器的,kmalloc() 分配的虚拟地址范围在内核空间的「直接内存映射区」。按字节为单位虚拟内存,一般用于分配小块内存,释放内存对应于 kfree ,可以分配连续的物理内存。函数原型在 <linux/kmalloc.h> 中声明,一般情况下在驱动程序中都是调用 kmalloc() 来给数据结构分配内存 。
为什么要分专用和通用 slab ? 最直观的一个原因就是通用 slab 会造成内存浪费:出于 slab 管理的方便,每个 slab 管理的对象大小都是一致的,当我们需要分配一个处于 64-96字节中间大小的对象时,就必须从保存 96 字节的 slab 中分配。而对于专用的 slab,其管理的都是同一个结构体实例,申请一个就给一个恰好内存大小的对象,这就可以充分利用空间。
非连续内存区管理
vmalloc
伙伴系统也好、slab技术也好,从内存管理理论角度而言目的基本是一致的,它们都是为了防止“碎片”。不过度片又分为外部碎片和内部碎片之说,所谓内部碎片是说系统为了满足一小段内存区(连续)的需要,不得不分配了一大区域连续内存给它,从而形成了空间浪费;外部碎片是指系统虽有足够的内存,但倒是分散的碎片,没法知足对大块“连续内存”的需求。不管何种碎片都是系统有效利用内存的障碍。slab分配器使得一个页面内包含的众多小块内存可独立被分配使用,避免了内部碎片,节约了空闲内存。伙伴系统把内存块按大小分组管理,必定程度上减轻了外部碎片的危害,由于页框分配不再盲目,而是按照大小依次有序进行,不过伙伴关系只是减轻了外部碎片,但并未完全消除。
因此避免外部碎片的最终思路仍是落到了如何利用不连续的内存块组合成“看起来很大的内存块”——这里的状况很相似于用户空间分配虚拟内存,内存逻辑上连续,其实映射到并不必定连续的物理内存上。Linux内核借用了这个技术,容许内核程序在内核地址空间中分配虚拟地址,一样也利用页表(内核页表)将虚拟地址映射到分散的内存页上。以此完美地解决了内核内存使用中的外部碎片问题。内核提供vmalloc函数分配内核虚拟内存,该函数不一样于kmalloc,它能够分配较kmalloc大得多的内存空间(可远大于128K,但必须是页大小的倍数),但相比kmalloc来讲,vmalloc需要对内核虚拟地址进行重映射,必须更新内核页表,所以分配效率上要低一些(用时间换空间)
由get_free_page或kmalloc函数所分配的连续内存都限于物理映射区域,因此它们返回的内核虚拟地址和实际物理地址仅仅是相差一个偏移量(PAGE_OFFSET),你能够很方便的将其转化为物理内存地址,同时内核也提供了virt_to_phys()函数将内核虚拟空间中的物理映射区地址转化为物理地址。物理内存映射区中的地址与内核页表是有序对应的,系统中的每一个物理页面均可以找到它对应的内核虚拟地址(在物理内存映射区中的)。
而vmalloc分配的地址则限于vmalloc_start与vmalloc_end之间。每一块vmalloc分配的内核虚拟内存都对应一个vm_struct结构体(可别和vm_area_struct搞混,那是进程虚拟内存区域的结构),不一样的内核虚拟地址被4k大小的空闲区间隔,以防止越界。与进程虚拟地址的特性同样,这些虚拟地址与物理内存没有简单的位移关系,必须经过内核页表才可转换为物理地址或物理页。它们有可能还没有被映射,在发生缺页时才真正分配物理页面。
vmalloc和kmalloc的区别
1,kmalloc对应于kfree, 分配的内存处于3GB~high memory之间,这段内核空间与物理内存的映射一一对应,可以分配连续的物理内存;
vmalloc对应于vfree, 分配的内存在VMALLOC_START ~ 4GB之间,分配连续的虚拟内存,但物理上不一定连续。
2,kmalloc分配内存是基于slab, 因此slab的一些特性包括着色,对齐等都具备,性能较好。物理地址和逻辑地址都是连续的。
3,最主要的区别是分配大小的问题,比如你需要28个字节,那一定用kmalloc。而vmalloc用于大块内存的分配。
尽管仅仅在某些情况下才需要物理上连续的内存块,但是很多内核代码都调用kmalloc(), 而不是vmalloc()获得内存。这主要是出于性能的考虑,vmalloc()函数为了把物理上不连续的页面转换为虚拟地址空间上的连续的页,必须建立页表才行。还有通过vmalloc()获得的页必须一个个的进行映射(因为它们物理上不是连续的),这就会导致比直接内存映射大得多的缓冲区刷新。因为这些原因,vmalloc()仅在绝对必要时才会使用——典型的就是为了获得大块内存时,例如,当模块被动态插入到内核中时,就把模块装在到由vmalloc()分配的内存上。
下面的图总结了上述两种用户空间和内核空间虚拟内存分配方式。
总结
本文首先回顾了内存虚拟化技术,介绍了其中的分段、分页具体原理;再描述了地址空间,其中包括用户空间和内核空间的划分;最后介绍了Linux物理页框管理、内存区管理和非连续内存区管理的内容。
操作系统中的很多思想和经典的算法,我们都可以在日常开发使用的各种工具或者框架中找到它们的影子,例如利用局部性原理缓存优化,batch移动CPU空闲对象等。Linux内存管理是一个非常复杂的系统,本文所述只是冰山一角,从宏观角度展现内存管理的全貌。当然也希望大家能够通过阅读了解更深层次的原理。
推荐书籍:
- 《操作系统导论》
- 《深入理解Linux内核》