[内核内存] slab分配器2---slab系统初始化

2024-05-25 09:48

本文主要是介绍[内核内存] slab分配器2---slab系统初始化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1 slab系统初始化---kmem_cache_init&&kmem_cache_init_late
  • 2 kmem_cache实例中cpu本地高速缓存和 kmem_cache_node实例中共享高速缓存间的关系
    • 2.1 本地cpu高速缓存(cpu_cache)和cpu共享高速缓存(shared_cache)间的交互关系
    • 2.2 shared cache引入原因
  • 3 slab系统中特殊结构体内存分配
    • 3.1 创建对应数据结构的slab cache描述符
    • 3.2 分配slab obj
    • 3.3 slab obj的释放
  • 4 slab系统通用内存分配(kmalloc)
    • 4.1 kmalloc的实现
    • 4.2 释放slab系统分配的通用内存(kfree)

前面介绍的arm64架构的linux内核内存管理系统的初始化流程,linux os从start_kernel开始,完成内核内存的分页初始化和内核内容基本数据结构的初始化,并将内存管理从memblock迁移到了伙伴系统。伙伴系统建立后,内核随后就完成slab系统的初始化工作。以上整个流程的调用过程如下所示:

start_kernel()|---setup_arch()//每次分页初始化和内核内存基本数据结构的初始化|---build_all_zonelist()|---mm_init()|---mem_init()//伙伴系统初始化|---kmem_cache_init()//slab系统初始化|---......cpu初始化|---kmem_cache_init_late()

通过上面调用关系看出linux内核内存在伙伴系统初始化后通过调用函数kmem_cache_init()完成slab系统的初始化工作

1 slab系统初始化—kmem_cache_init&&kmem_cache_init_late

由于slab系统初始化前,linux内核已经完成了伙伴系统的初始化工作,内核可以很方便地通过伙伴系统提供内存。但是slab系统初始化时需要定义一些基本的数据结构来维护和管理slab系统(如slab cache描述符struct kmem_cache)。像stcuct kmem_cache这些数据结构需要小块内存进行存储,且这些小块内存往往只有几十或者几百字节远远小于一个页的大小。若通过伙伴系统分配内存来存储着这些数据结构,则违背了slab系统设计的初衷(内存资源浪费,内存分配效率低下)。所以我们只能通过slab系统给struct kmem_cache结构体分配slab缓存,然后从刚分配的slab缓存中去获取对应的slab obj(小块内存)来存储struct kmem_cache结构中的数据。看到这里已经懵逼了,在slab系统中slab缓存的描述符就是strcut kmem_cache.此时slab 系统还未开始初始化,也就是struct kmem_cache还没有呢,又怎么能从slab系统中分配到小块内存呢?这活生生的一个是鸡生蛋,蛋生鸡的问题啊(slab系统只能在slab系统初始化完成后初始化),貌似无解???

机智的linux内核使用了一个特殊的技巧来解决上面slab系统初始化时遇到的问题。

linux内核镜像编译时创建的静态数据会在内核内存初始化时就会被加载到内存中去,后续内核使用该静态数据时并不需要分配内存来存储它。linux利用这一特性,使用了编译时创建的静态结构kmem_cache_boot数据来作为系统中的第一个slab 缓存,用它来为创建struct kmem_cache时分配小块内存。

linux内核在初始化完kmem_cache_boot后,将其添加到slab系统的全局slab缓存链表slab_caches上,作为slab系统的第一个slab缓存。后面linux通过slab系统给其他基本数据结构A获取小块内存时,必然会创建一个stuct kmem_cache数据结构(作为A结构的slab cache),此时内核就能正常地从kmem_cache_boot指向的slab缓存中获取slab obj来存储刚为A创建的kmem_cache数据。

