输入子系统概念介绍

651 阅读9分钟

输入子系统

使用现成的驱动:input子系统

在此节之前,我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架、自动创建设备节点、linux中断、poll机制、异步通知、同步互斥/非阻塞、定时器去抖动。

其中驱动框架如下:

1)写file_operations结构体的成员函数: .open()、.read()、.write()

2)在入口函数里通过register_chrdev()创建驱动名,生成主设备号,赋入file_operations结构体

3)在出口函数里通过unregister_chrdev() 卸载驱动

若有多个不同的驱动程序时,应用程序就要打开多个不同的驱动设备,由于是自己写肯定会很清楚,如果给别人来使用时是不是很麻烦?

所以需要使用输入子系统, 使应用程序无需打开多个不同的驱动设备便能实现

回顾字符设备程序的框架

1.major

2.file_operations .open .read .write

3.register.chrden

4.入口函数

5.出口函数

input子系统

现成的,别人做好的

输入子系统简介

同样的输入子系统也需要输入驱动的框架,好来辨认应用程序要打开的是哪个输入驱动

比如: 鼠标、键盘、游戏手柄等等这些都属于输入设备;这些输入设备的驱动都是通过输入子系统来实现的(当然,这些设备也依赖于usb子系统)

这些输入设备都各有不同,那么输入子系统也就只能实现他们的共性,差异性则由设备驱动来实现。差异性又体现在哪里?

最直观的就表现在这些设备功能上的不同了。对于我们写驱动的人来说在设备驱动中就只要使用输入子系统提供的工具(也就是函数)来完成这些“差异”就行了,其他的则是输入子系统的工作。这个思想不仅存在于输入子系统,其他子系统也是一样(比如:usb子系统、video子系统等)

一、输入子系统框架

1.input.c (核心层)

看一个驱动程序从入口函数开始看

register_chrdev(注册字符设备)

input_open_file (open函数)

input_handler(输入处理器)

input_table

new_fops(new_file_operations)

file-> f_op = new_fops

new_fops ->open(inode ,file)

读按键用到new_fops

input.c中转作用,最终用到input_table


drivers/input/input.c 输入子系统的代码在这个c文件中

看一个程序是从“入口函数”开始查看。

QQ图片20210514085339.png

以前注册字符设备驱动的函数是自已写的。上面的注册是内核有的。

QQ图片20210514085518.png

#define INPUT_MAJOR (13)

QQ图片20210514090012.png

这里注册了一个主设备号“INPUT_MAJOR”为13的字符设备,名字为“input”,他的file_operations结构是 “input_fops”。这个结构里面只有一个“.open”函数。 输入子系统,如这里想读键,而这里只有一个“open”函数,那么这个Open函数中应该做了某些工作。

分析 “int input_open_file(struct inode *inode, struct file *file)”:

image.png 其中有一个“input_handler”(输入处理器)

image.png 这里这个“输入处理句柄”结构指向一个“input_table[]”数组。从这个数组里面根据这个“次设备号iminor(inode)>>5”把打开的文件,根绝它的次设备号找到一项。

image.png

接着新的“file_operations”结构“new_fops”等于上面“input_handler”指针变量handler的成员“ops” (这是一个file_operations结构。input_handler结构中有这个file_operations 成员)。

file ->f_op = new_fops;

接着把这个新的 file_operations结构赋给此函数“input_open_file”的形参“file”的f_op。然后再调用这个新的file_operations结构“new_fops”的 open(inode,file)函数

err = new_fops ->open(inode,file);

这样以后要来读按键时,用到的是“struct input_handler *handler”中的“new_fops”. Input.c 只是一个“中转”作用。最终还会用到“input_table[]”.

(1)流程如下:input.c

(2)int _init input_init(void):

QQ图片20210514100545.png

问题:怎么读按键?

这里只有一个Open函数而没有读函数。则可能是这个“input_open_file”函数中做了什么工作。

(3)分析“input_open_file”函数

int input_open_file(struct inode *inode, struct file *file):

->input_handler *handler = input_table[iminor(inode) >> 5]; 根据传进来的“inode” 这个打开的文件的次设备号(“iminor(inode) >> 5”)得到 一个 “input_handler *handler”。

