这篇笔记简单介绍了 linux mm 子系统中的 userfaultfd 的原理和使用方法。 我们假设读者已经理解了#Virtual_Memory 中的一些基本概念,比如什么是 pagefault userfaultfd 这一名称暗示了其功能:在用户态中处理 pagefault 的基础设施
create a file descriptor for handling page faults in user space
- user: 在用户态中
in user space
- fault: 处理 page fault
handling page faults
- fd: 的基础设施(通过文件描述符和 ioctl 使用)
create a file descriptor
相关的官方文档
-
userfaultfd(2): 基本的设计与理论 manpage
-
ioctl_userfaultfd(2) : 可用的 ioctl 命令
-
Userfaultfd: 内核中描述设计的文档
建议阅读顺序: 内核文档⇒ manpage ⇒ ioctl manpage
基本的使用方法概览
-
创建
uffd
这里没什么神奇的魔法,文件描述符的存在形态就是一个
i64
. 普通的 fd 用open
syscall 获得,uffd
用一个专门的syscall
获得- c code block below:
long uffd; /* userfaultfd file descriptor */ uffd = syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK); ```
-
配置
uffd
-
我们要配置什么? uffd 的目标是在用户态处理 pagefault,那么显而易见的,我们需要配置 uffd 处理哪里(即哪个地址空间里)的 fault
-
怎么配置?既然它是 fd,那么应该使用ioctl进行配置
- c code block below:
// describe the config parameter struct uffdio_register uffdio_register; addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); uffdio_register.range.start = (unsigned long) addr; uffdio_register.range.len = len; uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; // config the uffd ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) ```
-
-
获得 page fault
同样没有什么神奇的魔法,从 fd 中获取信息的通用方式就是 poll (epoll/poll/select)
- c code block below:
struct pollfd pollfd; static struct uffd_msg msg; pollfd.events = POLLIN; nready = poll(&pollfd, 1, -1); // block until some fault occurs nread = read(uffd, &msg, sizeof(msg)); ```
-
处理 page fault
显然我们需要一个真实存在于 PA 中的页面来实现 page fault handling 另外,不出所料,调用 uffd 的方式仍然是
ioctl
- c code block below:
struct uffdio_copy uffdio_copy; static struct uffd_msg msg; // page from somewhere // msg from poll uffdio_copy.src = (unsigned long) page; uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(page_size - 1); uffdio_copy.len = page_size; uffdio_copy.mode = 0; uffdio_copy.copy = 0; ioctl(uffd, UFFDIO_COPY, &uffdio_copy); ```
细节 topics
-
UFFD 监听模式
通过配置 uddfio_register 结构体中的 mode 字段,uffd 能够上报以下三种类型的 page faultUFFDIO_REGISTER_MODE_MISSING
: major fault 情形, 页不在内存里 since:: 4.10UFFDIO_REGISTER_MODE_WP
: write protect fault 情形,写了被保护的页面 since:: 5.7UFFDIO_REGISTER_MODE_MINOR
: minor fault 情形,页在内存里,但是没有#PTE since:: 5.13
-
UFFD 操作
UFFDIO_ZEROPAGE
: 内核的默认行为,分配一个零页UFFDIO_COPY
: 可以选择页面的内容UFFDIO_CONTINUE
: 可以 mapping 一个已经存在的页UFFDIO_WRITEPROTECT
: 可以 write unprotect 某个页面
源码概览
-
核心逻辑
uffd 的核心逻辑实现在linux/fs/userfaultfd.c,大约 2000 行代码,以设备驱动(
miscdevice
)的形式实现,接口通过 file_operations (code)暴露 -
触发入口
核心的触发入口是
handle_userfault
这个函数主要被放置于位于
mm/memory.c
的 do_anonymous_page 中