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;
}
- 这个表示整个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()
内容
如果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++; } ... }
- 进一步判断pagecache
- 说明已经映射
- 进一步判断swapcache, 这里会从pte中获取swp entry,然后根据entry获取到所在的
swap_addres_space
以及在其中的offset, 这里不再展开
- 继续深层遍历
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.