->new_fops = fops_get(handler->fops): 然后的新的 file_operations 结构体(new_fops)等于这个“input_handler *handler”结构 里面的“file_operations”结构“handler->fops”。

->file->f_op = new_fops: 然后所打开的这个 file 中的 f_op(file_operations)等于这个“new_fops”。

->file->f_op = new_fops: 然后所打开的这个 file 中的 f_op(file_operations)等于这个“new_fops”。

以后APP来读的时候,是最终调用“file->f_op”中的read函数。最终是要明白 “input_handler” 是如何定义的,input_table 数组由谁构造。

(4)“input_table[]”的构造:

“static struct input_handler *input_table[8]”是个静态变量,所以只能用在这个文件里面, 所以只 在这个 Input.c 中找到使用了这个数组的地方,如下: int input_register_handler(struct input_handler *handler)这个函数中构造了这个 input_table[] 数 组项:

image.png

(5) 这个“input_register_handler()”函数被谁调用过:

搜索源码工程,见到如下

image.png

有游戏手柄,键盘和鼠标等的源代码调用过它。这些使用就离开了“input.c”这个核心层了。 它们就向上 面的核心层“input.c”注册了“input_register_handler()”

QQ图片20210514102259.png

打开这个“evdev.c”源码。查看调用“input_register_handler()”的调用:

image.png 只是在这个入口函数“evdev_init()”中调用了"input_register_handler()"。 以上是具体的设备与核心层 input.c 的层次调用关系。

(6) 查看“read”的过程:

image.png

在“evdev.c”的入口函数“evdev_init()”中注册了“evdev_handler”这个结构。它的原型如 下:

image.png

它是一个“input_handler”结构体。它的定义如下:

image.png

从上面的"input_handler"结构变量“evdev_handler”的定义可以看到一个“file_operations” 结构“.fops = &evdev_fops”。在这个“evdev_fops”中便有相关的 read,write 等。

image.png

以前自已写驱动时,这个 file_operations 结构体是自已构造的,这里是由系统构造了。

image.png

image.png

次设备号是“64”。 通过“input_register_handler(&evdev_handler)”注册后,就放到:

image.png

通过传值后,形参“handler”就是“&evdev_handler”,则“handler->minor”就是 “evdev_handler->minor”即“EVDEV_MINOR_BASE”(即次设备号 64)。 就相当于 64 除以 2 的 5 次方,即 64 除以 32 为 2. 则这个“handler”是放在“input_table[2]”这个第二项处。handler 是纯软件的概念。

软件方面(evdev.c/keyboard.c/mousedev.c)是向核心层“input.c”注册“handler” (input_register_handler),这一边代表“软件”;还有另一边另一个层“设备”,是向“核 心 层 input.c”注册“input_register_device”,这一边代表硬件.

image.png

一边的“handler 软件处理者”是否支持另一边的“device”,中间会有一个联系:

image.png

“id_table”表示这个“evdev_handler”能够支持哪些“输入设备”。当我们注册上图中的 “handler”和 “device”时,这两者就会比较(handler 和设备比较),看 handler 是否支持 这个设备。若能支持则,则从 上面的“evdev_handler”结构中知道,应该会调用其中的 “.connect = evdev_connect”.

看看谁会调用“input_register_device”,如鼠标:Amimouse.c (drivers\input\mouse),如键盘:

image.png

注册输入设备inputer_register_device:

分析“int input_register_device(struct input_dev *dev)”所做的事情:看“input_c”中此函数 源码。 注册函数,将输入设备注册到核心层中,注册前需要输入设备需要调用 input_allocate_device 来分配,然后设置设备处理能力.

(1) 放入一个链表:

list_add_tail(&dev->node, &input_dev_list);

input_dev:子系统中用此结构体来描述一个输入设备 .这里是:
将设备加入全局链表中→
然后遍历 input_handler_list 中的每一个 handler,调用 input_attach_handler 进行 attach。

(2) 对链表里的每个条目

input_handler_list(注册 handler 时加入的链表)”都会调用“input_attach_handler()”函数来
对“硬件设备 device”与“处理方式 handler”进行关联。
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
List_for_each_entry 函数遍历 Input_handler_list(全局链表,连接所有的 input_handler)上的
handler,并调用 Input_attach_handler 来进行输入设备和处理方法的关联。

image.png

