Linux内存管理 mmap(补充)

Posted Arnold Lu@南京

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内存管理 mmap(补充)相关的知识,希望对你有一定的参考价值。

 

之前写过一篇简单的介绍mmap()/munmap()的文章《Linux内存管理 (9)mmap》,比较单薄,这里详细的梳理一下。

从常用的使用者角度介绍两个函数的使用;然后重点是分析内核的实现流程;最后对mmap()/munmap()进行一些验证测试。

 

mmap系统调用并不完全是为了共享内存而设计的,它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件操作。

mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read()/write()等操作。

mmap并不分配空间,只是将文件映射到调用进程的地址空间里(占用虚拟地址空间),然后就可以使用memcpy()等操作,内存中内容并不立即更行到文件中,而是有一段时间的延迟,可以使用msync()显式同步。

取消内存映射通过munmap()。

下面这张图示意了mmap的内存映射,起始地址是返回的addr,off和len分别对应参数offset和length。

1. mmap API解释

 对mmap()/munmap()的使用比较简单,有两个参数组合导致了多样性,分别是protflags

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);

下面对这些参数做一个简单的介绍:

  • addr:如果不为NULL,内核会在此地址创建映射;否则,内核会选择一个合适的虚拟地址。大部分情况不指定虚拟地址,意义不大,而是让内核选择返回一个地址给用户空间使用。
  • length:表示映射到进程地址空间的大小。
  • prot:内存区域的读/写/执行属性。
  • flags:内存映射的属性,共享、私有、匿名、文件等。
  • fd:表示这是一个文件映射,fd是打开文件的句柄。如果是文件映射,需要指定fd;匿名映射就指定一个特殊的-1。
  • offset:在文件映射时,表示相对文件头的偏移量;返回的地址是偏移量对应的虚拟地址。

 

1.1 mmap优点

1.1.1 提升效率

一般读写文件需要open、read、write,需要先将磁盘文件读取到内核cache缓冲区,然后再拷贝到用户空间内存区,设计两次读写操作。

mmap通过将磁盘文件映射到用户空间,当进程读文件时,发生缺页中断,给虚拟内存分配对应的物理内存,在通过磁盘调页操作将磁盘数据读到物理内存上,实现了用户空间数据的读取,整个过程只有一次内存拷贝。

1.1.2 用于进程间大数据量通信

两个进程映射同一个文件,在两个进程中,同一个文件区域映射的虚拟地址空间不同。一个进程操作文件时,先通过缺页获取物理内存,进而通过磁盘文件调页操作将文件数据读入内存。

另一个进程访问文件的时候,发现没有物理页面映射到虚拟内存,通过fs的缺页处理查找cache区是否有读入磁盘文件,有的话建立映射关系,这样两个进程通过共享内存就可以进行通信。

1.1.3 文件关闭,内存可以继续使用

因为在内核中已经通过fd找到对应的磁盘文件,从而将文件跟vma关联。

 

1.2 mmap缺点

映射时文件长度已经确定,没法通过mmap访问操作len的区间。

1.3 私有/共享、文件/匿名映射组合

共有四种组合,下面逐一介绍。

1.3.1 私有文件映射

多个进程使用同样的物理页面进行初始化,但是各个进程对内存文件的修改不会共享,也不会反映到物理文件中。

比如对linux .so动态库文件就采用这种方式映射到各个进程虚拟地址空间中。

1.3.2 私有匿名映射

mmap会创建一个新的映射,各个进程不共享,主要用于分配内存(malloc分配大内存会调用mmap)。

1.3.3 共享文件映射

多个进程通过虚拟内存技术共享同样物理内存,对内存文件的修改会反应到实际物理内存中,也是进程间通信的一种。

1.3.4 共享匿名映射

这种机制在进行fork时不会采用写时复制,父子进程完全共享同样的物理内存页,也就是父子进程通信。

2. mmap内核实现

系统调用的入口是entry_SYSCALL_64_fastpath,然后根据系统调用号在sys_call_table中找到对应的函数。

mmap()和munmap()对应的系统调用分别是SyS_mmap()和SyS_munmap()下面就来分析一下实现。

2.0 mmap/munmap调用路径

在分析具体内核实现之前,通过脚本来看看mmap/munmap调用路径。

通过增加set_ftrace_filter的函数,修改current_tracer发现函数的调用者,逐步丰富调用路径。

#!/bin/bash 
 
DPATH="/sys/kernel/debug/tracing"
PID=$$ 
## Quick basic checks 
[ `id -u` -ne 0 ] && { echo "needs to be root" ; exit 1; } # check for root permissions 
[ -z $1 ] && { echo "needs process name as argument" ; exit 1; } # check for args to this function 
mount | grep -i debugfs &> /dev/null 
[ $? -ne 0 ] && { echo "debugfs not mounted, mount it first"; exit 1; } #checks for debugfs mount 
 
# flush existing trace data 
echo > $DPATH/trace
echo nop > $DPATH/current_tracer

echo > $DPATH/set_ftrace_filter
echo "SyS_mmap SyS_mmap_pgoff SyS_munmap SyS_open SyS_read SyS_write SyS_close SyS_brk SyS_msync" >> $DPATH/set_ftrace_filter
echo "do_brk elf_map load_elf_binary" >> $DPATH/set_ftrace_filter
echo "do_mmap do_munmap get_unmapped_area mmap_region vm_mmap vm_munmap vm_mmap_pgoff" >> $DPATH/set_ftrace_filter
echo "__split_vma* unmap_region" >> $DPATH/set_ftrace_filter

# set function tracer
echo function_graph > $DPATH/current_tracer



# write current process id to set_ftrace_pid file
echo $PID > $DPATH/set_ftrace_pid


#echo "common_pid==$PID" > /sys/kernel/debug/tracing/events/syscalls/sys_enter_mmap/filter
#echo 1 > /sys/kernel/debug/tracing/events/syscalls/sys_enter_mmap/enable
#echo "common_pid==$PID" > /sys/kernel/debug/tracing/events/syscalls/sys_enter_munmap/filter
#echo 1 > /sys/kernel/debug/tracing/events/syscalls/sys_enter_munmap/enable

# start the tracing
echo 1 > $DPATH/tracing_on
# execute the process
exec $*


#sudo cat $DPATH/trace > /home/al/v4l2/trace.txt

 

最后使用function_graph跟踪器查看调用关系如下:

 1)               |  SyS_mmap() {
 1)               |    SyS_mmap_pgoff() {
 1)               |      vm_mmap_pgoff() {
 1)               |        do_mmap() {
 1)   0.548 us    |          get_unmapped_area();
 1)   3.388 us    |          mmap_region();
 1)   4.598 us    |        }
 1)   5.286 us    |      }
 1)   5.756 us    |    }
 1)   6.058 us    |  }
 1)               |  SyS_munmap() {
 1)               |    vm_munmap() {
 1)               |      do_munmap() {
 1) + 99.985 us   |        unmap_region();
 1) ! 101.439 us  |      }
 1) ! 101.838 us  |    }
 1) ! 102.410 us  |  }

下面就围绕这条路径展开分析。

2.1 mmap()

mmap()系统调用的核心是do_mmap(),可以分为三部分。

第一部分通过get_unmapped_area()函数,找到一段虚拟地址,范围是[addr, addr+len]。

用户进程一般不会指定addr,也就是由内核指定这个虚拟空间的首地址addr在哪里。

在函数do_mmap_pgoff()调用get_unmapped_area()之前会预指定addr,通过round_hint_to_min()实现,然后用这个预指定addr为参数调用get_unmapped_area()。

 

第二部分确定vma线性区的flags,针对文件、匿名,私有、共享有所不同。

 

第三部分是实际创建vma先行区,通过函数mmap_region()实现。

 

asmlinkage unsigned long
sys_mmap (unsigned long addr, unsigned long len, int prot, int flags, int fd, long off)
{
    if (offset_in_page(off) != 0)
        return -EINVAL;

    addr = sys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
    if (!IS_ERR((void *) addr))
        force_successful_syscall_return();
    return addr;
}

SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len,
        unsigned long, prot, unsigned long, flags,
        unsigned long, fd, unsigned long, pgoff)
{
    struct file *file = NULL;
    unsigned long retval;

    if (!(flags & MAP_ANONYMOUS)) {------------------------------------------对非匿名文件映射的检查,必须能根据文件句柄找到struct file。
        audit_mmap_fd(fd, flags);
        file = fget(fd);
        if (!file)
            return -EBADF;
        if (is_file_hugepages(file))
            len = ALIGN(len, huge_page_size(hstate_file(file)));-------------根据file->f_op来判断是否是hugepage,然后进行hugepage页面对齐。
        retval = -EINVAL;
        if (unlikely(flags & MAP_HUGETLB && !is_file_hugepages(file)))
            goto out_fput;
    } else if (flags & MAP_HUGETLB) {
        struct user_struct *user = NULL;
        struct hstate *hs;

        hs = hstate_sizelog((flags >> MAP_HUGE_SHIFT) & SHM_HUGE_MASK);
        if (!hs)
            return -EINVAL;

        len = ALIGN(len, huge_page_size(hs));
        /*
         * VM_NORESERVE is used because the reservations will be
         * taken when vm_ops->mmap() is called
         * A dummy user value is used because we are not locking
         * memory so no accounting is necessary
         */
        file = hugetlb_file_setup(HUGETLB_ANON_FILE, len,
                VM_NORESERVE,
                &user, HUGETLB_ANONHUGE_INODE,
                (flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK);
        if (IS_ERR(file))
            return PTR_ERR(file);
    }

    flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);

    retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
out_fput:
    if (file)
        fput(file);
    return retval;
}

unsigned long vm_mmap_pgoff(struct file *file, unsigned long addr,
    unsigned long len, unsigned long prot,
    unsigned long flag, unsigned long pgoff)
{
    unsigned long ret;
    struct mm_struct *mm = current->mm;
    unsigned long populate;

    ret = security_mmap_file(file, prot, flag);
    if (!ret) {
        down_write(&mm->mmap_sem);
        ret = do_mmap_pgoff(file, addr, len, prot, flag, pgoff,
                    &populate);
        up_write(&mm->mmap_sem);
        if (populate)
            mm_populate(ret, populate);
    }
    return ret;
}

unsigned long do_mmap(struct file *file, unsigned long addr,
            unsigned long len, unsigned long prot,
            unsigned long flags, vm_flags_t vm_flags,
            unsigned long pgoff, unsigned long *populate)
{
    struct mm_struct *mm = current->mm;

    *populate = 0;

    if (!len)
        return -EINVAL;

    if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))
        if (!(file && path_noexec(&file->f_path)))
            prot |= PROT_EXEC;

    if (!(flags & MAP_FIXED))-------------------------------------------------对于非MAP_FIXED,addr不能小于mmap_min_addr大小,如果小于则使用mmap_min_addr页对齐后的地址。
        addr = round_hint_to_min(addr);

    /* Careful about overflows.. */
    len = PAGE_ALIGN(len);
    if (!len)-----------------------------------------------------------------这里不是判断len是否为0,而是检查len是否溢出。
        return -ENOMEM;

    /* offset overflow? */
    if ((pgoff + (len >> PAGE_SHIFT)) < pgoff)--------------------------------检查offset是否溢出
        return -EOVERFLOW;

    /* Too many mappings? */
    if (mm->map_count > sysctl_max_map_count)---------------------------------进程中mmap个数限制,超出返回ENOMEM错误。
        return -ENOMEM;
    addr = get_unmapped_area(file, addr, len, pgoff, flags);------------------在创建新的ma区域之前首先寻找一块足够大小的空闲区域,本函数就是用于查找未映射的区域,返回值addr就是这段空间的首地址。
    if (offset_in_page(addr))
        return addr;

    vm_flags |= calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) |
            mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;------------根据prot/flags以及mm->flags来得到vm_flags。

    if (flags & MAP_LOCKED)
        if (!can_do_mlock())
            return -EPERM;

    if (mlock_future_check(mm, vm_flags, len))
        return -EAGAIN;

    if (file) {---------------------------------------------------------------文件映射情况处理,主要更新vm_flags。
        struct inode *inode = file_inode(file);

        if (!file_mmap_ok(file, inode, pgoff, len))
            return -EOVERFLOW;

        switch (flags & MAP_TYPE) {
        case MAP_SHARED:------------------------------------------------------共享文件映射
            if ((prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE))
                return -EACCES;
            if (IS_APPEND(inode) && (file->f_mode & FMODE_WRITE))
                return -EACCES;
            if (locks_verify_locked(file))
                return -EAGAIN;
            vm_flags |= VM_SHARED | VM_MAYSHARE;
            if (!(file->f_mode & FMODE_WRITE))
                vm_flags &= ~(VM_MAYWRITE | VM_SHARED);
        case MAP_PRIVATE:-----------------------------------------------------私有文件映射
            if (!(file->f_mode & FMODE_READ))
                return -EACCES;
            if (path_noexec(&file->f_path)) {
                if (vm_flags & VM_EXEC)
                    return -EPERM;
                vm_flags &= ~VM_MAYEXEC;
            }
            if (!file->f_op->mmap)
                return -ENODEV;
            if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
                return -EINVAL;
            break;
        default:
            return -EINVAL;
        }
    } else {------------------------------------------------------------------匿名映射情况处理
        switch (flags & MAP_TYPE) {
        case MAP_SHARED:------------------------------------------------------共享匿名映射
            if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
                return -EINVAL;
            pgoff = 0;--------------------------------------------------------为什么为0?
            vm_flags |= VM_SHARED | VM_MAYSHARE;
            break;
        case MAP_PRIVATE:-----------------------------------------------------私有匿名映射
            pgoff = addr >> PAGE_SHIFT;
            break;
        default:
            return -EINVAL;
        }
    }
    if (flags & MAP_NORESERVE) {
        /* We honor MAP_NORESERVE if allowed to overcommit */
        if (sysctl_overcommit_memory != OVERCOMMIT_NEVER)
            vm_flags |= VM_NORESERVE;

        /* hugetlb applies strict overcommit unless MAP_NORESERVE */
        if (file && is_file_hugepages(file))
            vm_flags |= VM_NORESERVE;
    }

    addr = mmap_region(file, addr, len, vm_flags, pgoff);--------------------实际创建vma
    if (!IS_ERR_VALUE(addr) &&
        ((vm_flags & VM_LOCKED) ||
         (flags & (MAP_POPULATE | MAP_NONBLOCK)) == MAP_POPULATE))
        *populate = len;
    return addr;
}

