PRINTK_RINGBUFFER OVERVIEW(9)
π printk_ringbuffer overview(9) π
Kernel API Manual Β· part 1 of 2 β¨
π NAME π
printk_ringbuffer overview β bestie, this is the full vibe check on the printk ringbuffer: the data structure, the implementation, how writers and readers use it, ABA issues, and memory barriers. No cap, every detail is staying. π
π DATA STRUCTURE π
The printk_ringbuffer is made up of 2 internal ringbuffers:
desc_ringβ a ring of descriptors and their meta data (such as sequence number, timestamp, loglevel, etc.) as well as internal state information about the record and logical positions specifying where in the other ringbuffer the text strings are located. πtext_data_ringβ a ring of data blocks. A data block consists of anunsigned long integer (ID)that maps to adesc_ringindex followed by the text string of the record. π¦
The internal state information of a descriptor is the key element to allow readers and writers to locklessly synchronize access to the data. Periodt. β¨
β¨ IMPLEMENTATION β¨
πΈ Descriptor Ring πΈ
The descriptor ring is an array of descriptors. A descriptor contains essential meta data to track the data of a printk record using blk_lpos structs pointing to associated text data blocks (see "Data Rings" below). Each descriptor is assigned an ID that maps directly to index values of the descriptor array and has a state. The ID and the state are bitwise combined into a single descriptor field named state_var, allowing ID and state to be synchronously and atomically updated. π«
Descriptors have four states:
reservedβ A writer is modifying the record. βοΈcommittedβ The record and all its data are written. A writer can reopen the descriptor (transitioning it back toreserved), but in thecommittedstate the data is consistent. βfinalizedβ The record and all its data are complete and available for reading. A writer cannot reopen the descriptor. πreusableβ The record exists, but its text and/or meta data may no longer be available. β»οΈ
Querying the state_var of a record requires providing the ID of the descriptor to query. This can yield a possible fifth (pseudo) state:
missβ The descriptor being queried has an unexpected ID. π«
The descriptor ring has a tail_id that contains the ID of the oldest descriptor and head_id that contains the ID of the newest descriptor. π
When a new descriptor should be created (and the ring is full), the tail descriptor is invalidated by first transitioning to the reusable state and then invalidating all tail data blocks up to and including the data blocks associated with the tail descriptor (for the text ring). Then tail_id is advanced, followed by advancing head_id. And finally the state_var of the new descriptor is initialized to the new ID and reserved state. π
The tail_id can only be advanced if the new tail_id would be in the committed or reusable queried state. This makes it possible that a valid sequence number of the tail is always available. π
π Descriptor Finalization π
When a writer calls the commit function prb_commit, record data is fully stored and is consistent within the ringbuffer. However, a writer can reopen that record, claiming exclusive access (as with prb_reserve), and modify that record. When finished, the writer must again commit the record. π€
In order for a record to be made available to readers (and also become recyclable for writers), it must be finalized. A finalized record cannot be reopened and can never become "unfinalized". Record finalization can occur in three different scenarios:
- A writer can simultaneously commit and finalize its record by calling
prb_final_commitinstead ofprb_commit. β‘ - When a new record is reserved and the previous record has been committed via
prb_commit, that previous record is automatically finalized. πͺ - When a record is committed via
prb_commitand a newer record already exists, the record being committed is automatically finalized. π
π¦ Data Ring π¦
The text data ring is a byte array composed of data blocks. Data blocks are referenced by blk_lpos structs that point to the logical position of the beginning of a data block and the beginning of the next adjacent data block. Logical positions are mapped directly to index values of the byte array ringbuffer. π§
Each data block consists of an ID followed by the writer data. The ID is the identifier of a descriptor that is associated with the data block. A given data block is considered valid if all of the following conditions are met:
- The descriptor associated with the data block is in the
committedorfinalizedqueried state. β - The
blk_lposstruct within the descriptor associated with the data block references back to the same data block. π - The data block is within the head/tail logical position range. π
If the writer data of a data block would extend beyond the end of the byte array, only the ID of the data block is stored at the logical position and the full data block (ID and writer data) is stored at the beginning of the byte array. The referencing blk_lpos will point to the ID before the wrap and the next data block will be at the logical position adjacent the full data block after the wrap. π―
Data rings have a tail_lpos that points to the beginning of the oldest data block and a head_lpos that points to the logical position of the next (not yet existing) data block. π
When a new data block should be created (and the ring is full), tail data blocks will first be invalidated by putting their associated descriptors into the reusable state and then pushing the tail_lpos forward beyond them. Then the head_lpos is pushed forward and is associated with a new descriptor. If a data block is not valid, the tail_lpos cannot be advanced beyond it. π«
β Info Array β
The general meta data of printk records are stored in printk_info structs, stored in an array with the same number of elements as the descriptor ring. Each info corresponds to the descriptor of the same index in the descriptor ring. Info validity is confirmed by evaluating the corresponding descriptor before and after loading the info. π
π USAGE π
Here are some simple examples demonstrating writers and readers. For the examples a global ringbuffer (test_rb) is available (which is not the actual ringbuffer used by printk):
DEFINE_PRINTKRB(test_rb, 15, 5);
This ringbuffer allows up to 32768 records (2 ^ 15) and has a size of 1 MiB (2 ^ (15 + 5)) for text data. π€―
Sample writer code:
const char *textstr = "message text";
struct prb_reserved_entry e;
struct printk_record r;
// specify how much to allocate
prb_rec_init_wr(r, strlen(textstr) + 1);
if (prb_reserve(e, test_rb, r)) {
snprintf(r.text_buf, r.text_buf_size, "s", textstr);
r.info->text_len = strlen(textstr);
r.info->ts_nsec = local_clock;
r.info->caller_id = printk_caller_id;
// commit and finalize the record
prb_final_commit(e);
}
Note that additional writer functions are available to extend a record after it has been committed but not yet finalized. This can be done as long as no new records have been reserved and the caller is the same. π―ββοΈ
Sample writer code (record extending):
// alternate rest of previous example
r.info->text_len = strlen(textstr);
r.info->ts_nsec = local_clock;
r.info->caller_id = printk_caller_id;
// commit the record (but do not finalize yet)
prb_commit(e);
}
...
// specify additional 5 bytes text space to extend
prb_rec_init_wr(r, 5);
// try to extend, but only if it does not exceed 32 bytes
if (prb_reserve_in_last(e, test_rb, r, printk_caller_id, 32)) {
snprintf(r.text_buf[r.info->text_len],
r.text_buf_size - r.info->text_len, "hello");
r.info->text_len += 5;
// commit and finalize the record
prb_final_commit(e);
}
Sample reader code:
struct printk_info info;
struct printk_record r;
char text_buf[32];
u64 seq;
prb_rec_init_rd(r, info, text_buf[0], sizeof(text_buf));
prb_for_each_record(0, test_rb, seq, r) {
if (info.seq != seq)
pr_warn("lost llu records0, info.seq - seq);
if (info.text_len > r.text_buf_size) {
pr_warn("record llu text truncated0, info.seq);
text_buf[r.text_buf_size - 1] = 0;
}
pr_info("llu: llu: s0, info.seq, info.ts_nsec,
text_buf[0]);
}
Note that additional less convenient reader functions are available to allow complex record access. π§
π ABA Issues π
To help avoid ABA issues, descriptors are referenced by IDs (array index values combined with tagged bits counting array wraps) and data blocks are referenced by logical positions (array index values combined with tagged bits counting array wraps). However, on 32-bit systems the number of tagged bits is relatively small such that an ABA incident is (at least theoretically) possible. For example, if 4 million maximally sized (1KiB) printk messages were to occur in NMI context on a 32-bit system, the interrupted context would not be able to recognize that the 32-bit integer completely wrapped and thus represents a different data block than the one the interrupted context expects. π
To help combat this possibility, additional state checking is performed (such as using cmpxchg even though set would suffice). These extra checks are commented as such and will hopefully catch any ABA issue that a 32-bit system might experience. π
π§ Memory Barriers π§
Multiple memory barriers are used. To simplify proving correctness and generating litmus tests, lines of code related to memory barriers (loads, stores, and the associated memory barriers) are labeled:
LMM(function:letter)
Comments reference the labels using only the "function:letter" part. π·οΈ
The memory barrier pairs and their ordering are:
desc_reserve:D / desc_reserve:Bβ push descriptor tail (id), then push descriptor head (id) π«Άdesc_reserve:D / data_push_tail:Bβ push data tail (lpos), then set new descriptor reserved (state) πdesc_reserve:D / desc_push_tail:Cβ push descriptor tail (id), then set new descriptor reserved (state) πΈdesc_reserve:D / prb_first_seq:Cβ push descriptor tail (id), then set new descriptor reserved (state) π¦desc_reserve:F / desc_read:Dβ set new descriptor id and reserved (state), then allow writer changes β¨data_alloc:A(ordata_realloc:A) /desc_read:Dβ set old descriptor reusable (state), then modify new data block area π«data_alloc:A(ordata_realloc:A) /data_push_tail:Bβ push data tail (lpos), then modify new data block area π₯_prb_commit:B/desc_read:Bβ store the writer changes first, then set the new descriptor committed (state) β¨desc_reopen_last:A/_prb_commit:Bβ set the descriptor reserved (state) first, then read the descriptor data π_prb_commit:B/desc_reserve:Dβ set the new descriptor committed (state) first, then check the descriptor head (id) πdata_push_tail:D/data_push_tail:Aβ set the descriptor reusable (state) first, then push the data tail (lpos) π¦desc_push_tail:B/desc_reserve:Dβ set the descriptor reusable (state) first, then push the descriptor tail (id) πΈdesc_update_last_finalized:A/desc_last_finalized_seq:Aβ store the finalized record first, then set the new highest finalized sequence number β
lowkey the whole point of this list is the order, bestie. Store this, then set that. Flip them around and the vibe check fails catastrophically π so please don't.
π SEE ALSO
Kernel file ./kernel/printk/printk_ringbuffer.c, plus all these iconic little functions that live there β go read them, they're literally right there π«Ά
prb_reserve_in_last(9)prb_reserve(9)prb_commit(9)prb_final_commit(9)prb_next_reserve_seq(9)prb_read_valid(9)prb_read_valid_info(9)prb_first_valid_seq(9)prb_next_seq(9)prb_init(9)prb_record_text_space(9)
okay that's the whole page, you made it π ngl this one's a lot but you ate it and left no crumbs. source is the kernel dir <code>./kernel/printk</code>, the version stamp says October 2026, and this absolute icon is filed under <code>printk_ringbuffer overview(9)</code>. go forth and ringbuffer responsibly, bestie π
π¦ where this page comes from
- nixpkgs unstable: linux-manual 7.3.1
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms