linux内核内存管理slub

article/2025/11/10 2:26:20

一、概述

linux内存管理核心是伙伴系统,slab,slub,slob是基于伙伴系统之上提供api,用于内核内存分配释放管理,适用于小内存(小于1页)分配与释放,当然大于1页,也是可以的.小于一页的,我们也可以直接用伙伴系统api申请内存和释放,但伙伴系统最小单位是页,如果我们只需要100byte,伙伴系统申请内存最小1页(一般情况1页是4k, 具体看系统PAGE_SHIFT定义),显然就很浪费.

二、slub核心原理

slub是从伙伴系统中分配连续一块内存页作为缓存池,再将其分成大小相等,多块小内存块,当申请内存时,返回其中一小块(返回小块内存起始地址.如下图),假设分成9小块内存(简单的数值表示地址):
在这里插入图片描述
每块小内存的首地址,存放下一小块内存起始地址,最后一小块内存首地址存放null.
缓存池(如上图,9个地址连续小内存块,可视为缓存池),从开始分配到小块内存分配完的过程:
假设指针p=0,表示指向第0小块内存.首次分配内存时,首先addr=p, p=*p(从第0小块内存首地址中获取下一小块内存起始地址,并写入p指针中)之后p=1指向第1小块内存起始地址,addr(指向第0小块内存起始地址)返回给申请分配内存的程序;第二次分配内存时,首先addr=p,p=*p,此后p=2,以此类推,直到最后,将第8小块内存地址分配完后,若再次发起内存申请时,发现p=null,表示内存分配完了,此时,会再次向伙伴系统申请分配连续的,一块大内存(缓存池).

一块缓存池,释放过程:
释放内存时,假设有个指针pa, 首次释放的内存,是第5小块内存,pa=5,*pa=null(第5小块内存首地址存放null), 然后第8小块内存被释放,这时候pa=8,*pa=5(第8小块内存首地址存放5),第5小块内存首地址仍然存放null,, 以此类推.

假如释放顺序是5,8,2,0,6,4,3,1,7释放完后,pa=7,内存情况如下:
在这里插入图片描述
释放完或者只释放部分,随时都可以加入到分配中去.

slub就是负责:缓存池之间的切换,以及小内存块分配与释放.

那么,小内存块(如上面9小块)大小,块数是怎么计算,缓存池大小又是怎么计算?

小内存块大小计算:

假如,申请x_size大小的内存,那么:

n = fls(x_size - 1);
j = 1 << n;
or = j < x_size ? 1 << n+1 : j;

or就是小块内存大小
fls()返回最高位为1的位置,起始是从1开始,返回0表示没有找到为1的位置
小块内存大小,也可以直接调用include/linux/slab.h里面定义的kmalloc_index()得到:

a = kmalloc_index(x_size);
or = a < 0 ? 0 : 1 << a;

由此,可看出,最终分配的小内存块大小,不一定是申请时指定的大小,而是2的n次方,不足往大的计算,如申请5字节,最终计算下来,会分配8字节返回.在申请内存时,大小按照2的n次方申请最佳,否则会出现浪费.如我们申请5字节,分配8字节,多余的3字节,申请者,不知道,就不会去使用这3字节,出现浪费.

缓存池大小计算:


int rem = 0;
int order = 0;
int max_order;
unsigned long slab_size;//2的order次幂,就是要向伙伴系统申请的,连续大块内存,单位页
for(order = 0; order < max_order; order++)//max_order最大限制
{slab_size = PAGE_SIZE << order;//将order,转化为字节(byte)大小//reserved,一般为0//x_size小块内存大小rem = (slab_size - reserved) % x_size;//碎片,残留rem字节大小内存,是无法分配的if (rem <= slab_size / fract_leftover)//fract_leftover会分别用16,8,4试探,看是否满足条件break;//表示成功
}printk("order:%d\n",order)

上面的代码,是个人理解后的简单描述,实现在mm/slub.c中calculate_order()函数.
2^order(2的order次幂)个页,就是缓存池大小.
简而言之,以2^order个页,循环试探,找到满足,对碎片要求的值order.

缓存池中小内存块,块数计算:
slab_size = PAGE_SIZE << order;
num = slab_size / or;
num就是小内存块,块数.

slub最大只能申请分配2页内存空间(小内存块),在include/linux/slab.h中定义:
#define KMALLOC_SHIFT_HIGH (PAGE_SHIFT + 1)
其中的PAGE_SHIFT,需要看具体系统配置,一般会定义在arch/xxx/include/asm/page.h里面:
#define PAGE_SHIFT 12

slub最小能分配内存空间(小内存块),由宏KMALLOC_SHIFT_LOW定义,在include/linux/slab.h,KMALLOC_SHIFT_LOW会受平台定义的ARCH_DMA_MINALIGN影响,如果平台没定义ARCH_DMA_MINALIGN,slub默认:
#define KMALLOC_SHIFT_LOW 3
即,最小可以申请8个字节(1 << 3),假如申请3个字节,也会分配8字节.

三、slub code实现

为了便于描述,如下定义:

同类缓存池:struct kmem_cache *s;
小缓存池:struct page *page;
当前小缓存池:s->cpu_slab->page;
当前小内存块链:s->cpu_slab->freelist
一级(小缓存池)回收链表:s->cpu_slab->partial
二级(小缓存池)回收链表:s->node[x]->partial

小内存块:"slub核心原理"描述的,分成多个内存块,其中的一块,就叫做小内存块,即每次向slub申请分配,获取到的内存,就是一块小内存块,小内存块大小由struct kmem_cache成员size指定.
同类缓存池:一个struct kmem_cache实例化对象,管理着同一类缓存池(一个或多个缓存池),这里说的同一类,指相同大小小内存块,即可以叫做相同size小内存块,struct kmem_cache成员size(object_size与size相等)就是小内存块的大小.例如:size=1024的小内存块,与size=2048的小内存块,就属于不同类.(个人觉得,struct kmem_cache成员size,命名为size_type,或许更合适)
小缓存池:slub每次向伙伴系统申请分配内存页(单个或多个连续页),就可以叫做小缓存池.向伙伴系统分配的页大小由s->oo决定.如上"slub核心原理"提到的0到8的九个小块内存,组成一个小缓存池.
大缓存池:为方便描述和理解,同类缓存池取个别名,叫做大缓存池,里面有多个小缓存池.

1、同类缓存池初始化

同类缓存池结构初始化后,形成的大致结构图:
在这里插入图片描述                图一
slab_caches是链表头,为了便于描述,我们取名为同类缓存池链表,或者大缓存池链表,定义在mm/slab_common.c中:
LIST_HEAD(slab_caches);
kmem_cache是指向struct kmem_cache的一个实体对象,定义在mm/slab_common.c中:
struct kmem_cache *kmem_cache;
kmem_cache_node是指向struct kmem_cache_node的一个实体对象,定义在mm/slub.c中;
static struct kmem_cache *kmem_cache_node;