get_unmapped_area()根据输入的addr,以及其它参数通过get_area()来找到一个满足条件的虚拟空间,返回这个虚拟空间的首地址。

get_area()是一个函数指针,有两种可能使用mm->get_unmapped_area()或者file->f_op->get_unmapped_area()。

unsigned long
get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,
        unsigned long pgoff, unsigned long flags)
{
    unsigned long (*get_area)(struct file *, unsigned long,
                  unsigned long, unsigned long, unsigned long);

    unsigned long error = arch_mmap_check(addr, len, flags);
    if (error)
        return error;

    /* Careful about overflows.. */
    if (len > TASK_SIZE)
        return -ENOMEM;

    get_area = current->mm->get_unmapped_area;------------使用mm_struct->get_unmapped_area()方法,即arch_get_unmapped_area()。
    if (file && file->f_op->get_unmapped_area)------------如果是文件映射,并且该文件的file_operations定义了get_unmapped_area方法,那么使用它实现定位虚拟区间。
        get_area = file->f_op->get_unmapped_area;
    addr = get_area(file, addr, len, pgoff, flags);
    if (IS_ERR_VALUE(addr))
        return addr;

    if (addr > TASK_SIZE - len)
        return -ENOMEM;
    if (offset_in_page(addr))
        return -EINVAL;

    addr = arch_rebalance_pgtables(addr, len);
    error = security_mmap_addr(addr);
    return error ? error : addr;
}

 

看arch_get_unmapped_area()名字就知道,可能有各架构自己的实现函数。这里以平台无关的函数进行分析。

arch_get_unmapped_area()完成从低地址向高地址创建新的映射,而arch_get_unmapped_area_topdown()完成从高地址向低地址创建新的映射。

unsigned long
arch_get_unmapped_area(struct file *filp, unsigned long addr,
        unsigned long len, unsigned long pgoff, unsigned long flags)
{
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *vma;
    int do_align = 0;
    int aliasing = cache_is_vipt_aliasing();
    struct vm_unmapped_area_info info;

    if (aliasing)
        do_align = filp || (flags & MAP_SHARED);

    if (flags & MAP_FIXED) {------------------这里可以看出MAP_FIXED不参与选址,固定地址创建。
        if (aliasing && flags & MAP_SHARED &&
            (addr - (pgoff << PAGE_SHIFT)) & (SHMLBA - 1))
            return -EINVAL;
        return addr;
    }

    if (len > TASK_SIZE)
        return -ENOMEM;

    if (addr) {--------------------------------当addr非0,表示制定了一个特定的优先选用地址,内核会检查该区域是否与现存区域重叠,有find_vma()完成查找功能。
        if (do_align)
            addr = COLOUR_ALIGN(addr, pgoff);
        else
            addr = PAGE_ALIGN(addr);

        vma = find_vma(mm, addr);
        if (TASK_SIZE - len >= addr &&
            (!vma || addr + len <= vm_start_gap(vma)))
            return addr;
    }

    info.flags = 0;
    info.length = len;
    info.low_limit = mm->mmap_base;
    info.high_limit = TASK_SIZE;
    info.align_mask = do_align ? (PAGE_MASK & (SHMLBA - 1)) : 0;
    info.align_offset = pgoff << PAGE_SHIFT;
    return vm_unmapped_area(&info);-----------当addr为空或者指定的优选地址不满足分配条件时,内核必须遍历进程中可用的区域,设法找到一个大小适当的空闲区域,vm_unmapped_area()完成实际的工作。
}

static inline unsigned long
vm_unmapped_area(struct vm_unmapped_area_info *info)
{
    if (info->flags & VM_UNMAPPED_AREA_TOPDOWN)
        return unmapped_area_topdown(info);--从高地址到低地址穿点映射。
    else
        return unmapped_area(info);----------从低地址到高地址创建映射。
}

unsigned long unmapped_area(struct vm_unmapped_area_info *info)
{
    /*
     * We implement the search by looking for an rbtree node that
     * immediately follows a suitable gap. That is,
     * - gap_start = vma->vm_prev->vm_end <= info->high_limit - length;
     * - gap_end   = vma->vm_start        >= info->low_limit  + length;
     * - gap_end - gap_start >= length
     */

    struct mm_struct *mm = current->mm;
    struct vm_area_struct *vma;
    unsigned long length, low_limit, high_limit, gap_start, gap_end;

    /* Adjust search length to account for worst case alignment overhead */
    length = info->length + info->align_mask;
    if (length < info->length)
        return -ENOMEM;

    /* Adjust search limits by the desired length */
    if (info->high_limit < length)
        return -ENOMEM;
    high_limit = info->high_limit - length;

    if (info->low_limit > high_limit)
        return -ENOMEM;
    low_limit = info->low_limit + length;

    /* Check if rbtree root looks promising */
    if (RB_EMPTY_ROOT(&mm->mm_rb))
        goto check_highest;
    vma = rb_entry(mm->mm_rb.rb_node, struct vm_area_struct, vm_rb);
    if (vma->rb_subtree_gap < length)
        goto check_highest;

    while (true) {
        /* Visit left subtree if it looks promising */
        gap_end = vm_start_gap(vma);----------------------------------先从低地址开始查询。
        if (gap_end >= low_limit && vma->vm_rb.rb_left) {
            struct vm_area_struct *left =
                rb_entry(vma->vm_rb.rb_left,
                     struct vm_area_struct, vm_rb);
            if (left->rb_subtree_gap >= length) {
                vma = left;
                continue;
            }
        }

        gap_start = vma->vm_prev ? vm_end_gap(vma->vm_prev) : 0;------当前结点rb_subtree_gap已经是最后一个可能满足这次分配。
check_current:
        /* Check if current node has a suitable gap */
        if (gap_start > high_limit)
            return -ENOMEM;
        if (gap_end >= low_limit &&
            gap_end > gap_start && gap_end - gap_start >= length)
            goto found;

        /* Visit right subtree if it looks promising */
        if (vma->vm_rb.rb_right) {
            struct vm_area_struct *right =
                rb_entry(vma->vm_rb.rb_right,
                     struct vm_area_struct, vm_rb);
            if (right->rb_subtree_gap >= length) {
                vma = right;
                continue;
            }
        }

        /* Go back up the rbtree to find next candidate node */
        while (true) {
            struct rb_node *prev = &vma->vm_rb;
            if (!rb_parent(prev))
                goto check_highest;
            vma = rb_entry(rb_parent(prev),
                       struct vm_area_struct, vm_rb);
            if (prev == vma->vm_rb.rb_left) {
                gap_start = vm_end_gap(vma->vm_prev);
                gap_end = vm_start_gap(vma);
                goto check_current;
            }
        }
    }

check_highest:
    /* Check highest gap, which does not precede any rbtree node */
    gap_start = mm->highest_vm_end;
    gap_end = ULONG_MAX;  /* Only for VM_BUG_ON below */
    if (gap_start > high_limit)
        return -ENOMEM;

found:
    /* We found a suitable gap. Clip it with the original low_limit. */
    if (gap_start < info->low_limit)
        gap_start = info->low_limit;

    /* Adjust gap address to the desired alignment */
    gap_start += (info->align_offset - gap_start) & info->align_mask;

    VM_BUG_ON(gap_start + info->length > info->high_limit);
    VM_BUG_ON(gap_start + info->length > gap_end);
    return gap_start;
}

mmap_region()首先调用find_vma_links()查找是否已有vma线性区包含addr,如果有调用do_munmap()把这个vma干掉。

Linux不希望vma和vma之间存在空洞,只要新创建vma的flags属性和前面或者后面vma仙童,就尝试合并成一个新的vma,减少slab缓存消耗量,同时也减少了空洞浪费。

如果无法合并,那么只好新创建vma并对vma结构体初始化先关成员;根据vma是否有页锁定标志(VM_LOCKED),决定是否立即分配物理页。

最后将新建的vma插入进程空间vma红黑树中,并返回addr。

unsigned long mmap_region(struct file *file, unsigned long addr,
        unsigned long len, vm_flags_t vm_flags, unsigned long pgoff)
{
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *vma, *prev;
    int error;
    struct rb_node **rb_link, *rb_parent;
    unsigned long charged = 0;

    /* Check against address space limit. */
    if (!may_expand_vm(mm, len >> PAGE_SHIFT)) {--------------------检查当前total_vm+len是否查过RLIMIT_AS,确保虚拟映射可以进行。
        unsigned long nr_pages;

        if (!(vm_flags & MAP_FIXED))
            return -ENOMEM;

        nr_pages = count_vma_pages_range(mm, addr, addr + len);

        if (!may_expand_vm(mm, (len >> PAGE_SHIFT) - nr_pages))
            return -ENOMEM;
    }

    while (find_vma_links(mm, addr, addr + len, &prev, &rb_link,
                  &rb_parent)) {-----------------------------------遍历该进程已有的vma红黑树,如果找到vma覆盖[addr, end]区域,那么返回0,表示找到。如果覆盖已有的vma区域,返回ENOMEM。
        if (do_munmap(mm, addr, len))------------------------------存在覆盖已有区域的情况,那么尝试取munmap这块区域。如果munmap成功返回0,不成功则mmap_region()失败。
            return -ENOMEM;
    }

    if (accountable_mapping(file, vm_flags)) {
        charged = len >> PAGE_SHIFT;
        if (security_vm_enough_memory_mm(mm, charged))
            return -ENOMEM;
        vm_flags |= VM_ACCOUNT;
    }

    vma = vma_merge(mm, prev, addr, addr + len, vm_flags,
            NULL, file, pgoff, NULL, NULL_VM_UFFD_CTX);-----------------------至此表示已经可以找到合适的vma区域,原有映射是否可以被新的映射复用,减少因为vma导致的slab消耗和虚拟内存的空洞。
    if (vma)
        goto out;

    vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);----------------------在没有找到的情况下,新建一个vma。
    if (!vma) {
        error = -ENOMEM;
        goto unacct_error;
    }

    vma->vm_mm = mm;---------------------------------------------------------初始化vma数据
    vma->vm_start = addr;
    vma->vm_end = addr + len;
    vma->vm_flags = vm_flags;
    vma->vm_page_prot = vm_get_page_prot(vm_flags);---------------------------设置vma区域内页面属性。
    vma->vm_pgoff = pgoff;
    INIT_LIST_HEAD(&vma->anon_vma_chain);

    if (file) {--------------------------------------------------------------如果是文件映射
        if (vm_flags & VM_DENYWRITE) {
            error = deny_write_access(file);
            if (error)
                goto free_vma;
        }
        if (vm_flags & VM_SHARED) {
            error = mapping_map_writable(file->f_mapping);
            if (error)
                goto allow_write_and_free_vma;
        }

        vma->vm_file = get_file(file);
        error = file->f_op->mmap(file, vma);---------------------------------Linux 内核 内存管理内存管理系统调用 ④ ( 代码示例 | mmap 创建内存映射 | munmap 删除内存映射 )

Linux内存管理之mmap详解

Linux 内核 内存管理mmap 系统调用源码分析 ① ( mmap 与 mmap2 系统调用 | Linux 内核中的 mmap 系统调用源码 )

Linux内存管理之mmap详解 (可用于android底层内存调试)

Linux内存管理之mmap详解

Linux 内核 内存管理内存管理系统调用 ① ( mmap 创建内存映射 | munmap 删除内存映射 | mprotect 设置虚拟内存区域访问权限 )