http://blog.csdn.net/chobit_s/article/details/5903223


认识linux input子系统 (一)

-0-.序

  本来只是想写个内核态的键盘记录的,但是发现现在的linux驱动模型已经和以前版本不同,增加了input层,几乎所有的底层驱动都把数据封装在event里上报给input子系统,这样一来,kernel看起来更加模块化,但是没有原来键盘驱动那种一站通的感觉了。

  于是研究起input层比起键盘记录更有意思了:)这里只是记录下自己学习后理清的思路,其实自己学习过程挺乱的,最近才有所感悟input层,毕竟硬件的底子我是没有的。


-1-.从用户层看input(event事件)

  经常捣鼓linux一定会对/dev,/sys,/proc这几个目录有所印象,这是从内核导出到用户层的接口(从这里几乎可以观览内核)。这下就方便了,kernel为我们导出了input在用户态的接口,就是/dev/input/下的接口,这里我们只关注此目录下的eventX字符设备。

  那么这些eventX是干什么用的?简单来说就是我们对计算机的输入(包括敲击键盘,移动鼠标等等操作)经过内核(底层驱动,input)处理最后就上报到这些eventX里面了。

  而这里event0,event1,..就是用来区分各个外设的,可以通过命令来查看外设具体和哪个event相关联:

  cat /proc/bus/input/devices 这里结果比较多,应为现在PC外设也蛮多的,我们可以看下键盘对应的条目,这里我截取2段:

I: Bus=0011 Vendor=0001 Product=0001 Version=ab54

N: Name="AT Translated Set 2 keyboard"

P: Phys=isa0060/serio0/input0

S: Sysfs=/devices/platform/i8042/serio0/input/input4

U: Uniq=

H: Handlers=kbd event4

B: EV=120013

B: KEY=4 2000000 3803078 f800d001 feffffdf ffefffff ffffffff fffffffe

B: MSC=10

B: LED=7


I: Bus=0003 Vendor=046d Product=c52f Version=0111

N: Name="Logitech USB Receiver"

P: Phys=usb-0000:00:1d.0-1/input1

S: Sysfs=/devices/pci0000:00/0000:00:1d.0/usb6/6-1/6-1:1.1/input/input8

U: Uniq=

H: Handlers=kbd event8

B: EV=1f

B: KEY=837fff 2c3027 bf004444 0 0 1 f84 8a27c000 667bfa d9415fed 8e0000 0 0 0

B: REL=40

B: ABS=1 0

B: MSC=10


  从上面我们可以看到H:一行是 Handlers=kbd event4(或Handlers=kbd event8)

kbd(KEYBOARD)代表键盘,而后面eventX就是此键盘在/dev/input/下就对应的eventX字符设备,

我们可以看到,linux为我准备了2个驱动,分别是AT键盘和USB键盘,这里我的笔记本使用的是AT键盘(又叫PS/2键盘,一般笔记本自带的键盘都是AT兼容的)对应于/dev/input/event4,

bash> hexdump /dev/input/event4  [现在我们随意敲击键盘,会发现大量数据涌现]

  其实这些数据都是组织好的,在linux/input.h中有这些数据的结构:

struct input_event {

        struct timeval time;//事件发生的时间

        __u16 type;//事件类类型:按键和移动鼠标就是不同类型

        __u16 code;

        __s32 value;//事件值:按键a和按键b就对应不同值

};

这里事件指的是我们对外设的操作,比如按键一次a可能就产生数个input_event数据

有了这个结构就好办了,我们可以自己写个测试程序读取/dev/input/event4中的数据:

  1. #include <stdio.h>

  2. #include <sys/time.h>

  3. #include <linux/input.h>

  4. #include <stdlib.h>

  5. void usage(char *str)  

  6. {  

  7.    fprintf(stderr, "<usage> %s /dev/input/eventX/n", str);  

  8.    exit(1);  

  9. }  

  10. int main(int argc,char **argv)  

  11. {  

  12.    FILE *fp;  

  13. struct input_event ie;  

  14. if (argc != 2)  

  15.        usage(argv[0]);  

  16.    fp = fopen(argv[1], "r");  

  17. if (fp == NULL) {  

  18.        perror(argv[1]);  

  19.        exit(1);  

  20.    }  

  21. while (1) {  

  22.        fread((void *)&ie, sizeof(ie), 1, fp);  

  23. if (ferror(fp)) {  

  24.            perror("fread");  

  25.            exit(1);  

  26.        }  

  27.        printf("[timeval:sec:%d,usec:%d,type:%d,code:%d,value:%d]/n",  

  28.                    ie.time.tv_sec, ie.time.tv_usec,  

  29.                    ie.type, ie.code, ie.value);  

  30.    }  

  31. return 0;  

  32. }  


编译成测试test1

bash> test1 /dev/input/event4  [按回车运行程序]

[timeval:sec:1285305058,usec:857706,type:4,code:4,value:28]

[timeval:sec:1285305058,usec:857718,type:1,code:28,value:0]

[timeval:sec:1285305058,usec:857721,type:0,code:0,value:0]      [之后按键a]

[timeval:sec:1285305059,usec:978376,type:4,code:4,value:30]      

[timeval:sec:1285305059,usec:978401,type:1,code:30,value:1]

[timeval:sec:1285305059,usec:978406,type:0,code:0,value:0]                  

a[timeval:sec:1285305060,usec:34315,type:4,code:4,value:30]      [行首显示我在此终端按下的a]

[timeval:sec:1285305060,usec:34327,type:1,code:30,value:0]

[timeval:sec:1285305060,usec:34329,type:0,code:0,value:0]

[timeval:sec:1285305061,usec:406962,type:4,code:4,value:29]


运行程序后,我们首先看到3组input_event显示出来了,这三组数据其实是我们刚刚运行程序时按的回车键的UP码

之后我们每按一次键都会有6组input_event显示出来,前3组是DOWN码,后3组是UP码(DOWN是按键被按下,UP是按键弹起,键盘有弹性:P)

其实这个程序写完善,配合分析input_event输出便可以做个简单的用户态按键记录了。

可以对照linux/input.h中分析input_event的结果,比如input_event.value值为30 对应于#define KEY_A 30 显然这是按键a被按下的值


既然eventX可以读,那么eventX当然可以写,写对应的结果就是 模拟人敲击键盘,蛮有趣的,这有个完整的写eventX测试代码,

由于我们可能对input_event不是很了解,所以先从eventX读input_event,稍加修改再回写进eventX.

  1. #include <stdio.h>

  2. #include <sys/time.h>

  3. #include <linux/input.h>

  4. #include <stdlib.h>

  5. void usage(char *str)  

  6. {  

  7.    fprintf(stderr, "<usage> %s /dev/input/eventX/n", str);  

  8.    exit(1);  

  9. }  

  10. int main(int argc,char **argv)  

  11. {  

  12.    FILE *fp;  

  13. struct input_event ie[6];  

  14. if (argc != 2)  

  15.        usage(argv[0]);  

  16. /* 先读取回车UP码对应的3个input_event 并将其丢弃 */

  17.    fp = fopen(argv[1], "r");  

  18. if (fp == NULL) {  

  19.        perror(argv[1]);  

  20.        exit(1);  

  21.    }  

  22.    fread((void *)ie, sizeof(struct input_event), 3, fp);  

  23. if (ferror(fp))  

  24.        perror("fread");  

  25.    fclose(fp);  

  26. /* 循环读写eventX 每次读取完整按键码:6组input_event

  27.     * 稍加修改并写回eventX

  28.     */

  29. do {  

  30.        fp = fopen(argv[1], "r");  

  31. if (fp == NULL) {  

  32.            perror(argv[1]);  

  33.            exit(1);  

  34.        }  

  35.        fread((void *)ie, sizeof(struct input_event), 6, fp);  

  36. if (ferror(fp))  

  37.            perror("fread");  

  38.        fclose(fp);  

  39.        fp = fopen(argv[1], "w");  

  40. if (fp == NULL) {  

  41.            perror(argv[1]);  

  42.            exit(1);  

  43.        }  

  44.        ie[1].code += 1;  

  45.        ie[4].code += 1;  

  46.        fwrite((void *)ie, sizeof(struct input_event), 6, fp);  

  47. if (ferror(fp)) {  

  48.            perror("fwrite");  

  49.            exit(1);  

  50.        }  

  51.        fclose(fp);  

  52. //      printf("[timeval:sec:%d,usec:%d,type:%d,code:%d,value:%d]/n",

  53. //              ie.time.tv_sec, ie.time.tv_usec,

  54. //              ie.type, ie.code, ie.value);

  55.    } while (1);  

  56. return 0;  

  57. }  


编译成test2

bash> test2 /dev/input/event4           [按回车]

asbncv                                                [连续敲击abc]

这里KEY_A == 30 KEY_S == 31(即ie[1].code += 1;ie[4].code += 1;)

以上测试代码有几个值得注意的地方:

1.首先要清除我们运行测试程序时 回车的UP码

2.eventX不能同时读写,先读出1次敲击键盘的完整6个input_event,再回写入eventX(若不这样做,测试不会成功)

3.input_event.time不用修改,因为此项被内核忽视([?]从evdev_write()中可以看出内核并没有检测时间,而是直接响应事件)

经过测试,更改第0,3项input_event.value并没有反映,更改第1,4项的code才有反映([?]这里更改此项只是欺骗Xwindows,Xwindows解析/dev/input/eventX向用户回显外设操作,至于详细input_event解析就要到Xwindows代码中去找了,我没看过不敢多说)


这个测试程序只是简单模拟了按键事件

-----------------------------------------------------------------------------

发现语言不简洁,之后是从内核,从驱动看input:)