Article directory
1 Introduction
As we all know,Linux
the kernel mainly includes three driver models, character device driver, block device driver and network device driver.
Among them, Linux
character device driver can be said to be the most common driver model in Linux
driver development.
Our series of articles is mainly to help you get started quicklyLinux
Driver development, this article mainly focuses on understanding some character device driver frameworks and mechanisms.
Series of articles based on
Kernel 4.19
2. Key data structures
2.1 cdev
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;
Structure name:cdev
Text position:include/linux/cdev.h
Main function: cdev
can be understood as char device
, used to abstract a character device.
Core members and meaning:
kobj
: Represents a kernel object.owner
: Pointer to the moduleops
: Pointer to file operation, includingopen
,read
,write
and other operation interfaceslist
: Used to add the device to the kernel module linked listdev
: Device number, consisting of major device number and minor device numbercount
: Indicates how many devices of the same type there are, and also indirectly indicates the range of device numbers.__randomize_layout
: A compiler directive that randomizes the layout of a structure for added safety.
2.2 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 (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
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 *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
Structure name:file_operations
Text position:include/linux/fs.h
Main function: As the name suggests, it is mainly used to describe various interfaces for file operations. Linux
All ideas of connecting files, the kernel wants to Whichever file is operated needs to be implemented through these interfaces.
Core members and meaning:
open
:Function to open a fileread
: Function to read files.write
: Function for writing files.release
: Function to close the file.flush
: Function to refresh the file, usually called when closing the file.llseek
: Function that changes the position of the file read and write pointer.fsync
: Function to write file data to disk synchronously.poll
:Ask whether the file can be read and written non-blockingly
2.3 dev_t
typedef u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
Type name:dev_t
Text position:include/linux/types.h
Main function: Indicates the device number corresponding to the character device, including the major device number and the minor device number.
3. Relationship between data structures
The above figure is a diagram of the data structure of the character device driver and
API
,If you need the original files, you can get them at the public address [Embedded Art].
4. Overall architecture of character device driver
4.1 Loading and unloading functions
The first thing the driver implements is the loading and unloading functions, which are also the entry functions of the driver program.
We generally define the driver's loading and unloading functions like this:
static int __init xxx_init(void)
{
}
static void __exit xxx_exit(void)
{
}
module_init(xxx_init);
module_exit(xxx_exit);
This code is to implement the loading and unloading of a universal driver. For the implementation mechanism of module_init
and module_exit
, you can check the previous summary article.
4.2 Device number management
4.2.1 The concept of device number
Each type of character device has a unique device number, and the device number is divided into a major device number and a minor device number. So what are the functions of these two?
- Major device number: used to identify the type of device,
- Minor device number: used to distinguish different devices of the same type
To put it simply, the major device number is used to distinguish whether it is a
IIC
device or aSPI
device, while the minor device number is used to distinguish < Under the a i=3> device, which device is it? or .IIC
MPU6050
EEPROM
4.2.2 Assignment of device numbers
Understand the concept of device numbers. There are so many device numbers in
Linux
, so how do we use the correct device number?
There are two ways to allocate device numbers, one is dynamic allocation and the other is static allocation. It can also be understood as one is automatic allocation by the kernel and the other is manual allocation.
Static allocation function:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
from
: Indicates a known device numbercount
: Indicates the number of consecutive device numbers (how many devices of the same type are there)name
: Indicates the name of the device or driver
Function effect: Starting with from
device number, continuously allocate count
device numbers of the same type a>
Dynamic allocation function:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
dev
: Pointer to the device number, used to store the value of the assigned device numberbaseminor
: Starting value for allocation of minor device numbercount
: Indicates the number of consecutive device numbers (how many devices of the same type are there)name
: Indicates the name of the device or driver
Function effect: Starting from the baseminor
secondary device number, continuously allocate count
device numbers of the same type. And automatically assign a major device number, and assign the device number information composed of major and minor to*dev
The biggest difference between these two functions is:
register_chrdev_region
: Before calling, the major device number and minor device number have been predefined. After calling this interface, the customized device number will be registered and added to the subsystem to facilitate the system to track the usage of the system device number.alloc_chrdev_region
: Before calling, the major device number and minor device number are not defined; after calling, the major device number is represented by0
to be automatically assigned, and the automatically assigned device number will be It is also added to the subsystem to facilitate the system to track the usage of system device numbers.
What these two functions have in common is:
The system maintains an array list to register all used device number information. In the final analysis, these two interfaces also register their device number information into the device number list maintained by the system to avoid subsequent conflicting use.
InLinux
, we can use the cat /proc/devices
command to view the list of all device numbers registered by i.
When we have time later, we can talk in detail about the automatic allocation mechanism and management mechanism of device numbers.
4.2.3 Cancellation of device number
As a system resource, the device number must be returned to the system when the corresponding device is uninstalled. Regardless of whether it is allocated statically or dynamically, the following function will eventually be called to log out.
void unregister_chrdev_region(dev_t from, unsigned count);
from
: Indicates a known device numbercount
: Indicates the number of consecutive device numbers (how many devices of the same type are there)
Function effect: To log outfrom
consecutive devices under the major device numbercount
4.2.4 Obtaining device number
The management of device number is very simple. In the key data structure, we see that the type of device number is dev_t
, which is represented by the type u32
A numerical value.
The dividing line between the major device number and the minor device number is specified byMINORBITS
macro definition:
#define MINORBITS 20
That is, the occupation of the major device number is high12bit
, and the occupation of the minor device number is low20bit
Moreover, the kernel also provides relatedAPI
interfaces to obtain the major device number and minor device number, as well as the interface to generate the device number, as follows:
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
In the above, the primary and secondary device numbers are obtained through the shift operation.
4.2.4 Common code implementation
#define CUSTOM_DEVICE_NUM 0
#define DEVICE_NUM 1
#device DEVICE_NAME "XXXXXX"
static dev_t global_custom_major = CUSTOM_DEVICE_NUM;
static int __init xxx_init(void)
{
dev_t custom_device_number= MKDEV(global_custom_major, 0); // custom device number
/* device number register*/
if (global_custom_major) {
ret = register_chrdev_region(custom_device_number, DEVICE_NUM, DEVICE_NAME);
} else {
ret = alloc_chrdev_region(&custom_device_number, 0, DEVICE_NUM, DEVICE_NAME);
global_custom_major = MAJOR(custom_device_number);
}
}
static void __exit xxx_exit(void)
{
unregister_chrdev_region(MKDEV(global_mem_major, 0), DEVICE_NUM);
}
module_init(xxx_init);
module_exit(xxx_exit);
This function implements the allocation of device numbers. If the main device number is0
, dynamic allocation is used, otherwise static allocation is used.
More useful information can be found at:A gathering place for senior engineers to help everyone reach the next level!
4.3 Management of character devices
After understanding the management of device numbers, let's take a look at how character devices are managed.
4.3.1. Character device initialization
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
cdev
: A character device object, which is the character device we createdfops
: The file processing interface of the character device
Function function: Initialize a character device and bind the corresponding file processing pointer to the character device.
4.3.2. Character device registration
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
p
: A character device pointer, only the character device object to be addeddev
: The first device number responsible for this character devicecount
: The number of devices of this type
Function function: Add a character device driver toLinux
the system.
4.3.3. Character device logout
void cdev_del(struct cdev *p);
p
: Pointer to character device object
Function: Remove the character device driver from the system
4.4 Implementation of file operation interface
Because inLinux
, everything is a file, so each character device also has a file node corresponding to it.
When we initialize the character device, we will bind the object of struct file_operations
to the character device, and its function is to process the character deviceopen
, read
, write
and other operations.
What we have to do is to implement the functional interface we need, such as:
static const struct file_operations global_mem_fops = {
.owner = THIS_MODULE,
.llseek = global_mem_llseek,
.read = global_mem_read,
.write = global_mem_write,
.unlocked_ioctl = global_mem_ioctl,
.open = global_mem_open,
.release = global_mem_release,
};
At this point, we have a basic understanding of the framework of a basic character device driver.
5. Summary
This article aims to explain in an easy-to-understand manner:
- Character device driver related data structures
- data structure diagram
- core
API
intersection - Character device driver overall framework
I hope to be helpful.