欢迎光临散文网 会员登陆 & 注册

一文带你搞懂Linux之输入子系统分析详解(含源码)

2022-04-07 16:36 作者:补给站Linux内核  | 我要投稿

一.输入子系统简介

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

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

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

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

  • 所以我们先来分析下输入子系统input.c的代码,然后怎么来使用输入子系统(在内核中以input来形容输入子系统)

二.打开input.c,位于内核deivers/input

  • 有以下这么两段:

显然输入子系统是作为一个模块存在,我们先来分析下input_int()入口函数

  1. 上面第4行”err = class_register(&input_class);”是在/sys/class 里创建一个 input类, input_class变量如下图:


  • 如下图,我们启动内核,再启动一个input子系统的驱动后,也可以看到创建了个"input"类 :


  • 为什么这里代码只创建类,没有使用class_device_create()函数在类下面创建驱动设备?

  • 在下面第8小结会详细讲到,这里简单描述:当注册input子系统的驱动后,才会有驱动设备,此时这里的代码是没有驱动的

2. 上面第14行通过register_chrdev创建驱动设备,其中变量INPUT_MAJOR =13,所以创建了一个主设备为13的"input"设备。

  • 然后我们来看看它的操作结构体input_fops,如下图:




  • 只有一个.open函数,比如当我们挂载一个新的input驱动,则内核便会调用该.open函数,接下来分析该.open函数


【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码) 



 

三. 然后进入input_open_file函数(drivers/input/input.c)

  1. 第3行中,其中iminor (inode)函数调用了MINOR(inode->i_rdev);读取子设备号,然后将子设备除以32,找到新挂载的input驱动的数组号,然后放在input_handler 驱动处理函数handler中

  2. 第7行中,若handler有值,说明挂载有这个驱动,就将handler结构体里的成员file_operations * fops赋到新的file_operations *new_fops里面

  3. 第16行中, 再将新的file_operations *new_fops赋到file-> file_operations  *f_op里, 此时input子系统的file_operations就等于新挂载的input驱动的file_operations结构体,实现一个偷天换日的效果.

  4. 第18行中,然后调用新挂载的input驱动的*new_fops里面的成员.open函数

四.上面代码的input_table[]数组在初始时是没有值的,

  • 所以我们来看看input_table数组里面的数据又是在哪个函数里被赋值

  • 在input.c函数(drivers/input/input.c)中搜索input_table,找到它在input_register_handler()函数中被赋值,代码如下:

  • 就是将驱动处理程序input_handler注册到input_table[]中,然后放在input_handler_list链表中,后面会讲这个链表

五.继续来搜索input_register_handler,看看这个函数被谁来调用

  • 如下图所示,有evdev.c(事件设备),tsdev.c(触摸屏设备),joydev.c(joystick操作杆设备),keyboard.c(键盘设备),mousedev.c(鼠标设备) 这5个内核自带的设备处理函数注册到input子系统中



  • 我们以evdev.c为例,它在evdev_ini()函数中注册:

六.我们来看看这个evdev_handler变量是什么结构体,:

  • 就是我们之前看的input_handler驱动处理结构体

  1. 第5行中.fops:文件操作结构体,其中evdev_fops函数就是自己的写的操作函数,然后赋到.fops中

  2. 第6行中 .minor:用来存放次设备号

  • 其中EVDEV_MINOR_BASE=64, 然后调用input_register_handler(&evdev_handler)后,由于EVDEV_MINOR_BASE/32=2,所以存到input_table[2]中

  • 所以当open打开这个input设备,就会进入 input_open_file()函数,执行evdev_handler-> evdev_fops -> .open函数,如下图所示:




3. 第8行中.id_table : 表示能支持哪些输入设备,比如某个驱动设备的input_dev->的id和某个input_handler的id_table相匹配,就会调用.connect连接函数,如下图

4. 第3行中.connect:连接函数,将设备input_dev和某个input_handler建立连接,如下图




七.我们先来看看上图的input_register_device()函数,如何创建驱动设备的

  • 搜索input_register_device,发现内核自己就已经注册了很多驱动设备

7.1然后进入input_register_device()函数,代码如下:

  1. 第4行中,将要注册的input_dev驱动设备放在input_dev_list链表中

  2. 第6行中,其中input_handler_list在前面讲过,就是存放每个input_handle驱动处理结构体,然后list_for_each_entry()函数会将每个input_handle从链表中取出,放到handler中最后会调用input_attach_handler()函数,将每个input_handle的id_table进行判断,若两者支持便进行连接。

7.2然后我们在回过头来看注册input_handler的input_register_handler()函数,如下图所示



  • 所以,不管新添加input_dev还是input_handler,都会进入input_attach_handler()判断两者id是否有支持, 若两者支持便进行连接。

7.3我们来看看input_attach_handler()如何实现匹配两者id的:

  • 若两者匹配成功,就会自动进入input_handler 的connect函数建立连接

八.我们还是以evdev.c(事件驱动) 的evdev_handler->connect函数

  • 来分析是怎样建立连接的,如下图:




8.1 evdev_handler的.connect函数是evdev_connect(),代码如下:

  1. 第16行中,是在保存驱动设备名字,名为event%d, 比如下图(键盘驱动)event1: 因为没有设置子设备号,默认从小到大排列,其中event0是表示这个input子系统,所以这个键盘驱动名字就是event1

  2. 第18行中,是在保存驱动设备的主次设备号,其中主设备号INPUT_MAJOR=13,因为EVDEV_MINOR_BASE=64,所以此设备号=64+驱动程序本事子设备号, 比如下图(键盘驱动)event1:  主次设备号就是13,65

  3. 在之前在2小结里就分析了input_class类结构,所以第19行中,会在/sys/class/input类下创建驱动设备event%d,比如下图(键盘驱动)event1:




4. 最终会进入input_register_handle()函数来注册,代码在下面

8.2 input_register_handle()函数如下:

  1. 在第5行中, 因为handle->dev指向input_dev驱动设备,所以就是将handle->d_node放入到input_dev驱动设备的h_list链表中,

  • 即input_dev驱动设备的h_list链表就指向handle->d_node

2. 在第6行中, 同样, input_handler驱动处理结构体的h_list也指向了handle->h_node

  • 最终如下图所示:


  • 两者的.h_list都指向了同一个handle结构体,然后通过.h_list 来找到handle的成员.dev和handler,便能找到对方,便建立了连接

九.建立了连接后,又如何读取evdev.c(事件驱动) 的evdev_handler->.fops->.read函数?

  • 事件驱动的.read函数是evdev_read()函数,我们来分析下:

  • 十.若read函数进入了休眠状态,又是谁来唤醒?

  • 我们搜索这个evdev->wait这个等待队列变量,找到evdev_event函数里唤醒:


  • 其中evdev_event()是evdev.c(事件驱动) 的evdev_handler->.event成员,如下图所示:


  • 当有事件发生了,比如对于按键驱动,当有按键按下时,就会进入.event函数中处理事件

十一.分析下,是谁调用evdev_event()这个.event事件驱动函数

  • 应该就是之前分析的input_dev那层调用的

  • 我们来看看内核 gpio_keys_isr()函数代码例子就知道了 (driver/input/keyboard/gpio_key.c)

  • 显然就是通过input_event()来调用.event事件函数,我们来看看:

  • 若之前驱动input_dev和处理input_handler已经通过input_handler 的.connect函数建立起了连接,那么就调用evdev_event()的.event事件函数,如下图所示:


  • 十二.本节总结分析:

  • 1. 注册输入子系统,进入put_init():

  • 创建主设备号为13的"input"字符设备

  • 2. open打开驱动,进入input_open_file():

  • 更新设备的file_oprations

执行file_oprations->open函数


3. 注册input_handler,进入input_register_handler():

  • 添加到input_table[]处理数组中

  • 添加到input_handler_list链表中

  • 判断input_dev的id,是否有支持这个驱动的设备

  • 4. 注册input_dev,进入input_register_device():

  • 放在input_dev_list链表中

  • 判断input_handler的id,是否有支持这个设备的驱动

  • 5. 判断input_handler和input_dev的id,进入input_attach_handler():

  • 匹配两者id,

匹配成功调用input_handler ->connecthandler->connect(handler, dev, id);              //建立连接


6. 建立input_handler和input_dev的连接,进入input_handler->connect():

  • 创建全局结构体,通过input_handle结构体连接双方

7. 有事件发生时,比如按键中断,在中断函数中需要进入input_event()上报事件:

  • 找到驱动处理结构体,然后执行input_handler->event()



一文带你搞懂Linux之输入子系统分析详解(含源码)的评论 (共 条)

分享到微博请遵守国家法律