process_vm_readv() and process_vm_writev() let one Linux process copy bytes directly between its address space and another process’s address space. The calls operate on vectors of local and remote memory ranges, but a successful process lookup does not make remote memory stable. Mapping changes, page accessibility, permissions, and concurrent mutation remain separate parts of the contract.
These interfaces are Linux-specific system calls. They do not define C object lifetime, synchronization, or a portable interprocess-memory model.
Two iovec arrays describe two address spaces
Both calls receive a local iovec array and a remote iovec array. For process_vm_readv(), bytes move from the remote vectors into the local vectors. process_vm_writev() reverses that direction.
The vectors do not need matching element boundaries. Linux walks the local and remote vectors in array order and advances through their byte ranges. A 4 KiB remote element can therefore feed several smaller local elements, provided the total transferred byte count reaches those ranges.
The kernel validates the local and remote vector counts and computes the requested sizes subject to the interface limits. The addresses inside the remote vectors, however, refer to the target process’s virtual address space. Their meaning depends on that process’s mappings at the time access occurs.
This creates an important distinction: an iovec is a transfer description, not a retained reference to a memory object.
Permission checks gate access before byte transfer
Cross-process memory access is subject to a ptrace access-mode check. Credentials, dumpability, capabilities, user namespaces, and Linux Security Module policy can affect the result. Merely knowing a PID and a virtual address is not sufficient authority.
The access rules also mean that deployment context matters. Code that succeeds between related processes under one credential arrangement can fail after privilege changes, namespace changes, or a stricter security policy.
Neither call attaches to the target in the debugger sense. The target is not implicitly stopped, and the system calls do not establish application-level synchronization with threads that may read or modify the same memory.
A remote address is valid only while its mapping supports the access
A virtual address has no durable identity across mapping changes. The target can call munmap(), replace a mapping with mmap() using MAP_FIXED, change protection with mprotect(), or exit while another process prepares a transfer.
As a result, address discovery and memory transfer form separate operations unless the application supplies a stronger coordination protocol. An address observed from target metadata can become invalid or can later refer to different storage.
This differs from APIs that return a kernel handle with retained object identity. process_vm_readv() and process_vm_writev() take a PID plus raw remote addresses for each call. They do not pin a logical allocation merely because the address was valid earlier.
Partial transfers are part of the interface
The return value is a byte count, not a promise that every requested range was copied. A transfer can stop after copying a prefix when a later remote range cannot be accessed.
Current Linux documentation describes partial transfer at remote-page granularity. If a remote iovec element spans several pages and a later page is invalid or unavailable, bytes from preceding pages can already have been transferred. For data with an uncertain extent, splitting the remote description at page boundaries makes the progress boundary explicit and limits the range affected by a later inaccessible page.
Callers must therefore treat a positive return smaller than the requested total as meaningful state. Retrying from the original vector arrays without adjusting offsets can duplicate an earlier portion of a write or overwrite already filled local bytes during a read.
A robust caller advances both vector streams by the returned byte count before deciding whether another attempt is valid.
Byte copying does not create a synchronization relation
A completed read reports bytes copied during the system call; it does not make a concurrently changing data structure into a coherent snapshot. If the target mutates several fields without an agreed synchronization protocol, the reader can observe a combination that never represented an application-defined stable state.
The same issue applies to writes. Writing several bytes into another process does not grant atomicity matching the application’s object model. Other target threads may access the region concurrently, and language-level data-race rules still matter for code that interprets those bytes as language objects.
For cross-process inspection, a sequence counter, explicit target quiescence, a shared protocol, or another application-defined consistency mechanism may be required. The system call supplies transport across address spaces; it does not supply transactional semantics.
Remote pointers remain target-process values
Copying a structure that contains pointers does not make those pointers useful in the caller’s address space. A pointer field is normally a virtual address interpreted in the target process. Address-space layout, mapping placement, and object lifetime remain local to that process.
The caller can use such a value as a remote address for a later transfer when the value is trustworthy and still valid, but dereferencing it locally is a different operation with unrelated mappings.
This also makes ABI agreement relevant. Structure layout, field widths, endianness across supported architectures, and versioned application formats must be accounted for when bytes are interpreted rather than treated as opaque data.
Transfer boundaries belong in the protocol
These system calls are most predictable when the application treats remote memory access as a protocol with explicit identity, lifetime, and consistency rules. The kernel checks access and moves bytes, while the application remains responsible for deciding which address denotes which data and when that data may change.
That separation prevents a low-level copy primitive from being mistaken for a stable shared-memory abstraction. The observable result is a byte transfer across two virtual address spaces, bounded by current mappings, access policy, and the synchronization guarantees supplied elsewhere.