精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

解密Slab分配器:內存管理的高效武器

系統 Linux
Slab 分配器是 Linux 內核中一種極為重要的內存管理機制,主要用于高效管理小塊內存的分配。它針對頻繁分配和釋放的小對象進行了專門優化,能夠有效減少內存碎片,顯著提高系統性能。

在 Linux 操作系統的神秘世界里,內存管理如同一場精密的棋局,每一步都關乎著系統的性能和穩定性。而在這場棋局中,Slab 分配器宛如一顆璀璨的明珠,是內存管理的高效武器。它以其獨特的設計和強大的功能,為 Linux 系統的內存分配與釋放提供了卓越的解決方案。現在,讓我們一同揭開 Slab 分配器的神秘面紗,探索它在內存管理中的神奇之處。

Linux內核版本:5.0
架構:ARM64
Linux 5.0內核源碼注釋倉庫地址:
zhangzihengya/LinuxSourceCode_v5.0_study (http://github.com)

一、Slab分配器概述

Slab 分配器是 Linux 內核中一種極為重要的內存管理機制,主要用于高效管理小塊內存的分配。它針對頻繁分配和釋放的小對象進行了專門優化,能夠有效減少內存碎片,顯著提高系統性能。在 Linux 內核的眾多子系統中,Slab 分配器得到了廣泛應用,比如網絡緩沖區、文件系統緩存以及進程控制塊等領域。

Slab 分配器的核心概念包括 Cache(緩存)、Slab 和 Object。其中,Cache 是為每種類型的對象創建的緩存,每個緩存存儲相同大小的對象集合。Slab 則是一塊連續的內存區域,用于存儲一組特定大小的對象。而 Object 是在 Slab 中實際存儲的數據單元。

Slab 分配器具有諸多優勢。首先,它通過對象復用,避免了頻繁的分配與釋放操作,極大地提高了內存分配的效率。其次,它能夠保證內存分配尺寸和對齊一致,從而有效減少內存碎片問題。最后,Slab 分配器適用于不同大小的對象,能夠顯著提升內核的整體性能。

通過查看/proc/slabinfo文件,可以了解 Slab 分配器的內存使用情況,幫助調優系統性能。該文件輸出的頭部包含多個字段,其中name字段表示 slab 緩存的名稱,每個 slab 緩存存儲相同類型和大小的對象;active_objs字段表示當前在使用的(已分配的)對象數量;num_objs字段表示緩存中分配的對象總數,包括已經分配和空閑的對象;objsize字段表示每個對象的大小(以字節為單位);objperslab字段表示每個 slab 中包含的對象數量;pagesperslab字段表示每個 slab 使用的頁數;tunables字段中的值控制 slab 緩存的行為,如limit表示 slab 緩存中每個 CPU 可以緩存的最大對象數量,batchcount表示每次從全局緩存到 CPU 本地緩存中批量獲取的對象數,sharedfactor控制多個 CPU 是否共享 slab 緩存;slabdata部分包含有關 slab 使用的統計數據,如active_slabs表示當前正在使用的 slab 數量,num_slabs表示系統中分配的總 slab 數量,sharedavail表示 CPU 本地緩存中可用對象的數量。

二、核心技術

2.1 slab機制

slab分配器最終還使用伙伴系統來分配實際的物理頁面,只不過slab分配器在這些連續的物理頁面上實現了自己的機制,以此來對小內存塊進行管理。slab機制如下圖所示:

圖片圖片

其中每個slab描述符都會建立共享對象緩沖池和本地對象緩沖池。slab機制有如下特性:

把分配的內存塊當作對象(object)來看待。對象可以自定義構造函數(constructor) 和析構函數(destructor)來初始化對象的內容并釋放對象的內容。

slab對象被釋放之后不會馬上丟棄而是繼續保留在內存中,可能稍后會被用到,這樣不需要重新向伙伴系統申請內存。

slab機制可以根據特定大小的內存塊來創建slab描述符,如內存中常見的數據結構、打開文件對象等,這樣可以有效地避免內存碎片的產生,也可以快速獲得頻繁訪問的數據結構。另外,slab機制也支持按2的n次方字節大小分配內存塊。

slab機制創建了多層的緩沖池,充分利用了空間換時間的思想,未雨綢謬,有效地解決了效率問題。

每個CPU有本地對象緩沖池,避免了多核之間的鎖爭用問題。

每個內存節點有共享對象緩沖池。

2.2 slab框架

為了更好地理解slab分配器的細節,我們先從宏觀上大致了解下slab系統的架構,如下圖所示:

圖片圖片

slab系統由slab描述符、slab節點、本地對象緩沖池、共享對象緩沖池、3個slab鏈表、n個slab分配器,以及眾多slab緩存對象組成,相關數據結構的注解如下。slab描述符:

// kmem_cache數據結構是 slab 分配器中的核心成員,每個 slab 描述符都用一個 kmem_cache 數據結構來抽象描述
struct kmem_cache {
	// Per-cpu 變量的 array_cache 數據結構,每個CPU一個,表示本地 CPU 的對象緩沖池
	struct array_cache __percpu *cpu_cache;

/* 1) Cache tunables. Protected by slab_mutex */
	// 表示在當前 CPU 的本地對象緩沖池 array_cache 為空時,從共享對象緩沖池或 slabs_partial/slabs_free 列表中獲取的對象的數目
	unsigned int batchcount;
	// 當本地對象緩沖池中的空閑對象的數目大于 limit 時,會主動釋放 batchcount 個對象,便于內核回收和銷毀 slab
	unsigned int limit;
	// 用于多核系統
	unsigned int shared;

	// 對象的長度,這個長度要加上 align 對齊字節
	unsigned int size;
	struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */

	// 對象的分配掩碼
	slab_flags_t flags;		/* constant flags */
	// 一個 slab 中最多有多少個對象
	unsigned int num;		/* # of objs per slab */

/* 3) cache_grow/shrink */
	/* order of pgs per slab (2^n) */
	unsigned int gfporder;

	/* force GFP flags, e.g. GFP_DMA */
	gfp_t allocflags;

	// 一個 slab 中可以有多少個不同的緩存行
	size_t colour;			/* cache colouring range */
	// 著色區的長度,和 L1 緩存行大小相同
	unsigned int colour_off;	/* colour offset */
	struct kmem_cache *freelist_cache;
	// 每個對象要占用 1 字節來存放 freelist
	unsigned int freelist_size;

	/* constructor func */
	void (*ctor)(void *obj);

/* 4) cache creation/removal */
	// slab 描述符的名稱
	const char *name;
	struct list_head list;
	int refcount;
	// 對象的實際大小
	int object_size;
	// 對齊的長度
	int align;

...

	// slab 節點
	// 在 NUMA 系統中,每個節點有一個 kmem_cache_node 數據結構
	// 在 ARM Vexpress 平臺上,只有一個節點
	struct kmem_cache_node *node[MAX_NUMNODES];
};

slab節點:

struct kmem_cache_node {
	// 用于保護 slab 節點中的 slab 鏈表
	spinlock_t list_lock;

#ifdef CONFIG_SLAB
	// slab 鏈表,表示 slab 節點中有部分空閑對象
	struct list_head slabs_partial;	/* partial list first, better asm code */
	// slab 鏈表,表示 slab 節點中沒有空閑對象
	struct list_head slabs_full;
	// slab 鏈表,表示 slab 節點中全部都是空閑對象
	struct list_head slabs_free;
	// 表示 slab 節點中有多少個 slab 對象
	unsigned long total_slabs;	/* length of all slab lists */
	// 表示 slab 節點中有多少個全是空閑對象的 slab 對象
	unsigned long free_slabs;	/* length of free slab list only */
	// 空閑對象的數目
	unsigned long free_objects;
	// 表示 slab 節點中所有空閑對象的最大閾值,即 slab 節點中可容許的空閑對象數目最大閾值
	unsigned int free_limit;
	// 記錄當前著色區的編號。所有 slab 節點都按照著色編號來計算著色區的大小,達到最大值后又從 0 開始計算
	unsigned int colour_next;	/* Per-node cache coloring */
	// 共享對象緩沖區。在多核 CPU 中,除了本地 CPU 外,slab 節點中還有一個所有 CPU 都共享的對象緩沖區
	struct array_cache *shared;	/* shared per node */
	// 用于 NUMA 系統
	struct alien_cache **alien;	/* on other nodes */
	// 下一次收割 slab 節點的時間
	unsigned long next_reap;	/* updated without locking */
	// 表示訪問了 slabs_free 的 slab 節點
	int free_touched;		/* updated without locking */
#endif

...

};

對象緩沖池:

// slab 描述符會給每個 CPU 提供一個對象緩沖池(array_cache)
// array_cache 可以描述本地對象緩沖池,也可以描述共享對象緩沖池
struct array_cache {
	// 對象緩沖池中可用對象的數目
	unsigned int avail;
	// 對象緩沖池中可用對象數目的最大閾值
	unsigned int limit;
	// 遷移對象的數目,如從共享對象緩沖池或者其他 slab 中遷移空閑對象到該對象緩沖池的數量
	unsigned int batchcount;
	// 從緩沖池中移除一個對象時,將 touched 置為 1 ;
	// 當收縮緩沖池時,將 touched 置為 0;
	unsigned int touched;
	// 保存對象的實體
	// 指向存儲對象的變長數組,每一個成員存放一個對象的指針。這個數組最初最多有 limit 個成員
	void *entry[];
};

對象緩沖池的數據結構中采用了GCC編譯器的零長數組,entry[]數組用于存放多個對象,如下圖所示:

圖片圖片

⑴Cache(緩存)

Slab 分配器中的緩存(Cache)扮演著關鍵的角色。它是為每種類型的對象專門創建的,每個緩存都存儲著相同大小的對象集合。比如,對于特定大小的內核數據結構,會有對應的緩存來管理這些對象的分配和釋放。這種設計使得在需要分配相同類型的對象時,可以快速從緩存中獲取,提高了內存分配的效率。

⑵Slab

Slab 是一塊連續的內存區域,用于存儲一組特定大小的對象。每個 Slab 都經過精心劃分,以適應特定對象的存儲需求。Slab 的大小通常由所存儲對象的大小和數量決定。當系統需要為特定類型的對象分配內存時,Slab 分配器會從合適的 Slab 中分配空間。如果沒有合適的 Slab 可用,分配器可能會創建新的 Slab。

⑶Object(對象)

Object 是在 Slab 中實際存儲的數據單元。每個對象代表著特定類型的數據結構或資源。例如,在 Linux 內核中,進程控制塊(PCB)可以作為一個對象存儲在 Slab 中。對象可以處于不同的狀態,如已分配、空閑或部分空閑。空閑對象通過空閑鏈表進行跟蹤,以便在需要分配新對象時能夠快速找到可用的空間。

三、優勢解析

3.1 對象復用

Slab 分配器通過緩存對象避免了頻繁的分配與釋放操作。在內核中,會為有限的對象集分配大量內存,例如文件描述符和其他常見結構。而 Slab 分配器圍繞對象緩存進行,將內存保持為針對特定目的而初始化的狀態。例如,如果內存被分配給了一個互斥鎖,那么只需在為互斥鎖首次分配內存時執行一次互斥鎖初始化函數即可。后續的內存分配不需要執行這個初始化函數,因為從上次釋放和調用析構之后,它已經處于所需的狀態中了。這樣,當系統再次需要相同類型的對象時,可以直接從緩存中獲取,極大地提高了內存分配的效率。

3.2 減少內存碎片

Slab 分配器能夠保證內存分配尺寸和對齊一致,從而有效減少內存碎片問題。每個緩存結構都包括了兩個重要的成員:nodelists 和 array。nodelists 中的 kmem_list3 結構將 slab 分為完全用盡的 slab 鏈表、部分用盡的 slab 鏈表和空閑的 slab 鏈表。部分空閑的 slab 在最開始,當一個 slab 中的所有對象都被使用完時,就從 slabs_partial 列表中移動到 slabs_full 列表中。當一個 slab 完全被分配并且有對象被釋放后,就從 slabs_full 列表中移動到 slabs_partial 列表中。當所有對象都被釋放之后,就從 slabs_partial 列表移動到 slabs_empty 列表中。這種管理方式使得內存的分配和釋放更加有序,減少了內存碎片的產生。

此外,對象在 slab 中不是連續排列的,為了滿足對齊要求,會在 slab 對象中添加填充字節以滿足對齊要求,使用對齊的地址可以加速內存訪問。如果創建 slab 時指定了 SLAB_HWCACHE_ALIGN 標志,則會按照 cache_line_size 的返回值對齊,即對齊的硬件緩存行上。如果對象小于硬件緩存行的一半,則將多個對象放入一個緩存行。如果沒有指定對齊標記,則對齊到 BYTES_PER_WORD,即對齊到 void 指針所需字節數目。

3.2 高效的內存管理

Slab 分配器適用于不同大小的對象,能夠顯著提升內核的整體性能。Slab 分配器把對象分組放進高速緩存,每個高速緩存都是同種類型對象的一種 “儲備”。一個 cache 管理一組大小固定的內存塊,每個內存塊都可用作一種數據結構。cache 中的內存塊來自一到多個 slab,一個 slab 來自物理內存管理器的一到多個物理頁,該 slab 被分成一組固定大小的塊,被稱為 slab 對象。

與傳統的內存管理模式相比,Slab 緩存分配器提供了很多優點。首先,內核通常依賴于對小對象的分配,它們會在系統生命周期內進行無數次分配。Slab 緩存分配器通過對類似大小的對象進行緩存而提供這種功能,從而避免了常見的碎片問題。Slab 分配器還支持通用對象的初始化,從而避免了為同一目的而對一個對象重復進行初始化。最后,Slab 分配器還可以支持硬件緩存對齊和著色,這允許不同緩存中的對象占用相同的緩存行,從而提高緩存的利用率并獲得更好的性能。

四、關鍵結構

4.1 kmem_cache

kmem_cache定義了要管理的給定大小的對象池,是 Linux 內存管理中 Slab 分配器的核心結構之一。它包含多個重要參數和引用,對內存分配起著關鍵作用。

kmem_cache結構中的struct array_cache __percpu *cpu_cache是一個重要的成員,它是每個 CPU 的對象緩存池,相當于快表。當系統進行內存分配時,會優先從這個本地緩存中獲取對象,提高分配速度。

此外,batchcount、limit、shared等參數分別控制著從共享緩存或其他列表獲取對象的數量、本地緩存中空閑對象的最大數量以及多核系統中的共享設置。size參數表示要管理的對象的長度,這個長度需要加上對齊字節。flags是對象的分配掩碼,num表示一個 slab 中最多可以有多少個對象。

gfporder參數決定了一個 slab 中占用的連續頁框數的對數,而allocflags則是與伙伴系統交互時提供的分配標識。colour和colour_off參數用于控制 slab 的顏色設置,實現緩存著色以提高緩存命中率。freelist_cache和freelist_size在 off-slab 時使用,將 freelist 放在 slab 物理頁面外部。

ctor是構造函數指針,用于在創建對象時進行初始化操作。name是 slab 描述符的名稱,list用于將該結構鏈接到全局鏈表中,refcount是引用次數,在釋放 slab 描述符時會判斷,只有引用次數為 0 時才真正釋放。object_size是對象的實際大小,align是對齊的長度。

4.2 array_cache

array_cache是每個 CPU 的對象緩存池,在 Slab 分配器中起著實現快速分配和減少操作的關鍵作用。

array_cache結構中的avail表示對象緩存池中可用的對象數目。limit和batchcount與kmem_cache中的語義一致,分別控制著緩存的上限和從共享緩存或其他列表獲取對象的數量。touched參數在從緩存池移除一個對象時置 1,而收縮緩存時置 0。entry數組保存著對象的實體,采用 LIFO(后進先出)方式進行分配,即將該數組中的最后一個索引對應的對象分配出去,以保證該對象還駐留在高速緩存中的可能性。

4.3 kmem_cache_node

kmem_cache_node管理從伙伴系統分配的物理頁面,是 Slab 分配器在 NUMA 架構下的重要組成部分。它包含多個 slab 鏈表,對內存的分配和回收進行精細管理。

kmem_cache_node結構中的spinlock_t list_lock用于保護鏈表操作的互斥。在支持 CONFIG_SLAB 的情況下,它包含slabs_partial、slabs_full和slabs_free三個鏈表,分別對應部分用盡的 slab 鏈表、完全用盡的 slab 鏈表和空閑的 slab 鏈表。total_slabs表示三個鏈表中所有 slab 的總數,free_slabs表示空閑 slab 的數量,free_objects表示三個鏈表中所有空閑對象數目,free_limit表示 slab 中可以容許的空閑對象數目最大閾值。colour_next用于控制每個節點的緩存著色。

shared是多核 CPU 中的共享緩存區 slab 對象的指針。當一個 slab 中的所有對象都被使用完時,就從slabs_partial列表中移動到slabs_full列表中。當一個 slab 完全被分配并且有對象被釋放后,就從slabs_full列表中移動到slabs_partial列表中。當所有對象都被釋放之后,就從slabs_partial列表移動到slabs_empty列表中。這種管理方式使得內存的分配和釋放更加有序,減少了內存碎片的產生。

五、操作流程

5.1 創建 slab 緩存

使用kmem_cache_create函數創建一個描述特定對象類型內存池的結構。kmem_cache_create函數需要多個參數,包括可讀的名稱、被管理對象以字節計的長度、對齊數據時使用的偏移量、一組標志以及構造函數等。首先,對象長度會向上舍入到處理器字長的倍數。如果設置了SLAB_HWCACHE_ALIGN標志,內核會按照特定于體系結構的函數cache_line_size給出的值來對齊數據,并嘗試將盡可能多的對象填充到一個緩存行中。如果對象長度大于頁幀的 1/8,則將頭部管理數據存儲在 slab 之外,否則存儲在 slab 上。最后,通過迭代過程找到理想的 slab 長度,并對 slab 進行著色。

圖片圖片

為了使讀者有更真切的理解,下文將根據流程圖圍繞源代碼進行講解這個過程:

kmem_cache_create

// 創建 slab 描述符
// kmem_cache_create() 函數用于創建自己的緩存描述符;kmalloc() 函數用于創建通用的緩存
// name:slab 描述符的名稱
// size:緩沖對象的大小
// align:緩沖對象需要對齊的字節數
// flags:分配掩碼
// ctor:對象的構造函數
struct kmem_cache *
kmem_cache_create(const char *name, unsigned int size, unsigned int align,
		slab_flags_t flags, void (*ctor)(void *))

kmem_cache_create->...->__kmem_cache_create

// 創建 slab 緩存描述符
int __kmem_cache_create(struct kmem_cache *cachep, slab_flags_t flags)
{
    ...
	// 讓 slab 描述符的大小和系統的 word 長度對齊(BYTES_PER_WORD)
	// 當創建的 slab 描述符的 size 小于 word 長度時,slab 分配器會最終按 word 長度來創建
	size = ALIGN(size, BYTES_PER_WORD);

	// SLAB_RED_ZONE 檢查是否溢出,實現調試功能
	if (flags & SLAB_RED_ZONE) {
		ralign = REDZONE_ALIGN;
		size = ALIGN(size, REDZONE_ALIGN);
	}

	/* 3) caller mandated alignment */
	// 調用方強制對齊
	if (ralign < cachep->align) {
		ralign = cachep->align;
	}
	...
	 * 4) Store it.
	 */
	cachep->align = ralign;
	// colour_off 表示一個著色區的長度,它和 L1 高速緩存行大小相同
	cachep->colour_off = cache_line_size();
	/* Offset must be a multiple of the alignment. */
	if (cachep->colour_off < cachep->align)
		cachep->colour_off = cachep->align;

	// 枚舉類型 slab_state 用來表示 slab 系統中的狀態,如 DOWN、PARTIAL、PARTIAL_NODE、UP 和 FULL 等。當 slab 機制完全初始化完成后狀態變成 FULL
	// slab_is_available() 表示當 slab 分配器處于 UP 或者 FULL 狀態時,分配掩碼可以使用 GFP_KERNEL;否則,只能使用 GFP_NOWAIT
	if (slab_is_available())
		gfp = GFP_KERNEL;
	else
		gfp = GFP_NOWAIT;

...

	// slab 對象的大小按照 cachep->align 大小來對齊
	size = ALIGN(size, cachep->align);
	
    ...

	// 若數組 freelist 小于一個 slab 對象的大小并且沒有指定構造函數,那么 slab 分配器就可以采用 OBJFREELIST_SLAB 模式
	if (set_objfreelist_slab_cache(cachep, size, flags)) {
		flags |= CFLGS_OBJFREELIST_SLAB;
		goto done;
	}

	// 若一個 slab 分配器的剩余空間小于 freelist 數組的大小,那么使用 OFF_SLAB 模式
	if (set_off_slab_cache(cachep, size, flags)) {
		flags |= CFLGS_OFF_SLAB;
		goto done;
	}

	// 若一個 slab 分配器的剩余空間大于 slab 管理數組大小,那么使用正常模式
	if (set_on_slab_cache(cachep, size, flags))
		goto done;

	return -E2BIG;

done:
	// freelist_size 表示一個 slab 分配器中管理區————freelist 大小
	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_RECLAIM_ACCOUNT)
		cachep->allocflags |= __GFP_RECLAIMABLE;
	// size 表示一個 slab 對象的大小
	cachep->size = size;
	cachep->reciprocal_buffer_size = reciprocal_value(size);