解决了slab系统初始化时的鸡生蛋,蛋生鸡问题,下面分8步对slab系统初始化进行介绍。

  1. 定义一个全局指针变量kmem_cache,该变量指向的实体是slab系统第一个slab cache描述符实例,为后面其它结构体分配struct kmem_cache时分配小块内存。

    // /mm/slab.h
    extern struct kmem_cache *kmem_cache;
    
  2. 由于此时slab系统变量还未初始化,所以无法用slab系统给该全局变量kmem_cache分配内存空间。因此linux内核在编译时就创建一个静态数据kmem_cache_boot,它在内核加载到内存时就会给kmem_cache_boot结构分配内存空间,将kmem_cache_boot数据的地址复制给kmem_cache.此时kmem_cache指针内存空间已经分配(空间位于内核代码段)。---->此处进入kmem_cache_init函数

    // /mm/slab.c
    static struct kmem_cache kmem_cache_boot = {.batchcount = 1,.limit = BOOT_CPUCACHE_ENTRIES,.shared = 1,.size = sizeof(struct kmem_cache),.name = "kmem_cache",
    };
    void __init kmem_cache_init(void)
    {......kmem_cache = &kmem_cache_boot;......
    }
    
  3. 下面需要对全局指针变量kmem_cache指向的实体结构的各个数据成员赋值,成员中有两个成员是指针变量array_cache和node。这时又需要分配小块内存来给这两个指针变量申请对应的小块内存空间。但目前slab系统不具备内存分配能力,需要特殊处理:

    1. arry_cache为Per_CPU变量通过alloc_kmem_cache_cpus函数分配内存空间,此处只是kmem_cache中的每cpu变量分配内存空间(每个cpu分配一个sizeof(arry_cache)大小的空间并将地址赋值给cpu_cache成员),但并没有使能这些本地高速缓存。需要注意这个时候只是按照固定大小给每个cpu分配一个本地高速缓存(struct array_cache的avail和batchcount成员都为1),且不会给kmem_cache->node数组成员的每个节点分配共享cpu高速缓存,即是kmem_cache->shared=0.后面待所有cpu都初始化完全后,会调用kmem_cache_init_late函数完善cache_chain链表上所有struct kmem_cache实例的cpu本地高速缓存和其每个节点共享cpu缓存的初始化。

    2. node为指针数组,数组大小为系统节点数量,它内存空间的分配再次运用静态内存分配技术,如下代码所示预先定义一个全局静态数组init_kmem_cache_node,并初始化。然后将init_kmem_cache_node赋值给kmem_cache->node,这样就为kmem_cache->node静态分配了内存空间。(node只占用了init_kmem_cache_node数组空间的前半部分,后半部分为后续分配slab系统的第二个struct kmem_cache实例的node成员提供空间。slab系统的第二个kmem_cache实例就是为kmem_cache_node分配空间的slab cache描述符)

      // /mm/slab.c
      #define NUM_INIT_LISTS (2 * MAX_NUMNODES)
      static struct kmem_cache_node __initdata init_kmem_cache_node[NUM_INIT_LISTS];static void kmem_cache_node_init(struct kmem_cache_node *parent)
      {INIT_LIST_HEAD(&parent->slabs_full);INIT_LIST_HEAD(&parent->slabs_partial);INIT_LIST_HEAD(&parent->slabs_free);parent->shared = NULL;parent->alien = NULL;parent->colour_next = 0;spin_lock_init(&parent->list_lock);parent->free_objects = 0;parent->free_touched = 0;parent->num_slabs = 0;
      }void __init kmem_cache_init(void)
      {......for (i = 0; i < NUM_INIT_LISTS; i++)kmem_cache_node_init(&init_kmem_cache_node[i]);......
      }
  4. 步骤3的准备工作完成后,通过create_boot_cache正式给全局指针变量kmem_cache指向的实体结构进行初始化(赋值),初始化完后,将kmem_cache挂到全局的struct kmem_cache链表slab_caches中。目前slab系统处于PARTIAL状态,具有创建struct kmem_cache_node结构体的slab cache描述符的能力 。

    // mm/slab_common.c
    LIST_HEAD(slab_caches);
    
    // /mm/slab.c
    void __init kmem_cache_init(void)
    {......create_boot_cache(kmem_cache, "kmem_cache",offsetof(struct kmem_cache, node) +nr_node_ids * sizeof(struct kmem_cache_node *),SLAB_HWCACHE_ALIGN);list_add(&kmem_cache->list, &slab_caches);......
    }
    

    ps:为什么处于PARTIAL只有给kmem_cache_node结构体创建slab cache描述符的能力呢?因为slab系统想要为任意结构体创建slab cache描述符,必须要给该描述符的node成员分配空间,但是目前全局的slab_caches中还没有一个能为kmem_cache_node结构体分配小块内存空间的slab cache描述符实例。所以还需要在slab系统中创建一个kmem_cache_node结构体的slab cache实例,slab系统才能为所有结构体分配其对应的slab cache描述符。那么创建kmem_cache_node结构体的slab cache实例过程中其node成员指向内存空间怎么分配呢?步骤3中的init_kmem_cache_node数组的后半部分内存空间就是为它而准备的(第三次利用静态内存分配技术)。

  5. create_kmalloc_cache函数再次利用静态分配技术为struct kmem_cache_node结构体创建一个slab cache描述符,并将描述符放在全局数组kmalloc_caches对应的index处,然后也会将描述符也会挂入全局链表slab_caches中。此时slab系统处于PARTIAL_NODE状态,该状态的slab系统能够利用kmalloc函数为struct kmem_cache_node分配对应大小的内存空间。(kmalloc_caches数组和kmalloc函数间的对应关系参考后面关于kmalloc_caches的讲解).

    // include/slab.h
    #define KMALLOC_SHIFT_HIGH	PAGE_SHIFT
    /** State of the slab allocator.** This is used to describe the states of the allocator during bootup.* Allocators use this to gradually bootstrap themselves. Most allocators* have the problem that the structures used for managing slab caches are* allocated from slab caches themselves.*/
    enum slab_state {DOWN,			/* No slab functionality yet */PARTIAL,		/* SLUB: kmem_cache_node available */PARTIAL_NODE,		/* SLAB: kmalloc size for node struct available */UP,			/* Slab caches usable but not all extras yet */FULL			/* Everything is working */
    };
    
    // mm/slab_common.c
    struct kmem_cache *kmalloc_caches[KMALLOC_SHIFT_HIGH + 1];
    EXPORT_SYMBOL(kmalloc_caches);
    
    // /mm/slab.c
    void __init kmem_cache_init(void)
    {......kmalloc_caches[INDEX_NODE] = create_kmalloc_cache("kmalloc-node",kmalloc_size(INDEX_NODE), ARCH_KMALLOC_FLAGS);slab_state = PARTIAL_NODE;......
    }
    
  6. 该步骤主要完成的工作是分别将slab系统前两个slab cache描述符中的node成员指向的静态内存区域数据迁移到kmalloc动态分配的内存区域中。

    目前slab系统处于PARTIAL_NODE状态,能为所有结构体创建slab cache描述符并分配内存空间。用slab系统的kmalloc函数动态分配两段内存区域分别记为A和B(A,B内存区域的大小都为MAX_NUMNODES*sizeof(struct kmem_cache_node)),然后将init_kmem_cache_node数组中的前半部分数据拷贝到A区域中,后半部分数据拷贝到B区域中。最后将slab系统中struct kmem_cache结构的slab cache描述符的node成员指向A区域,struct kmem_cache_node结构体slab cache描述符node成员指向B区域.

    // /mm/slab.c
    void __init kmem_cache_init(void)
    {......{int nid;for_each_online_node(nid) {init_list(kmem_cache, &init_kmem_cache_node[CACHE_CACHE + nid], nid);init_list(kmalloc_caches[INDEX_NODE],&init_kmem_cache_node[SIZE_NODE + nid], nid);}}......
    }
    

    ps:内存区域迁移原因—>init_kmem_cache_node数组定义时加了__initdata标志,这些数据在所有模块都初始化完成以后会被内核统一释放,因此要在释放前完成数据迁移工作。

  7. 通过create_kmalloc_caches函数分配一些通用的slab cache描述符,并按一定规律将这些slab cache描述符存放在全局数组kmalloc_caches中缓存起来(kmalloc_caches数组中每个索引对应一个区间大小的slab cache描述符,可以通过全局数组kmalloc_info来查看)。以后kmalloc分配指定大小内存块时,就可以直接在kmalloc_caches数组中直接搜寻对应的slab cache描述符。到此slab系统进入了UP状态.---->此处退出kmem_cache_init函数,该函数的完整源码分析参考文章 kmem_cache_init函数源码详细解析

    // mm/slab_common.c
    static void __init new_kmalloc_cache(int idx, unsigned long flags)
    {kmalloc_caches[idx] = create_kmalloc_cache(kmalloc_info[idx].name,kmalloc_info[idx].size, flags);
    }
    
    // mm/slab_common.c
    void __init create_kmalloc_caches(unsigned long flags)
    {int i;for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {if (!kmalloc_caches[i])new_kmalloc_cache(i, flags);/** Caches that are not of the two-to-the-power-of size.* These have to be created immediately after the* earlier power of two caches*/if (KMALLOC_MIN_SIZE <= 32 && !kmalloc_caches[1] && i == 6)new_kmalloc_cache(1, flags);if (KMALLOC_MIN_SIZE <= 64 && !kmalloc_caches[2] && i == 7)new_kmalloc_cache(2, flags);}
    #ifdef CONFIG_ZONE_DMAfor (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {struct kmem_cache *s = kmalloc_caches[i];if (s) {int size = kmalloc_size(i);char *n = kasprintf(GFP_NOWAIT,"dma-kmalloc-%d", size);BUG_ON(!n);kmalloc_dma_caches[i] = create_kmalloc_cache(n,size, SLAB_CACHE_DMA | flags);}}
    #endif
    }
    
    // /mm/slab.c
    void __init kmem_cache_init(void)
    {......create_kmalloc_caches(ARCH_KMALLOC_FLAGS);......
    }
    
  8. 待所有cpu都完成初始化后,通过调用kmem_cache_init_late函数完善cache_chain上每个struct kmem_cache实例的cpu高速缓存机制和初始化struct kmem_cache实例的node成员数组中每个节点对应的struct kmem_cache_node实例。

    1. 完善cpu高速缓存机制包括本地高速cpu缓存的更新和共享cpu高速缓存的更新
      1. 本地cpu高速缓存(struct kmem_cache的 cpu_array成员)更新是通过slab obj的大小重新计算array_cache每个成员的 值,然后为每个cpu重新分配一个array_cache实例(每cpu变量),并替换原先固定大小的arry_cache实例。
      2. 若是NUMA结构,还需要给slab cache缓存在每个节点上分配一个cpu共享的高速缓存空间(struct kmem_cache实例的node成员数组中每个struct kmem_cache_node 实例元素的shard成员指向该共享缓存空间)
    2. 初始化struct kmem_cache实例的node成员数组中每个节点对应的struct kmem_cache_node实例中:主要是给slab cache描述符在每个节点上初始化3个slab链表。
    // mm/slab.c
    //完善全局链表slab_caches上所有struct kmem_cache实例的缓存机制(本地缓存和每个节点shared cache)
    void __init kmem_cache_init_late(void)
    {struct kmem_cache *cachep;slab_state = UP;/* 6) resize the head arrays to their final sizes */mutex_lock(&slab_mutex);遍历slab_caches上所有的slab cache描述符(struct kmem_cache结构体实例)list_for_each_entry(cachep, &slab_caches, list)/**完善当前slab cache描述符的cpu高速缓存机制:*1.对本地cpu高速缓存(cachep->cpu_array),通过slab obj的大小重新计算array_cache每个成员的*  值,然后为每个cpu重新分配一个array_cache实例(每cpu变量),并替换旧的arry_cache实例。*2.若是NUMA结构,完善cpu共享高速缓存机制(分配足够的共享array_cache实例并完成地址关联).*3.完成slab cache在每个节点上初始化3个slab链表,用于管理3种slab类型中slab obj*/if (enable_cpucache(cachep, GFP_NOWAIT))BUG();mutex_unlock(&slab_mutex);/* Done! */slab_state = FULL;......
    }   
    

