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

本文涉及的产品
语种识别,语种识别 100万字符
文档翻译,文档翻译 1千页
图片翻译,图片翻译 100张
简介: Linux应用开发基础知识——输入系统应用编程(七)

一、输入系统框架及调试

1.框架概述

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

       假设用户程序直接访问/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 需要掌握的知识

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

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

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

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

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

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

type:表示哪类事件

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

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

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

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

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

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

cat /proc/bus/input/devices

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

I:id of the device(设备 ID)

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

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,对应以下这些宏:

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

(2)使用命令读取数据

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

       上图中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 种方式:

妈妈怎么知道孩子醒了?

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

  简单,但是累

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

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

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

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

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

 妈妈、小孩互不耽误。

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

3.获取设备信息

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

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

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

       比如 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 <linux/input.h>
  2 #include <sys/types.h>
  3 #include <sys/stat.h>
  4 #include <fcntl.h>
  5 #include <sys/ioctl.h>
  6 #include <stdio.h>
  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 <dev>\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<<bit)) {
 78                     printf("%s ", ev_names[i*8 + bit]);
 79                 }
 80             }
 81         }
 82         printf("\n");
 83     }
 84
 85     return 0;
 86 }
 87
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/

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 <string.h>
  9 #include <unistd.h>
 
 
 23     struct input_event event;
 51     if (argc < 2)
 52     {
 53         printf("Usage: %s <dev> [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

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 函数可以监测多个文件,可以监测多种事件:

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

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

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

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

(2)编程:使用 POLL

10 #include <poll.h>
 
26     struct pollfd fds[1];
27     nfds_t nfds = 1;
 55     if (argc != 2)
 56     {
 57         printf("Usage: %s <dev>\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

7.异步通知方式

(1)功能介绍

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

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

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

       ⚫ 谁发:驱动程序发

       ⚫ 发什么:信号

       ⚫ 发什么信号:SIGIO

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

(2)应用编程

  9 #include <signal.h>
 10 #include <sys/types.h>
 11 #include <unistd.h>
 
 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/


目录
相关文章
|
11天前
|
消息中间件 运维 监控
Linux命令lsipc:深入解析与实战应用
`lsipc` (通常指 `ipcs`) 是Linux命令,用于查看系统中的IPC资源,包括消息队列、信号量和共享内存。它显示详细信息,支持过滤,并且需要相应权限。示例用法:显示共享内存(`-m`)、查询消息队列(`-q -i ID`)、查看关联进程(`-m -p`)。注意权限、操作影响及定期监控。结合`ipcrm`等工具可进行更深入管理。
|
5天前
|
弹性计算 安全 Cloud Native
Alibaba Cloud Linux镜像系统超好用!兼容CentOS生态,性能稳定性绝对可以!
Alibaba Cloud Linux是阿里云的自研Linux发行版,兼容CentOS/RHEL,提供长期免费支持。它针对云服务器ECS优化,适用于多种场景,如Web服务、云原生应用等。Alibaba Cloud Linux 3基于Anolis OS 8,提供安全、高性能、十年维护及丰富的开源生态。用户可在ECS购买时选择镜像安装,支持多架构并提供热补丁、解决方案和快速启动版。更换ECS操作系统是免费的。
42 5
|
3天前
|
域名解析 网络协议 Linux
Linux系统下DNS配置指南
Linux系统下DNS配置指南
16 1
|
11天前
|
监控 Linux 数据处理
lslocks:Linux系统中的锁信息查看利器
`lslocks`是Linux工具,用于查看系统上的文件锁信息,帮助诊断进程同步问题。它显示持有锁的进程、锁类型(如POSIX、flock)和状态。通过简洁的输出,用户能识别死锁和资源争用,优化性能。结合其他命令如`grep`和`awk`可增强分析能力。需适当权限运行,定期监控以预防并发访问问题,处理死锁时要谨慎。
|
7天前
|
NoSQL Linux 程序员
Linux objdump命令:深入解析与实战应用
`objdump`是Linux下的反汇编工具,用于将二进制文件转换为汇编代码,便于理解程序底层。它可以反汇编目标文件、可执行文件和库,支持多种参数,如显示符号表(-t)、反汇编代码(-d)、源代码与汇编混合视图(-S)。在实践中,结合-g编译选项和特定段(-j)反汇编,能辅助调试和分析。使用时注意包含调试信息,选择适当参数,并与其他工具(如gdb)配合使用。
|
7天前
|
Linux 数据处理
Linux中的nproc命令:轻松查看系统CPU核心数
`nproc`命令在Linux中用于查看CPU核心数,简洁高效,无参数直接运行。它读取`/proc/cpuinfo`获取信息,适用于资源分配。例如,`nproc`显示核心数,`nproc --all`(非标准选项,可能需结合其他命令)展示更多详情。在脚本中,可将`nproc`输出赋值给变量以适应动态资源管理。使用时注意文件访问权限,检查结果准确性,并结合其他工具如`lscpu`获取更全面硬件信息。
|
11天前
|
监控 Linux 数据处理
探索Linux中的`lsmem`命令:深入了解系统内存布局
`lsmem`是Linux命令,用于显示系统内存布局和大小,帮助管理员和开发者理解内存使用情况。它提供详细输出,包括内存块的大小、范围、类型和关联,支持多种格式展示,如树状图。命令参数如`-h`显示帮助,`-t`以树形展示,`--human-readable`使大小更易读。需root权限运行,常与`free`、`vmstat`等工具结合使用,用于监控和优化内存。注意不同发行版可能存在兼容性差异。
|
11天前
|
数据挖掘 Linux 数据处理
探索Linux下的Lua命令:轻量级脚本语言在数据处理和分析中的应用
**探索Linux上的Lua:轻量级脚本语言用于数据处理。Lua通过命令行解释器执行,适用于游戏开发、数据分析及自动化。特点包括小巧、高效、可扩展和动态类型。使用`lua`或`luajit`,配合-e、-l、-i参数执行脚本或互动模式。示例:执行`hello.lua`脚本打印&quot;Hello, Lua!&quot;。最佳实践涉及版本兼容、性能优化、使用C API、测试和文档编写。**
|
1天前
|
Linux
常用的Linux系统命令及其使用技巧
常用的Linux系统命令及其使用技巧
|
3天前
|
Linux Perl
如何在Linux系统中确定CPU架构
如何在Linux系统中确定CPU架构
7 0