System I/O Integration

This section explains how buffer sequences interface with operating system I/O operations.

Prerequisites

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 ConstBufferSequence or MutableBufferSequence concepts

  • 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

POSIX: iovec

struct iovec {
    void*  iov_base;  // Pointer to data
    size_t iov_len;   // Length of data
};

Used with readv(), writev(), recvmsg(), sendmsg().

Windows: WSABUF

typedef struct _WSABUF {
    ULONG  len;  // Length (note: first!)
    CHAR*  buf;  // Pointer
} WSABUF;

Used with WSARecv(), WSASend().

Note the different member order—Capy handles this platform difference internally.

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:

  1. Counts the number of buffers in the sequence

  2. Allocates space for platform buffer structures (on stack for small sequences)

  3. Copies buffer descriptors (pointer/size pairs) to platform structures

  4. Calls the OS function with the platform array

  5. 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

Zero-Copy Transmission

Data doesn’t need to be copied into a single contiguous buffer. The OS reads directly from each buffer in sequence.

Atomic Operations

The vectored write is atomic at the file offset level—other processes see either none or all of the data.

Registered Buffers

Advanced platforms offer registered buffer optimizations:

io_uring (Linux 5.1+)

Buffers can be pre-registered with the kernel, eliminating per-operation address translation:

// Registration (done once)
io_uring_register_buffers(ring, buffers, count);

// Use (fast path - no translation)
io_uring_prep_write_fixed(sqe, fd, buf, len, offset, buf_index);

IOCP (Windows)

Similar optimization with pre-registered memory regions for zero-copy I/O.

Capy’s Corosio library exposes these optimizations where available.

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);
};

Profile Before Optimizing

Buffer translation is rarely the bottleneck. Focus on:

  • Network latency

  • Disk I/O time

  • Data processing logic

Not buffer descriptor copying.

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.