掘金 后端 ( ) • 2024-04-28 10:45

关注微信公众号:Linux内核拾遗

文章来源:https://mp.weixin.qq.com/s/JGThRju13r57iQM-BjeebQ

1 cdev结构和文件操作

当需要对设备执行打开/关闭和读写操作时,我们需要在设备驱动程序中注册一些结构,主要是struct cdev和文件操作struct file_operations。

1.1 cdev结构

Linux内核中,struct inode用来表示具体的文件,它包含了文件的大量信息;而struct file表示一个打开的文件描述符。一个文件可能有多个struc file表示打开的文件描述符,但它们都指向一个相同的struct inode结构。

struct cdev是inode结构其中的一个元素,它是Linux内核的一个内部结构体,用于表示字符设备。

 struct cdev { 
   struct kobject kobj; 
   struct module *owner; 
   const struct file_operations *ops; 
   struct list_head list; 
   dev_t dev; 
   unsigned int count; 
 };

设备驱动程序需要设置file_operations和owner(通常是THIS_MODULE宏)两个字段。

有两种分配和初始化cdev结构的方式:runtime allocation和own allocation。

1.1.1 Runtime Allocation

可以通过下面的方式在运行时获取一个单独的cdev结构:

 struct cdev *my_cdev = cdev_alloc( );
 my_cdev->ops = &my_fops;

1.1.2 Own Allocation

可以将cdev结构体嵌入到一个设备特定的结构中,然后通过下面的方法进行初始化:

 void cdev_init(struct cdev *cdev, struct file_operations *fops);

当cdev结构体设置好file_operations和owner后,可以通过下面的方法来通知给内核:

 int cdev_add(struct cdev *dev, dev_t num, unsigned int count);

一旦cdev_add()成功调用,相应的设备就能立即可用,并且file_operations中定义的所有函数都能够被调用。

最后可以通过下面方法释放cdev结构体:

 void cdev_del(struct cdev *dev);

1.2 文件操作

"linux/fs.h"头文件中定义的file_operations结构体,它是注册到设备驱动中字符设备的打开/关闭和读写等操作的回调函数。

它包含了一系列的函数指针,每个打开的文件都会通过file_operations关联到对应的操作处理函数集合。这些操作通常是作为系统调用的实现,例如open、read、write等等。

 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 *);
   unsigned int (*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 *);
   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);
   ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
                                u64);
 };

值得注意的是,file_operations中包含了struct module *owner字段,它不是函数指针,而是”拥有“该结构体的模块指针,通常设置为"linux/module.h"中定义宏THIS_MODULE。它用于防止file_operations还在使用的时候对应的内核模块被意外卸载。

一般情况下不需要设置所有的函数指针:

 static struct file_operations fops =
 {
   .owner    = THIS_MODULE,
   .read     = my_read,
   .write    = my_write,
   .open     = my_open,
   .release  = my_release,
 };

1.2.1 read

 ssize_t (*read) (struct file *, char _ _user *, size_t, loff_t *);

用于从设备中获取数据,非负数返回值表示成功读取的字节数。

1.2.2 write

 ssize_t (*write) (struct file *, const char _ _user *, size_t, loff_t *);

用于往设备发送数据,非负数返回值表示成功写入的字节数。

1.2.3 ioctl

 int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

ioctl系统调用提供了一种发送设备特定命令的方式,例如格式化磁盘,它既不是读也不是写操作。

1.2.4 open

 int (*open) (struct inode *, struct file *);

打开设备文件的时候会调用open函数。

1.2.5 release (close)

 int (*release) (struct inode *, struct file *);

struct file被释放的时候会调用release函数。

2 文件操作示例

下面是一个简单的设备文件操作的示例:

file_ops.c

 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/module.h>
 #include <linux/kdev_t.h>
 #include <linux/fs.h>
 #include <linux/err.h>
 #include <linux/cdev.h>
 #include <linux/device.h>
 dev_t dev = 0;
 static struct class *dev_class;
 static struct cdev my_cdev;
 ​
 static int      __init my_init(void);
 static void     __exit my_exit(void);
 static int      my_open(struct inode *inode, struct file *file);
 static int      my_release(struct inode *inode, struct file *file);
 static ssize_t  my_read(struct file *filp, char __user *buf, size_t len,loff_t * off);
 static ssize_t  my_write(struct file *filp, const char *buf, size_t len, loff_t * off);
 static struct file_operations fops =
 {
   .owner      = THIS_MODULE,
   .read       = my_read,
   .write      = my_write,
   .open       = my_open,
   .release    = my_release,
 };
 ​
 static int my_open(struct inode *inode, struct file *file)
 {
   pr_info("Driver Open Function Called\n");
   return 0;
 }
 ​
 static int my_release(struct inode *inode, struct file *file)
 {
   pr_info("Driver Release Function Called\n");
   return 0;
 }
 ​
 static ssize_t my_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
 {
   pr_info("Driver Read Function Called\n");
   return 0;
 }
 ​
 static ssize_t my_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
 {
   pr_info("Driver Write Function Called\n");
   return len;
 }
 ​
 static int __init my_init(void)
 {
   if((alloc_chrdev_region(&dev, 0, 1, "my_dev")) <0){
     pr_err("Cannot allocate major number\n");
     return -1;
   }
   pr_info("Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
 ​
   cdev_init(&my_cdev,&fops);
   if((cdev_add(&my_cdev, dev, 1)) < 0){
     pr_err("Cannot add the device to the system\n");
     goto r_class;
   }
 ​
   if(IS_ERR(dev_class = class_create(THIS_MODULE,"my_class"))){
     pr_err("Cannot create the struct class\n");
     goto r_class;
   }
 ​
   if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"my_device"))){
     pr_err("Cannot create the Device\n");
     goto r_device;
   }
   pr_info("Device Driver Inserted\n");
   return 0;
   r_device:
   class_destroy(dev_class);
   r_class:
   unregister_chrdev_region(dev,1);
   return -1;
 }
 ​
 static void __exit my_exit(void)
 {
   device_destroy(dev_class,dev);
   class_destroy(dev_class);
   cdev_del(&my_cdev);
   unregister_chrdev_region(dev, 1);
   pr_info("Device Driver Removed\n");
 }
 ​
 module_init(my_init);
 module_exit(my_exit);
 ​
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("feifei <[email protected]>");
 MODULE_DESCRIPTION("Simple Linux device driver (File Operations)");
 MODULE_VERSION("1.3");

编译运行如下:

image-20240427215850441

关注微信公众号:Linux内核拾遗

文章来源:https://mp.weixin.qq.com/s/JGThRju13r57iQM-BjeebQ