Skip to content

Day 6 - User Space โ†” Kernel Driver Interface

๐Ÿ“… Date

2026-03-XX


๐ŸŽฏ Today's Goal

  • Understand how user space interacts with kernel drivers
  • Learn character device driver basics
  • Implement open / read / write / release
  • Understand file abstraction in Linux

๐Ÿง  What I Learned

1. Everything is a file

In Linux, devices are exposed as files:

/dev/mydev
/dev/tty
/dev/i2c-1

User space interacts with drivers using:

open()
read()
write()
ioctl()
close()

2. Call Flow

User Space:
    open()
    read()
    write()
    close()

        โ†“ syscall

Kernel (VFS):
    sys_open()
    sys_read()
    sys_write()
    sys_close()

        โ†“

Driver:
    file_operations
        .open
        .read
        .write
        .release

3. open vs release

  • open() โ†’ driver open()
  • close() โ†’ driver release()

Important:

  • release is NOT close
  • release is called when last reference is dropped

4. File instance and offset

Each open() creates a new file instance:

  • has its own offset
  • has its own lifecycle

EOF is per-open, not global


๐Ÿ”ง Implementation Summary

file_operations

static struct file_operations fops = {
    .owner   = THIS_MODULE,
    .open    = my_open,
    .read    = my_read,
    .write   = my_write,
    .release = my_release,
};

Read with EOF handling

if (*offset >= data_size)
    return 0;

bytes_read = min(len, data_size - *offset);

copy_to_user(...);

*offset += bytes_read;

Write

copy_from_user(buffer, user_buf, bytes);
data_size = bytes;

๐Ÿ” Observations

Why cat always reads data

Each execution of cat:

open โ†’ read โ†’ EOF โ†’ close

Next execution:

new open โ†’ offset reset โ†’ read again

EOF behavior

  • EOF only applies within one open
  • new open resets offset

๐Ÿ”’ Concurrency

Problem

if (device_opened)
    return -EBUSY;

device_opened = 1;

This has race condition


Solution

mutex_lock(&lock);

if (device_opened) {
    ret = -EBUSY;
    goto out;
}

device_opened = 1;

out:
mutex_unlock(&lock);

๐Ÿง  Key Takeaways

  • Driver is a stateful object, not just functions
  • Must follow file semantics
  • Multi-process is default in Linux
  • Offset and EOF must be handled correctly

โš ๏ธ Pitfalls

  • EOF is not global
  • release is not equal to close
  • global variables must be protected
  • kernel arguments usually do not need NULL check

๐Ÿš€ Next

  • ioctl
  • blocking read
  • poll/select

or

  • device tree
  • platform driver


๐Ÿงช How to Test the Driver

This section describes the full workflow from loading the module to unloading it.


1. Build the module

make

2. Insert the module

sudo insmod mydev.ko

Check kernel log:

dmesg | tail

Expected output:

mydev: registered with major XXX

3. Create device node

Replace <major> with the value from dmesg:

sudo mknod /dev/mydev c <major> 0
sudo chmod 666 /dev/mydev

4. Write to device

echo "hello world" > /dev/mydev

Check kernel log:

dmesg | tail

5. Read from device

cat /dev/mydev

Expected output:

hello world

6. Verify EOF behavior

Run:

cat /dev/mydev
cat /dev/mydev

Observation:

  • Each cat triggers a new open
  • Offset resets to 0
  • Data is readable again

7. Test partial read

head -c 5 /dev/mydev

Expected:

hello

8. Test single-open protection

cat /dev/mydev &
cat /dev/mydev

Expected:

  • Second open should fail with Device or resource busy

9. Remove device node (optional)

sudo rm /dev/mydev

10. Remove the module

sudo rmmod mydev

Verify:

dmesg | tail

Expected:

mydev: unregistered

๐Ÿ” Full Workflow Summary

make
sudo insmod mydev.ko
dmesg | tail

sudo mknod /dev/mydev c <major> 0
sudo chmod 666 /dev/mydev

echo "hello world" > /dev/mydev
cat /dev/mydev

sudo rmmod mydev

โš ๏ธ Notes

  • Device node must match correct major number
  • If insmod fails, check dmesg
  • Always remove module before rebuilding

๐ŸŽฏ Summary

Linux driver = file interface + stateful behavior