Linux kernel printk new implementation

For the tool to decode the buffer snapshot

Posted by half cup coffee on June 5, 2024

Reasons and detail of new implementation

The Linux kernel’s fundamental printk() function new implementation is coming with Linux version 5.10.
It is a fully lock-less ring-buffer implementation for printk. This new implementation allows for storing and reading messages without the possibility of deadlocks or relying on temporary per-CPU buffers.

Here is the link describe the particular issues of the old printk and list of changes from the new implementation.

See the detail for decoding printk buffer snapshot

Printk buffer

The buffer of printk is defined as static array (__log_buf) in the printk.c.
This part is no change from the new implementation.

/* record buffer */
#define LOG_ALIGN __alignof__(struct printk_log)
#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
#define LOG_BUF_LEN_MAX (u32)(1 << 31)
static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
static char *log_buf = __log_buf;

The new implementation defines new organization for the buffer:

"The printk log buffer consists of a sequenced collection of records, each
containing variable length message text. Every record also contains its
own meta-data (@info).

Every record meta-data carries the timestamp in microseconds, as well as
the standard userspace syslog level and syslog facility. The usual kernel
messages use LOG_KERN; userspace-injected messages always carry a matching
syslog facility, by default LOG_USER. The origin of every message can be
reliably determined that way."

From printk_ringbuffer.h:

printk_ringbuffer structure is defined like this. We can see there are 2 ring buffers which one is for description and another one for data buffer.

struct printk_ringbuffer {
	struct prb_desc_ring	desc_ring;
	struct prb_data_ring	text_data_ring;
	atomic_long_t		fail;
};

Ring buffer for data structure is easy to understand:

/* A ringbuffer of "ID + data" elements. */
struct prb_data_ring {
	unsigned int	size_bits;
	char		*data;
	atomic_long_t	head_lpos;
	atomic_long_t	tail_lpos;
};

Descriptior ring buffer structure is defined as below. It is meta-data for the data buffer.

struct prb_desc_ring {
	unsigned int		count_bits;
	struct prb_desc		*descs;
	struct printk_info	*infos;
	atomic_long_t		head_id;
	atomic_long_t		tail_id;
	atomic_long_t		last_finalized_id;
};

For adding or reading one printk record. A structure is defined also to be used:

struct printk_record {
	struct printk_info	*info;
	char			*text_buf;
	unsigned int		text_buf_size;
};

So if we get a buffer snapshot from Linux. The use case is for current Linux virtual machine. We can make a printk snapshot to check the LVM printk logs in case the system can’t bootup at early stage.

Based on the understanding of the new implementation, below function can be used for decoding the buffer snapshot:

decode_printk_snapshot() 
{
    //reference : static int devkmsg_open(struct inode *inode, struct file *file)
	prb_rec_init_rd(&user->record, &user->info,&user->text_buf[0], sizeof(user->text_buf));

	atomic64_set(&user->seq, prb_first_valid_seq(prb)); //prb is the printk buffer
    
    for(;;) 
    {
    //reference: static ssize_t devkmsg_read()
    if (prb_read_valid(prb,atomic64_read(&user->seq), r) == false)
        break;
        len = info_print_ext_header(user->buf, sizeof(user->buf), r->info);
        len += msg_print_ext_body(user->buf + len, sizeof(user->buf) - len,
                      &r->text_buf[0], r->info->text_len,
                      &r->info->dev_info);
        //user->buf is the buffer which store one printk record
        
        atomic64_set(&user->seq, r->info->seq + 1);
    }
}