用户空间缺页异常

缺页异常

CPU通过地址总线可以访问连接在地址总线上的所有外设,包括物理内存、IO设备等;但从CPU发出的访问地址并非是这些外设在地址总线上的物理地址,而是一个虚拟地址;有MMU(Memory Management Uint)将虚拟地址转换成物理地址再从地址总线上发出;MMU上的这种虚拟地址和物理地址的转换关系是需要创建的;并且MMU还可以设置这个物理页是否可以进行写操作;当没有创建一个虚拟地址到物理地址的映射,或者创建了这样的映射,但是该物理页不可写的时候,MMU将会通过CPU产生一个缺页异常;
在实际需要某个虚拟内存区域的数据之前,虚拟内存和物理内存之间的关联不会建立;如果进程访问的虚拟地址空间尚未于页帧关联,CPU自动的产生一个缺页异常,内核必须处理此异常;
缺页异常的处理实现因CPU的不同而有所不同;由于CPU采用了不同的内存管理概念,生成缺页异常的细节也不太相同;因此,缺页异常的处理例程在内核代码中位于特定于体系结构的部分;

页表和TLB

页表(Page Table)和TLB(Translation Lookaside Buffer)是在计算机体系结构中用于地址转换的密切相关的概念。

  1. 页表
  • 页表是一种数据结构,存储了虚拟内存地址和物理内存地址之间的映射关系。当CPU发出内存访问请求时,操作系统通过页表将虚拟地址转换为物理地址。如果页面不在内存中,会发生缺页异常,并触发加载页面到内存中的操作。
  1. TLB
  • TLB是存储最近使用的页表条目的高速缓存,用于加速地址转换过程。TLB可以在CPU内部或者在MMU内部,存储一部分页表的映射关系,当CPU发出内存访问请求时,会先在TLB中寻找对应的物理地址,以提高地址转换的速度。

因此,页表和TLB之间的关系可以理解为页表提供了全部的地址映射关系,而TLB则是一种缓存机制,存储了最近频繁访问的一部分地址映射关系,用于加速地址转换过程。当CPU访问内存时,会先在TLB中寻找物理地址,如果缺失则会根据页表进行地址转换。

用户缺页异常处理流程

ARM64架构,页错误处理入口函数是do_translation_fault;

/*
 * First Level Translation Fault Handler
 *
 * We enter here because the first level page table doesn't contain a valid
 * entry for the address.
 *
 * If the address is in kernel space (>= TASK_SIZE), then we are probably
 * faulting in the vmalloc() area.
 *
 * If the init_task's first level page tables contains the relevant entry, we
 * copy the it to this task.  If not, we send the process a signal, fixup the
 * exception, or oops the kernel.
 *
 * NOTE! We MUST NOT take any locks for this case. We may be in an interrupt
 * or a critical region, and should only copy the information from the master
 * page table, nothing more.
 */
static int __kprobes do_translation_fault(unsigned long addr,
					  unsigned int esr,
					  struct pt_regs *regs)
{
	if (addr < TASK_SIZE)
		return do_page_fault(addr, esr, regs);

	do_bad_area(addr, esr, regs);
	return 0;
}

如果触发异常的虚拟地址是空户虚拟地址,调用函数do_page_fault来处理;如果触发异常的虚拟地址是内核虚拟地址或不规范地址,调用函数do_bad_area来处理;

do_page_fault最后会委托handle_mm_fault处理用户空间的页错误异常;从函数handle_mm_fault开始的部分是所有处理器架构共用的部分;用户空间页错误异常是指进程访问用户虚拟地址生成的页错误异常,分两种情况:
1)进程在用户模式下访问用户虚拟地址,生成页错误异常;
2)进程在内核模式下访问用户虚拟地址,生成页错误异常;进程通过系统调用进入内核模式,系统调用传入用户空间的缓冲区,进程在内核模式下访问用户空间的缓冲区;

handle_mm_fault处理流程如下:如果虚拟内存区域使用标准巨型页,那么调用函数hugetlb_fault处理标准巨型页的页错误异常;如果虚拟内存区域使用普通页,那么调用函数__handle_mm_fault处理普通页的页错误异常;
image.png