通过上面8个步骤的初始化slab系统处于了FULL状态(slab_state = FULL),后续就可以通过用slab系统相关的函数接口为小块内存分配内存空间了。

2 kmem_cache实例中cpu本地高速缓存和 kmem_cache_node实例中共享高速缓存间的关系

kmem_cache实例有一个Per_CPU成员变量cpu_cache,表示kmem_cache实例对于每个cpu都有一个array_cache,作为每cpu申请对应slab obj内存的本地高速缓存。同时kmem_cache实例对每个内存节点都维护了一个kmem_cache_node实例,kmem_cache_node实例的shared成员也指向一个cache_arry缓存区域(后面统一称为shared_cache),shared_cache会为每个cpu内存的申请提供缓存(也是缓存对应的slab obj)。

2.1 本地cpu高速缓存(cpu_cache)和cpu共享高速缓存(shared_cache)间的交互关系

slab系统常规slab obj申请流程:

系统首先会从cpu_cache中尝试获取一个对象用于分配,若失败,则尝试来到所有CPU共享的shared_cache中尝试获取;若还是失败就会从对应节点slab链表中分
配一个slab obj,若仍然失败,kmem_cache实例则会从伙伴系统获取一组连续的页框建立一个新的SLAB,然后从新的slab中获取一个对象

