天天看點

linux 程序0 寫時複制,linux 寫時複制 COW 過程梳理

最後一次談到缺頁,是在一年多以前,http://blog..net/chenyu105/article/details/7061845

那時結個了草率的尾,定格在了handle_pte_fault,留下一句:I will be back

為避免來回翻閱,将handle_mm_fault再次粘上:

int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,

unsigned long address, unsigned int flags)

{

pgd_t *pgd;

pud_t *pud;

pmd_t *pmd;

pte_t *pte;

if (unlikely(is_vm_hugetlb_page(vma)))

return hugetlb_fault(mm, vma, address, flags);

pgd = pgd_offset(mm, address);

pud = pud_alloc(mm, pgd, address);

pmd = pmd_alloc(mm, pud, address);

pte = pte_alloc_map(mm, pmd, address);

return handle_pte_fault(mm, vma, address, pte, pmd, flags);

}

這裡介紹一下pmd_alloc(mm, pud, address)以及pte_alloc_map(mm, pmd, address)

執行pmd_alloc的流程:如果pmd表不存在,則調用__pmd_alloc建立此pmd表,

并将此pmd表的所有成員都指向invalid_pte_table,最後傳回vaddr對應的pmd entry指針(指向一個pte表,當然可能是invalid)。

執行pte_alloc_map(mm, pmd, address)的流程:如果pte表是invalid,則配置設定一個pte表,并傳回pte表中此

address對應的pte_t指針,其值is hoped to filled by page frame number, pfn,

但很多時候,新配置設定出來的pte表是全0的一個page,是以他的成員pte entry也就是0了,即*(pte_t) = 0

可以看出,在走到handle_pte_fault時,頁表已建好對應vaddr的從pgd->pmd->pud->pte的路徑,并搜尋出對

應的pte entry值。這個pte entry 的值,要麼是0(未通路過時),要麼是有效的頁框号pfn。

接着将此pte指針傳入handle_pte_fault函數,表示要對此頁做進一步處理。

static inline int handle_pte_fault(struct mm_struct *mm,

struct vm_area_struct *vma, unsigned long address,

pte_t *pte, pmd_t *pmd, unsigned int flags)

{

pte_t entry;

entry = *pte;

if (!pte_present(entry)) {

if (pte_none(entry)) {

if (vma->vm_ops) {

if (likely(vma->vm_ops->fault))

return do_linear_fault(mm, vma, address,

pte, pmd, flags, entry);

}

return do_anonymous_page(mm, vma, address,

pte, pmd, flags);

}

if (pte_file(entry))

return do_nonlinear_fault(mm, vma, address,

pte, pmd, flags, entry);

return do_swap_page(mm, vma, address,

pte, pmd, flags, entry);

}

if (flags & FAULT_FLAG_WRITE) {

if (!pte_write(entry))

return do_wp_page(mm, vma, address,

pte, pmd, ptl, entry);

entry = pte_mkdirty(entry);

}

flush_tlb_page(vma, address);

}

如果pte entry的值是0,則pte_present(entry)傳回0,表示此頁框不在記憶體中。

頁不在記憶體中,有兩種情況,一種是因為還沒有配置設定頁框;另外一種是配置設定了頁框,但是頁面資料

被交換到磁盤上。第一種情況,即我們常說的請求調頁,通常是由于第一次通路了mmap的位址引起。

有了上面的鋪墊,我們來看看寫時複制是個什麼情況。

COW的原因有兩種,一種是通路mmap(MAP_PRIVATE,PROT_READ,fd)空間,另外一種是通路fork出來的程序空間。

現在來看mmap(MAP_PRIVATE)的流程。

MAP_PRIVATE的意思是寫時複制COW,man mmap裡對此的解釋是

The MAP_SHARED and MAP_PRIVATE options describe the disposition of write references to the

underlying object. If MAP_SHARED is specified, write references will change the memory object.

If MAP_PRIVATE is specified, the initial write reference will create a private copy of the

memory object page and redirect the mapping to the copy. The private copy is not created until the first write;

until then, other users who have the object mapped MAP_SHARED can change the object. Either

MAP_SHARED or MAP_PRIVATE must be specified, but not both.

The mapping type is retained across fork(2).

上面這段話最重要的一句,就是在第一次寫空間的時候出發拷貝。

是以,隐含的兩個要點就是,

1) 觸發寫時複制的時機是在缺頁時,而不是mmap時

2) 缺頁時如何區分寫時複制引起的異常,答案是在缺頁異常裡,如果發現是寫操作,并且對MAP_PRIVATE的

vma執行的操作,那麼就認為是一個寫時複制引起的異常,需要進一步做拷貝。

在第一次通路MAP_PRIVATE出來的空間時,走請求調頁,對于MAP_PRIVATE标志,檔案映射和共享記憶體映射才有

效,而匿名映射無效,是以走do_linear_fault

static int do_linear_fault(struct mm_struct *mm, struct vm_area_struct *vma,

unsigned long address, pmd_t *pmd,

pgoff_t pgoff, unsigned int flags, pte_t orig_pte)

{

ret = vma->vm_ops->fault(vma, &vmf); //配置設定一個新頁面

if (flags & FAULT_FLAG_WRITE) { //1.如果是寫操作引起的異常

if (!(vma->vm_flags & VM_SHARED)) { //2. 此空間是MAP_PRIVATE标志

page = alloc_page_vma(vma, address); //配置設定新頁

copy_user_highpage(page, vmf.page, address, vma);//将原頁資料拷貝到新頁

}

entry = mk_pte(page, vma->vm_page_prot); //按新頁生成pte entry

//将此頁置可寫,以避免其他路徑也觸發COW

if (flags & FAULT_FLAG_WRITE)

entry = maybe_mkwrite(pte_mkdirty(entry), vma);

set_pte_at(mm, address, page_table, entry); //設定到頁表

}

以後再通路這塊虛拟位址,就通路的是原資料的拷貝了,這就是MAP_PRIVATE設計的初衷。

再來看fork時的cow。 fork時,會把父程序頁表的屬性改為隻讀,這樣下次通路就會産生異常。

具體是在copy_mm的copy_one_pte裡,有一句判斷區間是否為cow屬性(即是否可寫,可寫的話就去掉寫屬性)

if (is_cow_mapping(vm_flags)) {

ptep_set_wrprotect(src_mm, addr, src_pte);

pte = pte_wrprotect(pte);

}

如果是的話,就去掉寫屬性。

之後的缺頁,根據handle_pte_fault,因為滿足pte_present(entry)(頁在記憶體裡,缺頁是因為權限問題)

是以嘗試檢查是否COW引起,條件是對不可寫空間執行了寫操作

if (flags & FAULT_FLAG_WRITE) {

if (!pte_write(entry))

最後走cow流程即do_wp_page