每一个用户态进程都有独立的页目录,pgd就是页目录指针。进程的3GB虚拟地址空间不可能是全部映射到物理内存中的,所以mm结构的vm_area_struct记录了虚拟地址空间的使用情况。 注意:page结构记录了物理内存的使用情况
struct vm_area_struct {
struct mm_struct * vm_mm;
unsigned long vm_start;
unsigned long vm_end;
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot; /* Access permissions of this VMA. */
unsigned long vm_flags; /* Flags, listed below. */
struct rb_node vm_rb;
-
union {
struct {
struct list_head list;
void *parent; - struct vm_area_struct *head;
} vm_set;
- struct vm_area_struct *head;
struct prio_tree_node prio_tree_node;
} shared;
struct list_head anon_vma_node;
struct anon_vma *anon_vma;
/* Function pointers to deal with this struct. */
struct vm_operations_struct * vm_ops;
/* Information about our backing store: */
unsigned long vm_pgoff;
struct file * vm_file;
void * vm_private_data;
#ifdef CONFIG_NUMA
struct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
};
在这个结构中,vm_start和vm_end记录了这个进程当前使用的虚拟地址空间。假设一个进程的某段合法的起始虚拟地址为A,大小为2个物理页面,就需要一个vm_area_struct来描述这段虚拟地址区域。一个进程有多个虚拟地址区域,这些vm_area_struct根据虚拟地址被组织成一颗红黑树,这主要是为了加速查找的速度。假设进程最初仅仅从起始虚拟地址A开始映射了一个物理页面,当该进程首次访问大于A+4KB小于A+8KB的虚拟地址时,由于这个虚拟地址没有映射物理页面,因此会触发页面故障,此时缺页中断处理函数do_page_fault()将查找该进程的vm_area_struct结构,在本例中,这个被访问的虚拟地址在合法的范围内,所以do_page_fault()会分配一个新的物理页面并为这个虚拟地址建立映射,此时该进程成功的访问到第二个物理页面。
我们知道fork()会复制当前进程的task_struct结构,同时会为新进程复制mm结构。此时当前进程的3GB~4GB的内核态虚拟地址对应的页表项被复制到新进程的页表项中,所以说所有进程共享1GB的内核态地址空间。但是对于用户态虚拟地址区域,则把它的进程页表项设置为只读,这样当其中一个进程对其进行写入操作时,do_page_fault()会分配新的物理页面,并建立映射,从而实现Copy On Write机制。