...

	// 繼續配置 slab 描述符
	err = setup_cpu_cache(cachep, gfp);
	if (err) {
		__kmem_cache_release(cachep);
		return err;
	}

	return 0;
}

5.2 分配內存

通過kmem_cache_alloc從已創建的 slab 緩存中分配內存。首先會從每個 CPU 的本地對象緩存池(array_cache)中獲取對象,如果本地緩存為空,則從共享緩存或其他列表中獲取。如果所有列表中都沒有空閑對象,則會調用cache_grow函數創建新的 slab。kmem_cache_alloc() 函數的流程圖如下所示:

圖片圖片

為了使讀者有更真切的理解,下文將根據流程圖圍繞源代碼進行講解這個過程:kmem_cache_alloc->slab_alloc

// slab_alloc() 函數在 slab 對象分配過程中是全程關閉本地中斷的
static __always_inline void *
slab_alloc(struct kmem_cache *cachep, gfp_t flags, unsigned long caller)
{
	...
    local_irq_save(save_flags);
	// 獲取 slab 對象
	objp = __do_cache_alloc(cachep, flags);
	local_irq_restore(save_flags);
	...

	// 如果分配時設置了 __GFP_ZERO 標志位,那么使用 memset() 把 slab 對象的內容清零
	if (unlikely(flags & __GFP_ZERO) && objp)
		memset(objp, 0, cachep->object_size);

	slab_post_alloc_hook(cachep, flags, 1, &objp);
	return objp;
}

