低价网站建设公司,常熟市建设局网站,青岛专业建设网站,网站设计这个专业怎么样内存映射(mmap)是 Linux 内核的一个重要机制#xff0c;它为程序提供了一种将文件内容直接映射到进程虚拟地址空间的方式。同时内存映射也是虚拟内存管理和文件 IO 的重要组成部分。
在 Linux 中#xff0c;虚拟内存管理是基于内存映射来实现的。在调用 mmap 函数时#xf…内存映射(mmap)是 Linux 内核的一个重要机制它为程序提供了一种将文件内容直接映射到进程虚拟地址空间的方式。同时内存映射也是虚拟内存管理和文件 IO 的重要组成部分。
在 Linux 中虚拟内存管理是基于内存映射来实现的。在调用 mmap 函数时会创建一个 vm_area_struct 结构体该结构体代表了一段连续的虚拟地址空间它们会相应地映射到一个后备文件或者一个匿名文件的虚拟页。
一个 vm_area_struct 结构体映射到一组连续的页表项这些页表项指向物理内存中的一页。这样就把一个文件和物理内存页相映射起来。当进程试图访问映射到 vm_area_struct 的虚拟地址空间时如果该空间没有在内存中则会发生缺页异常内核会通过文件系统将文件中对应的数据读入内存。
内存映射的优点是可以有效地减少文件 IO 的次数提高文件读写性能。同时内存映射还支持多进程共享同一个映射可以节省内存空间并且方便不同的进程之间进行通信。
虚拟地址映射的过程涉及到对 vm_area_struct 的匹配以及对页表项的查找和操作。具体来说当程序访问一个虚拟地址时系统会根据已有的 vm_area_struct 结构来确定这个虚拟地址是否属于某个区域。
如果没有匹配到相应的 vm_area_struct就会触发段错误因为访问了一个未分配的虚拟地址这表示程序在访问一个未经内核分配的内存区域是非法的操作。
如果匹配到了相应的 vm_area_struct系统会根据虚拟地址和页表的映射关系找到对应的页表项PTE。如果 PTE 没有分配就会触发缺页异常此时系统会将相应的文件数据加载到物理内存中如果 PTE 已经分配就可以直接从对应的物理页的偏移位置读取数据。
在这个过程中虚拟页有三种状态
未分配虚拟页指的是没有使用 mmap 建立相应的 vm_area_struct因此也就没有对应到具体的页表项。已分配虚拟页未映射到物理页表示已经使用了 mmap 建立的 vm_area_struct虚拟页可以映射到对应的页表项但页表项尚未指向具体的物理页。已分配虚拟页已映射到物理页表示已经使用了 mmap 建立的 vm_area_struct虚拟页可以映射到对应的页表项并且页表项已经指向具体的物理页。
mmap 函数可以将虚拟地址映射到一个后备文件或者一个匿名文件。当操作系统分配物理内存时实际上会利用匿名文件的 mmap 来完成内存分配。
mmap和虚拟内存管理
用户进程的虚拟内存管理是通过Linux内核中的mm_struct结构来表示一个用户进程的虚拟内存地址空间。该结构包含了几个重要的字段来描述不同区域的地址范围和属性。 start_code和end_code指定了进程的代码段的起始地址和结束地址用于表示可执行代码的边界。一旦ELF二进制文件映射到虚拟内存后这些地址就不会再改变。 start_data和end_data指定了进程数据段的起始地址和结束地址用于表示已初始化数据的边界。类似于代码段这些地址在映射后也不再改变。 start_brk和brk指定了堆的起始地址和结束地址用于表示动态分配内存的边界。start_brk表示堆的初始位置在进程的整个生命周期中保持不变而brk表示堆的结束位置会随着堆的长度动态改变。 stack_top指定了栈的起始位置一般位于用户进程地址空间的顶部用于存放函数调用的栈帧。 task_size指定了用户进程地址空间的长度即用户空间的顶部边界。 mmap_base指定了用户进程虚拟地址空间中用作内存映射部分的基地址。通常情况下它位于用户地址空间的1/3处即TASK_SIZE / 3位置。
这些字段中的地址都是用户进程的虚拟地址通过虚拟地址和页表结构用户进程可以访问内存。当用户进程访问一个虚拟地址时会将该地址转换成对应的页表项索引然后查找页表项中保存的物理内存页的页号并加上虚拟地址低12位的偏移量从而确定一个唯一的物理内存地址。
如果物理内存地址所在的页已经存在就可以返回该物理地址存放的内容。如果不存在则会触发缺页异常。虚拟内存管理采用按需分配和缺页异常机制来管理页表项并分配对应的物理内存页。当一个虚拟地址对应的页表项不存在时会先创建页表结构然后分配物理内存页并最后修改页表。
除了mm_struct结构进程的虚拟内存管理还涉及到虚拟内存区域的管理即通过vm_area_struct结构来管理用户进程的不同虚拟内存区域如数据段、文本段和共享库等。这些区域通过vm_area_struct结构进行管理和映射。
vm_area_struct
struct vm_area_struct {struct mm_struct *vm_mm; /* 所属进程的内存描述符 */unsigned long vm_start; /* 区域起始地址 */unsigned long vm_end; /* 区域结束地址 */struct vm_area_struct *vm_next, *vm_prev; /* 双向链表指针 */pgprot_t vm_page_prot; /* 页保护标志 */unsigned long vm_flags; /* VMA标志位如映射类型等 */struct rb_node rb;union {struct {unsigned long shared_vm; /* 共享区域大小 */} anon_vma;struct vm_userfaultfd_ctx *userfaultfd_ctx;spinlock_t lock; /* 文件锁 */struct list_head list; /* 指向共享VMA列表项 */ };#ifdef CONFIG_MMU_NOTIFIERstruct mmu_notifier_mm *mmu_notifier_mm;
#endif#ifdef CONFIG_NUMA_BALANCING/** Virtual memory areas in a shared-memory area. Protected by* mmap_sem and guarded by mm-mmap_lock.** WARNING: Once you add a new member to this group you MUST update* dup_mmap() function!!!** You also have to modify arch_dup_mmap() if your architecture is one* of the architectures which implement that function.** Also, __split_vma() must be taught about how to copy the information.*/unsigned long shared_dirty_pages;unsigned long private_dirty_pages;unsigned long shared_clean_pages;unsigned long private_clean_pages;
#endif /* CONFIG_NUMA_BALANCING */
};vm_area_struct是一种用于管理用户进程虚拟内存区域的数据结构它可以以两种不同的组织形式存在。
首先是单链表形式包含了所有已创建的vm_area_struct实例。这种形式使得可以按照顺序遍历和访问所有的虚拟内存区域。
其次是红黑树形式用于加速对虚拟内存区域的查找。这种组织形式可以通过快速的二分查找来定位特定的虚拟内存区域提高了访问效率。
需要注意的是这两种组织形式都是针对同一份vm_area_struct实例而言的只是在不同的数据结构中进行组织和管理。
在考虑vm_area_struct和页表之间的关系时我们可以看到vm_area_struct本质上表示了用户进程的一段虚拟地址空间。而虚拟地址和页表数组的索引是一一对应的关系。页表数组的最后一级PTE数组的数组项存放着物理内存页的页号从而建立了虚拟内存地址到物理内存地址的对应关系。
有一种情况是当先有虚拟地址时通过访问虚拟地址触发缺页异常然后加载相应的物理内存页并更新页表以建立起虚拟地址、页表和物理内存之间的联系。
另一种情况是在进行内存映射mmap时首先从设备加载文件数据建立address_space和页缓存物理内存然后创建vm_area_struct结构更新页表并返回相应的虚拟地址。这样就实现了从设备加载文件到建立虚拟地址、页表和物理内存之间联系的过程。
vm_area_struct是用于描述用户进程虚拟内存区域的结构体 vm_start和vm_end表示区域的起始位置和结束位置用于确定区域的边界。这两个字段确保了不同的vm_area_struct之间不会出现交叉的情况从而清晰地划分了各个虚拟内存区域。 vm_page_prot表示了该区域的页的访问权限包括读、写、执行等。这些权限信息将影响到用户进程对该区域的访问行为。 shared处理具有后备文件的内存映射。它将该区域与后备文件的address_space地址空间进行关联以便在需要时能够正确地读取和写入数据。 anon_vma_node和anon_vma处理匿名文件共享内存映射的情况。当多个虚拟内存区域映射到同一物理内存页时这些映射将保存在一个链表中并由anon_vma_node进行管理确保它们之间的正确关联。 vm_pgoff和vm_file处理具有后备文件的内存映射的情况。vm_pgoff表示了该映射在文件中的页偏移量而vm_file则包含了打开文件file实例的相关信息以便在需要时能够正确地定位和操作对应的文件数据。
这些字段的信息使得vm_area_struct能够全面描述用户进程的虚拟内存区域包括区域的边界、访问权限、关联文件信息以及共享情况为内核提供了管理和操作虚拟内存的重要依据。
对于有后备文件的映射内核利用优先查找树结构来加速确定一个文件和所有映射到这个文件的虚拟内存区域vm_area_struct实例的关系从而可以方便地获取所有映射到这个文件的进程信息。这种优先查找树结构能够高效地管理文件和映射关系提高查找效率和操作性能。
同时内核提供了一系列函数用于对虚拟内存区域vm_area_struct进行操作包括创建、删除、合并、查找等功能。这些函数可以帮助内核有效地管理用户进程的虚拟内存区域确保内存映射的正确性和一致性。
另外mmap是C标准库提供给用户程序的函数用于通过内存映射建立文件地址空间和虚拟内存区域的映射关系。通过mmap函数用户程序可以将文件映射到自身的虚拟内存空间中实现了方便的文件访问和操作。这种映射关系的建立是通过内核提供的相关功能实现的确保了对文件数据的高效管理和访问。
mmap的4种类型
mmap函数在Linux系统中用于创建内存映射可以分为有后备文件的映射和匿名文件的映射每种映射又有私有映射和共享映射之分因此mmap可以创建4种类型的映射。 有后备文件的共享映射多个进程的vm_area_struct指向同一个物理内存区域一个进程对文件内容的修改会被其他进程看到并且这些修改会被写回到后备文件中。 有后备文件的私有映射多个进程的vm_area_struct指向同一个物理内存区域但采用写时拷贝的方式。当一个进程对文件内容做修改不会被其他进程看到并且对文件的修改也不会被写回到后备文件。当内存不足时私有映射的页被交换到交换区。这种映射常用于加载共享代码库。 匿名文件的共享映射内核创建一个初始为0的物理内存区域然后多个进程的vm_area_struct指向这个共享的物理内存区域。对该区域内容的修改对所有进程可见而且在页回收时被交换到交换区。 匿名文件的私有映射内核创建一个初始为0的物理内存区域对该区域内容的修改只对创建者进程可见。在页回收时这种映射也会被交换到交换区。malloc()函数底层使用了匿名文件的私有映射来分配大块内存。
这些不同类型的映射提供了灵活的内存管理方式使得进程可以根据需要选择适合的映射方式来处理内存数据并且能够满足不同场景下的内存管理需求。
内核对堆空间的管理
从内核管理用户进程虚拟地址空间的角度来看内存映射是主要的手段通过建立vm_area_struct结构来分配虚拟内存区域。对于堆空间的分配主要通过brk系统调用来实现。brk系统调用本质上也是利用了匿名文件的私有映射机制它分配并初始化为0的物理内存页然后建立相应的vm_area_struct最后更新页表结构。
brk系统调用分配的内存最小单位是页需要按页对齐。在内核的视角下每次对堆空间的分配至少是一页大小即以页面为单位进行扩展。换句话说更细粒度的字节级内存分配是由C语言标准库实现的而在内核层面堆空间的分配是以页面为单位进行管理的。这种设计确保了内核对虚拟内存的有效管理并提供了一种简单且高效的方式来处理用户进程的内存分配需求。
参考Linux内核源码分析内存调优/文件系统/进程管理/设备驱动/网络协议栈教程
Linux内核源码学习