LV17-01-输入类设备-01-基础知识
本文主要是输入类设备控制——基础知识的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.6的64位版本(一开始使用的是16.04版本,后来进行了升级) | |
VMware® Workstation 17 Pro | 17.0.0 build-20800274 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 | |
Linux开发板环境 | Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) | |
linux内核 | linux-4.15(NXP官方提供) |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.nxp.com.cn/ | NXP官方网站 | |
https://www.nxpic.org.cn/ | NXP 官方社区 | |
https://u-boot.readthedocs.io/en/latest/ | u-boot官网 | |
https://www.kernel.org/ | linux内核官网 | |
其他网站 | kernel - Linux source code (v4.15) - Bootlin | linux内核源码在线查看 |
点击查看相关文件下载
分类 | 网址 | 说明 |
NXP | https://github.com/nxp-imx | NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库 |
https://elixir.bootlin.com/linux/latest/source | 在线阅读linux kernel源码 | |
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) |
一、输入类设备编程简介
1. 什么是输入设备?
先来了解什么是输入设备(也称为 input 设备),常见的输入设备有鼠标、键盘、触摸屏、 遥控器、电脑画图板等,用户通过输入设备与系统进行交互。
2. 输入系统框架
2.1 input 子系统
由上面的介绍可知,输入设备种类非常多,每种设备上报的数据类型又不一样,那么 Linux 系统如何管理呢? Linux 系统为了统一管理这些输入设备,实现了一套能够兼容所有输入设备的框架,那么这个框架就是 input 子系统。驱动开发人员基于 input 子系统开发输入设备的驱动程序。
input 子系统可以屏蔽硬件的差异,向应用层提供一套统一的接口。基于 input 子系统注册成功的输入设备,都会在/dev/input 目录下生成对应的设备节点(设备文件), 设备节点名称通常为 eventX(X 表示一个数字编号 0、 1、 2、 3 等),例如 /dev/input/event0、 /dev/input/event1、/dev/input/event2 等, 通过读取这些设备节点可以获取输入设备上报的数据。
2.2 框架概述
输入系统框架如图所示:
3. 读取数据的流程
如果我们要读取触摸屏的数据,假设触摸屏设备对应的设备节点为/dev/input/event0,那么数据读取流程如下:
(1)应用程序打开/dev/input/event0 设备文件;
(2)应用程序发起读操作(例如调用 read),如果没有数据可读则会进入休眠(阻塞 I/O 情况下);
(3)用户操作设备,硬件上产生中断;
(4)输入系统驱动层对应的驱动程序处理中断:读取到数据,转换为标准的输入事件,向核心层汇报。所谓输入事件就是一个“ struct input_event”结构体。
(5)核心层可以决定把输入事件转发给上面哪个 handler 来处理:从 handler 的名字来看,它就是用来处输入操作的。有多种 handler,比如: evdev_handler、 kbd_handler、 joydev_handler 等等。最常用的是 evdev_handler:它只是把 input_event 结构体保存在内核buffer 等, APP 来读取时就原原本本地返回。它支持多个 APP 同时访问输入设备,每个 APP 都可以获得同一份输入事件。(当应用程序正在等待数据时, evdev_handler 会把它唤醒,这样应用程序就可以返回数据。)
(6)当有数据可读时,应用程序会被唤醒,读操作获取到数据返回;
(4)应用程序对读取到的数据进行解析。
当无数据可读时,程序会进入休眠状态(也就是阻塞),例如应用程序读触摸屏数据, 如果当前并没有去触碰触摸屏, 自然是无数据可读; 当我们用手指触摸触摸屏或者在屏上滑动时,此时就会产生触摸数据、应用程序就有数据可读了,应用程序会被唤醒,成功读取到数据。那么对于其它输入设备亦是如此,无数据可读时应用程序会进入休眠状态(阻塞式 I/O 方式下), 当有数据可读时才会被唤醒。
4. 应用程序如何解析数据?
4.1 内核中怎么表示一个输入设备?
我们先来看一下内核中怎么表示一个输入设备?使用 input_dev 结构体来表示输入设备,它的内容如图所示:
4.2 应用程序得到什么样的数据?
先我们要知道,应用程序打开输入设备对应的设备文件,向其发起读操作,那么这个读操作获取到的是什么样的数据呢? 其实每一次 read 操作获取的都是一个 struct input_event 结构体类型数据, 该结构体定义在<linux/input.h>头文件中,它的定义如下:
结构体中的 time 成员变量是一个 struct timeval 类型的变量,timeval 表示的是“自系统启动以来过了多少时间”,它是一个结构体,含有“ tv_sec、 tv_usec”两项(即秒、微秒), 内核会记录每个上报的事件其发生的时间,并通过变量 time 返回给应用程序。时间参数通常不是那么重要,而其它3 个成员变量 type(哪类事件)、 code(哪个事件)、value(事件值) 更为重要。
4.2.1 type
type 用于描述发生了哪一种类型的事件(对事件的分类) , Linux 系统所支持的输入事件类型如下所示(input-event-codes.h - include/uapi/linux/input-event-codes.h - Linux source code v4.15 - Bootlin):
1 | /* |
以上这些宏定义也是在<linux/input.h>头文件中,所以在应用程序中需要包含该头文件; 一种输入设备 通常可以产生多种不同类型的事件,例如点击鼠标按键(左键、右键,或鼠标上的其它按键)时会上报按键 类事件,移动鼠标时则会上报相对位移类事件。
在type成员中,我们看到有一个同步事件类型 EV_SYN, 同步事件用于实现同步操作、告知接收者本轮上报的数据已经完整。应用程序读取输入设备上报的数据时,一次 read 操作只能读取一个 struct input_event 类型数据,例如对于触摸屏来说,一个触摸点的信息包含了 X 坐标、 Y 坐标以及其它信息, 对于这样情况,应用程序需要执行多次 read 操作才能把一个触摸点的信息全部读取出来, 这样才能得到触摸点的完整信息。
那么应用程序如何得知本轮已经读取到完整的数据了呢?其实这就是通过同步事件来实现的, 内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、 可以进行同步了。同步类事件中也包含了多种不同的事件,如下所示:
1 | /* |
所以的输入设备都需要上报同步事件, 上报的同步事件通常是 SYN_REPORT, 而 value 值通常为 0。
4.2.2 code
code 表示该类事件中的哪一个具体事件(input-event-codes.h - include/uapi/linux/input-event-codes.h - Linux source code v4.15 - Bootlin), 以上列举的每一种事件类型中都包含了一系列具体事件, 如一个键盘上通常有很多按键, 例如字母 A、B、 C、 D 或者数字 1、 2、 3、 4 等, 而 code变量则告知应用程序是哪一个按键发生了输入事件。每一种事件类型都包含多种不同的事件。
- 按键类事件:
1 |
|
- 相对位移事件:
1 |
- 绝对位移事件
触摸屏设备是一种绝对位移设备,它能够产生绝对位移事件; 例如对于触摸屏来说,一个触摸点所包含的信息可能有多种,例如触摸点的 X 轴坐标、 Y 轴坐标、 Z 轴坐标、按压力大小以及接触面积等, 所以 code变量告知应用程序当前上报的是触摸点的哪一种信息(X 坐标还是 Y 坐标、亦或者其它)。绝对位移事件如下:
1 |
|
除了以上列举出来的之外,还有很多,我们可以自己浏览<linux/input.h>头文件(这些宏其实是定义在input-event-codes.h 头文件中,该头文件被< linux/input.h >所包含了) , 关于这些具体的事件,后面遇到了再学习。
4.2.3 value
表示事件值 ,内核每次上报事件都会向应用层发送一个数据 value, 对 value 值的解释随着 code 的变化而变化。对于按键事件(type=1) 来说, 如果 code=2(键盘上的数字键 1,也就是 KEY_1), 那么如果 value 等于 1,则表示 KEY_1 键按下; value 等于 0 表示 KEY_1 键松开,如果 value 等于 2,则表示 KEY_1 键长按。再比如, 在绝对位移事件中(type=3),如果 code=0 (触摸点 X 坐标 ABS_X),那么 value 值就等于触摸点的 X 轴坐标值; 同理, 如果 code=1(触摸点 Y 坐标 ABS_Y),此时value 值便等于触摸点的 Y 轴坐标值; 所以对 value 值的解释需要根据不同的 code 值而定。
4.3 事件之间的界线
应用程序读取数据时,可以得到一个或多个数据,比如一个触摸屏的一个触点会上报 X、 Y 位置信息,也可能会上报压力值。那它怎么知道已经读到了完整的数据?
驱动程序上报完一系列的数据后,会上报一个“同步事件”,表示数据上报完毕。 APP 读到“同步事件”时,就知道已经读完了当前的数据。同步事件也是一个 input_event 结构体,它的 type、 code、 value 三项都是 0。
5. 输入子系统支持完整的 API 操作
支持这些机制:阻塞、非阻塞、 POLL/SELECT、异步通知。
二、如何获取输入设备信息?
1. 如何确认设备信息?
ALPHA 开发板上有一个用户按键 KEY0, 它就是一个典型的输入设备, 如下图所示:
该按键是提供给用户使用的一个 GPIO 按键, 在出厂系统中,该按键驱动基于 input 子系统而实现, 所以在/dev/input 目录下存在 KEY0 的设备节点, 具体是哪个设备节点, 可以通过查看/proc/bus/input/devices 文件得知,查看该文件可以获取到系统中注册的所有输入设备相关的信息,如下所示(这张图是正点原子教程资料里面的):
那么这里的 I、 N、 P、 S、 U、 H、 B 对应的每一行是什么含义呢?
(1)I:d of the device(设备 ID) ,该参数由结构体 struct input_id 来进行描述,驱动程序中会定义这样的结构体:
(2)N:name of the device,设备名称
(3)P:physical path to the device in the system hierarchy,系统层次结构中设备的物理路径。
(4)S:sysfs path位于 sys 文件系统的路径
(5)U:unique identification code for the device(if device has it),设备的唯一标识码 。
(6)H:list of input handles associated with the device,与设备关联的输入句柄列表。
(7)B:bitmaps(位图)
1
2
3
4
5 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。 再举一个例子,下面是我自己使用的4.3寸触摸屏的信息:
1
2
3
4
5
6
7
8
9
10 I: Bus=0018 Vendor=dead Product=beef Version=28bb
N: Name="goodix-ts"
P: Phys=input/ts
S: Sysfs=/devices/virtual/input/input2
U: Uniq=
H: Handlers=event1
B: PROP=3
B: EV=b
B: KEY=e520 0 0 0 0 0 0 0 0 0 0
B: ABS=2658000 0“ B: ABS=2658000 0”如何理解? 它表示该设备支持 EV_ABS 这一类事件中的哪一些事件。这是 2 个 32 位的数字: 0x2658000、 0x0, 高位在前低位在后, 组成一 个 64 位 的数字 :
0x2658000 00000000
这样的话数值为1的位有47、48、50、53、54,即0x2f、0x20、0x30、0x32、0x35、0x36,对应这些宏(input-event-codes.h - include/uapi/linux/input-event-codes.h - Linux source code v4.15 - Bootlin):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 #define ABS_MT_SLOT 0x2f /* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
#define ABS_MT_POSITION_X 0x35 /* Center X touch position */
#define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */
#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */
#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */
#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */
#define ABS_MT_TOOL_X 0x3c /* Center X tool position */
#define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */即 这 款 输 入 设 备 支 持 上 述 的、 ABS_MT_SLOT 、ABS_MT_TOUCH_MAJOR 、 ABS_MT_WIDTH_MAJOR 、ABS_MT_POSITION_X 、ABS_MT_POSITION_Y 这些绝对位置事件。
这里其实还有一种办法可以用来确认(这里使用的是出厂系统),我们的输入设备,其对应的设备文件在/dev/input/目录下 :
如何确定到底是哪个设备文件,可以通过对设备文件进行读取来判断,例如使用 od 命令:
1 | sudo od -x /dev/input/eventX # eventX表示上面的event多少 |
Tips:需要添加 sudo,在 Ubuntu 系统下,普通用户是无法对设备文件进行读取或写入操作。 要是直接在开发板中的话,看自己是什么用户权限了。
我们可以一个一个试,当执行完命令后按下按键,哪一个读取到了数据,就说明这个就是按键对应的输入设备文件。
比如这里是event2,按下按键的时候就会出现对应的打印信息。这些信息都是什么?我们来分析一下:
type 为 1 , 对 应 EV_KEY ; code 为 0x72,按键的code定义的是十进制数字,所以这里是114,对应KEY_VOLUMEDOWN;上图中还发现有 2 个同步事件:它的 type、 code、 value 都为 0。表示按键上报了 2 次完整的数据。
2. APP 访问硬件的 4 种方式
举个例子:妈妈怎么知道卧室里小孩醒了?
(1)时不时进房间看一下: 查询方式。简单,但是累。
(2)进去房间陪小孩一起睡觉,小孩醒了会吵醒她: 休眠-唤醒。不累,但是妈妈干不了活了。
(3)妈妈要干很多活,但是可以陪小孩睡一会,定个闹钟: poll 方式。有浪费点时间,但是可以继续干活。妈妈要么是被小孩吵醒,要么是被闹钟吵醒。
(4)妈妈在客厅干活,小孩醒了他会自己走出房门告诉妈妈: 异步通知。妈妈、小孩互不耽误。
这 4 种方法没有优劣之分,在不同的场合使用不同的方法。
3. 获取设备信息
3.1 ioctl函数
过 ioctl 获取设备信息, ioctl 的参数如下:
1 | int ioctl(int fd, unsigned long request, ...); |
些驱动程序对 request 的格式有要求,它的格式如下(ioctl.h - include/uapi/asm-generic/ioctl.h - Linux source code v4.15 - Bootlin):
1 |
|
比如 dir 为_IOC_READ(即 2)时,表示 APP 要读数据;为_IOC_WRITE(即 4)时,表示 APP 要写数据。
size 表示这个 ioctl 能传输数据的最大字节数。
type、 nr 的含义由具体的驱动程序决定。
比如要读取输入设备的 evbit 时, ioctl 的 request 要写为“ EVIOCGBIT(0,size)”, size 的大小可以由自己决定:你想读多少字节就设置为多少。这个宏的定义如下:
1 |
3.2 代码实例
可以看这里:
我们编译后执行以下命令:
1 | ./app_demo /dev/input/event2 |
前面我们知道B后面的EV表示支持的事件类型,这里的按键就是0x100003,第0、1、20位为1,也就是0x0、0x1、0x14,就对应这些事件(input-event-codes.h - include/uapi/linux/input-event-codes.h - Linux source code v4.15 - Bootlin):
1 |
三、如何获取输入数据?
1. 休眠-唤醒方式
APP 调用 open 函数时,不要传入“ O_NONBLOCK”。APP 调用 read 函数读取数据时,如果驱动程序中有数据,那么 APP 的 read
函数会返回数据;否则 APP 就会在内核态休眠,当有数据时驱动程序会把 APP 唤醒, read 函数恢复执行并返回数据给 APP。
1.1 代码示例
可以看这里:LV17_INPUT_DEVICE/01_read_input · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
执行程序时需要传入参数,这个参数就是对应的输入设备的设备节点(设备文件),程序中会对传参进行校验。程序中首先调用 open()函数打开设备文件,之后在 while 循环中调用 read()函数读取文件,将读取到的数据存放在 struct input_event 结构体对象中,之后将结构体对象中的各个成员变量打印出来。
程序中使用了阻塞式 I/O 方式读取设备文件,所以当无数据可读时 read 调用会被阻塞,直到有数据可读时才会被唤醒!
1.2 开发板验证
我们在串口终端执行以下命令:
1 | ./app_demo /dev/input/event2 |
程序运行后,执行按下 KEY0、松开 KEY0 等操作,终端将会打印出相应的信息,如上图所示。
第一行中 type 等于 1,表示上报的是按键事件 EV_KEY, code=114, 打开 input-event-codes.h - include/uapi/linux/input-event-codes.h - Linux source code v4.15 - Bootlin 头文件进行查找,可以发现 cpde=114 对应的是键盘上的 KEY_VOLUMEDOWN 按键,这个是 ALPHA 开发板出厂系统已经配置好的。 而 value=1 表示按键按下,所以整个第一行的意思就是按键 KEY_VOLUMEDOWN被按下。
第二行, 表示上报了 EV_SYN 同步类事件(type=0)中的 SYN_REPORT 事件(code=0), 表示本轮数据已经完整、报告同步。
第三行, type 等于 1,表示按键类事件, code 等于 114、value 等于 0,所以表示按键 KEY_VOLUMEDOWN被松开。
第四行,又上报了同步事件。
所以整个上面 4 行的打印信息就是开发板上的 KEY0 按键被按下以及松开这个过程, 内核所上报的事件以及发送给应用层的数据 value。 我们试试长按按键 KEY0, 按住不放, 如下所示:
可以看到上报按键事件时,对应的 value 等于 2,表示长按状态。
2. 查询方式
APP 调用 open 函数时,传入“ O_NONBLOCK”表示“非阻塞”。APP 调用 read 函数读取数据时,如果驱动程序中有数据,那么 APP 的 read函数会返回数据,否则也会立刻返回错误。
2.1 代码示例
可以看这里:LV17_INPUT_DEVICE/01_read_input_noblock · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
2.2 开发板验证
我们执行这个demo的时候注意传入noblock参数,不传的时候它会以休眠唤醒的方式运行,这样我们就会看到按键没有按下的时候一直打印错误信息,当按键按下就会有按键的信息打印出来:
3. POLL/SELECT 方式
3.1 POLL/SELECT介绍
POLL 机制、 SELECT 机制是完全一样的,只是 APP 接口函数不一样。简单地说,它们就是“定个闹钟”:在调用 poll、 select 函数时可以传入“超时时间”。在这段时间内,条件合适时(比如有数据可读、有空间可写)就会立刻返回,否则等到“超时时间”结束时返回错误。用法如下。
(1)APP 先调用 open 函数打开设备节点,打开的时候要使用noblock模式。
(2)APP 不是直接调用 read 函数,而是先调用 poll 或 select 函数,这 2 个函数中可以传入“超时时间”。它们的作用是:如果驱动程序中有数据,则立刻返回;否则就休眠。在休眠期间,如果有人操作了硬件,驱动程序获得数据后就会把 APP唤醒,导致 poll 或 select 立刻返回;如果在“ 超时时间”内无人操作硬件,则时间到后 poll 或 select 函数也会返回。 APP 可以根据函数的返回值判断返回原因:有数据?无数据超时返回?
(3)APP 根据 poll 或 select 的返回值判断有数据之后,就调用 read 函数读取数据时,这时就会立刻获得数据。
(4)poll/select 函数可以监测多个文件,可以监测多种事件:
事件类型 | 说明 |
---|---|
POLLIN | 有数据可读 |
POLLRDNORM | 等同于 POLLIN |
POLLRDBAND | Priority band data can be read,有优先级较较高的“ band data”可读 Linux 系统中很少使用这个事件 |
POLLPRI | 高优先级数据可读 |
POLLOUT | 可以写数据 |
POLLWRNORM | 等同于 POLLOUT |
POLLWRBAND | Priority data may be written |
POLLERR | 发生了错误 |
POLLHUP | 挂起 |
POLLNVAL | 无效的请求,一般是 fd 未 open |
注意:在调用 poll 函数时,要指明: 要监测哪一个文件:哪一个 fd ;想监测这个文件的哪种事件:是 POLLIN、还是 POLLOUT 。
示例代码如下:
1 | struct pollfd fds[1]; |
3.2 代码示例
3.2.1 POLL
可以看这里:LV17_INPUT_DEVICE/01_read_input_poll · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
3.2.2 SELECT
可以看这里:LV17_INPUT_DEVICE/01_read_input_select · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
3.2 开发板验证
编译后我们执行以下命令:
1 | ./app_demo /dev/input/event2 |
然后会有如下打印信息:
会发现,即便我们以noblock的方式打开了节点,也不会再直接返回,当有数据的时候直接返回并打印信息,当没有数据,超过5000ms的时候,超时返回。POLL和SELECT的实验现象是一样的,这里就不重复写了。
4. 异步通知
4.1 什么是异步通知?
所谓同步,就是“你慢我等你”。那么异步就是:你慢那你就自己玩,我做自己的事去了,有情况再通知我。所谓异步通知,就是 APP 可以忙自己的事,当驱动程序用数据时它会主动给APP 发信号,这会导致 APP 执行信号处理函数。
“ 发信号”,这只有 3 个字,却可以引发很多问题:
- 谁发?驱动程序发。
- 发什么?信号。
- 发什么信号?SIGIO。
- 怎么发?内核里提供有函数 。
- 发给谁?APP, APP 要把自己告诉驱动。
- APP 收到后做什么?执行信号处理函数。
- 信号处理函数和信号,之间怎么挂钩? APP 注册信号处理函数 。
- 内核里有那么多驱动,你想让哪一个驱动给我们的APP发 SIGIO 信号?APP 要打开驱动程序的设备节点。
- 驱动程序怎么知道要发信号给我们的APP而不是别人的APP? APP 要把自己的进程 ID 告诉驱动程序。
- APP 有时候想收到信号,有时候又不想收到信号:应该可以把 APP 的意愿告诉驱动:设置 Flag 里面的 FASYNC 位为 1,使能“异步通知”。
4.1.1 有哪些信号?
Linux 系统中有很多信号,在 Linux 内核源文件 include/uapi/asm-generic/signal.h(signal.h - include/uapi/asm-generic/signal.h - Linux source code v4.15 - Bootlin )中,有很多信号的宏定义:
1 |
|
SIGIO在驱动中很常用,表示有IO事件。驱动程序通知 APP 时,它会发出“ SIGIO”这个信号,表示有“ IO 事件”要处理。
4.1.2 信号注册
就 APP 而言,想处理 SIGIO 信息,那么需要提供信号处理函数,并且要跟SIGIO 挂钩。这可以通过一个 signal 函数来“给某个信号注册处理函数”,用法如下:
1 |
|
(1)编写信号处理函数,比如signal_handler();
(2)调用signal函数进行注册,函数的第一个参数代表哪个信号,第二个参数就是刚才编写的信号处理函数。
4.1.3 基本步骤
- (1)编写信号处理函数
1 | static void sig_func(int sig) |
- (2)注册信号处理函数:
1 | signal(SIGIO, sig_func); |
- (3)打开驱动(设备节点 )
1 | fd = open(argv[1], O_RDWR); |
- (4)把进程 ID 告诉驱动
1 | fcntl(fd, F_SETOWN, getpid()); |
- (5)使能驱动的 FASYNC 功能
1 | flags = fcntl(fd, F_GETFL); |
4.2 代码示例
可以看这里:LV17_INPUT_DEVICE/01_read_input_fasync · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
4.3 开发板验证
我们执行以下命令:
1 | ./app_demo /dev/input/event2 |
会看到,我们一直在进程中循环打印循环的次数,当有按键来的时候就会打印出按键的相关信息。