System I/O Integration
This section explains how buffer sequences interface with operating system I/O operations.
Prerequisites
-
Completed Buffer Sequences
-
Understanding of buffer sequence concepts
The Virtual Boundary
User-facing APIs use concepts for composition flexibility. But at type-erasure boundaries—where virtual functions are needed—concrete types are required.
Capy’s design:
-
User-facing API — Accepts
ConstBufferSequenceorMutableBufferSequenceconcepts -
Internal boundary — Converts to concrete arrays for virtual dispatch
-
OS interface — Translates to platform-specific structures
The library handles all conversions automatically.
Platform Buffer Structures
Translation Process
When you call an I/O function with a buffer sequence:
template<ConstBufferSequence Buffers>
io_result<std::size_t> write_some(Buffers const& buffers);
Internally, Capy:
-
Counts the number of buffers in the sequence
-
Allocates space for platform buffer structures (on stack for small sequences)
-
Copies buffer descriptors (pointer/size pairs) to platform structures
-
Calls the OS function with the platform array
-
Returns the result
Stack-Based Conversion
Conversion always happens on the stack—the implementation never allocates. A fixed-size, on-frame window of buffer descriptors (16 entries) is filled from the sequence and passed to the OS call. If the sequence has more buffers than fit in the window, the window is refilled and the OS call is repeated for the remaining buffers:
// Pseudocode of internal implementation
template<ConstBufferSequence Buffers>
auto platform_write(Buffers const& buffers)
{
iovec iovecs[16]; // fixed on-frame window, never heap-allocated
auto it = begin(buffers);
auto last = end(buffers);
while (it != last)
{
std::size_t count = fill_iovecs(iovecs, it, last, 16); // up to 16
auto result = writev(fd, iovecs, count);
// ... advance the window past the buffers just written
}
}
The window size (16) is fixed and implementation-defined. Sequences with more buffers than the window are handled by refilling it across successive OS calls; there is no heap fallback.
Scatter/Gather Benefits
Using vectored I/O provides:
Fewer System Calls
Without scatter/gather:
write(fd, header, header_len); // syscall 1
write(fd, body, body_len); // syscall 2
With scatter/gather:
iovec iov[2] = {{header, header_len}, {body, body_len}};
writev(fd, iov, 2); // single syscall
Registered Buffers
Advanced platforms offer registered buffer optimizations:
Writing Efficient Code
Minimize Buffer Count
Fewer buffers means less translation overhead:
// Prefer: single buffer when possible
auto buf = assemble_message(); // Build in one buffer
write(stream, buf);
// Avoid: many tiny buffers
std::array<const_buffer, 100> tiny_bufs;
write(stream, tiny_bufs); // 100-element translation
Reuse Buffer Structures
For repeated I/O with the same structure, consider caching the platform buffer array:
// Build once, use many times
struct message_buffers
{
std::array<iovec, 3> iovecs;
void set_header(void const* p, std::size_t n);
void set_body(void const* p, std::size_t n);
void set_footer(void const* p, std::size_t n);
};
Reference
The buffer sequence concepts and translation utilities are in:
#include <boost/capy/buffers.hpp>
OS-specific I/O is handled by Corosio, which builds on Capy’s buffer model.
You have now learned how buffer sequences integrate with operating system I/O. Continue to Buffer Algorithms to learn about measuring and copying buffers.