kmem_cache_alloc->slab_alloc->...->____cache_alloc

// 獲取 slab 對象
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
	void *objp;
	struct array_cache *ac;

	check_irq_off();

	// 獲取 slab 描述符 cachep 中的本地對象緩沖池 ac
	ac = cpu_cache_get(cachep);
	// 判斷本地對象緩沖池中有沒有空閑的對象
	if (likely(ac->avail)) {
		ac->touched = 1;
		// 獲取 slab 對象
		objp = ac->entry[--ac->avail];

		STATS_INC_ALLOCHIT(cachep);
		goto out;
	}

	STATS_INC_ALLOCMISS(cachep);
	// 第一次分配緩存對象時 ac->avail 值為 0,所以它應該在 cache_alloc_refill() 函數中
	objp = cache_alloc_refill(cachep, flags);
	...
    
	return objp;
}

kmem_cache_alloc->slab_alloc->__do_cache_alloc->____cache_alloc->cache_alloc_refill

static void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags)
{
	...
	
	// 獲取本地對象緩沖池 ac
	ac = cpu_cache_get(cachep);
	...
	// 獲取 slab 節點
	n = get_node(cachep, node);

	BUG_ON(ac->avail > 0 || !n);
	// shared 表示共享對象緩沖池
	shared = READ_ONCE(n->shared);
	// 若 slab 節點沒有空閑對象并且共享對象緩沖池 shared 為空或者共享對象緩沖池里也沒有空閑對象,那么直接跳轉到 direct_grow 標簽處
	if (!n->free_objects && (!shared || !shared->avail))
		goto direct_grow;

	...
	
	// 若共享對象緩沖池里有空閑對象,那么嘗試遷移 batchcount 個空閑對象到本地對象緩沖池 ac 中
	// transfer_objects() 函數用于從共享對象緩沖池遷移空閑對象到本地對象緩沖池
	if (shared && transfer_objects(ac, shared, batchcount)) {
		shared->touched = 1;
		goto alloc_done;
	}

	while (batchcount > 0) {
		/* Get slab alloc is to come from. */
		// 如果共享對象緩沖池中沒有空閑對象,那么 get_first_slab() 函數會查看 slab 節點中的 slabs_partial 鏈表和 slabs_free 鏈表
		page = get_first_slab(n, false);
		if (!page)
			goto must_grow;

		check_spinlock_acquired(cachep);

		// 從 slab 分配器中遷移 batchcount 個空閑對象到本地對象緩沖池中
		batchcount = alloc_block(cachep, ac, page, batchcount);
		fixup_slab_list(cachep, n, page, &list);
	}

must_grow:
	// 更新 slab 節點中的 free_objects 計數值
	n->free_objects -= ac->avail;
alloc_done:
	spin_unlock(&n->list_lock);
	fixup_objfreelist_debug(cachep, &list);

// 表示 slab 節點沒有空閑對象并且共享對象緩沖池中也沒有空閑對象,這說明整個內存節點里沒有 slab 空閑對象
// 這種情況下只能重新分配 slab 分配器,這就是一開始初始化和配置 slab 描述符的情景
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 分配器
		page = 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 && page)
			// 從剛分配的 slab 分配器的空閑對象中遷移 batchcount 個空閑對象到本地對象緩沖池中
			alloc_block(cachep, ac, page, batchcount);
		// 把剛分配的 slab 分配器添加到合適的隊列中,這個場景下應該添加到 slabs_partial 鏈表中
		cache_grow_end(cachep, page);

		if (!ac->avail)
			return NULL;
	}
	// 設置本地對象緩沖池的 touched 為 1,表示剛剛使用過本地對象緩沖池
	ac->touched = 1;

	// 返回一個空閑對象
	return ac->entry[--ac->avail];
}

