一、内核模块
- 字符设备驱动属于内核模块,而内核模块主要由模块加载函数、模块卸载函数和模块许可证声明组成。
- 模块加载/卸载函数:当通过insmod/rmmod命令加载/卸载模块时,模块的加载/卸载函数会自动被内核执行。
static int __init xxx_init(void);
static void __exit xxx_exit(void);
module_init(xxx_init); //指定模块加载函数xxx_init
module_exit(xxx_exit); //指定模块卸载函数xxx_exit
- 模块许可证声明描述了内核模块的许可权限。
MODULE_LICENSE("xxxx");
二、cdev结构体
- 在Linux内核中,使用cdev结构体描述一个字符设备,结构体在文件include/linux/cdev.h中定义。
struct cdev {
struct kobject kobj;
struct module *owner; /* 所属模块*/
const struct file_operations *ops; /* 文件操作结构体*/
struct list_head list;
dev_t dev; /* 设备号*/
unsigned int count;
};
- Linux设备号由主设备号和次设备号组成,用一个dev_t(即unsigned int)类型表示设备号,其中高12位为主设备号,低20位为次设备号。主设备号是与驱动对应的概念,同一类设备一般使用相同的主设备号;同一驱动可支持多个同类设备,因此用次设备号来描述使用该驱动的设备的序号,序号一般从0开始。即主设备号表示一个具体的驱动,次设备号表示驱动下的设备。
- file_operations结构体定义了字符设备驱动提供给虚拟文件系统的接口函数,该结构体在内核文件include/linux/fs.h中。
三、申请/注销设备号
- 在向系统注册字符设备之前,应该先申请设备号;在系统注销字符设备后,应该释放掉申请的设备号。
/*向Linux内核申请未使用的设备号;
参数dev是返回的主/次设备号; baseminor是起始次设备号;
count是要申请的数量; name是设备名字*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
/*使用完后释放设备号;
参数from是注销的起始设备号; count是注销的设备数量*/
void unregister_chrdev_region(dev_t from, unsigned count);
四、构建字符设备的接口函数
- file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,在应用程序使用open、write、read、ioctrl、close等系统调用操作设备节点文件时,最终被内核调用来操作字符设备。
static struct file_operations xxxx_fops = {
.owner = THIS_MODULE,
.open = xxxx_open,
.read = xxxx_read,
.write = xxxx_write,
.unlocked_ioctl = xxxx_ioctl,
.release = xxxx_release,
};
五、操作cdev结构体
- 在加载驱动时,需要向内核注册字符设备;在卸载驱动时,需要向内核注销字符设备。
/*初始化cdev的成员,并建立cdev和file_operations之间的连接*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
/*注册字符设备.
dev是设备号,count是要注册的设备数量 */
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
/*注销字符设备*/
void cdev_del(struct cdev *p);
六、 自动创建/删除设备文件
/* 创建class结构体变量;
owner参数一般为THIS_MODULE;name是设备文件的名字;返回值为class指针*/
struct class *class_create (struct module *owner, const char *name);
/* 删除class结构体变量;
cls为要删除的class结构体*/
void class_destroy(struct class *cls);
- 在class类下创建/删除设备
/* 创建设备节点文件;
参数class表示设备节点文件需要创建在哪个class结构体下面;
parent表示父设备,NULL表示没有;devt表示设备号;
drvdata表示需要使用到的数据,一般为NULL;
fmt表示创建dev/fmt设备节点文件 */
struct device *device_create(
struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...);
/* 删除设备节点文件;
class表示要删除的设备节点所处的结构体,devt是指设备号 */
void device_destroy(struct class *class, dev_t devt);
七、伪代码示例
/*Test 驱动模块结构体*/
struct TestDev_t
{
dev_t dev_num; //设备号
struct cdev TestCdev; //使用Linux内核下cdev结构体,描述一个字符设备
struct class *TestClass; //创建LedClass类指针
struct device *TestDevice; //设备节点指针
};
struct TestDev_t TestDev;
/*字符设备操作函数*/
/*··················
····省略函数实现····
····················*/
static struct file_operations TestDevFops = {
.owner = THIS_MODULE,
.open = test_open,
.read = test_read,
.write = test_write,
.unlocked_ioctl = test_ioctl,
.release = test_release,
};
/*模块加载函数,省略判断流程*/
static int __init test_init(void)
{
/*注册设备号*/
alloc_chrdev_region(TestDev.dev_num, 0, 1, "TestDev");
/*注册字符设备*/
cdev_init(&TestDev.TestCdev, &TestDevFops);
cdev_add(&TestDev.TestCdev, TestDev.dev_num, 1);
/*创建设备节点文件*/
TestDev.TestClass = class_create(THIS_MODULE, "TestDev");
TestDev.TestDevice = device_create(TestDev.TestClass, NULL, TestDev.dev_num, NULL, "TestDev");
return 0;
}
/*模块卸载函数*/
static void __exit test_exit(void)
{
/*注销字符设备*/
cdev_del(&TestDev.TestCdev);
/*注销设备号*/
unregister_chrdev_region(TestDev.dev_num, 1);
/*删除设备节点文件*/
device_destroy(TestDev.TestClass, TestDev.dev_num);
class_destroy(TestDev.TestClass);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
八、注解
-
dev_t即为unsigned int
/* include/linux/type.h中有如下定义 */
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
-
Linux虚拟文件系统
Linux虚拟文件系统隐藏了各种硬件的具体细节,为所有设备提供了统一的接口。它独立于各个具体的文件系统,是对各种文件系统的一个抽象。它为上层的应用程序提供了统一的vfs_read()、vfs_write()等接口,并调用具体底层文件系统或者设备驱动中实现的file_operations结构体的成员函数,来访问文件。
-
设备文件
- 应用程序和VFS之间的接口是系统调用,而VFS与文件系统以及设备文件之间的接口是file_operations结构体成员函数。
- 由于字符设备的上层没有类似于磁盘的ext2等文件系统,所以字符设备的file_operations成员函数就直接由设备驱动提供了。
- 如果通过文件系统来访问块设备,file_operations的实现则位于文件系统内,文件系统会把针对文件的读写转换为针对块设备原始扇区的读写。ext2、fat、Btrfs等文件系统中会实现针对VFS的file_operations成员函数,设备驱动层将看不到file_operations的存在。
-
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 *);
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 (*mremap)(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 (*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 **, 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
};
-
class结构体
struct class {
const char *name;
struct module *owner;
struct class_attribute *class_attrs;
const struct attribute_group **dev_groups;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
九、参考资料
- 《Linux设备驱动开发详解:基于最新的Linux4.0内核》
- 《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5》
以上是我在学习过程中的总结,不当之处请在评论区指出。