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_task<std::size_t> write_some(Buffers 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
co_await write(stream, buf);

// Avoid: many tiny buffers
std::array<const_buffer, 100> tiny_bufs;
co_await 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:

// Fragments deliberately leave results and bindings unused; the pages
// explain the values in prose instead.
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-value"
#pragma GCC diagnostic ignored "-Wunused-result"
#pragma GCC diagnostic ignored "-Wunused-function"
// gcc 15 with sanitizers misattributes coroutine frame delete paths
#pragma GCC diagnostic ignored "-Wmismatched-new-delete"
#endif
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wunused-lambda-capture"
#pragma clang diagnostic ignored "-Wunused-private-field"
#endif
#if defined(_MSC_VER)
#pragma warning(disable: 4834) // discarding [[nodiscard]] return value
#pragma warning(disable: 4189) // local variable initialized but not referenced
#pragma warning(disable: 4100) // unreferenced formal parameter
#pragma warning(disable: 4101) // unreferenced local variable
#pragma warning(disable: 4456) // declaration hides previous local declaration
#pragma warning(disable: 4457) // declaration hides function parameter
#pragma warning(disable: 4458) // declaration hides class member
#pragma warning(disable: 4459) // declaration hides global declaration
#endif

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