我们上一章完成了input子系统的设备构成,并且在用户空间通过hexdump命令拿到了一堆不知道是什么的信息。今天我们就要借助input_event这个结构体来了解内核怎么通过那个结构体了解输入事件。
可能有心人已经发现了,上一章我们在加载完模块以后在/dev/input路径下生成了一个新的event文件
在按键被按下以后,驱动就会向内核上报一个输入事件,而不同的输入设备都会有不同的实际内容上报给内核。所以Linux内核就整定了一个通用的结构体来描述每个事件。我们在用户态的程序就可以通过上面图中的event1来了解按键的实际状态。
input_event结构
想要知道event1文件在按键按下时打印的内容
首先要知道input_event结构体的内容(include/uapi/linux/input.h,注意路径,不要搞错了!)
1 /* 2 * The event structure itself 3 */ 4 5 struct input_event { 6 struct timeval time; 7 __u16 type; 8 __u16 code; 9 __s32 value; 10 };
input_event结构体的内容很简单,先是一个timval结构体,这个结构体也可以展开看看!
1 struct timeval { 2 __kernel_time_t tv_sec; /* seconds */ 3 __kernel_suseconds_t tv_usec; /* microseconds */ 4 };
一个秒,一个微秒,查到底两个变量都是long型的数据,也就是32位的数据。
type就是事件类型,在我们驱动中定义的是EV_KEY。16位
code,事件码,在按键驱动中就是我们定义的键值。16位
value,值,在驱动中用来描述按键是否被按下或松开。32位
可以注意到,这个input_event结构体里后三个成员跟前面input_event函数中后面三个参数是一样的。按照上面的数据结构可以分析一下打印出来的信息,可以发现是这种结构的
编号 timeval_sec timeval_usec type code value 0000000 2217 0000 d132 0005 0001 000b 0001 0000 0000010 2217 0000 d132 0005 0000 0000 0000 0000 0000020 2217 0000 e29b 0006 0001 000b 0000 0000 0000030 2217 0000 e29b 0006 0000 0000 0000 0000
上面的信息都是由16进制来表示的,所以每个0都表示了4个bit。
第一组是编号,表示
第2、3列表示秒,一共8个数字,对应32位
第4、5列表示微秒
第6列为事件类型,对应EV_KEY,第一行是0001,就是对应的EV_KEY的值,第2行的0000对应EV_SYN事件
#define EV_SYN 0x00 #define EV_KEY 0x01
就是说按键在按下的时候,会上报一个EV_KEY事件,然后在定时处理函数里还调用了一个同步函数
input_sync(dev->inputdev);
可以转到这个函数的定义看一下,这个函数会上报一个EV_SYN信号
1 static inline void input_sync(struct input_dev *dev) 2 { 3 input_event(dev, EV_SYN, SYN_REPORT, 0); 4 }
所以就会有第2行那个0000,对应的type就是EV_SYN。
第7列是code,对应我们定义的key_value,我们在初始化input_dev的时候定义的事件code是keybit,值为KEY_0,KEY_0是个宏
#define KEY_0 11
十进制值为11,对应的code就是000b。
最后是value的值,表示我们上报的按键值,1对应按键按下,0表示释放。但是在不松开的时候这个值是1和2来回变化,我不知道是怎么来的。(这个值我感觉是个小端模式,0001在左边,包括前面那个时间的数据,感觉也是左边4个数先跳,后面的再变)。所以打印出来的这4行数据分别表示了按键按下事件——同步事件——按键弹起事件——同步事件。
应用程序编写
在前面写驱动的时候我们把文件操作集合以及对应的函数都删掉了,通过input子系统操作的输入设备对应的文件就是前面我们关注的/dev/input/路径下的event文件,也就是如果我们想要获取按键的状态,就要在用户态程序中将前面通过hexdump命令打印出的数据解析出来。
先放代码
1 /** 2 * @file inputAPP.c 3 * @author your name (you@domain.com) 4 * @brief input子系统测试APP 5 * @version 0.1 6 * @date 2022-08-28 7 * 8 * @copyright Copyright (c) 2022 9 * 10 */ 11 #include <sys/types.h> 12 #include <sys/stat.h> 13 #include <fcntl.h> 14 #include <unistd.h> 15 #include <stdio.h> 16 #include <stdlib.h> 17 #include <sys/ioctl.h> 18 #include <linux/input.h> 19 20 /** 21 * @brief 22 * 23 * @param argc //参数个数 24 * @param argv //参数 25 * @return int 26 */ 27 int main(int argc,char *argv[]) 28 { 29 char *filename; //文件名 30 int ret = 0; //初始化操作返回值 31 int f = 0; //初始化文件句柄 32 33 struct input_event inputevent; //inputevent事件结构体 34 35 if(argc != 2){ 36 printf("format err!/r/n"); 37 return -1; 38 } 39 40 filename = argv[1]; // 41 f = open(filename, O_RDWR); //打开文件 42 if(f < 0){ 43 printf("file open error/r/n"); 44 return -1; 45 } 46 47 while(1){ 48 ret = read(f,&inputevent,sizeof(inputevent)); //读取文集 49 if(ret<0){ 50 //读取错误 51 printf("data read err/r/n"); 52 } 53 else{ 54 switch(inputevent.type) { 55 case EV_SYN: 56 break; 57 case EV_KEY: 58 printf("btn event/r/n"); 59 if(inputevent.code< BTN_MISC){ 60 //按键键值为KEY_xxx 61 printf("key %d %s/r/n",inputevent.code,inputevent.value?"pressed":"released"); 62 } 63 else{ 64 //按键键值为BTN_xxx 65 printf("button %d %s/r/n",inputevent.code,inputevent.value?"pressed":"released"); 66 } 67 break; 68 case EV_REL: 69 break; 70 } 71 } 72 } 73 close(f); //关闭文件 74 return 0; 75 }
整个应用程序的思路还是比较清晰度,主要就是在主程序里声明了一个inputevent结构体变量(教程里是按照全局变量的方法声明在主函数外部的)。打开文件什么的就不说了,我们要关注的就是从54行开始的switch结构,如果inputevent的type是EV_KEY的话,就按照键值读取。并且在case里做了一个判断,inputevent的value在0~255之间是对应的是键盘的键值,否则是button(如果我们只是当个按键使用不建议把按钮模拟成键盘,所以在写驱动的时候可以把bitkey的值放在btn区间)。
有意思的是即便我们并没有在驱动或应用程序中定义文件读写的阻塞或非阻塞的模式,但是我感觉实际状态下应该是按照阻塞模式读取的文件,可以在程序以后台模式运行以后通过top命令查询一下
/lib/modules/4.1.15 # ./inputAPP /dev/input/event1 &
可以看出来,程序并没有占用过多的资源。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/282725.html