国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

[linux kernel]slub內(nèi)存管理分析(5) kfree

這篇具有很好參考價值的文章主要介紹了[linux kernel]slub內(nèi)存管理分析(5) kfree。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

背景

省流

如果對代碼細節(jié)不感興趣,可以直接跳轉底部內(nèi)存釋放邏輯總結。

前情回顧

關于slab幾個結構體的關系和初始化和內(nèi)存分配的邏輯請見:

[linux kernel]slub內(nèi)存管理分析(0) 導讀

[linux kernel]slub內(nèi)存管理分析(1) 結構體

[linux kernel]slub內(nèi)存管理分析(2) 初始化

[linux kernel]slub內(nèi)存管理分析(2.5) slab重用

[linux kernel]slub內(nèi)存管理分析(3) kmalloc

[linux kernel]slub內(nèi)存管理分析(4) 細節(jié)操作以及安全加固

描述方法約定

PS:為了方便描述,這里我們將一個用來切割分配內(nèi)存的page 稱為一個slab page,而struct kmem_cache我們這里稱為slab管理結構,它管理的真?zhèn)€slab 體系成為slab cachestruct kmem_cache_node這里就叫node。單個堆塊稱為object或者堆塊內(nèi)存對象

kfree 操作總覽

簡介

kfree 是用來回收kmalloc 分配的內(nèi)存的函數(shù),這里詳細分析流程。

kfree之中同樣有很多設計cpu搶占、NUMA架構node相關的邏輯,這里不詳細分析了,因為我們分析的目的是為了搞內(nèi)核安全,而寫漏洞利用的時候可以通過將程序綁定在一個cpu 來避免涉及這些邏輯的代碼發(fā)生。

還有一些和Kasan、slub_debug 等檢測內(nèi)存越界等的操作,也不過多分析。

邏輯圖預覽

釋放邏輯

[linux kernel]slub內(nèi)存管理分析(5) kfree

slab page各個狀態(tài)轉化

[linux kernel]slub內(nèi)存管理分析(5) kfree

調(diào)用棧

  • kfree
    • __free_pages 釋放頁面(大塊)
    • slab_free 釋放slab 的內(nèi)存
      • slab_free_freelist_hook
      • do_slab_free 入口,和cpu_slab快速釋放
        • memcg_slab_free_hook memcg相關計數(shù)操作
        • __slab_free 慢速釋放
          • kfence_freekmem_cache_debug、free_debug_processing 檢查
          • 釋放邏輯
          • put_cpu_partial
            • unfreeze_partials
          • discard_slab slab為空則銷毀該slab page
            • free_slab->__free_slab

詳細分析

kfree

kfree總入口:

linux\mm\slub.c : kfree

void kfree(const void *x)
{
	struct page *page;
	void *object = (void *)x;

	trace_kfree(_RET_IP_, x);

	if (unlikely(ZERO_OR_NULL_PTR(x)))
		return;

	page = virt_to_head_page(x);//[1]根據(jù)地址找到對應的page結構體
	if (unlikely(!PageSlab(page))) {//[2]如果該page 不是slab,那么就是大塊內(nèi)存了,調(diào)用free_page釋放
		unsigned int order = compound_order(page);

		BUG_ON(!PageCompound(page));
		kfree_hook(object);
		mod_lruvec_page_state(page, NR_SLAB_UNRECLAIMABLE_B,
				      -(PAGE_SIZE << order));
		__free_pages(page, order);
		return;
	}
	slab_free(page->slab_cache, page, object, NULL, 1, _RET_IP_);//[3]釋放slab 分配的內(nèi)存塊
}

[1] 調(diào)用kfree 的都是要釋放的內(nèi)存地址,所以首先要根據(jù)內(nèi)存虛擬地址找到它所在頁的頁結構體。

[2] 判斷該頁是不是slab page,如果不是,說明這個要釋放的內(nèi)存不是slab切割分配的,而是一個大內(nèi)存塊,當初是直接分配的頁,所以這里頁調(diào)用__free_pages來釋放內(nèi)存頁。

[3] 否則說明這個內(nèi)存塊是slab page分配的內(nèi)存塊,進入slab 的釋放流程slab_free。參數(shù)是通過page 獲得page 的slab管理結構體kmem_cache。

slab_free

釋放slab 內(nèi)存的入口slab_free

linux\mm\slub.c : slab_free

static __always_inline void slab_free(struct kmem_cache *s, struct page *page,
				      void *head, void *tail, int cnt,
				      unsigned long addr)
{
	if (slab_free_freelist_hook(s, &head, &tail))
		do_slab_free(s, page, head, tail, cnt, addr);//[1]調(diào)用do_slab_free
}

