一、字符设备驱动框架解析
设备的操作函数如果比喻是桩的话(性质类似于设备操作函数的函数,在一些场合被称为桩函数),则:
驱动实现设备操作函数 ----------- 做桩
insmod调用的init函数主要作用 --------- 钉桩
rmmod调用的exitt函数主要作用 --------- 拔桩
应用层通过系统调用函数间接调用这些设备操作函数 ------- 用桩
1.1 两个操作函数中常用的结构体说明
内核中记录文件元信息的结构体
struct inode
{
//....
dev_t i_rdev;//设备号
struct cdev *i_cdev;//如果是字符设备才有此成员,指向对应设备驱动程序中的加入系统的struct cdev对象
//....
}
/*
1. 内核中每个该结构体对象对应着一个实际文件,一对一
2. open一个文件时如果内核中该文件对应的inode对象已存在则不再创建,不存在才创建
3. 内核中用此类型对象关联到对此文件的操作函数集(对设备而言就是关联到具体驱动代码)
*/
读写文件内容过程中用到的一些控制性数据组合而成的对象------文件操作引擎(文件操控器)
struct file
{
//...
mode_t f_mode;//不同用户的操作权限,驱动一般不用
loff_t f_pos;//position 数据位置指示器,需要控制数据开始读写位置的设备有用
unsigned int f_flags;//open时的第二个参数flags存放在此,驱动中常用
struct file_operations *f_op;//open时从struct inode中i_cdev的对应成员获得地址,驱动开发中用来协助理解工作原理,内核中使用
void *private_data;//本次打开文件的私有数据,驱动中常来在几个操作函数间传递共用数据
struct dentry *f_dentry;//驱动中一般不用,除非需要访问对应文件的inode,用法flip->f_dentry->d_inode
int refcnt;//引用计数,保存着该对象地址的位置个数,close时发现refcnt为0才会销毁该struct file对象
//...
};
/*
1. open函数被调用成功一次,则创建一个该对象,因此可以认为一个该类型的对象对应一次指定文件的操作
2. open同一个文件多次,每次open都会创建一个该类型的对象
3. 文件描述符数组中存放的地址指向该类型的对象
4. 每个文件描述符都对应一个struct file对象的地址
*/
1.2 字符设备驱动程序框架分析
驱动实现端:
驱动使用端:
syscall_open函数实现的伪代码:
int syscall_open(const char *filename,int flag)
{
dev_t devno;
struct inode *pnode = NULL;
struct cdev *pcdev = NULL;
struct file *pfile = NULL;
int fd = -1;
/*根据filename在内核中查找该文件对应的struct inode对象地址
找到则pnode指向该对象
未找到则创建新的struct inode对象,pnode指向该对象,并从文件系统中读取文件的元信息到该对象*/
if(/*未找到对应的struct inode对象*/)
{/*根据文件种类决定如何进行下面的操作,如果是字符设备则执行如下操作*/
/*从pnode指向对象中得到设备号*/
devno = pnode->i_rdev;
/*用devno在字符设备链表查找对应节点,并将该节点的地址赋值给pcdev*/
/*pcdev赋值给pnode的i_cdev成员*/
pnode->i_cdev = pcdev;
}
/*创建struct file对象,并将该对象的地址赋值给pfile*/
pfile->f_op = pnode->i_cdev->ops;
pfile->f_flags = flag;
/*调用驱动程序的open函数*/
pfile->f_op->open(pnode,pfile,flag);
/*将struct file对象地址填入进程的描述符数组,得到对应位置的下标赋值给fd*/
return fd;
}
syscall_read函数实现的伪代码
int syscall_read(int fd,void *pbuf,int size)
{
struct file *pfile = NULL;
struct file_operations *fops = NULL;
int cnt;
/*将fd作为下标,在进程的描述符数组中获得struct file对象的地址赋值给pfile*/
/*从struct file对象的f_op成员中得到操作函数集对象地址赋值给fops*/
/*从操作函数集对象的read成员得到该设备对应的驱动程序中read函数,并调用之*/
cnt = fops->read(pfile,pbuf,size,&pfile->f_pos);
。。。。
return cnt;
}
1.3 参考原理图
1.4 常用操作函数说明
int (*open) (struct inode *, struct file *); //打开设备
/*
指向函数一般用来对设备进行硬件上的初始化,对于一些简单的设备该函数只需要return 0,对应open系统调用,是open系统调用函数实现过程中调用的函数,
*/
int (*release) (struct inode *, struct file *); //关闭设备
/*
,指向函数一般用来对设备进行硬件上的关闭操作,对于一些简单的设备该函数只需要return 0,对应close系统调用,是close系统调用函数实现过程中调用的函数
*/
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读设备
/*
指向函数用来将设备产生的数据读到用户空间,对应read系统调用,是read系统调用函数实现过程中调用的函数
*/
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写设备
/*
指向函数用来将用户空间的数据写进设备,对应write系统调用,是write系统调用函数实现过程中调用的函数
*/
loff_t (*llseek) (struct file *, loff_t, int); //数据操作位置的定位
/*
指向函数用来获取或设置设备数据的开始操作位置(位置指示器),对应lseek系统调用,是lseek系统调用函数实现过程中调用的函数
*/
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
/*
指向函数用来获取、设置设备一些属性或设备的工作方式等非数据读写操作,对应ioctl系统调用,是ioctl系统调用函数实现过程中调用的函数
*/
unsigned int (*poll) (struct file *, struct poll_table_struct *);//POLL机制,实现对设备的多路复用方式的访问
/*
指向函数用来协助多路复用机制完成对本设备可读、可写数据的监控,对应select、poll、epoll_wait系统调用,是select、poll、epoll_wait系统调用函数实现过程中调用的函数
*/
int (*fasync) (int, struct file *, int); //信号驱动
/*
指向函数用来创建信号驱动机制的引擎,对应fcntl系统调用的FASYNC标记设置,是fcntl系统调用函数FASYNC标记设置过程中调用的函数
*/
小结
- 使用open函数的第一个参数也就是文件的名称在内核中的struct inode链表里面找是否有对应的节点,没有就创建,有的话且是设备文件的话就把对应的设备号去struct cdev哈希链表中去找有没有对应的描述该设备的对象,没有就创建插入到该链表,然后将该节点的地址赋值给struct inode中的struct cdev * 这个成员。
- 每open一个文件都会创建一个struct file的对象(文件操作引擎),该对象里面的file_operations * 成员从struct cdev里得到,这样就拿到了对应文件的操作函数集的地址(struct file_operations结构体的地址),每个进程都会有一个struct file*类型的数组,该数组的下标就是所有的文件描述符。
- 接着的read、write函数就可以直接从struct file(文件操作引擎)中调用到相关的操作函数集里的相应的函数。