九色91_成人精品一区二区三区中文字幕_国产精品久久久久一区二区三区_欧美精品久久_国产精品99久久久久久久vr_www.国产视频

Hello! 歡迎來到小浪云!


深入理解Linux內核之mmu-gather操作


avatar
小浪云 2025-01-06 204

linux內核的虛擬內存管理中,特別是在處理器架構arm64、內核源碼版本為linux-5.10.50、運行的Ubuntu版本為20.04.1,并借助于代碼閱讀工具vim、ctags以及cscope的情況下,我們將深入探討mmu_gather操作的機制。我們將看到這個操作是如何確保在釋放物理頁面之前正確地刷新tlb,并且如何聚集更多的頁面以便統一釋放。

當一個進程退出或者執行munmap操作時,內核需要解除相關虛擬內存區域的頁表映射,并且在刷新或者無效化tlb后釋放或者回收相關的物理頁面。這個過程的正確順序至關重要,按照如下步驟進行:

  1. 解除頁表映射。
  2. 刷新相關的tlb。
  3. 釋放物理頁面。

在刷新相關虛擬內存區域的tlb之前,絕對不能提前釋放物理頁面,否則可能會導致不正確的結果。這就是mmu-gather(mmu積聚)操作的作用所在,它確保了這種順序,并且將需要釋放的相關物理頁面聚集在一起以便統一釋放。

2.源代碼解讀

2.1 重要數據結構

首先我們先介紹一下,與mmu-gather相關的一些重要結構體,對于理解源碼很有幫助。

相關的主要數據結構有三個:

struct mmu_gather

struct mmu_table_batch

Struct mmu_gather_batch

1)mmu_gather

來表示一次mmu積聚操作,在每次解除相關虛擬內存區域時使用。

struct?mmu_gather?{ ????????struct?mm_struct????????*mm;  #ifdef?CONFIG_MMU_GATHER_TABLE_FREE ????????struct?mmu_table_batch??*batch; #endif  ????????unsigned?long???????????start; ????????unsigned?long???????????end; ????????/* ????????|*?we?are?in?the?middle?of?an?operation?to?clear ????????|*?a?full?mm?and?can?make?some?optimizations ????????|*/ ????????unsigned?int????????????fullmm?:?1;  ????????/* ????????|*?we?have?performed?an?operation?which ????????|*?requires?a?complete?flush?of?the?tlb ????????|*/ ????????unsigned?int????????????need_flush_all?:?1;  ????????/* ????????|*?we?have?removed?page?directories ????????|*/ ????????unsigned?int????????????freed_tables?:?1;  ????????/* ????????|*?at?which?levels?have?we?cleared?entries? ????????|*/ ????????unsigned?int????????????cleared_ptes?:?1; ????????unsigned?int????????????cleared_pmds?:?1; ????????unsigned?int????????????cleared_puds?:?1; ????????unsigned?int????????????cleared_p4ds?:?1;  ????????/* ????????|*?tracks?VM_EXEC?|?VM_HUGETLB?in?tlb_start_vma ????????|*/ ????????unsigned?int????????????vma_exec?:?1; ????????unsigned?int????????????vma_huge?:?1; ????????????????????????????????????????????????????????????????????????? ????????unsigned?int????????????batch_count;??????????? ????????#ifndef?CONFIG_MMU_GATHER_NO_GATHER??????????????????????????? ????????struct?mmu_gather_batch?*active;?????????????????????? ????????struct?mmu_gather_batch?local;???????????????????????? ????????struct?page?????????????*__pages[MMU_GATHER_BUNDLE];? ??????????????????????????...???????????????????????????????????????????????????? ????????#endif ?????????????????????????? };??????????????????????????????????????????????????????????????????????? 

其中, mm 表示操作哪個進程的虛擬內存;batch 用于積聚進程各級頁目錄的物理頁;start和end 表示操作的起始和結束虛擬地址,這兩個地址在處理過程中會被相應的賦值;fullmm 表示是否操作整個用戶地址空間;freed_tables 表示我們已經釋放了相關的頁目錄;cleared_ptes/pmds/puds/p4ds 表示我們在哪個級別上清除了表項;vma_exec 表示操作的是否為可執行的vma;vma_huge 表示操作的是否為hugetlb的vma;batch_count 表示積聚了多少個“批次”,后面會講到 ;active、local和__pages 和多批次釋放物理頁面相關;active表示當前處理的批次,local表示“本地”批次,__pages表示“本地”批次積聚的物理頁面。

這里需要說明一點就是,mmu積聚操作會涉及到local批次和多批次操作,local批次操作的物理頁面相關的struct page數組內嵌到mmu_gather結構的__pages中,且我們發現這個數組大小為8,也就是local批次最大積聚8 * 4k = 32k的內存大小,這因為mmu_gather結構通常在內核中分配,不能占用太多的內核空間,而多批次由于動態分配批次積聚結構所以每個批次能積聚更多的頁面。

2)mmu_table_batch

用于積聚進程使用的各級頁目錄的物理頁,在釋放進程相關的頁目錄的物理頁時使用(文章中稱為頁表批次的積聚結構)。

struct?mmu_table_batch?{ #ifdef?CONFIG_MMU_GATHER_RCU_TABLE_FREE ????????struct?rcu_head?????????rcu;???????????????? #endif?? ????????unsigned?int????????????nr;????????????????? ????????void????????????????????*tables[0];????????? }; 

rcu 用于rcu延遲釋放頁目錄的物理頁;

nr 表示頁目錄的物理頁的積聚結構的page數組中頁面個數;

tables 表示頁表積聚結構的page數組。

3)mmu_gather_batch

表示物理頁的積聚批次,用于積聚進程映射到用戶空間物理頁(文章中稱為批次的積聚結構)。

?struct?mmu_gather_batch?{?????? ?????????????????? ?????????struct?mmu_gather_batch?*next; ?????????unsigned?int????????????nr;???? ?????????? ?????????unsigned?int????????????max;?? ??????????? ?????????struct?page?????????????*pages[0];???? ??? ?};?????? 

next 用于多批次積聚物理頁時,連接下一個積聚批次結構 ;

nr 表示本次批次的積聚數組的頁面個數;

max 表示本次批次的積聚數組最大的頁面個數;

pages 表示本次批次積聚結構的page數組。

2.2 總體調用

通常mmu-gather操作由一下幾部分函數組成:

tlb_gather_mmu

unmap_vmas

free_pgtables

tlb_finish_mmu

其中tlb_gather_mmu表示mmu-gather初始化,也就是struct mmu_gather的初始化;

unmap_vmas 表示解除相關虛擬內存區域的頁表映射;

free_pgtables 表示釋放頁表操作 ;

tlb_finish_mmu 表示進行刷tlb和釋放物理頁操作。

2.3 tlb_gather_mmu

這個函數主要是初始化從進程內核中傳遞過來的mmu_gather結構。

void?tlb_gather_mmu(struct?mmu_gather?*tlb,?struct?mm_struct?*mm,??????????? ????? ????????????????????????unsigned?long?start,?unsigned?long?end)?????????????????? {???????????????????????????????????????????????????????????????????????????????? ????????tlb->mm?=?mm;??????????//賦值進程的內存描述符?????????????????????????????????????????????????? ????????????????????????????????????????????????????????????????????????????????? ????????/*?Is?it?from?0?to?~0??*/??????????????????????????????????? ????????????? ????????tlb->fullmm?????=?!(start?|?(end+1));????//如果是操作進程整個地址空間,  則?start=0,???end=-1,這個時候?fullmm會被賦值1??????????????????????? ????????????????????????????????????????????????????????????????????????????????? #ifndef?CONFIG_MMU_GATHER_NO_GATHER?????????????????????????????????????????????? ????????tlb->need_flush_all?=?0;??????????????????//初始化“本地”積聚相關成員??????????????????????????????? ????????tlb->local.next?=?NULL;?????????????????????????????????????????????????? ????????tlb->local.nr???=?0;????????????????????????????????????????????????????? ????????tlb->local.max??=?ARRAY_SIZE(tlb->__pages);?????????????????????????????? ????????tlb->active?????=?&tlb->local;????//active指向本地的積聚結構???????????????????????????????????????? ????????tlb->batch_count?=?0;???????????????????????????????????????????????????? #endif??????????????????????????????????????????????????????????????????????????? ????????????????????????????????????????????????????????????????????????????????? ????????tlb_table_init(tlb);??????????//?????tlb->batch?=?NULL???,來表示先不使用多批次積聚??????????????????????????????????? #ifdef?CONFIG_MMU_GATHER_PAGE_SIZE??????????????????????????????????????????????? ????????tlb->page_size?=?0;?????????????????????????????????????????????????????? #endif??????????????????????????????????????????????????????????????????????????? ????????????????????????????????????????????????????????????????????????????????? ????????__tlb_reset_range(tlb);??????//tlb->start/end?和??lb->freed_tables、tlb->cleared_xxx初始化???????????????????????????????????????? ????????inc_tlb_flush_pending(tlb->mm);?????????????????????????????????????????? }???????????????????????????????????????????????????????????????????????????????? 

下面給出tlb_gather_mmu時的圖解:

深入理解Linux內核之mmu-gather操作

2.4 unmap_vmas

這個函數用于解除相關進程虛擬內存區域的頁表映射,還會將相關的物理頁面放入積聚結構中,后面統一釋放。

下面我們來看下這個函數:

void?unmap_vmas(struct?mmu_gather?*tlb, ????????????????struct?vm_area_struct?*vma,?unsigned?long?start_addr, ????????????????unsigned?long?end_addr) { ????????... ????????for?(?;?vma?&&?vma->vm_start?vm_next) ????????????????unmap_single_vma(tlb,?vma,?start_addr,?end_addr,?NULL); ????????... } 

函數傳遞進已經初始化好的mmu積聚結構、操作的起始vma、以及虛擬內存范圍[start_addr, end_addr], 然后調用unmap_single_vma來操作這個范圍內的每一個vma。

unmap_single_vma的實現相關代碼比較多,在此不在贅述,我們會分析關鍵代碼,它主要做的工作為:通過遍歷進程的多級頁表,來找到vma中每一個虛擬頁對應的物理頁(存在的話),然后解除虛擬頁到物理頁的映射關系,最后將物理頁放入積聚結構中。

總體調用如下:

//?mm/memory.c unmap_vmas ->?unmap_single_vma?//處理單個vma ????->??unmap_page_range ????????->zap_p4d_range?//遍歷pge頁目錄中每一個p4d表項 ????????????->zap_pud_range??//遍歷p4d頁目錄中每一個pud表項 ????????????????->zap_pmd_range?//遍歷pud頁目錄中每一個pmd表項 ????????????????????->zap_pte_range??//遍歷pmd頁目錄中每一個pmd表項 

下面我們省略中間各級頁表的遍歷過程,重點看下最后一級頁表的處理(這段代碼相當關****鍵):

zap_pte_range ->  static?unsigned?long?zap_pte_range(struct?mmu_gather?*tlb, ????????????????????????????????struct?vm_area_struct?*vma,?pmd_t?*pmd, ????????????????????????????????unsigned?long?addr,?unsigned?long?end, ????????????????????????????????struct?zap_details?*details) { ????????????... again: ????????init_rss_vec(rss); ????????start_pte?=?pte_offset_map_lock(mm,?pmd,?addr,?&ptl);????//根據addr從pmd指向的頁表中獲得頁表項指針,  并申請頁表的自旋鎖???????????????????????????????????????? ????????pte?=?start_pte; ????????flush_tlb_batched_pending(mm); ????????arch_enter_lazy_mmu_mode(); ????????do?{ ????????????????pte_t?ptent?=?*pte;???//獲得頁表項??????????????????????????????????????????????????????????????????? ????????????????if?(pte_none(ptent))??//頁表項的內容?為空表示沒有映射過,繼續下一個虛擬頁 ????????????????????????continue;  ?????????????????... ???????????????? ????????????????if?(pte_present(ptent))?{??//虛擬頁相關的物理頁在內存中(如沒有被換出到swap) ????????????????????????struct?page?*page;  ????????????????????????page?=?vm_normal_page(vma,?addr,?ptent);????//獲得虛擬頁相關的物理頁?????????????????????????????????????? ????????????????????????... ???????????????????????? ?????????????????????????ptent?=?ptep_get_and_clear_full(mm,?addr,?pte,???????????????????????????? ?????????????????????????????????????????????????????????tlb->fullmm);??//將頁表項清空(即是解除了映射關  系),并返回原來的頁表項的內容?????????????????????????? ?????????????????????????tlb_remove_tlb_entry(tlb,?pte,?addr);????????????????????????????????????? ?????????????????????????if?(unlikely(!page))?????????????????????????????????????????????????????? ?????????????????????????????????continue;????????????????????????????????????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????????????? ?????????????????????????if?(!PageAnon(page))?{????//如果是文件頁???????????????????????????????????????????????? ?????????????????????????????????if?(pte_dirty(ptent))?{??//是臟頁????????????????????????????????????????? ?????????????????????????????????????????force_flush?=?1;?????//強制刷tlb????????????????????????????????????? ?????????????????????????????????????????set_page_dirty(page);??//臟標志傳遞到page結構??????????????????????????????????? ?????????????????????????????????}????????????????????????????????????????????????????????????????? ?????????????????????????????????if?(pte_young(ptent)?&&??????????????????????????????????????????? ?????????????????????????????????|???likely(!(vma->vm_flags?&?VM_SEQ_READ)))???????//如果頁表項訪問標志置  位,且是隨機訪問的vma,則標記頁面被訪問???????????????? ?????????????????????????????????????????mark_page_accessed(page);????????????????????????????????? ?????????????????????????}????????????????????????????????????????????????????????????????????????? ?????????????????????????rss[mm_counter(page)]--;??//進程的相關rss?做減1記賬???????????????????????????????????????????????? ?????????????????????????page_remove_rmap(page,?false);???//??page->_mapcount-- ?????????????????????????if?(unlikely(page_mapcount(page)?if?(unlikely(__tlb_remove_page(tlb,?page)))?{??//將物理頁記錄到積聚結構中,???如果  分配不到mmu_gather_batch結構或不支持返回true???????????????????????? ?????????????????????????????????force_flush?=?1;?????//強制刷tlb?????????????????????????????????????????????  ??????????????????????????????addr?+=?PAGE_SIZE;?????//操作下一個虛擬頁??????????????????????????? ??????????????????????????????break;?????//退出循環?????????????????????????????????????????????????? ??????????????????????}???????????????????????????????????????????????????????????????????? ??????????????????????continue;???//正常情況下,處理下一個虛擬頁????????????????????????????????????????????????????????? ??????????????}???????????????????????????????????????????????????????????????????????????? ???????????????????????????????????????? ???????????????//下面處理虛擬頁相關的物理頁“不在”內存中的情況,可能是交換到swap或者是遷移類型等  ??????????????entry?=?pte_to_swp_entry(ptent);???//頁表項得到??swp_entry???????????????????????????????????????? ??????????????if?(is_device_private_entry(entry))?{???//處理設備內存表項????????????????????????????????????? ??????????????????????struct?page?*page?=?device_private_entry_to_page(entry);????????????? ??????????????????????????????????????????????????????????????????????????????????????????? ??????????????????????if?(unlikely(details?&&?details->check_mapping))?{??????????????????? ??????????????????????????????/*??????????????????????????????????????????????????????????? ??????????????????????????????|*?unmap_shared_mapping_pages()?wants?to????????????????????? ??????????????????????????????|*?invalidate?cache?without?truncating:?????????????????????? ??????????????????????????????|*?unmap?shared?but?keep?private?pages.?????????????????????? ??????????????????????????????|*/?????????????????????????????????????????????????????????? ??????????????????????????????if?(details->check_mapping?!=???????????????????????????????? ??????????????????????????????|???page_rmapping(page))????????????????????????????????????? ??????????????????????????????????????continue;???????????????????????????????????????????? ??????????????????????}???????????????????????????????????????????????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????? ??????????????????????pte_clear_not_present_full(mm,?addr,?pte,?tlb->fullmm);?????????????? ??????????????????????rss[mm_counter(page)]--;????????????????????????????????????????????? ??????????????????????page_remove_rmap(page,?false);??????????????????????????????????????? ??????????????????????put_page(page);?????????????????????????????????????????????????????? ??????????????????????continue;???????????????????????????????????????????????????????????? ??????????????}???????????????????????????????????????????????????????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????? ????????????????????????????????????????????????????????????????????????????????????? ???????????????....????????????????????????????????????? ????????????????????????????????????????????????????????????????????????????????????? ????????????????if?(!non_swap_entry(entry))??//非遷移類型的swap_entry???????????????????????????????? ????????????????????????rss[MM_SWAPENTS]--;??????//進程相關的交換條目的rss?減1?????????????????????????????????????? ????????????????else?if?(is_migration_entry(entry))?{???//遷移類型的表項?????????????????????????????? ????????????????????????struct?page?*page;??????????????????????????????????????????? ????????????????????????????????????????????????????????????????????????????????????? ????????????????????????page?=?migration_entry_to_page(entry);?????????//得到對應的物理頁?????????????? ????????????????????????rss[mm_counter(page)]--;?????//進程相關的物理頁類型的rss?減1 ????????????????}???????????????????????????????????????????????????????????????????? ????????????????if?(unlikely(!free_swap_and_cache(entry)))???//釋放swap條目 ????????????????????????print_bad_pte(vma,?addr,?ptent,?NULL);??????????????????????? ????????????????pte_clear_not_present_full(mm,?addr,?pte,?tlb->fullmm);?????//清除虛擬頁相關的物理頁的頁表映  射?????????? ????????}?while?(pte++,?addr?+=?PAGE_SIZE,?addr?!=?end);?????//遍歷pmd表項管轄范圍內的每一個虛擬頁???????????????????????? ????????????????????????????????????????????????????????????????????????????????????? ????????add_mm_rss_vec(mm,?rss);????//記錄到進程的相關rss結構中????????????????????????????????????????????????? ????????arch_leave_lazy_mmu_mode();?????????????????????????????????????????????????? ????????????????????????????????????????????????????????????????????????????????????? ????????/*?Do?the?actual?TLB?flush?before?dropping?ptl?*/???????????????????????????? ????????if?(force_flush)????????????????????????????????????????????????????????????? ????????????????tlb_flush_mmu_tlbonly(tlb);???//如果是強制刷tlb,則刷tlb??????????????????????????????????????? ????????pte_unmap_unlock(start_pte,?ptl);?????//釋放進程的頁表自旋鎖??????????????????????????????????????? ????????????????????????????????????????????????????????????????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????? ???????/*?????????????????????????????????????????????????????????????????????????????????? ???????|*?If?we?forced?a?TLB?flush?(either?due?to?running?out?of??????????????????????????? ???????|*?batch?buffers?or?because?we?needed?to?flush?dirty?TLB???????????????????????????? ???????|*?entries?before?releasing?the?ptl),?free?the?batched?????????????????????????????? ???????|*?memory?too.?Restart?if?we?didn't?do?everything.?????????????????????????????????? ???????|*/????????????????????????????????????????????????????????????????????????????????? ???????if?(force_flush)?{????????//如果是強制刷tlb,則釋放掉本次聚集的物理頁???????????????????????????????????????????????? ???????????????force_flush?=?0;???????????????????????????????????????????????????????????? ???????????????tlb_flush_mmu(tlb);???//釋放本次聚集的物理頁????????????????????????????????????????????????????????? ???????}??????????????????????????????????????????????????????????????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????? ???...????????????????????????????????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????? ???????return?addr;?????????? ??????? ??} ??????????????????????????????????????????????????????????????????????????????????????????? 

以上函數,遍歷進程相關頁表(一個pmd表項指向一個頁表)所描述的范圍的每一個虛擬頁,如果之前已經建立過映射,就將相關的頁表項清除,對于在內存中物理頁來說,需要調用__tlb_remove_page將其加入到mmu的積聚結構中,下面重點看下這個函數:

__tlb_remove_page ->__tlb_remove_page_size??//mm/mmu_gather.c ????-> ????bool?__tlb_remove_page_size(struct?mmu_gather?*tlb,?struct?page?*page,?int?page_size)?????????????? {?????????????????????????????????????????????????????????????????????????????????????????????????? ????????struct?mmu_gather_batch?*batch;???????????????????????????????????????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????????????? ????????...??????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????????????? ????????batch?=?tlb->active;??//獲得當前批次的積聚結構???????????????????????????????????????????????????????????????????? ????????/*????????????????????????????????????????????????????????????????????????????????????????? ????????|*?Add?the?page?and?check?if?we?are?full.?If?so???????????????????????????????????????????? ????????|*?force?a?flush.?????????????????????????????????????????????????????????????????????????? ????????|*/???????????????????????????????????????????????????????????????????????????????????????? ????????batch->pages[batch->nr++]?=?page;??//將頁面加入到???批次的積聚結構的pages數組中,并增加?batch->nr計數???????????????????????????????????????????????????? ????????if?(batch->nr?==?batch->max)?{??//如果當前批次的?積聚結構的pages數組中積聚的頁面個數到達最大個數?????????????????????????????????????????????????????????? ????????????????if?(!tlb_next_batch(tlb))??//獲得下一個批次積聚結構???????????????????????????????????????????????????????? ????????????????????????return?true;????//獲得不成功返回true??????????????????????????????????????????????????????????? ????????????????batch?=?tlb->active;??????????????????????????????????????????????????????????????? ????????}?????????????????????????????????????????????????????????????????????????????????????????? ????????VM_BUG_ON_PAGE(batch->nr?>?batch->max,?page);?????????????????????????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????????????? ????????return?false;???//獲得???下一個批次??批次積聚結構成功,返回??false;??????????????????????????????????????????????????????????????????????? }?????????????????????????????????????????????????????????????????????????????????????????????????? 

我們再來看下tlb_next_batch的實現:

static?bool?tlb_next_batch(struct?mmu_gather?*tlb)??????????????????????????? { ????????struct?mmu_gather_batch?*batch; ???????? ????????batch?=?tlb->active;????????????????????????????????????????????????? ????????if?(batch->next)?{??//下一個批次積聚結構存在 ????????????????tlb->active?=?batch->next;?//當前的批次積聚結構指向這個批次結構 ????????????????return?true; ????????}  ????????if?(tlb->batch_count?==?MAX_GATHER_BATCH_COUNT)??//如果批次數量達到最大值?則返回false ????????????????return?false;  ????????//批次還沒有到達最大值,則分配并初始化批次的積聚結構 ????????batch?=?(void?*)__get_free_pages(GFP_NOWAIT?|?__GFP_NOWARN,?0);??????//申請一個物理頁面由于存放  mmu_gather_batch和page數組 ????????if?(!batch) ????????????????return?false;  ????????tlb->batch_count++;???//批次計數加1 ????????batch->next?=?NULL; ????????batch->nr???=?0; ????????batch->max??=?MAX_GATHER_BATCH;???//批次積聚結構的?page數組最大個數賦值為MAX_GATHER_BATCH ???????? ????????//插入到mmu?積聚結構的批次鏈表中 ????????tlb->active->next?=?batch;??????????????????????????????????????????? ????????tlb->active?=?batch; ???????? ????????return?true;????????????????????????????????????????????????????????? }??????? 

這里有幾個地方需要注意:MAX_GATHER_BATCH_COUNT 表示的是mmu積聚操作最多可以有多少個批次積聚結構,他的值為10000UL/MAX_GATHER_BATCH (考慮到非搶占式內核的soft lockups的影響)。MAX_GATHER_BATCH 表示一個批次的積聚結構的 page數組的最多元素個數,他的值為((PAGE_SIZE – sizeof(struct mmu_gather_batch)) / sizeof(void *)),也就是物理頁面大小去除掉struct mmu_gather_batch結構大小。

下面給出相關圖解:

解除頁表過程:

深入理解Linux內核之mmu-gather操作

添加的到積聚結構page數組頁面小于等于8個的情況:

深入理解Linux內核之mmu-gather操作

添加的到積聚結構page數組頁面大于8個的情況:

1個批次積聚結構->

深入理解Linux內核之mmu-gather操作

2個批次積聚結構->

深入理解Linux內核之mmu-gather操作

更多批次積聚結構加入->

深入理解Linux內核之mmu-gather操作

2.5 free_pgtables

unmap_vmas函數主要是積聚了一些相關的虛擬頁面對應的物理頁面,但是我們還需要釋放各級頁表對應的物理頁等。下面看下free_pgtables的實現:

首先看下它的主要脈絡:

//?mm/memory.c void?free_pgtables(struct?mmu_gather?*tlb,?struct?vm_area_struct?*vma,????????????????????????????? ????????????????unsigned?long?floor,?unsigned?long?ceiling)???????????????????????????????????????? {?????????????????????????????????????????????????????????????????????????????????????????????????? ????????while?(vma)?{??????//從起始的vma開始遍歷每個vma????????????????????????????????? ????????????????struct?vm_area_struct?*next?=?vma->vm_next;????//獲得下一個vma???????????????? ????????????????unsigned?long?addr?=?vma->vm_start;?????//獲得vma的起始地址???????????????? ??????????????????????????????????????????????????????????????????????????????????????????????????? ????????????????/*????????????????????????????????????????????????????????????????????????????????? ????????????????|*?Hide?vma?from?rmap?and?truncate_pagecache?before?freeing???????????????????????? ????????????????|*?pgtables???????????????????????????????????????????????????????????????????????? ????????????????|*/???????????????????????????????????????????????????????????????????????????????? ????????????????unlink_anon_vmas(vma);???//解除匿名vma的反向映射關系??????????????????????? ????????????????unlink_file_vma(vma);??????//解除文件vma反向映射關系???????????????????????????????????????????????????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????????????? ????????????????if?(is_vm_hugetlb_page(vma))?{?????????????????????????????????????????????????????? ????????????????????????hugetlb_free_pgd_range(tlb,?addr,?vma->vm_end,????????????????????????????? ????????????????????????????????floor,?next???next->vm_start?:?ceiling);??????????????????????????? ????????????????}?else?{??????????????????????????????????????????????????????????????????????????? ????????????????????????/*????????????????????????????????????????????????????????????????????????? ????????????????????????|*?Optimization:?gather?nearby?vmas?into?one?call?down????????????????????? ????????????????????????|*/???????????????????????????????????????????????????????????????????????? ????????????????????????while?(next?&&?next->vm_start?vm_end?+?PMD_SIZE???????????????????? ????????????????????????|??????&&?!is_vm_hugetlb_page(next))?{????????????????????????????????????? ????????????????????????????????vma?=?next;???????????????????????????????????????????????????????? ????????????????????????????????next?=?vma->vm_next;??????????????????????????????????????????????? ????????????????????????????????unlink_anon_vmas(vma);????????????????????????????????????????????? ????????????????????????????????unlink_file_vma(vma);?????????????????????????????????????????????? ????????????????????????}?????????????????????????????????????????????????????????????????????????? ????????????????????????free_pgd_range(tlb,?addr,?vma->vm_end,????????????????????????????????????? ????????????????????????????????floor,?next???next->vm_start?:?ceiling);????//遍歷各級頁表???????????????????????? ????????????????}?????????????????????????????????????????????????????????????????????????????????? ????????????????vma?=?next;???????????????????????????????????????????????????????????????????????? ????????}?????????????????????????????????????????????????????????????????????????????????????????? }?????????????????????????????????????????????????????????????????????????????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????????????? 

我們主要看free_pgd_range的實現:

free_pgd_range ->free_p4d_range? ????->free_pud_range? ????????->free_pmd_range?? ????????????->free_pte_range?? ????????????????->?.... ????????????->?pud_clear(pud);??////清除pud頁目錄中的對應的pud表項 ????????????????pmd_free_tlb(tlb,?pmd,?start);?//pmd頁目錄的物理頁放入?頁表的積聚結構中 ????????????????mm_dec_nr_pmds(tlb->mm)?//進程使用的頁表的物理頁統計減1 ????????->p4d_clear(p4d);??//清除p4d頁目錄中的對應的p4d表項 ???????????pud_free_tlb(tlb,?pud,?start)??//pud頁目錄的物理頁放入?頁表的積聚結構中 ????->?pgd_clear(pgd);??//清除pgd頁目錄中的對應的pgd表項 ????????p4d_free_tlb(tlb,?p4d,?start);?//p4d頁目錄的物理頁放入?頁表的積聚結構中(存在p4d頁目錄的話) 

我們以最后一級頁表(pmd表項指向)為例說明:

static?void?free_pte_range(struct?mmu_gather?*tlb,?pmd_t?*pmd, ????????????????????????|??unsigned?long?addr) { ????????pgtable_t?token?=?pmd_pgtable(*pmd);??//從相關的pmd表項指針中獲得頁表 ????????pmd_clear(pmd);???//清除pmd頁目錄中的對應的pmd表項,即是頁表指針 ????????pte_free_tlb(tlb,?token,?addr);?//存放頁表的物理頁放入?頁表的積聚結構中 ????????mm_dec_nr_ptes(tlb->mm);?//進程使用的頁表的物理頁統計減1 } 

看下pte_free_tlb函數:

//?include/asm-generic/tlb.h #ifndef?pte_free_tlb #define?pte_free_tlb(tlb,?ptep,?address)???????????????????????? ????????do?{???????????????????????????????????????????????????? ????????????????tlb_flush_pmd_range(tlb,?address,?PAGE_SIZE);?????//更新tlb->start和tlb->end,?tlb->cleared_pmds?=?1 ????????????????tlb->freed_tables?=?1;??????????????????????????? ????????????????__pte_free_tlb(tlb,?ptep,?address);??????????????//存放頁表的物理頁放入頁表的積聚結構中 ????????}?while?(0) #endif 

再看看__pte_free_tlb:

//?arch/arm64/include/asm/tlb.h __pte_free_tlb ->pgtable_pte_page_dtor(pte);?//執行釋放頁表的時候的構造函數,如釋放ptlock內存,zone的頁表頁面統計減1等 ????tlb_remove_table(tlb,?pte);??//mm/mmu_gather.c ????->??struct?mmu_table_batch?**batch?=?&tlb->batch;?//獲得?頁表的積聚結構  ??????if?(*batch?==?NULL)?{??//如何為空,則分配一個物理頁,存放積聚結構和積聚數組 ??????????????*batch?=?(struct?mmu_table_batch?*)__get_free_page(GFP_NOWAIT?|?__GFP_NOWARN); ??????????????if?(*batch?==?NULL)?{ ??????????????????????tlb_table_invalidate(tlb); ??????????????????????tlb_remove_table_one(table); ??????????????????????return; ??????????????} ??????????????(*batch)->nr?=?0; ??????}  ??????(*batch)->tables[(*batch)->nr++]?=?table;???//相關的頁目錄對應的物理頁放入?積聚數組中 ??????if?((*batch)->nr?==?MAX_TABLE_BATCH)?//加入的物理頁達到最大值 ??????????????tlb_table_flush(tlb);??//做一次刷tlb和釋放當前已經積聚的頁目錄的物理頁 

需要說明的是:對于存放各級頁目錄的物理頁的釋放,每當一個頁表積聚結構填滿了就會釋放,不會構建批次鏈表。

2.6 tlb_finish_mmu

通過上面的unmap_vmas和free_pgtables之后,我們積聚了大量的物理頁以及存放各級頁目錄的物理頁,現在需要將這些頁面進行釋放。

下面我們來看下tlb_finish_mmu做的mmu-gather的收尾動作:

void?tlb_finish_mmu(struct?mmu_gather?*tlb, ????????????????unsigned?long?start,?unsigned?long?end) { ??????...  ????????tlb_flush_mmu(tlb);??//刷tlb和釋放所有積聚的物理頁  #ifndef?CONFIG_MMU_GATHER_NO_GATHER ????????tlb_batch_list_free(tlb);?//釋放各批次結構對應的物理頁 #endif ?????... } 

首先看下tlb_flush_mmu:

mm/mmu_gather.c  ?void?tlb_flush_mmu(struct?mmu_gather?*tlb) ?{ ?????????tlb_flush_mmu_tlbonly(tlb);??//刷tlb ?????????tlb_flush_mmu_free(tlb);?//釋放各個批次積聚結構的物理頁 ?} 

tlb_flush_mmu_tlbonly的實現:

?static?inline?void?tlb_flush_mmu_tlbonly(struct?mmu_gather?*tlb) ?{ ?????????/* ?????????|*?Anything?calling?__tlb_adjust_range()?also?sets?at?least?one?of ?????????|*?these?bits. ?????????|*/ ?????????if?(!(tlb->freed_tables?||?tlb->cleared_ptes?||?tlb->cleared_pmds?|| ?????????|?????tlb->cleared_puds?||?tlb->cleared_p4ds))??//有一個為0?即返回 ?????????????????return; ? ?????????tlb_flush(tlb);??//刷tlb,和處理器架構相關 ???????... ?????????__tlb_reset_range(tlb);?//將tlb->start?和tlb->end以及tlb->freed_tables,tlb->cleared_xxx復位 ?} 

我們來看下tlb_flush:

arch/arm64/include/asm/tlb.h static?inline?void?tlb_flush(struct?mmu_gather?*tlb) { ????????struct?vm_area_struct?vma?=?TLB_FLUSH_VMA(tlb->mm,?0); ????????bool?last_level?=?!tlb->freed_tables; ????????unsigned?long?stride?=?tlb_get_unmap_size(tlb); ????????int?tlb_level?=?tlb_get_level(tlb);?//得到刷tlb的級別,如只刷pte級別  ????????/* ????????|*?If?we're?tearing?down?the?address?space?then?we?only?care?about ????????|*?invalidating?the?walk-cache,?since?the?ASID?allocator?won't ????????|*?reallocate?our?ASID?without?invalidating?the?entire?TLB. ????????|*/ ????????if?(tlb->fullmm)?{??//刷整個mm的tlb ????????????????if?(!last_level) ????????????????????????flush_tlb_mm(tlb->mm); ????????????????return; ????????} ???? ????????//刷一個虛擬內存范圍的tlb ????????__flush_tlb_range(&vma,?tlb->start,?tlb->end,?stride, ????????????????????????|?last_level,?tlb_level); } 

最后我們看tlb_flush_mmu_free:

static?void?tlb_flush_mmu_free(struct?mmu_gather?*tlb) { ????????tlb_table_flush(tlb);??//釋放之前積聚的存放各級頁目錄的物理頁 #ifndef?CONFIG_MMU_GATHER_NO_GATHER ????????tlb_batch_pages_flush(tlb);?//釋放各個批次積聚結構積聚的物理頁 #endif } 

tlb_table_flush的實現:

?static?void?tlb_table_flush(struct?mmu_gather?*tlb) ?{ ?????????struct?mmu_table_batch?**batch?=?&tlb->batch;?//獲得當前的?頁表批次?積聚結構 ????????? ?????????if?(*batch)?{ ?????????????????tlb_table_invalidate(tlb);?//刷tlb ?????????????????tlb_remove_table_free(*batch);?//釋放頁目錄物理頁 ?????????????????*batch?=?NULL; ?????????} ?} static?void?tlb_remove_table_free(struct?mmu_table_batch?*batch) { ????????call_rcu(&batch->rcu,?tlb_remove_table_rcu);?//rsu延遲調用 ????????->?__tlb_remove_table_free(container_of(head,?struct?mmu_table_batch,?rcu)); ????????????->?static?void?__tlb_remove_table_free(struct?mmu_table_batch?*batch) ?????????????{ ?????????????????????int?i;  ?????????????????????for?(i?=?0;?i?nr;?i++)?//釋放頁表批次?積聚結構中的page數組中每一個物理頁 ?????????????????????????????__tlb_remove_table(batch->tables[i]);  ?????????????????????free_page((unsigned?long)batch);?//釋放這個?表批次?積聚結構對應的物理頁 ?????????????}???????  }??????? ? 

tlb_batch_pages_flush的實現:

static?void?tlb_batch_pages_flush(struct?mmu_gather?*tlb) { ????????struct?mmu_gather_batch?*batch;  ????????for?(batch?=?&tlb->local;?batch?&&?batch->nr;?batch?=?batch->next)?{??//遍歷積聚批次鏈表的每一個?批  次積聚結構 ????????????????free_pages_and_swap_cache(batch->pages,?batch->nr);?//釋放積聚結構的page數組的每一個物理頁 ????????????????batch->nr?=?0; ????????}??????? ????????tlb->active?=?&tlb->local; } 

最終是:調用free_pages_and_swap_cache將物理頁的引用計數減1 ,引用計數為0時就將這個物理頁釋放,還給伙伴系統。

雖然上面已經釋放了相關的各級頁表的物理頁和映射到進程地址空間的物理頁,但是存放積聚結構和page數組的物理頁還沒有釋放,所以調用tlb_batch_list_free來做這個事情:

tlb_batch_list_free ->?static?void?tlb_batch_list_free(struct?mmu_gather?*tlb) ?{ ?????????struct?mmu_gather_batch?*batch,?*next; ? ?????????for?(batch?=?tlb->local.next;?batch;?batch?=?next)?{??//釋放積聚結構的物理頁從tlb->local.next開始  的,遍歷所有批次的積聚結構 ?????????????????next?=?batch->next; ?????????????????free_pages((unsigned?long)batch,?0);??//釋放這個批次積聚結構的物理頁 ?????????}??????? ?????????tlb->local.next?=?NULL; ?} 

于是相關的所有物理頁面都被釋放了(包括相關地址范圍內進程各級頁目錄對應的物理頁,映射到進程地址空間的物理頁,和各個積聚結構所在的物理頁)。

最后給出整體的圖解:

深入理解Linux內核之mmu-gather操作

tlb_flush_mmu函數的tlb_table_flush會將B鏈表中的相關物理頁面釋放(包括之前保存的各級頁表的頁面和mmu_table_batch結構所在頁面),tlb_batch_pages_flush會將A鏈表的所有除了積聚結構以外的所有物理頁面釋放,而tlb_batch_list_free會將A鏈表的所有批次積聚結構(mmu_gather_batch)的物理頁面釋放。

3.應用場景

使用mmu-gather的應用場景主要是進程退出,執行execv和調用munmap等。

下面我們主要來看下他們的調用鏈:

3.1 進程退出時

進程退出時會釋放它的所有的相關聯的系統資源,其中就包括內存資源:

kernel/exit.c do_exit ->exit_mm ????->mmput??//kernel/fork.c ????????->if?(atomic_dec_and_test(&mm->mm_users))??//如果mm->mm_users減1為0時,也就是當前進程是最后一個mm的使用者 ????????__mmput(mm);?//釋放mm ????????->exit_mmap??//mm/mmap.c ????????????->tlb_gather_mmu(&tlb,?mm,?0,?-1);?//初始化mmu_gather結構,start=0,end=-1?表示釋放整個mm ????????????->unmap_vmas(&tlb,?vma,?0,?-1);?//解除頁表映射,相關的物理頁放入積聚結構中 ????????????>free_pgtables(&tlb,?vma,?FIRST_USER_ADDRESS,?USER_PGTABLES_CEILING);?//釋放各級頁表,頁表相關物理頁放入頁表積聚結構,滿則釋放 ????????????>tlb_finish_mmu(&tlb,?0,?-1);?//刷mm的tlb,釋放所有積聚物理頁,釋放所有積聚結構相關物理頁 

3.2 執行execv時

執行execv時進程會將所有的mm釋放掉:

fs/exec.c  ... do_execveat_common ->bprm_execve ????->exec_binprm ????????->search_binary_handler ????????????... ????????????->load_elf_binary?//?fs/binfmt_elf.c ????????????????->begin_new_exec ????????????????????->exec_mmap ????????????????????????->mmput(old_mm) ????????????????????????????->if?(atomic_dec_and_test(&mm->mm_users))??//如果mm->mm_users減1為0時,也就是當前進程是最后一個mm的使用者 ???????????????????????????????__mmput(mm);?//釋放mm ????????????????????????????????->exit_mmap??//mm/mmap.c ?????????????????????????????????????->tlb_gather_mmu(&tlb,?mm,?0,?-1);?//初始化mmu_gather結構,start=0,end=-1標識釋放整個mm ????????????????????????????????????->unmap_vmas(&tlb,?vma,?0,?-1);?//解除頁表映射,相關的物理頁放入積聚結構中 ????????????????????????????????????->free_pgtables(&tlb,?vma,?FIRST_USER_ADDRESS,?USER_PGTABLES_CEILING);?//釋放各級頁表,頁表相關物理頁放入頁表積聚結構,滿則釋放 ?????????????????????????????????????->tlb_finish_mmu(&tlb,?0,?-1);?//刷mm的tlb,釋放所有積聚物理頁,釋放所有積聚結構相關物理頁 

3.3 調用munmap時

執行munmap時,會將一個地址范圍的頁表解除并釋放相關的物理頁面:

mm/mmap.c  ... __do_munmap ->unmap_region(mm,?vma,?prev,?start,?end);?? ????->?tlb_gather_mmu(&tlb,?mm,?start,?end);?//初始化mmu_gather結構 ?????unmap_vmas(&tlb,?vma,?start,?end);?//解除頁表映射,相關的物理頁放入積聚結構中 ?????free_pgtables(&tlb,?vma,?prev???prev->vm_end?:?FIRST_USER_ADDRESS, ?????????????????????????????|next???next->vm_start?:?USER_PGTABLES_CEILING);?//釋放各級頁表,頁表相關物理頁放入頁表積聚結構,滿則釋放 ?????tlb_finish_mmu(&tlb,?start,?end);?//刷mm的tlb,釋放所有積聚物理頁,釋放所有積聚結構相關物理頁 

4.總結

Linux內核mmu-gather用于積聚解除映射的相關物理頁面,并保證了刷tlb和釋放物理頁面的順序。首先解除掉相關虛擬頁面對應物理頁面(如果有的話)的頁表映射關系,然后將相關的物理頁面保存在積聚結構的數組中,接著將相關的各級頁目錄表項清除,并放入頁表相關的積聚結構的數組中,最后刷對應內存范圍的tlb,釋放掉所有放在積聚結構數組中的物理頁面。

相關閱讀

主站蜘蛛池模板: 欧美一区视频 | av免费网址 | 精品国产欧美在线 | 成人在线小视频 | chengrenzaixian | 久久精品国产一区二区电影 | 日韩一区二区在线播放 | 91在线精品视频 | 中文字幕亚洲欧美日韩在线不卡 | 草久久免费视频 | av在线伊人 | 五月婷婷婷 | 九九av| 成人欧美一区二区三区黑人孕妇 | h视频在线观看免费 | 中文字幕视频在线看 | 人人鲁人人莫人人爱精品 | 久久久久久久久久久久久9999 | 亚洲精品一区二区二区 | 给我免费的视频在线观看 | 亚洲视频在线观看 | 亚洲视频免费观看 | 日韩视频在线一区 | 国产激情视频在线观看 | 极品电影院 | 国产精品69毛片高清亚洲 | 天天色天天色 | 久久久久久久久国产成人免费 | 小川阿佐美pgd-606在线 | av在线免费看网址 | 久在线精品视频 | 一级黄色毛片 | 99re视频在线 | 综合久久一区 | 亚洲国产欧美在线人成 | 男人天堂午夜 | 草草网 | 中文字幕精品一区 | 欧美日韩在线观看视频 | 亚洲a视频 | 国产三级日本三级 |