Article directory
foreword
In the family of Linux device drivers, the character device driver is a relatively simple driver, but also a very widely used driver. Therefore, learning character device drivers is very important for building the knowledge structure of Linux device drivers. This blog will lead readers to write a complete character device driver.
Character Device Driver Framework
This section provides a brief analysis of the character device driver framework. There are many very important concepts in the character device driver, the following will start with the simplest concept: character device and block device.
character and block devices
The Linux system divides devices into three categories: character devices, block devices, and network interface devices. Among them, it is difficult to distinguish between character devices and block devices, and an important explanation will be given below.
1、字符设备
字符设备是指那些只能一个字节一个字节读写数据的设备
, cannot randomly read a certain data in the device memory. It needs to read data in sequence. From this point of view, character devices are data stream-oriented devices. Common characters include devices such as mouse, keyboard, serial port, console and LED.
2、块设备
块设备是指那些可以从设备的任意位置读取一定长度数据的设备
. It does not need to read data in sequence, and can locate a specific location of the device to read data. Common block devices include hard disks, magnetic disks, U disks, SD cards, etc.
3、字符设备和块设备的区分
Each character device or block device /dev
corresponds to a device file in the directory. Readers can /dev
distinguish whether the device is a character device or a block device by viewing the attributes of the files in the directory. Use cd
the command to enter /dev
the directory and execute ls -l
the command to view the properties of the device.
ls -l
The first character c in the first field of the command indicates that the device is a character device, and b indicates that the device is a block device. Field 234 is not relevant for driver development. Fields 5 and 6 represent the major device number and minor device number of the device respectively, which will be explained later. The seventh field indicates the last modification time of the file. The eighth field represents the name of the device.
It can be seen from the 1st and 8th fields that adsp
it is a character device and dm-0
a block device. The adsp
major device number of the device is 14, and the minor device number is 12.
major and minor numbers
A character or block device has a major and minor number. The major device number and the minor device number are collectively referred to as 设备号
. The major number is used to identify a specific driver. The minor number is used to denote each device using the driver. For example, an embedded system has two LED indicators, and the LED lights need to be turned on or off independently. Then, you can write a character device driver for LED lights, and you can register its major device number as device No. 5, and its minor device numbers are 1 and 2 respectively. Here, the minor device numbers represent two LED lights respectively.
1、主设备号和次设备的表示
In the Linux kernel, dev_t
types are used to represent device numbers. In Linux2.6.29.4, dev_t
it is defined as an unsigned long integer variable, as follows:
typedef u_long dev_t
u_long
It is 4 bytes on a 32-bit machine and 8 bytes on a 64-bit machine. Take a 32-bit machine as an example, where 高12位表示主设备号,低20位表示次设备号
, as follows:
2、主设备号和次设备号的获取
In order to write a portable driver, the number of bits in the major and minor device numbers cannot be assumed. In different models, the number of digits of the major device number and the minor device number may be different. A macro should be used MAJOR
to get the major number and MINOR
a macro to get the minor number. Here are the definitions of the two macros:
#define MINORBITS 20 /*次设备号位数*/
#define MINORMASK ((1U << MINORBITES) - 1) /*次设备号掩码*/
#define MAJOR(dev) ((unsigned int)((dev) >> MINORBITS)) /*dev右移20位得到主设备号*/
#define mINOR(dev) ((unsigned int)((dev) & MINORMASK)) /*与次设备掩码与,得到次设备号*/
MAJOR
The macro will dev_t
move 20 bits to the right to get the major device number; MAJOR
the macro will dev_t
clear the upper 12 bits to get the minor device number. Instead, the major and minor numbers can be converted to device types ( dev_t
), MKDEV
which can be done using macros.
#define MKDEV(ma, mi) (((ma) << MINORBITS) | (mi))
MKDEV
The macro shifts the major device number (ma) to the left by 20 bits, and then ANDs it with the minor device number (mi) to obtain the device number.
3、静态分配设备号
Statically assigning a device number means that the driver developer statically assigns a device number. For some commonly used devices, kernel developers have assigned device numbers to them. These device numbers can documentation/devices.txt
be found in the kernel source files. If only the developer himself uses these device drivers, he can choose an unused device number. When no new hardware is added, this method will not cause device number conflicts. However, when new hardware is added, it is likely to cause device conflicts and affect the use of the device.
4、动态分配设备号
Due to the problem of conflicts in statically allocating device numbers, the kernel community recommends that developers use the method of dynamically allocating device numbers. The function of dynamically allocating device numbers is alloc_chrdev_region()
described in the section "Applying and releasing device numbers".
5、查看设备号
When statically assigning device numbers, it is necessary to check the existing device numbers in the system to determine which new device number to use. You can read /proc/devices
the file to get the device number of the device. /proc/devices
The file contains device numbers for character devices and block devices. As follows.
Apply for and release device number
The kernel maintains a special data structure used to store the relationship between device numbers and devices. When installing a device, you should apply for a device number for the device so that the system can specify the device number corresponding to the device. Many functions in the device driver operate the device through the device number. In the following, firstly, the device number application is briefly described.
1、申请设备号
Before building a character device, one or more device numbers must be applied to the system. The function that does the job is defined register_chrdev_region()
in <fs/char_dev.c>
:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
where from
is the starting value of the range of device numbers to be allocated. Generally only from
the major device number is provided, from
and the minor device number is usually set to 0. count
It is the number of consecutive device numbers that need to be applied for. The last name
is the device name associated with that range number, which cannot be exceeded 64字节
.
Like most kernel functions, register_chrdev_region()
the function returns 0 on success. On error, a negative error code is returned, and no device number can be assigned to a character device. The following is an example code, which applies for CS5535_GPIO_COUNT
a device number.
retval = register_chrdev_region(dev_id, CS5535_GPIO_COUNT, NAME);
There are a lot of character devices in Linux, and conflicts are likely to occur when artificially assigning device numbers to character devices. Linux kernel developers have been working hard to make device numbers dynamic. alloc_chrdev_region()
Functions can be used for this purpose.
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
In the above function, dev
as an output parameter, the allocated device number will be saved after the function returns successfully. The function may apply for a continuous device number, which is dev
the first device number returned. baseminor
Indicates the first minor device number to apply for, which is usually set to 0. count
Same as the corresponding parameter name
to register_chrdev_region()
the function. count
Indicates the number of consecutive device numbers to apply for, and name
indicates the name of the device. The following is an example code that applies for CS5535_CPIO_COUNT device numbers.
retval = alloc_chrdev_region(&dev_id, 0, CS5535_GPIO_COUNT, NAME);
2、释放设备号
The device number applied for by the above two methods should be released when the device is not in use. The release of the device number uses the following functions uniformly:
void unregister_chrdev_region(dev_t from, unsigned count);
In the above function, from
it indicates the device number to be released, and count
indicates from
the number of device numbers to be released from the beginning. Typically, the function is called within the module's unload function unregister_chrdev_region()
.
First understanding of cdev structure
After applying for the device number of the character device, at this time, the character device needs to be registered in the system before the character device can be used. In order to understand this implementation process, first explain cdev
the structure.
cdev structure
cdev
Character devices are described using structures in the Linux kernel . This structure is an abstraction of all character devices, which contains a large number of common features of character devices. cdev
The structure is defined as follows:
struct cdev{
struct kobject kobj; /*内嵌的kobject结构,用于内核设备驱动模型的管理*/
struct module *owner; /*指向包含该结构的模块的指针,用于引用计数*/
const struct file_operations *ops; /*指向字符设备操作函数集的指针*/
struct list_head list; /*该结构将使用该驱动的字符设备连接成一个链表*/
dev_t dev; /*该字符设备的起始设备号,一个设备可能有多个设备号*/
unsigned int count; /*使用该字符设备驱动的设备数量*/
};
cdev
The structure in the structure kobj
is used by the kernel to manage character devices, and driver developers generally do not use this member. ops
is file_operations
a pointer to a structure that defines functions for manipulating character devices. Due to the complexity of this structure, it will be explained in the next section.
dev
It is used to store the device number applied for by the character device. count
Indicates how many character devices are currently using the driver. When using rmmod
the unload module, if count
the member is not 0, then the system does not allow the unload module.
list
The structure is a doubly linked list, which is used to connect other structures into a doubly linked list. This structure is widely used in the Linux kernel1 and needs to be mastered by the reader.
struct list_head{
struct list_head *next, *prev;
};
As shown above, the members cdev
of the structure list
are connected to the members inode
of the structure i_devices
. Which i_devices
is also a list_head
structure. In this way, cdev
the structure and inode
nodes form a doubly linked list. inode
The structure represents /dev
the device files under the directory. This structure is relatively complicated, and its jurisdiction will be described below.
Each character device /dev
has a device file in the directory, and opening the device file is equivalent to opening the corresponding character device. For example, if the application program opens device file A, then the system will generate a inode
node. In this way, the character structure can be found through the field inode
of the node . Through the pointer, you can find the operation function of device A. The explanation of the operation function will be placed in the following content.i_cdev
c_dev
cdev
ops
file_operations structure
file_operations
It is an abstract structure that operates on the device. The design of the Linux kernel is very clever. The kernel allows a device file to be created for the device, and all operations on the device file are equivalent to operations on the device. The advantage of this is that the user program can use the method of accessing ordinary files to access device files, and then access the device. This method greatly reduces the programmer's programming burden, and the programmer can access the device without getting familiar with the new driver interface.
Access to ordinary files often uses open()、read()、write()、close()、ioctl()
methods such as . Similarly, access to device files can also use these methods. These calls eventually lead to file_operations
calls to the corresponding functions in the structure. For programmers, it is enough to write different operation functions for different devices.
In order to increase file_operations
the functionality, many functions are concentrated in this structure. The definition of this structure is already relatively large, and its definition is as follows:
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int , unsigned long);
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 *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, 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 **);
};
file_operations
The important members of the structure are explained below .
owner
member isn't a function at all; it's a pointer to the module that owns the struct. This member is used to maintain the reference count of the module. When the module is still in use, it cannot be used tormmod
unload the module. Almost all the time, it is simply initialized toTHIS_MODULE
a<linux/module.h>
macro defined in .llseek()
The function is used to change the current read/write position in the file and return the new position.loff_t
The argument is a "long long" type, which is 64 bits wide even on 32-bit machines. This is for compatibility with 64-bit machines, because the file size of 64-bit machines can completely exceed 4G.read()
The function is used to get data from the device, the function returns the number of bytes read when successful, and returns an error code when it fails.write()
The function is used to write data to the device. The function returns the number of bytes written on success, or a negative error code on failure.ioctl
Functions provide a way to execute device-specific commands. For example, to reset the device, which is neither a read operation nor a write operation, is not suitable for implementationread()
with andwrite()
methods.ioctl
If an undefined command is passed in to the application ,-ENOTTY
an error will be returned, indicating that the device does not support this command.open()
The function is used to open a device, in which the device can be initialized. If this function is copied NULL, then the device will always be successfully opened and will not affect the device.release()
The function is used to releaseopen()
the resources requested in the function, and will be called by the system when the file reference count is 0. It corresponds to the method of the applicationclose()
, but not every timeclose()
the method is called, the function will be triggeredrelease()
, and it will be called only after all the device files are released.
The relationship between cdev and file_operation structure
Generally speaking, the driver developer will put the specific data of a specific device into cdev
the structure to form a new structure. As shown in the figure below, the "custom character device" contains the data of a specific device. cdev
There is a structure in this "custom device" . cdev
The structure has a file_operations
pointer to it. Here, file_operations
the function in can be used to operate the hardware, or other data in the "custom character device", so as to play the role of controlling the device.
inode structure
The kernel uses inode
structures to represent files internally. It is generally passed inode
as a parameter of a function in a structure. file_operation
For example, open()
the function will pass a inode
pointer in, indicating the currently open file node. It should be noted that inode
the members have been given appropriate values by the system, and the driver only needs to use the information in the node without changing it. open()
The function is:
int (*open) (struct inode *, struct file *);
inode
The structure contains a lot of information about the file. Here, only the fields that are useful for writing drivers are introduced. For more information about this structure, you can refer to the kernel source code.
dev_t i_rdev
, indicating the device number corresponding to the device file.struct list_head i_devices
, this member connects the device file to the correspondingcdev
structure, thus corresponding to its own driver.struct cdev *i_cdev
This member also points tocdev
the device.
In addition todev_t
getting the major device number and minor device number from it, you can also use theimajor()
andiminor()
function to get the major device number and minor device numberi_rdev
from it.
imajor()
The function calls the macro internallyMAJOR
, as shown in the following code.
static inline unsigned imajor(const struct inode *inode)
{
return MAJOR(inode->i_rdev); /*从inode->i_rdev中提取主设备号*/
}
Likewise, iminor()
the function calls the macro internally MINOR
, as shown in the following code.
static inline unsigned iminor(const struct inode *inode)
{
return MINOR(inode->i_rdev); /*从inode->i_rdev中提取次设备号*/
}
Composition of character device drivers
Understanding the composition of character device drivers is very useful for writing drivers. Because character devices have many similarities in structure, as long as you can write a character device driver, it is not difficult to write a similar character device driver. In the Linux system, the character device driver consists of the following parts.
Character device load and unload functions
In the loading function of the character device, the application and cdev
registration of the character device number should be realized. cdev
On the contrary, the release and logout of the character device number should be implemented in the unload function of the character device .
cdev
It is an abstraction of character devices by kernel developers. In addition to cdev
the information in, a specific character device also needs specific information, and the specific information is often placed cdev
after it to form a device structure, such as in the code xxx_dev
.
The common device structure, loading function and unloading function are as follows:
struct xxx_dev /*自定义设备结构体*/
{
struct cdev dev; /*cdev结构体*/
... /*特定设备的特定数据*/
};
static int __init xxx_init(void)
{
...
/*申请设备号,当xxx_major不为0时,表示静态指定;当为0时,表示动态申请*/
if(xxx_major)
result = register_chrdev_region(xxx_devno, 1, "DEV_NAME"); /*静态申请*/
else
result = alloc_chrdev_region(&xxx_devno, 0, 1, "DEV_NAME"); /*动态申请*/
xxx_major = MAJOR(xxx_devno); /*获取申请的主设备号*/
/*初始化cdev结构,并传递file_operations结构指针*/
cdev_init(&xxx_dev.cdev, &xxx_fops);
dev->cdev.owner = THIS_MODULE; /*指定所属模块*/
err = cdev_add(&xxx_dev.cdev, xxx_devno, 1); /*注册设备*/
}
static void __exit xxx_exit(void) /*模块卸载函数*/
{
cdev_del(&xxx_dev.cdev); /*注销cdev*/
unregister_chrdev_region(xxx_devno, 1); /*释放设备号*/
}
file_operations structure and other member functions
file_operations
The member functions in the structure correspond to the interface of the driver program, and the user program can call these interfaces through the kernel to control the device. Most character device drivers will implement read()、write()和ioctl()
functions, and the common writing methods of these three functions are shown in the following code.
/*文件操作结构体*/
static const struct file_operations xxx_fops =
{
.owner = THIS_MODULE, /*模块引用,任何时候都赋值THIS_MODULE*/
.read = xxx_read, /*指定设备的读函数*/
.write = xxx_write, /*指定设备的写函数*/
.ioctl = xxx_ioctl /*指定设备的控制函数*/
};
/*读函数*/
static ssize_t xxx_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
...
if(size>8)
copy_to_user(buf,...,...); /*当数据较大时,使用copy_to_user(),效率较高*/
else
put_user(....,buf); /*当数据较小时,使用put_user(),效率较高*/
....
}
/*写函数*/
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
...
if(size>8)
copy_from_user(...,buf,...); /*当数据较大时,使用copy_to_user(),效率较高*/
else
get_user(...,buf); /*当数据较小时,使用put_user(),效率较高*/
....
}
/*ioctl设备控制函数*/
static long xxx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
...
switch(cmd)
{
case xxx_cmd1:
... /*命令1执行的操作*/
break;
case xxx_cmd2:
... /*命令2执行的操作*/
break;
default:
return -EINVAL; /*内核和驱动程序都不支持该命令时,返回无效的命令*/
}
return 0;
}
xxx_fops
The pointer to the operation function is saved in the file operation structure . For functions that are not implemented, it is assigned a value of NULL
. xxx_fops
The structure is associated cdev_init()
with the character device loading function as a parameter . Device driver functions have the same parameters. It is a pointer to the file structure, pointing to the opened file. is the data address from user space, which cannot be read directly in the driver. is the bytes to read. Is the position for reading and writing, which is equivalent to the beginning of the file. The parameter of the control function is a pre-defined I/O control command, and arg corresponds to the parameter of the command.cdev
read()和write()
filp
buf
size
ppos
xxx_ioctl
cmd
Data exchange between driver and application
Data exchange between drivers and applications is very important. file_operations
The functions in read()和write()
are used to exchange data between the driver and the application. Through data exchange, drivers and applications can learn about each other. But drivers and applications belong to different address spaces. The driver cannot directly access the address space of the application program; similarly, the application program cannot directly access the address space of the driver program, otherwise the data in each other's space will be destroyed, resulting in system crash or data destruction.
The safe method is to use the dedicated function provided by the kernel to complete the exchange of data between the application program and the driver program space. These functions strictly check and convert the pointer passed by the user program, so as to ensure the security of data exchange between the user program and the driver program. These functions are:
unsigned long copy_to_user(void __user *to, const void *from, unsigned long);
unsigned long copy_from_user(void *to, const void __user *from, unsigned long);
put_user(local, user);
get_user(local, user);
Character device driver composition summary
A character device is a relatively simple type of device among the three major types of devices (character device, block device, network device). The main work done in its driver is to initialize, add and delete structures, apply for and release device numbers, and cdev
fill file_operation
Operate functions in the structure, and implement other important functions file_operation
in the structure . read()、write()、ioctl()
The relationship between the cdev structure file_operations
and the user space call driver is shown in the figure below.
VirtualDisk character device driver
The following parts will be explained based on a VirtualDisk device. VirtualDisk is a virtual disk device, in which 8K continuous memory space is allocated, and two port data (port1 and port2) are defined. The driver program can read, write, control and locate the device, and the program in the user space can access the data in the VirtualDisk device through the Linux system call.
VirtualDisk header files, macros and device structures
The VirtualDisk driver should include necessary header files and macro information, and define a device structure corresponding to the actual device. The related definitions are shown in the following code.
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#define VIRTUALDISK_SIZE 0x2000 /*全局内存最大8K字节*/
#define MEM_CLEAR 0x1 /*全局内存清零*/
#define PORT1_SET 0x2 /*将port1端口清零*/
#define PORT2_SET 0x3 /*将port2端口清零*/
#define VIRTUALDISK_MAJOR 200 /*预设的VitrualDisk的主设备号为200*/
static int VirtualDisk_major = VIRTUALDISK_MAJOR;
/*VirtualDisk设备结构体*/
struct VirtualDisk
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[VIRTUALDISK_SIZE]; /*全局内存8K*/
int port1; /*两个不同类型的端口*/
long port2;
long count; /*记录设备目前被多少设备打开*/
};
- Lines 1 to 11 list the necessary header files, which contain functions that may be used by the driver.
- 19~26 lines of code define the VirtualDisk device structure. It contains
cdev
the character device structure and a continuous 8K device memory.port1
In addition, two ports and are definedport2
, which are used to simulate the ports of actual devices.count
Indicates the number of times the device was opened. In the driver program, it is not necessary to put these members in a structure, but the benefit of putting them together is to use the object-oriented encapsulation idea to encapsulate the device-related members into a whole. - Line 22 defines an 8K memory block. The driver generally does not allocate memory statically, because the life cycle of statically allocated memory is very long, and it lives and dies with the driver. The driver generally runs in the entire boot state of the system, so the memory allocated by the driver will never be released. Therefore, when writing a driver, you should avoid applying for large blocks of memory and statically allocating memory. Here, just for the convenience of demonstration, static memory is allocated.
Load and unload drivers
The loading and unloading templates of character device drivers have been introduced above. The loading and unloading functions of VirtualDisk are also similar to those described above, and their implementation is as follows:
/*设备驱动模型加载函数*/
int VirtualDisk_init(void)
{
int result;
dev_t devno = MKDEV(VirtualDisk_major, 0); /*构建设备号*/
/*申请设备号*/
if(VirtualDisk_major)
result = register_chardev_region(devno, 1, "VirtualDisk");
else
result = alloc_chrdev_region(&devno, 0, 1, "VirtualDisk");
VirtualDisk_major = MAJOR(devno); /*从申请设备号中得到主设备号*/
if(result < 0)
return result;
/*动态申请设备结构体的内存*/
struct VirtualDisk *Virtualdisk_devp = kmalloc(sizeof(struct VitualDisk), GFP_KERNEL);
if(!Vitualdisk_devp) /*申请失败*/
{
result = -ENOMEM;
goto fail_kmalloc;
}
memset(Virtualdisk_devp, 0, sizeof(struct VirtualDisk));/*清零*/
/*初始化并且添加cdev结构体*/
VirtualDisk_setup_cdev(Virtualdisk_devp, 0);
return 0;
fail_kmalloc:
unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
void VirtualDisk_exit(void)
{
cdev_del(&Vitualdisk_devp->cdev); /*注销cdev*/
kfree(Virtualdisk_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1); /*释放设备号*/
}
- Lines 7~13, use two methods to apply for the device number.
VirtualDisk_major
The variable is statically defined as 200. Do not set equal to 0 when loading modulesVirtualDisk_major
. Then executeregister_chrdev_region()
the function to statically allocate a device number; if itVirtualDisk_major
is equal to 0, then usealloc_chrdev_region()
the function to dynamically allocate a device number anddevno
return it by the parameter. Line 12, useMAJOR
the macro to return the obtained major device number. - Lines 17~22 allocate a VirtualDisk device structure.
- Line 23, clear the allocated VirtualDisk device structure.
- Line 25, call a custom
VirtualDisk_setup_cdev()
function to initializecdev
the structure and add it to the kernel. This function will be described below. cdev
Lines 32~37 are the unloading function. In this function, the structure is canceled ,VirtualDisk
the memory occupied by the device is released, and the device number occupied by the device is released.
Initialization and registration of cdev
The function called in the previous code VirtualDisk_setup_cdev()
completes the initialization and registration of cdev, and its code is as follows:
/*初始化并注册cdev*/
static void VirtualDisk_setup_cdev(struct VirutalDisk *dev, int minor)
{
int err;
devno = MKDEV(VirtualDisk_major, minor); /*构造设备号*/
cdev_init(&dev->cdev, &VirtualDisk_fops); /*初始化cdev设备*/
dev->cdev.owner = THIS_MODULE; /*使驱动程序属于该模块*/
dev->cdev.ops = &VirtualDisk_fops; /*cdev连接file_operations指针*/
err = cdev_add(&dev->cdev, devno, 1); /*将cdev注册到系统中*/
if(err)
printk(KERNEL_NOTICE "Error in cdev_add()\n");
}
Here is a brief explanation of the function:
- Line 5, use
MKDEV
a macro to construct a device number with a major device number ofVirtualDisk_major
, and a minor deviceminor
number of - Line 6 calls
cdev_init()
the function and associates the device structurecdev
withfile_operations
the pointer. This file operation pointer is defined as shown in the following code.
/*文件操作结构体*/
static const struct file_operation VirtualDisk_fops =
{
.owner = THIS_MODULE,
.llseek = VirtualDisk_llseek, /*定位偏移量函数*/
.read = VirtualDisk_read, /*读设备函数*/
.write = VirtualDisk_write, /*写设备函数*/
.ioctl = VirtualDisk_ioctl, /*控制函数*/
.open = VirtualDisk_open, /*打开设备函数*/
.release = VirtualDisk_release, /*释放设备函数*/
};
- Line 8, designated
VirtualDisk_fops
as the file operation function pointer of the character device. - Line 9, call
cdev_add
the function to add the character device to the kernel. - Lines 10 and 11, if the registration of the character device fails, return.
open and release functions
When the user program calls open()
the function to open the device file, the kernel will finally call VirtualDisk_open()
the function. The code for this function is as follows:
/*文件打开函数*/
int VirutalDisk_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针*/
filp->private_data = Virtualdisk_devp;
struct VirtualDisk *devp = filp->private_data; /*获得设备结构体指针*/
devp->count++; /*增加设备打开次数*/
return 0;
}
Here is a brief explanation of the function:
- Lines 5 and 6 will
Virtualdisk_devp
be assigned to the private data pointer, which will be used later. - Line 7, increment the device open count by 1.
When the user program callsclose()
the function to close the device file, the kernel will finally callVirtualDisk_release()
the function. This function is mainly about decrementing the counter by 1. The function code is as follows.
/*文件释放函数*/
int VirtualDisk_release(struct inode *inode, struct file *filp)
{
struct VirtualDisk *devp = filp->private_data; /*获得设备结构体指针*/
devp->count--; /*减少设备打开次数*/
return 0;
}
read and write functions
When the user program calls read()
the function to read the data in the device file, the kernel will finally call VirtualDisk_read()
the function. The code for this function is as follows:
/*读函数*/
static ssize_t VirtualDisk_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos; /*记录文件指针偏移位置*/
unsigned int count = size; /*记录需要读取的字节数*/
int ret = 0; /*返回值*/
struct VirtualDisk *devp = filp->private_data; /*获得设备结构体指针*/
/*分析和获得有效的读长度*/
if(p >= VIRTUALDISK_SIZE) /*需要读取的偏移大于设备内存空间*/
return count? -ENXIO: 0; /*读取地址错误*/
if(count > VIRTUALSIZE - p) /*要读取的字节大于设备的内存空间*/
count = VIRTUALSIZE - p; /*将要读取的字节数设为剩余的字节数*/
/*内核空间->用户空间交换数据*/
if(copy_to_user(buf, (void *)(devp->mem + p), count))
ret = -EFAULT;
else
*ppos += count;
ret = count;
printk(KERNEL_INFO "read %d bytes(s) from %d\n",count, p);
return ret;
}
The following is a brief analysis of the function
- Lines 5~7 define some local variables
- Line 8, get the device structure pointer from the file pointer.
- Line 10, it is an error if the location to read is larger than the size of the device.
- Line 12, if the position of the data to be read is greater than the size of the device, only read to the end of the device.
- Lines 15 to 24, copy data from user space to the device. If the copy data is successful, the offset position of the file is added to the number of read data.
When the user program callswrite()
the function to write data to the device file, the kernel will finally callVirtualDisk_write()
the function. The code for this function is as follows:
/*写函数*/
static ssize_t VirtualDisk_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos; /*记录文件指针偏移位置*/
int ret = 0; /*返回值*/
unsigned int count = size; /*记录需要写入的字节数*/
struct VirtualDisk *devp = file ->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if(p >= VIRTUALDISK_SIZE) /*要写入的偏移大于设备的内存空间*/
return count ? -ENXIO: 0; /*写入地址错误*/
if(count > VIRTUALDISK - p) /*要写入的字节大于设备的内存空间*/
count = VIRTUALDISK_SIZE - p; /*将要写入的字节数设为剩余的字节数*/
/*用户空间->内核空间*/
if(copy_from_user(devp->mem + p , buf, count))
ret = -EFAULT;
else
*ppos += count; /*增加偏移位置*/
ret = count; /*返回实际的写入字节数*/
printk(KERNEL_INFO "write %d bytes(s) from %d\n",count, p);
return ret;
}
The following is a brief introduction to the function
- Lines 5~7 define some local variables
- Line 8, get the device structure pointer from the file pointer.
- Line 10, error if the location to read is larger than the size of the device.
- Line 12, if the position of the data to be read is greater than the size of the device, only read to the end of the device.
- Lines 15-24, copy data from the device to user space. If the copy data is successful, the offset position of the file is added to the number of written data.
seek function
When the user program calls fssek()
the function to move the file pointer in the device file, the kernel will eventually call VirtualDisk_llseek()
the function. The code for this function is as follows:
/*seek文件定位函数*/
static loff_t VirtualDisk_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0; /*返回的位置偏移*/
switch(orig)
{
case SEEK_SET:
if(offset < 0)
{
ret = - EINVAL
break;
}
if((unsigned int )offset > VIRTUALDISK_SIZE)
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int) offset;
ret = filp->f_pos;
break;
case SEEK_CUR:
if((filp->f_pos + offset) > VIRTUALDISK_SIZE)
{
ret = - EINVAL;
break;
}
if((filp->f_pos + offset) < 0)
{
ret = - EINVAL
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = - EINVAL;
break;
}
return ret;
}
Here is a brief introduction to this function:
- Line 4 defines a return value to indicate the current offset of the file pointer.
- 5 lines, used to select the moving direction of the file pointer.
- Lines 7~20 indicate that the type of file pointer movement is
SEEK_SET
, indicating that the pointer is moved relative to the beginning of the fileoffset
. - Lines 8~12, if the offset is less than 0, an error will be returned.
- Lines 13~17, if the offset is greater than the length of the file, an error will be returned.
- Line 18, set the offset value of the file to
filp->f_pos
, this pointer indicates the current location of the file. - Lines 21 to 34 indicate that the type of file pointer movement is yes
SEEK_CUR
, indicating that the pointer is moved relative to the current position of the fileoffset
. - Lines 22~26, if the offset value is greater than the length of the file, an error will be returned.
- Lines 27~31 indicate that the pointer is less than 0, and the pointer is illegal in this case.
- Line 32, add an offset
filp->f_pos
to the offset value of the file.offset
- Lines 35 and 36 indicate that the command is not
SEEK_SET
orSEEK_CUR
. In this case, it means that an illegal command has been passed in, and it will return directly.
ioctl() function
When a user program calls ioctl()
a function to change the functionality of the device, the kernel will eventually call VirtualDisk_ioctl()
the function. The code for this function is as follows:
/*ioctl设备控制函数*/
static int VirtualDisk_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg)
{
struct VirtualDisk *devp = filp->private_date;
switch(cmd)
{
case MEM_CLEAR: /*设备内存清零*/
memset(devp->mem, 0, VIRTUALDISK_SIZE);
printk(KERNEL_INFO "VirtualDisk is set to zero\n");
break;
case PORT1_SET: /*将端口1置为0*/
devp->port1 = 0;
break;
case PORT2_SET: /*将端口2置0*/
devp->port2 = 0;
break;
default:
return -EINVAL;
}
return 0;
}
Here is a brief introduction to this function:
- Line 5, the private data of the file is obtained, and the pointer of the device is stored in the private data
VirtualDisk
. - Lines 6~20,
ioctl()
judge the operation to be performed according to the parameters passed in by the function. The character device here supports 3 operations, the first operation is to clear all the memory of the character device, the second is to set port 1 to 0, and the third is to set port 2 to 0.
summary
Describes character device drivers. Character device is one of the three major devices in Linux. Many devices can be regarded as character devices, so it is very useful to learn the programming of character device drivers. This chapter first introduces the frame structure of the character device as a whole, and then introduces the structure of the character device struct cdev
. Then it introduces the composition of the character device, and finally explains a VirtualDisk
character device driver in detail.