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_none(pte)

页表项为空

do_pte_missing()

!pte_present(pte)

页表项存放但不在内存中

do_swap_page()

pte_present(pte)!pte_write(pte)

页在内存中但写保护

do_wp_page()

pte_present(pte)且可写

更新访问标志

pte_mkyoung()/pte_mkdirty()

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

分配匿名页或零页

复制数据 __wp_page_copy_user

从旧页复制到新页(除非是零页)

构造新 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

#嵌入式##嵌入式笔面经分享##牛客在线求职答疑中心##牛客创作赏金赛#
全部评论

相关推荐

评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务