linux 缺页中断处理流程

文章详细解析了Linux内核中处理缺页中断的过程,涉及__do_page_fault、__handle_mm_fault和handle_pte_fault等函数,讨论了不同情况下的异常处理,如匿名页面、文件映射、swap和write时复制的处理策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

__do_page_fault 函数分析

static vm_fault_t __do_page_fault(struct mm_struct *mm, unsigned long addr,
			   unsigned int mm_flags, unsigned long vm_flags,
			   struct task_struct *tsk)
{
	struct vm_area_struct *vma;
	vm_fault_t fault;
//找到vma 如果找不到 就返回VM_FAULT_BADMAP
	vma = find_vma(mm, addr);
	fault = VM_FAULT_BADMAP;
	if (unlikely(!vma))
		goto out;
	//如果找到的vma的起始地址大于addr 跳转到check_stack
	if (unlikely(vma->vm_start > addr))
		goto check_stack;

	/*
	 * Ok, we have a good vm_area for this memory access, so we can handle
	 * it.
	 */
good_area:
	/*
	 * Check that the permissions on the VMA allow for the fault which
	 * occurred.
	 */
	 //通过对比vm_flags 是否具备可读属性,如果不具备可读的属性说明这次访问是错的
	if (!(vma->vm_flags & vm_flags)) {
		fault = VM_FAULT_BADACCESS;
		goto out;
	}
// handle_mm_fault 缺页中断的核心处理函数
//PAGE_MASK  --- >   ~(PAGE_SIZE-1)   PAGE_SIZE ---> 1<<12
	return handle_mm_fault(vma, addr & PAGE_MASK, mm_flags);

check_stack:
//判断是否VMA地址拓展到addr,如果可以这说明VMA是一个ok的VMA 如果不可以返回fault = VM_FAULT_BADMAP;
	if (vma->vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr))
		goto good_area;
out:
	return fault;
}

最终使用handle_mm_fault 这个函数处理缺页中断 ,handle_mm_fault 使用 __handle_mm_fault 处理当前错误

__handle_mm_fault函数分析

static vm_fault_t __handle_mm_fault(struct vm_area_struct *vma,
		unsigned long address, unsigned int flags)
{
	//定义一个vm_fault 用于填充参数并且传递给进程的地址空间的fault函数 
	// 通过 --->   return handle_pte_fault(&vmf);传递给进程的地址空间的fault函数 
	struct vm_fault vmf = {
		.vma = vma,//目标VMA
		.address = address & PAGE_MASK,//发生缺页中断时的虚拟地址
		.flags = flags,//与进程内存描述符相关的标志位
		.pgoff = linear_page_index(vma, address),//在VMA中的偏移量
		.gfp_mask = __get_fault_gfp_mask(vma),//分配掩码,用于页面分配器分配页面时请求的集合
	};
	unsigned int dirty = flags & FAULT_FLAG_WRITE;
	struct mm_struct *mm = vma->vm_mm;
	pgd_t *pgd;
	p4d_t *p4d;
	vm_fault_t ret;
//(pgd_offset_raw((mm)->pgd, (addr)))
//((pgd) + pgd_index(addr))
//(((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
	pgd = pgd_offset(mm, address);//计算出PGD页表项
	p4d = p4d_alloc(mm, pgd, address);//计算出P4D页表项
	if (!p4d)
		return VM_FAULT_OOM;

	vmf.pud = pud_alloc(mm, p4d, address);//计算PUD页表项
    ...
	vmf.pmd = pmd_alloc(mm, vmf.pud, address);//计算出PMD页表项
	...

	return handle_pte_fault(&vmf);
}

__handle_mm_fault 函数分配 一个vm_fault 并填充这个这个结构体。给到handle_pte_fault 函数使用

handle_pte_fault 函数分析

static vm_fault_t handle_pte_fault(struct vm_fault *vmf)
{
	pte_t entry;
//判断*vmf->pmd 是否为空
	if (unlikely(pmd_none(*vmf->pmd))) {
		vmf->pte = NULL;
	} else {
		/* See comment in pte_alloc_one_map() */
		if (pmd_devmap_trans_unstable(vmf->pmd))
			return 0;

		vmf->pte = pte_offset_map(vmf->pmd, vmf->address);//计算出pte
		vmf->orig_pte = *vmf->pte;

		barrier();
		if (pte_none(vmf->orig_pte)) {
			pte_unmap(vmf->pte);
			vmf->pte = NULL;
		}
	}
//判断pte是否为空,页表项为空有两种可能,1 页表项还没有建立,2 页表项的内容被清空了
	if (!vmf->pte) {
		//当前VMA 是匿名页 使用do_anonymous_page, 否则就是文件映射使用 do_fault
		if (vma_is_anonymous(vmf->vma))//	return !vma->vm_ops; 当前VMA是否有方法集
			return do_anonymous_page(vmf);
		else
			return do_fault(vmf);
	}
//页面没有在内存中,PTE没有映射页物理页面,真正的缺页,从交换分区回读页面
	if (!pte_present(vmf->orig_pte))
		return do_swap_page(vmf);
//页面在内存中,但是页面被设置为NUMA调度的页面,所以会使用do_numa_page
	if (pte_protnone(vmf->orig_pte) && vma_is_accessible(vmf->vma))
		return do_numa_page(vmf);

	vmf->ptl = pte_lockptr(vmf->vma->vm_mm, vmf->pmd);//获取进程的内存描述符mm中定义的自旋锁
	spin_lock(vmf->ptl);
	entry = vmf->orig_pte;
	//判断这段时间PTE是否被修改了
	if (unlikely(!pte_same(*vmf->pte, entry)))
		goto unlock;
	//判断vmf->flags 是否设置了FAULT_FLAG_WRITE
	//通过ESR 的WnR判断是否 因为写内容触发的缺页异常
	if (vmf->flags & FAULT_FLAG_WRITE) {
		if (!pte_write(entry))//如果PTE是只读属性
			return do_wp_page(vmf);//触发写时复制的缺页中断,父子进程共享内存,当其中一个需要写入新的内容的时候,就会触发写时复制
		entry = pte_mkdirty(entry);
	}
	entry = pte_mkyoung(entry);//对于ARM64 架构会设置PET_AF
	//判断PTE内容是否发生变化,如果改变就需要更新新的内容到PTE中,并且要刷新对应的TLB和高速缓存
	if (ptep_set_access_flags(vmf->vma, vmf->address, vmf->pte, entry,
				vmf->flags & FAULT_FLAG_WRITE)) {
		update_mmu_cache(vmf->vma, vmf->address, vmf->pte);
	} else {
		 //这只适用于保护故障,但arch代码还没有告诉我们这是否是保护故障。这仍然可以避免线程对.text页故障进行无用的tlb刷新。
		if (vmf->flags & FAULT_FLAG_WRITE)
			flush_tlb_fix_spurious_fault(vmf->vma, vmf->address);
	}
unlock:
	pte_unmap_unlock(vmf->pte, vmf->ptl);
	return 0;
}

缺页中断流程

异常addr出现在用户空间---> 
   	vma=find_vma(addr)
   			没有找到VMA
   				如果发生在内核模式则发生Oops
   				如果发生在用户模式,给线程发送SIGSEGV或者SIGBUS信号终止进程
   			找到VMA 
   				__handle_mm_fault重新建立页面到VMA的映射关系
   				遍历页表找到addres对应的PTE
   					PTE页表是否存在 ?
   						N 是否为匿名页面
   							Y do_anonymous_page
   							N do_fault
   						Y PTE的PRESENT是否设置,确定是否真正的缺页 ?
   							!N do_swap_page
   							!Y pte_protnone
   								Y do_numa_page
   								N 写内存导致的缺页异常,并且PTE是只读属性 ?
   									Y do_wp_page
   									N 设置AF 更新PTE内容 刷新对应TLB和高速缓存

小结

发生缺页中断的时候,会根据PTE中的PRESENT位 PTE内容是否为空以及是否为文件映射等条件,执行不同的函数处理不类型的缺页异常。

​ 1 do_anonymous_page() ---- 处理匿名页面的缺页异常

​ 判断条件:PTE的内容为空并且没有设置 vma->vm_ops 方法集

​ 应用场景: malloc() 分配内存或者使用 mmap() 函数来分配匿名页面内存

​ 2 do_fault() ------ 处理文件映射缺页异常

​ PTE的内容为空并且设置了 vma->vm_ops 方法集。

​ 如果发生了读错误,那么调用 do_read_fault() 函数读取这个页面

​ 如果在私有映射VMA中发生写保护错误,那么发生写时复制,新分配一个页面,旧的页面内容要复制到新的页面中,利用新页面生成一个PTE并将这个页面设置到硬件页表项中。

​ 如果写保护错误发生在共享映射VMA中,就会产生脏页,调用系统的回写机制来回写这个脏页

​ 应用场景:使用mmap读文件内容,如驱动使用mmap映射设备内存到用户空间

​ 动态库映射,如不同的进程可以通过文件映射来共享同一个动态库

3 do_swp_page () ------ 处理swap缺页异常

​ 判断条件:PTE中的PRESENT位没有置位并且PTE内容不为空

4 do_wp_page() ---- 处理写时复制缺页异常

​ 复用旧页面(对 _mapcount=0 的页面和可以写的共享页面)

​ 写时复制旧页面(对 _mapcount!=0 只读或者非共享的文件映射页面)

​ 判断条件:PTE中的PRESENT置位并且发生了写错误缺页中断

​ 应用场景:通过fork调用父进程创建子进程,父子进程都共享父进程的匿名页面,其中一方修改内容的时候就会发生写时复制

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DipsyHu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值