# 前置知识
(1)输入子系统分为三层,分别是事件处理层、核心层、设备驱动层;
(2)鼠标移动、键盘按键按下等输入事件都需要通过设备驱动层→核心层→事件处理层→用户空间,层层上报,直到应用程序;
事件处理层
(1)事情处理层主要是负责将输入事件上报到应用程序;对于向内核输入子系统注册的输入设备,在sysfs中创建设备节点,应用程序通过操作设备节点来获取输入事件;
(2)事件处理层将输入事件划分为几大类,比如:通用事件(event)、鼠标事件(mouse)、摇杆事件(js)等等,每个输入类设备在注册时需要指定自己属于哪个类;
(3)通用事件是能和任何输入设备匹配上的,意味着只要注册一个输入类设备就会sysfs就会创建对应的/dev/input/eventn设备节点;
核心层
(1)核心层是起到承上启下的作用,负责协调输入事件在事件处理层和设备驱动层之间的传递;
(2)核心层负责管理事件处理层和设备驱动层,核心层提供相关的接口函数,事件处理层和设备驱动层都必须先向核心层注册,然后才能工作;
(3)核心层负责设备驱动层和事件处理层的匹配问题,设备驱动根据硬件特性是各种各样的,事件处理层也是分为好几种类型,具体硬件驱动和哪一类或者哪几类事件处理类型匹配,需要核心层去做判断;
设备驱动层
(1)设备驱动层分为两大部分:硬件特性部分 + 核心层注册接口;
(2)设备驱动层的硬件特性部分是具体操作硬件的,不同的硬件差异很大,且不属于内核,这也是我们移植驱动的重点;
(3)核心层注册接口:输入子系统提供的输入设备向内核注册的接口,属于内核代码部分,我们需要理解和会使用这些接口,接口的使用都是模式化的,降低了编写驱动的难度;
示例
假设用户程序直接访问/dev/input/event0 设备节点,或者使用 tslib 访问设备节点,数据的流程如下:
- APP 发起读操作,若无数据则休眠;
- 用户操作设备,硬件上产生中断;
- 输入系统驱动层对应的驱动程序处理中断:读取到数据,转换为标准的输入事件,向核心层汇报。所谓输入事件就是一个“ struct input_event”结构体。
- 核心层可以决定把输入事件转发给上面哪个 handler 来处理:从 handler 的名字来看,它就是用来处输入操作的。有多种 handler,比如: evdev_handler、 kbd_handler、 joydev_handler 等等。最常用的是 evdev_handler:它只是把 input_event 结构体保存在内核buffer 等, APP 来读取时就原原本本地返回。它支持多个 APP 同时访问输入设备,每个 APP 都可以获得同一份输入事件。当 APP 正在等待数据时, evdev_handler 会把它唤醒,这样 APP 就可以返回数据。
APP 对输入事件的处理:
- APP 获 得 数 据 的 方 法 有 2 种 : 直 接 访 问 设 备 节 点 ( 比 如/dev/input/event0,1,2,...),或者通过 tslib、 libinput 这类库来间接访问设备节点。这些库简化了对数据的处理
# input_dev结构体详解
struct input_dev {
const char *name; //设备名称
const char *phys; //设备在系统中的物理路径
const char *uniq; //设备唯一识别符
struct input_id id; //设备工D,包含总线ID(PCI 、 USB)、厂商工D,与 input handler 匹配的时会用到
/*
*EV_SYN 同步事件
*EV_KEY 按键事件
*EV_REL 相对坐标事件:比如说鼠标
*EV_ABS 绝对坐标事件:比如触摸屏
*EV_MSC 杂项事件
*EV_SW 开关事件
*EV_LED 指示灯事件
*EV_SND 音效事件
*EV_REP 重复按键事件
*EV_FF 力反馈事件
*EV_PWR 电源事件
*EV_FF_STATUS 力反馈状态事件
*/
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //设备支持的事件类型
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //设备支持的具体的按键、按钮事件
unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //户设备支持的具体的相对坐标事件
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //设备支持的具体的绝对坐标事件
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; //设备支持的具体的混杂事件
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; //设备支持的具体的LED指示灯事件
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; //户设备支持的具体的音效事件
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; //设备支持的具体的力反馈事件
unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; //设备支持的具体的开关事件
unsigned int keycodemax; //键盘码表的大小
unsigned int keycodesize; //键盘码表中的元素个数
void *keycode; //设备的键盘码表
//下面两个是可选方法,用于配置和获取键盘码表
int (*setkeycode)(struct input_dev *dev,
unsigned int scancode, unsigned int keycode);
int (*getkeycode)(struct input_dev *dev,
unsigned int scancode, unsigned int *keycode);
struct ff_device *ff; //如果设备支持力反馈,则该成员将指向力反馈设备描述结构
unsigned int repeat_key; //保存上一个键值,用于实现软件自动重复按键(用户按住某个键不放)
struct timer_list timer; //用于软件自动重复按键的定时器
int sync; //在上次同步事件(EV_SYNC)发生后没有新事件产生,则被设置为 1
int abs[ABS_CNT]; //用于上报的绝对坐标当前值
int rep[REP_MAX + 1]; //记录自动重复按键参数的当前值
unsigned long key[BITS_TO_LONGS(KEY_CNT)]; //舍反映设备按键、 按钮的当前状态
unsigned long led[BITS_TO_LONGS(LED_CNT)]; //反映设备LED指示灯的当前状态时
unsigned long snd[BITS_TO_LONGS(SND_CNT)]; //反映设备音效的当前状态会
unsigned long sw[BITS_TO_LONGS(SW_CNT)]; //反映设备开关的当前状态
int absmax[ABS_CNT]; //绝对坐标的最大值
int absmin[ABS_CNT]; //绝对坐标的最小值
int absfuzz[ABS_CNT]; //绝对坐标的噪音值,变化小于该值的一半可忽略该事件
int absflat[ABS_CNT]; //摇杆中心位置大小
int absres[ABS_CNT];
//提供以下4个设备驱动层的操作接口,根据具体的设备需求实现它们
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
//用于处理送到设备驱动层来的事件,很多事件在事件处理层被处理,但有的事件仍需送到设备驱动中.
//如LED指示灯事件和音效事件,因为这些操作通常需要设备驱动执行(如点亮某个键盘指示灯)
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
//指向独占该设备的输入句柄( input handle ),通常设备驱动上报的事件会被分发到与设备
//关联的所有事件处理程序( input handler )中处理,但如果通过ioctl 的EVIOCGRAB命令
//设置了独占句柄,则上报事件只能被所设置的输入句柄对应的事件处理程序处理
struct input_handle *grab;
spinlock_t event_lock; //调用 event () 时需要使用该自旋锁来实现互斥
struct mutex mutex; //用于串行化的访问 open()、 close()和flush()等设备方法
//记录输入事件处理程序(input handlers)调用设备open()方法的次数.保证设备open()方法是在
//第一次调用 input_open_device()中被调用,设备close()方法在最后一次调用 input_close_device()中被调用
unsigned int users;
bool going_away;
struct device dev; //内嵌device结构
struct list_head h_list; //与该设备相关的输入句柄列表(struct input handle)
struct list_head node; //挂接到input_dev_list链表上
};
# 输入事件
输入事件结构体
APP 可以通过read函数得到一系列的输入事件,就是一个一个“ struct input_event”:
/*
* The event structure itself
* Note that __USE_TIME_BITS64 is defined by libc based on
* application's request to use 64 bit time_t.
*/
struct input_event {
#if (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64)) && !defined(__KERNEL__)
struct timeval time;
#define input_event_sec time.tv_sec /* 秒 */
#define input_event_usec time.tv_usec /* 微妙 */
#else
__kernel_ulong_t __sec;
#if defined(__sparc__) && defined(__arch64__)
unsigned int __usec;
unsigned int __pad;
#else
__kernel_ulong_t __usec;
#endif
#define input_event_sec __sec
#define input_event_usec __usec
#endif
__u16 type; /* 哪类事件 */
__u16 code; /* 哪个事件 */
__s32 value; /* 事件值 */
};
- type: 表示哪类事件
EV_SYN | 用于事件间的分割标志。事件可能按时间或空间进行分割,就像在多点触摸协议中的例子 |
EV_KEY | 用来描述键盘,按键或者类似键盘设备的状态变化 |
EV_REL | 用来描述相对坐标轴上数值的变化,例如:鼠标向左方移动了5个单位 |
EV_ABS | 用来描述相对坐标轴上数值的变化,例如:描述触摸屏上坐标的值 |
EV_MSC | 当不能匹配现有的类型时,使用该类型进行描述 |
EV_SW | 用来描述具备两种状态的输入开关 |
EV_LED | 用于控制设备上的LED灯的开和关 |
EV_SND | 用来给设备输出提示声音 |
EV_REP | 用于可以自动重复的设备(autorepeating) |
EV_FF | 用来给输入设备发送强制回馈命令。(震动?) |
EV_PWR | 特别用于电源开关的输入 |
EV_FF_STATUS | 用于接收设备的强制反馈状态 |
- code: 表示该类事件下的哪一个事件
比如对于 EV_KEY(按键)类事件,它表示键盘。键盘上有很多按键,比如数字键 1、 2、 3,字母键 A、 B、 C 里等
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
...
对于触摸屏,它提供的是绝对位置信息,有 X 方向、 Y 方向,还有压力值
/*
* Absolute axes
*/
#define ABS_X 0x00
#define ABS_Y 0x01
#define ABS_Z 0x02
#define ABS_RX 0x03
#define ABS_RY 0x04
#define ABS_RZ 0x05
#define ABS_THROTTLE 0x06
#define ABS_RUDDER 0x07
#define ABS_WHEEL 0x08
#define ABS_GAS 0x09
#define ABS_BRAKE 0x0a
#define ABS_HAT0X 0x10
#define ABS_HAT0Y 0x11
#define ABS_HAT1X 0x12
#define ABS_HAT1Y 0x13
#define ABS_HAT2X 0x14
#define ABS_HAT2Y 0x15
#define ABS_HAT3X 0x16
#define ABS_HAT3Y 0x17
#define ABS_PRESSURE 0x18
#define ABS_DISTANCE 0x19
#define ABS_TILT_X 0x1a
#define ABS_TILT_Y 0x1b
#define ABS_TOOL_WIDTH 0x1c
#define ABS_VOLUME 0x20
#define ABS_PROFILE 0x21
#define ABS_MISC 0x28
- value:表示事件值
对于按键,它的 value 可以是 0(表示按键被按下)、 1(表示按键被松开)、2(表示长按);
对于触摸屏,它的 value 就是坐标值、压力值
- 事件之间的界线
APP 读取数据时,可以得到一个或多个数据,比如一个触摸屏的一个触点会上报 X、 Y 位置信息,也可能会上报压力值。
APP 怎么知道它已经读到了完整的数据?驱动程序上报完一系列的数据后,会上报一个“同步事件”,表示数据上报完毕。 APP 读到“同步事件”时,就知道已经读完了当前的数据。
同步事件也是一个 input_event 结构体,它的 type、 code、 value 三项都是 0
使用命令读取数据
调试输入系统时,直接执行类似下面的命令,然后操作对应的输入设备即可读出数据:
hexdump /dev/input/event1
在开发板上执行上述命令之后,点击按键或是点击触摸屏,就会打印如下信息:
比如第一行 type 为3,即 EV_ABS ;code 为 0x39 对应的 ABS_MT_TRACKING_ID,表示手指ID。手指 ID 才能唯一代表一个手指,槽的 ID 并不能代表一个手指。因为假如一个手指抬起,另外一个手指按下,这两个手指的事件可能由同一个槽进行上报,但是手指 ID 肯定是不一样的。
比如 type 为3,即 EV_ABS ;code 为 0x35对应的 ABS_MT_POSITION_X,code 为 0x36对应的 ABS_MT_POSITION_Y
上图中还发现有 2 个同步事件:它的 type、 code、 value 都为 0。表示电容屏上报了 2 次完整的数据
注意:
当第一个手指移动时,会有如下事件
EV_ABS ABS_MT_POSITION_X 000002ec EV_ABS ABS_MT_POSITION_Y 00000526 EV_SYN SYN_REPORT 00000000
此时没有指定 ABS_MT_SLOT 事件和 ABS_MT_TRACKING_ID 事件,默认使用前面的值,因为此时只有一个手指。
当第二个手指按下时,会有如下事件
EV_ABS ABS_MT_SLOT 00000001 EV_ABS ABS_MT_TRACKING_ID 00000001 EV_ABS ABS_MT_POSITION_X 00000470 EV_ABS ABS_MT_POSITION_Y 00000475 EV_SYN SYN_REPORT 00000000
很简单,第二个手指的事件,由另外一个槽进行上报。
当两个手指同时移动时,会有如下事件
EV_ABS ABS_MT_SLOT 00000000 EV_ABS ABS_MT_POSITION_Y 000004e0 EV_ABS ABS_MT_SLOT 00000001 EV_ABS ABS_MT_POSITION_X 0000046f EV_ABS ABS_MT_POSITION_Y 00000414 EV_SYN SYN_REPORT 00000000
通过指定槽,就可以清晰看到事件由哪个槽进行上报,从而就可以区分出两个手指产生的事件。
当其中一个手指抬起时,会有如下事件
EV_ABS ABS_MT_SLOT 00000000 // 注意,ABS_MT_TRACKING_ID 的值为 -1 EV_ABS ABS_MT_TRACKING_ID ffffffff EV_ABS ABS_MT_SLOT 00000001 EV_ABS ABS_MT_POSITION_Y 000003ee EV_SYN SYN_REPORT 00000000
当一个手指抬起时,ABS_MT_TRACKING_ID 事件的值为 -1,也就是十六进制的 ffffffff。通过槽事件,可以知道是第一个手指抬起了。
如果最后一个手指也抬起了,会有如下事件
EV_ABS ABS_MT_TRACKING_ID ffffffff // 同步事件,不属于触摸事件 EV_SYN SYN_REPORT 00000000
# 查看输入设备节点
使用如下指令查看输入设备节点:
cat /proc/bus/input/devices
- I:id of the device(设备 ID)
该参数由结构体 struct input_id 来进行描述,驱动程序中会定义这样的结构体:
/*
* IOCTLs (0x00 - 0x7f)
*/
struct input_id {
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
};
- 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”这是 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_SLOT 、ABS_MT_TOUCH_MAJOR 、 ABS_MT_WIDTH_MAJOR 、 ABS_MT_POSITION_X 、ABS_MT_POSITION_Y 这些绝对位置事件
# APP访问硬件
阻塞、非阻塞
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <poll.h>
#include <signal.h>
int fd = 0;
char *ev_names[] = {
"EV_SYN ","EV_KEY ","EV_REL ","EV_ABS ","EV_MSC ","EV_SW","NULL ","NULL ",
"NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ",
"EV_LED ","EV_SND ","NULL ","EV_REP ","EV_FF","EV_PWR ",};
static void print_ev_info(void)
{
int len;
int err;
struct input_id id;
unsigned char byte;
int bit;
unsigned int evbit[5];
err = ioctl(fd, EVIOCGID, &id);
if(err < 0)
{
perror("ioctl:");
return;
}
printf("bustype = 0x%x\n", id.bustype );
printf("vendor = 0x%x\n", id.vendor );
printf("product = 0x%x\n", id.product );
printf("version = 0x%x\n", id.version );
len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit);
if (len > 0 && len <= sizeof(evbit))
{
printf("support ev type: ");
for (int i = 0; i < len; i++)
{
byte = ((unsigned char *)evbit)[i];
for (bit = 0; bit < 8; bit++)
{
if (byte & (1<<bit)) {
printf("%s ", ev_names[i*8 + bit]);
}
}
}
printf("\n");
}
}
static void read_block_nblock(void)
{
int len = 0;
struct input_event ev;
while(true)
{
len = read(fd, &ev, sizeof(struct input_event));
if(len == sizeof(struct input_event))
{
printf("**********************************\r\n");
printf("time.tv_sec = %ld\r\n", ev.time.tv_sec);
printf("time.tv_usec = %ld\r\n", ev.time.tv_usec);
printf("type = %d\r\n", ev.type);
printf("code = %d\r\n", ev.code);
printf("value = %d\r\n", ev.value);
}
else
{
printf("read error %d\r\n", len);
}
}
}
int main(int argc, char **argv)
{
if (argc < 2)
{
printf("Usage: %s <dev> [noblock]\n", argv[0]);
return -1;
}
if((argc == 3) && (!strcmp(argv[2], "noblock")))
{
fd = open(argv[1], O_RDWR | O_NONBLOCK);
}
else
{
fd = open(argv[1], O_RDWR);
}
if(fd < 0)
{
perror("open:");
return -1;
}
print_ev_info();
read_block_nblock();
//read_poll();
//read_sync();
close(fd);
}
- 使用如下指令进行阻塞式询问
./input_demo /dev/input/event1
- 使用如下指令进行非阻塞式询问
./input_demo /dev/input/event1 noblock
POLL/SELECT 方式
API
- poll 函数
poll 是在指定时间内论询一定数量的文件描述符,来测试其中是否有就绪的
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
函数参数:
其中,struct pollfd 定义为:
struct pollfd {
/* 文件描述符 */
int fd; /* file descriptor */
/*
*监听事件
POLLIN 有数据可读
POLLPRI 等同于 POLLIN
POLLOUT 可以写数据
POLLERR 发生了错误
POLLHUP 挂起
POLLNVAL 无效的请求,一般是 fd 未 open
POLLRDNORM 等同于 POLLIN
POLLRDBAND Priority band data can be read,有优先级较较高的“ band data”可读
Linux 系统中很少使用这个事件
POLLWRNORM 等同于 POLLOUT
POLLWRBAND Priority data may be written.
*/
short events; /* requested events */
/*
*接收到的事件,参数同上
*/
short revents; /* returned events */
};
返回值:
- 非负值:成功
- 0:超时
- -1:错误
程序代码:
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <poll.h>
#include <signal.h>
int fd = 0;
char *ev_names[] = {
"EV_SYN ","EV_KEY ","EV_REL ","EV_ABS ","EV_MSC ","EV_SW","NULL ","NULL ",
"NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ",
"EV_LED ","EV_SND ","NULL ","EV_REP ","EV_FF","EV_PWR ",};
static void print_ev_info(void)
{
int len;
int err;
struct input_id id;
unsigned char byte;
int bit;
unsigned int evbit[5];
err = ioctl(fd, EVIOCGID, &id);
if(err < 0)
{
perror("ioctl:");
return;
}
printf("bustype = 0x%x\n", id.bustype );
printf("vendor = 0x%x\n", id.vendor );
printf("product = 0x%x\n", id.product );
printf("version = 0x%x\n", id.version );
len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit);
if (len > 0 && len <= sizeof(evbit))
{
printf("support ev type: ");
for (int i = 0; i < len; i++)
{
byte = ((unsigned char *)evbit)[i];
for (bit = 0; bit < 8; bit++)
{
if (byte & (1<<bit)) {
printf("%s ", ev_names[i*8 + bit]);
}
}
}
printf("\n");
}
}
static void read_poll(void)
{
int ret;
struct input_event ev;
struct pollfd fds[1];
nfds_t nfds = 1;
while (true)
{
fds[0].fd = fd;
fds[0].events = POLLIN;
fds[0].revents = 0;
ret = poll(fds, nfds, 5000);
if(ret > 0)
{
if(fds->revents == POLLIN)
{
while(read(fd, &ev, sizeof(struct input_event)) == sizeof(struct input_event))
{
printf("**********************************\r\n");
printf("time.tv_sec = %ld\r\n", ev.time.tv_sec);
printf("time.tv_usec = %ld\r\n", ev.time.tv_usec);
printf("type = %d\r\n", ev.type);
printf("code = %d\r\n", ev.code);
printf("value = %d\r\n", ev.value);
}
}
}
else if(ret == 0)
{
printf("read out %d\r\n", ret);
}
else
{
perror("read:");
}
}
}
int main(int argc, char **argv)
{
if (argc < 2)
{
printf("Usage: %s <dev> [noblock]\n", argv[0]);
return -1;
}
if((argc == 3) && (!strcmp(argv[2], "noblock")))
{
fd = open(argv[1], O_RDWR | O_NONBLOCK);
}
else
{
fd = open(argv[1], O_RDWR);
}
if(fd < 0)
{
perror("open:");
return -1;
}
print_ev_info();
//read_block_nblock();
read_poll();
//read_sync();
close(fd);
}
- 使用如下指令进行询问
./input_demo /dev/input/event1 noblock
异步通知方式
步骤:
- 编写信号处理函数:
static void sig_func(int sig)
{
int val;
read(fd, &val, 4);
printf("get button : 0x%x\n", val);
}
- 注册信号处理函数:
signal(SIGIO, sig_func);
Linux 系统中也有很多信号,在 Linux 内核源文件 include\uapi\asmgeneric\signal.h 中,有很多信号的宏定义:
- 打开驱动:
fd = open(argv[1], O_RDWR);
- 把进程 ID 告诉驱动:
fcntl(fd, F_SETOWN, getpid());
- 使能驱动的 FASYNC 功能:
flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | FASYNC);
API:
-
signal函数
设置某一信号的对应动作
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函数参数:
参数 | 描述 |
---|---|
signum | 指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号 |
handler | 描述了与信号关联的动作 |
- fcntl
根据文件描述词来操作文件的特性
int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); int fcntl(int fd, int cmd, struct flock *lock);
fcntl函数有5种功能:
- 复制一个现有的描述符(cmd=F_DUPFD).
- 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
- 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
- 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
- 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
cmd 选项:
- F_GETFD 取得与文件描述符fd联合close-on-exec标志
- F_SETFD 设置close-on-exec旗标。该旗标以参数arg的FD_CLOEXEC位决定
- F_GETFL 取得fd的文件状态标志,如同下面的描述一样(arg被忽略)
- F_SETFL 设置给arg描述符状态标志,可以更改的几个标志是:O_APPEND, O_NONBLOCK,O_SYNC和O_ASYNC(O_NONBLOCK :非阻塞I/O;如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,read或write调用返回-1和EAGAIN错误 ; O_APPEND: 强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志 ; O_DIRECT : 最小化或去掉reading和writing的缓存影响.系统将企图避免缓存你的读或写的数据. 如果不能够避免缓存,那么它将最小化已经被缓存了的数 据造成的影响.如果这个标志用的不够好,将大大的降低性能 ; O_ASYNC: 当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候)
- F_GETOWN 取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回成负值(arg被忽略)
- F_SETOWN 设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明,否则,arg将被认为是进程id
程序代码:
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <poll.h>
#include <signal.h>
int fd = 0;
char *ev_names[] = {
"EV_SYN ","EV_KEY ","EV_REL ","EV_ABS ","EV_MSC ","EV_SW","NULL ","NULL ",
"NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ",
"EV_LED ","EV_SND ","NULL ","EV_REP ","EV_FF","EV_PWR ",};
static void print_ev_info(void)
{
int len;
int err;
struct input_id id;
unsigned char byte;
int bit;
unsigned int evbit[5];
err = ioctl(fd, EVIOCGID, &id);
if(err < 0)
{
perror("ioctl:");
return;
}
printf("bustype = 0x%x\n", id.bustype );
printf("vendor = 0x%x\n", id.vendor );
printf("product = 0x%x\n", id.product );
printf("version = 0x%x\n", id.version );
len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit);
if (len > 0 && len <= sizeof(evbit))
{
printf("support ev type: ");
for (int i = 0; i < len; i++)
{
byte = ((unsigned char *)evbit)[i];
for (bit = 0; bit < 8; bit++)
{
if (byte & (1<<bit)) {
printf("%s ", ev_names[i*8 + bit]);
}
}
}
printf("\n");
}
}
static void my_sig_handler(int sig)
{
struct input_event ev;
if(sig == SIGIO)
{
while(read(fd, &ev, sizeof(struct input_event)) == sizeof(struct input_event))
{
printf("**********************************\r\n");
printf("time.tv_sec = %ld\r\n", ev.time.tv_sec);
printf("time.tv_usec = %ld\r\n", ev.time.tv_usec);
printf("type = %d\r\n", ev.type);
printf("code = %d\r\n", ev.code);
printf("value = %d\r\n", ev.value);
}
}
}
static void read_sync(void)
{
int flags;
int count = 0;
/* 注册信号处理函数 */
signal(SIGIO, my_sig_handler);
/* 把APP的进程号告诉驱动程序 */
fcntl(fd, F_SETOWN, getpid());
/* 使能"异步通知" */
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
while (true)
{
printf("main loop count = %d\n", count++);
sleep(2);
}
}
int main(int argc, char **argv)
{
if (argc < 2)
{
printf("Usage: %s <dev> [noblock]\n", argv[0]);
return -1;
}
if((argc == 3) && (!strcmp(argv[2], "noblock")))
{
fd = open(argv[1], O_RDWR | O_NONBLOCK);
}
else
{
fd = open(argv[1], O_RDWR);
}
if(fd < 0)
{
perror("open:");
return -1;
}
print_ev_info();
//read_block_nblock();
//read_poll();
read_sync();
close(fd);
}
- 使用如下指令进行询问
./input_demo /dev/input/event1