Linux Character Driver from Scratch (Beginner to Pro)

On: March 29, 2026
Linux Character Driver

Table of Contents

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

FeatureCharacter DriverBlock Driver
Data AccessSequentialRandom
Data TypeStream (byte-by-byte)Blocks (chunks)
ExampleUART, GPIOHard disk
SpeedUsually slowerFaster (optimized)
BufferingMinimalHeavy 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

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_device

Created 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 instance

c) 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)

  1. User runs program
  2. Calls open("/dev/my_device")
  3. VFS finds your driver
  4. Calls my_open()
  5. User calls write()
  6. Kernel calls my_write()
  7. Driver interacts with hardware
  8. 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.ko

Remove Module

sudo rmmod hello

What 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>
PurposeModule handlingKernel utilities
Needed forDriver lifecycleDebugging/logging
Key Featuremodule_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 of printf
  • KERN_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

  1. You run:insmod driver.ko
  2. Kernel calls:hello_init()
  3. Message printed:Hello Driver Loaded
  4. 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 loads
hello_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_exit

5. (void)

No parameters

6. Function Body

printk(KERN_INFO "Driver Removed\n");

Prints message to kernel log

Check using:

dmesg

Output:

Driver Removed

Full Flow

  1. You run:rmmod driver
  2. Kernel calls:hello_exit()
  3. Cleanup happens
  4. 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_device

This 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 number
  • cdev → character device structure
  • file_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 CallDriver 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

  1. You register your driver (cdev_add)
  2. Kernel stores pointer to fops
  3. When user performs operations:
    • Kernel looks into fops
    • Calls the corresponding function

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

  1. User program calls write()
  2. Kernel calls my_write()
  3. Data stored in kernel buffer
  4. 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:

dmesg

Add 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 snprintf instead of sprintf
  • 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?

FeatureCharacter DriverBlock Driver
Data AccessByte-by-byteBlock (chunks)
ExampleUART, GPIOHard Disk
BufferingMinimalUses cache
Access TypeSequentialRandom

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 .

Related Posts

Leave a Comment