输入系统应用编程

所学来自百问网

目录

1. 输入系统框架

2. 内核如何表示输入设备

2.1 type:表示哪类事件

2.2 code:表示该类事件下的哪一个事件

2.3 value:表示事件值

2.4 事件之间的界限

2.5 示例代码

3. 输入设备支持完整的API操作

3.1 阻塞(查询方式)

3.2 非阻塞(休眠-唤醒方式)

3.3 POLL/SELECT方式

3.3.1 poll函数

3.3.2 select函数

3.4 异步通知方式

3.4.1 操作步骤

3.4.2 signal函数

3.4.3 fcntl函数

4. 示例代码

4.1 阻塞和非阻塞方式

4.2 POLL方式

4.3 select方式

4.4 异步通知方式


1. 输入系统框架

假设用户程序直接访问/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就可以返回数据。

5.APP对输入事件的处理:

APP 获得数据的方法有 2 种:直接访问设备节点(比如 /dev/input/event0,1,2,...),或者通过 tslib、libinput 这类库来间接访问设备节点。这些库简化了对数据的处理。

2. 内核如何表示输入设备

获取的数据:

每个输入事件input_event中都含有发生时间:timeval表示的是“自系统启动以来过了多少时间”,它是一个结构体,含有“tv_sec、tv_usec”两项 (即秒、微秒)。

图7.11 中的 type 为 3,对应 EV_ABS;code 为 0x35 对应 ABS_MT_POSITION_X;code 为 0x36 对应ABS_MT_POSITION_Y。

上图中还发现有2个同步事件:它的type、code、value都为0。表示电容屏上报了2次完整的数据。

输入事件input_event中更重要的是:type(哪类事件)、code(哪个事件)、 value(事件值)

2.1 type:表示哪类事件

2.2 code:表示该类事件下的哪一个事件

比如对于EV_KEY(按键)类事件,它表示键盘。键盘上有很多按键,比如数字键1、2、3,字母键A、B、C里等。所以可以有图 7.5这些事件:

对于触摸屏,它提供的是绝对位置信息,有 X 方向、Y 方向,还有压力值。 所以code值有图 7.6这些:

2.3 value:表示事件值

对于按键,它的value可以是0(表示按键被按下)、1(表示按键被松开)、 2(表示长按);

对于触摸屏,它的value就是坐标值、压力值。

2.4 事件之间的界限

驱动程序上报完一系列的数据后,会上报一个“同步事件”,表示数据上报完毕。APP读到“同步事件”时,就知道已经读完了当前的数据。

同步事件也是一个input_event结构体,它的type、code、value三项都是0。

2.5 示例代码

// 本段代码用于获取设备节点的input_id信息和获取设备节点所支持的事件类型
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
​
/*运行方式: ./01_get_input_info /dev/input/event0 */
int main(int argc, char **argv)
{
    int fd;
    int err;
    int len;
    int i;
    unsigned char byte;
    int bit;
    struct input_id id;
    unsigned int evbit[2];
    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 ",
        };
    
    if (argc != 2)
    {
        printf("Usage: %s <dev>\n", argv[0]);
        return -1;
    }
​
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("open %s err\n", argv[1]);
        return -1;
    }
    // EVIOCGID是请求码 用于获取struct input_id的信息
    err = ioctl(fd, EVIOCGID, &id);
    if (err == 0)
    {
        // 打印获取到struct input_id的信息
        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 );
    }
    // EVIOCGBIT(ev_type, len)是请求码 用于查询输入设备支持的事件类型
    // ev_type:事件类型,传0代表所有事件类型  len:期望的缓冲区长度 
    len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
    if (len > 0 && len <= sizeof(evbit))
    {
        printf("support ev type: ");
        for (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");
    }
    return 0;
}

3. 输入设备支持完整的API操作

支持的机制:阻塞、非阻塞、POLL/SELECT、异步通知

3.1 阻塞(查询方式)

APP调用open函数时,传入“O_NONBLOCK”表示“非阻塞”。

APP调用read函数读取数据时,如果驱动程序中有数据,那么APP的read函数会返回数据,否则立刻返回错误。

fd = open(argv[1], O_RDWR);

3.2 非阻塞(休眠-唤醒方式)

APP调用open函数时,不要传入“O_NONBLOCK”。

APP调用read函数读取数据时,如果驱动程序中有数据,那么APP的read函数会返回数据;否则APP就会在内核态休眠,当有数据时驱动程序会把APP唤醒,read函数恢复执行并返回数据APP。

fd = open(argv[1], O_RDWR | O_NONBLOCK);

3.3 POLL/SELECT方式

POLL 机制、SELECT机制是完全一样的,只是APP接口函数不一样。

在调用 poll、select 函数时可以传入 “超时时间”。在这段时间内,条件合适时(比如有数据可读、有空间可写)就会立刻返回,否则等到“超时时间”结束时返回错误。

