In-Depth Understanding of Step-by-Step Implementation of Character Device Drivers

In Linux driver development, character device drivers are the most common and fundamental type of driver. Most peripherals, whether they are LED lights, buttons, serial ports, or GPIOs, are initially provided to user space as character devices.

This article will guide you from scratch,step by step to implement a character device driver, along with practical API analysis, allowing you to learn how to write drivers.

1. Basic Framework of Character Device DriversIn-Depth Understanding of Step-by-Step Implementation of Character Device Drivers

2. Why Implement in Steps?

  • Reduce Complexity: Writing the driver all at once can be chaotic; implementing it step by step makes it easier to understand each part.

  • Facilitate Debugging: Testing after each step allows for quick identification of issues.

  • Enhance Maintainability: Clear steps make it easier to extend or modify later.

  • Align with Engineering Practices: In team collaboration, step-by-step implementation is easier for division of labor and management.

3. Internal Implementation Analysis of register_chrdev Function

int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); } | \|/ int __register_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char *name, const struct file_operations *fops) { struct char_device_struct *cd; struct cdev *cdev; // Character device structure object int err = -ENOMEM; // Allocate device number cd = __register_chrdev_region(major, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); // Allocate space for cdev object cdev = cdev_alloc(); if (!cdev) goto out2; // Initialize cdev object cdev->owner = fops->owner; cdev->ops = fops; kobject_set_name(&cdev->kobj, "%s", name); // Register object err = cdev_add(cdev, MKDEV(cd->major, baseminor), count); if (err) goto out; cd->cdev = cdev; return major ? 0 : cd->major; out: kobject_put(&cdev->kobj); out2: kfree(__unregister_chrdev_region(cd->major, baseminor, count)); return err; }
void __unregister_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char *name) { struct char_device_struct *cd; cd = __unregister_chrdev_region(major, baseminor, count); if (cd && cd->cdev) cdev_del(cd->cdev); kfree(cd); }

4. API Interface Functions Corresponding to Step-by-Step Implementation of Character Device Drivers

#include <linux/cdev.h> 1. Allocate Object struct cdev { struct kobject kobj; struct module *owner; // THIS_MODULE const struct file_operations *ops; // Pointer to operations structure struct list_head list; // Linked list, many data in the kernel are maintained through linked lists dev_t dev; // Device number unsigned int count; // Count } struct cdev *cdev; cdev = cdev_alloc(); // Allocate space struct cdev *cdev_alloc(void) // Function: Allocate memory space for cdev structure pointer // Parameters: None // Return value: // On success: Address of the space pointed to by cdev structure pointer // On failure: NULL 2. Object Initialization void cdev_init(struct cdev *cdev, const struct file_operations *fops) // Function: Function to initialize cdev object // Parameters: // @ cdev : Pointer to cdev object // @ fops : Pointer to operations structure // Return value: None 3. Request Device Number int register_chrdev_region(dev_t from, unsigned count, const char *name) // Function: Statically specify device number // Parameters: // @ from : Starting value of device number // @ count : Count // @ name : Name // Return value: // On success: 0, on failure: error code int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) // Function: Dynamically request device number // Parameters: // @ dev : Obtained device number // @ baseminor : Starting value of minor device number // @ count : Count // @ name : Name // Return value: // On success: 0, on failure: error code 4. Register Object int cdev_add(struct cdev *p, dev_t dev, unsigned count) // Function: Register character device driver object // Parameters: // @ p : Pointer to cdev structure object // @ dev : Obtained device number // @ count : Count of devices // Return value: // On success: 0, on failure: error code 1. Destroy Object void cdev_del(struct cdev *p) // Function: Destroy registered character device driver object // Parameters: // @ p : Pointer to cdev structure object // Return value: None 2. Destroy Device Number void unregister_chrdev_region(dev_t from, unsigned count) // Function: Destroy device number // Parameters: // @ from : Starting value of device number // @ count : Count of devices // Return value: // None 3. Free Memory Space #include <linux/slab.h> void kfree(const void *p); // Function: Free memory space // Parameters: // @ p : Address of the memory space // Return value: // None

5. Conclusion

By implementing step by step, you can:

  • Clearly understand the lifecycle of character device drivers

  • Clarify the role and usage scenarios of each API

  • Quickly implement a readable and writable device example

The step-by-step implementation approach is easier to understand than writing a “large block driver” directly, and it is also more convenient for debugging and expansion.

Please open in the WeChat client

Leave a Comment