Post

sys_mincore

简介

sys_mincore 是 Linux 内核中实现 mincore 系统调用的函数, 位于内核源码中。它主要用于检查用户态传递的内存地址范围中 哪些页面驻留在物理内存中(即是否被分页到内存中)。

这里我们需要注意

驻留在物理内存和是否建立映射关系是两回事,例如,我们 mmap了一段内存到文件中,该文件在内存中有全部offset的 pagecache, 但是此时进程未访问这些虚拟地址空间,此时, 进程还未建立虚拟地址到物理地址的映射。这种情况下,mincore 也会认为这部分地址所在的page驻留在物理内存中。

原理

mincore的原理很简单,就是去walk_page_range(), 然后, 在 walk_page_range的hook中,去判断,这段空间中的page是否 驻留在内存中。主要区分以下三种类型page:

  • swapcache
  • pagecache
  • anonymous

代码流程大概如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
SYS_mincore:
  => 验证参数[vec, vec+len) 是否`access_ok()`
  => find_vma() <- 上面range
  => walk_page_range -- mincore_walk_ops
    => walk_page_test         ## 先不看
    => __walk_page_range
    => ops->pre_vma
    => walk_pgd_range (省略一些流程)
       => walk_p4d_range
          => walk_pud_range
             => walk_pmd_range
                => walk_pte_range
    => ops->ops_vma

由于walk_xxx_range()代码相似度高,我们对其中两个流程做分析:

  • intermediate pgtable walk - walk_pmd_range

    为啥要看这个呢,因为回调中只定义了pmd_entry, see follow

  • last pgtable walk - walk_pte_range

其中回调如下:

1
2
3
4
5
static const struct mm_walk_ops mincore_walk_ops = {
        .pmd_entry              = mincore_pte_range,
        .pte_hole               = mincore_unmapped_range,
        .hugetlb_entry          = mincore_hugetlb,
};

walk_pmd_range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
                          struct mm_walk *walk)
{
        ...
        do {
again:
                if (pmd_none(*pmd) || (!walk->vma && !walk->no_vma)) {
                        if (ops->pte_hole)
                        //=======(1)====
                                err = ops->pte_hole(addr, next, depth, walk);
                        if (err)
                                break;
                        continue;
                }
                ...
                //=======(2)====
                if (ops->pmd_entry)
                        err = ops->pmd_entry(pmd, addr, next, walk);
                ...
                //=======(3)====
                err = walk_pte_range(pmd, addr, next, walk);
                if (err)
                        break;
        } while (pmd++, addr = next, addr != end);

        return err;
}
  1. 这个表示整个pmd 是none, 没有建立映射, 此时为中间页表,大概有两种情况:
    • anonymous page: 整个range,没有驻留的page
    • pagecache: 需要根据该range在pagecache中的index范围,去查看是否有存 在的pagecache

      代码如下:

      代码折叠
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      
      static int __mincore_unmapped_range(unsigned long addr, unsigned long end,
                                      struct vm_area_struct *vma, unsigned char *vec)
      {
              unsigned long nr = (end - addr) >> PAGE_SHIFT;
              int i;
          
              //如果是file,则调用`mincore_page()`
              if (vma->vm_file) {
                      pgoff_t pgoff;
                      //找到在vma中的偏移
                      pgoff = linear_page_index(vma, addr);
                      for (i = 0; i < nr; i++, pgoff++)
                              //根据address_space, 查找
                              vec[i] = mincore_page(vma->vm_file->f_mapping, pgoff);
              //如果是anonymous page则直接全赋值0
              } else {
                      for (i = 0; i < nr; i++)
                              vec[i] = 0;
              }
              return nr;
      }
           
      static int mincore_unmapped_range(unsigned long addr, unsigned long end,
                                         __always_unused int depth,
                                         struct mm_walk *walk)
      {
              walk->private += __mincore_unmapped_range(addr, end,
                                                        walk->vma, walk->private);
              return 0;
      }
      

      我们在后面再展开mincore_page()内容

  2. 如果pmd不为none,则说明映射的有pte,则调用pmd_entry回调, 遍历pte table, 查看每个 pte所指向的空间,是否驻留page

    代码如下:

    代码折叠
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    static int mincore_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,
                            struct mm_walk *walk)
    {
            ...
            for (; addr != end; ptep++, addr += PAGE_SIZE) {
                    if (pte_none(pte))
                            //===(1)===
                            __mincore_unmapped_range(addr, addr + PAGE_SIZE,
                                                     vma, vec);
                            //===(2)===
                    else if (pte_present(pte))
                            *vec = 1;
                    else { /* pte is a swap entry */
                            //===(3)===
                            swp_entry_t entry = pte_to_swp_entry(pte);
       
                            if (non_swap_entry(entry)) {
                                    /*
                                     * migration or hwpoison entries are always
                                     * uptodate
                                     */
                                    *vec = 1;
                            } else {
    #ifdef CONFIG_SWAP
                                    *vec = mincore_page(swap_address_space(entry),
                                                        swp_offset(entry));
    #else
                                    WARN_ON(1);
                                    *vec = 1;
    #endif
                            }
                    }
                    vec++;
            }
            ...
    }
    
    1. 进一步判断pagecache
    2. 说明已经映射
    3. 进一步判断swapcache, 这里会从pte中获取swp entry,然后根据entry获取到所在的swap_addres_space 以及在其中的offset, 这里不再展开
  3. 继续深层遍历

mincore_page

该部分代码比较简单, 不展开代码

1
2
3
4
mincore_page:
  => page = find_get_page()
  => present = PageUptodate(page);
  => return present

find_get_page()的作用是,利用address_space以及其offset,获取到相应的page, 而PG_updtodate 则表示这个page中的内容是合法的,例如如果磁盘I/O error 发生时,虽然为其建立了相应的pagecache, 但是其中的内容和磁盘上是不一致的, 此时这种pagecache不被统计在内

1
2
PG_uptodate tells whether the page's contents is valid.  When a read
completes, the page becomes uptodate, unless a disk I/O error happened.
This post is licensed under CC BY 4.0 by the author.