Linux slab分配器

在Linux中,伙伴系统是以页为单位分配内存。但是现实中很多时候却以字节为单位,不然申请10Bytes内存还要给1页的话就太浪费了。slab分配器就是为小内存分配而生的。slab分配器分配内存以Byte为单位。但是slab分配器并没有脱离伙伴系统,而是基于伙伴系统分配的大内存进一步细分成小内存分配。

走进slub

做个小实验:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mm.h>

static struct kmem_cache* slub_test;

struct student{
    int age;
    int score;
};

static void mystruct_constructor(void *addr)
{
    memset(addr, 0, sizeof(struct student));
}

struct student* peter;

int slub_test_create_kmem(void)
{
    int ret = -1;
    slub_test = kmem_cache_create("slub_test", sizeof(struct student), 0, 0, mystruct_constructor);
    if(slub_test != NULL){
        printk("slub_test create success!\n");
        ret=0;
    }


    peter = kmem_cache_alloc(slub_test, GFP_KERNEL);
    if(peter != NULL){
        printk("alloc object success!\n");
        ret = 0;
    }

    return ret;
}

static int __init slub_test_init(void)
{
    int ret;
    printk("slub_test kernel module init\n");
    ret = slub_test_create_kmem();
    return 0;
}

static void __exit slub_test_exit(void)
{
    printk("slub_test kernel module exit\n");
    kmem_cache_destroy(slub_test);
}

module_init(slub_test_init);
module_exit(slub_test_exit);

看下结果:

相关参数也可以通过如下路径查看:

总结:slab从buddy拿到一个order为0的内存,也就是一页,然后把这一页称为名为slub_test的slab,然后把这一页分成很多小的object的。当我们使用的时候就从slab中获取一个object使用,用完了在归还给slab管理即可。

数据结构

  • struct kmem_cache:用于管理slab缓存,包括该缓存中对象的信息描述,per-CPU/Node管理slab页面等;
  • struct kmem_cache_cpu:是对本地内存缓存池的描述,每一个cpu对应一个结构体。可以使用无锁访问,提高缓存对象分配速度;
  • struct kmem_cache_node:是对共享内存缓存池的描述,用于管理每个Node的slab页面;
  • struct page:用于描述slab页面,一个slab页面由一个或多个page组成。

他们之间的关系可以用一张图来描述:

流程分析

slab缓存的创建(主要是就是填充kmem_cache结构体成员)。

kmem_cache_create
   create_cache
      kmem_cache_zalloc //分配kmem_cache结构
         kmem_cache_alloc
            slab_alloc
                  slab_alloc_node
      __kmem_cache_create //初始化kmem_cache结构
         kmem_cache_open
      list_add //将创建的kmem_cache添加到全局链表slab_caches中,构成slab缓存池

kmem_cache_create 主要三步:

  1. 分配kmem_cache结构。
  2. 初始化kmem_cache结构。
  3. 将创建的kmem_cache添加到全局链表slab_caches中,构成slab缓存池。

object对象的分配。

kmem_cache_alloc
    slab_alloc
        slab_alloc_node
            if (kmem_cache_cpu里的freelist没有可用的object)
                __slab_alloc
                    ___slab_alloc
                      slub_percpu_partial  //2.接着去 kmem_cache_cpu->partital链表中分配,如果此链表为null
                      new_slab_objects
                          get_partial  //3.接着去 kmem_cache_node->partital链表分配,如果此链表为null
                          new_slab  //4.这就需要重新分配一个slab了。
                              allocate_slab 
            else
                get_freepointer_safe  //1.先从 kmem_cache_cpu->freelist中分配,如果freelist为null

kmem_cache_alloc 主要四步:

  1. 先从 kmem_cache_cpu->freelist中分配,如果freelist为null
  1. 接着去 kmem_cache_cpu->partital链表中分配,如果此链表为null
  1. 接着去 kmem_cache_node->partital链表分配,如果此链表为null
  1. 重新分配一个slab。

kmalloc

虽然刚开始我们通过一个小实验了解了slab的用法,但是实际工作中什么时候会用到呢?其实我们在写驱动的时候经常用,那就是kmalloc。

kmalloc的内存分配就是基于slab分配器,系统在启动的时候会调用create_kmalloc_caches,来创建不同大小的kmem_cache,并将这些kmem_cache存储在kmalloc_caches全局变量中以备后面kmalloc的使用。各个kmem_cache的名字和大小如下所示:

const struct kmalloc_info_struct kmalloc_info[] __initconst = {
 {NULL,                      0},  {"kmalloc-96",             96},
 {"kmalloc-192",           192},  {"kmalloc-8",               8},
 {"kmalloc-16",             16},  {"kmalloc-32",             32},
 {"kmalloc-64",             64},  {"kmalloc-128",           128},
 {"kmalloc-256",           256},  {"kmalloc-512",           512},
 {"kmalloc-1024",         1024},  {"kmalloc-2048",         2048},
 {"kmalloc-4096",         4096},  {"kmalloc-8192",         8192},
 {"kmalloc-16384",       16384},  {"kmalloc-32768",       32768},
 {"kmalloc-65536",       65536},  {"kmalloc-131072",     131072},
 {"kmalloc-262144",     262144},  {"kmalloc-524288",     524288},
 {"kmalloc-1048576",   1048576},  {"kmalloc-2097152",   2097152},
 {"kmalloc-4194304",   4194304},  {"kmalloc-8388608",   8388608},
 {"kmalloc-16777216", 16777216},  {"kmalloc-33554432", 33554432},
 {"kmalloc-67108864", 67108864}
};

我们来看看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);
}
  1. 先是通过kmalloc_index查找符合满足分配大小的最小kmem_cache
static __always_inline int kmalloc_index(size_t size)
{
 if (!size)
  return 0;

 if (size <= KMALLOC_MIN_SIZE)
  return KMALLOC_SHIFT_LOW;

 if (KMALLOC_MIN_SIZE <= 32 && size > 64 && size <= 96)
  return 1;
 if (KMALLOC_MIN_SIZE <= 64 && size > 128 && size <= 192)
  return 2;
 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 (size <=  4 * 1024 * 1024) return 22;
 if (size <=  8 * 1024 * 1024) return 23;
 if (size <=  16 * 1024 * 1024) return 24;
 if (size <=  32 * 1024 * 1024) return 25;
 if (size <=  64 * 1024 * 1024) return 26;
 BUG();

 /* Will never be reached. Needed because the compiler may complain */
 return -1;
}
  1. 然后将index作为下表从kmalloc_caches数组中找到符合的kmem_cache,并从slab缓存池中分配对象。 此时和上面讲的slab缓存池就联系起来了。
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;
}

本文分享自微信公众号 - 人人都是极客(rrgeek),作者:布道师Peter

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-04-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Linux内存管理 - slab分配器

    Linux内存管理是一个非常复杂的子系统,要完全说清的话估计要一本书的篇幅。但Linux内存管理可以划分成多个部分来阐述,这篇文章主要介绍slab算法。

    用户7686797
  • linux内存源码分析 - SLAB分配器概述

    233333
  • 记一次内存占用问题的调查过程

    马哥linux运维 | 最专业的linux培训机构 ---- 最近在维护一台CentOS服务器的时候,发现内存无端"损失"了许多,free和ps统计的结果相差...

    小小科
  • allocater

    此程序相当于Linux里面的一个slab内存分配器 一、Slab 内存slab分配器最初思想来自Solaris的内核态小数据结构(一页以内)的内存分配,受到So...

    瓜大三哥
  • 伙伴系统和slab机制

    Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表。四级页表分别...

    233333
  • 万字长文,别再说你不懂Linux内存管理了,30 张图给你安排的明明白白

    之前写了两篇详细分析 Linux 内存管理的文章,读者好评如潮。但由于是分开两篇来写,而这两篇内容其实是有很强关联的,有读者反馈没有看到另一篇读起来不够不连贯,...

    程序IT圈
  • 分享Linux内存占用几个案例

    最近一台 CentOS 服务器,发现内存无端损失了许多,free 和 ps 统计的结果相差十几个G,非常奇怪,后来Google了许久才搞明白。

    YP小站
  • Linux 内存使用率

    PS:什么是SReclaimable?在linux内核中会有许多小对象,这些对象构造销毁十分频繁,比如i-node,dentry。那么这些对象如果每次构建的时候...

    踏歌行
  • Linux内核内存管理算法Buddy和Slab

    有了前两节的学习相信读者已经知道CPU所有的操作都是建立在虚拟地址上处理(这里的虚拟地址分为内核态虚拟地址和用户态虚拟地址),CPU看到的内存管理都是对page...

    刘盼

扫码关注云+社区

领取腾讯云代金券