5.3 釋放內存

使用kmem_cache_free釋放內存。如果 per-CPU 緩存中的對象數目低于允許的限制,則在其中存儲一個指向緩存中對象的指針。否則,將一些對象從緩存移回 slab,并將剩余的對象向數組起始處移動。如果在刪除之后,slab 中的所有對象都是未使用的,且緩存中空閑對象的數目超過預定義的限制,則使用slab_destroy將整個 slab 返回給伙伴系統。如果 slab 同時包含使用和未使用對象,則插入到slabs_partial鏈表。如果要銷毀只包含未使用對象的一個緩存,則必須調用kmem_cache_destroy函數。該流程如下所示:

圖片圖片

為了使讀者有更真切的理解,下文將根據流程圖圍繞源代碼進行講解這個過程:kmem_cache_free->__cache_free->___cache_free

void ___cache_free(struct kmem_cache *cachep, void *objp,
		unsigned long caller)
{
	struct array_cache *ac = cpu_cache_get(cachep);
	...
	// 當本地對象緩沖池的空閑對象數量 ac->avail 大于或等于 ac->limit 閾值時,就會調用 cache_flusharray() 做刷新動作,嘗試回收空閑對象
	if (ac->avail < ac->limit) {
		STATS_INC_FREEHIT(cachep);
	} else {
		STATS_INC_FREEMISS(cachep);
		// 主要用于回收 slab 分配器
		cache_flusharray(cachep, ac);
	}
	...
	// 把對象釋放到本地對象緩沖池 ac 中
	ac->entry[ac->avail++] = objp;
}