kmem_cache,kmem_cache_node,kmalloc_caches[0], kmalloc_caches[1], …kmalloc_caches[i]都是struct kmem_cache实体,以slab_caches为列表头,通过链表(struct list_head)形式连接起来.

s0,s1,s2…sn分别代表不同size的,同类缓存池管理结构(即struct kmem_cache一个实体对象)

memory0, memory1, memory2…memoryN代表缓存池,即实际内存空间,图一方框大小并不代表缓存池空间大小

kmem_cache, s0代表同一个struc kmem_cache实体,即同类缓存池管理结构,kmem_cache和s0不分彼此,图一只是为了更好的描述.有意思的地方,mermory0用来存放小内存块s0, s1, s2…sn;而s0用来管理memory0缓存池里面其他小内存块分配释放.
依次,有如下关系:
kmem_cache_node对应s1,管理memory1
kmalloc_caches[0]对应s2,管理memory2
kmalloc_caches[1]对应s3,管理memory3

如下:
kmalloc_caches[6]管理所有大小为64byte的内存块(本文称为小内存块)分配释放.
kmalloc_caches[7]管理所有大小为128byte的内存块分配释放.
kmalloc_caches[8]管理所有大小为256byte的内存块分配释放.

在分析代码是如何描述图一结构前,先了解下同类缓存池struct kmem_cache结构体(定义在include/linux/slub_def.h里面):