APP不是直接调用read函数,而是先调用poll或select函数,这2个函数中可以传入“超时时间”。它们的作用是:如果驱动程序中有数据,则立刻返回; 否则就休眠。在休眠期间,如果有人操作了硬件,驱动程序获得数据后就会把APP唤醒,导致poll或select立刻返回;如果在“超时时间”内无人操作硬件,则时间到后poll或select函数也会返回。

APP根据poll或select的返回值判断有数据之后,就调用read函数读取数据时,这时就会立刻获得数据。

poll/select函数可以监测多个文件,可以监测多种事件:

3.3.1 poll函数

  • int poll(struct pollfd *fds, nfds_t nfds, int timeout);

    • struct pollfd { int fd; /* 文件描述符 / short events; / 等待的事件 / short revents; / 实际发生了的事件,由内核填充 */ };

    • fds 是指向 pollfd 结构体数组的指针,每个 pollfd 结构体指定了一个文件描述符及其事件类型(如读就绪、写就绪、异常)

    • nfdsfds 数组中 pollfd 结构体的数量。

    • timeout 指定了等待的最长时间(毫秒为单位),如果为-1,则无限期等待。

    • 返回值:poll 返回就绪的文件描述符的数量,或者在超时后返回0,或者在出错时返回-1。

3.3.2 select函数

  • int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

    • nfds 是文件描述符集合中最大文件描述符的值加1。

    • readfdswritefdsexceptfds 分别指向可读、可写、异常条件的文件描述符集合。

    • timeout 指定了等待的最长时间,如果为NULL,则无限期等待。

    • 返回值:select 返回就绪的文件描述符的数量,或者在超时后返回0,或者在出错时返回-1。

3.4 异步通知方式

所谓异步通知,就是APP可以忙自己的事,当驱动程序用数据时它会主动给 APP发信号,这会导致APP执行信号处理函数。

3.4.1 操作步骤

1.注册信号处理函数 signal

2.打开驱动程序 open

3.把APP的进程号告诉驱动程序 fcntl

4.使能异步通知 fcntl

3.4.2 signal函数

*signal 函数用于设置特定信号的处理方式

  • typedef void (*sighandler_t)(int);

  • sighandler_t signal(int sig, sighandler_t func);

    • int sig:指定要处理的信号编号。这些信号编号是预定义的,如 SIGINT(通常由Ctrl+C产生,表示中断信号)、SIGTERM(请求程序终止的信号)、SIGSEGV(无效的内存引用,如访问未分配的内存)等

    • void (*func)(int):一个函数指针,指向当接收到指定信号时要调用的处理函数。这个函数必须接受一个整型参数(信号编号)并返回 void

    • 返回值:signal函数返回之前设置的信号处理函数的指针。如果发生错误,则返回SIG_ERR

3.4.3 fcntl函数

  • int fcntl(int fd, int cmd, ...);

    • fd:要进行操作的文件描述符。

    • cmd:指定要执行的操作的命令。cmd参数决定了fcntl函数的具体行为,并可能决定了是否需要额外的参数。

    • arg(可选):与cmd相关联的可选参数。根据cmd的值,arg可能是long类型的整数、指向struct flock结构的指针等。

    • 返回值:fcntl函数的返回值与命令(cmd)有关。如果操作成功,它通常返回非负值(如新的文件描述符、状态标志等)。如果出错,则返回-1,并设置全局变量errno以指示错误原因。

4. 示例代码

4.1 阻塞和非阻塞方式

#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
​
/* 运行方式:
        非阻塞:./01_get_input_info /dev/input/event0 noblock
          阻塞:./01_get_input_info /dev/input/event0
   阻塞和非阻塞的区别:有无加noblock
*/
int main(int argc, char **argv)
{
    int fd;
    int err;
    int len;
    int i;
    unsigned char byte;
    int bit;
    struct input_id id;
    unsigned int evbit[2];
    struct input_event event;
    
    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 ",
        };
    
    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)
    {
        printf("open %s err\n", argv[1]);
        return -1;
    }
​
    err = ioctl(fd, EVIOCGID, &id);
    if (err == 0)
    {
        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 (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");
    }
​
    while (1)
    {
        len = read(fd, &event, sizeof(event));
        if (len == sizeof(event))
        {
            printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
        }
        else
        {
            printf("read err %d\n", len);
        }
    }
    return 0;
}

4.2 POLL方式

#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
​
​
/*运行方式: ./01_get_input_info /dev/input/event0 */
int main(int argc, char **argv)
{
    int fd;
    int err;
    int len;
    int ret;
    int i;
    unsigned char byte;
    int bit;
    struct input_id id;
    unsigned int evbit[2];
    struct input_event event;
    struct pollfd fds[1];
    nfds_t nfds = 1;
    
    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 ",
        };
    
    if (argc != 2)
    {
        printf("Usage: %s <dev>\n", argv[0]);
        return -1;
    }
    // 非阻塞
    fd = open(argv[1], O_RDWR | O_NONBLOCK);
    if (fd < 0)
    {
        printf("open %s err\n", argv[1]);
        return -1;
    }