六、重要字段解析

name(緩存名稱)

表示 slab 緩存的名稱,存儲相同類型和大小的對象。例如 /proc/slabinfo 文件中的kmem_cache字段表示緩存的對象名,如task_struct。每個 slab 緩存存儲相同類型和大小的對象,例如kmalloc-32是用于分配 32 字節的內存塊。

active_objs(活動對象數)

當前在使用的對象數量。該字段表示當前在使用的(已分配的)對象數量,即系統中實際分配給內核使用的對象數量。例如,在系統中實際分配給內核使用的進程控制塊等對象的數量就是活動對象數。

num_objs(總對象數)

緩存中分配的對象總數。這個字段表示緩存中分配的對象總數,包括已經分配和空閑的對象。這個值通常大于或等于active_objs。比如一個特定大小的 slab 緩存中,所有已經分配出去和尚未分配但已準備好的對象總數就是num_objs。

objsize(對象大小)

每個對象的大小。該字段表示每個對象的大小(以字節為單位),即 slab 緩存中每個對象占用的內存空間大小。例如對于特定的內核數據結構,其對象大小可以通過這個字段確定。

objperslab(每個 slab 包含的對象數)

每個 slab 中包含的對象數量。每個 slab 是一個較大的內存塊,其中包含多個對象,這個字段表示每個 slab 中具體包含的對象數量。例如對于一個特定大小的 slab,根據對象大小和 slab 總大小,可以計算出objperslab的值。

pagesperslab(每個 slab 使用的頁數)

每個 slab 使用的頁數,通常為 4KB 大小。該字段表示每個 slab 使用的頁數。Linux 內核使用分頁機制來管理內存,頁面通常為 4KB 大小。一個 slab 由一定數量的連續物理頁組成,這個字段反映了每個 slab 占用的物理頁數量。

tunables(可調參數)

控制 slab 緩存的行為,如limit、batchcount、sharedfactor等。這個字段中的值控制 slab 緩存的行為:

  • limit:slab 緩存中每個 CPU 可以緩存的最大對象數量。
  • batchcount:每次從全局緩存到 CPU 本地緩存中批量獲取的對象數。
  • sharedfactor:控制多個 CPU 是否共享 slab 緩存。

slabdata(slab 統計信息)

包含有關 slab 使用的統計數據,如active_slabs、num_slabs、sharedavail等。這部分包含有關 slab 使用的統計數據:

  • active_slabs:當前正在使用的 slab 數量。
  • num_slabs:系統中分配的總 slab 數量。
  • sharedavail:CPU 本地緩存中可用對象的數量。通過這些統計數據,管理員可以了解系統中 slab 分配器的內存使用情況,幫助調優系統性能。

七、高速緩存分類

7.1 普通高速緩存

普通高速緩存為 kmem_cache結構本身和內核提供通用高速緩存。它通過通用高速緩存實現小塊連續內存的分配,為內核提供了一種高效的內存管理方式。

普通高速緩存首先會為 kmem_cache結構本身提供高速緩存,這類緩存保存在 cache_cache變量中。cache_cache變量代表著 cache_chain 鏈表中的第一個元素,它保存著對高速緩存描述符的高速緩存。

通用高速緩存所提供的對象具有幾何分布的大小,范圍為 32 到 131072 字節。內核中提供了 kmalloc()和 kfree()兩個接口分別進行內存的申請和釋放。

7.2 專用高速緩存

專用高速緩存是根據內核所需,通過指定具體的對象而創建。它提供一套完整的接口用于高速緩存的申請、釋放以及 slab 的申請和釋放。

內核為專用高速緩存的申請和釋放提供了接口,kmem_cache_create()用于對一個指定的對象創建高速緩存。它從 cache_cache普通高速緩存中為新的專有緩存分配一個高速緩存描述符,并把這個描述符插入到高速緩存描述符形成的 cache_chain鏈表中。kmem_cache_destory()用于撤銷一個高速緩存,并將它從 cache_chain鏈表上刪除。

對于 slab 的申請和釋放,kmem_cache_alloc()在其參數所指定的高速緩存中分配一個 slab。相反,kmem_cache_free()在其參數所指定的高速緩存中釋放一個 slab。

八、應用案例分析

8.1 定義和使用特定大小的對象

以下以專用 slab 緩存為例,展示如何定義和使用特定大小的對象。

首先,我們需要為特定的結構體創建一個專用的 slab 緩存。假設我們有一個名為sample_struct的結構體:

