内存学习(四):内存映射3

创建内存映射

C标准库封装了函数mmap用来创建内存映射,内核提供了POSIX标准定义的系统调用mmap:

1-sys_mmap

    asmlinkage long sys_mmap(unsigned long addr, unsigned long len,
                  unsigned long prot, unsigned long flags,
                  unsigned long fd, off_t off);

Linux内核从2.3.31版本开始提供私有的系统调用mmap2:

    asmlinkage long sys_mmap2(unsigned long addr, unsigned long len,
                  unsigned long prot, unsigned long flags,
                  unsigned long fd, off_t off);

两个系统调用的区别是:

  • **mmap指定的偏移的单位是字节,而mmap2指定的偏移的单位是页。**有的处理器架构实现了这两个系统调用,有的处理器架构只实现了其中一个系统调用,例如ARM64架构只实现了系统调用mmap。

系统调用sys_mmap的执行流程如图3.13所示。

在这里插入图片描述

  • (1)检查偏移是不是页的整数倍,如果偏移不是页的整数倍,返回“-EINVAL”。
  • (2)如果偏移是页的整数倍,那么把偏移转换成以页为单位的偏移,然后调用函数sys_mmap_pgoff。

2-sys_mmap_pgoff

函数sys_mmap_pgoff的执行流程如下。

  • (1)如果是创建文件映射,根据文件描述符在进程的打开文件表中找到file实例。

  • (2)如果是创建匿名巨型页映射,在hugetlbfs文件系统中创建文件“anon_hugepage”,并且创建该文件的一个打开实例file。注意:文件名没有实际意义,创建匿名巨型页映射两次,就会在hugetlbfs文件系统中创建两个名为“anon_hugepage”的文件,这两个文件没有关联。

  • (3)调用函数vm_mmap_pgoff进行处理。

3-vm_mmap_pgoff

函数vm_mmap_pgoff的执行流程如下。

  • (1)以写者身份申请读写信号量mm->mmap_sem。
  • (2)把创建内存映射的主要工作委托给函数do_mmap。
  • (3)释放读写信号量mm->mmap_sem。
  • (4)如果调用者要求把页锁定在内存中,或者要求填充页表并且允许阻塞,那么调用函数mm_populate,分配物理页,并且在页表中把虚拟页映射到物理页。

常见的情况是:创建内存映射的时候不分配物理页,等到进程第一次访问虚拟页的时候,生成页错误异常,页错误异常处理程序分配物理页,在页表中把虚拟页映射到物理页。

4-do_mmap

函数do_mmap实现创建内存映射的主要工作,执行流程如图所示。

在这里插入图片描述

  • (1)调用函数get_unmapped_area,从进程的虚拟地址空间分配一个虚拟地址范围。函数get_unmapped_area根据情况调用特定函数以分配虚拟地址范围。

    • 1)如果是创建文件映射或匿名巨型页映射,那么调用file->f_op->get_unmapped_area以分配虚拟地址范围。
    • 2)如果是创建共享的匿名映射,那么调用shmem_get_unmapped_area以分配虚拟地址范围。
    • 3)如果是创建私有的匿名映射,那么调用mm->get_unmapped_area以分配虚拟地址范围。ARM64架构的内核在装载程序时,如果选择传统布局,函数arch_pick_mmap_layout把mm->get_unmapped_area设置为函数arch_get_unmapped_area。
  • (2)计算虚拟内存标志。

      vm_flags |= calc_vm_prot_bits(prot, pkey) | calc_vm_flag_bits(flags) |
                mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;

把系统调用中指定的保护位和标志合并到一个标志集合中,函数calc_vm_prot_bits把以“PROT_”开头的保护位转换成以“VM_”开头的标志,函数calc_vm_flag_bits把以“MAP_”开头的标志转换成以“VM_”开头的标志。

mm->def_flags是默认的虚拟内存标志:进程默认的虚拟内存标志是VM_NOHUGEPAGE,即不使用透明巨型页;内核线程默认的虚拟内存标志是0。

