I
In the linux kernel, the character device is described by the cdev structure, which is located in / include / linux / cdev H medium
/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _LINUX_CDEV_H #define _LINUX_CDEV_H #include <linux/kobject.h> #include <linux/kdev_t.h> #include <linux/list.h> #include <linux/device.h> struct file_operations; struct inode; struct module; struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; //List field to organize all character devices into a linked list. Each equipment is determined by the main equipment number and the secondary equipment number, dev_t dev; //dev is the device number of the character device, including the primary device number and secondary device number unsigned int count; //The count field is the number of secondary device numbers in the same primary device number } __randomize_layout; void cdev_init(struct cdev *, const struct file_operations *); struct cdev *cdev_alloc(void); void cdev_put(struct cdev *p); int cdev_add(struct cdev *, dev_t, unsigned); 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 *); void cd_forget(struct inode *); #endif
The device driver of linux can be defined in two forms: one is the global static variable, and the other is to use the API provided by the kernel. Here, the second method is used to realize the driver of a simple virtual device and its read-write function.
First look at the kernel state code
device_drive.c
# include <linux/module.h> # include <linux/fs.h> # include <linux/uaccess.h> # include <linux/init.h> # include <linux/cdev.h> # define DEMO_NAME "my_demo_dev" static dev_t dev; //Equipment number static struct cdev *demo_cdev; static signed count = 1; static int demodrv_open(struct inode *inode, struct file *file) { //The method of reading primary device number and secondary device number provided by Linux kernel int major = MAJOR(inode->i_rdev); int minor = MINOR(inode->i_rdev); printk("%s: major=%d, minor=%d\n",__func__,major,minor); //__ func__ Macro gets the current function name return 0; } static ssize_t demodrv_read(struct file *file, char __user *buf,size_t lbuf,loff_t *ppos) { printk("%s enter\n",__func__); //Print function name return 0; } static ssize_t demodrv_write(struct file *file, const char __user *buf,size_t count,loff_t *f_pos) { printk("%s enter\n",__func__); return 0; } //The operation of the device is the same structure as that used in the file system static const struct file_operations demodrv_fops = { .owner = THIS_MODULE, .open = demodrv_open, .read = demodrv_read, .write = demodrv_write }; static int __init simple_char_init(void) { int ret; ret = alloc_chrdev_region(&dev,0,count,DEMO_NAME); if(ret) { printk("failed to allocate char device region\n"); return ret; } demo_cdev = cdev_alloc(); //Allocate space if(!demo_cdev) { printk("cdev_alloc failed\n"); goto unregister_chrdev; } cdev_init(demo_cdev,&demodrv_fops); ret = cdev_add(demo_cdev,dev,count); if(ret) { printk("cdev_add failed\n"); goto cdev_fail; } printk("successed register char device: %s\n",DEMO_NAME); printk("Major number = %d,minor number = %d\n",MAJOR(dev),MINOR(dev)); return 0; cdev_fail: cdev_del(demo_cdev); unregister_chrdev: unregister_chrdev_region(dev,count); return ret; } static void __exit simple_char_exit(void) { printk("removing device\n"); if(demo_cdev) cdev_del(demo_cdev); unregister_chrdev_region(dev,count); } module_init(simple_char_init); module_exit(simple_char_exit); MODULE_LICENSE("GPL");
The kernel module initialization function executes alloc_chrdev_region function, enter the source code, located in fs/char_dev.c.
/** * alloc_chrdev_region() - register a range of char device numbers * @dev: output parameter for first assigned number * @baseminor: first of the requested range of minor numbers * @count: the number of minor numbers required * @name: the name of the associated device or driver * * Allocates a range of char device numbers. The major number will be * chosen dynamically, and returned (along with the first minor number) * in @dev. Returns zero or a negative error code. */ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) { struct char_device_struct *cd; cd = __register_chrdev_region(0, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); *dev = MKDEV(cd->major, cd->baseminor); return 0; }
It will be executed later__ register_chrdev_region function. The first parameter is 0, and the main equipment number is automatically assigned.
Then use cdev_alloc function to allocate space. The type defined here is struct cdev *
Next, CDEV will be executed_ Init and perform the assignment operation of fops
Enter the source code and take a look: cedv_alloc allocates space and returns a pointer to the CDEV structure. cdev_init initializes CDEV and assigns fops one more step
If you define a struct cdev structure instead of a pointer type, you only need to execute cdev_init() is OK
/** * cdev_alloc() - allocate a cdev structure * * Allocates and returns a cdev structure, or NULL on failure. */ 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; } /** * cdev_init() - initialize a cdev structure * @cdev: the structure to initialize * @fops: the file_operations for this device * * Initializes @cdev, remembering @fops, making it ready to add to the * system with cdev_add(). */ 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; }
Next, execute cdev_add to add this device to the system.
In the implementation method, we are in demodrv_ Print the primary and secondary device numbers in the open operation
In demodrv_read and demodrv_ Print only function names in write
Makefile
#Makefile file note: if the previous C file named first c. So here's the makefile file o Wen #It's going to be called first O only the root user can load and unload modules obj-m:=device_drive.o #Generate device_ Object file of drive module #The object file should be the same as the module name CURRENT_PATH:=$(shell pwd) #Current path of the module LINUX_KERNEL:=$(shell uname -r) #Current version of linux kernel code LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #Compilation module #[Tab] the path of the kernel where the current directory is compiled indicates that the kernel module is compiled clean: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #Cleaning module
Compile and insert kernel modules
Using dmesg to view kernel messages
Here you can see that the character device has been successfully registered, my_demo_dev is the device name, the primary device number is 243, and the secondary device number is 0
In addition, the generated device needs to generate the corresponding node in the / dev directory, which needs to be generated manually
Using the mknod command
mknod /dev/demo_drv c 243 0
c stands for character equipment, the main equipment number is 243, and the secondary equipment number is 0
Then check the / dev directory
here
Next, use the user space test program to test the character device driver
User space is a program for testing character equipment c
# include <stdio.h> # include <fcntl.h> # include <unistd.h> # define DEMO_DEV_NAME "/dev/demo_drv" int main() { char buffer[64]; int fd; fd = open(DEMO_DEV_NAME,O_RDONLY); if(fd<0) { printf("open device %s failed\n",DEMO_DEV_NAME); return -1; } read(fd,buffer,64); close(fd); return 0; }
Define the path of the device in this test file
Perform an open operation, and the read operation only prints the function name
Compile and execute the user test program
Print kernel messages using dmesg
Print out the methods of open and read
II
Character device drivers can also be registered using misc mechanism, that is, Linux divides some character devices that do not meet the predetermined characters into miscellaneous devices. The main device number of such devices is 10, which is described by miscdevice structure in the kernel
If you use the misc mechanism to create a device, you need to define the miscdevice structure. Let's take a look at the second experiment
Kernel module
drive2.c
# include <linux/module.h> # include <linux/fs.h> # include <linux/uaccess.h> # include <linux/init.h> # include <linux/cdev.h> //Adding misc mechanism # include <linux/miscdevice.h> # include <linux/kfifo.h> DEFINE_KFIFO(mydemo_fifo,char,64); //Device name # define DEMO_NAME "my_demo_dev" static struct device *mydemodrv_device; static int demodrv_open(struct inode *inode, struct file *file) { int major = MAJOR(inode->i_rdev); int minor = MINOR(inode->i_rdev); printk("%s: major=%d, minor=%d\n",__func__,major,minor); return 0; } static ssize_t demodrv_read(struct file *file, char __user *buf,size_t count,loff_t *ppos) { int actual_readed; int ret; ret = kfifo_to_user(&mydemo_fifo,buf, count, &actual_readed); if(ret) return -EIO; printk("%s,actual_readed=%d,pos=%lld\n",__func__,actual_readed,*ppos); return actual_readed; } static ssize_t demodrv_write(struct file *file, const char __user *buf,size_t count,loff_t *ppos) { unsigned int actual_write; int ret; ret = kfifo_from_user(&mydemo_fifo,buf, count, &actual_write); if(ret) return -EIO; printk("%s: actual_write=%d,ppos=%lld\n",__func__,actual_write,*ppos); return actual_write; } static const struct file_operations demodrv_fops = { .owner = THIS_MODULE, .open = demodrv_open, .read = demodrv_read, .write = demodrv_write, }; static struct miscdevice mydemodrv_misc_device = { .minor = MISC_DYNAMIC_MINOR, .name = DEMO_NAME, .fops = &demodrv_fops, //Corresponding operation of equipment }; static int __init simple_char_init(void) { int ret; ret = misc_register(&mydemodrv_misc_device); if(ret) { printk("failed register misc device\n"); return ret; } mydemodrv_device = mydemodrv_misc_device.this_device; printk("successed register char device: %s\n",DEMO_NAME); return 0; } static void __exit simple_char_exit(void) { printk("removing device\n"); misc_deregister(&mydemodrv_misc_device); } module_init(simple_char_init); module_exit(simple_char_exit); MODULE_LICENSE("GPL");
Let's look at the kernel module initialization function. First, use the kernel API: misc_register() function to register. You can automatically create a device node without mknod to manually create a device node. The passed in parameter is the address of the defined miscdevice structure
Makefile
#Makefile file note: if the previous C file named first c. So here's the makefile file o Wen #It's going to be called first O only the root user can load and unload modules obj-m:=drive2.o #Generate the target file of drive2 module #The object file should be the same as the module name CURRENT_PATH:=$(shell pwd) #Current path of the module LINUX_KERNEL:=$(shell uname -r) #Current version of linux kernel code LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #Compilation module #[Tab] the path of the kernel where the current directory is compiled indicates that the kernel module is compiled clean: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #Cleaning module
Compile and insert kernel modules
View the contents under / dev ll /dev
You can see that the main device number of the generated device file is 10misc and the secondary device number is 53
User mode test program test c
# include <stdio.h> # include <fcntl.h> # include <unistd.h> # include <malloc.h> # include <string.h> # define DEMO_DEV_NAME "/dev/my_demo_dev" int main() { char buffer[64]; int fd; int ret; size_t len; char message[] = "hello"; char *read_buffer; len = sizeof(message); fd = open(DEMO_DEV_NAME,O_RDWR); if(fd<0) { printf("open device %s failed\n",DEMO_DEV_NAME); return -1; } //Write data to device ret = write(fd,message,len); if(ret != len) { printf("cannot write on device %d,ret=%d\n",fd,ret); return -1; } read_buffer = malloc(2*len); memset(read_buffer,0,2*len); //Turn off the device ret = read(fd,read_buffer,2*len); printf("read %d bytes\n",ret); printf("read buffer=%s\n",read_buffer); close(fd); return 0; }
Perform an open, write, and read operation
Use gcc to compile and execute
You can see that hello is read, which is 6 bytes
Use dmesg to view the kernel message and print the corresponding information
This is a simple character device driver to share with you.