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處理這種異常.