struct kmem_cache {//cpu_slab管理当前小缓存池和一级回收链表struct kmem_cache_cpu __percpu *cpu_slab;/* Used for retriving partial slabs etc */unsigned long flags;//二级回收链表上小缓存池数量超过min_partial时,//将会把二级回收链表超过的部分,且inuse为0时,释放回伙伴系统//unfreeze_partials()函数做的处理unsigned long min_partial;//size同类缓存池关键,小内存块空间大于等于sizeint size;		/* The size of an object including meta data *///size == object_sizeint object_size;	/* The size of an object without meta data */int offset;		/* Free pointer offset. *///一级回收链表上小内存块数量超过cpu_partial时,//将会把一级回收链表上所有小缓存池移动到二级回收链表上//unfreeze_partials()函数会做这样的处理int cpu_partial;	/* Number of per cpu partial objects to keep around *///oo低16位存放小缓存池小内存块数量,//oo高16位,存放小缓存池空间大小(高16位是2的幂指数,2^(oo>>16)得到的值,就是小缓存池空间大小)struct kmem_cache_order_objects oo;/* Allocation and freeing of slabs *///max是oo>>16的上限值,slub向伙伴系统申请内存页时,不得超过这个值struct kmem_cache_order_objects max;// oo高16位的最小值,当缓存池向伙伴系统申请分配页时,按照oo申请不成功时,//会降低为min的值,再次向伙伴系统申请页struct kmem_cache_order_objects min;gfp_t allocflags;	/* gfp flags to use on each alloc */int refcount;		/* Refcount for slab cache destroy */void (*ctor)(void *);//inuse == sizeint inuse;		/* Offset to metadata */int align;		/* Alignment *///reserved一般为0int reserved;		/* Reserved bytes at the end of slabs *///同类缓存池名字,便于管理和查询,debugconst char *name;	/* Name (only for display!) *///所有同类缓存池都是通过list连接到slab_caches(同类缓存池链表头)struct list_head list;	/* List of slab caches */
#ifdef CONFIG_SYSFSstruct kobject kobj;	/* For sysfs */
#endif
#ifdef CONFIG_MEMCG_KMEMstruct memcg_cache_params *memcg_params;int max_attr_size; /* for propagation, maximum size of a stored attr */
#endif#ifdef CONFIG_NUMA/** Defragmentation by allocating from a remote node.*/int remote_node_defrag_ratio;
#endif//node管理二级回收链表//多核CPU,MAX_NUMNODES代表CPU核数量,每个CPU核都有自己独立的一个nodestruct kmem_cache_node *node[MAX_NUMNODES];
};struct kmem_cache_cpu {//freelist指向当前小内存块链头void **freelist;	/* Pointer to next available object */unsigned long tid;	/* Globally unique transaction id *///page指向当前小缓存池struct page *page;	/* The slab from which we are allocating *///partial指向一级回收链表struct page *partial;	/* Partially allocated frozen slabs */
#ifdef CONFIG_SLUB_STATSunsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};//min的作用(在mm/slub.c中函数allocate_slab())如下:
static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{.........page = alloc_slab_page(alloc_gfp, node, oo);if (unlikely(!page)) {//按照oo申请失败,降低为min再次试图向伙伴系统申请oo = s->min;/** Allocation may have failed due to fragmentation.* Try a lower order alloc if possible*/page = alloc_slab_page(flags, node, oo);if (page)stat(s, ORDER_FALLBACK);}.........

slub初始化
函数调用情况:
start_kernel() --> mm_init() --> kmem_cache_init()
start_kernel(), mm_init()都定义在init/main.c中
kmem_cache_init()定义在mm/slub.c中,函数如下:

void __init kmem_cache_init(void)
{//boot_kmem_cache为启动图一里面kmem_cache(s0)铺路或者引路//boot_kmem_cache_node为启动kmem_cache_node(s1)铺路或引路static __initdata struct kmem_cache boot_kmem_cache,boot_kmem_cache_node;if (debug_guardpage_minorder())slub_max_order = 0;kmem_cache_node = &boot_kmem_cache_node;kmem_cache = &boot_kmem_cache;//初始化boot_kmem_cache_node//第二个参数,作为缓存池管理结构名字boot_kmem_cache_node.name//第三个参数,作为缓存池管理结构boot_kmem_cache_node大小,即小内存块大小//这个函数,还会计算小内存块数量	create_boot_cache(kmem_cache_node, "kmem_cache_node",sizeof(struct kmem_cache_node), SLAB_HWCACHE_ALIGN);register_hotmemory_notifier(&slab_memory_callback_nb);/* Able to allocate the per node structures */slab_state = PARTIAL;//初始化缓存池管理结构boot_kmem_cache//第二个参数,作为缓存池管理结构名字boot_kmem_cache.name//第三个参数,作为缓存池管理结构boot_kmem_cache大小,即小内存块空间大小//这个函数,还会计算出小内存块数量	create_boot_cache(kmem_cache, "kmem_cache",offsetof(struct kmem_cache, node) +nr_node_ids * sizeof(struct kmem_cache_node *),SLAB_HWCACHE_ALIGN);//将从memory0中分配s0,然后将boot_kmem_cache内容拷贝到s0	//当bootstrap()返回后,kmem_cache将不再指向boot_kmem_cache,//而是指向从伙伴系统分配来的内存空间s0,//之后kmem_cache(s0)管理的缓存池memory0,将用存放所有同类缓存池管理结构实体的,//(需要注意,同类缓存池管理结构struct kmem_cache实体,本身就是需要空间存放,//所有管理结构实体都存放在memory0中,而s0用来管理memory0分配释放)kmem_cache = bootstrap(&boot_kmem_cache);/** Allocate kmem_cache_node properly from the kmem_cache slab.* kmem_cache_node is separately allocated so no need to* update any list pointers.*///从memory0中分配出s1小内存块,然后把boot_kmem_cache_node拷贝到s1里面//bootstrap()返回后,kmem_cache_node指向s1kmem_cache_node = bootstrap(&boot_kmem_cache_node);/* Now we can use the kmem_cache to allocate kmalloc slabs *///主要初始化kmalloc_caches[n]//后期分配内存,通过申请size,调用函数kmalloc_index()或者kmalloc_slab()//计算查找出n,并得出kmalloc_caches[x]同类缓存池管理结构实体//其中n,表示将分配出大小为1<<n的内存块空间(小内存块),即2的n次方create_kmalloc_caches(0);#ifdef CONFIG_SMPregister_cpu_notifier(&slab_notifier);
#endifprintk(KERN_INFO"SLUB: HWalign=%d, Order=%d-%d, MinObjects=%d,"" CPUs=%d, Nodes=%d\n",cache_line_size(),slub_min_order, slub_max_order, slub_min_objects,nr_cpu_ids, nr_node_ids);
}void __init create_boot_cache(struct kmem_cache *s, const char *name, size_t size,unsigned long flags)
{int err;s->name = name;//同类缓存池管理结构名字s->size = s->object_size = size;//小内存块空间大小,这很关键哦//内存对齐s->align = calculate_alignment(flags, ARCH_KMALLOC_MINALIGN, size);//同类缓存池管理结构主要初始化err = __kmem_cache_create(s, flags);if (err)panic("Creation of kmalloc slab %s size=%zu failed. Reason %d\n",name, size, err);s->refcount = -1;	/* Exempt from merging for now */
}int __kmem_cache_create(struct kmem_cache *s, unsigned long flags)
{int err;err = kmem_cache_open(s, flags);if (err)return err;...
}static int kmem_cache_open(struct kmem_cache *s, unsigned long flags)
{s->flags = kmem_cache_flags(s->size, flags, s->name, s->ctor);s->reserved = 0;if (need_reserve_slab_rcu && (s->flags & SLAB_DESTROY_BY_RCU))s->reserved = sizeof(struct rcu_head);//小缓存池里面小内存块数量计算,计算方法,可参考以上"slub核心原理"if (!calculate_sizes(s, -1))goto error;...	//二级回收链表上,小缓存池数量min_partial上限初始化set_min_partial(s, ilog2(s->size) / 2);...	//一级回收链表上小内存块数量上限cpu_partial初始化//超过cpu_partial,将会把一级回收链表上所有小缓存池移动到二级回收链表上if (kmem_cache_debug(s))s->cpu_partial = 0;else if (s->size >= PAGE_SIZE)s->cpu_partial = 2;else if (s->size >= 1024)s->cpu_partial = 6;else if (s->size >= 256)s->cpu_partial = 13;elses->cpu_partial = 30;...//二级回链表初始化if (!init_kmem_cache_nodes(s))goto error;//当前缓存池,一级回收链表初始化if (alloc_kmem_cache_cpus(s))return 0;//当前缓存池,一级回收链表,二级回收链表初始化失败时,将会释放掉二级回收链表free_kmem_cache_nodes(s);
error:if (flags & SLAB_PANIC)panic("Cannot create slab %s size=%lu realsize=%u ""order=%u offset=%u flags=%lx\n",s->name, (unsigned long)s->size, s->size, oo_order(s->oo),s->offset, flags);return -EINVAL;
}static int calculate_sizes(struct kmem_cache *s, int forced_order)
{unsigned long flags = s->flags;unsigned long size = s->object_size;int orde...size = ALIGN(size, sizeof(void *));...s->inuse = size;...size = ALIGN(size, s->align);s->size = size;if (forced_order >= 0)order = forced_order;else//calculate_order()计算方式,见"slub核心原理"描述order = calculate_order(size, s->reserved);if (order < 0)return 0;...//oo高16位存放order,其中order是向伙伴系统申请内存空间的大小,2的order次方页//oo低16位存放,小内存块数量,即小缓存池里面有多少个小内存块(2的order次方除以size)s->oo = oo_make(order, size, s->reserved);	s->min = oo_make(get_order(size), size, s->reserved);if (oo_objects(s->oo) > oo_objects(s->max))s->max = s->oo;return !!oo_objects(s->oo);static int init_kmem_cache_nodes(struct kmem_cache *s)
{int node;//node cpu核编号(多核cpu)for_each_node_state(node, N_NORMAL_MEMORY) {struct kmem_cache_node *n;...//从memory1中分配一个二级回收链表struct kmem_cache_node实体内存空间n = kmem_cache_alloc_node(kmem_cache_node,GFP_KERNEL, node);if (!n) {//分配失败,释放二级回收链表free_kmem_cache_nodes(s);return 0;}s->node[node] = n;//二级回收链表指针,指向实体struct kmem_cache_nodeinit_kmem_cache_node(n);//初始化二级回收链表}return 1;static inline int alloc_kmem_cache_cpus(struct kmem_cache *s)
{...//为cpu_slab分配内存块空间s->cpu_slab = __alloc_percpu(sizeof(struct kmem_cache_cpu),2 * sizeof(void *));if (!s->cpu_slab)return 0;//初始tidinit_kmem_cache_cpus(s);return 1;
}static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache)
{int node;//从memory0中分配struct kmem_cache实体(这主要指s0或s1)struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);memcpy(s, static_cache, kmem_cache->object_size);...for_each_node_state(node, N_NORMAL_MEMORY) {struct kmem_cache_node *n = get_node(s, node);struct page *p;if (n) {list_for_each_entry(p, &n->partial, lru)p->slab_cache = s;//二级回收链表上小缓存池归属于那类大缓存池...}...}list_add(&s->list, &slab_caches);//加入同类缓存池链表return s;
}

2、slub分配和释放分析

首先看下同类缓存池,小缓存池,小内存块组成的结构,或者说模型,如下图:
在这里插入图片描述                  图二
代码是如何描述图中的结构,看下面慢慢分析:

每次分配内存时,都是通过申请size查找到对应的同类缓存池,再从同类缓存池中找到小缓存池,最后从小缓存池中找到空闲小内存块,并将小内存块起始地址返回给申请者.那么究竟是从s->cpu_slab->page, s->cpu_slab->partial, s->node[x]->partial三个中那个获取到小内存块,且看下面分析:

s->cpu_slab:
当前小内存块链头指针freelist
当前小缓存池指针page
一级回收链表partial

s->cpu_slab->freelist :
如图二,我们把freelist指向的,多个相互连接的小内存块,叫做小内存块链.
s->cpu_slab->freelist始终指向当前小缓存池里面小内存块链上第一个可用小内存块,即小缓存池首个小内存块.如图,小缓存池里面小内存块是按照"slub核心原理"描述方式串联在一起的,关于分配和释放,在"slub核心原理"节也有描述.

s->cpu_slab->page :
指向当前小缓存池首页.
在当前小缓存池上分配内存,主要函数slab_alloc_node(),定义在mm/slub.c中:
函数调用过程:
kmalloc() --> slab_alloc() --> slab_alloc_node()

static __always_inline void *slab_alloc_node(struct kmem_cache *s,gfp_t gfpflags, int node, unsigned long addr)
{void **object;struct kmem_cache_cpu *c;struct page *page;unsigned long tid;...	c = __this_cpu_ptr(s->cpu_slab);...object = c->freelist;//小缓存池上首个可用小内存块page = c->page;//当前小缓存池首页if (unlikely(!object || !node_match(page, node)))//object为NULL时表示当前小缓存池没有可用小内存块,需要从其他地方分配内存object = __slab_alloc(s, gfpflags, node, addr, c);else {//当前小缓存池有空闲小内存块,从当前小缓存池中分配获取小内存块void *next_object = get_freepointer_safe(s, object);//获取当前小缓存池上,下一个可用小内存块地址.../*this_cpu_cmpxchg_double()将做如下操作:s->cpu_slab->freelist = next_object;s->cpu_slab->tid = tid;即s->cpu_slab->freelist指向小缓存池中下一个可用小内存块		*/if (unlikely(!this_cpu_cmpxchg_double(s->cpu_slab->freelist, s->cpu_slab->tid,object, tid,next_object, next_tid(tid)))) {...}...}...	return object;//返回可用小内存块首地址
}

小内存块释放,发生在当前小缓存池上,主要函数slab_free(), 定义在mm/slub.c:
函数调用过程:
kfree() --> slab_free()

void kfree(const void *x)
{struct page *page;void *object = (void *)x;//释放内存地址	...page = virt_to_head_page(x);//通过释放内存虚拟地址,查找到小内存所在小缓存池,即小缓存池首页地址...slab_free(page->slab_cache, page, object, _RET_IP_);
}static __always_inline void slab_free(struct kmem_cache *s,struct page *page, void *x, unsigned long addr)
{void **object = (void *)x;//释放内存地址首地址struct kmem_cache_cpu *c;unsigned long tid;...c = __this_cpu_ptr(s->cpu_slab);...if (likely(page == c->page)) {//释放内存,属于当前小缓存池set_freepointer(s, object, c->freelist);//c->freelist被写入到释放小内存块object空间头部,//这也是小内存块串联在一起的关键,就如"slub核心原理"描述的//this_cpu_cmpxchg_double执行主要操作://s->cpu_slab->freelist = object;刚释放的小内存块,被放在小内存块链上第一个位置//s->cpu_slab->tid = next_tid(tid);		if (unlikely(!this_cpu_cmpxchg_double(s->cpu_slab->freelist, s->cpu_slab->tid,c->freelist, tid,object, next_tid(tid)))) {...			}...} else__slab_free(s, page, x, addr);
}

从以上可以看出:

if (likely(page == c->page)) {//释放内存,属于当前小缓存池

当释放的小内存块,属于当前缓存池c->page时,释放才会发生在当前小缓存池上,即把释放的小内存块,回收到当前缓存池.

内存分配和释放,发生在当前缓存池,速度是最快的.
下面,将会分析,不是发生在当前缓存池的情况.

s->cpu_slab->partial :
如图二,多个相互有连接的小缓存池,我们取名为小缓存池链
s->cpu_slab->partial始终指向小缓存池链头
当前缓存池s->cpu_slab->page指向的小缓存池里面小内存块被分配使用完时, 会从一级回收链表s->cpu_slab->partial上取小缓存池, 给到s->cpu_slab->page上,即切换到新的小缓存池,s->cpu_slab->freelist也会指向新小缓存池里面小内存块链上头个小内存块.

函数slab_alloc_node()在当前缓存池耗尽时,会调用__slab_alloc()进一步找寻空闲内存:

static __always_inline void *slab_alloc_node(struct kmem_cache *s,gfp_t gfpflags, int node, unsigned long addr)
{void **object;struct kmem_cache_cpu *c;struct page *page;unsigned long tid;...c = __this_cpu_ptr(s->cpu_slab);...object = c->freelist;page = c->page;...if (unlikely(!object || !node_match(page, node)))//object为NULL时,表示小缓存池里面小内存块耗尽object = __slab_alloc(s, gfpflags, node, addr, c);else {//从当前小缓存池分配内存...}...
}

__slab_alloc()首先从一级回收链表s->cpu_slab->partial上取小缓存池,并在小缓存池中找寻空闲小内存块:

static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,unsigned long addr, struct kmem_cache_cpu *c)
{void *freelist;struct page *page;unsigned long flags;...c = this_cpu_ptr(s->cpu_slab);...page = c->page;//当前小缓存池if (!page)goto new_slab;
redo:...freelist = get_freelist(s, page);//第一次执行时,当前小缓存池已经没有了空闲小内存块,返回的freelist为NULL,//只有通过下面goto redo跳转,再次执行时,//page指向新的小缓存池,如存在空闲小内存块,返回的freelist才会是非NULLif (!freelist) {c->page = NULL;//清空指向当前小缓存池的指针stat(s, DEACTIVATE_BYPASS);goto new_slab;//跳转到切换当前小缓存池的地方}...
load_freelist://执行到这里,表示已经找到空闲小内存块...c->freelist = get_freepointer(s, freelist);//freelist是指向小内存块链上第一块,//get_freepointer返回后,c->freelist指向小内存块链上第二块//get_freepointer()函数,就是从小内存块链上取小内存块的过程,并返回下一块c->tid = next_tid(c->tid);...return freelist;//成功获取小内存块,返回new_slab:if (c->partial) {//小缓存池链上,如有小缓存池page = c->page = c->partial;//从c->partial上取新的小缓存池,即切换当前小缓存池c->partial = page->next;//c->partial指向小缓存池链上,下一个小缓存池stat(s, CPU_PARTIAL_ALLOC);c->freelist = NULL;//清空当前小内存块链指针,为切换当前小内存块链做准备goto redo;//切换当前小缓存池后,跳转准备再次查找空闲小内存块.}...

函数get_freelist():

static inline void *get_freelist(struct kmem_cache *s, struct page *page)
{struct page new;unsigned long counters;void *freelist;do {freelist = page->freelist;//从当前小缓存池上取小内存块链头//page->freelist始终指向小内存块链头//小缓存池从伙伴系统分配来,切换为当前小缓存//池时,page->freelist会被置为NULLcounters = page->counters;new.counters = counters;VM_BUG_ON(!new.frozen);new.inuse = page->objects;new.frozen = freelist != NULL;//page为当前小缓存池时,freelist为//NULL,那么new.frozen=0,//frozen为0表示解冻,可被释放,为1时//表示冻住,小缓存池不可被释放,//这里的释放,是指释放回伙伴系统//page不为当前小缓存池时,如果有空闲//小内存块,则new.fozen=1,没有空闲//小内存块时,new.fozen=0//__cmpxchg_double_slab函数主要作用:	//page->freelist=NULL//page->counters=new.counters,即修改page->counter值} while (!__cmpxchg_double_slab(s, page,freelist, counters,NULL, new.counters,"get_freelist"));return freelist;//返回小内存块链头地址
}

在上面分配过程中,s->cpu_slab->page指向的当前小缓存池,在切换到新小缓存池时,旧的小缓存池,似乎直接被抛弃,那么,slub后面是怎么知道旧小缓存池的存在?请看小内存块释放,是怎么发生在一级回收链表s->cpu_slab->partial上:
函数调用过程:
kfree() --> slab_free() --> __slab_free() --> put_cpu_partial()

void kfree(const void *x)
{struct page *page;void *object = (void *)x;//释放小内存块的地址...page = virt_to_head_page(x);//通过内存地址,查找到内存所在页的首页地址,//即,被释放小内存块,所在小缓存池首页地址...//同类缓存池page->slab_cache//被释放内存所在小缓存池首页page//被释放object小内存块起始地址slab_free(page->slab_cache, page, object, _RET_IP_);
}static __always_inline void slab_free(struct kmem_cache *s,struct page *page, void *x, unsigned long addr)
{void **object = (void *)x;struct kmem_cache_cpu *c;unsigned long tid;...c = __this_cpu_ptr(s->cpu_slab)...if (likely(page == c->page)) {//当前缓存池...}else__slab_free(s, page, x, addr);//释放内存,不在当前小缓存池中
}//__slab_free需要关注:
//同一个page小缓存池里面小内存块分配完后,
//是否为首次发生小内存块释放
static void __slab_free(struct kmem_cache *s, struct page *page,void *x, unsigned long addr)
{void *prior;void **object = (void *)x;int was_frozen;struct page new;unsigned long counters;struct kmem_cache_node *n = NULL;unsigned long uninitialized_var(flags);...do {...prior = page->freelist;//page小缓存池,里面小内存块分配完后,//首次发生小内存块释放时,page->freelist为NULL//不是首次释放时,表示小缓存池,前面发生过小内存块释放,page->freelist不为NULLcounters = page->counters;set_freepointer(s, object, prior);//将prior写入到object空间开头地方new.counters = counters;was_frozen = new.frozen;//page小缓存池,里面小内存块分配完后,//首次发生小内存块释放时,new.frozen=0,可看切换当前//小缓存池时get_freelist()函数的调用//不是首次释放时,new.frozen=1new.inuse--;//inuse表示小缓存池里面有多少个小内存块被分配使用中,//释放时,自然就需要减去1if ((!new.inuse || !prior) && !was_frozen) {//此条件,在page小缓存池,里面小内存块分配完,//首次发生小内存块释放时,将会为真//不是首次释放小内存块时,将为假//kmem_cache_debug(s)主要用于slub调试时打开CONFIG_SLUB_DEBUG,//正常情况下CONFIG_SLUB_DEBUG是不被打开的			if (!kmem_cache_debug(s) && !prior)//page上首次发生小内存块释放时,此条件为真,//不是首次发生小内存块释放,则为假/** Slab was on no list before and will be partially empty* We can defer the list move and instead freeze it.*/new.frozen = 1;else { /* Needs to be taken off a list */n = get_node(s, page_to_nid(page));/** Speculatively acquire the list_lock.* If the cmpxchg does not succeed then we may* drop the list_lock without any processing.** Otherwise the list_lock will synchronize with* other processors updating the list of slabs.*/spin_lock_irqsave(&n->list_lock, flags);}}} while (!cmpxchg_double_slab(s, page,//cmpxchg_double_slab主要完成://page->freelist=object//page->counters=new.countersprior, counters,object, new.counters,"__slab_free"));if (likely(!n)) {//此条件,基本为真/** If we just froze the page then put it onto the* per cpu partial list.*/if (new.frozen && !was_frozen) {//小缓存池page在一级回收链表上,//首次发生小内存块释放时,//此条件为真,不是首次释放时,为假put_cpu_partial(s, page, 1);//将小缓存池page放到s->cpu_slab->partial链表上			stat(s, CPU_PARTIAL_FREE);}/** The list lock was not taken therefore no list* activity can be necessary.*/if (was_frozen)stat(s, FREE_FROZEN);return;}...//put_cpu_partial将把小缓存池,放到s->cpu_slab->partial
static void put_cpu_partial(struct kmem_cache *s, struct page *page, int drain)
{struct page *oldpage;int pages;int pobjects;do {pages = 0;pobjects = 0;oldpage = this_cpu_read(s->cpu_slab->partial);//一级回收链表if (oldpage) {//同类缓存池,首次发生小内存块释放,oldpage为NULL//之后,发生的小内存块释放,oldpage将为非NULLpobjects = oldpage->pobjects;//一级回收链表上,小内存块总数pages = oldpage->pages;//一级回收链表上,小缓存池总数//一级回收链表上,小内存块总数超过cpu_partial时,//将会把一级回收链表上的小缓存池,取出放入到二级回收链表上//注意:__slab_free()调用本函数,drain是为1if (drain && pobjects > s->cpu_partial) {unsigned long flags;/** partial array is full. Move the existing* set to the per node partial list.*/local_irq_save(flags);//将一级回收链表上小缓存池,取出放入到二级unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));local_irq_restore(flags);oldpage = NULL;pobjects = 0;pages = 0;stat(s, CPU_PARTIAL_DRAIN);}}pages++;//统计一级回收链表上,小缓存池数pobjects += page->objects - page->inuse;//统计一级回收链表上,空闲小内存块数//page->objects小缓存池上小内存块总数,从伙伴系统分配时确定,之后一直不变//page->inuse小缓存池已分配使用的小内存块数page->pages = pages;//将统计的小缓存池数信息,存放到一级回收链表头中//由此可看出,一级回收链表上小缓存池总数信息,始终存放在链表头中page->pobjects = pobjects;//将统计的小内存块数,存放到一级回收链表头中//由此可看出,一级回收链表上小内存块总数信息,始终存放在链表头中page->next = oldpage;//新加入的小缓存池,放入一级回收链表头//this_cpu_cmpxchg主要操作://s->cpu_slab->partial=page} while (this_cpu_cmpxchg(s->cpu_slab->partial, oldpage, page) != oldpage);
}

s->node[x]->partial :
二级回收链表
在什么情况下,才会从二级回收链表分配获取小缓存池呢?
在什么情况下,才会把小缓存池放入到二级回收链表呢?

从上面分析中,可以看到分配调用过程,有个函数__slab_alloc(),在这个函数里面,根据一定条件满足,将从二级回收链表分配内存:

static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,unsigned long addr, struct kmem_cache_cpu *c)
{void *freelist;struct page *page;unsigned long flags;...c = this_cpu_ptr(s->cpu_slab);...
load_freelist://执行到这里,表示已经成功切换到有空闲小内存块的小缓存池/** freelist is pointing to the list of objects to be used.* page is pointing to the page from which the objects are obtained.* That page must be frozen for per cpu allocations to work.*/VM_BUG_ON(!c->page->frozen);c->freelist = get_freepointer(s, freelist);c->tid = next_tid(c->tid);local_irq_restore(flags);return freelist;new_slab:if (c->partial) {//c->partial为NULL时,表示一级级回收链表无小缓存池,//将进入下面的code,从二级回收链表上分配获取小缓存池...}//将有可能从二级回收链表上获取小缓存池freelist = new_slab_objects(s, gfpflags, node, &c);...page = c->page;//此时当前小缓存池已经切换到新小缓存池if (likely(!kmem_cache_debug(s) && pfmemalloc_match(page, gfpflags)))goto load_freelist;//跳转...
}static inline void *new_slab_objects(struct kmem_cache *s, gfp_t flags,int node, struct kmem_cache_cpu **pc)
{void *freelist;struct kmem_cache_cpu *c = *pc;struct page *page;freelist = get_partial(s, flags, node, c);//从二级回收链表上分配获取内存if (freelist)return freelist;page = new_slab(s, flags, node);//当前小缓存池,一级,二级回收链表上都没//有空闲小内存块,将会从伙伴系统分配//获取新小缓存池if (page) {//成功从伙伴系统中分配获取到小缓存池c = __this_cpu_ptr(s->cpu_slab);if (c->page)flush_slab(s, c);//清空当前小缓存池和小内存块链//并且根据条件,是否将小缓存池释放会伙伴系统/** No other reference to the page yet so we can* muck around with it freely without cmpxchg*/freelist = page->freelist;//新小缓存池中小内存块链page->freelist = NULL;//情况当前小缓存池中小内存块链,表示小内存块都被用//page->freelist始终指向未被用小内存块链stat(s, ALLOC_SLAB);c->page = page;//新小缓存池,作为当前小缓存池*pc = c;} elsefreelist = NULL;return freelist;
}static void *get_partial(struct kmem_cache *s, gfp_t flags, int node,struct kmem_cache_cpu *c)
{void *object;int searchnode = (node == NUMA_NO_NODE) ? numa_node_id() : node;//从二级回收链表上分配获取小缓存池object = get_partial_node(s, get_node(s, searchnode), c, flags);if (object || node != NUMA_NO_NODE)return object;...
}static void *get_partial_node(struct kmem_cache *s, struct kmem_cache_node *n,struct kmem_cache_cpu *c, gfp_t flags)
{struct page *page, *page2;void *object = NULL;int available = 0;int objects;/** Racy check. If we mistakenly see no partial slabs then we* just allocate an empty slab. If we mistakenly try to get a* partial slab and there is none available then get_partials()* will return NULL.*/if (!n || !n->nr_partial)//二级回收链表是否为空//n指向二级回收链表//n->nr_partial二级回收链表上小缓存池数量return NULL;spin_lock(&n->list_lock);//page指向前一个小缓存池,page2指向下一个小缓存池//n->partial二级回收链表//这个循环将把二级回收链表上,第一个小缓存池作为当前小缓存池,其他的放到二级回收链表list_for_each_entry_safe(page, page2, &n->partial, lru) {void *t;if (!pfmemalloc_match(page, flags))continue;//从二级回收链表上获取空闲小内存块链tt = acquire_slab(s, n, page, object == NULL, &objects);if (!t)break;available += objects;//统计小内存块数量if (!object) {c->page = page;//第一个,从二级回收链表获取的小缓存池作为当前小缓存池stat(s, ALLOC_FROM_PARTIAL);object = t;} else {put_cpu_partial(s, page, 0);//将后续从二级回收链表上获取的小缓存//池全部取出来放入到一级回收链表上stat(s, CPU_PARTIAL_NODE);}if (kmem_cache_debug(s) || available > s->cpu_partial / 2)break;}spin_unlock(&n->list_lock);return object;
}static inline void *acquire_slab(struct kmem_cache *s,struct kmem_cache_node *n, struct page *page,int mode, int *objects)
{void *freelist;unsigned long counters;struct page new;/** Zap the freelist and set the frozen bit.* The old freelist is the list of objects for the* per cpu allocation list.*/freelist = page->freelist;counters = page->counters;new.counters = counters;*objects = new.objects - new.inuse;//计算空闲小内存块数量if (mode) {//mode非0,表示二级回收链表上第一个小缓存池new.inuse = page->objects;new.freelist = NULL;} else {//二级回收链表上,其他小缓存池new.freelist = freelist;}VM_BUG_ON(new.frozen);new.frozen = 1;//表示小缓存池被冻结,不能释放回伙伴系统//page->freelist = new.freelist//page->counters=new.countersif (!__cmpxchg_double_slab(s, page,freelist, counters,new.freelist, new.counters,"acquire_slab"))return NULL;remove_partial(n, page);//从二级回收链表上移除小缓存池WARN_ON(!freelist);return freelist;
}

slub释放内存时,如何将内存释放到二级回收链表上?看如下:
函数调用过程:
kfree() --> slab_free() --> __slab_free() --> put_cpu_partial()

static void put_cpu_partial(struct kmem_cache *s, struct page *page, int drain)
{struct page *oldpage;int pages;int pobjects;...//pobjeccts表示一级回收链表上空闲小内存块数量//s->cpu_partial表示一级回收链表上空闲小内存块数量上限,大于上限将会移动到二级回收链表if (drain && pobjects > s->cpu_partial) {unsigned long flags;...//将一级回收链表上小缓存池,移动到二级回收链表unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));...}...
}static void unfreeze_partials(struct kmem_cache *s,struct kmem_cache_cpu *c)
{struct kmem_cache_node *n = NULL, *n2 = NULL;struct page *page, *discard_page = NULL;while ((page = c->partial)) {//循环从一级回收链表上获取小缓存池struct page new;struct page old;c->partial = page->next;//修改一级回收链表头指针n2 = get_node(s, page_to_nid(page));//n2指向二级回收链表if (n != n2) {if (n)spin_unlock(&n->list_lock);n = n2;spin_lock(&n->list_lock);}do {old.freelist = page->freelist;old.counters = page->counters;VM_BUG_ON(!old.frozen);new.counters = old.counters;new.freelist = old.freelist;new.frozen = 0;//清楚冻结标志//page->freelist=new.freelist//page->counters=new.counters} while (!__cmpxchg_double_slab(s, page,old.freelist, old.counters,new.freelist, new.counters,"unfreezing slab"));//n->nr_partial二级回收链表上,小缓存池数量// s->min_partial二级回收链表上,小缓存池数量上限,超过将被释放回到伙伴系统//new.inuse为0时,表示小缓存池上,没有小内存块被分配使用if (unlikely(!new.inuse && n->nr_partial > s->min_partial)) {page->next = discard_page;discard_page = page;} else {add_partial(n, page, DEACTIVATE_TO_TAIL);//将小缓存池page加入到二级回收链表n上stat(s, FREE_ADD_PARTIAL);}}if (n)spin_unlock(&n->list_lock);while (discard_page) {//将到达n->nr_partial > s->min_partial条件的//二级缓存回收链表上小缓存池将被释放回伙伴系统page = discard_page;discard_page = discard_page->next;stat(s, DEACTIVATE_EMPTY);discard_slab(s, page);//释放回伙伴系统stat(s, FREE_SLAB);}
}

从当前小缓存池,一级,二级回收链表上,都无法寻找到具有空闲小内存块的缓存池时,将会从伙伴系统分配获取小缓存池:
函数调用:
kmalloc() --> slab_alloc() --> slab_alloc_node() --> __slab_alloc() --> new_slab_objects() --> new_slab()

static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
{struct page *page;void *start;void *last;void *p;int order;...//从伙伴系统中分配内存页,作为小缓存池page = allocate_slab(s,flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);if (!page)goto out...page->slab_cache = s;//小缓存池page,属于同类缓存池s__SetPageSlab(page);if (page->pfmemalloc)SetPageSlabPfmemalloc(page);start = page_address(page);//通过内存页计算内存页首地址...last = start;//将分配获取到的小缓存池,按照"slub核心原理"方式初始化,也可以视为格式化小缓存池for_each_object(p, s, start, page->objects) {setup_object(s, page, last);set_freepointer(s, last, p);last = p;}setup_object(s, page, last);set_freepointer(s, last, NULL);//最后一个小内存块,头部空间写入NULLpage->freelist = start;//小缓存池中,第一个小内存块起始地址page->inuse = page->objects;//小缓存池中小内存块总数量objectspage->frozen = 1;//小缓存池被冻结,不能释放回伙伴系统
out:return page;static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{struct page *page;struct kmem_cache_order_objects oo = s->oo;gfp_t alloc_gf...flags |= s->allocflags;/** Let the initial higher-order allocation fail under memory pressure* so we fall-back to the minimum order allocation.*/alloc_gfp = (flags | __GFP_NOWARN | __GFP_NORETRY) & ~__GFP_NOFAIL;page = alloc_slab_page(alloc_gfp, node, oo);//小缓存池空间大小,oo是2的幂指数if (unlikely(!page)) {//按照oo大小分配失败,将降低为s->min继续分配oo = s->min;/** Allocation may have failed due to fragmentation.* Try a lower order alloc if possible*/page = alloc_slab_page(flags, node, oo);if (page)stat(s, ORDER_FALLBACK);}...page->objects = oo_objects(oo);//计算小缓存池里面小内存块总数...return page;
}

简单总结:

slub分配时小内存块计算:
也可以说,申请size小内存块,查找属于那类大缓存池(同类缓存池).
第一种办法:
(1)kmalloc_index()得到kmalloc_caches[]角标index.
(2)然后kmalloc_caches[index]就是同类缓存池,之后见上面"slub分配释放"分析.
第二种办法:
通过调用kmalloc_slab()直接获取,同样,之后见上面"slub分配释放"分析.

slub分配内存时:
首先在当前小缓存池上检查有没有空闲小内存块,有则直接分配,如没有,检查一级回收链表上有没有小缓存池,有,则把该小缓存池切换为当前小缓存池,然后分配,如果一级回收链表还是没有,继续检查二级回收链表上小缓存池,同样,有,则切换为当前小缓存池,然后分配,要是二级回收链表上都还是没有,最后,向伙伴系统申请分配新的小缓存池,并将新分配的小缓存池切换为当前小缓存池,再分配.

slub释放内存:
释放小内存块,属于当前小缓存池时,直接释放回当前小缓存池.
释放小内存块,不属于当前小缓存池时,会被释放回所所属小缓存池,如果该小缓存池是分配使用完后,首次发生小内存块释放,那么将会把小缓存池挂接到一级回收链表上.每当有其他小缓存池发生挂接到一级回收链表时,会借此时机,先检查一级回收链表上小内存块总数是否大于s->cpu_partial,如果大于,将会把一级回收链表上所有小缓存池移动到二级回收链表上,移动过程中,还会检查二级回收链表上小缓存池数,如果超过s->min_partial,将会把超过的小缓存池(里面小内存块都未被分配使用情况)释放回伙伴系统,然后再将该小缓存池放入到一级回收链表.


http://chatgpt.dhexx.cn/article/iXNBl6LK.shtml

相关文章

内存管理 slub算法

内核管理页面使用了2个算法&#xff1a;伙伴算法和slub算法&#xff0c;伙伴算法以页为单位管理内存&#xff0c;但在大多数情况下&#xff0c;程序需要的并不是一整页&#xff0c;而是几个、几十个字节的小内存。于是需要另外一套系统来完成对小内存的管理&#xff0c;这就是s…

linux slub分配器,slub分配器

原标题&#xff1a;slub分配器 概述&#xff1a; Linux的物理内存管理采用了以页为单位的buddy system(伙伴系统)&#xff0c;但是很多情况下&#xff0c;内核仅仅需要一个较小的对象空间&#xff0c;而且这些小块的空间对于不同对象又是变化的、不可预测的&#xff0c;所以需要…

Linux内存管理(八): slub分配器和kmalloc

kernel: 5.10 Arch: aarch64 上文 介绍过&#xff0c; 伙伴系统在分配内存时是以物理页为单位的&#xff0c;但在很多场景下&#xff0c;内存需要分配的大小是以字节为单位的&#xff0c;达不到一个物理页的大小。如果继续使用伙伴系统进行内存分配&#xff0c; 那么就会出现严…

Linux 踩内存 slub,Linux SLUB 内存分配器分析

本文简介 本文主要介绍了Linux SLUB分配的产生原因、设计思路及其代码分析。适合于对Linux内核&#xff0c;特别是对Linux内存分配器感兴趣的读者。 1.为何需要SLUB&#xff1f; Linux SLUB内存分配器合入Linux主分支已经整整10年了&#xff01;并且是Linux目前默认的内存分配器…

SLUB

 内核管理页面使用了2个算法:伙伴算法和slub算法,伙伴算法以页为单位管理内存,但在大多数情况下,程序需要的并不是一整页,而是几个、几十个字节的小内存。于是需要另外一套系统来完成对小内存的管理,这就是slub系统。slub系统运行在伙伴系统之上,为内核提供小内存管…

linux内存源码分析 - SLUB分配器概述

本文为原创&#xff0c;转载请注明&#xff1a;http://www.cnblogs.com/tolimit/ SLUB和SLAB的区别 首先为什么要说slub分配器&#xff0c;内核里小内存分配一共有三种&#xff0c;SLAB/SLUB/SLOB&#xff0c;slub分配器是slab分配器的进化版&#xff0c;而slob是一种精简的小内…

Linux 内存管理(三)—— SLUB

目录 一、概述 二、SLUB 2.1 数据结构 2.2 初始化 2.2.1 静态建立过程 2.3 API 2.3.1 kmem_cache_create 2.3.2 kmem_cache_alloc 2.3.3 kmem_cache_free 2.3.4 kmalloc/kfree 三、参考 一、概述 伙伴系统最小的分配单位是页&#xff0c;这对于较小的内存需求会造…

SLUB内存管理的4个主要接口函数介绍(4)

slub内存管理的4个主要接口函数如下&#xff08;参考kernel-4.19&#xff09;&#xff1a; //slab缓存的创建 struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *)); //slab object的分配 void *k…

Linux内核:内存管理——SLUB分配器

SLUB和SLAB的区别 首先为什么要说slub分配器&#xff0c;内核里小内存分配一共有三种&#xff0c;SLAB/SLUB/SLOB&#xff0c;slub分配器是slab分配器的进化版&#xff0c;而slob是一种精简的小内存分配算法&#xff0c;主要用于嵌入式系统。慢慢的slab分配器或许会被slub取代&…

SLUB的引入及举例说明

我们都知道Buddy分配器是按照页的单位分配的&#xff08;Buddy系统分配器实现&#xff09;&#xff0c;如果我们需要分配几十个字节&#xff0c;几百个字节的时候&#xff0c;就需要用到SLAB分配器。 SLAB分配器专门是针对小内存分配而设计的&#xff0c;比如我们驱动中常见的…

电信光猫改桥接模式

如果只是改桥接 可以试试下面这两个地址&#xff1a;http://192.168.1.1/bridge_route.gchhttp://192.168.1.1:8080/bridge_route.gch 转载于:https://www.cnblogs.com/Devopser/p/11257535.html

电信光猫改桥接还在苦苦破解超级密码吗?

电信光猫路由改桥接&#xff0c;不同的地区有不通的方法。比较幸运的地区和终端&#xff0c;有通用的超级密码。但是不幸的地区&#xff0c;就需要通过破解这个超级密码。我就属于比较不幸的地区&#xff0c;遇到不幸的终端&#xff1a;天翼网关TEWA-708G。然后按照网上大神的破…

获取电信光猫TEWA-600超级管理密码,修改电信光猫为桥接模式

文章转载&#xff1a;玩转盒子 前些年各地运营商响应国家政策&#xff0c;光进铜退小区宽带由网线入户改成光缆入户&#xff0c;这样必须用光猫把光信号转换成电信号。我用的是中国电信的宽带&#xff0c;2017年我们小区也进行了网络改造&#xff0c;网线入户变成了光缆入户。…

友华PT921G光猫破解获取超级密码和更改桥接模式

获取超级密码 1.登陆光猫管理地址192.168.1.1 2.打开新的窗口输入&#xff1a;http://192.168.1.1/romfile.cfg ,就能下载到配置文件 3.用记事本打开romfile.cfg&#xff0c;点击编辑–>查找–>输入telecomadmin->点击查找下一个 4.查找到username“telecomadmin”,而…

为了改桥接,我决定破解中兴F450G V2光猫

还记得我之前买了个猫棒来替换光猫么&#xff1f;用了一个来月&#xff0c;发现这玩意真的不稳定&#xff0c;短则几分钟长则一两天它必定自己重启一次&#xff0c;导致我的网络时不时就会断线。这玩意不好使&#xff0c;我也没有别的光猫&#xff0c;只好找电信装维师傅给我改…

大话设计模式-桥接模式

使用场景&#xff1a;桥接模式的核心意图就是将这些实现独立出来&#xff0c;让它们各自地变化。这就使得每种实现的变化不会影响其他实现&#xff0c;从而达到应对变化的目的。 多用聚合&#xff0c;少用继承 1. 手机软件抽象类、通讯录类、游戏类 package com.hj.designPat…

电信光猫/烽火HG6543c1光猫超级密码获取改桥接模式( 中国电信浙江公司定制天翼网关3.0)

第一步&#xff1a;开telnet 浏览器网址栏输入&#xff1a;192.168.1.1:8080/cgi-bin/telnetenable.cgi?telnetenable1 第二步&#xff1a;使用pytty软件登入光猫 &#xff08;Putty软件下载&#xff1a;https://download.csdn.net/download/qq_34885669/12098009&#xff0…

保姆级-光猫改桥接-路由拨号-openwrt端口转发-阿里云DNS域名解析访问家中设备

准备&#xff1a; 1.公网ip&#xff08;江苏省电信&#xff0c;电话1分钟解决&#xff09; 2.域名(最好备案了) 3.路由器(我的是红米AC2100刷openwrt&#xff0c;重点路由器要有动态dns服务的功能&#xff0c;端口转发功能什么路由器都有) 往期教程 路由器固件刷写 红米AC210…

单臂路由 光猫桥接 一根网线复用

由于弱电箱太小&#xff0c; 二级路由拨号只能放客厅里。 这就导致其他房间无法上网了。 下面是单臂路由设置方法&#xff0c; 光猫桥接的端口下的设备&#xff0c;同样可以上网。 充分利用光猫端口。 前提&#xff1a; 光猫需要超级密码 二级路由固件为 openwrt ​​​​​

光猫桥接模式路由器拨号成功 端口映射失败的原因

路由器拨号成功&#xff0c;光猫必须是桥接模式。 光猫从路由模式设置为桥接模式的操作过程: 在浏览器地址栏中输入光猫的地址192.168.1.1&#xff0c; 进入光猫的登陆界面&#xff0c;输入超级用户名和密码&#xff0c; 用户&#xff1a;CMCCAdmin(或telecomadmin &#xff0…