Page Fault handle on userspace - Userfaultfd
Author : 堇姬 Naup
What is userfaultfd
userfaultfd 是一種 syscall (syscall table rax = 388)
user 可以去自訂 user pagefault handler 來去處理 userspace 的缺頁異常 (https://zh.wikipedia.org/wiki/%E9%A1%B5%E7%BC%BA%E5%A4%B1)
https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md
首先需要先去註冊一個 userfaultfd
通過 ioctl 來去監視一塊記憶體
之後去創建一個 uffd monitor 的 thread 來發出 poll() 輪巡,直到出現 page fault

這張圖解釋了如何去處理 userfault
首先是當一個 thread 發生 page fault (faulting thread) ,他會去 kernel space 處理
memory management 會去調用 handle_userfault() 丟去 userfaultfd 來去處理
首先 userfaultfd 先告知 faulting thread 阻塞,之後發送 uffd_msg 到 uffd monitor 並等待處理
隨後 monitor 會處理 page fault 最後處理完發送訊號告知 faulting page 繼續執行
用法
用法可以參考 man page
https://man7.org/linux/man-pages/man2/userfaultfd.2.html
先初始化
1 |
|
首先要先註冊 userfaultfd,可以通過 syscall 來註冊,並存到 uffd
第一個參數是 userfaultfd 的 syscall number,第二個是 flags
1 | /* Create and enable userfaultfd object. */ |
這邊設置 userfaultfd 的 api
在使用 userfaultfd() 建立 userfaultfd 物件之後,應用程式必須透過 UFFDIO_API 的 ioctl 操作來啟用它。這個操作允許核心與使用者空間之間進行兩階段的握手,以決定核心所支援的 API 版本與功能
1 | /* NOTE: Two-step feature handshake is not needed here, since this |
接下來創建一個匿名映射區段,並存下他的 address
之後去註冊這個位置,以及要監控的長度
之後通過 ioctl 給 userfaultfd 註冊
UFFDIO_REGISTER_MODE_MISSING(自 Linux 4.10 起提供)
當以 UFFDIO_REGISTER_MODE_MISSING 模式註冊記憶體區段時,
當存取尚未存在的頁面(missing page)時,用戶空間會接收到 page fault 通知。
此時,發生錯誤的執行緒會被暫停,直到用戶空間透過 UFFDIO_COPY 或 UFFDIO_ZEROPAGE 的 ioctl 指令來處理這個 page fault 為止
1 | /* Create a private anonymous mapping. The memory will be |
之後去創建一個 userfaultfd 處理的 process
1 | /* Create a thread that will process the userfaultfd events. */ |
poll() 是一個系統呼叫(system call)或函式,常用於事件驅動的 I/O 多工(I/O multiplexing),主要用來監控多個檔案描述符(file descriptors)
創建一個迴圈,用 poll 循環去監視 userfaultfd 狀況
1 | struct pollfd { |
https://man7.org/linux/man-pages/man2/poll.2.html
man 中的實作在我的 VM 上跑不了,所以改成這樣 (準確實作參考最下面的 demo)
總之最後去讀 uffd 有沒有發生事件,有的話去處理
最後返回 UFFDIO_COPY
1 | void * uffdio_handle_thread(void * user_fault_fd){ |
完整 demo
1 |
|
總之最後就成功了,因為你 mmap 建立映射後,當你第一次去使用,因為還沒有去 mapping 到 physical address
所以理論上他會觸發 page fault 去 kernel 分配
但是你在 userspace 實作了 page fault
所以說就會被 poll 捕捉到事件,進而在 userspace 處理 page fault

這邊將上面的 Demo mmap 方式改成
1 | char* addr = mmap(NULL,page_size,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS| MAP_POPULATE,-1,0); |
他就會在 mmap 時候預先分配 physical page
這樣去讀取時候就不會觸發 page fault 了
