这篇笔记简单介绍了 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

相关的官方文档

基本的使用方法概览

  • 创建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 fault
    • UFFDIO_REGISTER_MODE_MISSING: major fault 情形, 页不在内存里 since:: 4.10
    • UFFDIO_REGISTER_MODE_WP: write protect fault 情形,写了被保护的页面 since:: 5.7
    • UFFDIO_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.cdo_anonymous_page

    1. L3582: UFFDIO_REGISTER_MODE_WP
    2. L4422: 最简单的情况,只读页面 fault,没有 page 被分配
    3. L4471: 稍复杂的情况,内核已经分配了新的 page,用户可以用UFFD_COPY之类的方法实现一些业务逻辑
    4. 5193: 大页的 WP fault