【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 未 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种功能:

  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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Windows10蓝牙开关按钮不见了问题??

Windows10蓝牙开关按钮不见了问题&#xff1f;&#xff1f;此类问题一般是系统更新不及时的bug&#xff0c;遗漏掉了蓝牙相关驱动插件 试过很多方法&#xff0c;直接下载一个驱动人生即可&#xff0c;主要通过官网下载 下载这个就行 打开软件自动扫描就可以了 最后查看结果

Linkedln领英账号限制问题|通过代理IP安全使用Linkedln

LinkedIn是跨境外贸必备的拓客工具&#xff0c;世界各地的许多专业人士都使用领英来作为发布和共享内容的主要工具&#xff0c;这使得它成为跨境出海必备的渠道工具。 但是不少做外贸的朋友都知道&#xff0c;领英账号很容易遭遇限制封禁&#xff0c;但如果善用工具&#xff0…

深度相机xyz点云文件三维坐标和jpg图像文件二维坐标的相互变换函数

深度相机同时拍摄xyz点云文件和jpg图像文件。xyz文件里面包含三维坐标[x,y,z]和jpg图像文件包含二维坐标[x&#xff0c;y],但是不能直接进行变换&#xff0c;需要一定的步骤来推演。 下面函数是通过box二维框[xmin, ymin, xmax, ymax, _, _ ]去截取xyz文件中对应box里面的点云…

2023人机交互期末复习

考试题型及分值分布 1、选择题&#xff08;10题、20分&#xff09; 2、填空题&#xff08;10题、20分&#xff09; 3、判断题&#xff08;可选、5题、10分&#xff09; 4、解答题&#xff08;5~6题、30分&#xff09; 5、分析计算题&#xff08;1~2题、20分&#xff09; 注意&…

埃隆・马斯克46页文件起诉OpenAI违反成立初衷:一场涉及通用人工智能未来的法律纠纷

近日&#xff0c;科技界的重量级人物埃隆・马斯克对十年前他参与创立的生成式人工智能公司OpenAI提起诉讼&#xff0c;引发了广泛关注。马斯克指控OpenAI违反了他们之间的合同&#xff0c;并提出了一系列引人注目的言论&#xff0c;包括指责OpenAI将GPT-4称为通用人工智能&…

C# Onnx segment-anything 分割万物 一键抠图

目录 介绍 效果 模型信息 sam_vit_b_decoder.onnx sam_vit_b_encoder.onnx 项目 代码 下载 C# Onnx segment-anything 分割万物 一键抠图 介绍 github地址&#xff1a;https://github.com/facebookresearch/segment-anything The repository provides code for runn…

光伏储能MPPT控制系统如何进行浪涌静电保护?

MPPT&#xff08;Maximum Power Point Tracking&#xff09;是太阳能电池板光伏发电系统中重要的一种控制技术。MPPT控制器能够实时侦测太阳能板的发电电压&#xff0c;并追踪最高电压电流值&#xff08;VI&#xff09;&#xff0c;使系统以最大功率输出对蓄电池充电&#xff0…

libswift_Concurrency.dylib (which was built for iOS 13.0)

app启动时崩溃&#xff1a;libswift_Concurrency.dylib (which was built for iOS 13.0) ios真机系统版本过低

AI技术初探:普通人ALL IN AI入门指南

自从去年ChatGPT如流星划过夜空&#xff0c;照亮了整个AI领域&#xff0c;它所带来的技术革新与热潮仿佛一场无声的暴风雨&#xff0c;席卷了全球的科技圈。身为一名低阶IT从业者&#xff0c;感觉这太高大上了&#xff0c;与我的工作有毛线关系。 但是&#xff0c;AI技术的飞速…

数据库学习案例20240304-mysql数据库案例总结(碎片,统计信息)

1 表中的碎片 在InnoDB中删除行的时候&#xff0c;这些行只是被标记为“已删除”&#xff0c;而不是真正从物理存储上进行了删除&#xff0c;因而存储空间也没有真正被释放回收。InnoDB的Purge线程会异步地来清理这些没用的索引键和行。但是依然没有把这些释放出来的空间还给操…

pydub、playsound播放声音;gradio、streamlit页面播放声音;gradio 页面图像、视频及调用摄像头

1、pydub from pydub import AudioSegment from pydub.playback import playsong AudioSegment.from_wav(r"C:\Users\loong\Downloads\zh.wav") play(song)2、playsound from playsound import playsoundplaysound(r"voice.wav")3、streamlit import s…

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

减少batch_size&#xff08;如减少到1&#xff09;是否会影响读取性能&#xff1f; 肯定会影响&#xff0c;计算机io性能而言&#xff0c;随着batch_size增大&#xff0c;读取越来越快&#xff0c;需要的时间越少。这里会涉及到计算机操作系统的知识点&#xff0c;内存与硬盘之…

牛客网C++专项题目整理(1)

1. 若有定义语句:char s[3][10],(*k)[3],*p;则以下赋值语句错误的是 1.p s; 2.p k; 3.p s[0]; 4.k s; 答案&#xff1a;124 char s[3][10] s 是数组指针&#xff0c;类型为char (*)[3]&#xff0c;所指向的每个数组长度为10; char (*k)[3] k是一个数组指针&a…

C/C++ 乘积尾零问题(蓝桥杯)

如下的10行数据&#xff0c;每行有10个整数&#xff0c;请你求出它们的乘积的末尾有多少个零&#xff1f; 5650&#xff0c;4542 3554 473 946 4114 3871 9073 90 4329 2758 7949 6113 5659 5245 7432 3051 4434 6704 3594 9937 1173 6866 3397 4759 7557 3070 2287 1453 9899…

MySQL数据库运维第一篇(日志与主从复制)

文章目录 一、错误日志二、二进制日志三、查询日志四、慢查询日志&#xff08;记录超时的sql语句&#xff09;五、主从复制概括六、主从复制原理七、搭建主从复制八、主从复制的测试 在这篇深入的技术文章中&#xff0c;作者将以明晰透彻的方式详细介绍MySQL数据库中关键的日志…

蓝桥杯——123

123 二分等差数列求和前缀和数组 题目分析 连续一段的和我们想到了前缀和&#xff0c;但是这里的l和r的范围为1e12&#xff0c;明显不能用O(n)的时间复杂度去求前缀和。那么我们开始观察序列的特点&#xff0c;可以按照等差数列对序列进行分块。如上图&#xff0c;在求前10个…

C++基于多设计模式下的同步异步日志系统day5

C基于多设计模式下的同步&异步日志系统day5 &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C基于多设计模式下的同步&异步日志系统 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&am…

【Vue3】3-6 : 仿ElementPlus框架的el-button按钮组件实

文章目录 前言 本节内容实现需求完整代码如下&#xff1a; 前言 上节,我们学习了 slot插槽&#xff0c;组件内容的分发处理 本节内容 本小节利用前面学习的组件通信知识&#xff0c;来完成一个仿Element Plus框架的el-button按钮组件实现。 仿造的地址&#xff1a;uhttps://…

SpringBoot接口防抖(防重复提交)的一些实现方案

前言 啥是防抖 思路解析 分布式部署下如何做接口防抖&#xff1f; 具体实现 请求锁 唯一key生成 重复提交判断 前言 作为一名老码农&#xff0c;在开发后端Java业务系统&#xff0c;包括各种管理后台和小程序等。在这些项目中&#xff0c;我设计过单/多租户体系系统&a…

2024最新算法:鹦鹉优化算法(Parrot optimizer,PO)求解23个基准函数

一、鹦鹉优化算法 鹦鹉优化算法&#xff08;Parrot optimizer&#xff0c;PO&#xff09;由Junbo Lian等人于2024年提出的一种高效的元启发式算法&#xff0c;该算法从驯养的鹦鹉中观察到的觅食、停留、交流和对陌生人行为的恐惧中汲取灵感。这些行为被封装在四个不同的公式中…