2026-07-11
You already know splice() moves data between two file descriptors by shuffling pipe buffer pointers instead of copying bytes. But splice() requires one end to already be a pipe. What if the source is a plain char* in your process? That's where vmsplice() comes in — it "splices" user-space memory into a pipe without a copy, by mapping your pages directly into the pipe's buffer ring.
The mechanism is subtle. A Linux pipe is a ring of up to 16 struct pipe_buffer entries, each pointing at a struct page. Normally the kernel allocates those pages. With vmsplice(fd, iov, nr, SPLICE_F_GIFT), the kernel instead calls get_user_pages() on your buffer, pins the physical pages, and installs them directly into pipe_buffer slots. No memcpy. The reader on the other end of the pipe sees your bytes by reading the same physical RAM you wrote.
The "gift" is literal. With SPLICE_F_GIFT set, you are transferring ownership of those pages to the kernel. Your virtual address still maps them, but if you write to that memory before the reader consumes it, you corrupt the reader's data. The kernel doesn't COW-protect gift pages — that would defeat the zero-copy point. Worse: pages must be page-aligned and page-sized. A misaligned buffer silently falls back to a copy.
Real-world example: the vmsplice-based log shipper pattern. A logging daemon mmap()s a huge anonymous region, writes structured records into 4KB-aligned slabs, then vmsplice()s completed slabs into a pipe connected to a compressor process via splice()-to-socket. Throughput hits ~40 GB/s on a single core because the log bytes never touch the CPU cache after the initial write — the compressor reads them straight from the same physical pages. Compare to a naive write(pipe_fd, buf, len): the kernel copy_from_user()s every byte, capping you around 6 GB/s per core.
Rule of thumb: vmsplice wins when your buffer is ≥64KB, page-aligned, and you won't touch it again until the reader signals consumption. Below 64KB, the get_user_pages() overhead (page table walk + refcount + pipe_buffer setup) exceeds the copy cost of just calling write().
The security footnote: CVE-2022-0847 ("Dirty Pipe") exploited a bug where vmsplice'd pipe_buffer flags leaked between operations, letting an unprivileged user overwrite read-only files by splicing into a pipe whose stale PIPE_BUF_FLAG_CAN_MERGE flag pointed at a page-cache page. One uninitialized flag = arbitrary root file write.
vmsplice() achieves zero-copy from user memory to a pipe by gifting your physical pages to the kernel — fast, but you lose the right to modify those pages until the reader consumes them.