struct sample_struct {
    int id;
    char name[20];
    char address[50];
};

我們可以使用kmem_cache_create函數來創建一個用于存儲sample_struct結構體的專用 slab 緩存:

static struct kmem_cache *sample_struct_cachep;
static void init_sample_struct_cache( void ){
    sample_struct_cachep = kmem_cache_create(
        "sample_struct_cachep",  /* Name */
        sizeof(struct sample_struct), /* Object Size */
        0,     /* Alignment */
        SLAB_HWCACHE_ALIGN,  /* Flags */
        NULL);  /* Constructor */
    return;
}

這里創建的特定緩存包含sample_struct大小的對象,并且是硬件緩存對齊的(由標志參數SLAB_HWCACHE_ALIGN定義)。

使用所分配的 slab 緩存對象可以通過以下方式進行:

int slab_test( void ){
    struct sample_struct *object;
    printk( "Cache name is %s\n", kmem_cache_name( sample_struct_cachep ) );
    printk( "Cache object size is %d\n", kmem_cache_size( sample_struct_cachep ) );
    object = kmem_cache_alloc(sample_struct_cachep, GFP_KERNEL);
    if (object) {
        // 使用 object...
        kmem_cache_free(sample_struct_cachep, object);
    }
    return 0;
}

8.2 銷毀緩存

調用者必須確保在執行銷毀操作過程中,不要從緩存中分配對象。銷毀緩存的主要步驟如下:

  • 將緩存從cache_chain鏈表中刪除。
  • 將本地高速緩存、alien 高速緩存和共享本地高速緩存中的對象都釋放回 slab,并釋放所有的 free 鏈表,然后判斷 full 鏈表以及 partial 鏈表是否都為空,如果有一個不為空說明存在非空閑 slab,也就是說有對象還未釋放,此時無法銷毀緩存,重新將緩存添加到cache_chain鏈表中。
  • 確定所有的 slab 都為空閑狀態后,將緩存涉及到的所有描述符都釋放(這些描述符都是保存在普通高速緩存中的)。

負責銷毀緩存的函數為kmem_cache_destroy,其代碼實現如下:

void kmem_cache_destroy(struct kmem_cache *cachep){
    BUG_ON(!cachep || in_interrupt());
    /* Find the cache in the chain of caches. */
    get_online_cpus();
    mutex_lock(&cache_chain_mutex);
    /** the chain is never empty, cache_cache is never destroyed*//*將 cache 從 cache_chain 中刪除*/
    list_del(&cachep->next);
    /*釋放完 free 鏈表,如果 FULL 鏈表或 partial 鏈表中還有 slab,說明還有對象處于分配狀態因此不能銷毀該緩存!*/
    if (__cache_shrink(cachep)) {
        slab_error(cachep, "Can't free all objects");
        /*重新將緩存添加到 cache_chain 鏈表中*/
        list_add(&cachep->next, &cache_chain);
        mutex_unlock(&cache_chain_mutex);
        put_online_cpus();
        return;
    }
    if (unlikely(cachep->flags & SLAB_DESTROY_BY_RCU))
        rcu_barrier();
    /*釋放 cache 所涉及到的各個描述符的存儲對象*/
    __kmem_cache_destroy(cachep);
    mutex_unlock(&cache_chain_mutex);
    put_online_cpus();
}

static int __cache_shrink(struct kmem_cache *cachep){
    int ret = 0, i = 0;
    struct kmem_list3 *l3;
    /*將本地高速緩存,share 本地高速緩存以及 alien 高速緩存的空閑對象釋放 slab*/
    drain_cpu_caches(cachep);
    check_irq_on();
    for_each_online_node(i) {
        l3 = cachep->nodelists[i];
        if (!l3) continue;
        /*銷毀空閑鏈表中的 slab*/
        drain_freelist(cachep, l3, l3->free_objects);
        /*判斷 full 和 partial 是否為空,有一個不為空則 ret 就為 1*/
        ret +=!list_empty(&l3->slabs_full) ||!list_empty(&l3->slabs_partial);
    }
    return (ret? 1 : 0);
}

drain_cpu_caches()的最終落腳在 free_block()函數上,該函數在前面已做過分析,在此不再列出。

static int drain_freelist(struct kmem_cache *cache, struct kmem_list3 *l3, int tofree){
    struct list_head *p;
    int nr_freed;
    struct slab *slabp;
    nr_freed = 0;
    /*slab 中的對象還未釋放完并且 free 鏈表不為空*/
    while (nr_freed < tofree &&!list_empty(&l3->slabs_free)) {
        spin_lock_irq(&l3->list_lock);
        p = l3->slabs_free.prev;
        if (p == &l3->slabs_free) {/*鏈表中已無元素*/
            spin_unlock_irq(&l3->list_lock);
            goto out;
        }
        /*從 free 鏈表中取出一個 slab*/
        slabp = list_entry(p, struct slab, list);
#if DEBUG
        BUG_ON(slabp->inuse);
#endif
        /*從鏈表中刪除*/
        list_del(&slabp->list);
        /** Safe to drop the lock. The slab is no longer linked* to the cache.*//*空閑對象數量總數減去 num*/
        l3->free_objects -= cache->num;
        spin_unlock_irq(&l3->list_lock);
        /*銷毀 slab*/
        slab_destroy(cache, slabp);
        nr_freed++;
    }
out:
    return nr_freed;
}

slab_destroy()函數已在前文中分析。

static void __kmem_cache_destroy(struct kmem_cache *cachep){
    int i;
    struct kmem_list3 *l3;
    /*釋放存儲本地高速緩存描述符的對象*/
    for_each_online_cpu(i)
        kfree(cachep->array[i]);
    /* NUMA: free the list3 structures */
    for_each_online_node(i) {
        l3 = cachep->nodelists[i];
        if (l3) {
            /*釋放存儲共享本地高速緩存描述符的對象*/
            kfree(l3->shared);
            /*釋放存儲 alien 本地高速緩存描述符的對象*/
            free_alien_cache(l3->alien);
            /*釋放存儲 kmem_list3 描述符的對象*/
            kfree(l3);
        }
        /*釋放存儲緩存描述符的對象*/
        kmem_cache_free(&cache_cache, cachep);
    }}


責任編輯:武曉燕 來源: 深度Linux
相關推薦

2021-08-03 09:02:58

LinuxSlab算法

2009-12-25 15:34:54

slab分配器

2025-04-11 00:44:00

2013-10-12 11:15:09

Linux運維內存管理

2017-01-20 14:21:35

內存分配器存儲