slab系统常规slab obj释放流程:

系统先将对象释放到cpu_cache缓存中,若cpu_cache中slab obj数量超过限制,将cpu_array中的batchcount个slab obj迁移到shared_cache中.若
shared_cache中slab obj数量超过限制,则将shared_cache中的batchcount个slab obj迁移到自己所属的slab中(slab位于对应节点的3条链表中)。
若对应节点的空闲slab obj过多,kmem_cache实例会清理出一些完全空闲的slab,并将这些slab占用的页释放到伙伴系统中

shared cache充当了cpu_cache和slab间slab obj申请和释放的缓存

  1. 当cpu_cache中的slab obj用尽后,会调用cache_alloc_refill函数申请最多batchcount个slab obj到cpu_cache缓存中:

    //mm/slab.c
    cache_alloc_refill:先判断本地节点的shared_cache中是否有可用slab obja.若有,则调用transfer_objects函数将shared_cache中的最多bactchcount个slab obj转移到cpu_cache中.b.若无,则到本地内存节点的slab链表中去获取最多bactchcount个slab obj,并转移到cpu_cache中
    
  2. 当cpu_cache中的slab obj装满(数量达到或超过cpu_cache->limit),会调用cache_flusharray函数将最多batchcount个slab obj转移到shared_cache或slab链表中:

    //mm/slab.c
    cache_flusharray:先判断本地节点的的shared_cache是否已满:a.若未满(缓存的slab obj个数小于shared_cache->limit),则将最多batchcount个slab obj从cpu_cache中转移到shared_cache缓存中。b.若已满,则将最多batchcount个slab obj从cpu_cache中转移到本地内存节点的slab链表中
    

    ps:当slab三链的空闲slab对象超过上限,并且待释放的slab对象对应的slab中已经没有正在使用的slab对象时,将slab直接释放掉(释放到伙伴系统中)。释放slab对应的函数是slab_destroy

2.2 shared cache引入原因

  1. shared_cache加快了slab系统申请和释放slab obj的速度:
    slab obj释放到本地节点的shard_cache要比将释放的本地节点的slab链表更快.而且从shared_cache中申请slab
    obj的速度要快于从slab链表中申请slab obj的速度。
  2. shared_cache增加了硬件cache命中的几率:
    同种类型的slab obj被linux os短时间内连续地申请和释放时,刚被cpu释放的slab obj会先缓存在cpu_cache中,然后经过shared_cache中转后,最后才会将其返还给对应内存节点的slab链表。若在上述过程中cpu又需要刚被释放的同类型的slab obj,若我们再shared_cache缓存中获取一个slab obj,该slab obj在硬件缓存中被剔除的概率会很小,缓存命中率就会较大。但是我们从slab链表中去获得slab obj,此时可能会从其他slab中去分配slab obj,而不是刚释放的slab obj对应的slab,显然新分配的slab obj在硬件缓存中的概率会很小,硬件缓存命中率就会很小。

3 slab系统中特殊结构体内存分配

slab系统初始化后,就能在内核态用于对任意数据结构分配小块内存。具体方法如下:

3.1 创建对应数据结构的slab cache描述符

//mm/slab_common.c
/*参数和返回值介绍:* kmem_cache_create - Create a cache.* @name: A string which is used in /proc/slabinfo to identify this cache.* @size: The size of objects to be created in this cache.* @align: The required alignment for the objects.* @flags: SLAB flags* @ctor: A constructor for the objects.** Returns a ptr to the cache on success, NULL on failure.* Cannot be called within a interrupt, but can be interrupted.* The @ctor is run when new pages are allocated by the cache.** The flags are** %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5)* to catch references to uninitialised memory.** %SLAB_RED_ZONE - Insert `Red' zones around the allocated memory to check* for buffer overruns.** %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware* cacheline.  This can be beneficial if you're counting cycles as closely* as davem.*/
struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t align,unsigned long flags, void (*ctor)(void *))
kmem_cache_create()|--->__kmem_cache_alias()//查找是否有现成的slab cache描述符可用|--->find_mergeable()//遍历slab_caches链表上的所有slab cache描述符,进行匹配|--->若获取到现成的slab描述符直接返回该描述符,否则继续往下执行|--->create_cache()//给对应的结构体分配一个slab cache描述符,并将该描述符添加到全局slab_caches链表上|--->kmem_cache_zalloc()//利用struct kmem_cache的slab cache描述符,为指定结构体的slab cache描述分配空间	                        |--->利用传入的slab描述符数据初始化部分指定结构体slab描述相关成员.|--->__kmem_cache_create();//完善指定结构体slab cache描述的缓存机制|--->初始化指定结构体slab描述相关成员(对齐后大小,freelist_size等)|--->setup_cpu_cache()//完善slab cache描述符的缓存机制--->enable_cpucache()//使能指定结构体slab描述本地缓存和共享缓存|--->list_add(&s->list, &slab_caches)//将指定结构体slab cache描述符链入全局链表

