2026-07-16
Before Linux 3.2, if you wanted to read memory from another process, you had exactly two options: attach with ptrace (which stops the target and requires a word-at-a-time PEEKDATA loop) or read /proc/PID/mem (which requires a seek + read per region and traps into the VFS layer). Both are miserable for anything larger than a few kilobytes.
process_vm_readv(2) and process_vm_writev(2) fix this. They take a target PID, an array of local iovecs, and an array of remote iovecs, and perform a bulk cross-address-space copy in a single syscall — without stopping the target and without touching the page cache.
ssize_t process_vm_readv(pid_t pid,
const struct iovec *local_iov, unsigned long liovcnt,
const struct iovec *remote_iov, unsigned long riovcnt,
unsigned long flags);
Internally, the kernel calls get_user_pages_remote() on the target's mm, pins the pages, memcpys directly, then releases them. No signal, no stop, no scheduling perturbation on the target. The target keeps running throughout — reads see whatever value happens to be there when the copy hits that page.
Permissions: The caller must have PTRACE_MODE_ATTACH_REALCREDS access to the target — same rules as ptrace. Same UID, or CAP_SYS_PTRACE, and Yama's ptrace_scope still applies. This is not a security bypass; it's an ergonomics upgrade.
Real-world example: Google's gperftools heap profiler and criu (Checkpoint/Restore in Userspace) both use process_vm_readv to snapshot a target's heap. CRIU's dump phase reads gigabytes of anonymous memory this way at roughly memcpy speed — a ptrace(PTRACE_PEEKDATA) loop would take hours per GB (one syscall per 8 bytes = ~125M syscalls/GB). GDB uses it as of 7.7 for the same reason: attaching to a 32GB process and doing info proc mappings + x/ reads is now bounded by memory bandwidth, not syscall overhead.
Rule of thumb: One process_vm_readv call caps out around UIO_MAXIOV (1024) iovecs per side, and the kernel limits each transfer to SSIZE_MAX. For bulk copies, batch into ~64KB chunks per iovec — that amortizes the get_user_pages walk and stays under the pinned-memory limit. Expect ~5-10 GB/s on modern hardware, vs. ~50 MB/s for a PEEKDATA loop. That's a ~100× improvement, and the target never blocks.
Caveat: The read is not atomic across pages. If the target is writing while you're reading a struct that spans a page boundary, you can see torn state. For consistency, either freeze the target (SIGSTOP), use CRIU's seize protocol, or read only immutable regions.
process_vm_readv/writev gives you memcpy-speed cross-process memory access with ptrace-equivalent permissions and zero target perturbation — the right tool whenever you'd otherwise loop over PTRACE_PEEKDATA or seek through /proc/PID/mem.