2017-02-08 08:40:21

C++固定內存塊

2017-01-17 16:17:48

C++固定分配器

2024-10-11 10:00:20

2023-04-03 08:25:02

Linux內存slub

2025-05-27 02:45:45

2020-12-15 08:54:06

Linux內存碎片化

2025-07-30 01:27:00

2020-03-11 13:44:20

編程語言PythonJava

2013-10-14 10:41:41

分配器buddy syste

2025-02-10 07:30:00

malloc內存分配器內存

2023-12-22 07:55:38

Go語言分配策略

2014-09-01 10:09:44

Linux

2013-10-11 17:24:47

Linux運維內存管理

2022-02-23 16:49:19

Linux內存數據結構

2023-04-13 14:42:26

PoE供電器PoE交換機
點贊
收藏

51CTO技術棧公眾號

日韩电影网在线| 五月天丁香久久| 91成人理论电影| 日本天堂网在线观看| 亚洲精品进入| 欧美电影一区二区| a级黄色一级片| 中文日本在线观看| 成人精品电影在线观看| 国产精品久久久久久亚洲调教 | 欧美在线视频导航| 91麻豆精品久久毛片一级| 成人免费直播在线| 欧美亚洲国产一区二区三区| 成人性做爰片免费视频| 亚欧洲精品视频| 国产一区二区三区av电影| 97色在线观看| 午夜精品一区二区三区视频| 国产99久久久国产精品成人免费 | 亚洲免费观看高清完整版在线| 精品无人乱码一区二区三区的优势| 亚洲无码精品国产| 久久国产主播| 欧美精品国产精品日韩精品| 人人爽人人爽人人片| 农村少妇一区二区三区四区五区 | 中文字幕线观看| 秋霞伦理一区| 亚洲一区二区三区小说| 亚洲三级一区| 电影在线一区| 久久色在线观看| 国产精品视频在线免费观看| 96日本xxxxxⅹxxx17| 噜噜噜在线观看免费视频日韩| 久久99精品久久久久久琪琪| 国产小视频你懂的| 第一sis亚洲原创| 国产亚洲a∨片在线观看| 亚洲一区二区在线免费| 一区二区在线视频观看| 日韩美女在线视频| 亚洲综合伊人久久| 国产成人免费视频网站视频社区| 欧美四级电影在线观看| aaa毛片在线观看| 小视频免费在线观看| 欧美日韩日本国产| 国产成人a亚洲精v品无码| 2020av在线| 香蕉成人伊视频在线观看| av网站手机在线观看| 懂色av一区| 亚洲成人免费影院| 日本a视频在线观看| 丰满诱人av在线播放| 亚洲成精国产精品女| 日韩精品一区二区免费| eeuss鲁一区二区三区| 亚洲成av人片一区二区梦乃| 阿v天堂2017| 筱崎爱全乳无删减在线观看| 色国产综合视频| 91视频免费版污| 九七电影院97理论片久久tvb| 欧美久久免费观看| 成人在线短视频| av综合网页| 精品偷拍一区二区三区在线看| 97人妻精品一区二区三区免| 精品国产一区二区三区噜噜噜 | 日韩一级特黄| 91超碰这里只有精品国产| 福利视频999| 99这里只有精品视频| 日韩av在线免费播放| 少妇真人直播免费视频| 97久久视频| 欧美激情一二三| 国产精品久久久久久久久久久久久久久久久| 国产一区二区你懂的| 国产欧美久久久精品影院| 成人国产精品色哟哟| 国产强伦人妻毛片| 99天天综合性| 日韩精品成人一区二区在线观看| 天堂中文8资源在线8| 亚洲老司机在线| 欧美视频第一区| 国产精品1区| 日韩国产精品视频| 日日操免费视频| 亚洲黄色大片| 成人精品视频在线| 天天干天天操av| 国产精品福利一区| 丰满少妇久久久| 狠狠久久综合| 日韩国产欧美精品一区二区三区| 秋霞网一区二区三区| 国内成人在线| 国产精品男人的天堂| www.爱爱.com| 欧美经典三级视频一区二区三区| 欧美这里只有精品| 日本少妇一区| 亚洲国产成人爱av在线播放| 美国精品一区二区| 久久国产福利| 国产伦精品一区二区三区在线 | 中文字字幕在线中文乱码| 国产精品一区三区| 日韩午夜视频在线观看| 9999热视频在线观看| 欧美高清视频不卡网| 日韩中文字幕电影| 亚洲精品专区| 亚洲自拍偷拍色片视频| 成人全视频高清免费观看| 香蕉成人伊视频在线观看| 亚洲精品在线网址| 色乱码一区二区三区网站| 欧美自拍视频在线| 黄色片一区二区| 亚洲三级久久久| 激情五月婷婷久久| 一区二区三区视频免费观看| 久久久人成影片一区二区三区| 999久久久久久| 中文字幕一区二区三区不卡在线| 精品中文字幕av| 国产乱人伦精品一区| 大胆人体色综合| 国产又黄又粗又长| 中文字幕不卡一区| 日本熟妇人妻中出| 欧美激情在线免费| 日本伊人精品一区二区三区介绍| 色窝窝无码一区二区三区| 洋洋成人永久网站入口| 北条麻妃亚洲一区| 影视一区二区| caoporn国产精品免费公开| 久久精品视频观看| 欧美精品乱码久久久久久按摩| 精品国产aaa| 日本欧美一区二区三区乱码| 日韩精品在在线一区二区中文| 偷拍视频一区二区三区| 亚洲美女www午夜| 日韩综合在线观看| 久久日韩粉嫩一区二区三区 | 亚洲十八**毛片| 亚洲国产一区二区三区四区| 91精品国产乱码在线观看| 99re亚洲国产精品| 久久久久狠狠高潮亚洲精品| 要久久电视剧全集免费| 日产精品99久久久久久| 国产视频在线看| 欧洲精品在线观看| 九九热视频在线免费观看| 精品一区二区三区免费播放| 懂色av粉嫩av蜜臀av| 亚洲精品视频一二三区| 97久久精品人搡人人玩| 暖暖视频在线免费观看| 在线亚洲一区二区| 久久精品日韩无码| 国产福利91精品| 亚洲精品少妇一区二区| 国产一区二区三区亚洲| 国产v综合ⅴ日韩v欧美大片| 99青草视频在线播放视| 欧美一区二区人人喊爽| 日本亚洲欧美在线| 久久久高清一区二区三区| 亚欧美在线观看| 欧美精品色网| 欧美激情第一页在线观看| 四虎在线精品| 久久久免费观看视频| 国产大学生校花援交在线播放| 欧美狂野另类xxxxoooo| 精品无码免费视频| 国产欧美视频一区二区| 中文字幕第六页| 午夜在线观看免费一区| 一区精品在线| 国产一区福利| 成人精品一区二区三区| 蜜桃在线视频| 久久久久999| 桃花色综合影院| 欧美日韩国产乱码电影| 久久狠狠高潮亚洲精品| 国产精品不卡在线| 一级性生活大片| 国产麻豆日韩欧美久久| 韩国一区二区av| 欧美激情视频一区二区三区在线播放 | 欧美sss在线视频| 国产精品视频专区| 欧亚在线中文字幕免费| 欧美成年人在线观看| 国产小视频在线| 亚洲电影免费观看| 中文字幕一区二区三区波野结| 亚洲成人av福利| 国产精品精品软件男同| 久久美女艺术照精彩视频福利播放| 精品人妻一区二区三| 秋霞影院一区二区| 国产男女无遮挡| 日韩亚洲国产欧美| 欧美与动交zoz0z| 日韩精品看片| 日本一区视频在线观看| 久久久免费毛片| 99久久久精品免费观看国产| 欧美成人毛片| 国产精品久久久久高潮| 亚洲女同志freevdieo| 久久免费国产视频| 日本动漫理论片在线观看网站| 最近2019中文字幕mv免费看| 男人的天堂av高清在线| 亚洲精品国精品久久99热| 精品人妻av一区二区三区| 欧美精品vⅰdeose4hd| а中文在线天堂| 在线观看日韩一区| 尤物视频免费观看| 欧美性猛xxx| 秋霞精品一区二区三区| 亚洲成a人片在线观看中文| 久久精品一级片| 樱花草国产18久久久久| 三级黄色在线观看| 中文字幕一区二区三区在线不卡| 任你操精品视频| 国产精品美女一区二区| 亚洲一二三四视频| 国产精品久久久久影院色老大| 国产高潮呻吟久久| 国产欧美1区2区3区| 精品人妻中文无码av在线| 久久久久久97三级| 成人性视频免费看| 亚洲天堂成人网| 草视频在线观看| 亚洲欧美一区二区三区孕妇| 免费在线观看国产精品| 亚洲午夜三级在线| 好吊妞视频一区二区三区| 日韩欧美极品在线观看| 99re热视频| 欧美丰满高潮xxxx喷水动漫| 亚洲成人黄色片| 日韩精品福利在线| 精品av中文字幕在线毛片| 一区二区欧美日韩视频| 欧美性videos| 久久久久久亚洲精品不卡| 蜜桃视频www网站在线观看| 国产精品国语对白| 天堂久久一区| av一区二区三区免费| 亚洲品质自拍| 99re99热| 在线综合亚洲| 亚洲欧美自拍另类日韩| 国产成人综合亚洲网站| 熟女俱乐部一区二区视频在线| 亚洲国产成人一区二区三区| 麻豆视频在线免费看| 红桃av永久久久| 中文字幕网址在线| 日韩精品专区在线影院重磅| 嫩草研究院在线观看| 爱福利视频一区| h片在线观看| 国产精品久久久久久久久免费看 | 国产精品丝袜一区二区| 亚洲风情在线资源站| 特级西西444www高清大视频| 日韩欧美精品三级| 国产小视频免费在线观看| 裸体女人亚洲精品一区| 9i看片成人免费高清| 91在线免费观看网站| 亚洲香蕉视频| 可以免费看的黄色网址| 视频一区在线播放| 亚洲天堂小视频| 欧美国产乱子伦| 国产无码精品视频| 欧美日韩黄色一区二区| 天堂av在线播放| 欧美日本亚洲视频| 国产一区二区色噜噜| 精品欧美国产| 午夜欧美视频| 免费一区二区三区在线观看| 91影院在线免费观看| 欧美三根一起进三p| 欧美性欧美巨大黑白大战| 日本高清视频免费观看| 久久久精品国产| 亚洲第一影院| 国产原创精品| 欧美日韩视频| 精品综合久久久久| 久久久精品国产99久久精品芒果| 国产精品111| 欧美一区二区日韩| 免费黄色电影在线观看| 国产精品久久久久av免费| 午夜欧洲一区| www插插插无码视频网站| 国产伦精品一区二区三区视频青涩 | 亚洲国产成人自拍| 免费看一级视频| 亚洲精品久久久久久久久久久久久 | 韩国日本不卡在线| 国产精品一区免费在线| 亚洲国产一区二区精品视频| 久久国产免费| 免费看黄色的视频| 色哟哟国产精品| 免费在线毛片| 日本久久久久久久久久久| 美女呻吟一区| 欧美牲交a欧美牲交| 99国产精品国产精品久久| 精品在线视频免费| 精品人在线二区三区| 日韩av激情| 国产精品日韩欧美一区二区| 国产精品www.| av漫画在线观看| 亚洲aⅴ怡春院| 五月婷婷在线播放| 欧美性受xxx| 久久93精品国产91久久综合| 99久久国产宗和精品1上映| 日本一区二区三区国色天香| 中文字幕在线网址| 日韩在线视频导航| 国产精品亚洲综合在线观看| 粉嫩av一区二区三区天美传媒 | 国产在线观看免费视频今夜| 精品国产一二三区| 国产盗摄——sm在线视频| 久久久久一区二区三区| 老司机免费视频久久| 亚洲天堂av中文字幕| 欧美精品在欧美一区二区少妇| 国产福利在线播放麻豆| 99三级在线| 国产精品色网| 伊人影院综合网| 欧美一区二区三区四区视频| 先锋成人av| 裸模一区二区三区免费| 日韩精品国产欧美| 在线看的片片片免费| 精品国产乱码久久久久久图片 | 国产精品原创| 欧美大陆一区二区| 奇米在线7777在线精品| 国产盗摄一区二区三区在线| 亚洲国产精品999| 欧美特黄aaaaaaaa大片| 性做爰过程免费播放| av电影在线观看一区| 一区二区视频网站| 久久久久久国产精品美女| 一道本一区二区三区| 亚洲综合20p| 色综合婷婷久久| 91麻豆免费在线视频| 美女一区视频| 国产伦精一区二区三区| 依依成人综合网| 欧美理论电影在线播放| 免费视频国产一区| 在线视频日韩欧美| 色综合久久久久网| 亚洲丝袜一区| 四虎影院一区二区三区 | 欧美日本在线播放| 欧美伦理91| 妞干网这里只有精品| 久久蜜桃一区二区| 亚洲黄色精品视频| 国产美女主播一区| 国产色综合网| 国产一级片免费观看|