Writing Linux Loadable Kernel Module
Table of Content
- Acknowledgement
- Experiment and Programming Environment
- Writing a Simple Character Device Driver
- Testing the Kernel Module
- Further Studying
Acknowledgement
This tutorial is an exerpt from the “The Linux Kernel Module Programming Guide” maintained by Bob Mottram.
Experiment and Programming Environment
Debian Linux System
We tested the code shown here in a Debian Linux system with kernerl version 4.19. To build and install a Debian Linux system, please refer to the bootstrap tutorial.
Linux Packages for Kernel Module Development
Become the root and run the following to install necessary packages,
apt-get install -y build-essential kmod linux-headers-$(uname -r)
Writing a Simple Character Device Driver
Device Driver Source Code (chardev.c
)
In Linux, we typically write a device driver as a loadable kernel module.
Below is the source code a simple character device driver. Create chardev.c
file and copy the following to the file.
/*
* chardev.c: Creates a read-only char device that says how many times
* you've read from the dev file
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/poll.h>
#include <linux/cdev.h>
/*
* Prototypes - this would normally go in a .h file
*/
int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
#define SUCCESS 0
#define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */
#define BUF_LEN 80 /* Max length of the message from the device */
/*
* Global variables are declared as static, so are global within the file.
*/
static int Major; /* Major number assigned to our device driver */
static int Device_Open = 0; /* Is device open?
* Used to prevent multiple access to device */
static char msg[BUF_LEN]; /* The msg the device will give when asked */
static char *msg_Ptr;
static struct class *cls;
static struct file_operations chardev_fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
/*
* This function is called when the module is loaded
*/
int init_module(void)
{
Major = register_chrdev(0, DEVICE_NAME, &chardev_fops);
if (Major < 0) {
pr_alert("Registering char device failed with %d\n", Major);
return Major;
}
pr_info("I was assigned major number %d.\n", Major);
cls = class_create(THIS_MODULE, DEVICE_NAME);
device_create(cls, NULL, MKDEV(Major, 0), NULL, DEVICE_NAME);
pr_info("Device created on /dev/%s\n", DEVICE_NAME);
return SUCCESS;
}
/*
* This function is called when the module is unloaded
*/
void cleanup_module(void)
{
device_destroy(cls, MKDEV(Major, 0));
class_destroy(cls);
/*
* Unregister the device
*/
unregister_chrdev(Major, DEVICE_NAME);
}
/*
* Methods
*/
/*
* Called when a process tries to open the device file, like
* "cat /dev/mycharfile"
*/
static int device_open(struct inode *inode, struct file *file)
{
static int counter = 0;
if (Device_Open)
return -EBUSY;
Device_Open++;
sprintf(msg, "I already told you %d times Hello world!\n", counter++);
msg_Ptr = msg;
try_module_get(THIS_MODULE);
return SUCCESS;
}
/*
* Called when a process closes the device file.
*/
static int device_release(struct inode *inode, struct file *file)
{
Device_Open--; /* We're now ready for our next caller */
/*
* Decrement the usage count, or else once you opened the file, you'll
* never get get rid of the module.
*/
module_put(THIS_MODULE);
return SUCCESS;
}
/*
* Called when a process, which already opened the dev file, attempts to
* read from it.
*/
static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */
char *buffer, /* buffer to fill with data */
size_t length, /* length of the buffer */
loff_t * offset)
{
/*
* Number of bytes actually written to the buffer
*/
int bytes_read = 0;
/*
* If we're at the end of the message,
* return 0 signifying end of file
*/
if (*msg_Ptr == 0)
return 0;
/*
* Actually put the data into the buffer
*/
while (length && *msg_Ptr) {
/*
* The buffer is in the user data segment, not the kernel
* segment so "*" assignment won't work. We have to use
* put_user which copies data from the kernel data segment to
* the user data segment.
*/
put_user(*(msg_Ptr++), buffer++);
length--;
bytes_read++;
}
/*
* Most read functions return the number of bytes put into the buffer
*/
return bytes_read;
}
/*
* Called when a process writes to dev file: echo "hi" > /dev/hello
*/
static ssize_t device_write(struct file *filp,
const char *buff,
size_t len,
loff_t * off)
{
pr_alert("Sorry, this operation isn't supported.\n");
return -EINVAL;
}
MODULE_LICENSE("GPL");
Makefile
To compile the device driver kernel module, we create a file called Makefile
.
obj-m += chardev.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
Note that the “spaces” before the lines led by make
must be tabs
.
Compiling the Device Driver
To compile the device driver kernel module, type
make
Testing the Kernel Module
Listing Loaded Kernel Modules
Before we load the kernel module, we run lsmod
and observe kernel modules
that have been loaded,
lsmod
Loading the Kernel Module
Next, we load the chardev
kernel module using insmod
. You must do this as root
insmod chardev.ko
Examining Whether the Kernle Module is Loaded
We then examine kernel modules that have been loaded again,
lsmod
The result can be lengthy and cumbersome to locate the line that contains
“chardev”. We can use grep
to make this task earier,
lsmod | grep chardev
The following is an example output,
$ lsmod | grep chardev
chardev 16384 0
In addition, we can also view system logs. In Debian Linux, we view relevant
system logs in the /var/log/syslog
file. You can only view this file as
root. Switch to root and then run or use sudo to run the following
tail -f /var/log/syslog
Note that tail
only show you the “tail” of the file. You can also use an
editor to view the /var/log/syslog
file. The following is an example of
the above command,
# tail -f /var/log/syslog
Feb 10 13:33:54 debian10r32 kernel: [ 4525.232319] I was assigned major number 246.
Feb 10 13:33:54 debian10r32 kernel: [ 4525.232836] Device created on /dev/chardev
Unloading the Kernel Module
Sometimes you want to remove (or unload) the kernel module, e.g., you revise the kernel module source code, recompiled it, and wants to reload it. To remove the kernel module, run the following as root (or using sudo),
rmmod chardev
Testing the Device Driver
We write a simple C program called chardevclient.c
to access the “character
device” via the device driver,
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int fd, c, rtn;
fd = open("/dev/chardev", O_RDWR);
if (fd == -1) {
perror("open /dev/chardev");
exit(EXIT_FAILURE);
}
printf("Reading from /dev/chardev: \n");
while ((rtn = read(fd, &c, 1)) > 0) {
printf("%c", c);
}
if (rtn == -1) {
perror("reading /dev/chardev");
} else {
printf("\n");
}
printf("Writing to /dev/chardev: \n");
c = 'h';
while ((rtn = write(fd, &c, 1)) > 0) {
printf("wrote %c\n", c);
}
if (rtn == -1) {
perror("writing /dev/chardev");
}
exit(EXIT_SUCCESS);
}
Add the following lines to the Makefile
,
chardevclient:
cc -Wall chardevclient.c -o chardevclient
To compile, run
make
To run the chardevclient
program, switch to root and type (or run it using
sudo)
./chardevclient
What do you observe? Can you explain what you observe?
Further Studying
It is worth studying the “The Linux Kernel Module Programming Guide” completely.
To read the guide, first figure out the Linux kernel version by using the command below,
uname -r
Next, go to the guide and find the directory that best matches your kernel
version. For instance, I observe the uname -r
command outputs the following,
4.19.0-6-686
The directory in the guide that best matches the kernel version number is
4.17.2
under
older_versions