[RFC 1/2] page_fault: add mmap-backed ioengine for anonymous faults

Nico Pache <[email protected]> Thu, 29 Jan 2026 11:43:00 -0700
Newsgroups org.kernel.vger.fio
Message-ID <[email protected]>
Introduce a new ioengine that mmaps anonymous memory and copies data
on read/write to trigger page faults. This allows us to leverage FIOs
powerful framework for MM related testing, and will ideally allow us to
quickly expand testing, by leveraging previously FS related fio scripts.

Signed-off-by: Nico Pache <[email protected]>
---
 Makefile                |   2 +-
 engines/page_fault.c    | 105 ++++++++++++++++++++++++++++++++++++++++
 examples/page_fault.fio |   9 ++++
 3 files changed, 115 insertions(+), 1 deletion(-)
 create mode 100644 engines/page_fault.c
 create mode 100644 examples/page_fault.fio

diff --git a/Makefile b/Makefile
index 0337e8fe..099e2f94 100644
--- a/Makefile
+++ b/Makefile
@@ -57,7 +57,7 @@ SOURCE :=	$(sort $(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/crc/*.c)) \
 		smalloc.c filehash.c profile.c debug.c engines/cpu.c \
 		engines/mmap.c engines/sync.c engines/null.c engines/net.c \
 		engines/ftruncate.c engines/fileoperations.c \
-		engines/exec.c \
+		engines/exec.c engines/page_fault.c \
 		server.c client.c iolog.c backend.c libfio.c flow.c cconv.c \
 		gettime-thread.c helpers.c json.c idletime.c td_error.c \
 		profiles/tiobench.c profiles/act.c io_u_queue.c filelock.c \
diff --git a/engines/page_fault.c b/engines/page_fault.c
new file mode 100644
index 00000000..e0a3c9e5
--- /dev/null
+++ b/engines/page_fault.c
@@ -0,0 +1,105 @@
+#include "ioengines.h"
+#include "fio.h"
+#include <sys/mman.h>
+
+struct fio_page_fault_data {
+    void *mmap_ptr;
+    size_t mmap_sz;
+    off_t mmap_off;
+};
+
+static int fio_page_fault_init(struct thread_data *td)
+{
+    size_t total_io_size;
+    struct fio_page_fault_data *fpd = calloc(1, sizeof(*fpd));
+    if (!fpd)
+        return 1;
+    
+    total_io_size = td->o.size;
+    fpd->mmap_sz = total_io_size;
+    fpd->mmap_off = 0;
+    fpd->mmap_ptr = mmap(NULL, total_io_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+    if (fpd->mmap_ptr == MAP_FAILED)
+    {
+        free(fpd);
+        return 1;
+    }
+
+    FILE_SET_ENG_DATA(td->files[0], fpd);
+	return 0;
+}
+
+static int fio_page_fault_prep(struct thread_data *td, struct io_u *io_u)
+{
+	return 0;
+}
+
+static enum fio_q_status fio_page_fault_queue(struct thread_data *td, struct io_u *io_u)
+{
+    void * mmap_head;
+    struct fio_page_fault_data *fpd = FILE_ENG_DATA(io_u->file);
+    if (!fpd)
+        return 1;
+
+    if (io_u->offset + io_u->buflen > fpd->mmap_sz)
+        return 1;
+
+    mmap_head = fpd->mmap_ptr + io_u->offset;
+    switch (io_u->ddir)
+    {
+        case DDIR_READ:
+            for (size_t i = 0; i < io_u->buflen; i++)
+            {
+                ((unsigned char *)(io_u->xfer_buf))[i] = ((unsigned char *)(mmap_head))[i];
+            }
+            break;
+        case DDIR_WRITE:
+            for (size_t i = 0; i < io_u->buflen; i++)
+            {
+                ((unsigned char *)(mmap_head))[i] = ((unsigned char *)(io_u->xfer_buf))[i];
+            }
+            break;
+        default:
+            return 1;
+    }
+
+	return FIO_Q_COMPLETED;
+}
+
+static int fio_page_fault_open_file(struct thread_data *td, struct fio_file *f)
+{
+	return 0;
+}
+
+static int fio_page_fault_close_file(struct thread_data *td, struct fio_file *f)
+{
+	struct fio_page_fault_data *fpd = FILE_ENG_DATA(f);
+	if (!fpd)
+		return 1;
+    if (fpd->mmap_ptr && fpd->mmap_sz)
+        munmap(fpd->mmap_ptr, fpd->mmap_sz);
+	free(fpd);
+	return 0;
+}
+
+static struct ioengine_ops ioengine = {
+	.name		= "page_fault",
+	.version	= FIO_IOOPS_VERSION,
+	.init		= fio_page_fault_init,
+	.prep		= fio_page_fault_prep,
+	.queue		= fio_page_fault_queue,
+	.open_file	= fio_page_fault_open_file,
+	.close_file	= fio_page_fault_close_file,
+	.get_file_size	= generic_get_file_size,
+	.flags		= FIO_SYNCIO | FIO_NOEXTEND | FIO_DISKLESSIO,
+};
+
+static void fio_init fio_page_fault_register(void)
+{
+	register_ioengine(&ioengine);
+}
+
+static void fio_exit fio_page_fault_unregister(void)
+{
+	unregister_ioengine(&ioengine);
+}
\ No newline at end of file
diff --git a/examples/page_fault.fio b/examples/page_fault.fio
new file mode 100644
index 00000000..9001f570
--- /dev/null
+++ b/examples/page_fault.fio
@@ -0,0 +1,9 @@
+[global]
+bs=4k
+
+[page_fault]
+ioengine=page_fault
+size=2g
+rw=randrw
+rwmixread=50
+verify=crc32c
\ No newline at end of file
-- 
2.52.0