虚拟内存区的描述
由于虚拟空间是程序员使用的空间,在程序员的头脑中并不需要页的概念,因此也不需要页的描述,在虚拟空间Linux描述的是分区。所谓分区,就是按照虚存的代码或数据的属性分成的段。描述虚拟分区的结构定义在文件include/linux/mm.h中,其主要内容如下:
struct vm_area_struct { struct mm_struct * vm_mm; /* 指向上级结构mm_struct的指针 */ unsigned long vm_start; /* 虚存区的起始地址 */ unsigned long vm_end; /* 虚存区的结束地址 */ /* 指向VMA链表下一个元素的指针 */ struct vm_area_struct *vm_next; pgprot_t vm_page_prot; /* VMA虚存区的保护权限 */ unsigned long vm_flags; /* 描述虚存区中数据特征的标志 */ struct rb_node vm_rb; union { struct { struct list_head list; void *parent; /* aligns with prio_tree_node parent */ struct vm_area_struct *head; } vm_set; struct raw_prio_tree_node prio_tree_node; } shared; struct list_head anon_vma_node; /* Serialized by anon_vma->lock */ struct anon_vma *anon_vma; /* Serialized by page_table_lock */ /* 指向VMA操作函数集的指针 */ struct vm_operations_struct * vm_ops; /* Information about our backing store: */ unsigned long vm_pgoff; /* 映射文件内以页为单位的偏移 */ struct file * vm_file; /* 映射文件 */ void * vm_private_data; /* was vm_pte (shared mem) */ unsigned long vm_truncate_count;/* truncate_count or restart_addr */ #ifndef CONFIG_MMU struct vm_region *vm_region; /* NOMMU mapping region */ #endif #ifdef CONFIG_NUMA struct mempolicy *vm_policy; /* VMA的NUMA策略 */ #endif };
结构域中的域vm_ops是指向虚存区操作函数集的指针。虚存操作函数集结构vm_operations_struct的定义如下:
struct vm_operations_struct { void (*open)(struct vm_area_struct * area); void (*close)(struct vm_area_struct * area); int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf); int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf); int (*access)(struct vm_area_struct *vma, unsigned long addr, void *buf, int len, int write); #ifdef CONFIG_NUMA int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new); struct mempolicy *(*get_policy)(struct vm_area_struct *vma, unsigned long addr); int (*migrate)(struct vm_area_struct *vma, const nodemask_t *from, const nodemask_t *to, unsigned long flags); #endif };
每当系统创建一个程序时,就会把应用程序和共享库的代码与数据在用户空间所占用的各VMA的属性填写在各个vm_area_struct结构中,并把它们按照下图所示组织成一张链表:
为了给上述这些VAM一个统一的描述,Linux又把程序在用户空间的虚存块VMA与程序相关的属性写到了一个叫做mm_struct的数据结构中,并用其中的指针mmap指向了VMA链表:
struct mm_struct { struct vm_area_struct * mmap; /* 程序VMA链表的头指针 */ struct rb_root mm_rb; struct vm_area_struct * mmap_cache; /* 最近访问的虚存区的指针 */ unsigned long (*get_unmapped_area) (struct file *filp, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags); void (*unmap_area) (struct mm_struct *mm, unsigned long addr); unsigned long mmap_base; /* base of mmap area */ unsigned long task_size; /* size of task vm space */ unsigned long cached_hole_size; /* if non-zero, the largest hole below free_area_cache */ unsigned long free_area_cache; /* first hole of size cached_hole_size or larger */ pgd_t * pgd; /* 页目录指针 */ atomic_t mm_users; /* 使用本虚存空间的程序数 */ atomic_t mm_count; /* 本虚存空间的引用数 */ int map_count; /* 程序占用的VMA段的数目 */ struct rw_semaphore mmap_sem; //信号量 spinlock_t page_table_lock; /* Protects page tables and some counters */ struct list_head mmlist; mm_counter_t _file_rss; mm_counter_t _anon_rss; unsigned long hiwater_rss; /* High-watermark of RSS usage */ unsigned long hiwater_vm; /* High-water virtual memory usage */ unsigned long total_vm, locked_vm, shared_vm, exec_vm; unsigned long stack_vm, reserved_vm, def_flags, nr_ptes; unsigned long start_code, end_code, start_data, end_data; //段起止地址 unsigned long start_brk, brk, start_stack; unsigned long arg_start, arg_end, env_start, env_end; unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */ cpumask_t cpu_vm_mask; /* Architecture-specific MM context */ mm_context_t context; unsigned int faultstamp; unsigned int token_priority; unsigned int last_interval; unsigned long flags; /* Must use atomic bitops to access the bits */ struct core_state *core_state; /* coredumping support */ /* aio bits */ spinlock_t ioctx_lock; struct hlist_head ioctx_list; #ifdef CONFIG_MM_OWNER struct task_struct *owner; #endif #ifdef CONFIG_PROC_FS /* store ref to file /proc/<pid>/exe symlink points to */ struct file *exe_file; unsigned long num_exe_file_vmas; #endif #ifdef CONFIG_MMU_NOTIFIER struct mmu_notifier_mm *mmu_notifier_mm; #endif };
mm_struct可以看成是一个程序用户空间的控制块,程序就是通过用程序控制块task_struct的指针mm与mm_struct关联的。
值得注意的是:结构mm_struct中的域mm_users,它记录了共享该虚拟存储空间的程序数。因为在一般情况下,一个虚拟存储空间由一个程序来使用,但有时也会有多个程序使用同一个虚拟存储空间,例如一个程序调用fork()创建了一个子程序后,这个子程序就会与父程序共享一个虚拟存储空间。
也就是说,上图讲述了程序控制块、程序虚拟内存控制块和虚存段管理链表之间的关系。
随着vm_area_struct结构链表的生成,这些结构中对于虚拟内存空间进行操作的标准函数也由系统初始化。
与内存映射相关的几个文件在mm目录下,其中,mmap.c文件中函数do_mmap()的功能是把文件中的逻辑地址映射为虚存的线性地址,即把文件结构中得到的逻辑地址转换为vm_area_struct所需的地址;mremap.c文件中函数sys_mremap()的功能是扩张或缩小现存的虚拟内存空间;filemap.c文件中主要函数的功能是把线性地址映射到内存。
程序与虚拟内存和物理内存的关系
将之前所叙述的内容结合起来,可以得到描述Linux程序与虚拟内存空间和物理内存空间之间的关系,即程序与其拥有的所有内存的关系,如下图所示:
结构mm_struct可以看成是属于一个程序包括虚拟内存和物理内存的所有内存的控制块。为了在程序控制块与内存控制块之间建立联系,程序控制块中的指针mm指向了该程序的内存控制块。
内核可以通过程序控制块中的指针mm获得程序控制块mm_struct,进而可以了解程序占用虚拟内存空间各段的情况,并通过调用vm_area_struct中的指针vm_opst指向的操作函数集中的函数来对程序的虚拟内存进行相关的操作。也可以通过mm_struct中的指针pdg对分配给程序的物理页框进行了解和解读。