1. 程式人生 > >Linux 從PCB的角度談使用者空間

Linux 從PCB的角度談使用者空間

    程序控制塊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的結構