字符设备驱动
1.1 主设备号与此设备号
在构建字符设备驱动时候需要获得设备编号,类似于汽车牌照,没有没办法上路。主设备号标识设备对应的驱动程序,而次设备号由内核使用,用于确定/dev下的设备文件对应的具体设备。eg:虚拟控制台和串口终端有驱动程序4管理,而不同的终端分别有不同的次设备号。
MAJOR(dev_t dev);
获取主设备号
MINOR(dev_t dev);
获取此设备号
MKDEV(int major, int minor);
将主次设备号转化为dev_t类型
1.2 设备号分配与释放
分配
在已知主设备号时
int register_chrdev_region(dev_t first, unsigned int count, const char name);
first 设备编号起始值,count 连续设备编号个数, name与该设备关联的设备名称,会出现在/proc/devices和sysfs中,函数成功返回0
未知主设备号时
int alloc_chrdev_region(dev_t dev, unsigned int firstminor, unsigned int count, const char *name)
dev 用于存放申请的设备号, firstminor 要使用第一个此设备编号,此函数相比较于前面函数优点可以避免设备号重复的冲突
释放
void unregister_chrdev_region(dev_t dev, unsigned int count)
注意 :在调用cdev_del()
注销字符设备时需要先调用unregister_chrdev_region()
释放设备号
2 字符设备注册
在上面申请”牌照(设备号)“之后需要将牌照和 ’汽车(设备驱动)“绑定激活
void cdev_init(struct cdev *cdev, struct file_operation *fops)
初始化cdev成员,建立file_operations之间联系
int cdev_add(struct cdev *dev, dev_t num, unsigned int count)
void cdev_del(struct cdev *dev)
添加和删除一个cdev,即字符设备的注册与注销
3、cdev结构体成员
cdev结构体用于描述字符设备,
struct cdev {
struct kobject kobj;//内嵌的kobject对象
struct module *owner;所属模块,通常为THIS_MODULE
const struct file_operations *ops;//文件操作结构体 ops 见上面4
struct list_head list; //实现链表,包含所有该设备的设备特殊文件的inode ???
dev_t dev; //设备号
unsigned int count;//与该设备关联的设备数目
};
4 file_operations结构体成员函数
file_operations是VFS和磁盘文件系统、普通外设之间的结构,即是字符设备和内核的接口,对字符设备文件的打开、关闭、读写、控制等最终会该结构体中的函数调用。其功能如下
- 对设备初始化和释放
- 把数据从内核传送到硬件和从硬件读取数据
- 读取应用程序传送给设备文件的数据和回送应用程序请求的数据
- 检测和处理设备出现的错误
file_operations结构体成员函数数目十分庞大,只针对结构体中的打开、关闭、读写等主要成员进行分析
struct module *owner
一般被定义 THIS_MODULE,用来在操作正在被使用时阻止模块被卸载
loff_t (*llseek)(struct file*, loff_t, int)
文件定为函数,file* 文件指针,请求偏移量,文件定位起始位(0 文件开头,1当前位置)
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
内核读取用户空间数据,常与copy_to_user()搭配使用
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
内核向用户空间写数据,与copy_from_user()搭配使用
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
设备相关控制命令
int (*open) (struct inode *, struct file *);
打开文件时被调用
int (*release) (struct inode *, struct file *) ;
关闭文件,并释放资源
5、file结构体成员
系统每打开一个文件内核空间都会有一个struct file与其关联,设备文件同理。他由内核打开时创建,并传递给在文件上操作的任何函数,最常用的私有数据指针private_data,read(),write(),ioctl(),llseek()等函数通过它访问设备结构体。文件所有实例都关闭后,内核会释放该数据结构。
struct file {
const struct file_operations *f_op;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
void *private_data;
};
- f_mode 确定文件读写,通过FMODE_READ和FMODE_WRITE确定
- f_flags 文件标志,O_RDONLY,O_NONBLOCK,O_SYCN,所有文件标志在linux/fcntl.h中定义,O_NONBLOCK可以用来判断设备文件是以阻塞还是非阻塞方式打开
- private_data 比较常用,open系统调用时会将这个指针定义为NULL,如果需要也可在open中指向分配的数据,但要注意需要在release中释放内存
- f_ops 当前读写位置,read、write都是以它为最后参数来更新位置
6 inode结构体
VFS中每个文件都会关联一个inode,用来管理文件的属性(文件访问权限,大小,生成时间,访问时间,最后修改信息,文件系统最基本单位,也是连接任何子目录、文件桥梁)
struct inode {
umode_t i_mode;
dev_t i_rdev;
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
};
void *i_private; /* fs or device private pointer */
};
- i_mode 存储文件类型面向块,还是面向字符
- i_rdev 主从设备号,
- i_cdev 见3
7字符设备打开时流程
当打开字符设备文件时,内核为该文件分配file结构体,通过inode判断读写状态,读取inode.i_cdev>file_operations.open函数,可以在其中指定file.private_data,可以通过file_operations中的read、write等完成对字符设备的数据从内核传送到硬件和从硬件读取数据,读取应用程序传送给设备文件的数据和回送应用程序请求的数据。