​
    err = ioctl(fd, EVIOCGID, &id);
    if (err == 0)
    {
        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 (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");
    }
​
    while (1)
    {   
        // 获取一个文件描述符的事件
        fds[0].fd = fd;
        fds[0].events  = POLLIN; // POLLIN表示数据可读
        fds[0].revents = 0;
        ret = poll(fds, nfds, 5000);
        if (ret > 0)
        {   
            // 当事件可读时,获取事件信息
            if (fds[0].revents == POLLIN)
            {
                while (read(fd, &event, sizeof(event)) == sizeof(event))
                {
                    printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
                }
            }
        }
        else if (ret == 0)
        {
            printf("time out\n");
        }
        else
        {
            printf("poll err\n");
        }
    }
​
    return 0;
}

4.3 select方式

#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
​
/* ./01_get_input_info /dev/input/event0 */
int main(int argc, char **argv)
{
    int fd;
    int err;
    int len;
    int ret;
    int i;
    unsigned char byte;
    int bit;
    struct input_id id;
    unsigned int evbit[2];
    struct input_event event;
    int nfds;
    struct timeval tv;
    fd_set readfds;
    
    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 ",
        };
    
    if (argc != 2)
    {
        printf("Usage: %s <dev>\n", argv[0]);
        return -1;
    }
​
    fd = open(argv[1], O_RDWR | O_NONBLOCK);
    if (fd < 0)
    {
        printf("open %s err\n", argv[1]);
        return -1;
    }
​
    err = ioctl(fd, EVIOCGID, &id);
    if (err == 0)
    {
        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 (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");
    }
​
    while (1)
    {
        /* 设置超时时间 */
        tv.tv_sec  = 5;
        tv.tv_usec = 0;
        
        /* 想监测哪些文件? */
        FD_ZERO(&readfds);    /* 先全部清零 */   
        FD_SET(fd, &readfds); /* 想监测fd */
        
        /* 函数原型为:
            int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
         * 我们为了"read"而监测, 所以只需要提供readfds
         */
        nfds = fd + 1; /* nfds 是最大的文件句柄+1, 注意: 不是文件个数, 这与poll不一样 */ 
        ret = select(nfds, &readfds, NULL, NULL, &tv);
        if (ret > 0)  /* 有文件可以提供数据了 */
        {
            /* 再次确认fd有数据 */
            if (FD_ISSET(fd, &readfds))
            {
                while (read(fd, &event, sizeof(event)) == sizeof(event))
                {
                    printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
                }
            }
        }
        else if (ret == 0)  /* 超时 */
        {
            printf("time out\n");
        }
        else   /* -1: error */
        {
            printf("select err\n");
        }
    }
​
    return 0;
}
​
 

4.4 异步通知方式

#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
​
int fd;
​
void my_sig_handler(int sig)
{
    struct input_event event;
    while (read(fd, &event, sizeof(event)) == sizeof(event))
    {
        printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);     
    }
}
​
/* ./05_input_read_fasync /dev/input/event0 */
int main(int argc, char **argv)
{
    int err;
    int len;
    int ret;
    int i;
    unsigned char byte;
    int bit;
    struct input_id id;
    unsigned int evbit[2];
    unsigned int flags;
    int count = 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 ",
        };
    
    if (argc != 2)
    {
        printf("Usage: %s <dev>\n", argv[0]);
        return -1;
    }
​
    /* 注册信号处理函数 */
    signal(SIGIO, my_sig_handler);
    
    /* 打开驱动程序 */
    fd = open(argv[1], O_RDWR | O_NONBLOCK);
    if (fd < 0)
    {
        printf("open %s err\n", argv[1]);
        return -1;
    }
​
    err = ioctl(fd, EVIOCGID, &id);
    if (err == 0)
    {
        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 (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");
    }
​
    /* 把APP的进程号告诉驱动程序 
       F_SETOWN操作用于设置接收SIGIO和SIGURG信号的进程ID,这里是获取自己的进程号
    */
    fcntl(fd, F_SETOWN, getpid());
    
    /* 使能"异步通知" 
        F_GETFL用于获取当前的文件描述符标志,然后将其与FASYNC标志位进行或操作,最后通过F_SETFL设置回去。FASYNC标志启用了文件描述符的异步通知功能,即当文件描述符上有I/O事件发生时,内核会发送SIGIO信号给设置了该标志的进程。
    */
    flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | FASYNC);
    
    while (1)
    {
        printf("main loop count = %d\n", count++);
        sleep(2);
    }
    return 0;
}

  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值