Linux内核中的内存管理——slab

一、简介

在学习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缓存的对象不太可能相互刷新。 通过这种方案,原本被浪费掉的空间可以实现一项新功能。

二、系统中的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中获取内存。

不知大家有没有注意到,在上面最后的slab中 
Linux内核中的内存管理——slab_第1张图片Linux内核中的内存管理——slab_第2张图片Linux内核中的内存管理——slab_第3张图片

有没有觉得有什么共同点?大家先记着这个现象,后面我们会讨论到。

三、实现原理

3.1 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)
};
...

3.2 slab分配的内存类型

内存类型则由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
};
...

3.3 基本结构

Linux内核中的内存管理——slab_第4张图片

Linux内核中的内存管理——slab_第5张图片

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对应一个结构体。可以使用无锁访问,提高缓存对象分配速度;

Linux内核中的内存管理——slab_第6张图片

struct kmem_cache_node:是对共享内存缓存池的描述,用于管理每个Node的slab页面;

struct page:用于描述slab页面,一个slab页面由一个或多个page组成。

struct kmem_cache_cpu和struct kmem_cache_node之间的关系可以用一张图来描述:

Linux内核中的内存管理——slab_第7张图片

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数组)。

3.4 kmem初始化

在前面章节我们提到过kmem_cache_init过程中,会分配对应内存大小的内存数组池。kmem_cache_init就是一个初始化slab分配器的函数,其主要作用是通过create_kmalloc_caches初始化kmalloc_caches。

初始化的过程参考下图

3.5 申请和释放内存

申请内存通过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

Linux内核中的内存管理——slab_第8张图片

3.接着去 kmem_cache_cpu->partital链表中分配,如果此链表为null

Linux内核中的内存管理——slab_第9张图片

4.接着去 kmem_cache_node->partital链表分配,如果此链表为null

Linux内核中的内存管理——slab_第10张图片

5.重新分配一个slab。

3.6 kmalloc API

首先我们看一下kmalloc的代码实现流程

Linux内核中的内存管理——slab_第11张图片

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

你可能感兴趣的:(linux,服务器,c语言,缓存)