【Linux】输入系统应用

# 前置知识

(1)输入子系统分为三层,分别是事件处理层、核心层、设备驱动层;
(2)鼠标移动、键盘按键按下等输入事件都需要通过设备驱动层→核心层→事件处理层→用户空间,层层上报,直到应用程序;

 事件处理层

(1)事情处理层主要是负责将输入事件上报到应用程序;对于向内核输入子系统注册的输入设备,在sysfs中创建设备节点,应用程序通过操作设备节点来获取输入事件;
(2)事件处理层将输入事件划分为几大类,比如:通用事件(event)、鼠标事件(mouse)、摇杆事件(js)等等,每个输入类设备在注册时需要指定自己属于哪个类;
(3)通用事件是能和任何输入设备匹配上的,意味着只要注册一个输入类设备就会sysfs就会创建对应的/dev/input/eventn设备节点;

核心层 

(1)核心层是起到承上启下的作用,负责协调输入事件在事件处理层和设备驱动层之间的传递;
(2)核心层负责管理事件处理层和设备驱动层,核心层提供相关的接口函数,事件处理层和设备驱动层都必须先向核心层注册,然后才能工作;
(3)核心层负责设备驱动层和事件处理层的匹配问题,设备驱动根据硬件特性是各种各样的,事件处理层也是分为好几种类型,具体硬件驱动和哪一类或者哪几类事件处理类型匹配,需要核心层去做判断;

设备驱动层 

(1)设备驱动层分为两大部分:硬件特性部分 + 核心层注册接口;
(2)设备驱动层的硬件特性部分是具体操作硬件的,不同的硬件差异很大,且不属于内核,这也是我们移植驱动的重点;
(3)核心层注册接口:输入子系统提供的输入设备向内核注册的接口,属于内核代码部分,我们需要理解和会使用这些接口,接口的使用都是模式化的,降低了编写驱动的难度;

 示例

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

  1. APP 发起读操作,若无数据则休眠;
  2. 用户操作设备,硬件上产生中断;
  3. 输入系统驱动层对应的驱动程序处理中断:读取到数据,转换为标准的输入事件,向核心层汇报。所谓输入事件就是一个“ struct input_event”结构体。
  4. 核心层可以决定把输入事件转发给上面哪个 handler 来处理:从 handler 的名字来看,它就是用来处输入操作的。有多种 handler,比如: evdev_handler、 kbd_handler、 joydev_handler 等等。最常用的是 evdev_handler:它只是把 input_event 结构体保存在内核buffer 等, APP 来读取时就原原本本地返回。它支持多个 APP 同时访问输入设备,每个 APP 都可以获得同一份输入事件。当 APP 正在等待数据时, evdev_handler 会把它唤醒,这样 APP 就可以返回数据。

APP 对输入事件的处理:

  1. 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 未 openPOLLRDNORM 等同于 POLLINPOLLRDBAND Priority band data can be read,有优先级较较高的“ band data”可读
Linux 系统中很少使用这个事件POLLWRNORM 等同于 POLLOUTPOLLWRBAND 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种功能:

  1.  复制一个现有的描述符(cmd=F_DUPFD).
  2.  获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
  3.     获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
  4.     获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
  5.     获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

 cmd 选项:

  1.  F_GETFD     取得与文件描述符fd联合close-on-exec标志
  2.  F_SETFD     设置close-on-exec旗标。该旗标以参数arg的FD_CLOEXEC位决定
  3.  F_GETFL     取得fd的文件状态标志,如同下面的描述一样(arg被忽略)  
  4.  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信号发送到进程组,例如:当有数据可以读的时候)
  5.  F_GETOWN 取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回成负值(arg被忽略) 
  6.  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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/717006.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

腾讯云4核8G服务器申请费用多少?性能如何?支持几个人?

腾讯云4核8G服务器支持多少人在线访问&#xff1f;支持25人同时访问。实际上程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&#xff0c;假设公网带宽太小&#xff0c;流量直接卡在入口&#xff0c;4核8G配置的CPU内存也会造成计算…

大数据报告检测到风险等级太高是怎么回事呢?

随着金融风控越来越多元化&#xff0c;大数据作为新兴的技术被运用到贷前风控中去了&#xff0c;不少人也了解过自己的大数据&#xff0c;但是由于相关知识不足&#xff0c;看不懂报告&#xff0c;在常见的问题中&#xff0c;大数据检测到风险等级太高是怎么回事呢?小易大数据…

vsomeip源码剖析--00环境搭建

环境 Win11 WSL2 Ubuntu22.04安装依赖 sudo apt-get install cmake sudo apt-get install libboost-system1.71-dev libboost-thread1.71-dev libboost-log1.71-dev源码编译 获取源码 https://github.com/COVESA/vsomeip.git编译 cd vsomeip mkdir build cd build// 一般…

漫漫数学之旅035

文章目录 经典格言数学习题古今评注名人小传 - 黎勒•笛卡尔 经典格言 完美的数和完美的人是同样罕见的。——黎勒•笛卡尔&#xff08;Ren Descrates&#xff09; 完美的数和完美的人都是极为罕见的。这句话表达了一个哲学观点&#xff0c;即无论是在数学领域还是人类自身&am…

Spring框架相关问题

RabbitMQ相关问题 Spring框架相关问题 一、Spring容器中的Bean是线程安全的吗&#xff1f;二、如何保证Spring容器中的Bean是线程安全的呢&#xff1f;三、什么情况下会触发Spring事务回滚&#xff1f;四、如果事务方法抛出IOException&#xff0c;是否会触发Spring事务回滚&a…

Zookeeper学习2:原理、常用脚本、选举机制、监听器

