Linux Character Driver Read Method- Free Linux Device Drivers Course

PREV_LEC | NEXT_LEC

Linux Character Driver Read Method

Free Linux Kernel Development Course — Chapter 4.8

The linux character driver read method is the counterpart to the write method covered in the previous lecture. It is what runs when a user-space application calls read() on your device node, and it is responsible for safely handing kernel data back to that application. In this free linux device drivers course lecture, we extend the ep_chardev driver with a complete, working read() implementation for kernel 6.x.

Keywords covered in this lecture

read file operation
copy_to_user
end of file kernel
free linux development course
kernel 6.x

What You Will Learn

  • The prototype of the read() file operation and what each parameter does
  • How to correctly signal end-of-file to user space
  • How copy_to_user() safely returns kernel data to an application
  • How to add a working read() method to the ep_chardev driver
  • How to build, load, and verify the driver with real commands

Prerequisites

  • Lecture 4.7 of this free linux kernel development course, which added the write() method to ep_chardev
  • A Linux machine on kernel 6.x with kernel headers installed
  • Comfort with pointers and basic C structs

The read() Method Prototype

ssize_t (*read) (struct file *filp, char __user *buf,
size_t count, loff_t *f_pos);

buf is the user-space destination buffer, count is how many bytes the caller is requesting, and f_pos tracks the current read position. The return value is the number of bytes actually read, 0 to signal end-of-file, or a negative error code.

How Data Flows From Kernel to App

read() Call Path

Kernel Buffer

copy_to_user()

Driver ep_read()

VFS Layer

User App: read()

Steps Inside a read() Implementation

  1. Check for end-of-file. If *f_pos is already at or past the amount of valid data, return 0 so callers like cat know to stop.
  2. Clamp the count. Never let a read request pull more bytes than actually exist past the current offset.
  3. Lock the shared resource. Protect the buffer from concurrent access with a mutex, same as in write().
  4. Copy to user space. Use copy_to_user(), which validates the destination pointer before copying.
  5. Advance the position and return. Update *f_pos and return the number of bytes copied.

Why copy_to_user() Instead of memcpy()

Just as with writes, the destination buffer in a read call belongs to user space and cannot be trusted as a raw pointer. copy_to_user() validates the destination range and safely handles page faults, returning the number of bytes that could not be copied.

unsigned long not_copied = copy_to_user(dst_user, src_kernel, count);
if (not_copied)
return -EFAULT;

Full Example: Adding read() to ep_chardev

This builds directly on the ep_chardev struct and buffer introduced in the previous lecture, reusing the same data_len field that write() updates.

static ssize_t ep_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct ep_chardev *dev = filp->private_data;
ssize_t ret;if (mutex_lock_interruptible(&dev->lock))
return -ERESTARTSYS;if (*f_pos >= dev->data_len) {
ret = 0; /* end of file */
goto unlock;
}if (*f_pos + count > dev->data_len)
count = dev->data_len – *f_pos;if (copy_to_user(buf, dev->buffer + *f_pos, count)) {
ret = -EFAULT;
goto unlock;
}

*f_pos += count;
ret = count;

unlock:
mutex_unlock(&dev->lock);
return ret;
}

Notice that the end-of-file check happens after acquiring the lock. This matters because another process could be writing new data via ep_write() at the exact same moment, and data_len must be read under the same mutex that protects it.

Registering the read Callback

static const struct file_operations ep_fops = {
.owner = THIS_MODULE,
.open = ep_open,
.release = ep_release,
.write = ep_write,
.read = ep_read,
};

Building and Testing the Driver

$ make
$ sudo insmod ep_chardev.ko
$ sudo mknod /dev/ep_chardev c <major> 0
$ echo “hello kernel” | sudo tee /dev/ep_chardev
$ sudo cat /dev/ep_chardev

Expected output:

hello kernel
hello kernel
$ dmesg | tail -n 2
[ 145.902113] ep_chardev: read 13 bytes at offset 0
[ 145.902240] ep_chardev: read returned 0 (EOF)

The second line of output confirms the driver correctly returned the stored data and then signaled end-of-file on the next call, which is why cat exits cleanly instead of hanging.

read() Return Values

Return Value Meaning
Positive number Bytes successfully copied to the user buffer
0 End of file — no more data at this offset
-EFAULT Failed to copy data into the user buffer
-ERESTARTSYS Interrupted while waiting for the lock

Common Mistakes

  • Forgetting the end-of-file check, which causes tools like cat or dd to read forever.
  • Reading data_len without holding the same lock that write() uses to update it, creating a race condition.
  • Dereferencing buf directly instead of going through copy_to_user().
  • Returning a negative value on partial success instead of the actual byte count.

Best Practices

  • Always check for end-of-file before doing any copy work.
  • Share the same mutex between read() and write() when they touch the same buffer.
  • Return short reads instead of failing when less data is available than requested.
  • Keep the locked section limited to buffer access, not unrelated logic.

Performance and Security Considerations

A single mutex shared between read() and write() is simple and correct but can become a bottleneck under heavy concurrent access; a reader-writer lock (rwlock_t or rw_semaphore, covered later in this course) can help when reads vastly outnumber writes. On the security side, never trust count or assume the destination buffer is large enough — copy_to_user() protects the kernel, but a clamped count protects your device’s data boundaries too.

Summary

  • The read() file operation copies kernel data out to a user-space buffer using copy_to_user().
  • Return 0 to correctly signal end-of-file.
  • Clamp count so you never read past valid data.
  • Share locking with write() to avoid race conditions on shared state.

FAQ

What does returning 0 from read() mean?

It signals end-of-file. Utilities like cat and dd rely on this to know when to stop reading.

Why do read() and write() share the same mutex?

Because they both access data_len and the buffer. Using separate locks would allow a read to see a buffer that a concurrent write is only halfway through updating.

Can read() return fewer bytes than requested?

Yes, this is called a short read and is completely valid. The caller is expected to check the return value and call read() again if needed.

What happens if I skip the end-of-file check?

Programs like cat will call read() in an infinite loop since they never receive the 0 that tells them to stop.

Is copy_to_user() slower than a direct memory copy?

It has slightly more overhead due to access validation, but this cost is negligible next to the safety it provides against invalid or malicious user pointers.

Conclusion

With read() now in place alongside write(), the ep_chardev driver can fully exchange data with user space. The next lecture in this free embedded linux course covers the llseek() method, which lets applications move the read/write cursor to any position in the device.

Continue the Free Linux Kernel Development Course

More free embedded systems course and free linux development course lectures are on the way.

PREV_LEC | NEXT_LEC

 

Leave a Reply

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