代码路径:arch/arm64/mm/fault.c
/**
* @brief
* 通过汇编函数 el1_da 跳转到这个函数
* @param addr 失效地址 x0 FSR 提供
* @param esr el1_da 汇编函数中读取到的el1_da寄存器 x3 ESR提供
* @param regs 发生异常时寄存器pt_regs指针 x2
* @return asmlinkage
*/
//代码在.kprobes.text
//# define __kprobes __attribute__((__section__(".kprobes.text")))
static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,
struct pt_regs *regs)
{
const struct fault_info *inf;
struct task_struct *tsk;
struct mm_struct *mm;
vm_fault_t fault, major = 0;
unsigned long vm_flags = VM_READ | VM_WRITE;
unsigned int mm_flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE;
if (notify_page_fault(regs, esr))//通知页面故障
return 0;
tsk = current;
mm = tsk->mm;
/*
* If we're in an interrupt or have no user context, we must not take
* the fault.
*/
//判断 内核是否在执行一些关键路经的代码 tsk->mm == NULL
// pagefault_disabled()---> 页错误被disable || in_atomic() ----> 是否处于原子状态
if (faulthandler_disabled() || !mm)
goto no_context;
if (user_mode(regs))
mm_flags |= FAULT_FLAG_USER;
//判断当前异常类型 根据 esr 设置相关的标志位
if (is_el0_instruction_abort(esr)) {
vm_flags = VM_EXEC;
} else if ((esr & ESR_ELx_WNR) && !(esr & ESR_ELx_CM)) {
vm_flags = VM_WRITE;
mm_flags |= FAULT_FLAG_WRITE;
}
//根据FAR 寄存器 判断异常是否发生在用户空间
if (is_ttbr0_addr(addr) && is_el1_permission_fault(addr, esr, regs)) {
/* regs->orig_addr_limit may be 0 if we entered from EL0 */
if (regs->orig_addr_limit == KERNEL_DS)
die_kernel_fault("access to user memory with fs=KERNEL_DS",
addr, esr, regs);
if (is_el1_instruction_abort(esr))
die_kernel_fault("execution of user memory",
addr, esr, regs);
if (!search_exception_tables(regs->pc))
die_kernel_fault("access to user memory outside uaccess routines",
addr, esr, regs);
}
perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, addr);
/*
* As per x86, we may deadlock here. However, since the kernel only
* validly references user space from well defined areas of the code,
* we can bug out early if this is from code which shouldn't.
*/
//判断当前进程的 mm->mmap_sem 是否可以获取
//返回1 可以获取读写信号 0 不能获取读写信号
//如果brk在申请堆空间 或者mmap 正在进行内存映射 就获取不到读写信号
if (!down_read_trylock(&mm->mmap_sem)) {
if (!user_mode(regs) && !search_exception_tables(regs->pc))
goto no_context;//发生在内核空间 使用内核的__do_kernel_fault 函数
retry:
//发生在用户空间 使用down_read 等待持有者释放锁
down_read(&mm->mmap_sem);
} else {
/*
* The above down_read_trylock() might have succeeded in which
* case, we'll have missed the might_sleep() from down_read().
*/
might_sleep();
#ifdef CONFIG_DEBUG_VM
if (!user_mode(regs) && !search_exception_tables(regs->pc))
goto no_context;
#endif
}
// 用户空间 使用__do_page_fault
fault = __do_page_fault(mm, addr, mm_flags, vm_flags, tsk);
major |= fault & VM_FAULT_MAJOR;
...
//释放锁
up_read(&mm->mmap_sem);
...
return 0;
no_context:
__do_kernel_fault(addr, esr, regs);
return 0;
}