[1] slab_free_freelist_hook 是些kasan 的東西沒啥用,這里直接調(diào)用了do_slab_free,傳入?yún)?shù)有headtail,由于slab_free設計上可以釋放多個內(nèi)存塊,所以這里headtail 和釋放多個內(nèi)存塊相關,如果只釋放一個的話,不影響。

linux\mm\slub.c : do_slab_free

static __always_inline void do_slab_free(struct kmem_cache *s,
				struct page *page, void *head, void *tail,
				int cnt, unsigned long addr)
{
	void *tail_obj = tail ? : head;//[1]獲得尾部,這里tail 傳入是0, 所以tail_obj 就是head
	struct kmem_cache_cpu *c;
	unsigned long tid;

	memcg_slab_free_hook(s, &head, 1);//[2]memcg 相關
redo:
	/*
	 * Determine the currently cpus per cpu slab.
	 * The cpu may change afterward. However that does not matter since
	 * data is retrieved via this pointer. If we are on the same cpu
	 * during the cmpxchg then the free will succeed.
	 */
	do {//[3]獲取cpu_slab
		tid = this_cpu_read(s->cpu_slab->tid);
		c = raw_cpu_ptr(s->cpu_slab);
	} while (IS_ENABLED(CONFIG_PREEMPTION) &&
		 unlikely(tid != READ_ONCE(c->tid)));

	/* Same with comment on barrier() in slab_alloc_node() */
	barrier();//同步一下

	if (likely(page == c->page)) {//[4]釋放的對象正好屬于當前cpu_slab正在使用的slab則快速釋放
		void **freelist = READ_ONCE(c->freelist);//[4.1]獲取freelist

		set_freepointer(s, tail_obj, freelist);//[4.2]將新釋放內(nèi)存塊插入freelist 開頭

		if (unlikely(!this_cpu_cmpxchg_double(//[4.3]this_cpu_cmpxchg_double()原子指令操作存放
				s->cpu_slab->freelist, s->cpu_slab->tid,
				freelist, tid,
				head, next_tid(tid)))) {

			note_cmpxchg_failure("slab_free", s, tid);//失敗記錄
			goto redo;
		}
		stat(s, FREE_FASTPATH);
	} else
		__slab_free(s, page, head, tail_obj, cnt, addr);//[5]否則說明釋放的內(nèi)存不是當前cpu_slab立馬能釋放的

}

[1] 獲取一下尾部,由于我們傳入的tail 指針為0,則獲取head指針,也就是要釋放的內(nèi)存地址。換句話說,當只釋放一個內(nèi)存塊的時候,head=tail。

[2] memcg相關,這里不詳細分析。

[3] 跟kmalloc 的時候一樣,獲取一下當前使用cpu的cpu_slab。

[4] 如果要釋放的內(nèi)存塊的所在page正好是當前cpu_slab管理的slab,那么可以快速釋放。

? [4.1] 獲取cpu_slabfreelist

? [4.2] 調(diào)用set_freepointer將要釋放的內(nèi)存塊放到freelist 鏈表的開頭,在前一章分析過set_freepointer了。

? [4.3] 跟kmalloc時候相同的原子操作,這里等價于,也就是更新一下cpu_slab->freelist

freelist = s->cpu_slab->freelist;
s->cpu_slab->freelist=head;
tid=s->cpu_slab->tid;
s->cpu_slab->tid=next_tid(tid)

[5] 到這里說明釋放的內(nèi)存不是當前cpu_slab立馬能釋放的,那么調(diào)用__slab_free慢速分配。

__slab_free

linux\mm\slub.c : __slab_free

static void __slab_free(struct kmem_cache *s, struct page *page,
			void *head, void *tail, int cnt,
			unsigned long addr)