/*
 * By the time we get here, we already hold the mm semaphore
 *
 * The mmap_sem may have been released depending on flags and our
 * return value.  See filemap_fault() and __lock_page_or_retry().
 */
int handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
		unsigned int flags)
{
	int ret;

	__set_current_state(TASK_RUNNING);

	count_vm_event(PGFAULT);
	mem_cgroup_count_vm_event(vma->vm_mm, PGFAULT);

	/* do counter updates before entering really critical section. */
	check_sync_rss_stat(current);

	/*
	 * Enable the memcg OOM handling for faults triggered in user
	 * space.  Kernel faults are handled more gracefully.
	 */
	if (flags & FAULT_FLAG_USER)
		mem_cgroup_oom_enable();

	if (!arch_vma_access_permitted(vma, flags & FAULT_FLAG_WRITE,
					    flags & FAULT_FLAG_INSTRUCTION,
					    flags & FAULT_FLAG_REMOTE))
		return VM_FAULT_SIGSEGV;

	if (unlikely(is_vm_hugetlb_page(vma)))
		ret = hugetlb_fault(vma->vm_mm, vma, address, flags);
	else
		ret = __handle_mm_fault(vma, address, flags);

	if (flags & FAULT_FLAG_USER) {
		mem_cgroup_oom_disable();
		/*
		 * The task may have entered a memcg OOM situation but
		 * if the allocation error was handled gracefully (no
		 * VM_FAULT_OOM), there is no need to kill anything.
		 * Just clean up the OOM state peacefully.
		 */
		if (task_in_memcg_oom(current) && !(ret & VM_FAULT_OOM))
			mem_cgroup_oom_synchronize(false);
	}

	/*
	 * This mm has been already reaped by the oom reaper and so the
	 * refault cannot be trusted in general. Anonymous refaults would
	 * lose data and give a zero page instead e.g. This is especially
	 * problem for use_mm() because regular tasks will just die and
	 * the corrupted data will not be visible anywhere while kthread
	 * will outlive the oom victim and potentially propagate the date
	 * further.
	 */
	if (unlikely((current->flags & PF_KTHREAD) && !(ret & VM_FAULT_ERROR)
				&& test_bit(MMF_UNSTABLE, &vma->vm_mm->flags)))
		ret = VM_FAULT_SIGBUS;

	return ret;
}
EXPORT_SYMBOL_GPL(handle_mm_fault);

__handle_mm_fault处理流程:
1)在页全局目录中查找虚拟地址对应的表项;
2)在页四级目录中查找虚拟地址对应的表项,如果页四级目录不存在,那么先创建页四级目录;
3)在页上层目录中查找虚拟地址对应的表项,如果页上层目录不存在,那么先创建页上层目录;
4)在页中间目录中查找虚拟地址对应的表项,如果页中间目录不存在,那么先创建页中间目录;
5)到达直接页表,调用函数handle_pte_fault来处理;

handle_pte_fault中对处理匿名页的缺页异常、文件页的缺页异常和写时复制;

匿名页的缺页异常

什么情况会触发匿名页的缺页异常呢?
1)函数的局部变量比较大,或者函数调用的层次比较深,导致当前栈不够用,需要扩大栈;
2)进程调用malloc,从堆申请了内存块,只分配了虚拟内存区域,还没有映射到物理页,第一次访问时触发缺页异常;
3)进程直接调用mmap,创建匿名的内存映射,只分配了虚拟内存区域,还没映射到物理页,第一次访问时触发缺页异常;
函数do_anonymous_page处理私有匿名页的缺页异常;

文件页的缺页异常

什么情况会触发文件页的缺页异常呢?
1)启动程序的时候,内核为程序的代码段和数据段创建私有的文件映射,映射到进程的虚拟地址空间,第一次访问的时候触发文件页的缺页异常;
2)进程使用mmap创建文件映射,把文件的一个区间映射到进程的虚拟地址空间,第一次访问的时候触发文件的缺页异常;
函数do_fault处理文件页和共享匿名页的缺页异常;

handle_pte_fault的详细流程,do_anonymous_page,do_fault以及写时复制等,比较复杂,后续进行补充;
内核模式页错误异常,后续补充;

参考文献

  1. 《linux内核深度解析》,余华兵著
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值