字符设备驱动内部实现原理
1.字面理解解析:
字符设备驱动的内部实现有两种情况:
情况1.应用层调用open函数的内部实现:
open函数的第一个参数是要打开的文件的路径,根据这个路径 虚拟文件系统层VFS 可以找到这个文件在文件系统中唯一的标识,也就是inode号,通过inode号作为索引可以找到储存在内核中的struct inode结构体,struct inode结构体内部储存着 struct cdev结构体 和 储存该文件设备号的变量dev,因为设备文件想联系设备驱动,就要在inode结构体中保存该驱动的设备号 通过解析struct cdev结构体可知:结构体内部也有储存设备号的变量dev和操作方法结构体指针,通过操作方法结构体指针 VFS 就可以帮助我们回调对应的 mycdev_open 函数
open函数回调实现路线:
1.应用层open函数+打开路径参数 ----> 2.VFS层 --->3.对应设备文件inode号--->4.索引得 对应的struct inode结构体---> 5.struct cdev结构体---> 6.操作方法结构体、设备号---> 7.回调对应操作函数 myopen
情况2.应用层调用write/read等函数的内部实现:
write/read 函数没有指定路径的参数,换成了使用从open函数返回值得到的文件描述符来进行回调对应的操作方法 首先,当一个进程运行在操作系统中,那么就一定会在内核中的task_struct结构体空间中封存放进程的相关信息, 在task_struct结构体中, 有着存放着打开文件相关的结构体成员struct files_struct ,files_struct结构体成员struct file __rcu * fd_array[fd] 的下标 就是文件描述符的本质,这个结构体指针指向的结构体类型struct file 里就有操作方法结构体,通过文件描述符就可以确认是数组的哪个下标成员,VFS 虚拟文件系统层 来帮助我们回调对应的操作方法
read、write函数回调实现路线:
1.应用层write/read函数+fd文件描述符参数 ---> 2.VFS层---> 3.task_struct结构体---> 4.struct files_struct *files; //打开的文件相关结构体---> 5.struct file __rcu * fd_array[NR_OPEN_DEFAULT];//结构体指针数组,fd本质就是这个数组的下标---> 6.确定是数组中哪个struct file类型的成员---> 7.调用操作方法结构体成员---> 8.回调对应read、write函数
文件信息结构体:
struct inode
{umode_t i_mode;unsigned short i_opflags;kuid_t i_uid;kgid_t i_gid;dev_t i_rdev;union{struct block_device *i_dev;struct cdev;char *i_link;unsigned i_dir_seq;};
};字符设备驱动对象结构体:
struct cdev {struct kobject kobj;struct module *owner;//THIS_MODULEconst struct file_operations *ops;//操作方法结构体struct list_head list;//构成链表dev_t dev;//设备号unsigned int count;//设备数量
};
分步注册流程和代码实例:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/cdev.h>unsigned int major;
dev_t devno;
struct cdev *cdev;
unsigned int major=500;
unsigned int minor=0;
struct class *cls;
struct device *dev;char kbuf[128]={0};int mycdev_open(struct inode *inode, struct file *file){return 0;}ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *iof){return 0;}ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *iof)
{return 0;
}int mycdev_close(struct inode *inode, struct file *file)
{return 0;
}//定义一个操作方法结构体变量并且初始化
//结构体解析:需要使用一个结构体内的成员时才需要初始化这个变量的成员,
//此处需要准备4个函数初始化函数指针成员
//如果是结构体指针则需要实例化一个对应的结构体变量,或者指向函数申请的堆区空间
struct file_operations fops={.open=mycdev_open,.read=mycdev_read,.write=mycdev_write,.release=mycdev_close,
};static int __init mycdev_init(void)
{int ret,i;//1.分配字符设备驱动对象空间cdev=cdev_alloc();if(cdev==NULL){printk("分配字符设备驱动对象失败\n");ret=-EFAULT;goto LOOP1;}printk("分配对象空间成功\n");//2.字符驱动对象初始化cdev_init(cdev,&fops);//3.申请设备号//静态指定设备号if(major>0){ret=register_chrdev_region(MKDEV(major,minor),3,"myled");if(ret){printk("静态指定设备号失败\n");goto LOOP2;}}else if(major==0) //动态申请设备号{ret=alloc_chrdev_region(&devno,minor,3,"myled");if(ret){printk("动态申请设备号失败\n");goto LOOP2;}major=MAJOR(devno);minor=MINOR(devno);}printk("申请设备号成功\n");//4.添加字符设备驱动对象注册进内核ret=cdev_add(cdev,MKDEV(major,minor),3);if(ret){printk("字符设备驱动对象注册失败\n");goto LOOP3;}printk("添加字符设备驱动对象注册进内核成功\n");cls=class_create(THIS_MODULE,"myled");if(IS_ERR(cls)){printk("向上提交目录失败\n");ret=-PTR_ERR(cls);goto LOOP4;}printk("向上提交目录成功\n");//向上提交设备节点信息for(i=0;i<3;i++){dev=device_create(cls,NULL,MKDEV(major,i),NULL,"myled%d",i);if(IS_ERR(dev)){ret=-PTR_ERR(dev);goto LOOP5;}}return 0;LOOP5://释放已经申请的设备节点信息for(--i;i>=0;i--){device_destroy(cls,MKDEV(major,i));}//释放目录空间class_destroy(cls);LOOP4://注销字符设备驱动对象cdev_del(cdev);LOOP3://释放设备号unregister_chrdev_region(MKDEV(major,minor),3);LOOP2:kfree(cdev); LOOP1:return ret;
}static void __exit mycdev_exit(void)
{//销毁设备节点int i;for(i=0;i<3;i++){device_destroy(cls,MKDEV(major,i));}//释放目录空间class_destroy(cls);//注销字符设备驱动对象cdev_del(cdev);//释放设备号unregister_chrdev_region(MKDEV(major,minor),3);//释放对象空间kfree(cdev);
}module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");