{
	void *prior;
	int was_frozen;
	struct page new;
	unsigned long counters;
	struct kmem_cache_node *n = NULL;
	unsigned long flags;

	stat(s, FREE_SLOWPATH);

	if (kfence_free(head))
		return;

	if (kmem_cache_debug(s) && //這兩個里面就是各種檢測,實際沒有對free環(huán)節(jié)有影響的操作
	    !free_debug_processing(s, page, head, tail, cnt, addr))
		return; 

	do {//[1] 嘗試釋放內(nèi)存塊到所屬page的freelist
		if (unlikely(n)) {//循環(huán)了多次才會走到這,加鎖操作
			spin_unlock_irqrestore(&n->list_lock, flags);
			n = NULL;
		}
		prior = page->freelist;//[1.1]獲取當前page的freelist
		counters = page->counters;//獲取當前page的counters,這是一個聯(lián)合體,包括inuse、frozen等
		set_freepointer(s, tail, prior);//將freelist 接到要釋放內(nèi)存塊的后面*tail=prior
		new.counters = counters;
		was_frozen = new.frozen;
		new.inuse -= cnt;//正在使用內(nèi)存塊減掉要釋放的數(shù)量
		if ((!new.inuse || !prior) && !was_frozen) {//[2]釋放之后全空或釋放之前為滿并且不在cpu_slab中
            //釋放前為滿有2種情況:游離中,在cpu_slab->page中,但走到這里確定不在cpu_slab中
            //調(diào)試模式時可能在node->full中,默認不開啟,不考慮

			if (kmem_cache_has_cpu_partial(s) && !prior) {//[2.1]釋放前為滿(不管你釋放后空不空)
                //并且開啟了cpu_slab->partial
				//走到這可以確定slab page為游離狀態(tài)
				/*
				 * Slab was on no list before and will be
				 * partially empty
				 * We can defer the list move and instead
				 * freeze it.
				 */
				new.frozen = 1;//準備放入cpu_slab中

			} else { /* Needs to be taken off a list *///[2.2]釋放之后就為空slab了

				n = get_node(s, page_to_nid(page));//獲取page所在node
				/*
				 * 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, //[1.2]成功之前一直嘗試
		prior, counters,
		head, new.counters,
		"__slab_free"));
    /* if (page->freelist == prior && page->counters == counters)
     * page->freelist = head
     * page->counters = new.counters 完成釋放
     */

	if (likely(!n)) {//[3]n為空,說明這里釋放之前為滿,或釋放之前之后都半滿

		if (likely(was_frozen)) {//[3.1]原本就在cpu_slab中,啥也不用操作
			/* 走到這里說明該slab page在當前cpu_slab->partial中或其他cpu_slab的page或partial中
			直接釋放到page->freelist即可(已經(jīng)釋放完畢),其他cpu我們管不到
			 * The list lock was not taken therefore no list
			 * activity can be necessary.
			 */
			stat(s, FREE_FROZEN);
		} else if (new.frozen) {//[3.2]釋放之前是滿的也就是游離狀態(tài),放入cpu_slab->partial中
			/*
			 * If we just froze the page then put it onto the
			 * per cpu partial list.
			 */
			put_cpu_partial(s, page, 1);//放到cpu_slab->partial中,這里有一個bug,他沒有考慮node->full的情況
			stat(s, CPU_PARTIAL_FREE);
		}

		return;//[3.3]釋放之前在node->partial中,并且釋放之后也不為空,直接返回啥也不用干
	}

	if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))//[4]釋放之后為空 node 中的partial 滿足最小要求
		goto slab_empty;//去釋放這個page

	/*
	 * Objects left in the slab. If it was not on the partial list before
	 * then add it.
	 */
	if (!kmem_cache_has_cpu_partial(s) && unlikely(!prior)) {//[5]釋放前為full,但沒開啟cpu_slab->partial
		remove_full(s, n, page);//從full 中移除,如果是游離狀態(tài)不在鏈表中會直接返回
		add_partial(n, page, DEACTIVATE_TO_TAIL); //添加到partial中
		stat(s, FREE_ADD_PARTIAL);
	}
	spin_unlock_irqrestore(&n->list_lock, flags);
	return;

slab_empty:
	if (prior) {//[4.1]釋放之前不是full,在partial中
		/*
		 * Slab on the partial list.
		 */
		remove_partial(n, page);//從partial 刪除
		stat(s, FREE_REMOVE_PARTIAL);
	} else {//[4.2]釋放之前在full 中
		/* Slab must be on the full list */
		remove_full(s, n, page);//從full 中刪除
	}

	spin_unlock_irqrestore(&n->list_lock, flags);
	stat(s, FREE_SLAB);
	discard_slab(s, page);//[4]slab為空,則直接刪除整個空slab
}

[1] 嘗試釋放內(nèi)存塊到所屬pagefreelist,由于涉及到鎖的操作/原子操作,這里會一直循環(huán)嘗試直到成功為止

? [1.1] 獲取一下釋放堆塊之前pagefreelist、counters 等信息,counters是一個聯(lián)合體,同時包括pagefrozen(代表page 是在cpu_slab中還是在node中)、inuse(代表正在使用的內(nèi)存數(shù)量)等。然后將page->freelist 拼到準備釋放的內(nèi)存塊后面,也就是新釋放的放到freelist表頭,但目前還沒更新到page,然后計算新inuse等。

? [1.2] 進行原子操作嘗試完成分配,cmpxchg_double_slab 函數(shù)完成的操作等價于如下操作,即將新freelist 頭更新到page中,再更新counters

if (page->freelist == prior && page->counters == counters)
{
     page->freelist = head;
     page->counters = new.counters;
}

目前已經(jīng)將堆塊釋放到page->freelist中了,接下來要對page的種類進行判斷。目前一共有6種(有一種是DEBUG場景)可能:

  • 在當前cpu_slab->partial
  • cpu_slab->page
  • 在其他cpu_slab->partial
  • 游離狀態(tài)(分配滿了),不在任何列表之中
  • 在所屬nodepartial列表中
  • 如果開啟CONFIG_SLUB_DEBUG,則可能會是分配滿的狀態(tài)而在所屬node的full列表中,默認不開啟

[2] !new.inuse代表釋放之后已經(jīng)沒有inuse的內(nèi)存了,說明釋放之后該slab里面全是free 的堆塊,也就是釋放后整個page都空了;!prior說明釋放之前沒有freelist,即釋放之前該slab 為分配滿的slab page;!was_frozen說明釋放之前該slab不在cpu_slab之中。也就是說,當該slab 不在是cpu_slab正在使用的slab 時,如果釋放后為空slab或釋放前為full slab,則說明游離狀態(tài)、node->full中或node->partial中的page滿足該分支條件

? [2.1] 釋放前為full slab,對應游離狀態(tài)和node->full的slab page;kmem_cache_has_cpu_partial判斷是否開啟了cpu_slab->partial;若開啟則new.frozen設置為1,打算將該slab 放入cpu_slab->partial中,因為釋放后就變成了半滿狀態(tài)了,可以使用了。這里為什么放入cpu_slabpartial而不是放入node的partial,是因為cpu_slab永遠都是最近在使用的slab。

? [2.2] 接下來就是slab page在node->partial中且釋放之后就為空slab page的情況,獲取一下page 所屬node。方便后續(xù)操作。

[3] n為空,說明之前沒有獲取node,也就是說不是釋放之后就為空slab。滿足這個分支的有三種cpu_slab中的情況和游離狀態(tài)的slab page情況

? [3.1] 原本就在cpu_slab中(的三種情況),什么也不用改變,因為我們已經(jīng)將堆塊釋放到page->freelist中了,如果在本cpu_slab->partial 無論釋放完是否為空都無關緊要,因為不需要釋放page;其次在其他cpu_slab中的話,不管是在page還是在partial中我們都是無權干涉的,直接釋放到page->freelist然后返回即可。

? [3.2] 走到這里說明**page是申請滿的游離狀態(tài)或node->full中**,則釋放后為可用狀態(tài),將其放入cpu_slab->partial中然后返回(put_cpu_partial函數(shù)邏輯不止于此,下面會詳細分析)。這個地方有一個問題就是,他沒有考慮如果開啟了CONFIG_SLUB_DEBUG的情況下,滿的page可能會存在于node->full列表中,如果補充node->full雙向鏈表中把page刪掉直接加入到cpu_slab->partial中會破壞node->full雙鏈表,后續(xù)插入操作可能造成unlink崩潰,由于默認不開啟該CONFIG,再加上page進入node->full需要觸發(fā)強制下架的時候cpu_slab->page必須是滿的,概率非常低,用戶也不可控,一般不影響。不過內(nèi)核最新版已經(jīng)重寫了開啟CONFIG_SLUB_DEBUG的邏輯,不存在這樣的問題了,我這里的代碼是5.13 版本,不影響其他邏輯。

? [3.3] 最后說明釋放之前在node->partial中,并且釋放之后也不為空,那么還繼續(xù)呆在node->partial中就行,直接啥也不用干返回。

[4] 走到這里說明slab page在node->partial中(小概率在node->full中),且釋放之后為空且node 中當前partial slab數(shù)量比slab 規(guī)定的最小partial數(shù)量多,可以釋放空的slab page,則進入空slab處理流程:

? [4.1] 釋放之前的狀態(tài)是半滿,則調(diào)用remove_partialnode->partial 列表中刪除,然后調(diào)用discard_slab釋放該空slab page。

? [4.2] 釋放之前在node->full中,則調(diào)用remove_fullfull列表中刪除,之所以會有釋放之后直接從full 變?yōu)榭?,我猜是考慮到這個函數(shù)可以一次釋放多個堆塊的原因。還有一點就是,正常full狀態(tài)的page在上面就會加入到cpu_slab->partial或者node->partial中了,無論釋放完是否為空,所以是走不到這個分支的,可見這個版本的代碼對CONFIG_SLUB_DEBUG的處理還是比較粗糙的,目前已經(jīng)在后續(xù)版本都會更新完善。

