字符设备驱动框架解析

发布时间:2024年01月11日

设备的操作函数如果比喻是桩的话(性质类似于设备操作函数的函数,在一些场合被称为桩函数),则:

驱动实现设备操作函数 ----------- 做桩

insmod调用的init函数主要作用 --------- 钉桩

rmmod调用的exitt函数主要作用 --------- 拔桩

应用层通过系统调用函数间接调用这些设备操作函数 ------- 用桩

1.1 两个操作函数中常用的结构体说明

内核中->记录文件元信息的结构体
struct inode (存在于链表中)    ---->对应着外存中的某一个文件  1对1对应
{
    //....
    dev_t ?i_rdev;//设备号
    struct cdev ?*i_cdev;//如果是字符设备才有此成员,指向对应设备驱动程序中的加入系统的struct cdev对象
    //....
}
/*
    1. 内核中每个该结构体对象对应着一个实际文件,一对一
    2. open一个文件时如果内核中该文件对应的inode对象已存在则不再创建,不存在才创建
    3. 内核中用此类型对象关联到对此文件的操作函数集(对设备而言就是关联到具体驱动代码)
*/
读写文件内容过程中用到的一些控制性数据组合而成的对象------文件操作引擎(文件操控器)
struct file       由数组进行管理     与open函数1对1对应   数组的下标即为open函数的返回值
{
    //...
    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 参考原理图

字符设备驱动框架

Linux字符设备驱动工作原理图

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标记设置过程中调用的函数
*/

文章来源:https://blog.csdn.net/2302_76785838/article/details/135537405
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。