虽然我们知道了当我们写一个设备的模块时候,只要指定是字符输入设备,当你模块装载的时候,内核就会自动匹配input.ko event.ko模块,让这个设备模块完成驱动。
内核帮我们完成了这些事情
2,申请设备号 register_chrdev_region (与内核相关)
3,注册字符设备驱动 cdev_alloc cdev_init cdev_add (与内核相关)
4,利用udev/mdev机制创建设备文件(节点) class_create, device_create (与内核相关)
5,构建 file_operation结构 (与内核相关)
6,实现操作硬件方法 xxx_open,xxx_read,xxxx_write…(与硬件相关)
那我们肯定要了解,内核是怎么做这些事情的。
然后还有就是一些疑问?
1)就是它是怎么匹配的——————(之前猜的是总线,但是不是,就是两个链表,那具体是怎么实现的呢?看名字?)
2)我一直不理解,为什么应用层调一个read 那驱动层就会执行read的文件操作??虽然是通过文件描述符肯定有有一些联系?
但是怎么联系的呢?总不能是我应用层read了这个文件,然后我内核就知道o,你要read啊,那我就调read。内核肯定没怎么智能
————————————————————————————————————————————————————
首先看一个驱动的在运行的时候做的事情的顺序。
就是我把我的设备模块写好,装载进去的时候内核是执行那些步骤然后完成驱动的。
第一步:
把设备模块装载在内核中
第二步:
装载完成之后,在核心层就会,注册设备号(系统自动给你分配主次设备号)这里还有一个file_operation
然后还是在核心层创建类,这个类和c++中的类不是一样 的,这个类也是一个sys下的一个目录该目录下包含所有注册在
kernel里面的设备类型 我们的设备会出现在 /sys/class/input/这个目录里面。
然后我们要对硬件信息进行初始化(这个步骤是我们自己完成的,不是内核做的)
第三步:
把设备注册到input_dev_list这个链表里面去,
然后我们的input_handler_list 里面在内核运行的时候就已经把内核中有的调用层handler加进去了。
俩个链表就会进行匹配。怎么匹配的后面会讲。
第四步:
匹配完成之后handle层就会调用connet这个函数去创建我们的设备节点
然后实现file_operation
connect和怎么实现file_operation后面也会讲。
具体代码可以去看kernel下面的drives下面的input.c 和evdev.c这俩个就是核心层和调用层的源代码。
————————————————————————————————————————————————
一、input_dev_list 和 input_handler_list是怎么匹配的。
首先,无论你是input_dev_list 还是input_handler_list,这俩个链表中哪一个链表注册进新的节点,都会去遍历一遍对方的链表。
遍历如果发现条件相同就会进行匹配。那什么是条件呢?比如我dev的evbit 的值是一个条件 keybit的值也是一个条件等等。
然后我要handel指定去驱动evbit的值是按键的。那么就会和dev的evbit 的值是按键的设备匹配。
然后我们看一下event下匹配事件
这里event的id_table只有一个1,是什么意识?就是恒匹配的意思。
只要你属于输入设备,我都可以把你看成一个event一个事件,我的handler都可以和你一起组成一个完整的驱动。
其实之前没有引入event的时候,我们会把驱动模块进行区分,比如分mouse模块 joystatic模块,,这样我mouse只能和evbit的条件是mouse的设备进行匹配,引入event之后可以把所有的设备都可以看成一个事件,所以事件里面包含鼠标,
但是你去看drives下面的目录,就会发现不止有evdev.c 还有mouse.c 这就是,没有引入evdev之前的mouse
————————————————————————————————————————————
一、connect函数的作用
connect属于 evdev_handler里面的一个函数接口
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
connect:
首先:
1)会创建一个evdev的结构体,并初始化
evdev里面有一个叫dev的成员 //这个dev只要是用来创建设备节点的。
这个dev会调用一个set_dev_name的函数去创建一个设备节点
和我们之前用的device_create函数差不多。
而创建的dev设备节点点里面又包含了,handler和decvice的指针呢个,这就类似于:
这里可能有点乱了,我们再从另一个角度来看
———————————————————————————————————————————
这是核心层里面的
首先:input_dev_list和input_handler_list这是来个要匹配的链表,链表里面由很多相同的结构体组成。
如果我们的input_dev要和input_handle匹配,就会调用connect去创建设备节点。
这个设备节点就是我们这里的evdev对象。它下面有一个Input_handle的结构体,这个结构体里面会有指向handler和DEV的指针。
这样这三方就联系起来了
—————————————————————————————————————————————————
二、接下来我们看一下
为什么应用层调一个read 那驱动层就会执行read的文件操作
首先,我们会调用open函数去打开dev/。。。这个设备文件。就会返回一个文件描述符。
那这个文件描述符——我们对文件描述都哦比较熟悉了,比如我们打开一个文件返回的文件描述符应该就会是3但是这个三只是应该下标,
它还会有一个结构体,里面存放这很多文件的信息。
这个结构体叫struct file {};
我们之前的驱动中fiel_opreation里面的read,write等函数里面的参数就是要填一个struct file
我们看一下这个结构体里面有什么?
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
..............
我们只列了一部分:
可以看到里面有一个:
const struct file_operations *f_op;
这不就是我们经常用的文件操作结构体吗???
我们看一下是怎么通过这个文件操作结构体来实现的这些操作。
当我们打开一个文件的时候,就会创建一个文件结构体,这个文件结构体就会存储 flags等信息。
当我们对这个文件描述符进行文件io的操作的时候,就会进行系统调用。
会遍历VFS(虚拟文件系统)的一个cdev链表,然后根据主次设备号匹配这个链表;
这些CDEV链表是从哪里来的呢?就是我们在驱动层调用regeaust_chrdev申请设备号的函数的时候,创建的一个cdev
这是我们以前的没有引入高级驱动的方法的时候自己申请的设备号,现在内核会自己申请了,它也调用了这个函数
那我们来看一下,它传的参数 &my_fope :这个就是一个file_operation 所以这个cdev里面不止存放着主次设备号,还有file_operation的指针。
当系统调用read去遍历VFS层的cdev的时候就会去遍历主次设备号,一旦找到一样的就会拿到ile_operation的指针
然后就会把cdev的file_operation给到 file结构体里的f_ops结构体里面去。
然后调用对应的操作。
————————————————————————————————————————————————————
三、数据上报的详解
connect下面的evdev对象下面还会有一个client用于存放要上报的数据
在open的时候会创建这个缓冲区