文章目录 原理选举机制&#xff08;重点&#xff09;情况1&#xff1a;正常启动集群情况2&#xff1a;集群启动完&#xff0c;中途有机器挂了 监听器客户端向服务端写入数据客户端向服务端Leader节点写入客户端向服务端Follower节点写入 Paxos算法&#xff08;每个节点都可以提…

AMDGPU KFD Test 编译使用

ROCT-Thunk-Interface是一个用于在ROCm软件堆栈中提供设备无关性的层。它是ROCm的一部分,允许不同的硬件平台(如AMD GPU和Intel CPU)使用相同的API进行计算。 要安装ROCT-Thunk-Interface,首先需要创建一个新的目录,并进入该目录: mkdir rocm-build cd rocm-build然后,…

ruoyi 图片等文件资源读取

老是忘&#xff0c;记录一下 ResourcesConfig 文件下 /** 本地文件上传路径 */ registry.addResourceHandler(Constants.RESOURCE_PREFIX "/**").addResourceLocations("file:" RuoYiConfig.getProfile() "/"); /*** 资源映射路径 前缀*/ …

kafka消费者重平衡是什么?怎么避免?

消费者重平衡是指主题下的分区怎么分配给消费者的过程。下面这个图可以看出该过程&#xff1a;原来有2个消费者&#xff0c;3个分区&#xff0c;其中一个消费者肯定就的处理2个分区了。那么当新加入消费者时&#xff0c;则每个消费者就只处理一个分区了。处理这个分区过程的叫协…

详解Nacos注册中心的使用

文章目录 1、安装2、服务注册2.1、引入依赖2.2、配置nacos地址2.3、重启 3、服务分级存储模型3.1、给user-service配置集群3.2、同集群优先的负载均衡 4、权重配置5、环境隔离5.1、创建namespace5.2、配置namespace 6、Nacos与Eureka的区别7、代码免费分享 ​&#x1f343;作者…

首例以“冠状病毒”为主题的勒索病毒,篡改系统MBR

前言概述 2020年勒索病毒攻击仍然是网络安全的最大威胁&#xff0c;在短短三个月的时间里&#xff0c;已经出现了多款新型的勒索病毒&#xff0c;关于2020年勒索病毒攻击新趋势&#xff0c;可以阅读笔者写的上一篇文章&#xff0c;里面有详细的分析&#xff0c;从目前观察到的…

RH850P1X芯片学习笔记-Generic Timer Module -ATOM

文章目录 ARU-connected Timer Output Module (ATOM)OverviewGLOBAL CHANNEL CONTROL BLOCK ATOM Channel architectureATOM Channel modesSOMP-Signal Output Mode PWMSOMP - ARUSOMC-Signal Output Mode CompareSOMC - ARUSOMC – COMPARE COMMANDSOMC – OUTPUT ACTIONATOM …

2024年腾讯云新用户优惠券领取入口及使用教程

随着云计算技术的不断发展和普及&#xff0c;越来越多的个人和企业选择使用云服务。腾讯云作为国内领先的云服务提供商&#xff0c;为了吸引新用户&#xff0c;经常推出各种优惠活动&#xff0c;其中就包括新用户专属优惠券&#xff0c;本文将为大家分享腾讯云新用户优惠券的领…

5个好玩神奇还免费的工具网站收藏不后悔-搜嗖工具箱

生命倒计时 http://www.thismuchlonger.com 这是一个相哇塞的网站&#xff0c;可以让我们静下心来好好想想我们来这个世界究竟为了什么&#xff0c;因为当我们作为命运的主宰者。敲打键盘设定好自己一生长度的时候&#xff0c;我们的剩余寿命已经成绝对值&#xff0c;一旦生命…

创建型模式之原型模式

一、概述 1、工作原理&#xff1a;将一个原型对象传给要发动创建的对象(即客户端对象),这个要发动创建的对象通过请求原型对象复制自己来实现创建过程 2、通过克隆方法所创建的对象是全新的对象&#xff0c;它们在内存中拥有新的地址&#xff0c;每一个克隆对象都是独立的 3…

李沐动手学习深度学习——3.6练习

本节直接实现了基于数学定义softmax运算的softmax函数。这可能会导致什么问题&#xff1f;提示&#xff1a;尝试计算exp(50)的大小。 可能存在超过计算机最大64位的存储&#xff0c;导致精度溢出&#xff0c;影响最终计算结果。 本节中的函数cross_entropy是根据交叉熵损失函数…

JavaScript之数据类型

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 数据类型   Java…

20.图

图的基本概念 1.图的定义 由顶点和边组成的集合&#xff0c;G(V,E) 2.基本概念 邻接点&#xff1a; 对于无向图u v来说&#xff0c;uv互为邻接点 对于有向图u->v来说&#xff0c;v是u的邻接点&#xff0c;但u不是v的临界点 路径&#xff1a; 一个顶点到另一个顶点所经过的…

动态规划-最长公共子串(c)

动态规划 动态规划&#xff08;dynamic programming&#xff09;是一种算法设计方法。基本思想是在对一个问题的多阶段决策中&#xff0c;按照某一顺序&#xff0c;根据每一步所选决策的不同&#xff0c;会引起状态的转移&#xff0c;最后会在变化的状态中获取到一个决策序列。…

vs code更新后json文件无法识别通配符 ,编译多文件失败的解决办法

问题描述 在Mac或者LInux上&#xff0c;进行C/C相同路径下进行多文件编译时&#xff0c;之前设置好的json文件突然不能解释通配符&#xff0c;并且将带有单引号的地址传给clang&#xff0c;由于*.c被扩在单引号中&#xff0c;clang找不到文件导致失败。 如果将命令端中的指令复…