所学来自百问网
目录
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
结构体指定了一个文件描述符及其事件类型(如读就绪、写就绪、异常) -
nfds
是fds
数组中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。 -
readfds
、writefds
、exceptfds
分别指向可读、可写、异常条件的文件描述符集合。 -
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;
}