vm_area_struct
linux 内核使用 vm_area_struct 结构来表示一个独立的虚拟内存区域,由于每个不同地址的虚拟内存区域功能和内部机制都不同,因此 一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域,包括虚拟内存的起始和结束地址,以及内存的访问权限等 。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问,如下图所示:
Linux内核中,关于虚存管理的 最基本的管理单元 应该是 struct vm_area_struct 了,它描述的是一段连续的、具有相同访问属性的虚存空间,该虚存空间的大小为物理内存页面的整数倍。
<include/linux/mm_types.h>
struct vm_area_struct {
/* The first cache line has the info for VMA tree walking. */
unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address
within vm_mm. */
/*[vm_start, vm_end) 这么个情况,表示一块虚拟内存空间*/
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next, *vm_prev;
/*在mm->mmap链表中前后节点*/
struct rb_node vm_rb;
/*插入到mm->mm_rb红黑树的节点*/
/*
* Largest free memory gap in bytes to the left of this VMA.
* Either between this VMA and vma->vm_prev, or between one of the
* VMAs below us in the VMA rbtree and its ->vm_prev. This helps
* get_unmapped_area find a free area of the right size.
*/
unsigned long rb_subtree_gap;
/*以当前vma为根,左子树中最大可用虚拟内存区域的大小*/
/* Second cache line starts here. */
struct mm_struct *vm_mm; /* The address space we belong to. */
/*同一进程所有的vma指向的vm_mm是相同的*/
pgprot_t vm_page_prot; /* Access permissions of this VMA. */
/*访问权限*/
unsigned long vm_flags; /* Flags, see mm.h. */
/*属性,详看mm.h*/
/*
* For areas with an address space and backing store,
* linkage into the address_space->i_mmap interval tree.
*
* For private anonymous mappings, a pointer to a null terminated string
* in the user process containing the name given to the vma, or NULL
* if unnamed.
*/
union {
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} shared;
const char __user *anon_name;
};
/*
* A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
* list, after a COW of one of the file pages. A MAP_SHARED vma
* can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack
* or brk vma (with NULL file) can only be in an anon_vma list.
*/
struct list_head anon_vma_chain; /* Serialized by mmap_sem &
* page_table_lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */
/* Function pointers to deal with this struct. */
const struct vm_operations_struct *vm_ops;
/*该vma操作函数,包括打开、关闭、建立映射三个函数*/
/* Information about our backing store: */
unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE
units */
struct file * vm_file; /* File we map to (can be NULL). */
/*文件映射的文件信息,匿名映射为NULL*/
void * vm_private_data; /* was vm_pte (shared mem) */
atomic_long_t swap_readahead_info;
#ifndef CONFIG_MMU
struct vm_region *vm_region; /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
struct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
#ifdef CONFIG_SPECULATIVE_PAGE_FAULT
seqcount_t vm_sequence; /* Speculative page fault field */
atomic_t vm_ref_count; /* see vma_get(), vma_put() */
#endif
} __randomize_layout;
vm_area_struct结构所描述的虚存空间以 vm_start、vm_end 成员表示,它们分别保存了该虚存空间的首地址和末地址后第一个字节的地址,以字节为单位,所以虚存空间范围可以用[vm_start, vm_end)表示。
通常,进程所使用到的虚存空间不连续,且各部分虚存空间的访问属性也可能不同。所以一个进程的虚存空间需要多个vm_area_struct结构来描述。在vm_area_struct结构的数目较少的时候,各个vm_area_struct按照升序排序,以 单链表的形式组织数据(通过vm_next指针指向下一个vm_area_struct结构)。但是当vm_area_struct结构的数据较多的时候,仍然采用链表组织的化,势必会影响到它的搜索速度。针对这个问题,vm_area_struct还添加了 vm_avl_hight(树高)、 vm_avl_left (左子节点)、vm_avl_right(右子节点)三个成员来 实现AVL树 ,以提高vm_area_struct的搜索速度。
假如该vm_area_struct描述的是一个文件映射的虚存空间,成员 vm_file 便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面。
一个程序可以选择MAP_SHARED或MAP_PRIVATE共享模式将一个文件的某部分数据映射到自己的虚存空间里面。这两种映射方式的区别在于:MAP_SHARED映射后在内存中对该虚存空间的数据进行修改会影响到其他以同样方式映射该部分数据的进程,并且该修改还会被写回文件里面去,也就是这些进程实际上是在共用这些数据。而MAP_PRIVATE映射后对该虚存空间的数据进行修改不会影响到其他进程,也不会被写入文件中。
来自不同进程,所有映射同一个文件的vm_area_struct结构都会根据其共享模式分别组织成两个链表。链表的链头分别是:vm_file->f_dentry->d_inode->i_mapping->i_mmap_shared, vm_file->f_dentry->d_inode->i_mapping->i_mmap。而vm_area_struct结构中的vm_next_share指向链表中的下一个节点;vm_pprev_share是一个指针的指针,它的值是链表中上一个节点(头节点)结构的vm_next_share(i_mmap_shared或i_mmap)的地址。
进程建立vm_area_struct结构后,只是说明进程可以访问这个虚存空间,但有可能还没有分配相应的物理页面并建立好页面映射。在这种情况下,若是进程执行中有指令需要访问该虚存空间中的内存,便会产生一次缺页异常。这时候,就需要通过vm_area_struct结构里面的 vm_ops->nopage 所指向的函数来将产生缺页异常的地址对应的文件数据读取出来。
vm_flags主要保存了进程对该虚存空间的访问权限,然后还有一些其他的属性。vm_page_prot 是新映射的物理页面的页表项pgprot的默认值。