进程控制块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的结构