Creating an interactive char driver is surprisingly simple, but there are a few traps along the way.
Exposing it to the File System
This is by far the hardest part to understand, but honestly a full understanding isn't really necessary. The new intro_init function looks like this:
#define DEVICE_NAME "intro"
#define CLASS_NAME "intro"
// setting up the device
int major;
static struct class* my_class = NULL;
static struct device* my_device = NULL;
static int __init intro_init(void) {
major = register_chrdev(0, DEVICE_NAME, &fops); // explained later
if ( major < 0 )
printk(KERN_ALERT "[Intro] Error assigning Major Number!");
// Register device class
my_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(my_class)) {
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ALERT "[Intro] Failed to register device class\n");
}
// Register the device driver
my_device = device_create(my_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
if (IS_ERR(my_device)) {
class_destroy(my_class);
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ALERT "[Intro] Failed to create the device\n");
}
return 0;
}
A major number is essentially the unique identifier to the kernel module. You can specify it using the first parameter of register_chrdev, but if you pass 0 it is automatically assigned an unused major number.
We then have to register the class and the device. In complete honesty, I don't quite understand what they do, but this code exposes the module to /dev/intro.
Note that on an error it calls class_destroy and unregister_chrdev:
Cleaning it Up
These additional classes and devices have to be cleaned up in the intro_exit function, and we mark the major number as available:
static void __exit intro_exit(void) {
device_destroy(my_class, MKDEV(major, 0)); // remove the device
class_unregister(my_class); // unregister the device class
class_destroy(my_class); // remove the device class
unregister_chrdev(major, DEVICE_NAME); // unregister the major number
printk(KERN_INFO "[Intro] Closing!\n");
}
Controlling I/O
In intro_init, the first line may have been confusing:
major = register_chrdev(0, DEVICE_NAME, &fops);
The third parameter fops is where all the magic happens, allowing us to create handlers for operations such as read and write. A really simple one would look something like: