Implementing read() and write() in a Misc Character Device Driver-Linux Device Driver Training in Hyderabad

← Previous Lecture  |  Next Lecture →

Implementing read() and write() in a Misc Character Device Driver

Free Linux device drivers course — copy_to_user, copy_from_user and safe kernel buffers explained simply

Continuing this free Linux kernel development course, this lesson adds real I/O to the misc character device driver we registered previously. By the end you will understand exactly how data crosses the user space/kernel space boundary safely, which is one of the most-asked topics in any free Linux device drivers course.

What You Will Learn

  • Why you can never directly dereference a user space pointer inside the kernel
  • How copy_to_user() and copy_from_user() work internally
  • How to write a correct, bounds-checked read() method
  • How to write a correct, bounds-checked write() method
  • How to protect shared driver state with a mutex

Why User Pointers Are Dangerous

A pointer that a user space application passes to read(2) or write(2) lives in that process’s address space, not the kernel’s. The kernel runs with full privileges, so if it blindly dereferenced a user pointer, a malicious or buggy application could hand it a kernel address and trick the driver into reading or corrupting kernel memory. This is exactly why the kernel provides a dedicated, checked pair of functions for crossing this boundary.

Data Flow During a read() Call

App calls read(fd, buf, len) → Kernel driver read() runs
↓
copy_to_user() validates and copies kernel data into user buffer
↓
App’s buffer now holds the driver’s data

The read() Method

Continuing the greet_dev driver from the previous lesson, we now add a read() callback that copies the stored greeting message back to whichever application reads from /dev/greet_dev.

static ssize_t greet_read(struct file *filp, char __user *ubuf,
                           size_t count, loff_t *offp)
{
    ssize_t ret;
    size_t msg_len;

    if (mutex_lock_interruptible(&gctx->lock))
        return -ERESTARTSYS;

    msg_len = strlen(gctx->message);

    /* Once past end of message, signal EOF */
    if (*offp >= msg_len) {
        ret = 0;
        goto out;
    }

    if (count > msg_len - *offp)
        count = msg_len - *offp;

    if (copy_to_user(ubuf, gctx->message + *offp, count)) {
        ret = -EFAULT;
        goto out;
    }

    *offp += count;
    ret = count;

out:
    mutex_unlock(&gctx->lock);
    return ret;
}

Three things make this implementation solid rather than a toy example: it tracks the file offset *offp so repeated reads behave like a real file, it clamps count to the remaining data so it never reads past the end of the buffer, and it takes the mutex so two processes reading concurrently cannot race on gctx.

The write() Method

The write path is the mirror image: user space data must be pulled into the kernel with copy_from_user() before the driver can use it. We also validate the incoming size against our fixed-size buffer to avoid any overflow.

static ssize_t greet_write(struct file *filp, const char __user *ubuf,
                            size_t count, loff_t *offp)
{
    ssize_t ret;

    if (count == 0)
        return 0;

    if (count >= GREETING_MAX)
        count = GREETING_MAX - 1;

    if (mutex_lock_interruptible(&gctx->lock))
        return -ERESTARTSYS;

    if (copy_from_user(gctx->message, ubuf, count)) {
        ret = -EFAULT;
        goto out;
    }

    gctx->message[count] = '\0';
    ret = count;

out:
    mutex_unlock(&gctx->lock);
    return ret;
}

Finally, wire both callbacks into the file operations table declared in the previous lesson:

static const struct file_operations greet_fops = {
    .owner   = THIS_MODULE,
    .open    = greet_open,
    .release = greet_release,
    .read    = greet_read,
    .write   = greet_write,
};

copy_to_user() vs copy_from_user() at a Glance

FunctionDirectionUsed In
copy_to_user()Kernel → User spaceread() method
copy_from_user()User space → Kernelwrite() method

Both functions return the number of bytes that could not be copied — zero means full success. Always check this return value; never assume the copy succeeded.

Common Mistakes Beginners Make

  • Not clamping count against the buffer size, which can lead to an out-of-bounds write into kernel memory.
  • Ignoring the file offset, which breaks tools like cat that read in a loop until they see zero bytes returned.
  • Skipping locking around shared driver state, causing data races when two processes access the device at once.
  • Assuming copy_to_user()/copy_from_user() always succeed without checking the return value.

Summary & Key Takeaways

  • Never dereference user pointers directly — always go through copy_to_user()/copy_from_user().
  • Respect the file offset so your driver behaves correctly with standard tools like cat and echo.
  • Clamp all sizes against your buffer capacity before copying.
  • Protect shared state with a mutex once more than one process can open the device.

Frequently Asked Questions

Q1. Why can’t I just use memcpy() in a driver’s read/write method?
memcpy() does not validate that the destination pointer is a legitimate, accessible user space address, so using it instead of copy_to_user()/copy_from_user() can corrupt memory or crash the system.

Q2. What does it mean when read() returns 0?
A return value of 0 signals end-of-file to the calling application, telling tools like cat to stop reading.

Q3. Do I always need a mutex in a character driver?
You need one whenever driver state can be accessed by more than one process concurrently, which is the common case for any device that can be opened multiple times.

Q4. What happens if copy_from_user() partially fails?
It returns the number of bytes it could not copy; a well-written driver checks this and returns -EFAULT rather than trusting a partially filled buffer.

Q5. Is this read()/write() pattern used in real drivers?
Yes, this exact pattern (offset tracking, size clamping, locked access, checked copies) is the standard approach used throughout mainline Linux character drivers.

Continue the Free Linux Device Drivers Course

Next up: testing the driver, best practices, and security considerations

← Previous Lecture  |  Next Lecture →

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *