Linux 从PCB的角度谈用户空间

2019-07-14 07:34发布

    进程控制块PCB,实际上指的是一个结构体task_struct。下面说说这个task_struct   Task_struct有一个指向mm_struct的指针,mm_struct结构体是该进程整个用户空间的抽象。包含着装入的可执行映像信息以及进程的页目录指针。Mm_struct结构体非常有意思,在一个进程控制块里,只含有一个指向mm_struct的指针,但是一个mm_struct能够被若干个进程指向。怎么指呢?通过的fork。fork后会创建子进程,子进程在写实拷贝之前,和父进程指向同一块物理页面,如图 父子进程所指向的物理内存是同一块内存,两个进程所映射的物理页面仍然是相同的,所以mm_struct被共享。Mm_struct里放着一个变量mm_count,用来统计被指了多少次。mm_struct有一个非常有名的指针,pgd。这个指针,就是放在CR3的指针。在切换进程的时候,会在切换函数的最末把当前进程的pgd放进CR3里,进而保持页目录的准确。   下面是mm_struct的源码   struct mm_struct  {
struct vm_area_struct *mmap; /* list of VMAs */
struct vm_area_struct *mmap_avl; /* tree of VMAs */
struct vm_area_struct *mmap_cache; /* last find_vma result */
pgd_t * pgd;    atomic_t mm_users; /* How many users with user space? */ atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */ int map_count; /* number of VMAs */ struct semaphore mmap_sem; spinlock_t page_table_lock; struct list_head mmlist; /* List of all active mm's */   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 rss, total_vm, locked_vm; unsigned long def_flags; unsigned long cpu_vm_mask; unsigned long swap_cnt; /* number of pages to swap on next pass */ unsigned long swap_address; /* Architecture-specific MM context */ mm_context_t context; };    如果说,mm_struct是对进程的整个用户空间的抽象,那么,vm_area_struct就是对进程的某个用户空间的抽象。它描述的是一段连续的,具有相同访问属性的用户空间,该用户空间大小为物理页面的整数倍。一个进程拥有3G的虚拟内存,但是基本上没有哪个进程会真的把3G完全用完。同时,由于MMU的翻译,进程的3G用户空间不一定是连续的,通常会形成若干离散的区间。所以,回到mm_struct,为了找到这些区间,我们需要通过一个链表来访问,这就是结构体第一行*mmap的作用。如果vm_area_struct多了,一个个遍历下去又是一个麻烦事。所以,结构体第二行的*mmap_avl就派上用场了。会生成一个AVL树,AVL树的时间复杂度是O(logn)。这将节省时间。   接下来来谈谈vm_area_struct   Struct vm_area_struct { Struct mm_struct *vm_mm; Unsigned long vm_start; Unsigned long vm_end;   /* linked list of VM areas per task, sorted by address */
 struct vm_area_struct *vm_next;    pgprot_t vm_page_prot;
unsigned long vm_flags;

/* AVL tree of VM areas per task, sorted by address */
short vm_avl_height;
struct vm_area_struct * vm_avl_left;
struct vm_area_struct * vm_avl_right;    /* For areas with an address space and backing store,
 * one of the address_space->i_mmap{,shared} lists,
 * for shm areas, the list of attaches, otherwise unused.
 */  struct vm_area_struct *vm_next_share;
struct vm_area_struct **vm_pprev_share;    struct vm_operations_struct * vm_ops;
unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */
struct file * vm_file;
unsigned long vm_raend;
void * vm_private_data; /* was vm_pte (shared mem) */  }   Vm_area_struct所描述的用户空间的头尾用vm_start和vm_end表示,接下来的list和avl就看mm_struct打算用什么来表示了。   一块被vm_area_struct所描述的有可能是一个文件映射的用户空间,所以vm_file指向被映射的文件的file结构,上面的vm_pgoff就是其在该vm_area_struct起始地址的偏移量,单位是物理页面。   如果这个vm_area_struct所描述的用户空间最终需要开辟物理内存,那么就需要一些操作来开辟物理内存。这个东西是一个指向vm_operations_struct结构体的指针vm_ops。Vm_operations_struct包含了一些指针用来打开和关闭该VMA,以及为该VMA创建于物理内存的映射。结构体代码如下:   Vm_operations_struct { Void (*open)(struct vm_area_struct *area); Void (*close)(struct vm_area_struct *area);   Struct page *(*nopage)(struct vm_area_struct *area,unsigned long address,int *type); /*   访问的页不存在的时候调用 */ }     最后从进程的角度谈谈vm_area_struct 父进程进程下,进程在创建好vm_area_struct之后,表明进程能够直接访问该用户空间。如果直接访问,由于存在着没有分配相应的物理页面的情况,如果发生了这种情况,会发生缺页异常。系统会从vm_area_struct里寻找vm_operations_struct的方法为该进程分配一个物理页面。之后会再次访问。   子进程下,该task_struct所指向的mm_struct和父进程的mm_struct是同一个,所以不需要来创建。如果物理内存已满将该页面放到swap分区,正好子进程需要调用该物理页面,系统会从vm_area_struct里寻找vm_operations_struct的方法为该进程分配一个物理页面。之后会再次访问。     再从malloc等动态分配内存的角度谈谈vm_area_struct 当进程利用系统调用动态分配内存时,Linux会首先分配一个vm_area_struct结构。并且链接到进程的虚拟内存链表中。当后续指令访问到的时候,由于Linux尚未分配相应的物理内存。会发生缺页异常。系统会从vm_area_struct里寻找vm_operations_struct的方法为该进程分配一个物理页面。之后会再次访问。   最后用一张图来表示PCB的结构