从上图可知,不管是先注册加载右边的“handler”还是左边的“device”。最终都会成对的 调用“input_attach_handler()”。看看源码“input_attach_handler()”:

image.png

image.png

注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler, 根据 input_handler 的 id_table 判断这个 input_handler 能否支持这个 input_dev,如果能支 持,则调用 input_handler 的 connect 函数建立"连接。 如何建立连接,可能不同的 handler 都有自已不同的方式。

实例分析“evdev.c”中的“connect”:

image.png

(3) 进入“evdev_connect 函数”分析:

image.png

  1. ,分配了一个“input_handle evdev”结构变量 (不是 input_handler 结构)。

查看这个 evdev 结构中的成员:

image.png

成员中有一个“input_handle handle”结构。

  1. 再对这个“input_handle evdev”进行设置:

image.png

  1. 最后注册这个 handle:

image.png

image.png 将形参“handle”放到一个输入设备的链表里面:

image.png 再把“handler”放到右边一个“h_list”链表里面。

image.png

image.png

如何读数据“按键”:

App:read

应用程序来读,最终会导致“handler”里面的新的“.fops”里面的“读函数”被调用。如: “evdev.c”中的“evdev_handler”结构里面的成员“.fops=&evdev_fops”,在“evdev_fops” 结构中有一个“读”函数“evdev_read”

image.png

image.png

image.png

在“evdev_read()”中:

image.png

可以从上面的“wait_event_interruptible()”知道是在“evdev”上休眠,则唤醒也会是在它 上面。可以搜索它。

(1) 谁来“唤醒”:

如按下一个按键后,中断处理函数就会被调用。在中断处理函数里面先确定按键值。然后才 来唤醒。

image.png 在代码中搜索“evdev->wait”后找到在“evdev_event()”中有唤醒操作。这个“evdev->wait” 是结构“evdev_handler”的“.event”事件成员。 分析事件处理函数“evdev_event()”: 主图中右边是“纯软件”的部分,按键按下时应该是由左边输入设备这一层触发的。

(2) 谁调用“evdev_event()”:

image.png

总结“输入子系统”:

(1) 分为上下两层

核心层“input.c”.它里面有“register_chdev”(input_init()中),但它简单: image.png 因为这个 register_chrdev()中的“input_fops”file_operations 结构很简单:

image.png 只有一个“.open”函数,所以让它去读去写不行,里面还有个“中转”的过程: image.png 根据打开的设备节点的次设备号找到一个“handler”:

image.png

并且把这个文件的 f_op 指向新的“input_handler *handler”里面的“fops”:

image.png 然后再调用这个“handler”中的 open 函数:

image.png 其中的“iput_table[]”数组由下面的各个“纯软件”代码构建(如:evdev.c,keyboard.c).

“纯软件(input_handler)”部分和“硬件部分(input_dev)”联系起来,纯软件部分是由 “ input_register_handler ” 向 上 “ input.c ” 核 心 层 注 册 处 理 方 式 ; 硬 件 部 分 是 由 “input_register_device”向上“input.c”核心层注册硬层。这样注册后,会使它们两两比较, 看看其中的某个“handler”是否支持其中的某个“dev”。若是“handler”能支持某个“dev”, 则会接着调用“input_handler”结构下的“.connect”函数,这个函数一般会创建一个 “input_handle”结构(是 handle 而非 handler),并且这个结构“input_handle”会分别放 在两边的“h_list”链表中去。这个结构体中有“.dev”和“.handler”,让它两分别具体指向 右边的“纯软件处理部分”和左右的硬件部分,这样具体的某硬件就和纯软件联系起来了。 可以从任何一边通过“h_list”找到这个“input_handle”结构,再通过成员找到另一端的“.dev” 或“.handler”。

image.png

举例是如何读按键: 最终是应用程序读,最终会导致 handler 中的“read”函数。读的过程中,没有数据可读时 就休眠,有休眠就会唤醒,搜索的结果是“event”函数来唤醒。分析到这里就没有接着去分 析而是猜测是由“硬件”调用的“event”函数,硬件则是指“input_dev”层的设备中断服 务程序调用了“event”函数。通过这个“event”函数可以最终追踪到“纯软件”部分的 “input_handler”结构体中的“.event”成员。

写输入子系统实例:

image.png