2.3 Attaching and Mapping

2.3.1 Attaching #

A consumer attaches to one per-CPU ring buffer at a time by calling kmes_attach with a logical CPU index and a pointer to a u64 that receives the buffer's capacity. The call returns a file descriptor.

The caller MUST hold SeSecurityPrivilege, enabled; the call fails with EPERM otherwise.

The CPU index uses the same numbering as the cpu_id field in the ring buffer metadata and in event headers. Indexes run from 0 to one below the slot count, and the set of buffers is fixed when KMES initialises and does not change while the system runs.

The slot count is not the number of buffers. Slots are indexed by logical CPU id, so a slot within the range holds no buffer when that CPU is not possible, and the two quantities differ on any system whose possible-CPU mask is sparse. An index at or beyond the slot count fails with EINVAL, and so does an index inside it whose slot holds no buffer; the two are not distinguishable from the return value.

A consumer discovers the slot count by calling kmes_attach with the CPU index KMES_ATTACH_QUERY_SLOTS. The call writes the slot count through the capacity pointer, returns 0, and opens no descriptor. It is gated on SeSecurityPrivilege exactly as an attach is.

A consumer MUST enumerate by walking every index from 0 to one below the slot count, and MUST treat EINVAL as "this slot holds no buffer" and continue. A consumer MUST NOT treat the first EINVAL as the end of the set: doing so silently abandons every buffer above the first hole, whose events then accumulate and are overwritten with no consumer able to reach them.

A consumer SHOULD attach to every buffer in the set: a buffer with no consumer still receives events, and those events are lost when it wraps.

A consumer MAY call kmes_attach more than once for the same CPU and receives a distinct file descriptor each time. All descriptors for one CPU refer to the same buffer, and therefore to the same producer metadata, consumer metadata, and data region. Multiple consumers MAY attach to one buffer concurrently. Each maintains its own read position, in its own memory; the kernel does not track consumer read positions, does not know how many consumers exist, and does not know how far behind any of them is. The consumer metadata page is shared per buffer and is not a per-consumer read-position store.

The descriptor supports exactly two operations: mmap() and close(). Closing it invalidates the mapping.

2.3.2 Mapping #

The consumer maps the whole region in a single call:

mmap(NULL, 8192 + 2 * capacity, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)

The mapping request MUST use MAP_SHARED, MUST pass an offset of zero, and MUST pass a length of exactly 8192 + 2 * capacity, where capacity is the value kmes_attach returned. Any other combination fails with EINVAL. The consumer does not map the regions separately.

The mapped region has three parts:

OffsetSizeRegionConsumer access
04096Producer metadata pageRead-only
40964096Consumer metadata pageRead-write
81922 × capacityData regionRead-only

Per-page permissions are enforced by the kernel regardless of the PROT flags requested: the producer metadata page and the data region are mapped read-only whatever the consumer asks for, and no privilege raises that. The consumer metadata page is writable by ordinary stores.

capacity is always a power of two, so a position's offset within the data region is position & (capacity - 1).

2.3.3 The double mapping #

The data region occupies 2 × capacity bytes of address space backed by capacity bytes of memory: the same pages appear twice, consecutively. An event that crosses the end of the buffer is therefore readable as one contiguous byte sequence starting at its wrapped offset, and a consumer MUST NOT implement wrap handling of its own. Reading an event at offset position & (capacity - 1) is always correct, even when the event extends past capacity.

A consumer MUST NOT read past event_size bytes from the start of an event. The data region is not scrubbed when events are overwritten, so the bytes beyond an event are unrelated remnants of older events.

2.3.4 Producer metadata page #

The producer metadata page is written by KMES and read by consumers. Fields are separated onto 64-byte cache lines by update frequency, so that the position fields KMES writes on every event do not invalidate the line holding the fields it never writes.

2.3.4.1 Bytes 0–63: identification #

OffsetSizeTypeFieldDescription
08[u8; 8]magic4B 4D 45 53 52 49 4E 47, KMESRING in ASCII. Compared byte by byte, not as an integer.
84u32versionRing buffer format version. This version is 1.
122u16cpu_idThe CPU this buffer belongs to.
142u16reserved0Reserved, zero.
168u64capacityData region capacity in bytes. A power of two.
248u64data_offsetOffset from the start of the mapping to the data region. 8192.
328u64generationBuffer generation. Starts at 1 for the first buffer on each CPU and increases by one each time the buffer is replaced.
4024--reserved1Reserved, zero.

A consumer MUST verify magic and version before trusting any other field in the mapping.

A consumer MAY cache magic, version, cpu_id, capacity, and data_offset for the lifetime of the mapping; these do not change once the buffer exists. A consumer MUST NOT cache generation: it shares this cache line but is written when the buffer is superseded, and re-reading it is how a consumer learns that it must re-attach.

2.3.4.2 Bytes 64–127: positions #

OffsetSizeTypeFieldDescription
648u64write_posMonotonically increasing byte offset at which the next event will be written. Never wraps.
728u64tail_posByte offset of the oldest surviving event. Advanced by KMES as events are overwritten.
8048--reserved2Reserved, zero.

Both are absolute byte offsets that increase without bound; the corresponding data region offset is the value masked with capacity - 1. A u64 byte offset does not overflow in any practical deployment — at a sustained gigabyte per second it would take over five hundred years — and consumers MUST NOT implement wrap handling for these counters.

2.3.4.3 Bytes 128–191: notification #

OffsetSizeTypeFieldDescription
1284u32futex_counterIncremented by KMES when it wakes sleeping consumers.
13260--reserved3Reserved, zero.

The counter is 32-bit because the Linux futex operates on 32-bit integers, and it is incremented only when need_wake is set.

2.3.5 Consumer metadata page #

OffsetSizeTypeFieldDescription
40961u8need_wakeSet by a consumer that is about to sleep. Read by KMES after writing an event; any nonzero value counts as set.
40974095--reserved4Reserved.

This page is shared by every consumer attached to the buffer. A consumer MUST NOT store per-consumer state on it, and in particular MUST NOT store its read position there.

Consumers MUST NOT write to any offset in this page other than need_wake. Reserved bytes are reserved for future extension of this contract.

Edit this page