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

发表于 1年以前  | 总阅读数:265 次

1开场白

环境:

  • 处理器架构:arm64
  • 内核源码:linux-5.10.50
  • ubuntu版本:20.04.1
  • 代码阅读工具:vim+ctags+cscope

本文讲解Linux内核虚拟内存管理中的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时的图解:

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 < end_addr; vma = vma->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) < 0))                                    
                                 print_bad_pte(vma, addr, ptent, 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结构大小。

下面给出相关图解:

解除页表过程:

添加的到积聚结构page数组页面小于等于8个的情况:

添加的到积聚结构page数组页面大于8个的情况:

1个批次积聚结构->

2个批次积聚结构->

更多批次积聚结构加入->

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 <= vma->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 < batch->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;
 }

于是相关的所有物理页面都被释放了(包括相关地址范围内进程各级页目录对应的物理页,映射到进程地址空间的物理页,和各个积聚结构所在的物理页)。

最后给出整体的图解:

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,释放掉所有放在积聚结构数组中的物理页面。

本文由哈喽比特于1年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/w2vXhVx9e_7tRSp_HQZ05g

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:8月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:8月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:7月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:8月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:8月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:8月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:8月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:8月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:8月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:8月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:8月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:8月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:8月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:8月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:8月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:8月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:8月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:8月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:8月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:8月以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  236883次阅读
vscode超好用的代码书签插件Bookmarks 1年以前  |  6964次阅读
 目录