1. 程式人生 > >Linux缺頁中斷處理

Linux缺頁中斷處理

1.如果訪問的虛擬地址在程序空間沒有對應的VMA(mmap和malloc可以分配vma),則缺頁處理失敗,程式出現段錯誤.

2.Linux把沒有對映到檔案的對映叫做匿名對映(malloc和mmap的匿名對映)

3.remap_pfn_range把核心記憶體對映到使用者空間,一般在裝置驅動的mmap函式中呼叫.

int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
            unsigned long pfn, unsigned long size, pgprot_t prot)

4.mmap與malloc的區別

 malloc只能用來分配匿名私有對映,而mmap可以分配四種不同的對映

分配記憶體小於128k時,malloc分配地址從start_brk開始,也就是資料段之後,mmap起始地址位於mmap區

malloc分配小於128k時,由brk系統呼叫實現,而大於128k,由mmap系統呼叫實現.

總之,mmap功能比malloc強大. malloc是mmap的特殊情況。

5.缺頁中斷分析

   缺頁中斷,無非就是建立GPD->PMD->PTE到page的對映關係,malloc和mmap函式,只是給程序分配了虛擬地址空間(VMA),而並沒有分配實體記憶體,更沒有建立對應的頁表對映.缺頁中斷主要函式:handle_mm_fault>handle_pte_fault,主要進行實體記憶體分配,預讀檔案,建立頁表等,分四種情況:

  私有匿名記憶體對映, 檔案對映缺頁中斷, swap預設中斷,寫時複製(COW)缺頁中斷.

5.1 私有匿名記憶體對映

     私有的匿名記憶體對映又可以分為只讀缺頁,可寫缺頁中斷兩種情況,處理函式為do_anonymous_page,應用場景為malloc記憶體分配

 只讀缺頁時,會從zero page分配一個page,並填充具體的pte表項

    可寫缺頁時,直接從夥伴系統分配一個page,並填充pte表項

static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,
		unsigned long address, pte_t *page_table, pmd_t *pmd,
		unsigned int flags)
{
	/*只讀缺頁中斷,直接分配一個zero page */
	/* Use the zero-page for reads */
	if (!(flags & FAULT_FLAG_WRITE) && !mm_forbids_zeropage(mm)) {
		entry = pte_mkspecial(pfn_pte(my_zero_pfn(address),
						vma->vm_page_prot));
		page_table = pte_offset_map_lock(mm, pmd, address, &ptl);
		goto setpte;//設定pte頁表項
	}

	/*可讀的缺頁中斷,直接從夥伴系統分配頁面 */
	page = alloc_zeroed_user_highpage_movable(vma, address);
	if (!page)
		goto oom;
	/*設定頁面內容有效 */
	__SetPageUptodate(page);

        /*生成pte表項值 */
	entry = mk_pte(page, vma->vm_page_prot);
	if (vma->vm_flags & VM_WRITE)
		entry = pte_mkwrite(pte_mkdirty(entry));
        /*關聯匿名頁表 */
	page_add_new_anon_rmap(page, vma, address);
	/*新增到lru連結串列 */
	lru_cache_add_active_or_unevictable(page, vma);
setpte:
	set_pte_at(mm, address, page_table, entry);
	return 0;
}

5.2 檔案對映缺頁中斷

     可以分為三種情況,只讀缺頁中斷(mmap讀取檔案),  寫時複製缺頁中斷(載入動態庫),共享缺頁中斷(程序通訊),函式do_fault處理這三種情況

static int do_fault(struct mm_struct *mm, struct vm_area_struct *vma,
		unsigned long address, pte_t *page_table, pmd_t *pmd,
		unsigned int flags, pte_t orig_pte)
{
	pgoff_t pgoff = (((address & PAGE_MASK)
			- vma->vm_start) >> PAGE_SHIFT) + vma->vm_pgoff;

	pte_unmap(page_table);
	/*可讀缺頁中斷*/
	if (!(flags & FAULT_FLAG_WRITE))
		return do_read_fault(mm, vma, address, pmd, pgoff, flags,
				orig_pte);
	/*寫時複製缺頁中斷 */
             if (!(vma->vm_flags & VM_SHARED))
		return do_cow_fault(mm, vma, address, pmd, pgoff, flags,
		orig_pte);
        /*共享記憶體缺頁中斷 */
	return do_shared_fault(mm, vma, address, pmd, pgoff, flags, orig_pte);
}

 

5.2.1 只讀缺頁中斷

  先介紹struct vm_operations_struct *vm_ops的三個重要函式fault和map_pages,page_mkwrite

struct vm_operations_struct {

 /*從page cache或者夥伴系統分配頁面,進行檔案預讀(readahead),並填充頁面內容 */
    int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);                        

 /*從page cache獲取頁面,並建立頁表對映 */
    void (*map_pages)(struct vm_area_struct *vma, struct vm_fault *vmf);

    /* notification that a previously read-only page is about to become
     * writable, if an error is returned it will cause a SIGBUS */

 /* 標記頁面可寫,並回寫頁面內容到磁碟*/
    int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);

   檔案對映的缺頁異常處理,基本就是呼叫這三個函式,程序頁面分配和檔案預讀,頁表對映.

static int do_read_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)
{
	
	if (vma->vm_ops->map_pages && fault_around_bytes >> PAGE_SHIFT > 1) {
		pte = pte_offset_map_lock(mm, pmd, address, &ptl);
		/*do_fault_around函式會預先對映缺頁頁面周圍的16頁面,這樣可以減少缺頁中斷次數 */
                do_fault_around(vma, address, pte, pgoff, flags);
		if (!pte_same(*pte, orig_pte))
			goto unlock_out;
		pte_unmap_unlock(pte, ptl);
	}
        /*do_fault函式主要呼叫檔案系統對應的fault函式,從page cache分配頁面(如果page cache沒有頁面快取,則從夥伴系統分配),從檔案預讀,並填充頁面 */
	ret = __do_fault(vma, address, pgoff, flags, NULL, &fault_page);
	if (unlikely(ret & (VM_FAULT_ERROR | VM_FAULT_NOPAGE | VM_FAULT_RETRY)))
		return ret;
	/*建立頁表 */
	pte = pte_offset_map_lock(mm, pmd, address, &ptl);
	do_set_pte(vma, address, fault_page, pte, false, false);
	return ret;
}

5.2.2 寫時複製缺頁處理(載入動態庫)

  處理函式do_cow_fault主要分配new page,並從old page複製內容到new page,並且會呼叫__do_fault進行檔案預讀.

5.2.3 共享檔案缺頁處理(程序間通訊)

 處理函式do_shared_fault主要呼叫__do_fault函式預讀檔案,並回寫內容到磁碟

5.3 交換缺頁異常

   記憶體頁面被換出到磁碟,處理函式do_swap_page把頁面內容從磁碟換回記憶體.

5.4 寫時複製缺頁(fork)

 這裡的寫時複製缺頁異常,主要用於處理子父程序(fork)的缺頁.與檔案的寫時複製缺頁處理和應用場景不一樣。函式do_wp_page處理這種異常.