Task 1: Character Device Driver
Overview
In Linux, hardware devices are presented to user space as device files, which can be accessed using standard file I/O operations. These device files reside in the "/dev" directory and are backed by device drivers, kernel components that interact with the hardware device. Typically, device drivers are implemented as modules. System calls like open, read, write, close, lseek, mmap, etc., made to these device files are routed to the corresponding device driver for handling.
There exist two primary types of device files: character device files (chrdev) and block device files (blkdev). These types are differentiated by the speed, volume, and method of data transfer between the device and the system.
A character device file represents a slower device that transfers data in streams of bytes, examples include keyboards, mice, serial ports, or terminals. Managing these devices is relatively simple since they don't demand extremely high performance.
In contrast, a block device file represents a faster device that transfers data in fixed-size blocks, such as hard disks or flash drives. Block devices typically offer higher speeds compared to character devices, and their performance is critical. So Linux provides a different set of APIs for interacting with block devices, allowing for common support features like caching, buffering, and I/O scheduling to be implemented.
To identify device drivers in your kernel, use the command ls -l /dev
and
examine the information in each column. The first letter in each line indicates
if it's a char device (represented as "c") or a block device (represented as
"b"). The major and minor numbers are also displayed. The major number
identifies the specific driver associated with the device. It is used by the
kernel to route system calls for the device to the appropriate driver. The
minor number is used by the kernel to determine the exact device being referred
to.
For example, in a disk driver, the minor number might specify a partition on the
disk.
$ ls -l /dev/sd*
brw-rw---- 1 root disk 8, 0 Feb 20 16:36 /dev/sda
brw-rw---- 1 root disk 8, 1 Feb 20 16:36 /dev/sda1
brw-rw---- 1 root disk 8, 2 Feb 20 16:36 /dev/sda2
Onebyte Character Device
In this task, we will see a character device driver that exposes the "onebyte" hardware device to user space as a character device file. "onebyte" can store only one byte, backed by a single byte of memory.
- When the driver is loaded, "onebyte" is initialized to -1.
- When the device is read, the byte is returned to the user.
- When the device is written, the byte is overwritten with the new value.
It is both "global" and "persistent", meaning that multiple file descriptors can access the data inside, and its data remains available and unchanged even when the device is closed and reopened. By exposing it as a character device file, it can be accessed using standard file I/O operations and tested with shell commands such as cat, echo, and I/O redirection.
Before using the character device, it needs to be registered.
The file_operations
structure needs to be set up, within which, we can define
the behaviour of the device by implementing the open
, release
, read
and
write
functions.
Please refer to the following code snippet for the implementation of the onebyte character device driver.
Question
Please explain, in detail, how the container_of
macro works in the
onebyte_open
function, and how it's used to retrieve the onebyte_data
structure.
Additionally, Provide an explanation as to why the onebyte_data
structure
is stored as the private data of the file instead of being stored as a
global variable?
Compile the code and load the module. You may find that the entry in the "/dev" directory is not automatically created when the module is loaded. These device files need to be created either:
- manually using the
mknod
command, which creates a special file that represents the device file, or - automatically by the "udev" daemon.
For this task, we create the device file manually using the mknod
command.
Question
Provide the exact mknod
command you used to create the device file.
Here's the expected behaviour of the device:
$ sudo insmod onebyte.ko
$ xxd /dev/onebyte
00000000: ff .
$ echo 'a' > /dev/onebyte
bash: echo: write error: File too large
$ xxd /dev/onebyte
00000000: 61 a
$ echo -n 'b' > /dev/onebyte
$ xxd /dev/onebyte
00000000: 62 b
$ sudo rmmod onebyte
$ sudo insmod onebyte.ko
$ xxd /dev/onebyte
00000000: ff .