3.2 分配slab obj

kmem_cache_alloc函数利用创建的slab cache描述符给对应结构体分配内存空间(slab obj),该函数在创建结构体对应slab cache描述符时已经被调用过,用它来为struct kmem_cache结构体分配内存空间(kmem_cache_zalloc()函数调用了kmem_cache_alloc函数来给stuct kmem_cache实例分配内存空间,两函数的区别是kmem_cache_zalloc分配的内存空间要被0填充)

*** kmem_cache_alloc - Allocate an object* @cachep: The cache to allocate from.* @flags: See kmalloc().** Allocate an object from this cache.  The flags are only relevant* if the cache has no available objects.*/
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
//mm/slab.c
kmem_cache_alloc()|--->slab_alloc()|--->local_irq_save()|--->__do_cache_alloc()//在关闭本地中断的情况下调用该函数来分配slab obj|--->____cache_alloc()|--->local_irq_restore()

slab_alloc函数中在关闭本地中断的前提下调用____do_cache_alloc()函数从指定的结构体的slab描述符中分配地址空间(slab obj)

//mm/slab.c
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{void *objp;struct array_cache *ac;check_irq_off();//获取指定结构体对应的slab cache描述的本地高速缓存ac = cpu_cache_get(cachep);//若该描述符本地cpu高速缓存中有可用的slab obj直接从其entry数组中获取slab obj的地址(entry后进先出)if (likely(ac->avail)) {ac->touched = 1;objp = ac->entry[--ac->avail];STATS_INC_ALLOCHIT(cachep);goto out;}STATS_INC_ALLOCMISS(cachep);/**若对应的本地高速缓存中无slab obj,则调用cache_alloc_refill填充本地高速缓存,并分配slab obj:*(1)获取slab cache描述符中本地节点中的cpu共享缓存,若共享缓存含有slab obj,则通过transfer_objects*   函数将共享缓存的部分slab obj迁移到本地缓存中去(迁移数量不超过ac->batchcount),最后取出本地高*   速缓存的entry数组中最后一个slab obj并分配出去*(2)若本地内存节点cpu共享缓存中也无slab obj,则检查本地内存节点中的slabs_partial链表和slabs_free*   链表,若两个链表不都为空,则获取含有空闲slab obj的slab,将该slab的对象迁移部分到本地高速缓*   存中去(迁移对象个数不超过ac->batchcount),然后根据slab中空闲obj的数量将其插入到对应的slab链*   表中(slab_partial或slab_full),最后从本地高速缓存获取slb obj并分配出去.*(3)若该结构体对应的整个slab cache描述符中都找不到空闲slab obj,则只能通过伙伴系统分配2^gfporder*	 个页的内存块,并封装成slab,然后插入到本地节点的空闲链表中,最后将新增加的slab中的部分slab obj迁*   移到cpu本地缓存中去,并将本地高速缓存的entry数组中最后一个slab obj分配出去*/objp = cache_alloc_refill(cachep, flags);/** the 'ac' may be updated by cache_alloc_refill(),* and kmemleak_erase() requires its correct value.*///再次获取cpu本地高速缓存,因为它可能被cache_alloc_refill更新过ac = cpu_cache_get(cachep);out:/** To avoid a false negative, if an object that is in one of the* per-CPU caches is leaked, we need to make sure kmemleak doesn't* treat the array pointers as a reference to the object.* 防止每cpu变量出现内存泄露的情况*/if (objp)kmemleak_erase(&ac->entry[ac->avail]);//entry容器存放的指向slab obj的虚拟地址return objp;
}

本地高速缓存的slab obj填充函数cache_alloc_refill:

//mm/slab.c
static void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags)
{int batchcount;struct kmem_cache_node *n;struct array_cache *ac, *shared;int node;void *list = NULL;struct page *page;check_irq_off();node = numa_mem_id();ac = cpu_cache_get(cachep);batchcount = ac->batchcount;if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {/** If there was little recent activity on this cache, then* perform only a partial refill.  Otherwise we could generate* refill bouncing.*/batchcount = BATCHREFILL_LIMIT;}//获取slab cache描述符本地节点对应的struct kmem_cache_node实例n = get_node(cachep, node);BUG_ON(ac->avail > 0 || !n);//获取本地内存节点的cpu共享缓存池shared = READ_ONCE(n->shared);/**若共享缓存池缓存对象为空且本地内存节点中slab_partial和slab_free链表中的slab都为空,则需要通过伙伴*系统分配新的slab(2^gfporder个连续页)*/if (!n->free_objects && (!shared || !shared->avail))goto direct_grow;spin_lock(&n->list_lock);shared = READ_ONCE(n->shared);/* See if we can refill from the shared array *//**本地节点共享缓存池存在可用对象,通过transfer_objects函数迁移部分slab obj到本地高速缓存池,并从本*地高速缓存中将ac->entry[ac->avail]指向的slab obj分配出去*/if (shared && transfer_objects(ac, shared, batchcount)) {shared->touched = 1;goto alloc_done;}/**若本地节点存在包含空闲slab obj的slab,循环执行下面两个操作,直到迁移的slab obj个数等于*ac->batchcount或节点2个slab链表中不存在包含空闲slab obj的slab时为止:* 		1.通过get_first_slab获取本地节点中包含空闲slab obj的slab* 		2.利用alloc_block从slab中将空闲的slab obj迁移到本地高速缓存中(迁移obj数量等于batchcount或*		  slab中空闲obj被搬空为止)*/while (batchcount > 0) {/* Get slab alloc is to come from. */page = get_first_slab(n, false);if (!page)goto must_grow;check_spinlock_acquired(cachep);/**从slab中迁移空闲slab obj到本地cpu高速缓存(当迁移obj个数等于ac->batchcount或slab中不存在空闲*slab obj为止)*/batchcount = alloc_block(cachep, ac, page, batchcount);//根据当前slab中空闲obj的情况将该slab插入到对应的slab链表中fixup_slab_list(cachep, n, page, &list);}must_grow://node上空闲slab obj数量,减去放到本地CPU缓存的slab obj数量n->free_objects -= ac->avail;
alloc_done:spin_unlock(&n->list_lock);fixup_objfreelist_debug(cachep, &list);direct_grow://结构体对应的slab cache描述符中所有地方都找不到空闲的slab obj,就只能通过伙伴系统分配新的slabif (unlikely(!ac->avail)) {/* Check if we can use obj in pfmemalloc slab */if (sk_memalloc_socks()) {void *obj = cache_alloc_pfmemalloc(cachep, n, flags);if (obj)return obj;}//从伙伴系统分配页框,并初始化为slab管理对象,最后挂入本地节点的slab_partial链表page = cache_grow_begin(cachep, gfp_exact_node(flags), node);/**页块分配的时候中断可能被再次使能,所以再次确定本地CPU缓存是否可用,防止分配页框是其他进程也做了*相同操作*/ac = cpu_cache_get(cachep);/**1.将新获取的slab中部分slab obj迁移到本地高速缓存池中,然后将新slab出入到和自身状态一致的链表中*2.部分slab obj:若slab obj中空闲的obj大于等于batchcount,则迁移batchcount个obj,而小于*  batchcount,则会将迁移slab中所有空闲obj*/if (!ac->avail && page)alloc_block(cachep, ac, page, batchcount);//收尾工作,页框挂载到对应的slab链表,以及更新统计信息cache_grow_end(cachep, page);if (!ac->avail)return NULL;}ac->touched = 1;//返回将被分配出去的slab obj,本地高速缓存的entry容器是LIFO(后进先出),确保分配出去的页是最热的。return ac->entry[--ac->avail];
}

伙伴系统分配页块给slab系统提供slab(cache_grow_begin):

//mm/slab.c
cache_grow_begin()|--->kmem_getpages()//从伙伴管理系统上获取一个空闲页块(页面数为cachep->gfporder),并将该页块设置成slab.|--->__alloc_pages_node()|--->__SetPageSlab(page)|--->alloc_slabmgmt()//为新的slab分配空闲slab obj的索引数组(freelist),freelist数组可能嵌入slab,或与slab分离(通过OFF_SLAB(cachep)判断)|--->cache_init_objs()//初始化slab上的空闲对象,包括空闲对象索引数组freelist

3.3 slab obj的释放

slab系统通过kmem_cache_free函数来释放其为指定结构体分配的slab obj(地址空间).

/*** kmem_cache_free - Deallocate an object* @cachep: The cache the allocation was from.* @objp: The previously allocated object.** Free an object which was previously allocated from this* cache.*/
void kmem_cache_free(struct kmem_cache *cachep, void *objp)
{unsigned long flags;/**获取objp对应的slab cache描述符*1.obj虚拟地址通过virt_to_pfn和pfn_to_page两个函数找到obj对应空间页描述符struct page*2.page->slab_slab_cache获取到obj的slab cache描述符*/cachep = cache_from_obj(cachep, objp);......//关闭本地CPU中断local_irq_save(flags);......//objp释放的核心函数__cache_free(cachep, objp, _RET_IP_);local_irq_restore(flags);......
}
/** Release an obj back to its cache. If the obj has a constructed state, it must* be in this state _before_ it is released.  Called with disabled ints.*/
static inline void __cache_free(struct kmem_cache *cachep, void *objp,unsigned long caller)
{/* Put the object into the quarantine, don't touch it for now. */if (kasan_slab_free(cachep, objp))return;___cache_free(cachep, objp, caller);
}
//函数速省略了debug调试处理函数
void ___cache_free(struct kmem_cache *cachep, void *objp,unsigned long caller)
{//获取本地高速缓存struct array_cache *ac = cpu_cache_get(cachep);//检查本地cpu中断是否关闭check_irq_off();....../**slab cache描述符本地cpu高速缓存池缓存的空闲obj数量超过限制(ac->avail>=ac->limit),调用cache_flusharray*函数对本地缓存池的空闲obj进行回收尝试*/if (ac->avail < ac->limit) {//limit,avail和batchcount值在slab创建该slab cache描述时,enable_cpucache()								  函数根据slab obj大小计算而来STATS_INC_FREEHIT(cachep);} else {STATS_INC_FREEMISS(cachep);cache_flusharray(cachep, ac);}......//将释放的放在本地cpu高速缓存池目前的最热位置(ac->entry的末尾,它是LIFO容器)ac->entry[ac->avail++] = objp;
}

