笔者水平较低,如有错误欢迎各位看官老爷指正,Thanks♪(・ω・)ノ

笔者忽然高产了起来,原因是因为笔者总算是搞明白了一个近几天一直困扰自己的问题,为什么网上一些帖子说do_anonymous_page只用来处理私有匿名映射呢?

看到这个函数,下意识就会觉得其实它是用来处理所有匿名映射的才对啊。

话不多说,直接上代码

do_pte_missing()

这个函数是缺页异常处理流程中的一个函数,该函数会根据映射的类型(私有/共享,匿名/文件)来选择合适的处理方式。

static vm_fault_t do_pte_missing(struct vm_fault *vmf)
{
        if (vma_is_anonymous(vmf->vma))
                return do_anonymous_page(vmf);
        else
                return do_fault(vmf);
}

下面我们来看一下什么情况下会选择走do_anonymous_page()

vma_is_anonymous()

这个函数很简单,就是返回出现缺页的VMA所对应的vm_ops,如果vm_ops为空,返回真,执行do_anonymous_page()

static inline bool vma_is_anonymous(struct vm_area_struct *vma)
{
        return !vma->vm_ops;
}

也就是说,vm_ops是否为空是决定这个映射是否是私有匿名映射的重要标志,在这个函数相邻的正上方,我们可以发现一个与这个函数长得非常接近但是含义完全相反的另外一个函数:

vma_set_anonymous()

很明显这个函数就是用来将VMA的vm_ops设置为NULL的!这可是个大发现,通过这个函数,说不定就能顺藤摸瓜到设置不同类型映射vm_ops的地方了。

补充一嘴,这两个函数均位于/include/linux/mm.h中

static inline void vma_set_anonymous(struct vm_area_struct *vma)
{
        vma->vm_ops = NULL;
}

mmap_region()

笔者很快就发现是mmap_region()函数调用了vma_set_anonymous()这个函数,mmap_region()是mmap()的核心函数,mmap()又是Linux 用户态内存分配的一个非常重要的函数。

剔除掉与本文关系不大的代码,剩下一个if else代码块。

先看第一个if语句,如果file不为空,也就是文件映射的情况下,会调用call_mmap进行下一步工作

つまり,下面的两个else处理的都是匿名映射的逻辑。

如果是共享匿名映射(设置了VM_SHARED),会走下面shmem_zero_setup的逻辑,嘿,这是啥?这个shmem,看着好眼熟,原来之前学Linux应用的时候学过,是一种进程间通信的手段,底层基于虚拟文件,因此其实已经不能算是传统的匿名映射了!

这里就可以确认,vma_set_anonymous()实际上就仅仅是针对 私有匿名 映射而言的,共享匿名映射已经在上一个else if被过滤了!

unsigned long mmap_region(struct file *file, unsigned long addr,
                unsigned long len, vm_flags_t vm_flags, unsigned long pgoff,
                struct list_head *uf)
{
    
       ......
        if (file) {
            ...........
            error = call_mmap(file, vma);
            ...........
        } else if (vm_flags & VM_SHARED) {
                error = shmem_zero_setup(vma);
                if (error)
                        goto free_vma;
        } else {
                vma_set_anonymous(vma);
        }
}

call_mmap()

对于文件映射,实际上就是调用对应文件file_operations里的mmap函数,函数内部会设置vma的vm_ops。

static inline int call_mmap(struct file *file, struct vm_area_struct *vma)
{
        return file->f_op->mmap(file, vma);
}

以ext4文件系统为例:

ext4_file_mmap()

该函数内部会绑定vma->vm_ops与自己的ops

static int ext4_file_mmap(struct file *file, struct vm_area_struct *vma)
{
      ...........
        file_accessed(file);
        if (IS_DAX(file_inode(file))) {
                vma->vm_ops = &ext4_dax_vm_ops;
                vm_flags_set(vma, VM_HUGEPAGE);
        } else {
                vma->vm_ops = &ext4_file_vm_ops;
        }
        return 0;
}

erofs_file_mmap()

Erofs同理。

static int erofs_file_mmap(struct file *file, struct vm_area_struct *vma)
{
        ...............
        vma->vm_ops = &erofs_dax_vm_ops;
        vm_flags_set(vma, VM_HUGEPAGE);
        return 0;
}

其他文件系统也类似,因此文件映射的vm_ops是不为空的。

shmem_zero_setup()

我们来进一步确认一下,此时共享匿名映射是如何被处理的

可以看到代码的下方,vma->ops会被绑定为shmem特有的shmem_anon_vm_ops

然后这个特有的又会在/mm/shmem.c被定义为generic_file_vm_ops,通用的文件映射处理函数,说明共享匿名映射确实背叛了匿名映射的组织,《你为什么用着文件映射的函数啊?》

那么更加可以确定,共享匿名映射(其实已经不该称呼它为共享匿名映射了),还是叫它shmem吧,shmem的vm_ops是存在的,不为空。

#define shmem_anon_vm_ops                        generic_file_vm_ops

在/mm/filemap.c中,generic_file_vm_ops被定义为

const struct vm_operations_struct generic_file_vm_ops = {
        .fault                = filemap_fault,
        .map_pages        = filemap_map_pages,
        .page_mkwrite        = filemap_page_mkwrite,
};
int shmem_zero_setup(struct vm_area_struct *vma)
{
        struct file *file;
        .......
        if (vma->vm_file)
                fput(vma->vm_file);
        vma->vm_file = file;
        vma->vm_ops = &shmem_anon_vm_ops;

        return 0;
}

SUM

总结,在mmap()流程中的mmap_region()函数里,文件映射、共享匿名映射的VMA对应的vm_ops都会被设置,因此不为空,只有私有匿名映射的vm_ops()才会被设置为NULL。

所以最终只有私有匿名映射能在vma_is_anonymous()中被过滤出来。

因此可以确认,do_anonymous_page()仅用来处理私有匿名映射。