zoukankan      html  css  js  c++  java
  • 内存映射函数remap_pfn_range学习——代码分析(3)

    作者

    彭东林
    QQ 405728433
     

    平台

    Linux-4.10.17
    Qemu-2.8 + vexpress-a9

    概述

    前面两篇介绍了remap_pfn_range的使用,下面学习一下该函数的实现。
     

    正文

    前提:下面的分析基于2级页表
     
    remap_pfn_range的实现在mm/memory.c。
     1 int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
     2             unsigned long pfn, unsigned long size, pgprot_t prot)
     3 {
     4     pgd_t *pgd;
     5     unsigned long next;
     6     unsigned long end = addr + PAGE_ALIGN(size);
     7     struct mm_struct *mm = vma->vm_mm;
     8     unsigned long remap_pfn = pfn;
     9     int err;
    10     
    11     /*
    12      * Physically remapped pages are special. Tell the
    13      * rest of the world about it:
    14      *   VM_IO tells people not to look at these pages
    15      *    (accesses can have side effects).
    16      *   VM_PFNMAP tells the core MM that the base pages are just
    17      *    raw PFN mappings, and do not have a "struct page" associated
    18      *    with them.
    19      *   VM_DONTEXPAND
    20      *      Disable vma merging and expanding with mremap().
    21      *   VM_DONTDUMP
    22      *      Omit vma from core dump, even when VM_IO turned off.
    23      *
    24      * There's a horrible special case to handle copy-on-write
    25      * behaviour that some programs depend on. We mark the "original"
    26      * un-COW'ed pages by matching them up with "vma->vm_pgoff".
    27      * See vm_normal_page() for details.
    28      */
    29     vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;
    30 
    31     BUG_ON(addr >= end);
    32     pfn -= addr >> PAGE_SHIFT;
    33     pgd = pgd_offset(mm, addr);
    34     flush_cache_range(vma, addr, end);
    35     do {
    36         next = pgd_addr_end(addr, end);
    37         err = remap_pud_range(mm, pgd, addr, next,
    38                 pfn + (addr >> PAGE_SHIFT), prot);
    39         if (err)
    40             break;
    41     } while (pgd++, addr = next, addr != end);
    42 
    43     return err;
    44 }
    第2行,pfn是将要被映射的物理页帧号,size表示需要映射的尺寸
    第6行,计算本次映射的结尾虚拟地址
    第32行的pfn-=addr>>PAGE_SHIFT,和第38行的pfn+(addr>>PAGE_SHIFT)是为了循环处理上的便利
    第33行,计算addr在第1级页表中对应的页表项的地址,pgd_offset宏展开后是:mm->pgd + (addr >>21)
    第34行,刷新cache
    第36行,pgd_addr_end(addr, end)计算下一个将要被映射的虚拟地址,如果addr到end可以被一个pgd映射的话,那么返回end的值
    第37行的remap_pud_range的定义如下:
     1 static inline int remap_pud_range(struct mm_struct *mm, pgd_t *pgd,
     2             unsigned long addr, unsigned long end,
     3             unsigned long pfn, pgprot_t prot)
     4 {
     5     pud_t *pud;
     6     unsigned long next;
     7 
     8     pfn -= addr >> PAGE_SHIFT;
     9     pud = pud_alloc(mm, pgd, addr);
    10     if (!pud)
    11         return -ENOMEM;
    12     do {
    13         next = pud_addr_end(addr, end);
    14         if (remap_pmd_range(mm, pud, addr, next,
    15                 pfn + (addr >> PAGE_SHIFT), prot))
    16             return -ENOMEM;
    17     } while (pud++, addr = next, addr != end);
    18     return 0;
    19 }
    第9行,对于2级页表,pud_alloc(mm, pgd, addr)返回的是pgd的值
    第13行,对于2级页表,pud_addr_end(addr, end)返回end的值
    第14行,函数remap_pmd_range定义如下:
     1 static inline int remap_pmd_range(struct mm_struct *mm, pud_t *pud,
     2             unsigned long addr, unsigned long end,
     3             unsigned long pfn, pgprot_t prot)
     4 {
     5     pmd_t *pmd;
     6     unsigned long next;
     7 
     8     pfn -= addr >> PAGE_SHIFT;
     9     pmd = pmd_alloc(mm, pud, addr);
    10     if (!pmd)
    11         return -ENOMEM;
    12     VM_BUG_ON(pmd_trans_huge(*pmd));
    13     do {
    14         next = pmd_addr_end(addr, end);
    15         if (remap_pte_range(mm, pmd, addr, next,
    16                 pfn + (addr >> PAGE_SHIFT), prot))
    17             return -ENOMEM;
    18     } while (pmd++, addr = next, addr != end);
    19     return 0;
    20 }
    第9行,对于2级页表,pmd_alloc(mm, pud, addr)返回的是pud的值,其实也就是pgd的值
    第14行,对于2级页表,pmd_addr_end(addr, end)返回end的值
    第15行,函数remap_pte_range定义如下:
     1 static int remap_pte_range(struct mm_struct *mm, pmd_t *pmd,
     2             unsigned long addr, unsigned long end,
     3             unsigned long pfn, pgprot_t prot)
     4 {
     5     pte_t *pte;
     6     spinlock_t *ptl;
     7 
     8     pte = pte_alloc_map_lock(mm, pmd, addr, &ptl);
     9     if (!pte)
    10         return -ENOMEM;
    11     arch_enter_lazy_mmu_mode();
    12     do {
    13         BUG_ON(!pte_none(*pte));
    14         set_pte_at(mm, addr, pte, pte_mkspecial(pfn_pte(pfn, prot)));
    15         pfn++;
    16     } while (pte++, addr += PAGE_SIZE, addr != end);
    17     arch_leave_lazy_mmu_mode();
    18     pte_unmap_unlock(pte - 1, ptl);
    19     return 0;
    20 }
    第8行,pte_alloc_map_lock的定义如下:
    #define pte_alloc_map_lock(mm, pmd, address, ptlp)    
        (pte_alloc(mm, pmd, address) ?            
             NULL : pte_offset_map_lock(mm, pmd, address, ptlp))
    pte_alloc首先检查*pmd是否为空,如果为空的话,表示第2级页表还尚未分配,那么调用__pte_alloc分配一个页(其实是调用alloc_pages分配了一个page,也就是4KB),并将起始地址存放的*pmd中,其实就是*pgd。如果不出意外的话,pte_alloc返回0,这样pte_offset_map_lock就会被调用,返回address在第2级页表中的表项的地址
     
    第14行,调用pte_mkspecial构造第2级页表项的内容,函数set_pte_at用于将表项内容设置到pte指向的第2级页表项中
    第15行,计算下一个将要被映射的物理页帧号
    第16行,计算第2级页表项中下一个将要被填充的表项的地址
     
     
    ==
  • 相关阅读:
    Codeforces 1294E Obtain a Permutation
    Codeforces 1292C Xenon's Attack on the Gangs
    Codeforces 1292B Aroma's Search
    Codeforces 1288E Messenger Simulator
    Codeforces 1288D Minimax Problem
    Codeforces 1285E Delete a Segment
    P3368 【模板】树状数组 2 题解
    P3374 【模板】树状数组 1 题解
    P1494 [国家集训队]小Z的袜子 题解
    P2260 [清华集训2012]模积和 题解
  • 原文地址:https://www.cnblogs.com/pengdonglin137/p/8150981.html
Copyright © 2011-2022 走看看