Learn how to build a Linux Character Driver from scratch with this beginner-friendly guide. Step-by-step tutorial covering kernel modules, file operations, device files, and real-world driver examples.
If you’ve ever wanted to understand how Linux actually talks to hardware, learning a Linux Character Driver is one of the best places to start.
This guide is not just theory. We’ll go step by step, from zero to a working driver, and explain things like you’re sitting next to me with a coffee asking real questions.
By the end, you’ll:
- Understand what a Linux Character Driver is
- Know where it’s used in real systems (UART, GPIO, etc.)
- Build a complete driver from scratch
- Write a user-space program to interact with it
- Learn how real production drivers are structured
What is a Linux Character Driver?
A Linux Character Driver is a type of device driver that communicates with hardware one byte (character) at a time.
Think of it like reading a book letter by letter instead of jumping pages.
Examples:
- UART (serial communication)
- Keyboard input
- GPIO pins
- Sensors
These are all handled using character device drivers in Linux.
Character vs Block Drivers
Let’s make this super simple.
Imagine you’re reading data like you read content in real life.
Character Driver (One by One Data)
A Linux Character Driver works like reading a message letter by letter.
You don’t jump around. You go in order.
Example:
Think of:
- Typing on keyboard
- Reading from a serial port
- Getting sensor data
All of these send data continuously and sequentially.
That’s why they use a character device driver in Linux
Real Devices:
/dev/ttyS0(UART)/dev/input/event0(keyboard/mouse)/dev/gpiochip0(GPIO)
Block Driver (Data in Chunks)
A Block Driver works like reading a book where you can:
- Jump to page 10
- Then page 50
- Then page 2
No need to go in order.
Example:
Think of:
- Hard disk
- SSD
- USB storage
You can directly access any part of data.
That’s why they use block device drivers
Real Devices:
/dev/sda(hard disk)/dev/mmcblk0(SD card)
Simple Comparison
| Feature | Character Driver | Block Driver |
|---|---|---|
| Data Access | Sequential | Random |
| Data Type | Stream (byte-by-byte) | Blocks (chunks) |
| Example | UART, GPIO | Hard disk |
| Speed | Usually slower | Faster (optimized) |
| Buffering | Minimal | Heavy buffering |
Easy Analogy (Best Way to Remember)
- Character Driver → Like listening to a song live 🎧
You hear it as it plays (no jumping) - Block Driver → Like YouTube video 📺
You can skip anywhere
Golden Rule
Ask this question:
👉 “Do I need to access data randomly or sequentially?”
- Sequential → Linux Character Driver
- Random → Block Driver
In real linux driver development beginner journey:
- First drivers you learn → Character Drivers
- Advanced storage systems → Block Drivers
Because character drivers are:
- Easier to write
- Closer to hardware
- Great for learning linux kernel programming basics
If data flows like a stream → Character Driver
If data is accessed like storage → Block Driver
Code difference between char vs block driver
Where Are Character Drivers Used?
A Linux Character Driver isn’t just a textbook concept. It’s used everywhere Linux needs to handle stream-based, byte-by-byte communication with hardware.
Think of any device where data flows continuously rather than in chunks. That’s your signal that a character device driver in Linux is probably involved.
Let’s break down the most common real-world use cases.
UART Driver
Used for serial communication between devices.
GPIO Driver
Used to control pins (LED, buttons).
Sensors
Temperature, pressure sensors send data continuously.
So yes — when you asked earlier:
“Is UART, GPIO implemented using character driver?”
==> Yes, most of the time : absolutely.
Basic Architecture of Linux Character Driver
High-Level Architecture
Here’s the overall flow:
User Space Application
│
▼
System Calls (open, read, write, close)
│
▼
VFS (Virtual File System)
│
▼
Character Driver (Your Code)
│
▼
Hardware Device
Key Components Explained
1. User Space
This is where your application runs.
Example:
int fd = open("/dev/my_device", O_RDWR);
write(fd, "Hello", 5);
read(fd, buffer, 5);
close(fd);
These are system calls, not direct hardware access.
2. System Calls Interface
Linux provides standard APIs:
open()read()write()close()
These calls go through the kernel and reach your driver.
3. VFS (Virtual File System)
VFS acts as a bridge between system calls and drivers.
It ensures:
- Everything in Linux is treated as a file
- Your driver is accessed via
/dev/my_device
4. Device File (/dev)
Example:
/dev/my_deviceCreated using:
mknod(manual)udev(automatic)
It connects user space to your driver.
5. Character Driver Core Structure
Your driver mainly consists of:
a) File Operations Structure
This is the heart of the driver.
struct file_operations fops = {
.open = my_open,
.read = my_read,
.write = my_write,
.release = my_close,
};It tells the kernel:
“When user calls read(), call my_read()”
b) Major & Minor Number
- Major Number → Identifies driver
- Minor Number → Identifies device
Example:
Major: 240 → my driver
Minor: 0 → device instancec) Device Registration
alloc_chrdev_region(&dev, 0, 1, "my_device");
Registers device with kernel
d) cdev Structure
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
cdev_add(&my_cdev, dev, 1);
Links file operations with device
e) Class & Device Creation
class_create(THIS_MODULE, "my_class");
device_create(my_class, NULL, dev, NULL, "my_device");
Creates /dev/my_device
Driver Entry & Exit
Init Function
static int __init my_init(void)
{
// register device
return 0;
}
Exit Function
static void __exit my_exit(void)
{
// cleanup
}
Registered using:
module_init(my_init);
module_exit(my_exit);Complete Flow (Step-by-Step)
- User runs program
- Calls
open("/dev/my_device") - VFS finds your driver
- Calls
my_open() - User calls
write() - Kernel calls
my_write() - Driver interacts with hardware
- Data flows back via
read()
Pro Tip
“Character drivers are synchronous, stream-oriented, and accessed via file operations.”
Linux Kernel Module Basics
Before writing a driver, we need a kernel module.
Simple Hello World Module
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void) {
printk(KERN_INFO "Hello Driver Loaded\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Driver Removed\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
Compile
make
Insert Module
sudo insmod hello.koRemove Module
sudo rmmod helloWhat is #include?
#include <linux/module.h>
#include <linux/kernel.h>
In C, #include means:
“Bring definitions and declarations from another file so I can use them.”
1. #include <linux/module.h>
What it is:
This header is used for loadable kernel modules (LKM).
A Linux character driver is actually a kernel module, so this is mandatory.
What it provides:
Module Macros
module_init(my_init);
module_exit(my_exit);
These tell the kernel:
- Which function to run when module is loaded
- Which function to run when module is removed
License Declaration
MODULE_LICENSE("GPL");
Important because:
- Avoids kernel warnings
- Enables access to some kernel features
Author & Description
MODULE_AUTHOR("Raj");
MODULE_DESCRIPTION("Simple Character Driver");Simple Meaning:
This header makes your C file behave like a kernel module
2. #include <linux/kernel.h>
What it is:
This header provides kernel-level utilities and logging functions
What it provides:
printk() (Very Important)
printk(KERN_INFO "Hello Kernel\n");
This is like printf() but for the kernel.
Log Levels
KERN_INFO
KERN_WARNING
KERN_ERR
Example:
printk(KERN_ERR "Something went wrong\n");Difference Between Them
| Feature | <linux/module.h> | <linux/kernel.h> |
|---|---|---|
| Purpose | Module handling | Kernel utilities |
| Needed for | Driver lifecycle | Debugging/logging |
| Key Feature | module_init() | printk() |
static int __init hello_init(void) function
static int __init hello_init(void) {
printk(KERN_INFO "Hello Driver Loaded\n");
return 0;
}
What is this function?
This is the initialization function (entry point) of your kernel module (driver).
It runs when you load the driver using:
insmod driver.ko
Line-by-Line Explanation
1. static
Meaning:
- Limits the scope of this function only to this file
Other files in the kernel cannot access it
Why use it?
- Avoid name conflicts
- Good coding practice in kernel development
2. int
Meaning:
- Function returns an integer value
Important:
0→ Success- Negative value → Failure
Example:
return -ENOMEM; // Memory allocation failed
3. __init
What is this?
A special kernel macro
What it does:
- Marks this function as initialization-only code
- After driver loads → memory is freed automatically
Saves kernel memory
Important Rule:
❌ Never call an __init function after initialization
(its memory is gone!)
4. hello_init
Function Name:
- You can name it anything
- Convention:
drivername_init
5. (void)
Meaning:
- Function takes no arguments
6. { ... } (Function Body)
a) printk(KERN_INFO "Hello Driver Loaded\n");
This prints a message inside the kernel log
printk→ Kernel version ofprintfKERN_INFO→ Log level (informational message)
Where to see output?
dmesg
Example output:
Hello Driver Loaded
b) return 0;
Tells kernel:
“Driver loaded successfully”
Full Flow
- You run:
insmod driver.ko - Kernel calls:
hello_init() - Message printed:
Hello Driver Loaded - Kernel accepts module (since return = 0)
What if it fails?
If you return:
return -1;
Then:
- Driver will NOT load
- Kernel shows error
Real-World Use of hello_init
In real drivers, this function is used to:
- Register device (
alloc_chrdev_region) - Initialize
cdev - Create device file (
/dev/...) - Allocate memory
- Setup hardware
static void __exit hello_exit(void) function
static void __exit hello_exit(void) {
printk(KERN_INFO "Driver Removed\n");
}
What is this function?
This function runs when you remove/unload the driver:
rmmod driver
So:
hello_init()= when driver loadshello_exit()= when driver unloads
Line-by-Line Explanation
1. static
Meaning:
- Function is only visible inside this file
Prevents conflicts with other kernel code
2. void
Meaning:
- Function does not return anything
Unlike init, kernel doesn’t expect success/failure here
(you must clean up properly no matter what)
3. __exit
What it is:
A kernel macro similar to __init
What it does:
- Marks function as exit/cleanup code
- Used only when module is removed
- If driver is built into kernel (not module) → this code is ignored
Important:
If built-in:
Exit function may never run
4. hello_exit
Function name (can be anything)
Convention:
drivername_exit5. (void)
No parameters
6. Function Body
printk(KERN_INFO "Driver Removed\n");
Prints message to kernel log
Check using:
dmesgOutput:
Driver RemovedFull Flow
- You run:
rmmod driver - Kernel calls:
hello_exit() - Cleanup happens
- Message printed:
Driver Removed
Real-World Importance
In real drivers, this function is critical
You must clean everything you created in init():
Example Cleanup Tasks:
device_destroy(...)
class_destroy(...)
cdev_del(...)
unregister_chrdev_region(...)
kfree(...)
If you don’t clean properly:
- Memory leaks
- Device conflicts
- Kernel crash (serious bug)
Analogy
Think of it like:
“Shutdown procedure of your driver”
If you turned ON things in init(),
you MUST turn them OFF here.
How It Connects
You register it using:
module_exit(hello_exit);
This tells kernel:
“Call this function when module is removed”
Pro Tip (Interview Level)
Always say:
“Whatever is allocated in init must be freed in exit”
What is a Device File in Linux?
In Linux, everything is a file.
Your driver becomes usable through:
/dev/my_deviceThis is called a device file in Linux.
Core Components of a Linux Character Driver
Now we move into real Linux driver development for beginners.
Key parts:
dev_t→ device numbercdev→ character device structurefile_operations→ driver functions
File Operations Structure (Very Important)
This is the heart of your driver.
struct file_operations fops = {
.open = my_open,
.read = my_read,
.write = my_write,
.release = my_close,
};
This connects user-space calls to kernel functions.
What is struct file_operations?
It is a structure of function pointers defined by the kernel.
It tells the kernel:
“When a user performs a file operation, call these functions.”
Big Picture
When user runs:
fd = open("/dev/my_device", O_RDWR);
write(fd, "Hi", 2);
read(fd, buf, 2);
close(fd);
Kernel maps them like this:
| User Call | Driver Function |
|---|---|
open() | my_open() |
read() | my_read() |
write() | my_write() |
close() | my_close() |
Line-by-Line Breakdown
struct file_operations fops
Declares a variable fops of type file_operations
This structure is defined in:
#include <linux/fs.h>
.open = my_open
When user calls open() → kernel calls:
my_open()
Typical use:
- Initialize device
- Allocate resources
- Check permissions
.read = my_read
When user calls read() → kernel calls:
my_read()
Typical use:
- Send data from kernel → user
- Use
copy_to_user()
.write = my_write
When user calls write() → kernel calls:
my_write()
Typical use:
- Receive data from user → kernel
- Use
copy_from_user()
.release = my_close
Called when user runs close()
my_close()
Note:
release= close (kernel naming)
How It Works Internally
- You register your driver (
cdev_add) - Kernel stores pointer to
fops - When user performs operations:
- Kernel looks into
fops - Calls the corresponding function
- Kernel looks into
Example Function Prototypes
int my_open(struct inode *inode, struct file *file);
ssize_t my_read(struct file *file, char __user *buf, size_t len, loff_t *offset);
ssize_t my_write(struct file *file, const char __user *buf, size_t len, loff_t *offset);
int my_close(struct inode *inode, struct file *file);
Simple Analogy
Think of file_operations like:
📞 A contact list for the kernel
- open → call this number
- read → call this number
- write → call this number
Important Notes
You don’t need to implement all functions
Example:
struct file_operations fops = {
.read = my_read,
};
Only read is supported
If function is missing
- Kernel may return error (
-ENOSYS) - Operation fails
Must Register This
cdev_init(&my_cdev, &fops);
This connects your driver with fops
Real Driver Flow
User → open() → my_open()
User → write() → my_write()
User → read() → my_read()
User → close() → my_close()Full Linux Character Driver Example
Now let’s build a complete character driver example in Linux.
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "my_char_dev"
static dev_t dev_num;
static struct cdev my_cdev;
char kernel_buffer[1024];
static int my_open(struct inode *inode, struct file *file) {
printk("Device Opened\n");
return 0;
}
static int my_close(struct inode *inode, struct file *file) {
printk("Device Closed\n");
return 0;
}
static ssize_t my_read(struct file *file, char __user *buf, size_t len, loff_t *off) {
copy_to_user(buf, kernel_buffer, len);
return len;
}
static ssize_t my_write(struct file *file, const char __user *buf, size_t len, loff_t *off) {
copy_from_user(kernel_buffer, buf, len);
return len;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = my_open,
.read = my_read,
.write = my_write,
.release = my_close,
};
static int __init driver_init(void) {
alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
cdev_init(&my_cdev, &fops);
cdev_add(&my_cdev, dev_num, 1);
printk("Driver Loaded\n");
return 0;
}
static void __exit driver_exit(void) {
cdev_del(&my_cdev);
unregister_chrdev_region(dev_num, 1);
printk("Driver Unloaded\n");
}
module_init(driver_init);
module_exit(driver_exit);
MODULE_LICENSE("GPL");
Create Device File
sudo mknod /dev/my_char_dev c <major> <minor>
User Space Program
Now let’s interact with the driver.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("/dev/my_char_dev", O_RDWR);
write(fd, "Hello Driver", 12);
char buf[100];
read(fd, buf, 12);
printf("Data: %s\n", buf);
close(fd);
return 0;
}
How It Works End-to-End
- User program calls
write() - Kernel calls
my_write() - Data stored in kernel buffer
read()fetches it back
This is a complete Linux device driver examples
Why Exit Function is Important?
You asked earlier:
Why do we need exit function?
Because kernel modules must clean up resources.
Without cleanup:
- Memory leaks
- Device conflicts
- System instability
So always use:
module_exit(driver_exit);Real Production Driver Structure
In real embedded Linux driver development, drivers are more complex:
- Use
platform_driver - Device Tree (DTS)
- Interrupt handling
- DMA
- Hardware registers
Debugging Linux Drivers
Essential for real work.
Use:
dmesgAdd logs:
printk(KERN_INFO "Debug Message\n");Advanced:
- ftrace
- kgdb
Common Mistakes Beginners Make
- Forgetting
copy_to_user - Not checking return values
- Memory leaks
- Wrong device permissions
Linux Driver Interview Questions
You’ll definitely face these:
Q1: What is character driver?
Ans : Sequential data transfer driver
Q2: What is file_operations?
Ans : Structure linking user calls to kernel
Q3: Difference between user and kernel space?
Ans : Security and access level separation
Best Practices
- Always validate user input
- Use
snprintfinstead ofsprintf - Follow kernel coding style
- Keep driver modular
Advanced Topics (Next Step)
Once you’re comfortable:
- Interrupt handling
- Platform drivers + DTS
- SPI/I2C drivers
- Kernel synchronization
Final Thoughts
Learning a Linux Character Driver is like opening the door to kernel-level programming.
At first, it feels confusing:
- Kernel space
- File operations
- Device numbers
But once you build one working driver, everything clicks
FAQ : Linux Character Driver
1.What is a Linux Character Driver?
A Linux Character Driver is a type of device driver that allows communication between user space and hardware devices one byte at a time (stream-based data transfer). These drivers are commonly used for devices like UART, keyboards, GPIO, and sensors.
2.How does a Linux Character Driver work?
A Linux Character Driver works through a layered architecture:
- User application calls
open(),read(),write() - System calls reach the kernel via VFS (Virtual File System)
- Kernel invokes driver functions defined in
file_operations - Driver interacts with hardware
3.What is file_operations in a character driver?
file_operations is a structure that maps user-space system calls to driver functions.
Example:
open()→my_open()read()→my_read()write()→my_write()
It acts as the core interface between user space and kernel space.
4.What is the difference between Character Driver and Block Driver?
| Feature | Character Driver | Block Driver |
|---|---|---|
| Data Access | Byte-by-byte | Block (chunks) |
| Example | UART, GPIO | Hard Disk |
| Buffering | Minimal | Uses cache |
| Access Type | Sequential | Random |
5.What are major and minor numbers?
- Major Number → Identifies the driver
- Minor Number → Identifies the device instance
These numbers help the kernel route operations to the correct driver.
6.What is /dev in Linux Character Drivers?
/dev is a directory where device files are created.
Example:
/dev/my_device
This file acts as the entry point for user applications to interact with the driver.
7.What is printk() in Linux drivers?
printk() is used for kernel-level logging, similar to printf() in user space.
Example:
printk(KERN_INFO "Driver Loaded\n");
You can view logs using:
dmesg
8.What is __init and __exit in Linux drivers?
__init→ Marks initialization function (freed after loading)__exit→ Marks cleanup function (used during module removal)
These help optimize memory usage in the kernel.
9.How do you load and unload a character driver?
Load driver:
sudo insmod driver.ko
Unload driver:
sudo rmmod driver
Check logs:
dmesg
10.What is a kernel module in Linux?
A kernel module is a piece of code that can be loaded/unloaded into the kernel dynamically. A Linux Character Driver is typically implemented as a kernel module.
11.Where are Linux Character Drivers used?
They are used in real-world scenarios like:
- UART communication
- GPIO control
- Sensor interfacing
- Embedded systems
- Serial devices
12.Do I need hardware to learn character drivers?
No. You can start with a virtual character driver (dummy driver) and simulate read/write operations without real hardware.
13.What are common mistakes beginners make?
- Forgetting cleanup in
__exit - Not handling
copy_to_user()properly - Incorrect major/minor number handling
- Missing error checks in init function
14.Is Linux Character Driver development hard?
Initially, it may feel complex, but with step-by-step practice:
- Start with a simple module
- Learn
file_operations - Build a dummy driver
It becomes much easier over time.
15.What is the difference between read() and write() in drivers?
read()→ Kernel → User space (sending data)write()→ User space → Kernel (receiving data)
16.Can I build a production-level driver with character drivers?
Yes. Many real-world drivers (UART, I2C user interfaces, debug interfaces) are built using character drivers.
For detailed understanding of Platform Devices and Drivers on Linux, refer to the official Linux documentation on Platform Devices and Drivers .
Mr. Raj Kumar is a highly experienced Technical Content Engineer with 7 years of dedicated expertise in the intricate field of embedded systems. At Embedded Prep, Raj is at the forefront of creating and curating high-quality technical content designed to educate and empower aspiring and seasoned professionals in the embedded domain.
Throughout his career, Raj has honed a unique skill set that bridges the gap between deep technical understanding and effective communication. His work encompasses a wide range of educational materials, including in-depth tutorials, practical guides, course modules, and insightful articles focused on embedded hardware and software solutions. He possesses a strong grasp of embedded architectures, microcontrollers, real-time operating systems (RTOS), firmware development, and various communication protocols relevant to the embedded industry.
Raj is adept at collaborating closely with subject matter experts, engineers, and instructional designers to ensure the accuracy, completeness, and pedagogical effectiveness of the content. His meticulous attention to detail and commitment to clarity are instrumental in transforming complex embedded concepts into easily digestible and engaging learning experiences. At Embedded Prep, he plays a crucial role in building a robust knowledge base that helps learners master the complexities of embedded technologies.