VM_MAYREAD表示允许设置标志VM_READ, VM_MAYWRITE表示允许设置标志VM_WRITE, VM_MAYEXEC表示允许设置标志VM_EXEC。这3个标志是系统调用mprotect所需要的。

  • (3)调用函数mmap_region以创建虚拟内存区域。

5-mmap_region

  • (1)调用函数may_expand_vm以检查进程申请的虚拟内存是否超过限制。

    首先检查(进程的虚拟内存总数 + 申请的页数)是否超过地址空间限制:mm->total_vm +npages > rlimit(RLIMIT_AS)>> PAGE_SHIFT。
    如果是私有的可写映射,并且不是栈,那么检查(进程数据的虚拟内存总数 + 申请的页数)是否超过最大数据长度:mm->data_vm + npages > rlimit(RLIMIT_DATA) >>PAGE_SHIFT。

  • (2)如果是固定映射,调用者强制指定虚拟地址范围,可能和旧的虚拟内存区域重叠,那么需要从旧的虚拟内存区域删除重叠的部分。

  • (3)如果是私有的可写映射,检查所有进程申请的虚拟内存的总和是否超过物理内存的容量。

      /*
        * 如果是需要记账的映射,那么检查所有进程申请的虚拟内存的总和是否超过物理内存的容量。
        * 需要记账的映射具备以下3个条件。
        * (1)私有的可写映射。
        * (2)不是标准巨型页(因为标准巨型页单独记账)。
        * (3)需要预留物理内存(即未设置VM_NORESERVE)。
        */
      if (accountable_mapping(file, vm_flags)) {
            charged = len >> PAGE_SHIFT;
            /* 根据虚拟内存过量提交的策略,判断物理内存是否足够。*/
            if (security_vm_enough_memory_mm(mm, charged))
                return -ENOMEM;
            vm_flags |= VM_ACCOUNT;
      }
  • (4)如果可以和已有的虚拟内存区域合并,那么调用函数vma_merge,和已有的虚拟内存区域合并。

  • (5)如果不能和已有的虚拟内存区域合并,处理如下。

    • 1)创建新的虚拟内存区域。
    • 2)如果是文件映射,那么调用文件的文件操作集合中的mmap方法(file->f_op->mmap), mmap方法的主要功能是设置虚拟内存区域的虚拟内存操作集合(vm_area_struct.vm_ops),其中的fault方法很重要:第一次访问虚拟页的时候,触发页错误异常,异常处理程序将调用虚拟内存操作集合中的fault方法以把文件的数据读到内存。
    • 文件的文件操作集合是在打开文件的时候设置的,和文件所属的文件系统相关。
    • 很多文件系统把文件操作集合中的mmap方法设置为公共函数generic_file_mmap,函数generic_file_mmap的主要功能是把虚拟内存区域的虚拟内存操作集合设置为generic_file_vm_ops,其中fault方法是函数filemap_fault。
    • EXT4文件系统把文件操作集合中的mmap方法设置为函数ext4_file_mmap,函数ext4_file_mmap的主要功能是把虚拟内存区域的虚拟内存操作集合设置为ext4_file_vm_ops,其中fault方法是函数ext4_filemap_fault。
    • 3)如果是共享的匿名映射,那么在内存文件系统tmpfs中创建一个名为“/dev/zero”的文件,并且创建文件的一个打开实例file,虚拟内存区域的成员vm_file指向这个打开实例,把虚拟内存操作集合设置为shmem_vm_ops。如果没有开启共享内存的配置宏CONFIG_SHMEM, shmem_vm_ops等价于generic_file_vm_ops。
    • 4)调用函数vma_link,把虚拟内存区域添加到链表和红黑树中。如果虚拟内存区域关联文件,那么把虚拟内存区域添加到文件的区间树中,文件的区间树用来跟踪文件被映射到哪些虚拟内存区域。
    • 5)调用函数vma_set_page_prot,根据虚拟内存标志(vma->vm_flags)计算页保护位(vma-> vm_page_prot),如果共享的可写映射想要把页标记为只读,目的是跟踪写事件,那么从页保护位删除可写位。

内容来自前辈书籍:《Linux内核深度解析》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TrustZone_Hcoco

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值