关注微信公众号:Linux内核拾遗
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");
编译运行如下:
关注微信公众号:Linux内核拾遗