Hello, I am Lao Wu, continuing to document my learning insights.
1. Tips for Staying Focused
-
Do the most important tasks in the morning.
-
Stay in a distraction-free environment, such as a library.
-
It’s completely normal to feel a bit of negativity before you start working.
-
Make “happy moments” a part of your plan.
-
Have a reasonable daily and weekly plan.
2. Insights into Linux Character Device Drivers (1)
Table of Contents:
1. What is a character device driver?
2. Quick experience with character device drivers and applications (super simple demo)
3. Abstraction of character devices in the kernel
3.1 Overview of core character device code
3.2 Abstraction of character devices: struct cdev
3.3 Abstraction of operations on character devices: struct file_operations
4. More knowledge points worth learning
5. Related references
Purpose of Writing:
- Explore Linux character device drivers.
Testing Environment:
- Ubuntu 16.04
- Gcc 5.4.0
1. What is a character device driver?
- In the real world, there are thousands of hardware devices, each differing in hardware characteristics and usage. The experts of the Linux system have extracted commonalities from these diverse devices and abstracted them into three categories: character devices, block devices, and network devices.
- Considering code quality and reusability, the Linux kernel provides a corresponding driver model framework for each type of hardware device, generally including basic kernel facilities and file system interfaces. Developers can use a complete driver model framework when writing device drivers, allowing them to focus on controlling the hardware device itself.
- Simple structure diagram of a Linux device driver:
- More detailed structure diagram of a Linux device driver:
2. Quick Experience with Character Device Drivers and Applications (Super Simple Demo)
1) Character Device Driver (chrdev_drv.c):
Open and read functions of the character device:
static struct cdev chr_dev; // Character device abstraction
static dev_t ndev; // Device number
static int chr_open(struct inode *nd, struct file *filp)
{
printk("chr_open, major=%d, minor=%d\n", MAJOR(nd->i_rdev), MINOR(nd->i_rdev));
return 0;
}
static ssize_t chr_read(struct file *filp, char __user *u, size_t sz, loff_t *off)
{
printk("In chr_read()\n");
return 0;
}
static int chr_release(struct inode *nd, struct file *filp)
{
printk("In chr_release()\n");
return 0;
}
File operation function set:
struct file_operations chr_ops =
{
.owner = THIS_MODULE,
.open = chr_open,
.read = chr_read,
.release = chr_release,
};
Module loading and unloading:
static int demo_init(void)
{
int ret;
cdev_init(&chr_dev, &chr_ops);
ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev");
if(ret < 0)
return ret;
printk("demo_init():major=%d, minor=%d\n", MAJOR(ndev), MINOR(ndev));
ret = cdev_add(&chr_dev, ndev, 1);
if(ret < 0)
return ret;
return 0;
}
static void demo_exit(void)
{
printk("demo_exit...\n");
cdev_del(&chr_dev);
unregister_chrdev_region(ndev, 1);
}
2) Application to Access Character Device:
int main()
{
int ret;
char buf[32];
int fd = open("/dev/chr_dev", O_RDONLY|O_NDELAY);
if(fd < 0)
{
printf("open file %s failed!\n", CHR_DEV_NAME);
return -1;
}
read(fd, buf, 32);
close(fd);
return 0;
}
3) Without fully understanding the meaning of the code, just look at the running effect:
# Compile the driver program
$ make KERNELDIR=XXX/linux ARCH=arm CROSS_COMPILE=arm-linux-
# Compile the application program
$ arm-linux-gcc chrdev_app.c -o chrdev_app
# Load the driver module
$ insmod chrdev_drv.ko
demo_init():major=242, minor=0
# Manually create the character device file node
$ mknod /dev/chr_dev c 242 0
# Run the application program
$ ./chrdev_app
chr_open, major=242, minor=0
In chr_read()
In chr_release()
From the above test results, it can be seen that the application calling chrdev_app.c / open() will cause the driver chrdev_drv.c / struct file_operations chr_ops->open() to be called, and the read operation is similar.
How does the kernel implement the above functionality?
Understanding the framework of character device drivers with this question will help avoid getting lost in the complex code details of the kernel.
3. Abstraction of Character Devices in the Kernel
3.1 Overview of Core Character Device Code
Before delving into various codes, it is effective to get an overall view of the program files involved, identifying the core structure to avoid getting bogged down in complex code details.
1) Breakdown of C source file, fs/char_dev.c (Linux-4.14):
Function: char_dev.c is the core implementation file of the character device driver framework, located in the fs directory, indicating that character device drivers are closely related to the file system.
Content (sorted by importance):1> Public functions:
// 1. Character device subsystem initialization
void __init chrdev_init(void)
// 2. Related to struct cdev management
void chrdev_show(struct seq_file *f, off_t offset)
void cdev_put(struct cdev *p)
void cd_forget(struct inode *inode)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
void cdev_set_parent(struct cdev *p, struct kobject *kobj)
int cdev_device_add(struct cdev *cdev, struct device *dev)
void cdev_device_del(struct cdev *cdev, struct device *dev)
void cdev_del(struct cdev *p)
struct cdev *cdev_alloc(void)
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
// 3. Related to device number management
__register_chrdev_region(unsigned int major, unsigned int baseminor,
__unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
int __register_chrdev(unsigned int major, unsigned int baseminor,
void unregister_chrdev_region(dev_t from, unsigned count)
void __unregister_chrdev(unsigned int major, unsigned int baseminor,
-
In the Linux code, function names starting with double underscores (__) indicate low-level interfaces and should be used with caution. If you call this function, make sure you know what you are doing. In other words, when reading the code, functions starting with double underscores can be temporarily set aside.
-
Related to struct cdev management: cdev_add() / cdev_del() / cdev_init(), should be focused on.
-
Related to device number management: alloc_chrdev_region() / register_chrdev_region(), should be focused on.
2> Public variables:
const struct file_operations def_chr_fops = {
.open = chrdev_open,
.llseek = noop_llseek,
};
- Similar to the polymorphism mechanism in high-level languages, in the driver framework of various subsystems in Linux, a unified file_operations->open function is generally associated with the struct file_operations of each specific hardware driver, which should be focused on.
3> Private variables:
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
static struct kobj_map *cdev_map;
static struct kobj_type ktype_cdev_default;
static struct kobj_type ktype_cdev_dynamic;
-
The struct char_device_struct *chrdevs[] array is the core data structure of the character device driver framework and should be focused on.
4> Private functions:
static inline int major_to_index(unsigned major)
static int find_dynamic_major(void)
static struct kobject *cdev_get(struct cdev *p)
static int chrdev_open(struct inode *inode, struct file *filp)
static void cdev_purge(struct cdev *cdev)
static struct kobject *exact_match(dev_t dev, int *part, void *data)
static int exact_lock(dev_t dev, void *data)
static void cdev_unmap(dev_t dev, unsigned count)
static void cdev_default_release(struct kobject *kobj)
static void cdev_dynamic_release(struct kobject *kobj)
static struct kobject *base_probe(dev_t dev, int *part, void *data)
- Helper functions, not very important.
2) Breakdown of C header file, include/linux/cdev.h (Linux-4.14):
Function: Contains definitions of structures related to character device drivers and declarations of some core character device APIs.
Content:1> struct cdev structure:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
-
struct cdev is the core abstraction of character device drivers and should be focused on.
3.2 Abstraction of Character Devices: struct cdev
Writing character device drivers is about managing and controlling character devices. The Linux kernel abstracts character devices into a data structure: struct cdev. This structure seems to have remained unchanged from Linux-2.6 to the current Linux-5.8.
1) Overview of struct cdev members:
Currently, it is not necessary to fully understand the function of these members; just having a general impression is sufficient:
-
struct kobject kobj: An embedded kernel object related to the Linux device driver model, which will be introduced in a dedicated article later.
-
struct module *owner: A pointer to the module that owns this structure, used to prevent the module from being unloaded while its operations are still in use, generally initialized to THIS_MODULE;
-
struct file_operations *ops: The file operation function set for character devices under the Linux generic file model, which will be detailed later.
-
struct list_head list: Used to manage the linked list of struct cdev, which will also be introduced in a dedicated article later.
-
The device number of the character device, composed of a major device number and a minor device number, which will also be introduced in a dedicated article later.
2) Two ways to create struct cdev objects:
The term “object” is used here to guide everyone to view the design of the Linux kernel from an object-oriented perspective.
Procedural vs. object-oriented programming should not be tied to language; they should be understood as two different programming mindsets. The human brain is wonderful; in different scenarios, as long as you are willing, it can apply different ways of thinking to solve problems. The designers of Linux kernel code are all masters of object-oriented programming. Practicing programming should involve practicing the ability to abstract things; if C programmers feel they lack this ability, they might as well learn Java programming.
Static Definition:
static struct cdev chr_dev;
Dynamic Allocation:
struct cdev *my_cdev = cdev_alloc();
cdev_alloc() not only allocates memory space for the struct cdev object but also performs necessary initialization for that object:
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
One noteworthy point: I searched the kernel source code and found that most drivers choose to statically define struct cdev, which is simpler and more convenient.
3) Abstraction of a real character hardware:
The data structure struct cdev serves as an abstraction of character devices, merely to meet the structural design needs of the Linux kernel for character device driver programs.
In reality, the data structure abstraction of a specific character hardware device is often much more complex. In such cases, struct cdev often appears as an embedded member variable in the actual device’s data structure.
For example, in drvier/watchdog/watchdog_dev.c:
struct watchdog_core_data {
struct kref kref;
struct cdev cdev;
struct watchdog_device *wdd;
struct mutex lock;
unsigned long last_keepalive;
unsigned long last_hw_keepalive;
struct delayed_work work;
unsigned long status; /* Internal status bits */
};
4) Initializing cdev objects:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
The most important function of cdev_init() is to bind the struct cdev object with the struct file_operations object.
Some noteworthy points:
-
cdev_init() and cdev_alloc() have some overlapping functions (e.g., kobject_init()), so cdev_init() can only be used with statically defined struct cdev.
-
If using cdev_alloc() to dynamically allocate struct cdev objects, you need to manually set cdev->ops = fops.
3.3 Abstraction of Operations on Character Devices: struct file_operations
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
[...]
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
[...];
} __randomize_layout;
-
Currently, it is not necessary to fully understand the function of these members; just having a general impression is sufficient.
-
A critical data structure in character device drivers, the writing of character device drivers revolves around how to implement the function pointer members in struct file_operations.
-
Through the kernel file system components acting as intermediaries, calls to file-related functions (open, read, write) in application programs will ultimately be routed to the specific implementations of the function pointers in struct file_operations, which will be detailed in a dedicated article later.
Given that most people’s attention cannot be concentrated for too long in one article, more content will be placed in subsequent articles. It is recommended that everyone read related books independently, as things not understood by oneself cannot be digested.
4. More Knowledge Points Worth Learning
-
Composition and management of character device numbers
-
Registration of character devices
-
What methods are there to generate character device files?
-
What happens when creating character device files?
-
How are character device files associated with character device drivers?
-
Analyzing some real character device drivers
5. Related References
-
“Linux Kernel Documentation”
-
“Linux Device Drivers” (LDD) / Chapter 3
-
“Inside Linux Device Driver Kernel Mechanisms” (ILDD) / Chapter 2
-
“Mastering Linux Device Driver Development” (ELDD) / Chapter 2
-
“Detailed Explanation of Linux Device Driver Development” (LDDS) / Chapter 6
-
“In-depth Linux Kernel Architecture” (PLKA) / Chapter 6
-
“Complete Manual for Embedded Application Development”
# Recommended Reading: Collection|Summary of Linux Articles Collection|Programming Life Collection|C Language
Embedded Linux Scan the QR code to follow my public account