Re: [PATCH 0/3] vmsplice: make vmsplice a trivial wrapper for preadv2/pwritev2
From: Askar Safin <hidden>
Date: 2026-06-23 09:42:44
Also in:
fuse-devel, linux-fsdevel, linux-mm, linux-patches, lkml, netdev
Andrei Vagin [off-list ref]:
Actually, this change introduces a performance and functional regression for CRIU. Here is a brief overview of how CRIU currently dumps memory pages: CRIU injects a parasite code blob into the target process's address space. The parasite invokes vmsplice() with the SPLICE_F_GIFT flag to pin physical pages directly inside a pipe without copying them. The main CRIU process then takes over from outside the target context, calling splice() on the other end of the pipe to stream the data directly into checkpoint image files or a remote network socket. I ran a simple test that creates an anonymous mapping and touches every page within it: Without this patch, CRIU takes 9 seconds to dump the test process. With this patch, It takes 18 seconds... Plus, it obviously introduces some memory overhead. If these changes are merged, we will need to completely rework the memory dumping mechanism in CRIU. Using vmsplice() in this proposed form no longer makes any sense for our architecture...
I just have read some docs for CRIU. I found this statement:
#### Why `splice` is Better: * **Consistency via COW**: The `SPLICE_F_GIFT` flag ensures that if the process modifies a "gifted" page after resuming, the kernel performs a **Copy-on-Write (COW)**. The pipe buffer > continues to hold the *original* version of the page as it existed at the moment of the `vmsplice()` call, ensuring a perfectly consistent snapshot of that page.
This is wrong (with released kernels). I confirmed this by testing this on my current kernel (6.12.90).
See the code in the end of this message.
If you actually rely on mentioned consistency, then, it seems, CRIU is broken.
So, in fact, my patch actually brings consistency to CRIU. :)
--
Askar Safin
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <errno.h>
int
main (void)
{
int p[2];
if (pipe (p) != 0)
abort ();
char buf[1] = {'a'};
struct iovec iov[] = {
{
.iov_base = buf,
.iov_len = 1,
}
};
// I pass "SPLICE_F_NONBLOCK | SPLICE_F_GIFT" here, because this is what criu passes
if (vmsplice (p[1], iov, 1, SPLICE_F_NONBLOCK | SPLICE_F_GIFT) != 1)
abort ();
if (close (p[1]) != 0)
abort ();
buf[0] = 'b';
char buf2[1];
if (read (p[0], buf2, 1) != 1)
abort ();
printf ("[%c]\n", buf2[0]); // Prints "b" as opposed to "a" on Linux 6.12.90
return 0;
}