【Linux】输入系统应用

news2024/11/17 3:39:47

# 前置知识

(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

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

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

相关文章

数码管的动态显示(一)

1.原理 把每一个数码管闪烁的时间设置为1ms&#xff0c;肉眼观察不到就会认为6个数码管在同时闪烁。 实验目标&#xff1a; 使用6位8段数码管实现数码管的动态显示&#xff0c;显示的内容就是0-999_999。当计数到最大值&#xff0c;让他归零&#xff0c;然后循环显示。每0.1秒…

【嵌入式移植】8、U-Boot源码分析5—启动过程分析start.S

U-Boot源码分析5—启动过程分析start.S 1、boot0.h2、reset2.1、vectors2.2、ELn2.2.1 EL32.2.2、EL2、EL1 2.3、SMPEN2.3、core errate2.4、lowlevel_init 前面从U-Boot编译的角度分析了其Makefile、链接脚本等&#xff0c;本章开始正式分析U-Boot启动过程 从上一篇文章7、U-…

30天JS挑战(第十六天)----鼠标拖影效果

第十六天挑战(鼠标拖影效果) 地址&#xff1a;https://javascript30.com/ 所有内容均上传至gitee&#xff0c;答案不唯一&#xff0c;仅代表本人思路 中文详解&#xff1a;https://github.com/soyaine/JavaScript30 该详解是Soyaine及其团队整理编撰的&#xff0c;是对源代…

20 个不同的 Python 函数实例

Python 是一种广泛使用的高级编程语言&#xff0c;其函数是 Python 编程中至关重要的概念之一。函数是一段可以重复使用的代码块&#xff0c;可以接收输入参数并返回输出结果。使用函数能够提高代码的可读性、可维护性和重用性。 基础知识 在 Python 中&#xff0c;函数使用关…

[动态规划]---part1

前言 作者&#xff1a;小蜗牛向前冲 专栏&#xff1a;小蜗牛算法之路 专栏介绍&#xff1a;"蜗牛之道&#xff0c;攀登大厂高峰&#xff0c;让我们携手学习算法。在这个专栏中&#xff0c;将涵盖动态规划、贪心算法、回溯等高阶技巧&#xff0c;不定期为你奉上基础数据结构…

LeetCode-02

225. 用队列实现栈 用两个队列实现栈的功能&#xff0c;思路如下&#xff1a; 往空队列中放新元素把非空队列中的元素依次放入刚才添加了新元素的队列&#xff0c;直到非空队列变为空队列 class MyStack(object):def __init__(self):self.queue1 []self.queue2 []def push(…

thymeleaf 一个莫名其妙的错误提示 org.attoparser.ParseException

thymeleaf 一个莫名其妙的错误提示 介绍 开发过程中遇到一个莫名奇妙的错误&#xff0c;一时竟然不知道怎么解决&#xff0c;找官网也没有找到 问题 页面显示 错误日志 org.attoparser.ParseException: (Line 96, Column 5) Malformed markup: Attribute “}” appears m…

医学大数据|统计基础|医学统计学(笔记):开学说明与目录

开始学习统计基础&#xff0c;参考教材&#xff1a;医学统计学第五版 点点关注一切来学习吧 责任编辑&#xff1a;医学大数据刘刘老师&#xff1a;头部医疗大数据公司医学科学部研究员 邮箱&#xff1a;897282268qq.com 久菜盒子工作室 我们是&#xff1a;985硕博/美国全奖…

【开源】SpringBoot框架开发数据可视化的智慧河南大屏

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 数据模块 A4.2 数据模块 B4.3 数据模块 C4.4 数据模块 D4.5 数据模块 E 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的数据可视化的智慧河南大屏&#xff0c;包含了GDP、…

js面试 forEach ,map,for ,for in , for of

forEach ,map&#xff0c;for ,for in , for of 1 forEach 回调3个参数value&#xff0c;index&#xff0c;arr&#xff08;原数组&#xff09; 2 map 1&#xff1a;map() 不会改变原始数组 2&#xff1a;函数的作用是对数组中的每一个元素进行处理&#xff0c;返回新的元素…

动态规划(算法竞赛、蓝桥杯)--背包DP求具体方案

1、B站视频链接&#xff1a;E20 背包DP 求具体方案_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; const int N1010; int v[N],w[N]; int f[N][N],p[N][N];int main(){int n,m;cin>>n>>m;for(int i1;i<n;i)cin>>v[i]>>w[i…

cmd模式下启动mysql

1.打开cmd输入services.msc&#xff0c;找到MYSQL&#xff0c;右击属性&#xff0c;找到可执行文件路径&#xff0c;加载到环境变量。 2.打开cmd&#xff0c;启动MYSQL&#xff1a;输入net start mysql; 3.登陆MYSQL&#xff0c;需要管理权限&#xff1b; 输入&#xff1a;my…

day34贪心算法 part03

1005. K 次取反后最大化的数组和 简单 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后&#xff0c;返回数…

达梦数据库查询语句内存溢出问题解决

背景&#xff1a;达梦数据库使用过程中&#xff0c;某天突然服务宕机&#xff0c;导致各类后端服务无法注册到nacos上&#xff0c;重启之后nacos正常启动&#xff0c;可执行一条两千多条数据量的连表查询时间很长&#xff0c;甚至会报错&#xff0c;经查看日志发现在查询过程中…

【InternLM 笔记】使用InternStudio 体验书生·浦语2-chat-1.8b随记

书生浦语2-chat-1.8b 介绍 书生浦语-1.8B (InternLM2-1.8B) 是第二代浦语模型系列的18亿参数版本。为了方便用户使用和研究&#xff0c;书生浦语-1.8B (InternLM2-1.8B) 共有三个版本的开源模型&#xff0c;他们分别是&#xff1a; InternLM2-1.8B: 具有高质量和高适应灵活性…

找不到emp.dll如何处理?emp.dll丢失的多种的解决方法分享

在计算机游戏中&#xff0c;当特定的核心文件emp.dll发生丢失时&#xff0c;可能会引发一系列影响游戏运行的问题。由于emp.dll通常是游戏运行所必需的动态链接库文件&#xff0c;它的缺失会导致游戏无法正常启动或加载&#xff0c;玩家可能面临“无法进入游戏”的尴尬境地。其…

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

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

【海贼王的数据航海:利用数据结构成为数据海洋的霸主】链表—双向链表

目录 往期 1 -> 带头双向循环链表(双链表) 1.1 -> 接口声明 1.2 -> 接口实现 1.2.1 -> 双向链表初始化 1.2.2 -> 动态申请一个结点 1.2.3 -> 双向链表销毁 1.2.4 -> 双向链表打印 1.2.5 -> 双向链表判空 1.2.6 -> 双向链表尾插 1.2.7 -&…

枚举——完美立方算法

枚举 基于逐个尝试答案的一种问题求解策略 例如&#xff1a;求小于N的最大素数 找不到一个数学公式&#xff0c;使得根据N就可以计算出这个素数 N-1是素数吗&#xff1f;N-2是素数吗&#xff1f; …… 判断N-i是否是素数的问题 转化成求小于N的全部素数&#xff08;可以用筛法…

2024最佳steam搬砖项目,日入5000,保姆级教程,小白无脑操作

今天给带来的项目是“2024最佳steam搬砖项目&#xff0c;日入5000&#xff0c;保姆级教程&#xff0c;小白无脑操作” 二、项目准备 需要下载如下图所显示的app&#xff0c;一个是steam&#xff0c;一个是国内交易的网易buff 安装好了之后就可以开始实操了&#xff0c;国内外汇…