字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标、键盘、显示器、串口等等,当我们执行ls -l /dev的时候,就能看到大量的设备文件,c就是字符设备,b就是块设备,网络设备没有对应的设备文件。编写一个外部模块的字符设备驱动,除了要实现编写一个模块所需要的代码之外,还需要编写作为一个字符设备的代码。
一,关键数据结构
1,cdev
cdev可以理解为char device,用来抽象一个字符设备。
//kernel\include\linux\cdev.h
struct cdev {
struct kobject kobj; //表示一个内核对象
struct module *owner; //指向该模块的指针
const struct file_operations *ops; //指向文件操作的指针,包括open、read、write等操作接口
struct list_head list; //用于将该设备加入到内核模块链表中
dev_t dev; //设备号,由主设备号和次设备号构成
unsigned int count; //表示有多少个同类型设备,也间接表示设备号的范围
} __randomize_layout; //一个编译器指令,用于随机化结构体的布局,以增加安全性
2, file_operations
主要用来描述文件操作的各种接口,Linux一切接文件的思想,内核想要操作哪个文件,都需要通过这些接口来实现。
//kernel\include\linux\fs.h
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 (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*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 *);
unsigned long mmap_supported_flags;
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);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
ANDROID_KABI_RESERVE(3);
ANDROID_KABI_RESERVE(4);
} __randomize_layout;
3, dev_t
表示字符设备对应的设备号,其中包括主设备号和次设备号。
//kernel\include\linux\types.h
typedef u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
4,数据结构之间的关系
字符设备驱动程序的数据结构以及API的关系图:
二,设备号管理
1,设备号的概念
每一类字符设备都有一个唯一的设备号,其中设备号又分为主设备号和次设备号,那么这两个分别作用是什么呢?
-
主设备号:用于标识设备的类型,
-
次设备号:用于区分同类型的不同设备
简单来说,主设备号用于区分是IIC设备还是SPI设备,而次设备号用于区分IIC设备下,具体哪一个设备,是MPU6050还是EEPROM。
2,设备号的分配
了解了设备号的概念,Linux中设备号有那么多,那么我们该如何去使用正确的设备号呢?
设备号的分配方式有两种,一种是动态分配,另一种是静态分配,也可以理解为一种是内核自动分配,一种是手动分配。
静态分配函数:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
* from:表示已知的一个设备号
* count:表示连续设备编号的个数,(同类型的设备有多少个)
* name