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处理这种异常. 

     

猜你喜欢

转载自blog.csdn.net/bin_linux96/article/details/83619973