当slab cache描述符的本地cpu高速缓存池中的空闲slab obj数量超过ac->limit值,则slab系统会调用ache_flusharray函数将本地缓存池的部分空闲obj slab进行回收处理:将本地高速缓存cpu_cache的enrty容器中前batchcount个空闲slab obj迁移到本节点cpu共享缓存shared_cache的entry容器的末尾。

static void cache_flusharray(struct kmem_cache *cachep, struct array_cache *ac)
{int batchcount;struct kmem_cache_node *n;int node = numa_mem_id();LIST_HEAD(list);//获取ac,在回收收缩,迁出的空闲obj速率batchcount = ac->batchcount;//验证本地cpu中断是否关闭check_irq_off();//获取本地内存节点对应的struct kmem_cache_node描述符n = get_node(cachep, node);//加锁spin_lock(&n->list_lock);/**本地节点存在cpu共享缓存池,则将本地高速缓存池entry容器中前面N个元素拷贝到cpu共享缓存池的entry容器的尾端.* n = min(ac->batchcount,shared_array->limit - shared_array->avail).*/if (n->shared) {struct array_cache *shared_array = n->shared;int max = shared_array->limit - shared_array->avail;if (max) {//本地缓存迁移slab obj的个数min(ac->batchcount,shared_array->limit - shared_array->avail)if (batchcount > max)batchcount = max;/**ac为本地高速缓存,shared_array为共享cpu缓存。此处是将ac->entry容器头部的batchcount个元素迁移逐个*到shared_array->enrty容器的尾部.*/memcpy(&(shared_array->entry[shared_array->avail]),ac->entry, sizeof(void *) * batchcount);shared_array->avail += batchcount;goto free_done;}}free_block(cachep, ac->entry, batchcount, node, &list);
free_done:
#if STATS{int i = 0;struct page *page;list_for_each_entry(page, &n->slabs_free, lru) {BUG_ON(page->active);i++;}STATS_SET_FREEABLE(cachep, i);}
#endifspin_unlock(&n->list_lock);slabs_destroy(cachep, &list);ac->avail -= batchcount;//迁移后,将本地cpu缓存池的entry容器中后面剩余的空闲的obj对象逐个移动到容器的头部memmove(ac->entry, &(ac->entry[batchcount]), sizeof(void *)*ac->avail);
}

4 slab系统通用内存分配(kmalloc)

/*** kmalloc - allocate memory* @size: how many bytes of memory are required.* @flags: the type of memory to allocate.** kmalloc is the normal method of allocating memory* for objects smaller than page size in the kernel.** The @flags argument may be one of:** %GFP_USER - Allocate memory on behalf of user.  May sleep.** %GFP_KERNEL - Allocate normal kernel ram.  May sleep.** %GFP_ATOMIC - Allocation will not sleep.  May use emergency pools.*   For example, use this inside interrupt handlers.** %GFP_HIGHUSER - Allocate pages from high memory.** %GFP_NOIO - Do not do any I/O at all while trying to get memory.** %GFP_NOFS - Do not make any fs calls while trying to get memory.** %GFP_NOWAIT - Allocation will not sleep.** %__GFP_THISNODE - Allocate node-local memory only.** %GFP_DMA - Allocation suitable for DMA.*   Should only be used for kmalloc() caches. Otherwise, use a*   slab created with SLAB_DMA.** Also it is possible to set different flags by OR'ing* in one or more of the following additional @flags:** %__GFP_COLD - Request cache-cold pages instead of*   trying to return cache-warm pages.** %__GFP_HIGH - This allocation has high priority and may use emergency pools.** %__GFP_NOFAIL - Indicate that this allocation is in no way allowed to fail*   (think twice before using).** %__GFP_NORETRY - If memory is not immediately available,*   then give up at once.** %__GFP_NOWARN - If allocation fails, don't issue any warnings.** %__GFP_REPEAT - If allocation fails initially, try once more before failing.** There are other flags available as well, but these are not intended* for general use, and so are not documented here. For a full list of* potential flags, always refer to linux/gfp.h.*/
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{......
}

kmalloc函数是slab系统中传统意义上的内存分配函数,通过传入要申请内存空间的大小Size,就可以返回一个按字节对齐的内存空间给调用者使用,不会涉及到slab cache描述符的创建等操作。这有点类似于用户空间的malloc函数。例如向slab系统分配一个大小为15字节的内存空间,则可以用kmalloc(15,GFP_KERNEL)来实现,它会返回一个16字节大小的内存空间(slab obj)给调用者。

4.1 kmalloc的实现

在slab系统初始化时,它对一个全局数组进行了初始化

// mm/slab_common.c
struct kmem_cache *kmalloc_caches[KMALLOC_SHIFT_HIGH + 1];

初始化后,该数组中存储的每个slab cache描述符描述的都是大小不同的内存空间(内存空间大小都是按8字节对齐的)。可以通过kmalloc_info数组来了解全局素组kmalloc_caches中每个slab cache描述的名字和对应slab obj大小.

