我们这一期来具体分析一下EventHub是如何读取我们底层设备的事件的,首先我们来看一下图
当我们需要读取一个输入设备的话,我们所需要做的事情首先是打开我们的设备,设备的路径就是/dev/input下面的某一个输入设备。然后需要对我们的设备进行读取,读取操作一个是用read进行读取,这种方法可能是阻塞的,也可能是非阻塞的,但是会一直进行轮训。第二种就是使用poll这种方法,将文件描述符添加到我们poll的对象中,然后如果有消息过来就可以进行监听,这样的话可以在一个线程中监听多个输入设备,然后如果某一个设备有输入事件的话,就直接调取read读取事件,这样做一个循环的读取。最后当我们不使用这个设备的时候就可以关闭掉。这就是我们Linux做一个设备读入的标准的操作。
下面我们来看一下当我们进行read的时候需要使用一个struct input_event的结构
我们需要用这个数据结构来读取我们输入设备的事件,他的组成主要有几个成员变量,第一个是struct timeval time,这个time变量用来记录我们底层发上来的一个时间。第二个是type,type是一个事件的类型,在我们的底层事件中,type可以分为EV_SYN这个表示我们设备支持所有的事件,值是0X00.第二个是EV_KEY,他的值是0X01,代表键盘或者按键消息。第三个是EV_REL,他的值是0X02,代表鼠标设备的事件,比如鼠标滚动。最后一个是EV_ABS,他的值是0X03,代表我们触摸屏产生的一个值,他的值是一个绝对整数值。第三个是我们的code/value这个code和value会根据type的不同,而有不同的定义。如果说我们type的值是EV_KEY,这时候我们的code就代表我们键盘的一个值或者鼠标的一个button值,也就是左键和右键,那么value值代表的就是按键的抬起和按下,0表示抬起1表示按下,当我们触发一个键盘事件的话,输入一个C,那么他首先会触发一个1,代表按键已经按下,在没有发生0的时候就代表按键一直按着,这样一个按键的抬起和按下就能告诉我们的上层。第二个是EV_REL,这个代表一个鼠标的事件,其中code代表操作的是x/y轴,而value值代表的是鼠标具体的一个移动的值和方向,它是有正负号的,这时候就能知道一个鼠标的上下左右的移动,他其实是由两个消息的,第一个和第二个是x或者y轴的一个值,第三个会有一个说明,说明我们鼠标已经结束,所以我们鼠标才能在屏幕上画出一个轨迹。第三个是我们的EV_ABS,也就是我们的touch事件,code代表绝对坐标轴的一个轴向,而value值是相对于轴的一个绝对位置,因为我们的touch事件是从(0,0)点开始的,你向左或者向右移动绝对在我们设置的最大范围之内,比如我们的屏幕是720p的,那么纵向的最大值就是1080,而横向的就是720,这个值是固定的。下面我们看一下Android中的getevent命令的实现。他的实现首先是打开/dev/input目录并且对它进行监听,如果有热插拔设备插入或者拔出,那么他会做一个相应的操作;然后打开dev/input这个目录下的所有input设备,然后对它进行一个轮训,如果我们有设备有输入的话,那么就会拿到这个设备的事件,并且根据input的结构把事件给打出来,这样就能够知道我们那个设备有按键的输入,我们看一下getevent的代码的具体实现,打开system/core/toolbox/getevent.c,然后找到main函数,
在这里会有一个目录device_path,我们来看一下他的具体操作,如果说我们传的参数里面指定了某一个设备,那么就会直接打开这个设备,如果没有加参数,那么我们会打开dev下面的所有目录,在这里创建了一个inotify_init(),也就是要监听我们的dev/input目录有没有发生变化,然后会把这个目录inotify_add_watch添加到ufds[0]中,这样我们就能监控这个目录,监控的事件一个是DELETE,一个是CREATE,如果我插入一个鼠标就会检查到IN_CREATE的消息,如果拔出来之后会得到DELETE消息。第二个就是scan_dir我们的目录,他就是打开我们的目录,把这个目录下的所有设备都一个一个打开,打开的过程就是调用我们的open_device。我们来看一下这个函数
Open_device打开的过程就是传进来一个设备的名字然后打开它,打开过程中会显示一些其他的log,如果有错误的话会显示打开失败。然后在这里会拿到这个设备的一些version和ID,最后我们会拿到设备的名字,拿到之后他会申请一个内存,也就是realloc一下,然后相当于把ufds添加了一个列表,类似于列表的形式添加到ufds中,这样我们使用poll的话就能监听所有的文件描述符。当我们把这个添加完成之后,他在最后就把他添加到ufds这个列表中,其中这个fd就是我们要打开的设备,而我们监控的事件就是POLLIN,而这个文件描述符它所对应的名字就是我们传进来的那个设备名。这样当我们捕获一个事件就知道是哪个设备触发了一个input事件。当我们把open都打开之后我们来看一下他是如何来做轮训的
在scan_dir完成之后沃恩直接进入一个poll,如果说我们ufds[0],也就是监控的那个文件描述符有消息,那么就会读取这个notify,notify无非就是两种形式,一种是增加一个目录,;另一个就是减少一个目录。如果说我们event的mask是CREATE,那么我们重新打开一个设备,把这个设备添加到ufds这个数组中。如果不是CREATE那么肯定就是DELETE,那么我们就把这个文件从数组中给他删除掉,然后我们再去重新监听,如果说有其他事件,这样我们就直接读取事件,把事件的值给打出来,还会打印一些消息,包括type、code、value以及我们的flags,我们来看一下print_event
如果说是EV_SYN,那么会有一个get_label,当我们得到这些label,把他们打出来,这就是getevent打印的值,包括type_label,如果说没有那么就不打印,最后直接打印type,type的值就是type、code、value。下面我们切到adb下来看一下getevent的具体操作,我们调用一下getevent,我们先不加-l选项
首先在这里会有add device,第一个是我们的event5,这个5是我们的触摸屏,event4是我们的一个USB鼠标,event3是我们的一个耳机,event2是一个红外,event1是一个board,event0暂时还不知道是什么,我们先不管他,现在我们将鼠标拔下来,这里会有一个事件
这个事件是remove device,这时候就会发现这个设备已经不再监听范围之内了。现在我们再将鼠标插上,插上之后这里就新添加了一个设备,这个设备就是我们的event4,他的类型是一个USB设备。那么现在我动一下鼠标会有很多事件出来
首先他会显示我们设备的名字是/dev/input/event4所发生的事件,而我们这个事件的类型就是0002,后面就分别代表我们x轴或者y轴,而在后面代表我们鼠标具体移动的一个值,后边还有一个ffffffff,f代表负方向的一个值。下面我们再来看一下红外按键的值
当我们按一下的时候,它发生了两个事情,第一个是我们event2 设备所传上来的,设备的类型是01,这个01就是我们的一个键盘值。如果说我们的type是key的话,code就代表具体的值,而value会代表按键的抬起挥着按下,当我们按起的时候,他首先是一个按下,然后回报一个抬上,抬上的键值和type是一样的,只不过是0000和0001是有区别的。下面我们再来看一下触摸屏,我们首先触摸一下触摸屏
它是由event5触发出来的,手写板的值是03,当我们向上传的时候他也是由x轴和y轴方向的。那么我们来使用getevent-l来看一下每一个值的具体含义
首先我们动一下鼠标,他首先会报一下x轴和y轴的值,如果x轴有变化,那么他会把x轴的方向给报出来,y轴的方向也给报出来,这个值就是EV_REL,是我们鼠标一个值,下面我们再来看一下我们按键的一个值,我们按下一个按键
我们按下的是backspace,他首先是传了一个down然后是up,这样我们上层也能够收到,我们再来看一下触摸屏事件
在触摸的时候是一个多点触摸事件,这里面会有我们x轴和y轴的一个坐标,还有一个SYN,这个是在触摸过程中会向上报一个有没有结束,我们这个触摸屏事件其实还有好多种呢,他首先会有一个TRACKING_ID,然后是touch的一个消息,再就是xy坐标轴的一个变动,还有一个WIDTH_MAJOR的一个变动,最后会有一个SYN_REPORT,只有到SYN的时候我们的事件才会真正的报到上层,我们的屏幕才会响应他们每一个触摸的过程,这个就是我们触摸屏引发的事件,以及和我们上层对接的一个过程,我们上层解析这个过程也是等到SYN report的时候才会报这个事件,通过这个过程我们知道在Linux上如何对我们底层来进行一个调试。