1. Overview

It’s possible to use several logging mechanisms in the user space. For example, we can use the systemd-cat command to send messages to the journal.

What if we want to send messages to the kernel buffer in the kernel space? In this tutorial, we’ll discuss how to send messages to the kernel buffer.

2. Brief Information About the Kernel Buffer

The kernel buffer is a data structure used for keeping the log messages of the kernel and the kernel modules. It’s a ring buffer with a fixed size. Once it’s full, new messages overwrite the oldest messages.

During boot, the kernel saves the messages into the kernel buffer. When other logging daemons such as journald and rsyslogd in the user space start, they read all the current kernel logs and follow the kernel buffer for new logs.

The kernel provides /dev/kmsg and /proc/kmsg as the interface for user space daemons.

We can use the dmesg command, which uses /dev/kmsg, to display the contents of the kernel buffer.

3. Writing to /dev/kmsg

One method is to write directly to /dev/kmsg.

Let’s start by following the messages written to the kernel buffer using dmesg:

$ dmesg -WT

We started to follow the messages written to the kernel buffer. This is similar to using the -f option of the tail command.

The -W option of dmesg is for waiting and printing only new messages. Otherwise, dmesg just prints all the messages in the kernel buffer and exits. If the -W option isn’t available, we can use the -w option instead. It prints all the messages in the kernel buffer and waits for new ones.

The -T option of dmesg, on the other hand, is for printing human-readable timestamps.

Now, let’s write a message to /dev/kmsg:

$ echo "Hello kernel buffer" | sudo tee /dev/kmsg
Hello kernel buffer

We used the tee command to send the message, Hello kernel buffer, both to the console and to /dev/kmsg. As it’s apparent from the output, the message was printed in the console.

Let’s check whether we could append the message to the kernel buffer:

$ dmesg -WT
[Mon Jan 30 12:51:13 2023] Hello kernel buffer

As the output of the dmesg -WT command shows, we were successful in appending the message to the kernel buffer.

However, we must have root privileges to append messages to /dev/kmsg. We used the sudo command together with tee, sudo tee /dev/kmsg. Otherwise, we’d get an error:

$ echo "Hello once more kernel buffer" | tee /dev/kmsg
tee: /dev/kmsg: Permission denied
Hello once more kernel buffer

No new message was appended to the kernel buffer:

$ dmesg -WT
[Mon Jan 30 12:51:13 2023] Hello kernel buffer

4. Using printk

Another alternative for writing messages to the kernel buffer is to create a module that uses the printk() function. A module is compiled code that we can load into the kernel at run-time. Modules allow us to extend the functionality of the kernel without rebooting the system. Similarly, they can be unloaded from the kernel at run-time.

printk() is only available in the Linux kernel. It isn’t a part of the standard C library. However, its behavior is similar to the printf() function in the standard C library.

4.1. Code Example

We’ll use the C program, hello_dmesg.c, for writing messages to the kernel buffer:

#include <linux/module.h>
#include <linux/kernel.h>

MODULE_LICENSE("GPL");

int hello_dmesg_init(void) {
    printk(KERN_INFO "Hello dmesg\n");
    
    return 0;
}

void hello_dmesg_exit(void) {
    printk(KERN_INFO "Good bye dmesg\n");
}

module_init(hello_dmesg_init);
module_exit(hello_dmesg_exit);

Let’s discuss the source code briefly.

Every kernel module must have at least two functions, one for initialization and one for clean-up. The function for initialization is called when we load the module. On the other hand, the function for clean-up is called when we unload the module. There are no restrictions on the names of these two functions.

The first function we define is hello_dmesg_init(). This function calls the printk(KERN_INFO “Hello dmesg\n”) statement to print the message, Hello dmesg, to the kernel buffer, and then exits. The KERN_INFO part passed to printk() is the log level or priority of the message. There are several predefined priorities, and KERN_INFO is one of them. These log levels specify the importance of the message.

Then, we define the function, hello_dmesg_exit(). This function calls the statement, printk(KERN_INFO “Good bye dmesg\n”) statement to print the message, Good bye dmesg, to the kernel buffer.

After we define the functions, we call the module_init(hello_dmesg_init) and module_exit(hello_dmesg_exit) statements to tell the kernel which functions are the initialization and clean-up functions. module_init(hello_dmesg_init) specifies that the kernel must call hello_dmesg_init() when we load the module. Similarly, module_exit(hello_dmesg_exit) specifies that the kernel must call hello_dmesg_exit() when we unload the module.

4.2. Compilation of the Module

Now, it’s time to compile the source code. We’ll use the following Makefile to build the module:

obj-m += hello_dmesg.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

The special obj-m variable specifies to build hello_dmesg.o as a module.

The all rule in Makefile builds the module. The -C option of make specifies to make the build in the /lib/modules/$(shell uname -r)/build directory, where the kernel build system with its own Makefiles resides. Running the command uname -r gives 5.14.0-22.el9.x86_64 in our system, which is the kernel release. Therefore, we use the kernel Makefile in the /lib/modules/5.14.0-22.el9.x86_64/build directory to build our module.

We use the M variable to let the kernel build system know where the Makefile for our module is. We specify the current working directory using M=$(PWD). The modules directive at the end specifies to build the module.

The clean rule is for cleaning the built module. Its only difference from the all rule is the clean directive at the end.

Now, we’ll build the module using make:

$ ls
hello_dmesg.c  Makefile
$ make >& /dev/null
$ ls
hello_dmesg.c  hello_dmesg.ko  hello_dmesg.mod  hello_dmesg.mod.c  hello_dmesg.mod.o  hello_dmesg.o  Makefile  modules.order  Module.symvers

Firstly, we check the files in the current directory. There are just two files, hello_dmesg.c and Makefile, as expected. Then, we build our module using make >& /dev/null. We direct the output of make to /dev/null to suppress the details of the build. Finally, we check the files in the current directory once more. As we see, the build process generates a bunch of files. We’ll use the module file hello_dmesg.ko.

4.3. Running the Example

We must load our module to check whether any messages are appended to the kernel buffer. We’ll use the insmod command to load the module:

$ insmod hello_dmesg.ko

Loading a module using insmod requires root privileges.

Now, let’s check whether we could append a message to the kernel buffer:

$ dmesg -WT 
[Mon Jan 30 12:51:13 2023] Hello kernel buffer
[Mon Jan 30 13:01:48 2023] Hello dmesg

We were successful in adding the message, Hello dmesg, to the kernel buffer.

Now, let’s unload the module using the rmmod command:

$ rmmod hello_dmesg

Unloading a module using rmmod also requires root privileges.

Let’s check the kernel buffer once more:

$ dmesg -WT 
[Mon Jan 30 12:51:13 2023] Hello kernel buffer
[Mon Jan 30 13:01:48 2023] Hello dmesg
[Mon Jan 30 13:02:55 2023] Good bye dmesg

As we see, printk() added the message, Good bye dmesg, to the kernel buffer as expected.

Therefore, we could add two messages to the kernel buffer, while loading and unloading the module hello_dmesg, using the printk() function.

5. Conclusion

In this article, we discussed how to send messages to the kernel buffer.

Firstly, we learned about the kernel buffer. /dev/kmsg is one of the interfaces the kernel provides for accessing the kernel buffer from the user space. We saw that we can use dmesg for reading the log messages in the kernel buffer.

Then, we discussed writing messages directly to /dev/kmsg. We sent the output of the echo command to /dev/kmsg with root privileges.

Finally, we learned that we can write messages to the kernel buffer by using the printk() function in the Linux kernel interface. We wrote a simple module using printk(). We confirmed that the messages in the printk() functions we used were added to the kernel buffer while loading and unloading the module. This method also needed root privileges.