kmem_cache内存池使用

kmem_cache 作为一个基本的 allocator 组件,这里可以把他理解为用于某个特定大小的内存对象池。

Linux 系统中本身也存在很多这样的内存池,包括分配struct kmem_cache 和struct kmem_cache_node对象,也是在单独的对象内存池里面分配的。

当然系统里面也根据用途和大小分配了一些通用的对象内存池,参考:

struct kmem_cache *
kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1] __ro_after_init =
{ /* initialization for https://llvm.org/pr42570 */ };
EXPORT_SYMBOL(kmalloc_caches);

所以系统中存在各种各样的kmem_cache内存池,kmalloc这样的函数,就是在这些通用的内存池里面分配内存。

static __always_inline
void *__do_kmalloc_node(size_t size, gfp_t flags, int node,
                        unsigned long caller)
{
        struct kmem_cache *s;
        //...
        s = kmalloc_slab(size, flags, caller);  // 找到对应的kmem_cache

        ret = slab_alloc_node(s, NULL, flags, node, caller, size); //在kmem_cache池里进行分配

        //....
        return ret;
}

可以通过如下命令查看系统中的内存池:

# 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_groupinfo_4k     22     22    184   22    1 : tunables    0    0    0 : slabdata      1      1      0
p9_req_t               0      0    160   25    1 : tunables    0    0    0 : slabdata      0      0      0
//......

kmalloc-8k            20     20   8192    4    8 : tunables    0    0    0 : slabdata      5      5      0
kmalloc-4k            56     56   4096    8    8 : tunables    0    0    0 : slabdata      7      7      0
kmalloc-2k           160    160   2048   16    8 : tunables    0    0    0 : slabdata     10     10      0
kmalloc-1k           544    544   1024   32    8 : tunables    0    0    0 : slabdata     17     17      0
kmalloc-512          640    640    512   32    4 : tunables    0    0    0 : slabdata     20     20      0
kmalloc-256          736    736    256   32    2 : tunables    0    0    0 : slabdata     23     23      0
kmalloc-192         1575   1575    192   21    1 : tunables    0    0    0 : slabdata     75     75      0
kmalloc-128          512    512    128   32    1 : tunables    0    0    0 : slabdata     16     16      0
kmalloc-96           630    630     96   42    1 : tunables    0    0    0 : slabdata     15     15      0
kmalloc-64          1344   1344     64   64    1 : tunables    0    0    0 : slabdata     21     21      0
kmalloc-32          1408   1408     32  128    1 : tunables    0    0    0 : slabdata     11     11      0
kmalloc-16          2816   2816     16  256    1 : tunables    0    0    0 : slabdata     11     11      0
kmalloc-8           5120   5120      8  512    1 : tunables    0    0    0 : slabdata     10     10      0
kmem_cache_node      320    320     64   64    1 : tunables    0    0    0 : slabdata      5      5      0
kmem_cache           256    256    256   32    2 : tunables    0    0    0 : slabdata      8      8      0

如果kernel中的驱动,有特殊用途,也可以创建自己的内存池。

可以参考文件 arch/arm64/mm/pgd.c

static struct kmem_cache *pgd_cache __ro_after_init;


pgd_t *pgd_alloc(struct mm_struct *mm)
{
        gfp_t gfp = GFP_PGTABLE_USER;

        if (pgdir_is_page_size())
                return (pgd_t *)__get_free_page(gfp);
        else
                return kmem_cache_alloc(pgd_cache, gfp);
}

void pgd_free(struct mm_struct *mm, pgd_t *pgd)
{
        if (pgdir_is_page_size())
                free_page((unsigned long)pgd);
        else
                kmem_cache_free(pgd_cache, pgd);
}

void __init pgtable_cache_init(void)
{

        //.....
        /*
         * Naturally aligned pgds required by the architecture.
         */
        pgd_cache = kmem_cache_create("pgd_cache", PGD_SIZE, PGD_SIZE,
                                      SLAB_PANIC, NULL);
}

kernel/fork.c

static struct kmem_cache *thread_stack_cache;

static void thread_stack_free_rcu(struct rcu_head *rh)
{
        kmem_cache_free(thread_stack_cache, rh);
}

