1. What is a Character Device?
Character Device (Character Device) is a type of device that can perform data stream-style reading and writing "byte by byte". Common examples include serial ports, keyboards, mice, etc. Users and programs interact with character devices through file operations (open, read, write, close, etc.).
For example:
int fd = open("/dev/mydevice", O_RDWR);
write(fd, buffer, size);
read(fd, buffer, size);
close(fd);2
3
4
Goal: Pass user requests from user space to the character driver in kernel space.
2. Why Do We Need a Character Device Framework?
Without a unified character device framework, every driver developer would have to handle file operations, device number allocation, data reading/writing and other low-level details separately. This is not only complex and error-prone but also easily leads to inconsistent code styles.
The character device framework provides driver developers with a standard process and interface. You only need to focus on the device-specific implementation (such as how to read/write specific hardware), without repeatedly worrying about how to make the kernel recognize your device and how to communicate with user space. This makes driver development efficient, standardized, and easy to maintain and upgrade.
Explanation
The character device framework exists to "unify entry points, simplify development, avoid duplication, and reduce errors". Newcomers who follow the framework's process can more quickly write drivers that meet specifications.
3. Basic Flow of Character Device Driver
1. Register Character Device
In the kernel, character devices all have a "device number" (major number + minor number). During registration, the system is told the device number range that this driver is responsible for.
Main functions:
register_chrdev_region()oralloc_chrdev_region()- Returns a device number of type
dev_t
2. Initialize and Register cdev Structure
After registration, you need to "bind" your driver operation "collection" (read, write, etc.) with the device number:
Main structure:
struct cdev
Main functions:
cdev_init()cdev_add()
3. Implement file_operations Interface
When system calls are received, they enter the kernel through VFS (Virtual File System). The role of VFS is to uniformly manage operations of different file systems. Specifically:
- VFS finds the corresponding operation implementation in the device driver through the
file_operationsstructure. For example, when you read/write a file, VFS finds the specific hardware operation method based on the information in this structure. file_operationsis the core configuration table of the driver program. It records how the driver handles various operations (such as read, write, open file, etc.). You can think of it as the driver's "operation manual", telling the operating system which specific function to call for each action.- This design allows the operating system not to care about specific hardware details. It only needs to be compatible with different devices' file operations through VFS and
file_operations.
The file_operations structure defines which operations the device supports, such as open, read, write, etc.
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.release = xxx_release,
.read = xxx_read,
.write = xxx_write,
.... // Other operations
// Only implement part as needed
};2
3
4
5
6
7
8
9
4. Create Device Node
Allow user space to access this device through /dev/xxx.
Generally use class_create(), device_create() combined with udev to automatically generate nodes.
struct class* xxx_class = class_create(THIS_MODULE, "xxx_class");
device_create(xxx_class, NULL, devno, NULL, "xxx_dev");2
5. Unload Driver and Cleanup
cdev_del()unregister_chrdev_region()device_destroy()class_destroy()
4. Application Program Operations
When an application opens a character device file through open(), the kernel finds the corresponding driver in the character device registration table based on the major number of the device node. Then, through the file_operations structure provided during driver registration, it maps user requests (such as read, write, etc.) to the actual driver handling functions, achieving an efficient connection between user operations and device hardware.