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结构大小。
|