//mm/slab_common.c
/** kmalloc_info[] is to make slub_debug=,kmalloc-xx option work at boot time.* kmalloc_index() supports up to 2^26=64MB, so the final entry of the table is* kmalloc-67108864.*/
static struct {const char *name;unsigned long size;
} const 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}
};

slab系统可以通过内存空间的大小Size获得其匹配的slab cache描述符在全局素组slab caches中的索引,通过如下kmalloc_index函数来实现。

/** 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*/
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;
}

到此kmalloc函数的实现方式已经很明了了:

  1. 先将传入的内存空间大小Size,通过kmalloc_index函数处理,获得其匹配的slab cache描述符在全局数组kmalloc_caches中的索引Index_Size
  2. 通过索引Index_Size,在全局素组kmalloc_caches中获取到分配Index_Size内存空间的slab cache描述 Kmem_Cache_Size
  3. 在获得Kmem_Cache_Size描述符后,通过kmem_cache_alloc函数就能分配出需要的内存空间。但是这里需要注意Kmem_Cache_Size分配出的slab obj指向的内存空间大小是Size按8字节对齐后的值.

4.2 释放slab系统分配的通用内存(kfree)

/*** kfree - free previously allocated memory* @objp: pointer returned by kmalloc.** If @objp is NULL, no operation is performed.** Don't free memory not originally allocated by kmalloc()* or you will run into trouble.*/
void kfree(const void *objp)
{struct kmem_cache *c;unsigned long flags;//记录kfree轨迹trace_kfree(_RET_IP_, objp);//对地址做非零判断if (unlikely(ZERO_OR_NULL_PTR(objp)))return;local_irq_save(flags);kfree_debugcheck(objp);//将虚拟地址转化为页面描述符,然后用page的slab_cache成员获得对应的slab cache描述符c = virt_to_cache(objp);debug_check_no_locks_freed(objp, c->object_size);debug_check_no_obj_freed(objp, c->object_size);//将objp释放到对应slab cache描述符的本地缓存中去cpu_cache__cache_free(c, (void *)objp, _RET_IP_);local_irq_restore(flags);
}
EXPORT_SYMBOL(kfree);

kfree函数传入的参数是kmalloc函数执行成功的返回值,就是slab系统分配的一段内存空间的首地址(在slab系统中也可将其称为slab obj),kfreee函数就是要将该obj释放到其对应的slab cache描述符的本地高速缓存中去(缓存满了会涉及到缓存的收缩操作)

  1. 通过virt_to_cache获取到函数参数objp对应的slab cache描述符Kmem_cache_tmp
  2. 通过__cache_free函数将objp释放到Kmem_cache_tmp的本地高速缓存缓存池中(前面3.3.3小节已经对__cache_free函数有过详细介绍)

这篇关于[内核内存] slab分配器2---slab系统初始化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1001157

相关文章

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

2.1/5.1和7.1声道系统有什么区别? 音频声道的专业知识科普

《2.1/5.1和7.1声道系统有什么区别?音频声道的专业知识科普》当设置环绕声系统时,会遇到2.1、5.1、7.1、7.1.2、9.1等数字,当一遍又一遍地看到它们时,可能想知道它们是什... 想要把智能电视自带的音响升级成专业级的家庭影院系统吗?那么你将面临一个重要的选择——使用 2.1、5.1 还是

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

Ubuntu系统怎么安装Warp? 新一代AI 终端神器安装使用方法

《Ubuntu系统怎么安装Warp?新一代AI终端神器安装使用方法》Warp是一款使用Rust开发的现代化AI终端工具,该怎么再Ubuntu系统中安装使用呢?下面我们就来看看详细教程... Warp Terminal 是一款使用 Rust 开发的现代化「AI 终端」工具。最初它只支持 MACOS,但在 20

windows系统下shutdown重启关机命令超详细教程

《windows系统下shutdown重启关机命令超详细教程》shutdown命令是一个强大的工具,允许你通过命令行快速完成关机、重启或注销操作,本文将为你详细解析shutdown命令的使用方法,并提... 目录一、shutdown 命令简介二、shutdown 命令的基本用法三、远程关机与重启四、实际应用

Debian如何查看系统版本? 7种轻松查看Debian版本信息的实用方法

《Debian如何查看系统版本?7种轻松查看Debian版本信息的实用方法》Debian是一个广泛使用的Linux发行版,用户有时需要查看其版本信息以进行系统管理、故障排除或兼容性检查,在Debia... 作为最受欢迎的 linux 发行版之一,Debian 的版本信息在日常使用和系统维护中起着至关重要的作

Linux内核之内核裁剪详解

《Linux内核之内核裁剪详解》Linux内核裁剪是通过移除不必要的功能和模块,调整配置参数来优化内核,以满足特定需求,裁剪的方法包括使用配置选项、模块化设计和优化配置参数,图形裁剪工具如makeme... 目录简介一、 裁剪的原因二、裁剪的方法三、图形裁剪工具四、操作说明五、make menuconfig

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

什么是cron? Linux系统下Cron定时任务使用指南

《什么是cron?Linux系统下Cron定时任务使用指南》在日常的Linux系统管理和维护中,定时执行任务是非常常见的需求,你可能需要每天执行备份任务、清理系统日志或运行特定的脚本,而不想每天... 在管理 linux 服务器的过程中,总有一些任务需要我们定期或重复执行。就比如备份任务,通常会选在服务器资