在Linux 5.7内核中,内存管理是操作系统的核心功能之一,而缺页异常(Page Fault)处理是内存管理中的重要组成部分。缺页异常发生在进程访问的虚拟地址没有对应物理页面时。处理缺页异常涉及到内存保护、页面置换、内存映射和虚拟内存管理等多个方面。
缺页异常的触发
当CPU尝试访问一个没有映射到物理内存的虚拟地址时,硬件会触发一个缺页异常。这个异常会被送入操作系统内核,由内核的缺页异常处理程序来处理。
缺页异常处理流程
-
捕获缺页异常:当硬件触发缺页异常时,内核会捕获这个异常,并进入缺页处理程序。
-
分析缺页原因:内核首先需要确定缺页的原因。可能的原因包括:
- 访问的页面尚未分配。
- 页面已经被置换出物理内存。
- 访问的内存区域是受保护的,如写入一个只读页面。
-
处理缺页:根据缺页的原因,内核会采取不同的措施:
- 如果页面尚未分配,内核将分配一个新的页面,并将其映射到请求的虚拟地址。
- 如果页面已经被置换出物理内存,内核将从二级存储(通常是磁盘)中恢复页面到物理内存中。
- 如果访问受保护的内存区域,内核将根据访问类型(读、写或执行)来处理。
-
更新页表:内核更新页表项,将虚拟地址映射到正确的物理地址。
-
设置页面权限:内核设置页面的访问权限,确保符合进程的访问需求。
-
唤醒进程:如果进程因缺页而被挂起,内核将唤醒该进程,让它继续执行。
-
返回用户空间:处理完成后,内核返回到用户空间,让进程继续执行。
代码流程分析
在Linux内核中,缺页异常的处理通常由do_page_fault
函数来完成。以下是do_page_fault
函数的代码流程分析:
asmlinkage int do_page_fault(struct pt_regs *regs, unsigned long address,
unsigned long error_code)
{
struct task_struct *tsk = current;
struct mm_struct *mm = tsk->mm;
struct vm_area_struct *vma;
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;
pte_t *pte, pte_val;
spinlock_t *ptl;
int write_access, fault;
unsigned int flags;
// 1. 检查地址是否有效
if (!address_valid(address))
return -EFAULT;
// 2. 进入原子操作区域
enter_lazy_tlb(mm, vma);
// 3. 找到虚拟内存区域
vma = find_vma(mm, address);
if (!vma)
goto out_of_memory;
if (vma->vm_start > address)
goto bad_area;
if (!can_do_page_fault(vma))
goto bad_area;
// 4. 检查访问类型
write_access = (error_code & PF_WRITE) || !(address < vma->vm_end);
// 5. 处理缺页
flags = FAULT_FLAG_ALLOW_RETRY;
if (write_access)
flags |= FAULT_FLAG_WRITE;
fault = handle_mm_fault(mm, vma, address, flags);
// 6. 根据处理结果进行不同的操作
switch (fault) {
case VM_FAULT_MINOR:
break;
case VM_FAULT_MAJOR:
break;
case VM_FAULT_SIGBUS:
goto do_sigbus;
case VM_FAULT_OOM:
goto out_of_memory;
default:
current->thread.fault_address = reg_current_ip();
goto bad_area;
}
// 7. 更新统计信息
tsk->min_flt++;
if (fault & VM_FAULT_ERROR)
tsk->maj_flt++;
// 8. 退出原子操作区域
leave_lazy_tlb(&mm->mmap_sem);
return 0;
// 处理不同的错误情况
...
}
代码注释
struct pt_regs *regs
:包含触发缺页异常时的CPU寄存器状态。unsigned long address
:触发缺页异常的虚拟地址。unsigned long error_code
:错误码,包含触发缺页异常的原因。
函数首先检查触发缺页异常的地址是否有效,然后进入原子操作区域以避免并发问题。接着,函数尝试找到对应的虚拟内存区域(find_vma
),如果没有找到或者不能进行缺页处理,则会报错。
接下来,函数检查访问类型,区分是读访问还是写访问,并调用handle_mm_fault
来处理缺页。这个函数会尝试分配新页面、恢复被置换的页面或者处理其他缺页情况。
根据handle_mm_fault
的返回值,函数会执行不同的操作,如更新统计信息、处理内存不足等。
缺页异常处理的复杂性
缺页异常处理涉及到操作系统的多个层面,包括硬件交互、内存分配、文件系统交互和进程调度等。在实际的Linux内核实现中,缺页异常处理的代码会更加复杂,涉及到更多的细节和特殊情况处理。