Linux应用开发基础知识——输入系统应用编程(七)

前言:

常见的输入设备有键盘、鼠标、遥控杆、书写板、触摸屏等等,用户通过这些 输入设备与 Linux 系统进行数据交换。

输入设备种类繁多,能否统一它们的接口?既在驱动层面统一,也在应用程序层面统一?事实上是可以的。 Linux 系统为了统一管理这些输入设备,实现了一套能兼容所有输入设备的 框架:输入系统。驱动开发人员基于这套框架开发出程序,应用开发人员就可以使用统一的 API 去使用设备。

目录

一、输入系统框架及调试

1.框架概述

2.编写 APP 需要掌握的知识

(1)内核中怎么表示一个输入设备?

(2)APP 可以得到什么数据?

(3)输入子系统支持完整的 API 操作

3.调试技巧

(1)确定设备信息

(2)使用命令读取数据

二、不使用库的应用程序示例

1.输入系统支持完整的 API 操作

2.APP 访问硬件的 4 种方式:

3.获取设备信息

4.查询方式

5.休眠-唤醒方式

6.POLL/SELECT 方式 

(1)功能介绍

(2)编程:使用 POLL

7.异步通知方式

(1)功能介绍

(2)应用编程


一、输入系统框架及调试

1.框架概述

        作为应用开发人员,可以只基于API 使用输入子系统。但是了解内核中输入 子系统的框架、了解数据流程,有助于解决开发过程中碰到的硬件问题、驱动问题。

Linux应用开发基础知识——输入系统应用编程(七)_第1张图片

        假设用户程序直接访问/dev/input/event0 设备节点,或者使用 tslib 访问设备节点,数据的流程如下:

(1)  APP 发起读操作,若无数据则休眠;

(2)  用户操作设备,硬件上产生中断;

(3)  输入系统驱动层对应的驱动程序处理中断:

        读取到数据,转换为标准的输入事件,向核心层汇报。

        所谓输入事件就是一个“struct input_event”结构体。

(4)核心层可以决定把输入事件转发给上面哪个 handler 来处理:

        从handler的名字来看,它就是用来输入操作的。有多种handler,比如:evdev_handler、kbd_handler、joydev_handler 等等。

(5)  APP 对输入事件的处理:

        APP 获 得 数 据 的 方 法 有 2 种 : 直接访问设备节点 ( 比如 /dev/input/event0,1,2,...),或者通过 tslib、libinput 这类库来间接访问设备节点。这些库简化了对数据的处理。

2.编写 APP 需要掌握的知识

Linux应用开发基础知识——输入系统应用编程(七)_第2张图片

(1)内核中怎么表示一个输入设备?

     使用 input_dev 结构体来表示输入设备,它的内容如图

Linux应用开发基础知识——输入系统应用编程(七)_第3张图片

(2)APP 可以得到什么数据?

可以得到一系列的输入事件,就是一个“struct input_event”,它定义如图

Linux应用开发基础知识——输入系统应用编程(七)_第4张图片

        每个输入事件 input_event 中都含有发生时间:timeval 表示的是“自系统启动以来过了多少时间”,它是一个结构体,含有“tv_sec、tv_usec”两项 (即秒、微秒)。

        输入事件 input_event 中更重要的是type(哪类事件)、code(哪个事件)、 value(事件值),细讲如下:

type:表示哪类事件

        比如 EV_KEY 表示按键类、EV_REL 表示相对位移(比如鼠标),EV_ABS 表示绝对位置(比如触摸屏)。有下图这几类事件:

Linux应用开发基础知识——输入系统应用编程(七)_第5张图片

code:表示该类事件下的哪一个事件

        比如对于 EV_KEY(按键)类事件,它表示键盘。键盘上有很多按键,比如数字键 1、2、3,字母键 A、B、C 里等。所以可以有下图这些事件:

Linux应用开发基础知识——输入系统应用编程(七)_第6张图片

        对于触摸屏,它提供的是绝对位置信息,有 X 方向、Y 方向,还有压力值。 所以 code 值有下图这些:

Linux应用开发基础知识——输入系统应用编程(七)_第7张图片

value:表示事件值

        对于按键,它的 value 可以是 0(表示按键被按下)、1(表示按键被松开)、 2(表示长按); 对于触摸屏,它的 value 就是坐标值、压力值。

事件之间的界线

        APP 读取数据时,可以得到一个或多个数据,比如一个触摸屏的一个触点会 上报 X、Y 位置信息,也可能会上报压力值。

(3)输入子系统支持完整的 API 操作

        支持这些机制:阻塞、非阻塞、POLL/SELECT、异步通知

3.调试技巧

(1)确定设备信息

        输入设备的设备节点名为/dev/input/eventX(也可能是/dev/eventX,X 表示 0、1、2 等数字)。查看设备节点,可以执行以下命令:

ls /dev/input/* -l

或者

ls /dev/event* -l

Linux应用开发基础知识——输入系统应用编程(七)_第8张图片

 怎么知道这些设备节点对应什么硬件呢?可以在板子上执行以下命令:

cat /proc/bus/input/devices

这条指令的含义就是获取与 event 对应的相关设备信息,可以看到类似以下的结果:

Linux应用开发基础知识——输入系统应用编程(七)_第9张图片

 I:id of the device(设备 ID)

该参数由结构体 struct input_id 来进行描述,驱动程序中会定义这样的结构体:

Linux应用开发基础知识——输入系统应用编程(七)_第10张图片

N:name of the device

设备名称

P:physical path to the device in the system hierarchy

系统层次结构中设备的物理路径

S:sysfs path

位于 sys 文件系统的路径

U:unique identification code for the device(if device has it)

设备的唯一标识码

H:list of input handles associated with the device.

与设备关联的输入句柄列表。

B:bitmaps(位图)

PROP:device properties and quirks(设备属性)

EV:types of events supported by the device(设备支持的事件类型)

KEY:keys/buttons this device has(此设备具有的键/按钮)

MSC:miscellaneous events supported by the device(设备支持的其他事件)

LED:leds present on the device(设备上的指示灯)

注:B 位图,比如B: EV=b”用来表示该设备支持哪类输入事件。b 的二进制是 1011,bit0、1、3 为 1,表示该设备支持 0、1、3 这三类事件,即 EV_SYN、EV_KEY、EV_ABS

“B: ABS=2658000 3”它表示该设备支持 EV_ABS 这一类事件中的哪一些事件。这是 2 个 32 位的数字:0x2658000、0x3,高位在前低位在后,组成一个 64 位的数字: “0x2658000,00000003”,数值为 1 的位有:0、1、47、48、50、53、54,即: 0、1、0x2f、0x30、0x32、0x35、0x36,对应以下这些宏:

Linux应用开发基础知识——输入系统应用编程(七)_第11张图片

   即这款输入设备支持上述的ABS_X 、ABS_Y 、ABS_MT_TOUCH_MAJOR 、ABS_MT_SLOT 、ABS_MT_WIDTH_MAJOR 、 ABS_MT_POSITION_X 、 ABS_MT_POSITION_Y 这些绝对位置事件

(2)使用命令读取数据

调试输入系统时,直接执行类似下面的命令,然后操作对应的输入设备即可读出数据:

Linux应用开发基础知识——输入系统应用编程(七)_第12张图片

        上图中type 为 3 ,对应 EV_ABS ; code 为 0x35 对 应 ABS_MT_POSITION_X;code 为 0x36 对应 ABS_MT_POSITION_Y。

        上图中还发现有 2个同步事件:它的 type、code、value 都为 0。表示电 容屏上报了 2次完整的数据。

二、不使用库的应用程序示例

1.输入系统支持完整的 API 操作

支持这些机制:阻塞、非阻塞、POLL/SELECT、异步通知。

2.APP 访问硬件的 4 种方式:

妈妈怎么知道孩子醒了?

Linux应用开发基础知识——输入系统应用编程(七)_第13张图片

(1)时不时进房间看一下:查询方式

   简单,但是累

(2)进去房间陪小孩一起睡觉,小孩醒了会吵醒她:休眠-唤醒

  不累,但是妈妈干不了活了

(3)妈妈要干很多活,但是可以陪小孩睡一会,定个闹钟:poll 方式

  要浪费点时间,但是可以继续干活,妈妈要么是被小孩吵醒,要么是被闹钟吵醒。

(4)妈妈在客厅干活,小孩醒了他会自己走出房门告诉妈妈:异步通知

  妈妈、小孩互不耽误。

这 4 种方法没有优劣之分,在不同的场合使用不同的方法

3.获取设备信息

通过 ioctl 获取设备信息,ioctl 的参数如下:

int ioctl(int fd, unsigned long request, ...);

有些驱动程序对 request 的格式有要求,它的格式如下:

Linux应用开发基础知识——输入系统应用编程(七)_第14张图片

        比如 dir 为_IOC_READ(即 2)时,表示 APP 要读数据;为_IOC_WRITE(即 4)时, 表示 APP 要写数据。

        size 表示这个 ioctl 能传输数据的最大字节数。

        type、nr 的含义由具体的驱动程序决定。

        比如要读取输入设备的 evbit 时,ioctl 的 request 要写为“EVIOCGBIT(0, size)”,size 的大小可以由你决定:你想读多少字节就设置为多少。这个宏的 定义如下:

 ./01_get_input_info /dev/input/event0 

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7
  8
  9 /* ./01_get_input_info /dev/input/event0 */
 10 int main(int argc, char **argv)
 11 {
 12     int fd;
 13     int err;
 14     int len;
 15     int i;
 16     unsigned char byte;
 17     int bit;
 18     struct input_id id;
 19     unsigned int evbit[2];
 20     char *ev_names[] = {
 21         "EV_SYN ",
 22         "EV_KEY ",
 23         "EV_REL ",
 24         "EV_ABS ",
 25         "EV_MSC ",
 26         "EV_SW  ",
 27         "NULL ",
 28         "NULL ",
 29         "NULL ",
 30         "NULL ",
 31         "NULL ",
 32         "NULL ",
 33         "NULL ",
 34         "NULL ",
 35         "NULL ",
 36         "NULL ",
 37         "NULL ",
 38         "EV_LED ",
 39         "EV_SND ",
 40         "NULL ",
 41         "EV_REP ",
 42         "EV_FF  ",
 43         "EV_PWR ",
 44         };
 45
 46     if (argc != 2)
 47     {
 48         printf("Usage: %s \n", argv[0]);
 49         return -1;
 50     }
 51
 52     fd = open(argv[1], O_RDWR);
 53     if (fd < 0)
 54     {
 55         printf("open %s err\n", argv[1]);
 56         return -1;
 57     }
 58
 59     err = ioctl(fd, EVIOCGID, &id);
 60     if (err == 0)
 61     {
 62         printf("bustype = 0x%x\n", id.bustype );
 63         printf("vendor  = 0x%x\n", id.vendor  );
 64         printf("product = 0x%x\n", id.product );
 65         printf("version = 0x%x\n", id.version );
 66     }
 67
 68     len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
 69     if (len > 0 && len <= sizeof(evbit))
 70     {
 71         printf("support ev type: ");
 72         for (i = 0; i < len; i++)
 73         {
 74             byte = ((unsigned char *)evbit)[i];
 75             for (bit = 0; bit < 8; bit++)
 76             {
 77                 if (byte & (1<
book@100ask:~/source/11_input/01_app_demo$ arm-buildroot-linux-gnueabihf-gcc -o 01_get_input_info 01_get_input_info.c
book@100ask:~/source/11_input/01_app_demo$ cp 01_get_input_info ~/nfs_rootfs/
[root@100ask:/]#  mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
[root@100ask:/]#  cd /mnt/

Linux应用开发基础知识——输入系统应用编程(七)_第15张图片

4.查询方式

        APP 调用 open 函数时,传入“O_NONBLOCK”表示“非阻塞”。

        APP 调用 read 函数读取数据时,如果驱动程序中有数据,那么 APP 的 read 函数会返回数据,否则也会立刻返回错误。

5.休眠-唤醒方式

        APP 调用 open 函数时,不要传入“O_NONBLOCK”。

        APP 调用 read 函数读取数据时,如果驱动程序中有数据,那么 APP 的 read 函数会返回数据;否则 APP 就会在内核态休眠,当有数据时驱动程序会把 APP 唤 醒,read 函数恢复执行并返回数据给 APP。

./01_get_input_info /dev/input/event0 noblock

  8 #include 
  9 #include 


 23     struct input_event event;
 51     if (argc < 2)
 52     {
 53         printf("Usage: %s  [noblock]\n", argv[0]);
 54         return -1;
 55     }
 56
 57     if (argc == 3 && !strcmp(argv[2], "noblock"))
 58     {
 59         fd = open(argv[1], O_RDWR | O_NONBLOCK);
 60     }
 61     else
 62     {
 63         fd = open(argv[1], O_RDWR);
 64     }
 97     while (1)
 98     {
 99         len = read(fd, &event, sizeof(event));
100         if (len == sizeof(event))
101         {
102             printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
103         }
104         else
105         {
106             printf("read err %d\n", len);
107         }
108     }
book@100ask:~/source/11_input/01_app_demo$ arm-buildroot-linux-gnueabihf-gcc -o 02_input_read 02_input_read.c
book@100ask:~/source/11_input/01_app_demo$ cp 02_input_read ~/nfs_rootfs/
[root@100ask:/mnt]# /mnt/02_input_read /dev/input/event0

Linux应用开发基础知识——输入系统应用编程(七)_第16张图片

6.POLL/SELECT 方式 

(1)功能介绍

        POLL 机制、SELECT 机制是完全一样的,只是 APP 接口函数不一样。 简单地说,它们就是“定个闹钟”:在调用 poll、select 函数时可以传入 “超时时间”。在这段时间内,条件合适时(比如有数据可读、有空间可写)就会立刻返回,否则等到“超时时间”结束时返回错误。

⚫ APP 先调用 open 函数时

⚫ APP 不是直接调用 read 函数,而是先调用 poll 或 select 函数,这 2 个函数中可以传入“超时时间”。它们的作用是:如果驱动程序中有数据,则立刻返回; 否则就休眠。在休眠期间,如果有人操作了硬件,驱动程序获得数据后就会把 APP 唤醒,导致 poll 或 select 立刻返回;如果在“超时时间”内无人操作硬件,则时间到后 poll 或 select 函数也会返回。APP 可以根据函数的返回值判断返回 原因:有数据?无数据超时返回?

⚫ APP 根据 poll 或 select 的返回值判断有数据之后,就调用 read 函数读取数据时,这时就会立刻获得数据。

poll/select 函数可以监测多个文件,可以监测多种事件:

Linux应用开发基础知识——输入系统应用编程(七)_第17张图片

在调用 poll 函数时,要指明:

⚫ 你要监测哪一个文件:哪一个 fd

⚫ 你想监测这个文件的哪种事件:是 POLLIN、还是 POLLOUT

最后,在 poll 函数返回时,要判断状态。

(2)编程:使用 POLL

10 #include 

26     struct pollfd fds[1];
27     nfds_t nfds = 1;
 55     if (argc != 2)
 56     {
 57         printf("Usage: %s \n", argv[0]);
 58         return -1;
 59     }
 60
 61     fd = open(argv[1], O_RDWR | O_NONBLOCK);
 62     if (fd < 0)
 63     {
 64         printf("open %s err\n", argv[1]);
 65         return -1;
 66     }
 14 int main(int argc, char **argv)
 15 {
 16 int fd;
 26 struct pollfd fds[1];
 ......
 61     fd = open(argv[1], O_RDWR | O_NONBLOCK);
 ......
 94     while (1)
 95     {
 96         fds[0].fd = fd;
 97         fds[0].events  = POLLIN;
 98         fds[0].revents = 0;
 99         ret = poll(fds, nfds, 5000);
100         if (ret > 0)
101         {
102             if (fds[0].revents == POLLIN)
103             {
104                 while (read(fd, &event, sizeof(event)) == sizeof(event))
105                 {
106                     printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
107                 }
108             }
109         }
110         else if (ret == 0)
111         {
112             printf("time out\n");
113         }
114         else
115         {
116             printf("poll err\n");
117         }
118     }

第 61 行:打开设备文件。

第 96~98 行:设置 pollfd 结构体。

第 96 行:想查询哪个文件(fd)? 第 97 行:想查询什么事件(POLLIN)?

第 98 行:先清除“返回的事件”(revents)。

第 99 行:使用 poll 函数查询事件,指定超时时间为 5000(ms)。

第 100、110 行判断返回值:大于 0 表示期待的事件发生了,等于 0 表示超 时。 

book@100ask:~/source/11_input/01_app_demo$ arm-buildroot-linux-gnueabihf-gcc -o 03_input_read_poll 03_input_read_poll.c
book@100ask:~/source/11_input/01_app_demo$ cp 03_input_read_poll ~/nfs_rootfs/
[root@100ask:/mnt]# /mnt/03_input_read_poll /dev/input/event0

Linux应用开发基础知识——输入系统应用编程(七)_第18张图片

7.异步通知方式

(1)功能介绍

        所谓同步,就是“你慢我等你”。

        那么异步就是:你慢那你就自己玩,我做自己的事去了,有情况再通知我。 所谓异步通知,就是 APP 可以忙自己的事,当驱动程序用数据时它会主动给 APP 发信号,这会导致 APP 执行信号处理函数。

         仔细想想“发信号”,这只有 3 个字,却可以引发很多问题:

        ⚫ 谁发:驱动程序发

        ⚫ 发什么:信号

        ⚫ 发什么信号:SIGIO

        ⚫ 怎么发:内核里提供有函数

        ⚫ 发给谁:APP,APP 要把自己告诉驱动

        ⚫ APP 收到后做什么:执行信号处理函数

        ⚫ 信号处理函数和信号,之间怎么挂钩:APP 注册信号处理函数

        小孩通知妈妈的事情有很多:饿了、渴了、想找人玩。

        Linux 系统中也有很多信号,在 Linux 内核源文件 include\uapi\asm-generic\signal.h 中,有很多信号的宏定义:

Linux应用开发基础知识——输入系统应用编程(七)_第19张图片

        驱动程序通知 APP 时,它会发出“SIGIO”这个信号,表示有“IO 事件”要处理。

         就 APP 而言,你想处理 SIGIO 信息,那么需要提供信号处理函数,并且要跟 SIGIO 挂钩。这可以通过一个 signal 函数来“给某个信号注册处理函数”,用法如下:

Linux应用开发基础知识——输入系统应用编程(七)_第20张图片

除了注册 SIGIO 的处理函数,APP 还要做什么事?

⚫ 内核里有那么多驱动,你想让哪一个驱动给你发 SIGIO 信号?

         APP 要打开驱动程序的设备节点

⚫ 驱动程序怎么知道要发信号给你而不是别人?

        APP 要把自己的进程 ID 告诉驱动程序

⚫ APP 有时候想收到信号,有时候又不想收到信号

        应该可以把 APP 的意愿告诉驱动:设置 Flag 里面的 FASYNC 位为 1,使能“异步通知”。

(2)应用编程

  9 #include 
 10 #include 
 11 #include 

 35     unsigned int flags;
 36     int count = 0;
1)编写信号处理函数:
 15 void my_sig_handler(int sig)
 16 {
 17     struct input_event event;
 18     while (read(fd, &event, sizeof(event)) == sizeof(event))
 19     {
 20         printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n    ", event.type, event.code, event.value);
 21     }
 22 }
2)注册信号处理函数:
 70     /* 注册信号处理函数 */
 71     signal(SIGIO, my_sig_handler);
3)打开驱动程序:
 73     /* 打开驱动程序 */
 74     fd = open(argv[1], O_RDWR | O_NONBLOCK);
4)把进程 ID 告诉驱动:
107     /* 把APP的进程号告诉驱动程序 */
108     fcntl(fd, F_SETOWN, getpid());
5)使能驱动的 FASYNC 功能:
110     /* 使能"异步通知" */
111     flags = fcntl(fd, F_GETFL);
112     fcntl(fd, F_SETFL, flags | FASYNC);
114     while (1)
115     {
116         printf("main loop count = %d\n", count++);
117         sleep(2);
118     }
book@100ask:~/source/11_input/01_app_demo$ arm-buildroot-linux-gnueabihf-gcc -o 05_input_read_fasync 05_input_read_fasync.c
book@100ask:~/source/11_input/01_app_demo$ cp 05_input_read_fasync ~/nfs_rootfs/

Linux应用开发基础知识——输入系统应用编程(七)_第21张图片

你可能感兴趣的:(linux,运维,IMX6ULL,poll,异步通知,查询方式,man命令查看文件)