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

简介: 内存学习(四):内存映射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内核深度解析》

目录
相关文章
|
2月前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
56 6
|
3月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
111 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
7月前
|
NoSQL Java Redis
Redis系列学习文章分享---第十八篇(Redis原理篇--网络模型,通讯协议,内存回收)
Redis系列学习文章分享---第十八篇(Redis原理篇--网络模型,通讯协议,内存回收)
91 0
|
7月前
|
缓存 Java
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
59 0
|
3月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
67 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
4月前
|
存储 缓存 Linux
用户态内存映射
【9月更文挑战第20天】内存映射不仅包括物理与虚拟内存间的映射,还涉及将文件内容映射至虚拟内存,使得访问内存即可获取文件数据。mmap 系统调用支持将文件或匿名内存映射到进程的虚拟内存空间,通过多级页表机制实现高效地址转换,并利用 TLB 加速映射过程。TLB 作为页表缓存,存储频繁访问的页表项,显著提升了地址转换速度。
|
3月前
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
70 0
|
4月前
|
存储 安全 Linux
将文件映射到内存,像数组一样访问
将文件映射到内存,像数组一样访问
44 0
|
5月前
|
存储 JavaScript 前端开发
学习JavaScript 内存机制
【8月更文挑战第23天】学习JavaScript 内存机制
44 3
|
4月前
|
消息中间件 Linux 容器
共享内存的创建和映射过程
【9月更文挑战第1天】消息队列、共享内存及信号量在使用前需生成key并获取唯一ID,均通过`xxxget`函数实现。

热门文章

最新文章