Creating Character Devices
Character devices are essential components in the Linux kernel, providing an interface for the user space to interact with hardware devices. In this article, we'll walk through the process of creating a simple character device in the Linux kernel, focusing on the essentials you'll need to know to get started. Let's dive into the step-by-step guide!
Step 1: Understanding the Basics
Before we jump into the code, let’s clarify what a character device is. A character device is a type of device file that allows users to read or write data one character at a time. This includes devices such as keyboards, mice, and serial ports. When creating a character device, it is necessary to implement operations for opening, reading, writing, and closing the device.
Key Concepts:
- Device Files: Located in
/dev, these files represent the character devices. - Major and Minor Numbers: The major number identifies the driver associated with the device, while the minor number differentiates between different devices or instances of the same type.
Step 2: Setting Up Your Development Environment
To develop a character device, you’ll need a Linux environment with the necessary kernel headers and development tools. If you haven't done so, install them using the package manager of your choice. For example, in Ubuntu, you can run:
sudo apt-get install build-essential linux-headers-$(uname -r)
Step 3: Writing Your Character Device Code
Now, let’s get to the coding part. Create a new directory for your project and navigate into it. Here’s a step-by-step tutorial to help you create a simple character device.
3.1: Create Files
Create a file named simple_char_device.c. Here’s a basic skeleton of what you’ll be writing:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
// Define device name and major number
#define DEVICE_NAME "simplechar"
#define CLASS_NAME "simple"
// Function prototypes
static int simple_open(struct inode *inodep, struct file *filep);
static int simple_release(struct inode *inodep, struct file *filep);
static ssize_t simple_read(struct file *filep, char *buffer, size_t len, loff_t *offset);
static ssize_t simple_write(struct file *filep, const char *buffer, size_t len, loff_t *offset);
// Creating struct file_operations
static struct file_operations fops = {
.open = simple_open,
.read = simple_read,
.write = simple_write,
.release = simple_release,
};
// Variables for device
static int majorNumber;
static char message[256] = {0};
static int size_of_message;
static int __init simple_init(void) {
majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
if (majorNumber < 0) {
printk(KERN_ALERT "SimpleChar failed to register a major number\n");
return majorNumber;
}
printk(KERN_INFO "SimpleChar: registered correctly with major number %d\n", majorNumber);
return 0;
}
static void __exit simple_exit(void) {
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_INFO "SimpleChar: unregistered the device\n");
}
// Implement other functions...
module_init(simple_init);
module_exit(simple_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Linux character driver");
3.2: Implementing Functionality
Now let’s implement the functionalities of the file operations defined earlier.
3.2.1: Opening the Device
The open function is called when a user tries to interact with the device:
static int simple_open(struct inode *inodep, struct file *filep) {
printk(KERN_INFO "SimpleChar: Device has been opened\n");
return 0;
}
3.2.2: Reading from the Device
The read function passes data from the kernel to the user space:
static ssize_t simple_read(struct file *filep, char *buffer, size_t len, loff_t *offset) {
int error_count = 0;
error_count = copy_to_user(buffer, message, size_of_message);
if (error_count == 0) {
printk(KERN_INFO "SimpleChar: Sent %d characters to the user\n", size_of_message);
return (size_of_message = 0); // clear the position to the beginning
} else {
printk(KERN_INFO "SimpleChar: Failed to send %d characters to the user\n", error_count);
return -EFAULT; // Failed -- Return a bad address message (i.e., -14)
}
}
3.2.3: Writing to the Device
The write function allows the user to send data to the kernel:
static ssize_t simple_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) {
snprintf(message, sizeof(message), "%s(%zu letters)", buffer, len);
size_of_message = strlen(message);
printk(KERN_INFO "SimpleChar: Received %zu characters from the user\n", len);
return len;
}
3.2.4: Closing the Device
The release function handles cleanup when the device is closed:
static int simple_release(struct inode *inodep, struct file *filep) {
printk(KERN_INFO "SimpleChar: Device successfully closed\n");
return 0;
}
Step 4: Compiling Your Module
Next, we need a Makefile to compile the kernel module. Create a file named Makefile in the same directory:
MODULE := simple_char_device
obj-m += $(MODULE).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
Now compile the module by running:
make
Step 5: Loading and Unloading the Module
Once the module is compiled, it’s time to load the module into the kernel. You can do this using the insmod command:
sudo insmod simple_char_device.ko
Check for messages in the kernel log:
dmesg
Create a Device Node
Next, create a device node in /dev so that userspace applications can interact with it:
sudo mknod /dev/simplechar c <major number> 0
Replace <major number> with the number shown in the dmesg output.
Step 6: Testing Your Character Device
To test the character device, you can use echo and cat commands.
To write to the device:
echo "Hello, Kernel!" > /dev/simplechar
To read from the device:
cat /dev/simplechar
Step 7: Unload the Module
When you're done testing, you can unload your module with:
sudo rmmod simple_char_device
Check dmesg for the unload message.
Conclusion
Congratulations! You have successfully created a simple character device in the Linux kernel. This guide covered the essential steps from initialization to testing your character device. While this was a straightforward example, character devices can serve a wide range of more complex applications.
Feel free to expand your device functionality by adding more features, experimenting with different operations, or even building upon this foundation to interface with real hardware. Happy coding!