« Previous Lecture | Next Lecture »
If you are searching for a free linux device drivers course that actually explains how a linux misc character device driver works on a modern kernel, this lecture is your starting point. Instead of the old-style character driver setup that older books teach — manually allocating a major number, creating a class, and calling device_create() — we will use the kernel’s built-in misc framework, which is the approach most real drivers use today because it is shorter, safer, and easier to maintain on kernel 6.12 and later.
Prerequisites for This Linux Device Driver Tutorial
This lecture assumes you have already completed the basic Loadable Kernel Module (LKM) lectures in this free linux kernel development course — you should be comfortable with module_init(), module_exit(), and building an out-of-tree module with Kbuild. You should also have a Linux VM (Ubuntu 22.04/24.04 or a recent Fedora release) running a kernel of version 6.1 or newer, along with the matching kernel headers package installed.
What Is a Linux Character Device Driver?
A device driver is the software layer that sits between the operating system and a physical or virtual piece of hardware, translating generic system calls into the specific operations that a device understands. On Linux, drivers can live inside the kernel image itself, but the overwhelming majority are built as loadable modules so they can be inserted and removed without rebooting the machine.
User space programs never talk to a driver directly. Instead, Linux exposes the driver through a special file called a device node, usually located under /dev. When an application opens that node, the Virtual File System (VFS) routes the request into the correct driver based on the type and number stored in the node’s inode.
| User App open(“/dev/mychip”) |
→ | VFS Layer looks up inode |
→ | Char Device Layer major/minor lookup |
→ | Your Driver’s file_operations |
Character, Block, and Network Devices Compared
Linux groups devices into three broad categories. Understanding the difference matters before you pick the misc framework for your own driver.
| Device Type | Data Access Pattern | Typical Examples |
|---|---|---|
| Character | Sequential stream of bytes, no buffering by default | Sensors, serial ports, GPIO chips, RNG devices |
| Block | Random access in fixed-size blocks, cached by the page cache | SSDs, HDDs, SD cards |
| Network | Packet based, addressed through the networking stack, not /dev |
Ethernet and Wi-Fi adapters |
Device Nodes and Major/Minor Numbers Explained
Every device node carries two identifying numbers in its inode: a major number, which tells the kernel which driver owns the node, and a minor number, which the driver itself uses to distinguish between multiple instances of the same type of device. On a modern desktop or server distribution, udev creates and removes these nodes automatically as drivers register and unregister themselves, so you almost never need to run mknod by hand anymore.
Why the misc Framework Is the Easiest Way to Write a Linux Character Device Driver
Older tutorials teach you to call register_chrdev(), manually pick a free major number, create a struct class, and then call device_create() just to get a working /dev entry. The kernel’s misc framework (include/linux/miscdevice.h) removes almost all of that boilerplate. All misc drivers share a single major number (10), and the framework automatically creates the device node for you through udev once you call misc_register(). This is why the misc framework is the recommended starting point in this free linux device drivers course for anyone writing a simple character driver in kernel 6.12 and newer.
| Old-Style Char Driver | Modern misc Driver |
|---|---|
Call alloc_chrdev_region() to reserve a major number |
Major number 10 is shared automatically |
Create a struct class with class_create() |
Not required |
Call device_create() to make the /dev node appear |
Node appears automatically after misc_register() |
Understanding the file_operations Structure
Every driver method a user space application can trigger — open(), read(), write(), release(), unlocked_ioctl(), and others — is wired up through a single structure called struct file_operations, defined in include/linux/fs.h. You only need to fill in the callbacks your driver actually supports; the rest are left as NULL and the kernel returns the appropriate error automatically if user space tries to use them.
Writing Your First Linux Misc Character Device Driver
Step 1: Headers and the Data Buffer
We’ll build a tiny driver that stores a short message in kernel memory and lets user space read or overwrite it.
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#define EP_MSG_MAX 128
static char ep_msg[EP_MSG_MAX] = "hello from EmbeddedPathashala\n";
static size_t ep_msg_len = 30;
Step 2: Implement open, read, and write
static int ep_open(struct inode *inode, struct file *filp)
{
pr_info("ep_misc: device opened\n");
return 0;
}
static ssize_t ep_read(struct file *filp, char __user *buf,
size_t count, loff_t *offp)
{
if (*offp >= ep_msg_len)
return 0;
if (count > ep_msg_len - *offp)
count = ep_msg_len - *offp;
if (copy_to_user(buf, ep_msg + *offp, count))
return -EFAULT;
*offp += count;
return count;
}
static ssize_t ep_write(struct file *filp, const char __user *buf,
size_t count, loff_t *offp)
{
if (count > EP_MSG_MAX - 1)
count = EP_MSG_MAX - 1;
if (copy_from_user(ep_msg, buf, count))
return -EFAULT;
ep_msg_len = count;
return count;
}
Step 3: Wire Up file_operations and Register with misc_register()
static const struct file_operations ep_fops = {
.owner = THIS_MODULE,
.open = ep_open,
.read = ep_read,
.write = ep_write,
};
static struct miscdevice ep_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ep_misc",
.fops = &ep_fops,
.mode = 0666,
};
Step 4: Module init and exit
static int __init ep_init(void)
{
int ret = misc_register(&ep_miscdev);
if (ret) {
pr_err("ep_misc: registration failed\n");
return ret;
}
pr_info("ep_misc: registered at /dev/ep_misc\n");
return 0;
}
static void __exit ep_exit(void)
{
misc_deregister(&ep_miscdev);
pr_info("ep_misc: removed\n");
}
module_init(ep_init);
module_exit(ep_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("A simple misc character device driver example");
Building and Testing the Driver
Use a minimal Kbuild file to compile the module against your running kernel’s headers:
obj-m += ep_misc.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
Then load it and try it out:
$ make
$ sudo insmod ep_misc.ko
$ ls -l /dev/ep_misc
$ cat /dev/ep_misc
$ echo "new message" | sudo tee /dev/ep_misc
$ cat /dev/ep_misc
$ sudo rmmod ep_misc
$ dmesg | tail
Notice that we never ran mknod — the /dev/ep_misc node appeared on its own the moment misc_register() succeeded, because the misc framework asks udev to create it for us.
Common Mistakes When Writing a Linux misc Character Device Driver
- Forgetting to check the return value of misc_register(). If registration fails silently, your driver loads but no device node ever appears.
- Not validating the count argument in write(). Copying more bytes than your buffer can hold corrupts kernel memory.
- Using memcpy() instead of copy_to_user()/copy_from_user(). User space pointers must never be dereferenced directly from kernel code.
- Forgetting misc_deregister() in the exit path. This leaves a dangling device node after the module is removed.
- Hardcoding a minor number. Always prefer
MISC_DYNAMIC_MINORunless you have a specific reason to reserve a fixed one.
Best Practices for Character Device Drivers
- Keep the
file_operationstable minimal — only implement the callbacks your device genuinely needs. - Log meaningful context with
pr_info()/pr_err()so issues are traceable throughdmesg. - Bound every copy into or out of a fixed-size buffer before touching user memory.
- Prefer the misc framework for simple, single-instance devices; move to a full
cdev-based driver only when you need custom major numbers or many minor numbers.
Security Considerations
Because /dev/ep_misc in our example is created with mode 0666, any local user can read and write it — fine for a learning exercise, dangerous for a driver that talks to real hardware. In production drivers, restrict the mode, validate every length argument from user space before copying, and never trust that count or offset values arriving from an ioctl() or write() call are sane.
Real-World Use Cases of misc Drivers
The misc framework isn’t just a teaching tool — it backs many drivers you already use, including hardware random number generators, watchdog timer front-ends, and various vendor-specific control interfaces that only need a single, simple /dev entry rather than a full class of devices.
Performance Considerations
For small, infrequent transfers, the overhead of copy_to_user()/copy_from_user() is negligible. If your driver later needs to move large buffers frequently, look into mmap()-based zero-copy access or scatter-gather DMA, both of which are covered later in this course.
Summary and Key Takeaways
- A character device driver exposes hardware as a stream of bytes through a
/devnode. - The VFS routes
open(),read(), andwrite()calls into your driver using the major/minor numbers stored in the inode. - The misc framework, driven by
misc_register(), is the fastest and safest way to write a simple character driver on modern kernels. struct file_operationsis the single table that connects user space system calls to your driver’s functions.
Conclusion
You’ve now written and tested a complete, modern Linux misc character device driver from scratch — without touching a single line of major-number bookkeeping. This pattern scales to real hardware drivers too: once you’re comfortable with file_operations and misc_register(), the next lectures in this free linux kernel development course will build on it with I/O memory access, interrupt handling, and kernel timers.
Frequently Asked Questions
Q1. What is a misc character device driver in Linux?
It’s a character driver registered through the kernel’s misc framework, which shares a common major number and automatically creates its /dev node, removing the need for manual major-number allocation.
Q2. Do I still need to run mknod for a misc driver?
No. Once misc_register() succeeds, udev creates the device node for you automatically.
Q3. What is the difference between a character and a block device?
A character device is accessed as a sequential byte stream, while a block device is accessed in fixed-size blocks and benefits from page-cache buffering.
Q4. Why use copy_to_user() instead of a normal pointer copy?
User space and kernel space live in different memory contexts; copy_to_user()/copy_from_user() safely validate and bridge that boundary.
Q5. Can a misc driver handle ioctl() calls?
Yes, simply implement .unlocked_ioctl in your file_operations table just like any other character driver.
Q6. Is the misc framework suitable for drivers with multiple device instances?
It works best for single-instance devices. For drivers needing many minor numbers under one major, a full cdev-based approach is more appropriate.
Q7. Which kernel versions support the APIs shown in this tutorial?
The misc framework and the APIs used here are stable and available across kernel 6.1 through the current 6.12+ series.
Continue This Free Linux Device Drivers Course
Next up: working with user-kernel communication pathways — debugfs, sysfs, netlink, and ioctl.

2 Comments