内存管理框架---Slab(二)

news/2024/4/19 18:34:03/文章来源:https://blog.csdn.net/weixin_43379278/article/details/129205607

站在Arnold Lu@南京大佬的肩膀,俯瞰内存管理之slab

文章目录

  • slab层的由来
    • slab/slub/slob
  • slab层的设计思想
    • slab相关数据结构
    • 创建slab描述符
    • 分配slab对象
    • 释放slab对象
    • 销毁缓存
  • 在内核栈上的静态分配
  • 高端内存的映射
    • 永久映射
    • 临时映射
  • 每个CPU的分配
  • 新的每个CPU接口
    • 编译时的每个CPU数据
    • 运行时的每个CPU接口
    • 使用每个CPU数据的原因
  • 分配函数的选择(重点)
  • 参考资料

Linux内存管理框架图

Kernel代码阅读网站

关键词
slab/slub/slob、slab描述符、kmalloc、slabs_partial/slabs_full/slabs_free、avail/limit/batchcount。

从【Linux内存管理框架】图可以看出:slab/slub/slob都是基于伙伴系统。
伙伴系统是以page为单位进行操作的。但是很多场景并不需要如此大的内存分配,slab就是用在这种场景的。

本章节主要内容:从slab相关数据结构讲起,对slab有一个静态的认识;然后介绍slab从创建描述符->分配缓存->释放缓存->销毁描述符介绍整个slab生命周;最后介绍基于slab分配器的kmalloc的运行原理。

slab层的由来

分配和释放数据结构是所有内核中最普遍的操作之一。为了便于数据的频繁分配和回收,常常会用到一个空间链表,它就相当于对象高速缓存以便快速存储频繁使用的对象类型。在内核中,空闲链表面临的主要问题之一是不能全局控制。当可用内存变得紧张的时候,内核无法通知每个空闲链表,让其收缩缓存的大小以便释放一些内存来。实际上,内核根本就不知道存在任何空闲链表。为了弥补这一缺陷,也为了是代码更加稳固,linux内核提供了slab层(也就是所谓的slab分类器),slab分配器扮演了通用数据结构缓存层的角色。slab分配器试图在如下几个原则中寻求一种平衡:
1.频繁使用的数据结构也会频繁分配和释放,因此应当缓存它们。
2.频繁分配和回收必然会导致内存碎片。为了避免这种情况,空闲链表的缓存会连续地存放。因为已释放的数据结构又会放回空闲链表,不会导致碎片。
3.回收的对象可以立即投入下一次分配,因此,对于频繁的分配和释放,空闲链表能够提高其性能。
4.如果让部分缓存专属于单个处理器,那么,分配和释放就可以在不加SMP锁的情况下进行。
5.对存放的对象进行着色,以防止多个对象映射到相同的高速缓存行。
6.如果分配器知道对象大小,页大小和总的高速缓存的大小,会做出更明智的选择。

slab分配器可以创建新的slab;
slab层只有当给定的高速缓存中没有部分满或空闲的slab时才会调用页分配函数。
当内存短缺时,才会调用释放高速缓存接口释放高速缓存。
slab分配器模式对基于内存区分配算法有较高的效率。
slab分配器模式基于以下前提:
(1)所存放的数据可以影响内存区的分配方式
(2)内核函数倾向于反复请求同一类型的内存区
(3)对内存区的请求可以根据它们发生的频率来分类④数据结构的起始地址不是物理地址2的幂⑤硬件高速缓存的高性能可以尽可能的限制对伙伴系统分配器调用。
包含告诉缓存的主内存区被划分为多个slab,每个slab有一个或多个连续的页框组成,这些页框中既包含已分配的对象,也包含空闲对象。

slab/slub/slob

slab、slob和slub都是小内存分配器,slab是slob和slub实现的基础,而slob和slub是针对slab在不同场景下的优化版本。slob 适用于微小嵌入式系统,slub 使用大型大内存系统,这样性能比slab更好。

slab层的设计思想

1.slab层把不同的对象划分为所谓的高速缓存组,其中每个高速缓存都存放不同的类型对象。
2.每种对象类型对应一个高速缓存。kmalloc()接口建立在slab层上,使用了一组通用高速缓存。
3.这些高速缓存又被划分为slab, slab由一个或多个物理上连续的页组成,一般情况下,slab也就仅仅由一页组成。每个高速缓存可以由多个slab组成。每个slab都包含一些对象成员,这里的对象指的是被缓存的数据结构,每个slab处于三种状态之一:满,部分满,空。 当内核的某一部分需要一个新的对象时,先从部分满的slab中进行分配。如果没有部分满的slab,就从空的slab中进行分配。如果没有空的slab,就要创建一个slab了。这种策略能减少碎片。
高速缓存、slab和对象之间的关系:
在这里插入图片描述
在这里插入图片描述
slab分配器最终还是由伙伴系统来分配出实际的物理页面,只不过slab分配器在这些连续的物理页面上实现了自己的算法,以此来对小内存块进行管理。
在这里插入图片描述

  1. 每个slab由多少个页面组成?
    每个slab由一个或多个连续页面组成,最低一个,物理连续。
  2. slab需要的物理内存在什么时候分配?
    首先kmem_cache_create是并不分配页面,等到kmem_cache_alloc时才有可能分配页面。首先从本地缓冲池和共享缓冲池、三大链表都没有空闲对象时,才会去分配2^gfporder个页面,然后挂入到slabs_free中。
  3. slab描述符中空闲对象过多,是否要回收?
    有两种方式回收空闲对象:
    (1)使用kmem_cache_free释放对象,当本地和共享对象缓冲池中空闲对象数目ac->avail大于ac->limit时,系统会主动释放batchcount个对象。当所有空闲数目大于系统空闲对象数目极限值,并且slab没有活跃对象时,可以销毁此slab,回收内存。
    (2)系统注册了delayed_work,定时扫描slab描述符,回收一部分空闲对象,在cache_reap中实现。
  4. slab的cache colour着色区作用?
    使不同slab上同一个相对位置slab对象的起始地址在高速缓存中相互错开,有利于改善高速缓存的行能。
    另一个利用cache场景是Per-CPU类型本地对象缓冲池。两个优点:让一个对象尽可能地运行在同一个CPU上;访问Per-CPU类型本地对象缓冲池不需要获取额外自选锁。

slab相关数据结构

  1. slab对象的描述符struct kmem_cache,即高速缓存描述符

kernel-4.19/include/linux/slab_def.h

struct kmem_cache {struct array_cache __percpu *cpu_cache; -----------------用于找到空闲内存的结点信息/* 1) Cache tunables. Protected by slab_mutex */unsigned int batchcount;-----------------------------------表示当前CPU本地缓冲池array_cache为空时,从共享缓冲池或者slabs_partial/slabs_free列表中获取对象的数目。unsigned int limit;----------------------------------------表示当本地对象缓冲池空闲对象数目大于limit时就会主动释放batchcount个对象,便于内核回收和销毁slab。unsigned int shared;unsigned int size;-----------------------------------------align过后的对象长度struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */unsigned int flags;        /* constant flags */------------分配掩码unsigned int num;        /* # of objs per slab */----------slab中有多少个对象/* 3) cache_grow/shrink *//* order of pgs per slab (2^n) */unsigned int gfporder;------------------------------------此slab占用z^gfporder个页面/* force GFP flags, e.g. GFP_DMA */gfp_t allocflags;size_t colour;            /* cache colouring range */----一个slab有几个不同的cache lineunsigned int colour_off;    /* colour offset */----------一个cache order的长度,和L1 Cache Line长度相同struct kmem_cache *freelist_cache;unsigned int freelist_size;/* constructor func */void (*ctor)(void *obj);/* 4) cache creation/removal */const char *name;----------------------------------------slab描述符的名称struct list_head list;int refcount;--------------------------------------------被引用的次数,供slab描述符销毁参考int object_size;-----------------------------------------对象的实际大小int align;-----------------------------------------------对齐的大小/* 5) statistics */
#ifdef CONFIG_DEBUG_SLABunsigned 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;/** 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, the following two* variables contain the offset to the user object and its size.*/int obj_offset;
#endif /* CONFIG_DEBUG_SLAB */
#ifdef CONFIG_MEMCG_KMEMstruct memcg_cache_params memcg_params;
#endifstruct kmem_cache_node *node[MAX_NUMNODES];-------slab对应的节点的struct kmem_cache_node数据结构
}
  1. 本地CPU缓冲池struct array_cache

kernel-4.19/mm/slab.c

struct array_cache {unsigned int avail;-------------对象缓冲池中可用的对象数目unsigned int limit;-------------ac中最大可用缓存的片段数量unsigned int batchcount;--------ac一次性申请或者释放的片段数量unsigned int touched;----------从缓冲池移除一个对象时,touched置1;收缩缓存时,touched置0。void *entry[];-----------------保存对象的实体
};
  1. 内存节点的slab列表

kernel-4.19/mm/slab.h

/** The slab lists for all objects.*/
struct kmem_cache_node {spinlock_t list_lock;#ifdef CONFIG_SLABstruct list_head slabs_partial;    /* partial list first, better asm code */----slab链表中部分对象空闲struct list_head slabs_full;----------------------------------------------------slab链表中没有对象空闲struct list_head slabs_free;----------------------------------------------------slab链表中所有对象空闲unsigned long free_objects;-----------------------------------------------------三个链表中空闲对象数目unsigned int free_limit;--------------------------------------------------------slab中可容许的空闲对象数目最大阈值。unsigned int colour_next;    /* Per-node cache coloring */struct array_cache *shared;    /* shared per node */----------------------------多核CPU公用的共享对象缓冲池struct alien_cache **alien;    /* on other nodes */unsigned long next_reap;    /* updated without locking */int free_touched;        /* updated without locking */
#endif#ifdef CONFIG_SLUBunsigned long nr_partial;struct list_head partial;
#ifdef CONFIG_SLUB_DEBUGatomic_long_t nr_slabs;atomic_long_t total_objects;struct list_head full;
#endif
#endif};

kmem_cache的关键结构体,page的lru会链接到mem_node的slabs_xxx链表上,而page的freelist用来记录该order的page的不同片段的起始地址,page->s_mem为page所代码的地址的起始地址。另外有些从kmem_cache的ac中分配,然后再是mem_node(ac只是mem_node的一个缓存而已)。
在这里插入图片描述

  1. slab flags
Slab flags描述
SLAB_HWCACHE_ALIGN这个标志命令slab层把一个slab内的所有对象按高速缓存行对齐。可以防止“错误的共享”(两个或多个对象尽管位于不同的内存地址,但映射到相同的高速缓存行)。可以提高性能,但是以增加内存踪迹为代价。
SLAB_MUST_HWCACHE_ALIGN这个标志强制slab层缓存对齐对象。通常这个标志是不需要的,上一个标志就足够了。
SLAB_POSION这个标志使slab层用已知的值(a5a5a5a5)填充slab。这就是所谓的“中毒”,有利于对未初始化内存的访问。
SLAB_RED_ZONE这个标志导致slab层在已分配的内存周围插入“红色警戒区”以探测缓冲越界。
SLAB_PANIC这个标志当分配失败时提醒slab层。这在要求分配只能成功的时候非常有用。
SLAB_CACHE_DMA这个标志命令slab层使用可以执行DMA的内存给每个slab分配空间,只有在分配的对象用于DMA,而且必须驻留在ZONE_DMA区时才需要这个标志。
/** Flags to pass to kmem_cache_create().* The ones marked DEBUG are only valid if CONFIG_SLAB_DEBUG is set.*/
#define SLAB_CONSISTENCY_CHECKS	((slab_flags_t __force)0x00000100U)   /* DEBUG: Perform (expensive) checks on alloc/free */
#define SLAB_RED_ZONE		((slab_flags_t __force)0x00000400U)           /* DEBUG: Red zone objs in a cache */
#define SLAB_POISON		((slab_flags_t __force)0x00000800U)             /* DEBUG: Poison objects */
#define SLAB_HWCACHE_ALIGN	((slab_flags_t __force)0x00002000U)       /* Align objs on cache lines */
#define SLAB_CACHE_DMA		((slab_flags_t __force)0x00004000U)         /* Use GFP_DMA memory */
#define SLAB_CACHE_DMA32	((slab_flags_t __force)0x00008000U)         /* Use GFP_DMA32 memory */
#define SLAB_STORE_USER		((slab_flags_t __force)0x00010000U)         /* DEBUG: Store the last owner for bug hunting */
#define SLAB_PANIC		((slab_flags_t __force)0x00040000U)             /* Panic if kmem_cache_create() fails */

创建slab描述符

在这里插入图片描述
kmem_cache_create的最主要功能就是填充struct kmem_cache

kernel-4.19/mm/slab_common.c
kmem_cache_create函数调用核心流程是

kmem_cache_create-----------------------------进行合法性检查,以及是否有现成slab描述符可用create_cache----------------------将主要参数配置到slab描述符,然后将得到的描述符加入slab_caches全局链表中。__kmem_cache_create-------------------是创建slab描述符的核心进行对齐操作,计算需要页面,对象数目,对slab着色等等操作。set_objfreelist_slab_cache/set_off_slab_cache/set_on_slab_cache-------------------设置page片段的结构calculate_slab_order--------------计算slab对象需要的大小,以及一个slab描述符需要多少pagesetup_cpu_cache-------------------继续配置slab描述符
/*kmem_cache_create输入参数说明name:该参数指缓存名称,proc文件系统(在/proc/slabinfo中)使用它标识一个缓存。size:该参数指定了为这个缓存创建的对象的大小,它是以字节为单位的。align:该参数定义了每个对象的对齐方式。flags:该参数指定了分配缓存时的选项,这些选项标志如slab flags表所示。ctor:参数定义了一个可选的对象构造器,构造器是用户提供的回调函数。当从缓存中分配新对象时,可以通过构造器进行初始化。实际上,Linux内核的高速缓存不使用构造函数。你可以将ctor参数赋值为NULL
*/
struct kmem_cache *
kmem_cache_create(const char *name, unsigned int size, unsigned int align,slab_flags_t flags, void (*ctor)(void *))----------------该函数不能在中断上下文中调用,可能会睡眠。
{return kmem_cache_create_usercopy(name, size, align, flags, 0, 0,ctor);
}struct kmem_cache *
kmem_cache_create_usercopy(const char *name,unsigned int size, unsigned int align,slab_flags_t flags,unsigned int useroffset, unsigned int usersize,void (*ctor)(void *))
{struct kmem_cache *s = NULL;const char *cache_name;int err;if (!usersize)
/**检查传递的大小和一些flag是否可以和系统中已经创建的slab匹配上,*如果匹配上则就不用重新申请了,直接使用别名就行,相当于链接过去,下面if判断就直接跳到末尾执行,否则返回NULL,继续执行
*/s = __kmem_cache_alias(name, size, align, flags, ctor); if (s)goto out_unlock;// 定义这个缓存的名字,用于在/proc/slabinfo中显示cache_name = kstrdup_const(name, GFP_KERNEL);if (!cache_name) {err = -ENOMEM;goto out_unlock;}/* 如果没有找到可以复用的slab缓存,则创建一个新的slab缓存*/s = create_cache(cache_name, size,calculate_alignment(flags, align, size),flags, useroffset, usersize, ctor, NULL, NULL);if (IS_ERR(s)) {err = PTR_ERR(s);kfree_const(cache_name);}out_unlock:return s;
}static struct kmem_cache *create_cache(const char *name,unsigned int object_size, unsigned int align,slab_flags_t flags, unsigned int useroffset,unsigned int usersize, void (*ctor)(void *),struct kmem_cache *root_cache)
{struct kmem_cache *s;int err;// useroffset和usersize初始值就是0,跳过if判断if (WARN_ON(useroffset + usersize > object_size))useroffset = usersize = 0;err = -ENOMEM;s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL); -----------------创建一个slab缓存if (!s)goto out;s->name = name;s->size = s->object_size = object_size;s->align = align;s->ctor = ctor;
#ifdef CONFIG_HARDENED_USERCOPYs->useroffset = useroffset;s->usersize = usersize;
#endiferr = __kmem_cache_create(s, flags);   ----------- slab缓存创建成功,则返回0if (err)goto out_free_cache;s->refcount = 1;list_add(&s->list, &slab_caches);--------------将创建的slab缓存添加到系统的slab_caches链表中。
out:if (err)return ERR_PTR(err);return s;out_free_cache:kmem_cache_free(kmem_cache, s);goto out;
}

kernel-4.19/mm/slab.c

int __kmem_cache_create(struct kmem_cache *cachep, slab_flags_t flags)
{size_t ralign = BYTES_PER_WORD;gfp_t gfp;int err;unsigned int size = cachep->size;/** Check that size is in terms of words.  This is needed to avoid* unaligned accesses for some archs when redzoning is used, and makes* sure any on-slab bufctl's are also correctly aligned.*/size = ALIGN(size, BYTES_PER_WORD);if (flags & SLAB_RED_ZONE) {ralign = REDZONE_ALIGN;/* If redzoning, ensure that the second redzone is suitably* aligned, by adjusting the object size accordingly. */size = ALIGN(size, REDZONE_ALIGN);}/* 3) caller mandated alignment */if (ralign < cachep->align) {ralign = cachep->align;}/* disable debug if necessary */if (ralign > __alignof__(unsigned long long))flags &= ~(SLAB_RED_ZONE | SLAB_STORE_USER);/** 4) Store it.*/cachep->align = ralign;------------------------------对齐大小设置到struct kmem_cachecachep->colour_off = cache_line_size();----------------------------------------L1 Cache line大小,由CONFIG_ARM_L1_CACHE_SHIFT配置,此处为64B。/* Offset must be a multiple of the alignment. */if (cachep->colour_off < cachep->align)cachep->colour_off = cachep->align;if (slab_is_available())----------------------------slab_state>=UP时,可以使用GFP_KERNEL分配,否则只能使用GFP_NOWAITgfp = GFP_KERNEL;elsegfp = GFP_NOWAIT;kasan_cache_create(cachep, &size, &flags);-------------------- 预留Kasan的空间,即增加size大小size = ALIGN(size, cachep->align);------------------按照cachep->align对size进行对齐/** We should restrict the number of objects in a slab to implement* byte sized index. Refer comment on SLAB_OBJ_MIN_SIZE definition.*/if (FREELIST_BYTE_INDEX && size < SLAB_OBJ_MIN_SIZE)size = ALIGN(SLAB_OBJ_MIN_SIZE, cachep->align);if (set_objfreelist_slab_cache(cachep, size, flags)) {---------------- 设置管理page片段的管理结构位置flags |= CFLGS_OBJFREELIST_SLAB;goto done;}if (set_off_slab_cache(cachep, size, flags)) {------------------- 设置 page片段管理结构不在slab上flags |= CFLGS_OFF_SLAB;goto done;}if (set_on_slab_cache(cachep, size, flags))--------------- 设置Page片段管理结构在slab上goto done;return -E2BIG;done:cachep->freelist_size = cachep->num * sizeof(freelist_idx_t);cachep->flags = flags;cachep->allocflags = __GFP_COMP;if (flags & SLAB_CACHE_DMA)cachep->allocflags |= GFP_DMA;if (flags & SLAB_CACHE_DMA32)cachep->allocflags |= GFP_DMA32;if (flags & SLAB_RECLAIM_ACCOUNT)cachep->allocflags |= __GFP_RECLAIMABLE;cachep->size = size;cachep->reciprocal_buffer_size = reciprocal_value(size);err = setup_cpu_cache(cachep, gfp);------------根据slab_state状态进行不同处理,计算limit/batchcount,分配本地对象缓冲池,共享对象缓冲池if (err) {__kmem_cache_release(cachep);return err;}return 0;
}
注:
1. kasan是Linux内核引入的一种专门检测slab内存越界问题的,通过给内存打上特定的pad来实现,这个操作比较消耗内存。
2. 通过cache_line_size 可以得到L1 cache的size, 可见最终是通过读取SYS_CTR_EL0 这个寄存器来得到L1 cache size的
static inline int cache_line_size(void)
{u32 cwg = cache_type_cwg();return cwg ? 4 << cwg : L1_CACHE_BYTES;
}
static inline u32 cache_type_cwg(void)
{return (read_cpuid_cachetype() >> CTR_CWG_SHIFT) & CTR_CWG_MASK;
}
static inline u32 __attribute_const__ read_cpuid_cachetype(void)
{return read_cpuid(CTR_EL0);
}
#define read_cpuid(reg)            read_sysreg_s(SYS_ ## reg)

kernel-4.19/mm/slab.c

static bool set_objfreelist_slab_cache(struct kmem_cache *cachep,size_t size, slab_flags_t flags)
{size_t left;cachep->num = 0;/** If slab auto-initialization on free is enabled, store the freelist* off-slab, so that its contents don't end up in one of the allocated* objects.*/if (unlikely(slab_want_init_on_free(cachep)))return false;if (cachep->ctor || flags & SLAB_TYPESAFE_BY_RCU)return false;left = calculate_slab_order(cachep, size,flags | CFLGS_OBJFREELIST_SLAB);if (!cachep->num)return false;if (cachep->num * sizeof(freelist_idx_t) > cachep->object_size)return false;cachep->colour = left / cachep->colour_off;return true;
}slab_state用于表示slab分配器的状态:
```C
/** 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 */------------------------完全初始化
};

calculate_slab_order计算slab的大小,返回值是page order。同时也计算此slab中可以容纳多少个同样大小的对象。

static size_t calculate_slab_order(struct kmem_cache *cachep,size_t size, slab_flags_t flags)
{size_t left_over = 0;int gfporder;for (gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++) {-------------从gfporder=0开始,直到KMALLOC_MAX_ORDER=10,即从4KB到4MB大小。unsigned int num;size_t remainder;num = cache_estimate(gfporder, size, flags, &remainder);if (!num)----------------------不等于0则表示gfporder已经满足条件,最低分配到一个size大小的对象。等于0则继续下一次for循环。continue;/* Can't handle number of objects more than SLAB_OBJ_MAX_NUM */if (num > SLAB_OBJ_MAX_NUM)------------slab中对象最大数目,SLAB_OBJ_MAX_NUM为255,所以所有的slab对象不超过255break;if (flags & CFLGS_OFF_SLAB) {struct kmem_cache *freelist_cache;size_t freelist_size;size_t freelist_cache_size;freelist_size = num * sizeof(freelist_idx_t);if (freelist_size > KMALLOC_MAX_CACHE_SIZE) {freelist_cache_size = PAGE_SIZE << get_order(freelist_size);} else {freelist_cache = kmalloc_slab(freelist_size, 0u);if (!freelist_cache)continue;freelist_cache_size = freelist_cache->size;/** Needed to avoid possible looping condition* in cache_grow_begin()*/if (OFF_SLAB(freelist_cache))continue;}/* check if off slab has enough benefit */if (freelist_cache_size > cachep->size / 2)continue;}/* Found something acceptable - save it away */cachep->num = num;cachep->gfporder = gfporder;left_over = remainder;----------------确定对象个数和需要的页面数/** A VFS-reclaimable slab tends to have most allocations* as GFP_NOFS and we really don't want to have to be allocating* higher-order pages when we are unable to shrink dcache.*/if (flags & SLAB_RECLAIM_ACCOUNT)break;/** Large number of objects is good, but very large slabs are* currently bad for the gfp()s.*/if (gfporder >= slab_max_order)break;/** Acceptable internal fragmentation?*/if (left_over * 8 <= (PAGE_SIZE << gfporder))-------------------满足着色条件,退出for循环break;}return left_over;
}

cache_estimate() 根据当前大小2^gfporder来计算可以容纳多少个对象,以及剩下多少空间用于着色。
/*内核提供了计算一个Slab缓存的大小的函数,Slab缓存的内存布局分成以下两种:
*1. Slab缓存管理区在Slab内,即同对象存放在一起。
*2. Slab缓存管理区存放在单独的一片区域,即同对象分别存放。
*通过宏CFLGS_OFF_SLAB和CFLGS_OBJFREELIST_SLAB来区分。
*参数介绍:

  • gfporder: Slab缓存大小为2^gfporder个页面
  • buffer_size: 每个对象的大小
  • align:对齐
  • flags:CFLGS_OFF_SLAB表示Slab管理区在缓存外,否则表示在缓存内。
  • left_over:输出参数,在一个Slab中被浪费的大小。
  • num: 输出参数,一个Slab中,对象的数量。
    */
/** Calculate the number of objects and left-over bytes for a given buffer size.*/
static unsigned int cache_estimate(unsigned long gfporder, size_t buffer_size,slab_flags_t flags, size_t *left_over)
{unsigned int num;size_t slab_size = PAGE_SIZE << gfporder;/** The slab management structure can be either off the slab or* on it. For the latter case, the memory allocated for a* slab is used for:** - @buffer_size bytes for each object* - One freelist_idx_t for each object** We don't need to consider alignment of freelist because* freelist will be at the end of slab page. The objects will be* at the correct alignment.** If the slab management structure is off the slab, then the* alignment will already be calculated into the size. Because* the slabs are all pages aligned, the objects will be at the* correct alignment when allocated.*/if (flags & (CFLGS_OBJFREELIST_SLAB | CFLGS_OFF_SLAB)) {num = slab_size / buffer_size;*left_over = slab_size % buffer_size;} else {num = slab_size / (buffer_size + sizeof(freelist_idx_t));*left_over = slab_size %(buffer_size + sizeof(freelist_idx_t));}return num;
}

分配slab对象

1)slab分配机制: slab分配器是基于对象进行管理的,所谓的对象就是内核中的数据结构(例如:task_struct,file_struct 等)。相同类型的对象归为一类,每当要申请这样一个对象时,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免内部碎片。slab分配器并不丢弃已经分配的对象,而是释放并把它们保存在内存中。slab分配对象时,会使用最近释放的对象的内存块,因此其驻留在cpu高速缓存中的概率会大大提高。

2)kmem_cache_alloc() 是slab分配缓存对象的核心函数,在slab分配缓存过程中,是全程关闭本地中断的。kmem_cache_alloc()函数从给定的高速缓存cachep中, 返回一个指向对象的指针。如果高速缓存的所有slab中都没有空闲的对象,那么slab层必须通过kmem_getpages() 获取新的页,flags的值传递给 __get_free_pages()。

kmem_cache_alloc–>__kmem_cache_alloc_lru–>slab_alloc–>slab_alloc_node–>__do_cache_alloc是关中断的。

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{return __kmem_cache_alloc_lru(cachep, NULL, flags);
}
EXPORT_SYMBOL(kmem_cache_alloc);static __always_inline void *__kmem_cache_alloc_lru(struct kmem_cache *cachep, struct list_lru *lru,gfp_t flags)
{void *ret = slab_alloc(cachep, lru, flags, cachep->object_size, _RET_IP_);trace_kmem_cache_alloc(_RET_IP_, ret, cachep, flags, NUMA_NO_NODE);return ret;
}static __always_inline void *
slab_alloc(struct kmem_cache *cachep, struct list_lru *lru, gfp_t flags,size_t orig_size, unsigned long caller)
{return slab_alloc_node(cachep, lru, flags, NUMA_NO_NODE, orig_size,caller);
}static __always_inline void *
slab_alloc_node(struct kmem_cache *cachep, struct list_lru *lru, gfp_t flags,int nodeid, size_t orig_size, unsigned long caller)
{unsigned long save_flags;void *objp;struct obj_cgroup *objcg = NULL;bool init = false;flags &= gfp_allowed_mask;cachep = slab_pre_alloc_hook(cachep, lru, &objcg, 1, flags);if (unlikely(!cachep))return NULL;objp = kfence_alloc(cachep, orig_size, flags);if (unlikely(objp))goto out;local_irq_save(save_flags);objp = __do_cache_alloc(cachep, flags, nodeid);local_irq_restore(save_flags);objp = cache_alloc_debugcheck_after(cachep, flags, objp, caller);prefetchw(objp);init = slab_want_init_on_alloc(flags, cachep);out:slab_post_alloc_hook(cachep, objcg, flags, 1, &objp, init,cachep->object_size);return objp;
}

由于没有定义NUMA,所以__do_cache_alloc就仅通过____cache_alloc来分配缓存。

static __always_inline void *
__do_cache_alloc(struct kmem_cache *cachep, gfp_t flags, int nodeid __maybe_unused)
{return ____cache_alloc(cachep, flags);
}
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{void *objp;struct array_cache *ac;check_irq_off();ac = cpu_cache_get(cachep);----------------------------------获取本地对象缓冲池if (likely(ac->avail)) {-------------------------------------本地对象缓冲池有空闲对象ac->touched = 1;objp = ac->entry[--ac->avail];STATS_INC_ALLOCHIT(cachep);goto out;}STATS_INC_ALLOCMISS(cachep);objp = cache_alloc_refill(cachep, flags);------------是slab分配缓存的核心/** the 'ac' may be updated by cache_alloc_refill(),* and kmemleak_erase() requires its correct value.*/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.*/if (objp)kmemleak_erase(&ac->entry[ac->avail]);return objp;
}

cache_alloc_refill() 是slab分配缓存的核心。 如果高速缓存中的所有slab中都没有空闲的对象,那么slab层必须通过cache_alloc_refill()重新填充本地高速缓存并获得一个空闲对象

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 slab *slab;check_irq_off();node = numa_mem_id();ac = cpu_cache_get(cachep);  --------获取本地对象缓冲池acbatchcount = 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;}n = get_node(cachep, node);   --------找到对应的slab节点BUG_ON(ac->avail > 0 || !n);shared = READ_ONCE(n->shared);if (!n->free_objects && (!shared || !shared->avail))goto direct_grow;raw_spin_lock(&n->list_lock);shared = READ_ONCE(n->shared);/* See if we can refill from the shared array */if (shared && transfer_objects(ac, shared, batchcount)) {----判断共享对象缓冲池(n->shared)是否有空闲对象。tansfer_objects尝试迁移batchcount个空闲对象到ac中。shared->touched = 1;goto alloc_done;}while (batchcount > 0) {/* Get slab alloc is to come from. */slab = get_first_slab(n, false);if (!slab)---------------------------------如果从slabs_partial/slabs_free中分配不到对象,则跳到must_grow分配对象。goto must_grow;check_spinlock_acquired(cachep);batchcount = alloc_block(cachep, ac, slab, batchcount);fixup_slab_list(cachep, n, slab, &list);}must_grow:n->free_objects -= ac->avail;
alloc_done:raw_spin_unlock(&n->list_lock);fixup_objfreelist_debug(cachep, &list);direct_grow:----------------------------没有足够的内存片段,则向伙伴子系统进货if (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 = cache_grow_begin(cachep, gfp_exact_node(flags), node);/** cache_grow_begin() can reenable interrupts,* then ac could change.*/ac = cpu_cache_get(cachep);if (!ac->avail && slab)alloc_block(cachep, ac, slab, batchcount);cache_grow_end(cachep, slab);if (!ac->avail)return NULL;}ac->touched = 1;return ac->entry[--ac->avail];
}

释放slab对象

kmem_cache_free()函数释放一个曾经由slab分配器分配给某个内核函数的对象,在释放过程中也是全程关中断的。其参数为cachep(高速缓存描述符的地址)和objp(将被释放对象的地址)。

kernel-4.19/mm/slab.c

void kmem_cache_free(struct kmem_cache *cachep, void *objp)
{cachep = cache_from_obj(cachep, objp);---------------------通过对象找到slab描述符if (!cachep)return;trace_kmem_cache_free(_RET_IP_, objp, cachep);__do_kmem_cache_free(cachep, objp, _RET_IP_);
}
EXPORT_SYMBOL(kmem_cache_free);

cache_from_obj()通过要释放对象的虚拟地址,找到所在页面,继而找到对应的struct kmem_cache结构体。
然后将转换得到的slab描述符和入参描述符对比,即可判断两者是否有效。

static inline struct kmem_cache *cache_from_obj(struct kmem_cache *s, void *x)
{struct kmem_cache *cachep;if (!IS_ENABLED(CONFIG_SLAB_FREELIST_HARDENED) &&!kmem_cache_debug_flags(s, SLAB_CONSISTENCY_CHECKS))return s;cachep = virt_to_cache(x);if (WARN(cachep && cachep != s,"%s: Wrong slab cache. %s but object is from %s\n",__func__, s->name, cachep->name))print_tracking(cachep, x);return cachep;
}

kernel-4.19/mm/slab.c

static inline struct kmem_cache *virt_to_cache(const void *obj)
{struct page *page = virt_to_head_page(obj);return page->slab_cache;
}

销毁缓存

调用kmem_cache_destroy()函数之前必须确保以下两个条件:

  1. 高速缓存中的所有slab必须为空。
  2. 在调用kmem_cache_destroy期间不再访问这个高速缓存。

linux-4.19/mm/slab_common.c

void kmem_cache_destroy(struct kmem_cache *s)
{int err;if (unlikely(!s))  --------------- 如果s为NULL,直接退出return;get_online_cpus();-------------- get_online_cpus是对cpu_online_map的加锁,其与末尾的put_online_cpus()是配对使用的;get_online_mems类似,不过是对mem操作get_online_mems();mutex_lock(&slab_mutex);s->refcount--;if (s->refcount)goto out_unlock;#ifdef CONFIG_MEMCG_KMEMmemcg_set_kmem_cache_dying(s);mutex_unlock(&slab_mutex);put_online_mems();put_online_cpus();flush_memcg_workqueue(s);get_online_cpus();get_online_mems();mutex_lock(&slab_mutex);/** Another thread referenced it again*/if (READ_ONCE(s->refcount)) {spin_lock_irq(&memcg_kmem_wq_lock);s->memcg_params.dying = false;spin_unlock_irq(&memcg_kmem_wq_lock);goto out_unlock;}
#endiferr = shutdown_memcg_caches(s);if (!err)err = shutdown_cache(s);if (err) {pr_err("kmem_cache_destroy %s: Slab cache still has objects\n",s->name);dump_stack();}
out_unlock:mutex_unlock(&slab_mutex);put_online_mems();put_online_cpus();
}
EXPORT_SYMBOL(kmem_cache_destroy);

在内核栈上的静态分配

用户空间能够奢侈地负担起非常大的栈,而且空间还可以动态增长,但是内核却不能这么奢侈,因为内核栈大小固定。当给每个进程分配一个固定大小的小栈,不但可以减少内存的消耗,而且内核也无需负担太重的栈管理任务。

**每个进程的内核栈大小既取决于体系结构,也与编译时的选项有关。**通常,每个进程都有两页的内核栈,32位和64位体系结构的页面大小分别是4KB和8KB,所以内核栈大小分别是8KB和16KB。当然,可以在编译时设置单页内核栈,当这个选项被激活时,每个进程的内核栈就只有一页那么大了,这样做,可以让每个进程减少内存消耗,给分配连续的页带来了好处。历史上,中断处理程序耶要放在内核栈中,同时增加了更加严格的约束条件。

为了纠正这问题,实现了一个附加选项:中断栈。中断栈为每个进程提供一个用于中断处理程序的栈。

总的来说,内核栈可以是1页,也可以是2页。当1页栈的选项被激活时,中断处理程序获得了自己的栈。在任何情况下,无限制的递归和alloc是不允许的。所以,在任意一个函数中,你都必须尽量节省栈资源,在具体的函数中让所有局部变量所占空间之和不要超过几百字节。

高端内存的映射

根据定义,在高端内存中的页不能永久地映射到内核地址空间上。因此,通过alloc_pages()函数以 __GFP_HIGHMEM 标志获得的页不可能有逻辑地址。

在x86体系结构上,高于896MB的所有物理内存的范围大都是高端内存,它并不会永久地或自动地映射到内核地址空间,尽管x86处理器能够寻址物理RAM的范围达到4GB(启用PAE可以寻址到64GB),一旦这些页被分配,就必须映射到内核的逻辑地址空间上。在x86上,高端内存中的页被映射到3GB~4GB

内核有三种方式管理高端内存。第一种是非连续映射,在vmalloc中请求页面的时候,如果请求的是高端内存,则映射到VMALLOC_START与VMALLOC_END之间。第二种方式是永久内核映射。最后一种方式叫临时内核映射

永久映射

<linux/highmem.h>
要映射一个给定的page结构到内核地址空间,可以使用:

void *kmap(struct page *page);

这个函数在高端内存或低端内存上都能用。如果page结构对应的是低端内存中的一页,函数只会单纯地返回该页的虚拟地址。如果页位于高端内存,则会建立一个永久映射,在返回地址。这个函数可以睡眠,只能用于进程上下文中。因为允许永久映射的数量是有限的,当不再需要高端内存时,应该解除映射:

void kunmap(struct page *page);

临时映射

内核在FIXADDR_START到FIXADDR_TOP之间保留了一些线性空间用于特殊需求。这个空间称为“固定映射空间”。在这个空间中,有一部分用于高端内存的临时映射。这块空间具有如下特点:

  • 每个CPU占用一块空间
  • 在每个CPU占用的那块空间中,又分为多个小空间,每个小空间大小是1个page,每个小空间用于一个目的,这些目的定义在kmap_types.h中的km_type中。
    当要进行一次临时映射的时候,需要指定映射的目的。 根据映射目的,可以找到对应的小空间,然后把这个空间的地址作为映射地址。这意味着一次临时映射会导致以前的映射被覆盖。

当必须创建一个映射,而当前上下文不能睡眠时,内核提供了临时映射(原子映射):

void *kmap_atomic(struct page *page, enum_km_type type);

参数type定义在<asm/kmap_types.h>中,这个函数不会阻塞,禁止内核抢占,可以用在中断上下文中和其他不能重新调度的地方。
取消映射:

void kunmap_atomic(void *kvaddr, enum_km_type type);

这个函数也不会阻塞。在很多体系结构中,除非激活了内核抢占,否则kmap_atomic()根本就无事可做,因为只有在下一个临时映射到来前上一个临时映射才有效。因此,内核完全可以“忘掉”kmap_atomic()映射,kunmap_atomic()也无须做什么实际的事情。下一个原子映射将自动覆盖前一个映射。

每个CPU的分配

支持SMP的现代操作系统使用了每个CPU上的数据,对于给定的处理器,其数据时唯一的。一般来说,每个CPU的数据存放在一个数组中,数组中的每一项对应着系统上一个存在的处理器。这就是2.4内核的处理方式。该情况下,不再需要锁,而是关注内核抢占来防止并发访问。例如:

    unsigned long my_percpu[NR_CPUS];int cpu;cpu=get_cpu();  // 获得当前处理器,并禁止内核抢占my_percpu[cpu]++;put_cpu();      // 激活内核抢占

----------CPU 部分只做简单记录学习–START---------

新的每个CPU接口

编译时的每个CPU数据

2.6内核为了方便创建和操作每个CPU数据,从而引进了新的操作接口,称着percpu。头文件<linux/percpu.h>声明了所有的操作接口例程。可以在文件mm/slab.c和<asm/percpu.h>中找到他们的定义。

DEFINE_PER_CPU(type, name);

这个语句为系统的每个处理器都创建了一个类型为type,名字为name的变量实例,如果你需要在别处声明变量,可以使用:

DECLARE_PER_CPU(type, name);

调用get_cpu_var返回当前处理器上指定的变量,同时禁止内核抢占;调用put_cpu_var将相应地重新激活抢占。

get_cpu_var(name)++;/*增加当前处理器变量的值*/
put_cpu_var(name);/*释放CPU*/

可以调用下面这个接口处理指定CPU的数据:

per_cpu(name, cpu);

函数per_cpu函数既不会禁止内核抢占,也不会提供任何形式的锁保护。

注意,编译时每个CPU数据的例子并不能在模块使用,因为连接程序实际上将它们创建在一个唯一的可执行段中(.data.percpu)。

运行时的每个CPU接口

该例程为系统上的每个处理器创建所需内存的实例,其原型在<linux/percpu.h>中。

void *alloc_percpu(type);
void *__alloc_percpu(size_t size, size_t align);
void *free_percpu(const void *);

宏alloc_percpu 给系统中的每个处理器分配一个指定类型对象的实例。它其实是__alloc_percpu的一个封装例程。这个原始宏接收的参数有两个:一个是要分配的实际字节数;一个是分配时要按多少字节对齐。而封装后的__alloc_percpu按照单字节对齐–按照给定类型的自然边界对齐。例如:

struct rabid_cheetah = alloc_percpu(struct rabid_cheetab);

等价于:

struct rabid_cheetab = = __alloc_percpu(sizeof(struct rabid_cheetab),
__alignof__( struct rabid_cheetab));
__alignof__结构式gcc的一个功能,它会返回指定类型或lvalue所需的对齐字节数。

无论是alloc_percpu还是__alloc_percpu都返回一个指针,用来间接引用动态创建的每个CPU数据,内核提供了两个宏利用指针来获取每个CPU数据:

get_cpu_ptr(ptr);
put_cpu_ptr(ptr);

函数get_cpu_ptr返回一个指向当前处理器数据的特殊实例,同时禁止内核抢占;而put_cpu_ptr会重新激活内核抢占。最后,函数per_cpu_ptr返回指定处理器的唯一数据:

per_cpu_ptr(ptr);

该函数不会禁止内核抢占。

使用每个CPU数据的原因

第一: 减少了数据锁定。编程时就规定了只有这个CPU能访问这个数据,所以不需要任何锁。
第二: 使用每个CPU数据可以大大减少缓存失效。失效发生在处理器试图使它们的缓存保持同步时。

----------CPU 部分只做简单记录学习–END---------

分配函数的选择(重点)

  1. 如果需要连续的物理页,就可以使用某个低级页分配器或kmalloc。但是需要注意GFP_KERNEL和GFP_ATOMIC标志的使用。
  2. 如果需要从高端内存进行分配,就使用alloc_pages。它返回一个指向struct page结构的指针,而不是指向某个逻辑地址的指针。为了获得真正的指针,需要调用kmap函数,把高端内存映射到内核的逻辑地址空间。
  3. 如果不需要物理页上连续的页,而仅仅需要虚拟地址连续的页,就使用vmalloc(有一定性能损失)。
  4. 如果需要创建和销毁很多较大的数据结构,那么可以使用slab高速缓存。

参考资料

Linux内核设计与实现
图解Slub
[slab浅析](https://blog.csdn.net/weixin_28745975/article/details/116952017)
slub分配器
Arnold Lu@南京-slub分配器
深入理解Linux内存管理–slab

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_73841.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么需要这个岗位,软件测试是干什么

目录 一、软件测试概念 二、软件测试目的 三、软件测试基本原则 四、软件测试主要工作内容 五、软件测试方法 六、测试工程师的职责 七、为什么要软件测试这个岗位 一、软件测试概念 软件测试&#xff1a;为了发现软件错误和缺陷&#xff08;统称bug&#xff09;而执行…

浅谈liunx init.d 和 rc.local 两种起动方式

浅谈liunx init.d 和 rc.local 两种起动方式 以rabbitmq 举例 &#xff08;一&#xff09;.init.d 方式 开机自动重启设置 1.在/etc/init.d 目录下新建一个 rabbitmq [rootlocalhost init.d]# vi rabbitmq具体脚本如下所示&#xff1a; #!/bin/bash # # chkconfig: 2345 …

全球智慧能源解决方案服务商「雄韬股份」牵手企企通,谱写采购数字化变革之路

近日&#xff0c;全球知名智慧能源解决方案服务商「深圳市雄韬电源科技股份有限公司」&#xff08;以下简称“雄韬股份”&#xff09;与企企通达成合作。本次合作&#xff0c;企企通将为雄韬股份提供专业的采购数字化解决方案&#xff0c;推动企业采购更加智能、高效、透明&…

计算机网络(2)从十六进制的ip数据报中得到详细字段信息

本博文介绍如何将十六进制的ip报文拆分出具体的字段信息。社会计算机网络和网络协议分析的初学者参考&#xff08;今天看了网络协议分析期末复习重点的最后一个大题&#xff0c;竟然一头雾水&#xff0c;然后快马加鞭翻阅各种资料&#xff0c;然后差不多学会 了&#xff09;wir…

浏览器输入www.baidu.com后执行的全部过程

日升时奋斗&#xff0c;日落时自省 <1>URL输入 URL称为 : 统一资源定位符,用于定位互联网上的资源,也就是平常提起的"网址" 地址栏输入网址之后按下回车,浏览器会对输入的信息进行评判 (1)检查输入的内容是否是是一个合法的网址连接(非法地址不行) (2)合法的…

Python Unittest框架

1、unittest简介 unittest是Python自带的单元测试框架,具备编写用例、组织用例、执行用例、输出报告等自动化框架的条件,主要适用于单元测试,可以用来作自动化测试框架的用例组织执行框架。 2、unittest框架的特性: 提供用例组织与执行:当测试用例只有几条的时候可以不考虑…

Nginx 02篇——Nginx基本配置与参数说明篇

Nginx 02篇——Nginx基本配置与参数说明篇前言-默认配置文件1. 前言——关于nginx1.1 关于nginx1. 2 Nginx 01篇——Nginx安装2. Nginx 配置文件结构2.1 Nginx 安装后的默认文件2.2 Nginx 的三大组成部分3. 配置参说明-1——整个配置3.1 配置说明3.2 参考4. 配置说明-2—详细说…

postgres 源码解析51 LWLock轻量锁--2

本篇将着重讲解LWLock涉及的主要API工作流程与实现原理&#xff0c;相关基础知识见回顾&#xff1a;postgres 源码解析50 LWLock轻量锁–1 API介绍 函数API功能CreateLWLocks分配LWLocks所需的内存并进行初始化LWLockNewTrancheId分配新的Tranche ID,供用户使用Extension模块…

结构效度分析流程

结构效度分析流程如下图 一、结构效度的意义 效度分析在学术研究中非常常见&#xff0c;结构效度是为了分析“从量表获得的结果与设计该量表时所假定的理论之间的符合程度”。简单来讲&#xff0c;在研究者设计量表之初&#xff0c;一般会预设好几个维度&#xff0c;在经过因子…

kafka入门到精通

文章目录一、kafka概述&#xff1f;1.定义1.2消息队列1.2.1 传统消息队列的使用场景1.2.2 消息队列好处1.2.3 消息队列两种模式1.3 kafka基础架构二、kafka快速入门1.1使用docker-compose安装kafka1.2测试访问kafka-manager1.3 查看kafka版本号1.4 查看zookeeper版本号1.5 扩展…

python学习之OpenCV-Python模块的部分应用示例(生成素描图和动漫图)

文章目录前言一、图片转灰度二、对图片进行二值化处理三、对图片去除噪点四、调整图片透明度五、生成素描滤镜效果图&#xff08;方法结合应用&#xff09;六、生成动漫卡通滤镜效果图&#xff08;方法结合应用&#xff09;总结前言 OpenCV 是一个图像和视频处理库&#xff0c…

掌握饮食健康:了解你的宏量营养素摄入

谷禾健康 // 俗话说“病从口入”&#xff0c;我们的健康状况很大一部分取决于饮食。而食物基本上是由各种营养素构成的。 宏量营养素是人体大量需要的必需营养成分。宏量营养素指的是“三大”营养素&#xff1a;蛋白质、脂肪和碳水化合物&#xff0c;它们是我们饮食中的关键。 …

【JavaScript】基本语法大全

前言&#xff1a; 大家好&#xff0c;我是程序猿爱打拳。在学习C和Java这样的后端编程语言后&#xff0c;我们大概率会学习一些关于前端的语言如HTMLJavaScript。又因为前后端基本语法有些许不同&#xff0c;因此我整理出来。今天给大家讲解的是JS中的数据类型、运算符、选择结…

【华为OD机试模拟题】用 C++ 实现 - 最低位排序(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 货币单位换算(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 选座位(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 停车场最大距离(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 重组字符串(2023.Q1) 【华为OD机试模…

Eth-trunk :LACP模式链路聚合实战

Eth-trunk : LACP模式链路聚合实战 需求描述 PC1和PC3数据vlan10 &#xff0c;网段为192.168.10.0 /24PC2和PC4数据vlan20 &#xff0c;网段为192.168.20.0 /24确保设备之间互联互通&#xff0c;使用最大互联带宽并没有环路确保相同网段的PC可以互通判断交换机之间的每个端口…

ros下用kinectv2运行orbslam2

目录 前提 创建工作空间 orbslam2源码配置、测试&#xff1a; 配置usb_cam ROS功能包 配置kinect 前提 vim 、 cmake 、 git 、 gcc 、 g 这些一般都装了 主要是Pangolin 、 OpenCV 、 Eigen的安装 18.04建议Pangolin0.5 创建工作空间 我们在主目录下创建一个catkin_…

Node 10.0.8.6:9003 is unknown to cluster

解决方案解决方案一解决方案一 ① 概念介绍 公网ip&#xff1a;就是任意两台连接了互联网的电脑可以互相ping ip,能够通的ip 内网ip&#xff1a;只是在内网中使用无法与外网连接的ip ②问题背景 在腾讯云上搭建的一个redis集群&#xff0c;集群启动后 可以看到启动节点…

TX Text Control .NET Server for ASP.NET 31.0 SP2 CRK

用于 ASP.NET 31.0 SP2 的 TX 文本控件 .NET 服务器 用于 ASP.NET 的 TX 文本控件 .NET 服务器 TX Text Control Server for ASP.NET 是用于 Web 应用程序或服务的服务器端组件。它是一个完全可编程的 ASP.NET 文字处理器引擎&#xff0c;提供了广泛的文字处理功能。使用 TX Te…

C++中的内存管理

文章目录前言1.C中内存空间的划分2.C内存管理方式1.对内置类型的处理2.对自定义类型的处理3.new和delete实现原理4.定位new3.总结1. malloc/free和new/delete的区别2. 内存泄漏前言 C中的内存空间划分和C语言是很像的&#xff0c;基本上区别不大。但是因C中&#xff0c;引入了…

davis2016评估教程

DAVIS 2016是VOS任务中的一个经典的benchmark&#xff0c;但是一些VOT的算法有时候也可以预测mask&#xff0c;所以也会在上面测一测性能&#xff0c;本次就随手记录一下自己评测的过程&#xff0c;有需要的小伙伴可以往下看。 DAVIS 2016数据集官方项目网站&#xff1a;https:…