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
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 theep_chardevdriver - 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 toep_chardev - A Linux machine on kernel 6.x with kernel headers installed
- Comfort with pointers and basic C structs
The read() Method Prototype
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
→
copy_to_user()
→
Driver ep_read()
→
VFS Layer
→
User App: read()
Steps Inside a read() Implementation
- Check for end-of-file. If
*f_posis already at or past the amount of valid data, return0so callers likecatknow to stop. - Clamp the count. Never let a read request pull more bytes than actually exist past the current offset.
- Lock the shared resource. Protect the buffer from concurrent access with a mutex, same as in
write(). - Copy to user space. Use
copy_to_user(), which validates the destination pointer before copying. - Advance the position and return. Update
*f_posand 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.
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.
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
.owner = THIS_MODULE,
.open = ep_open,
.release = ep_release,
.write = ep_write,
.read = ep_read,
};
Building and Testing the Driver
$ 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
$ 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
catorddto read forever. - Reading
data_lenwithout holding the same lock thatwrite()uses to update it, creating a race condition. - Dereferencing
bufdirectly instead of going throughcopy_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()andwrite()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 usingcopy_to_user(). - Return
0to correctly signal end-of-file. - Clamp
countso 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.