[5] 否則說明**cpu_slab沒開啟partial列表,游離狀態(tài)的slab page釋放后仍需要添加到一個列表中**,則將其加入node->partial 中。

put_cpu_partial

put_cpu_partial 函數(shù)不止是將slab page 放到cpu_slab->partial鏈表中,如果cpu_slab->partial鏈表中的頁面數(shù)量已經(jīng)達到最大值,則還會進行洗牌操作:

mm\slub.c : put_cpu_partial

static void put_cpu_partial(struct kmem_cache *s, struct page *page, int drain)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
	struct page *oldpage;
	int pages; //[1] 代表這個slab 后面大概還有多少個page
	int pobjects;//代表這個slab 后面還有多少個object 可用

	preempt_disable();
	do {
		pages = 0;
		pobjects = 0;
		oldpage = this_cpu_read(s->cpu_slab->partial);//獲取cpu->partial列表頭部的那個

		if (oldpage) {//[1] 獲取之前的pages 和pobjects 值用來之后更新
			pobjects = oldpage->pobjects;
			pages = oldpage->pages;
			if (drain && pobjects > slub_cpu_partial(s)) {//[2] 當前cpu_slab->partial中的頁數(shù)量已經(jīng)達到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));//把cpu_slab->partial中現(xiàn)存的slab page都放到node->partial中
				local_irq_restore(flags);
				oldpage = NULL;
				pobjects = 0;//cpu_slab->partial數(shù)量清0
				pages = 0;
				stat(s, CPU_PARTIAL_DRAIN);
			}
		}

		pages++;//[3]更新pages和pobjects數(shù)量
		pobjects += page->objects - page->inuse;

		page->pages = pages; //設置當前slab 的pages 和pobjects
		page->pobjects = pobjects;
		page->next = oldpage;//加入鏈表頭部

	} while (this_cpu_cmpxchg(s->cpu_slab->partial, oldpage, page)//[4] 最后將當前頁放入cpu_slab->partial中
								!= oldpage);
	if (unlikely(!slub_cpu_partial(s))) {
		unsigned long flags;

		local_irq_save(flags);
		unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));
		local_irq_restore(flags);
	}
	preempt_enable();
#endif	/* CONFIG_SLUB_CPU_PARTIAL */
}

[1] cpu_slab->partial 中每個頁面中的pobjectspages 都代表這個頁以及后面的單鏈表中有多少內(nèi)存塊數(shù)和頁數(shù),這樣我們只訪問第一個頁即可了解全部信息。

[2] 如果cpu_slab->partial中的頁數(shù)已經(jīng)達到了slab 規(guī)定的cpu_partial 最大值,則要進行unfreeze_partial操作。下面分析該函數(shù)。主要邏輯就是將cpu_slab->partial中的slab page都放到node->partial中,然后更新統(tǒng)計信息,當前cpu_slab->partial為空。

[3] 將該slab page加入到cpu_slab->partial鏈表頭部。

[4] 最后將整個鏈表更新到cpu_slab->partial

unfreeze_partials

unfreeze_partials函數(shù)主要是在cpu_slab->partial中slab page數(shù)量已經(jīng)達到最大值的時候將cpu_slab->partial中的slab page都轉移到node->partial中,如果遇到空的slab page,則會釋放掉。

mm\slub.c : unfreeze_partials

static void unfreeze_partials(struct kmem_cache *s,
		struct kmem_cache_cpu *c)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
	struct kmem_cache_node *n = NULL, *n2 = NULL;
	struct page *page, *discard_page = NULL;

	while ((page = slub_percpu_partial(c))) {//[1] 遍歷cpu_slab->partial中的page
		struct page new;
		struct page old;

		slub_set_percpu_partial(c, page);//取出頭部的,更新新頭部

		n2 = get_node(s, page_to_nid(page));//獲取page所在node
		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;//[2] 獲取page 的基本信息聯(lián)合體
			VM_BUG_ON(!old.frozen);

			new.counters = old.counters;
			new.freelist = old.freelist;

			new.frozen = 0;//要從cpu_slab->partial中拿出來,肯定要解凍

		} while (!__cmpxchg_double_slab(s, page,
				old.freelist, old.counters,//更新page 信息
				new.freelist, new.counters,
				"unfreezing slab"));

		if (unlikely(!new.inuse && n->nr_partial >= s->min_partial)) {//[3] 如果page 為空,并且node->partial數(shù)量滿足最小要求
			page->next = discard_page;//把page加入到要釋放的page列表
			discard_page = page;
		} else {//[3.1] 否則就加入到node的的partial中
			add_partial(n, page, DEACTIVATE_TO_TAIL);
			stat(s, FREE_ADD_PARTIAL);
		}
	}

	if (n)
		spin_unlock(&n->list_lock);

	while (discard_page) {//[4] 如果釋放page 列表不為空
		page = discard_page;
		discard_page = discard_page->next;

		stat(s, DEACTIVATE_EMPTY);
		discard_slab(s, page);//依次調(diào)用discard_slab 釋放掉slab page
		stat(s, FREE_SLAB);
	}
#endif	/* CONFIG_SLUB_CPU_PARTIAL */
}

[1] 遍歷cpu_slab->partial 中的每一個slab page,并從鏈表中依次取出

[2] 獲取page 的基本信息,然后更新基本信息,這里是將frozen 設為0,也就是解凍,從cpu_slab中移除

[3] 如果slab page的inuse 為0,說明該slab page已經(jīng)為空,并且如果node->partial頁滿足最小數(shù)量要求,則將其加入到待釋放的slab page的列表

? [3.1] 否則將其加入到node->partial

[4] 將所有空的可以釋放的page調(diào)用discard_slab釋放掉

discard_slab->free_slab

linux\mm\slub.c : discard_slab

static void discard_slab(struct kmem_cache *s, struct page *page)
{
	dec_slabs_node(s, page_to_nid(page), page->objects);//更新node中的slab 和object數(shù)量,釋放了要減少
	free_slab(s, page);//free_slab 釋放slab
}

discard_slab中先調(diào)用dec_slabs_node進行一些統(tǒng)計,更新一下slab page被釋放后node中的slab page數(shù)量和堆塊數(shù)量。

然后調(diào)用free_slab 進行正式的slab page釋放:

linux\mm\slub.c : free_slab

static void free_slab(struct kmem_cache *s, struct page *page)
{
	if (unlikely(s->flags & SLAB_TYPESAFE_BY_RCU)) {//RCU延遲釋放
		call_rcu(&page->rcu_head, rcu_free_slab);
	} else
		__free_slab(s, page);
}

如果開啟了SLAB_TYPESAFE_BY_RCU 則使用RCU延遲釋放slab page,不太關心,默認不開啟。正常流程調(diào)用__free_slab釋放slab page:

linux\mm\slub.c : __free_slab

static void __free_slab(struct kmem_cache *s, struct page *page)
{
	int order = compound_order(page); //獲取slab所屬page階數(shù)
	int pages = 1 << order; //page 頁數(shù)

	if (kmem_cache_debug_flags(s, SLAB_CONSISTENCY_CHECKS)) {//如果開啟了debug,則進行一些檢測
		void *p;

		slab_pad_check(s, page);//slab 檢測
		for_each_object(p, s, page_address(page),
						page->objects)
			check_object(s, page, p, SLUB_RED_INACTIVE);//slab中的object 檢測
	}

	__ClearPageSlabPfmemalloc(page);//這兩個都是修改page->flags的宏,清楚slab相關標志位
	__ClearPageSlab(page);
	/* In union with page->mapping where page allocator expects NULL */
	page->slab_cache = NULL;//取消page指向slab 的指針
	if (current->reclaim_state)
		current->reclaim_state->reclaimed_slab += pages;//更新數(shù)據(jù)
	unaccount_slab_page(page, order, s);//如果開啟了memcg相關會進行memcg去注冊
	__free_pages(page, order); //釋放掉page
}

邏輯很簡單,因為在之前已經(jīng)將該slab page從node中unlink 出來了,那么接下來只需要釋放該page即可。在__free_slab 中進行一些常規(guī)的檢查和統(tǒng)計更新,最后調(diào)用__free_pages將頁面釋放。

特殊slab的釋放

特殊slab使用kmem_cache_free函數(shù)來釋放堆塊:

static void file_free_rcu(struct rcu_head *head)
{
	struct file *f = container_of(head, struct file, f_u.fu_rcuhead);

	put_cred(f->f_cred);
	kmem_cache_free(filp_cachep, f);//專屬slab的釋放函數(shù)
}

kmem_cache_free 雖然也是直接封裝的slab_free ,但釋放前會使用cache_from_obj函數(shù)校驗一下,避免其他堆塊的釋放到錯誤的slab cache中:

void kmem_cache_free(struct kmem_cache *s, void *x)
{
	s = cache_from_obj(s, x);//先校驗一下是不是這個slab的堆塊
	if (!s)
		return;
    //調(diào)用slab_free正常free掉x
	slab_free(s, virt_to_head_page(x), x, NULL, 1, _RET_IP_);
	trace_kmem_cache_free(_RET_IP_, x, s->name);
}

校驗邏輯無非就是根據(jù)堆塊找page,根據(jù)page找slab_cache,不多贅述了。

內(nèi)存釋放邏輯總結

[linux kernel]slub內(nèi)存管理分析(5) kfree

內(nèi)存對象釋放主要思路就是,如果是頁面對象,則直接伙伴系統(tǒng)釋放,如果是slab 對象,如果在cpu_slab中,在cpu_slab中釋放,如果不是,則根據(jù)釋放之前之后的狀態(tài)(為空、為滿、半滿),進行不同的操作。

  • 首先找到釋放的內(nèi)存對象所在的page 的page結構體。如果該page不是slab,也就是說內(nèi)存對象不是通過slab分配的,而是直接分配的頁(大塊內(nèi)存),那么直接釋放頁。

    • 大塊內(nèi)存分配的時候就是從伙伴系統(tǒng)直接分配的頁,釋放的時候頁通過伙伴系統(tǒng)釋放頁面(__free_pages)
  • 其他內(nèi)存是通過slab分配,則通過slab釋放(slab_free)

    • memcg相關處理(memcg_slab_free_hook)

    • 獲取當前cpu_slab,如果要釋放的內(nèi)存對象正好屬于當前cpu_slab(可以理解為是否是從當前cpu_slab分配的),則快速釋放

      • 獲取cpu_slabfreelist,將該內(nèi)存對象插入freelist頭部,刷新cpu_slab相關信息(do_slab_free)
    • 如果要釋放的內(nèi)存對象不屬于當前cpu_slab,(當前slab page在cpu_slab->partial、別的cpu_slab->page、別的cpu_slab->partial、游離狀態(tài)、node->partial 、node->full6種情況),需要慢速釋放(__slab_free)

      • 先把內(nèi)存對象釋放到slab page的freelist頭部,更新slab page相關統(tǒng)計信息
      • 如果該slab page為凍結狀態(tài)(說明是在cpu_slab中的三種情況)
        • 則直接結束(已經(jīng)將object 放到page->freelist了,剩下的就不用管了)
      • 如果釋放前該slab page是滿的(freelist為空),則說明page目前是游離狀態(tài)(不在任何列表中)或node->full
        • 如果開啟cpu->partial,則將該slab page放到cpu_slab->partial中(從node->full中移除)
          • 如果cpu_slab->partial滿了,則要將當前cpu_slab->partial中的所有slab page放到node->partial中,然后再將新的slab page放到cpu_slab->partial
        • 否則放入node->partial中(從node->full中移除)
      • 如果釋放后為空,則說明目前該slab page一定處在node->partial列表中,因為如果在cpu_slab或者游離狀態(tài)或node->full中不管釋放完是否為空,都會在上面的步驟中處理完畢
        • 如果當前slab 管理的partial頁面數(shù)量滿足最小要求,則將該釋放后為空的slab page釋放掉(__free_pages)
        • 否則不變,繼續(xù)呆在node->partial
      • 否則說明該page是本來就呆在node->partial中的半滿page,并且釋放后還是半滿,則什么也不操作。

slab page狀態(tài)轉換關系圖

結合kmallockfree的邏輯,可以畫出slab page的狀態(tài)轉換關系圖:

[linux kernel]slub內(nèi)存管理分析(5) kfree文章來源地址http://www.zghlxwxcb.cn/news/detail-407581.html

到了這里,關于[linux kernel]slub內(nèi)存管理分析(5) kfree的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內(nèi)容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • Linux-0.11 kernel目錄進程管理asm.s詳解

    Linux-0.11 kernel目錄進程管理asm.s詳解

    該模塊和CPU異常處理相關,在代碼結構上asm.s和traps.c強相關。 CPU探測到異常時,主要分為兩種處理方式,一種是有錯誤碼,另一種是沒有錯誤碼,對應的方法就是 error_code 和 no_error_code 。在下面的函數(shù)詳解中,將主要以兩個函數(shù)展開。 no_error_code 對于一些異常而言,CPU在出現(xiàn)

    2024年02月07日
    瀏覽(20)
  • 【Linux0.11代碼分析】07 之 kernel execve() 函數(shù) 實現(xiàn)原理

    系列文章如下: 系列文章匯總:《【Linux0.11代碼分析】之 系列文章鏈接匯總(全)》 https://blog.csdn.net/Ciellee/article/details/130510069 . 1.《【Linux0.11代碼分析】01 之 代碼目錄分析》 2.《【Linux0.11代碼分析】02 之 bootsect.s 啟動流程》 3.《

    2024年02月03日
    瀏覽(26)
  • 遍歷slub分配器申請的object(linux3.16)

    遍歷slub分配器申請的object(linux3.16)

    我只是把active_obj的數(shù)量基本湊齊了,細節(jié)還沒有去研究,可能有些地方是錯的 之前遇到了一個設備跑了一段直接之后內(nèi)存降低無法回收的問題。當時通過slabinfo看到某些slab池子里面active_objs和num_objs差距很大。slub分配器無法自己回收 (無法回收的原因有種這個說法,如果你

    2024年01月20日
    瀏覽(25)
  • 基于VSCode的Linux內(nèi)核調(diào)試環(huán)境搭建以及start_kernel跟蹤分析

    基于VSCode的Linux內(nèi)核調(diào)試環(huán)境搭建以及start_kernel跟蹤分析

    參考ppt的步驟: 準備工作:1-5 ,配置vscode環(huán)境:6 ,跟蹤分析:7。 目錄 1.安裝開發(fā)工具 ?2.下載內(nèi)核源碼 ?3.配置內(nèi)核選項 4.編譯和運行內(nèi)核 5.制作內(nèi)存根文件系統(tǒng) *gdb調(diào)試(可跳過) ?7.配置VSCode調(diào)試Linux內(nèi)核 ?7.跟蹤分析 下載出現(xiàn)了“Axel -n 20 :太多重定向”的問題,原因是

    2023年04月17日
    瀏覽(44)
  • Linux內(nèi)存管理 | 一、內(nèi)存管理的由來及思想

    1、前言 《中庸》有:“九層之臺,起于壘土” 之說,那么對于我們搞技術的人,同樣如此! 對于 Linux 內(nèi)存管理,你可以說沒有留意過,但是它存在于我們?nèi)粘i_發(fā)的方方面面,你所打開的文件,你所創(chuàng)建的變量,你所運行的程序,無不以此為基礎,它可以說是操作系統(tǒng)的

    2024年02月08日
    瀏覽(22)
  • Linux內(nèi)存管理--smaps內(nèi)存

    Linux內(nèi)存管理--smaps內(nèi)存

    swaps文件是Linux的proc文件系統(tǒng)提供的查看系統(tǒng)下運行進程內(nèi)存使用情況的方法,Linux給每個進程都提供了一個這樣的文件,學會查看并分析swaps文件有助于定位和解決諸如內(nèi)存泄漏、性能瓶頸等內(nèi)存資源相關問題。 了解smaps文件之前,需要先搞清楚Linux內(nèi)存管理中的虛擬內(nèi)存(

    2024年02月09日
    瀏覽(23)
  • Semantic Kernel 入門系列:?Memory內(nèi)存

    Semantic Kernel 入門系列:?Memory內(nèi)存

    了解的運作原理之后,就可以開始使用Semantic Kernel來制作應用了。 Semantic Kernel將embedding的功能封裝到了Memory中,用來存儲上下文信息,就好像電腦的內(nèi)存一樣,而LLM就像是CPU一樣,我們所需要做的就是從內(nèi)存中取出相關的信息交給CPU處理就好了。 使用Memory需要注冊 embedding

    2023年04月13日
    瀏覽(29)
  • 【Linux內(nèi)核】內(nèi)存管理——內(nèi)存回收機制

    【Linux內(nèi)核】內(nèi)存管理——內(nèi)存回收機制

    轉載請注明: https://www.cnblogs.com/Ethan-Code/p/16626560.html 前文提到malloc的內(nèi)存分配方式,malloc申請的是虛擬內(nèi)存,只有在程序去訪問時,才會觸發(fā)缺頁異常進入內(nèi)核態(tài),在缺頁中斷函數(shù)中建立物理內(nèi)存映射。 如果物理內(nèi)存充足,則直接建立頁框與頁的映射。當物理內(nèi)存不足時,內(nèi)

    2023年04月09日
    瀏覽(23)
  • 系統(tǒng)內(nèi)存管理:虛擬內(nèi)存、內(nèi)存分段與分頁、頁表緩存TLB以及Linux內(nèi)存管理

    系統(tǒng)內(nèi)存管理:虛擬內(nèi)存、內(nèi)存分段與分頁、頁表緩存TLB以及Linux內(nèi)存管理

    虛擬內(nèi)存是一種操作系統(tǒng)提供的機制,用于將每個進程分配的獨立的虛擬地址空間映射到實際的物理內(nèi)存地址空間上。通過使用虛擬內(nèi)存,操作系統(tǒng)可以有效地解決多個應用程序直接操作物理內(nèi)存可能引發(fā)的沖突問題。 在使用虛擬內(nèi)存的情況下,每個進程都有自己的獨立的虛

    2024年02月11日
    瀏覽(33)
  • 【管理運籌學】第 7 章 | 圖與網(wǎng)絡分析(1,圖論背景以及基本概念、術語、矩陣表示)

    【管理運籌學】第 7 章 | 圖與網(wǎng)絡分析(1,圖論背景以及基本概念、術語、矩陣表示)

    【管理運籌學】第 7 章 | 圖與網(wǎng)絡分析(2,最小支撐樹問題) 【管理運籌學】第 7 章 | 圖與網(wǎng)絡分析(3,最短路問題) 【管理運籌學】第 7 章 | 圖與網(wǎng)絡分析(4,最大流問題) 【管理運籌學】第 7 章 | 圖與網(wǎng)絡分析(5,最小費用流問題及最小費用最大流問題) 按照正常

    2024年02月09日
    瀏覽(32)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包