Linux内核6.6 内存管理(10)缺页中断
1.整体处理逻辑
页缺失错误——整体的处理函数是:
https://elixir.bootlin.com/linux/v6.6/source/arch/arm64/mm/fault.c#L530
do_page_fault(处理物理页的错误) handle_mm_fault __handle_mm_fault(处理进程的整个虚拟内存错误) handle_pte_fault(处理PTE错误) do_pte_missing 处理页表项(PTE)缺失错误 do_anonymous_page(匿名页面缺页) do_fault(文件页面缺页)
https://elixir.bootlin.com/linux/v6.6/source/mm/memory.c#L4947
handle_pte_fault
条件 | 含义 | 处理函数 |
| 页表项为空 |
|
| 页表项存放但不在内存中 |
|
| 页在内存中但写保护 |
|
| 更新访问标志 |
|
pte
的 present
位是什么?
在 Linux 的页表机制中,每个页表项(PTE)都有一个 present
位(通常是最低位),表示该页是否已经被映射到物理内存:
present = 1
:页已经存在于物理内存中。present = 0
:页不在内存中,可能在交换空间(swap)中,或者根本还没分配。
2.匿名页面的缺页中断
问题 1:什么是匿名页面?
在 Linux 内核中没有关联到文件映射的页面称为匿名页面(Anonymous Page,简称 anon page)。比如 malloc 分配的页面
问题 2:为啥需要匿名页面的缺页中断?
如果没有匿名页面缺页中断,那么你在用户空间使用 malloc () 这个 API 来分配虚拟内存,那么在内核中都需要实实在在的为其分配物理内存。主要的问题,就是浪费,因为很多时候应用程序分配虚拟内存并不会马上使用,所以没有必要马上就满足应用程序的需求。
问题 3:匿名页面缺页中断发生的条件是啥?
1)当 pte 页表项中 PRESENT 没有置位
2)pte 内容为空
3)没有指定 vma->vm_ops->fault () 函数指针
满足上述三个条件我们就判断现在是匿名页面缺页中断了
页表项为空中:匿名页面缺页处理函数
do_anonymous_page() ↓ 检查是否非法共享映射 ↓ 分配页表项(PTE) ↓ 只读访问?→ 是 → 使用零页 if (!(vmf->flags & FAULT_FLAG_WRITE) && !mm_forbids_zeropage(vma->vm_mm)) ↓ 否 → 分配新页 folio = vma_alloc_zeroed_movable_folio(vma, vmf->address); ↓ 构造 PTE → 设置属性 entry = mk_pte(&folio->page, vma->vm_page_prot); → 映射页表项 set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry); ↓ 写入页表 → 更新 MMU 缓存 update_mmu_cache_range(vmf, vma, vmf->address, vmf->pte, 1);
应用场合:malloc分配内存
3.页表项为空:文件映射缺页中断
文件映射缺页中断 do_fault ()
(1)判断条件:pte 页表项中的 PRESENT 没有置位、pte 内容为空且指定了 vma->vm_ops->fault () 函数指针。do_fault () 属于在文件映射中发生的缺页中断的情况。
✔如果仅发生读错误,那么调用 do_read_fault () 函数去处理
✔如果在私有映射 VMA 中发生写保护错误且不是共享内存, do_cow_fault()
写时复制:新分配一个页面 new_page,旧页面的内容要复制到新页面中,利用新页面生成一个 PTE entry 并设置到硬件 页表项中,这就是所谓的写时复制 COW。
✔如果写保护错误发生在共享映射 VMA 中,那么就产生了脏页 do_shared_fault()
调用系统的回写机制来回写这个脏页。
(2)应用场合:
✔使用 mmap 读文件内容,例如驱动中使用 mmap 映射设备内存到用户空间等。
✔动态库映射,例如不同的进程可以通过文件映射来共享同一个动态库。
https://elixir.bootlin.com/linux/v6.6/source/mm/memory.c#L4678
以写时复制举个例子
do_fault do_cow_fault alloc_page_vma 分配新页面 底层函数__do_fault处理页面错误 copy_user_highpage(vmf->cow_page, vmf->page, vmf->address, vma);原页面内容复制到新页面 __SetPageUptodate(vmf->cow_page);标记页面为已更新
4.页表项存在但页不在内存中
https://elixir.bootlin.com/linux/v6.6/source/mm/memory.c#L4983
5.页在内存中
5.1 不可写
https://elixir.bootlin.com/linux/v6.6/source/mm/memory.c#L3338
写时复制 COW 缺页中断 do_wp_page ()
- (1)do_wp_page () 最终有两种处理情况。
- reuse 复用 old_page: 私有映射匿名页,且当前进程是唯一持有者(引用计数为 1),就可以直接重用,不需要复制。 如果是共享映射(如 mmap(MAP_SHARED)),就不进行写时复制。
- gotten 写时复制:非单身匿名页面、只读或者非共享的文件映射页面。(真正COW)wp_page_copy
- (2)判断条件:pte 页表项中的 PRESENT 置位了且发生写错误缺页中断。
- (3)应用场景:
- fork: 父进程 fork 子进程,父子进程都共享父进程的匿名页面,当其中一方需要修改内容时,COW 便会发生。
分配新页 vma_alloc_folio | 分配匿名页或零页 |
复制数据 | 从旧页复制到新页(除非是零页) |
构造新 PTE | 设置为可写、已访问、已修改 |
更新页表 | 清除旧 PTE,设置新 PTE |
更新反向映射 folio_add_new_anon_rmap | 建立新页的 rmap,移除旧页的 rmap |
管理 LRU folio_add_lru_vma | 将新页加入 LRU,释放旧页 |
5.2 可写
https://elixir.bootlin.com/linux/v6.6/source/mm/memory.c#L4999
#嵌入式##嵌入式笔面经分享##牛客在线求职答疑中心##牛客创作赏金赛#