在学习c语言时,我们常常会使用到malloc()去申请一块内存空间,用于存放我们的数据。刚开始我们只要知道申请内存时使用用malloc去申请一块就可以,而其中的原理我们并不关心。但是随着我们对运行环境的要求越来越多样化,复杂化,以及对稳定性以及性能问题的要求逐渐变得越来越重要时,我们往往需要关注到性能问题。而研究性能问问,如果只停留在知道使用malloc()可以去申请一块内存空间是远远不够的,此时我们就需要去研究相关的原理和代码。
在Linux中,最先推出用于分配内存的管理单元和算法是伙伴分配器(buddy allocator),伙伴分配器是以页为单位管理和分配内存。但是伙伴分配器要求所有分配单元是2的幂,而在内核中的需求却以字节为单位(在内核中面临频繁的结构体内存分配问题)。假如我们需要动态申请一个内核结构体(占 20 字节),若仍然分配一页内存,这将严重浪费内存。这也将会导致内部碎片,也就是我们常说的内存碎片问题。那么该如何分配才能减少内存碎片呢?
slab 分配器专为小内存分配而生,slab分配器分配内存以字节为单位,基于伙伴分配器的大内存进一步细分成小内存分配。换句话说,slab 分配器仍然从 Buddy 分配器中申请内存,之后自己对申请来的内存细分管理。从而达到减少内存碎片化的目的。
我们可以把伙伴分配器理解成一个仓库,它提供给商店(slab)进行批发的货物,商店(slab)从仓库(伙伴分配器)进货以后,再零售给消费者(使用kmalloc的用户)。
除了提供小内存外,slab 分配器的第二个任务是维护常用对象的缓存。对于内核中使用的许多结构,初始化对象所需的时间可等于或超过为其分配空间的成本。当创建一个新的slab 时,许多对象将被打包到其中并使用构造函数(如果有)进行初始化。释放对象后,它会保持其初始化状态,这样可以快速分配对象。
SLAB分配器的最后一项任务是提高CPU硬件缓存的利用率。 如果将对象包装到SLAB中后仍有剩余空间,则将剩余空间用于为SLAB着色。 SLAB着色是一种尝试使不同SLAB中的对象使用CPU硬件缓存中不同行的方案。 通过将对象放置在SLAB中的不同起始偏移处,对象可能会在CPU缓存中使用不同的行,从而有助于确保来自同一SLAB缓存的对象不太可能相互刷新。 通过这种方案,原本被浪费掉的空间可以实现一项新功能。
root@test-PC:~# cat /proc/slabinfo
slabinfo - version: 2.1
# name : tunables : slabdata
kvm_async_pf 0 0 136 30 1 : tunables 0 0 0 : slabdata 0 0 0
kvm_vcpu 0 0 11264 1 4 : tunables 0 0 0 : slabdata 0 0 0
kvm_mmu_page_header 0 0 168 24 1 : tunables 0 0 0 : slabdata 0 0 0
x86_emulator 0 0 2672 12 8 : tunables 0 0 0 : slabdata 0 0 0
x86_fpu 0 0 4160 7 8 : tunables 0 0 0 : slabdata 0 0 0
fsverity_info 0 0 256 32 2 : tunables 0 0 0 : slabdata 0 0 0
ip6-frags 0 0 184 44 2 : tunables 0 0 0 : slabdata 0 0 0
PINGv6 260 260 1216 26 8 : tunables 0 0 0 : slabdata 10 10 0
RAWv6 416 416 1216 26 8 : tunables 0 0 0 : slabdata 16 16 0
UDPv6 384 384 1344 24 8 : tunables 0 0 0 : slabdata 16 16 0
tw_sock_TCPv6 0 0 248 33 2 : tunables 0 0 0 : slabdata 0 0 0
request_sock_TCPv6 0 0 304 26 2 : tunables 0 0 0 : slabdata 0 0 0
TCPv6 234 234 2432 13 8 : tunables 0 0 0 : slabdata 18 18 0
ashmem_area_cache 0 0 312 26 2 : tunables 0 0 0 : slabdata 0 0 0
kcopyd_job 0 0 3312 9 8 : tunables 0 0 0 : slabdata 0 0 0
dm_uevent 0 0 2888 11 8 : tunables 0 0 0 : slabdata 0 0 0
scsi_sense_cache 416 416 128 32 1 : tunables 0 0 0 : slabdata 13 13 0
mqueue_inode_cache 102 102 960 34 8 : tunables 0 0 0 : slabdata 3 3 0
fuse_request 390 390 152 26 1 : tunables 0 0 0 : slabdata 15 15 0
fuse_inode 78 78 832 39 8 : tunables 0 0 0 : slabdata 2 2 0
ecryptfs_inode_cache 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0
ecryptfs_file_cache 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0
ecryptfs_auth_tok_list_item 0 0 832 39 8 : tunables 0 0 0 : slabdata 0 0 0
fat_inode_cache 0 0 744 44 8 : tunables 0 0 0 : slabdata 0 0 0
fat_cache 0 0 40 102 1 : tunables 0 0 0 : slabdata 0 0 0
squashfs_inode_cache 0 0 704 46 8 : tunables 0 0 0 : slabdata 0 0 0
jbd2_journal_head 3298 3298 120 34 1 : tunables 0 0 0 : slabdata 97 97 0
jbd2_revoke_table_s 768 768 16 256 1 : tunables 0 0 0 : slabdata 3 3 0
ext4_fc_dentry_update 0 0 80 51 1 : tunables 0 0 0 : slabdata 0 0 0
ext4_inode_cache 111055 114021 1184 27 8 : tunables 0 0 0 : slabdata 4223 4223 0
ext4_allocation_context 512 512 128 32 1 : tunables 0 0 0 : slabdata 16 16 0
ext4_prealloc_space 1092 1092 104 39 1 : tunables 0 0 0 : slabdata 28 28 0
ext4_system_zone 612 612 40 102 1 : tunables 0 0 0 : slabdata 6 6 0
ext4_io_end 6976 7168 64 64 1 : tunables 0 0 0 : slabdata 112 112 0
ext4_pending_reservation 6656 6656 32 128 1 : tunables 0 0 0 : slabdata 52 52 0
ext4_extent_status 44019 66708 40 102 1 : tunables 0 0 0 : slabdata 654 654 0
mbcache 13286 13286 56 73 1 : tunables 0 0 0 : slabdata 182 182 0
userfaultfd_ctx_cache 0 0 192 42 2 : tunables 0 0 0 : slabdata 0 0 0
dnotify_struct 0 0 32 128 1 : tunables 0 0 0 : slabdata 0 0 0
dio 0 0 640 25 4 : tunables 0 0 0 : slabdata 0 0 0
pid_namespace 448 448 144 28 1 : tunables 0 0 0 : slabdata 16 16 0
ip4-frags 40 40 200 40 2 : tunables 0 0 0 : slabdata 1 1 0
xfrm_state 0 0 768 42 8 : tunables 0 0 0 : slabdata 0 0 0
PING 2260 2368 1024 32 8 : tunables 0 0 0 : slabdata 74 74 0
RAW 544 544 1024 32 8 : tunables 0 0 0 : slabdata 17 17 0
tw_sock_TCP 1221 1221 248 33 2 : tunables 0 0 0 : slabdata 37 37 0
request_sock_TCP 416 416 304 26 2 : tunables 0 0 0 : slabdata 16 16 0
TCP 375 476 2240 14 8 : tunables 0 0 0 : slabdata 34 34 0
hugetlbfs_inode_cache 50 50 632 25 4 : tunables 0 0 0 : slabdata 2 2 0
dquot 1152 1152 256 32 2 : tunables 0 0 0 : slabdata 36 36 0
eventpoll_pwq 3304 3304 72 56 1 : tunables 0 0 0 : slabdata 59 59 0
dax_cache 42 42 768 42 8 : tunables 0 0 0 : slabdata 1 1 0
request_queue 48 48 2024 16 8 : tunables 0 0 0 : slabdata 3 3 0
biovec-max 308 344 4096 8 8 : tunables 0 0 0 : slabdata 43 43 0
biovec-128 288 288 2048 16 8 : tunables 0 0 0 : slabdata 18 18 0
biovec-64 576 576 1024 32 8 : tunables 0 0 0 : slabdata 18 18 0
khugepaged_mm_slot 72 72 112 36 1 : tunables 0 0 0 : slabdata 2 2 0
user_namespace 270 270 544 30 4 : tunables 0 0 0 : slabdata 9 9 0
dmaengine-unmap-256 15 15 2112 15 8 : tunables 0 0 0 : slabdata 1 1 0
dmaengine-unmap-128 30 30 1088 30 8 : tunables 0 0 0 : slabdata 1 1 0
sock_inode_cache 8293 8424 832 39 8 : tunables 0 0 0 : slabdata 216 216 0
skbuff_ext_cache 6350 6594 192 42 2 : tunables 0 0 0 : slabdata 157 157 0
skbuff_fclone_cache 1088 1088 512 32 4 : tunables 0 0 0 : slabdata 34 34 0
skbuff_head_cache 3557 3776 256 32 2 : tunables 0 0 0 : slabdata 118 118 0
file_lock_cache 740 740 216 37 2 : tunables 0 0 0 : slabdata 20 20 0
file_lock_ctx 10804 11169 56 73 1 : tunables 0 0 0 : slabdata 153 153 0
fsnotify_mark_connector 23296 23296 32 128 1 : tunables 0 0 0 : slabdata 182 182 0
net_namespace 42 42 5120 6 8 : tunables 0 0 0 : slabdata 7 7 0
x86_lbr 0 0 800 40 8 : tunables 0 0 0 : slabdata 0 0 0
task_delay_info 5508 5508 80 51 1 : tunables 0 0 0 : slabdata 108 108 0
taskstats 736 736 352 46 4 : tunables 0 0 0 : slabdata 16 16 0
proc_dir_entry 1386 1386 192 42 2 : tunables 0 0 0 : slabdata 33 33 0
pde_opener 1632 1632 40 102 1 : tunables 0 0 0 : slabdata 16 16 0
proc_inode_cache 7528 7608 680 24 4 : tunables 0 0 0 : slabdata 317 317 0
seq_file 544 544 120 34 1 : tunables 0 0 0 : slabdata 16 16 0
bdev_cache 468 468 832 39 8 : tunables 0 0 0 : slabdata 12 12 0
shmem_inode_cache 3546 3600 720 45 8 : tunables 0 0 0 : slabdata 80 80 0
kernfs_node_cache 46400 46400 128 32 1 : tunables 0 0 0 : slabdata 1450 1450 0
mnt_cache 2875 2875 320 25 2 : tunables 0 0 0 : slabdata 115 115 0
filp 36630 41632 256 32 2 : tunables 0 0 0 : slabdata 1301 1301 0
inode_cache 19677 21502 608 26 4 : tunables 0 0 0 : slabdata 827 827 0
dentry 144512 166152 192 42 2 : tunables 0 0 0 : slabdata 3956 3956 0
names_cache 232 232 4096 8 8 : tunables 0 0 0 : slabdata 29 29 0
iint_cache 0 0 120 34 1 : tunables 0 0 0 : slabdata 0 0 0
buffer_head 524826 583352 144 28 1 : tunables 0 0 0 : slabdata 20834 20834 0
uts_namespace 0 0 440 37 4 : tunables 0 0 0 : slabdata 0 0 0
vm_area_struct 204783 204840 200 40 2 : tunables 0 0 0 : slabdata 5121 5121 0
mm_struct 630 630 1088 30 8 : tunables 0 0 0 : slabdata 21 21 0
files_cache 828 828 704 46 8 : tunables 0 0 0 : slabdata 18 18 0
signal_cache 1540 1764 1152 28 8 : tunables 0 0 0 : slabdata 63 63 0
sighand_cache 965 990 2112 15 8 : tunables 0 0 0 : slabdata 66 66 0
task_struct 2731 2793 4224 7 8 : tunables 0 0 0 : slabdata 399 399 0
cred_jar 4578 4578 192 42 2 : tunables 0 0 0 : slabdata 109 109 0
anon_vma_chain 121206 122432 64 64 1 : tunables 0 0 0 : slabdata 1913 1913 0
anon_vma 70747 71484 88 46 1 : tunables 0 0 0 : slabdata 1554 1554 0
pid 7680 7680 128 32 1 : tunables 0 0 0 : slabdata 240 240 0
Acpi-Operand 10528 10528 72 56 1 : tunables 0 0 0 : slabdata 188 188 0
Acpi-ParseExt 351 351 104 39 1 : tunables 0 0 0 : slabdata 9 9 0
Acpi-State 1071 1071 80 51 1 : tunables 0 0 0 : slabdata 21 21 0
numa_policy 41846 43180 24 170 1 : tunables 0 0 0 : slabdata 254 254 0
trace_event_file 5934 5934 88 46 1 : tunables 0 0 0 : slabdata 129 129 0
ftrace_event_field 11050 11050 48 85 1 : tunables 0 0 0 : slabdata 130 130 0
pool_workqueue 1888 1888 256 32 2 : tunables 0 0 0 : slabdata 59 59 0
radix_tree_node 74501 88032 584 28 4 : tunables 0 0 0 : slabdata 3144 3144 0
task_group 448 448 576 28 4 : tunables 0 0 0 : slabdata 16 16 0
vmap_area 30471 40256 64 64 1 : tunables 0 0 0 : slabdata 629 629 0
dma-kmalloc-8k 0 0 8192 4 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-4k 0 0 4096 8 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-2k 0 0 2048 16 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-1k 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-512 0 0 512 32 4 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-256 0 0 256 32 2 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-128 0 0 128 32 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-64 0 0 64 64 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-32 0 0 32 128 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-16 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-8 0 0 8 512 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-192 0 0 192 42 2 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-96 0 0 96 42 1 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-8k 0 0 8192 4 8 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-4k 0 0 4096 8 8 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-2k 0 0 2048 16 8 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-1k 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-512 0 0 512 32 4 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-256 256 256 256 32 2 : tunables 0 0 0 : slabdata 8 8 0
kmalloc-rcl-192 462 462 192 42 2 : tunables 0 0 0 : slabdata 11 11 0
kmalloc-rcl-128 1216 1216 128 32 1 : tunables 0 0 0 : slabdata 38 38 0
kmalloc-rcl-96 5712 5712 96 42 1 : tunables 0 0 0 : slabdata 136 136 0
kmalloc-rcl-64 14616 14720 64 64 1 : tunables 0 0 0 : slabdata 230 230 0
kmalloc-rcl-32 0 0 32 128 1 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-16 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-8 0 0 8 512 1 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-8k 289 292 8192 4 8 : tunables 0 0 0 : slabdata 73 73 0
kmalloc-4k 1037 1064 4096 8 8 : tunables 0 0 0 : slabdata 133 133 0
kmalloc-2k 1776 1776 2048 16 8 : tunables 0 0 0 : slabdata 111 111 0
kmalloc-1k 6311 6880 1024 32 8 : tunables 0 0 0 : slabdata 215 215 0
kmalloc-512 20608 23200 512 32 4 : tunables 0 0 0 : slabdata 725 725 0
kmalloc-256 34022 44704 256 32 2 : tunables 0 0 0 : slabdata 1397 1397 0
kmalloc-192 9452 11172 192 42 2 : tunables 0 0 0 : slabdata 266 266 0
kmalloc-128 4867 5728 128 32 1 : tunables 0 0 0 : slabdata 179 179 0
kmalloc-96 7014 7014 96 42 1 : tunables 0 0 0 : slabdata 167 167 0
kmalloc-64 22525 24064 64 64 1 : tunables 0 0 0 : slabdata 376 376 0
kmalloc-32 33408 33408 32 128 1 : tunables 0 0 0 : slabdata 261 261 0
kmalloc-16 17408 17408 16 256 1 : tunables 0 0 0 : slabdata 68 68 0
kmalloc-8 12288 12288 8 512 1 : tunables 0 0 0 : slabdata 24 24 0
kmem_cache_node 256 256 64 64 1 : tunables 0 0 0 : slabdata 4 4 0
kmem_cache 224 224 256 32 2 : tunables 0 0 0 : slabdata 7 7 0
如上所示,我们看到有些SLAB的名字比较特别,如ext4,kvm,signal,acpi等等,这些都是专用SLAB,专属于它们自己的模块。而靠后的诸如kmalloc-8,kmalloc-16...还有dma-kmalloc-96,dma-kmalloc-192...这些都是普通SLAB,当需要为一些小数据分配内存时,就会从这些普通SLAB中获取内存。
有没有觉得有什么共同点?大家先记着这个现象,后面我们会讨论到。
首先我们来解答下上面说到的共同点问题。
我们都知道,Linux中采用4KB大小的页框作为标准的内存分配单元,在实际应用中,经常需要分配一组连续的页框,而频繁的申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的空闲页框,这样,即使这些页框是空闲的,其他需要分配连续页框的应用也很难得到满足。
Linux内核引入了伙伴系统算法来避免这种情况。其把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1、2、4、8、16、32、64、128、256、512和1024个连续页框的页框块。最大可以申请1024个连续页框,也即4MB大小的连续空间。
而slab则基于伙伴系统,进一步将页框划分成各个小的内存块,而他的实现则是通过在kmem_cache_init过程中,通过kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE]来建立caches数组。
~# cat mm/slab.c
...
void __init kmem_cache_init(void)
{
...
/*
* Initialize the caches that provide memory for the kmem_cache_node
* structures first. Without this, further allocations will bug.
*/
kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE] = create_kmalloc_cache(
kmalloc_info[INDEX_NODE].name[KMALLOC_NORMAL],
kmalloc_info[INDEX_NODE].size,
ARCH_KMALLOC_FLAGS, 0,
kmalloc_info[INDEX_NODE].size);
...
create_kmalloc_caches(ARCH_KMALLOC_FLAGS);
}
...
kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE]中的INDEX_NODE
kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE]中的INDEX_NODE即为kmalloc_index,INDEX_NODE取值范围为0-21,分别存放着不同大小的内存caches。
~# cat include/linux/slab.h
...
/*
* Figure out which kmalloc slab an allocation of a certain size
* belongs to.
* 0 = zero alloc
* 1 = 65 .. 96 bytes
* 2 = 129 .. 192 bytes
* n = 2^(n-1)+1 .. 2^n
*
* Note: __kmalloc_index() is compile-time optimized, and not runtime optimized;
* typical usage is via kmalloc_index() and therefore evaluated at compile-time.
* Callers where !size_is_constant should only be test modules, where runtime
* overheads of __kmalloc_index() can be tolerated. Also see kmalloc_slab().
*/
static __always_inline unsigned int __kmalloc_index(size_t size,
bool size_is_constant)
{
/* 0 = zero alloc */
if (!size)
return 0;
if (size <= KMALLOC_MIN_SIZE)
return KMALLOC_SHIFT_LOW;
/* 2 = 129 .. 192 bytes分配65-96bytes的内存大小的块*/
if (KMALLOC_MIN_SIZE <= 32 && size > 64 && size <= 96)
return 1;
/* 1 = 65 .. 96 bytes分配65-96bytes的内存大小的块*/
if (KMALLOC_MIN_SIZE <= 64 && size > 128 && size <= 192)
return 2;
/* n = 2^(n-1)+1 .. 2^n 分配2^(n-1)+1 .. 2^n大小内存的块内存*/
if (size <= 8) return 3;
if (size <= 16) return 4;
if (size <= 32) return 5;
if (size <= 64) return 6;
if (size <= 128) return 7;
if (size <= 256) return 8;
if (size <= 512) return 9;
if (size <= 1024) return 10;
if (size <= 2 * 1024) return 11;
if (size <= 4 * 1024) return 12;
if (size <= 8 * 1024) return 13;
if (size <= 16 * 1024) return 14;
if (size <= 32 * 1024) return 15;
if (size <= 64 * 1024) return 16;
if (size <= 128 * 1024) return 17;
if (size <= 256 * 1024) return 18;
if (size <= 512 * 1024) return 19;
if (size <= 1024 * 1024) return 20;
if (size <= 2 * 1024 * 1024) return 21;
if (!IS_ENABLED(CONFIG_PROFILE_ALL_BRANCHES) && size_is_constant)
BUILD_BUG_ON_MSG(1, "unexpected size in kmalloc_index()");
else
BUG();
/* Will never be reached. Needed because the compiler may complain */
return -1;
}
...
通过create_kmalloc_cache(
kmalloc_info[INDEX_NODE].name[KMALLOC_NORMAL],
kmalloc_info[INDEX_NODE].size,
ARCH_KMALLOC_FLAGS, 0,
kmalloc_info[INDEX_NODE].size);创建出对应的内存区域,且它们对应着0,96byte,192byte,8byte,16byte,32byte,64byte...2M的连续内存空间。
~# cat mm/slab_common.c
...
/*
* kmalloc_info[] is to make slub_debug=,kmalloc-xx option work at boot time.
* kmalloc_index() supports up to 2^21=2MB, so the final entry of the table is
* kmalloc-2M.
*/
const struct kmalloc_info_struct kmalloc_info[] __initconst = {
INIT_KMALLOC_INFO(0, 0),
INIT_KMALLOC_INFO(96, 96),
INIT_KMALLOC_INFO(192, 192),
INIT_KMALLOC_INFO(8, 8),
INIT_KMALLOC_INFO(16, 16),
INIT_KMALLOC_INFO(32, 32),
INIT_KMALLOC_INFO(64, 64),
INIT_KMALLOC_INFO(128, 128),
INIT_KMALLOC_INFO(256, 256),
INIT_KMALLOC_INFO(512, 512),
INIT_KMALLOC_INFO(1024, 1k),
INIT_KMALLOC_INFO(2048, 2k),
INIT_KMALLOC_INFO(4096, 4k),
INIT_KMALLOC_INFO(8192, 8k),
INIT_KMALLOC_INFO(16384, 16k),
INIT_KMALLOC_INFO(32768, 32k),
INIT_KMALLOC_INFO(65536, 64k),
INIT_KMALLOC_INFO(131072, 128k),
INIT_KMALLOC_INFO(262144, 256k),
INIT_KMALLOC_INFO(524288, 512k),
INIT_KMALLOC_INFO(1048576, 1M),
INIT_KMALLOC_INFO(2097152, 2M)
};
...
内存类型则由kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE]中的KMALLOC_NORMAL决定,可选诸如KMALLOC_NORMAL、KMALLOC_DMA、KMALLOC_CGROUP、NR_KMALLOC_TYPES、KMALLOC_RECLAIM等,也可以由用户自己定义自己的专用内存类型,诸如kvm_vcpu、dquot、signal_cache等等都是其他模块自行定义的内存类型。
~# cat include/linux/slab.h
...
enum kmalloc_cache_type {
/* 对应着kmalloc的内存 */
KMALLOC_NORMAL = 0,
#ifndef CONFIG_ZONE_DMA
KMALLOC_DMA = KMALLOC_NORMAL,
#endif
#ifndef CONFIG_MEMCG_KMEM
KMALLOC_CGROUP = KMALLOC_NORMAL,
#endif
#ifdef CONFIG_SLUB_TINY
KMALLOC_RECLAIM = KMALLOC_NORMAL,
#else
KMALLOC_RECLAIM,
#endif
#ifdef CONFIG_ZONE_DMA
/* 对应着dma-kmalloc的内存 */
KMALLOC_DMA,
#endif
#ifdef CONFIG_MEMCG_KMEM
KMALLOC_CGROUP,
#endif
NR_KMALLOC_TYPES
};
...
kmem_cache数据结构代表一个slab 缓存,其中有一些缓存元信息包括:缓存名,缓存对象大小,关联的内存页帧数,着色信息等等;还有一个__per_cpu array_cache用于表示该缓存在各个CPU中的slab对象;kmem_cache_node用于管理各个内存节点上slab对象的分配。具体数据结构如下:
struct kmem_cache {
/* 0) per-CPU数据,在每次分配/释放期间都会访问 */
struct array_cache __percpu *cpu_cache; // 每个cpu中的slab对象
/* 1) Cache tunables. Protected by slab_mutex */
unsigned int batchcount; // 当__percpu cpu_cache为空时,从缓存slab中获取的对象数目,它还表示缓存增长时分配的对象数目。初始时为1,后续会调整。
unsigned int limit; // __percpu cpu_cache中的对象数目上限,当slab free达到limit时,需要将array_caches中的部分obj返回到kmem_cache_node的页帧中。
unsigned int shared;
unsigned int size; // slab中的每个对象大小
struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */
slab_flags_t flags; /* constant flags */
unsigned int num; /* # of objs per slab */
/* 3) cache_grow/shrink */
/* order of pgs per slab (2^n) */
unsigned int gfporder; // slab关联页数
/* force GFP flags, e.g. GFP_DMA */
gfp_t allocflags;
size_t colour; /* cache colouring range */
unsigned int colour_off; /* colour offset */
struct kmem_cache *freelist_cache; // 空闲对象管理
unsigned int freelist_size; // 空闲对象数量
/* constructor func */
void (*ctor)(void *obj); // 这个在2.6之后已经废弃了
/* 4) cache creation/removal */
const char *name;
struct list_head list;
int refcount;
int object_size;
int align;
/* 5) statistics */
#ifdef CONFIG_DEBUG_SLAB
unsigned long num_active;
unsigned long num_allocations;
unsigned long high_mark;
unsigned long grown;
unsigned long reaped;
unsigned long errors;
unsigned long max_freeable;
unsigned long node_allocs;
unsigned long node_frees;
unsigned long node_overflow;
atomic_t allochit;
atomic_t allocmiss;
atomic_t freehit;
atomic_t freemiss;
#ifdef CONFIG_DEBUG_SLAB_LEAK
atomic_t store_user_clean;
#endif
/*
* If debugging is enabled, then the allocator can add additional
* fields and/or padding to every object. 'size' contains the total
* object size including these internal fields, while 'obj_offset'
* and 'object_size' contain the offset to the user object and its
* size.
*/
int obj_offset;
#endif /* CONFIG_DEBUG_SLAB */
#ifdef CONFIG_MEMCG
struct memcg_cache_params memcg_params;
#endif
#ifdef CONFIG_KASAN
struct kasan_cache kasan_info;
#endif
#ifdef CONFIG_SLAB_FREELIST_RANDOM
unsigned int *random_seq;
#endif
unsigned int useroffset; /* Usercopy region offset */
unsigned int usersize; /* Usercopy region size */
struct kmem_cache_node *node[MAX_NUMNODES]; // 每个内存节点上的slab对象信息,每个node上包括部分空闲,全满以及全部空闲三个队列
};
struct kmem_cache_cpu:是对本地内存缓存池的描述,每一个cpu对应一个结构体。可以使用无锁访问,提高缓存对象分配速度;
struct kmem_cache_node:是对共享内存缓存池的描述,用于管理每个Node的slab页面;
struct page:用于描述slab页面,一个slab页面由一个或多个page组成。
struct kmem_cache_cpu和struct kmem_cache_node之间的关系可以用一张图来描述:
array_cache是一个per_cpu数组,所以访问不需要加锁,是与cpu cache打交道的直接数据结构,每次获取空闲slab对象时都是通过entry[avail--]去获取,当avail==0时,又从kmem_cache_node中获取batchcount个空闲对象到array_cache中。具体数据结构如下:
struct array_cache {
unsigned int avail; // 保存了当前array中的可用数目
unsigned int limit; // 同上
unsigned int batchcount; // 同上
unsigned int touched; // 缓存收缩时置0,缓存移除对象时置1,使得内核能确认在上一次收缩之后是否被访问过
void *entry[]; /*
* Must have this definition in here for the proper
* alignment of array_cache. Also simplifies accessing
* the entries.
*/
};
kmem_cache_node用于管理slab(实际对象存储伙伴页帧),其会管理三个slab列表,部分空闲partial,全部空闲empty,全部占用full。array_cache获取batchcount空闲对象时,先尝试从partial分配,如果不够则再从empty分配剩余对象,如果都不够,则需要grow分配新的slab页帧。具体数据结构如下:
struct kmem_cache_node {
spinlock_t list_lock;
#ifdef CONFIG_SLAB
struct list_head slabs_partial; /* partial list first, better asm code */
struct list_head slabs_full;
struct list_head slabs_free;
unsigned long total_slabs; /* length of all slab lists */
unsigned long free_slabs; /* length of free slab list only */
unsigned long free_objects;
unsigned int free_limit;
unsigned int colour_next; /* Per-node cache coloring */
struct array_cache *shared; /* shared per node */
struct alien_cache **alien; /* on other nodes */
unsigned long next_reap; /* updated without locking */
int free_touched; /* updated without locking */
#endif
#ifdef CONFIG_SLUB
unsigned long nr_partial;
struct list_head partial;
#ifdef CONFIG_SLUB_DEBUG
atomic_long_t nr_slabs;
atomic_long_t total_objects;
struct list_head full;
#endif
#endif
};
page页帧,这个就不必多说了,这是物理存储地址,是一个union结构,当被用作slab时,会初始化一下slab管理数据,诸如起始object地址s_mem,lru缓存节点,是否被激活active,关联到的kmem_cache以及freelist空闲对象数组(是一个void*指针,其实存的是char or short数组)。
在前面章节我们提到过kmem_cache_init过程中,会分配对应内存大小的内存数组池。kmem_cache_init就是一个初始化slab分配器的函数,其主要作用是通过create_kmalloc_caches初始化kmalloc_caches。
初始化的过程参考下图
申请内存通过kmem_cache_alloc 实现
主要分为5步:
1.slub系统刚刚创建出来,第一次申请时
此时slub系统刚建立起来,kmem_cache_cpu和kmem_cache_node中没有任何可用的slab可以使用,如下图中1所示:
只能向伙伴系统申请空闲的内存页,并把这些页面分成很多个object,取出其中的一个object标志为已被占用,并返回给用户,其余的object标志为空闲并放在kmem_cache_cpu中保存。kmem_cache_cpu的freelist变量中保存着下一个空闲object的地址。上图2表示申请一个新的slab,并把第一个空闲的object返回给用户,freelist指向下一个空闲的object。
2.先从 kmem_cache_cpu->freelist中分配,如果freelist为null
3.接着去 kmem_cache_cpu->partital链表中分配,如果此链表为null
4.接着去 kmem_cache_node->partital链表分配,如果此链表为null
5.重新分配一个slab。
首先我们看一下kmalloc的代码实现流程
kmalloc详细代码如下:
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) {
if (size > KMALLOC_MAX_CACHE_SIZE)
return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB
if (!(flags & GFP_DMA)) {
//查找符合满足分配大小的最小kmem_cache
int index = kmalloc_index(size);
if (!index)
return ZERO_SIZE_PTR;
//将index作为下表从kmalloc_caches数组中找到符合的kmem_cache,并从slab缓存池中分配对象
return kmem_cache_alloc_trace(kmalloc_caches[index],
flags, size);
}
#endif
}
return __kmalloc(size, flags);
}
kmem_cache_alloc_trace中会调用kmem_cache_alloc,这就和我们上一章节提到的kmem_cache_alloc相连接起来了。
static __always_inline void *kmem_cache_alloc_trace(struct kmem_cache *s,
gfp_t flags, size_t size)
{
void *ret = kmem_cache_alloc(s, flags);
kasan_kmalloc(s, ret, size, flags);
return ret;
}
深入linux内核架构--slab分配器 - 简书
Linux内存管理十三 Linux slab分配器_cft56200_ln的博客-CSDN博客
kernel/git/next/linux-next.git - The linux-next integration testing tree