static void thread_stack_delayed_free(struct task_struct *tsk)
{
        struct rcu_head *rh = tsk->stack;

        call_rcu(rh, thread_stack_free_rcu);
}

static int alloc_thread_stack_node(struct task_struct *tsk, int node)
{
        unsigned long *stack;
        stack = kmem_cache_alloc_node(thread_stack_cache, THREADINFO_GFP, node);
        stack = kasan_reset_tag(stack);
        tsk->stack = stack;
        return stack ? 0 : -ENOMEM;
}

void thread_stack_cache_init(void)
{
        thread_stack_cache = kmem_cache_create_usercopy("thread_stack",
                                        THREAD_SIZE, THREAD_SIZE, 0, 0,
                                        THREAD_SIZE, NULL);
        BUG_ON(thread_stack_cache == NULL);
}

上面两个例子唯一的区别是不是要使用到usercopy,如果使用到usercopy的话,就用kmem_cache_create_usercopy。 这样在copyfromuser的时候,位了避免userspace的攻击,这个时候就可以通过user 的size 和offset 来检查这个copy是不是合法,不合法就可以拒绝copy。

参考函数 __check_heap_object.

在使能 CONFIG_SLUB_DEBUG 这之后,可以对每个pool的分配情况进行查询,如下:

# cat /sys/kernel/debug/slab/task_struct/alloc_traces
    102 copy_process+0x144/0x141c age=39045/1797787/1833528 pid=2 cpus=0-1
        kmem_cache_alloc_node+0x20c/0x264
        copy_process+0x144/0x141c
        kernel_clone+0x68/0x368
        kernel_thread+0x80/0xb4
        kthreadd+0x144/0x1c4
        ret_from_fork+0x10/0x20

      7 copy_process+0x144/0x141c age=1833521/1833521/1833522 pid=1 cpus=0
        kmem_cache_alloc_node+0x20c/0x264
        copy_process+0x144/0x141c
        fork_idle+0x90/0x110
        idle_threads_init+0xcc/0x104
        smp_init+0x14/0x8c
        kernel_init_freeable+0x100/0x290
        kernel_init+0x20/0x1d8
        ret_from_fork+0x10/0x20

      4 copy_process+0x144/0x141c age=5/1371620/1828871 pid=1-149 cpus=6-7
        kmem_cache_alloc_node+0x20c/0x264
        copy_process+0x144/0x141c
        kernel_clone+0x68/0x368
        __do_sys_clone+0x74/0xb0
        __arm64_sys_clone+0x20/0x2c
        invoke_syscall+0x48/0x114
        el0_svc_common.constprop.0+0x40/0xe0
        do_el0_svc+0x1c/0x28
        el0_svc+0x34/0xd8
        el0t_64_sync_handler+0x120/0x12c
        el0t_64_sync+0x190/0x194

      1 copy_process+0x144/0x141c age=1833529 pid=0 cpus=0
        kmem_cache_alloc_node+0x20c/0x264
        copy_process+0x144/0x141c
        kernel_clone+0x68/0x368
        user_mode_thread+0x70/0xa4
        rest_init+0x30/0xe4
        start_kernel+0x50c/0x614
        __primary_switched+0x80/0x88

      1 copy_process+0x144/0x141c age=1833529 pid=0 cpus=0
        kmem_cache_alloc_node+0x20c/0x264
        copy_process+0x144/0x141c
        kernel_clone+0x68/0x368
        kernel_thread+0x80/0xb4
        rest_init+0xa0/0xe4
        start_kernel+0x50c/0x614
        __primary_switched+0x80/0x88

# cat /sys/kernel/debug/slab/task_struct/free_traces
    104 <not-available> age=4297136502 pid=0 cpus=0
     11 free_task+0x54/0x80 age=410662/1767494/2243661 pid=0-126 cpus=0-1,4-6
        kmem_cache_free+0x1fc/0x2a0
        free_task+0x54/0x80
        __put_task_struct+0x100/0x154
        delayed_put_task_struct+0x7c/0xa8
        rcu_core+0x25c/0x654
        rcu_core_si+0x10/0x1c
        handle_softirqs+0x108/0x24c
        __do_softirq+0x14/0x20

Comments !