在调试Android 的输入事件时,经常使用 “getevent -lrt” 命令,来确认驱动上报数据是否正常。从源码的角度来详细的分析一下getevent 这个程序。
首先用ls命令来看一下getevent
lrwxr-xr-x 1 root shell 7 2023-11-20 10:08 system/bin/getevent -> toolbox
可以看出,getevent 链接的是toolbox,来看一下toolbox的main方法
//system\core\toolbox\toolbox.c
int main(int argc, char** argv) {// Let's assume that none of this code handles broken pipes. At least ls,// ps, and top were broken (though I'd previously added this fix locally// to top). We exit rather than use SIG_IGN because tools like top will// just keep on writing to nowhere forever if we don't stop them.signal(SIGPIPE, SIGPIPE_handler);char* cmd = strrchr(argv[0], '/');char* name = cmd ? (cmd + 1) : argv[0];for (size_t i = 0; tools[i].name; i++) {if (!strcmp(tools[i].name, name)) {//name为geteventreturn tools[i].func(argc, argv);//1}}printf("%s: no such tool\n", argv[0]);return 127;
}
tools是一个数组
//system\core\toolbox\toolbox.c
static struct {const char* name;int (*func)(int, char**);
} tools[] = {
#define TOOL(name) { #name, name##_main },
#include "tools.h"
#undef TOOL{ 0, 0 },
};
而在tools.h里面定义了 TOOL(getevent),将name替换为getevent,即注释1处调用getevent_main方法。接下来重点看一下getevent_main方法
//system\core\toolbox\getevent.c
static struct pollfd *ufds;
static char **device_names;
static int nfds;int getevent_main(int argc, char *argv[])
{//省略const char *device_path = "/dev/input";//省略nfds = 1;ufds = calloc(1, sizeof(ufds[0]));ufds[0].fd = inotify_init();//1ufds[0].events = POLLIN;if(device) {//getevent命令后面可以指定device,如getevent dev/input/event0//省略} else {if(!print_flags_set)print_flags |= PRINT_DEVICE_ERRORS | PRINT_DEVICE | PRINT_DEVICE_NAME;print_device = 1;res = inotify_add_watch(ufds[0].fd, device_path, IN_DELETE | IN_CREATE);//2res = scan_dir(device_path, print_flags); //3}//省略
}
注释1处进行inotify初始化,注释2处使用inotify监听/dev/input目录下是否有设备增加或者移除,注释3处扫描/dev/input目录。此时/dev/input目录下是已经存在的设备。来看一下scan_dir扫描操作
//system\core\toolbox\getevent.c
static int scan_dir(const char *dirname, int print_flags)
{char devname[PATH_MAX];char *filename;DIR *dir;struct dirent *de;dir = opendir(dirname);if(dir == NULL)return -1;strcpy(devname, dirname);filename = devname + strlen(devname);*filename++ = '/';while((de = readdir(dir))) {if(de->d_name[0] == '.' &&(de->d_name[1] == '\0' ||(de->d_name[1] == '.' && de->d_name[2] == '\0')))continue;strcpy(filename, de->d_name);open_device(devname, print_flags);//1}closedir(dir);return 0;
}
注释1处,得到/dev/input目录下的设备名后,打开设备。注意该方法是处于循环中,会依次打开/dev/input目录下所有满足要求的设备。
//system\core\toolbox\getevent.c
static int open_device(const char *device, int print_flags)
{//省略fd = open(device, O_RDONLY | O_CLOEXEC);//打开设备//调用ioctl获取信息/*加到ufds数组中*/ufds[nfds].fd = fd;ufds[nfds].events = POLLIN;device_names[nfds] = strdup(device);nfds++;return 0;
}
open_device方法主要功能是打开设备,然后将打开设备的fd添加进ufds数组中。
已有的设备扫描完成后,在getevent_main方法内,接着会进入一个循环
//system\core\toolbox\getevent.c
int getevent_main(int argc, char *argv[])
{//省略while(1) {//int pollres =poll(ufds, nfds, -1);//1//printf("poll %d, returned %d\n", nfds, pollres);if(ufds[0].revents & POLLIN) {//2read_notify(device_path, ufds[0].fd, print_flags);}for(i = 1; i < nfds; i++) {if(ufds[i].revents) {if(ufds[i].revents & POLLIN) {//3res = read(ufds[i].fd, &event, sizeof(event));if(res < (int)sizeof(event)) {fprintf(stderr, "could not get event\n");return 1;}if(get_time) {printf("[%8ld.%06ld] ", event.time.tv_sec, event.time.tv_usec);}if(print_device)printf("%s: ", device_names[i]);print_event(event.type, event.code, event.value, print_flags);//4if(sync_rate && event.type == 0 && event.code == 0) {int64_t now = event.time.tv_sec * 1000000LL + event.time.tv_usec;if(last_sync_time)printf(" rate %lld", 1000000LL / (now - last_sync_time));last_sync_time = now;}printf("%s", newline);if(event_count && --event_count == 0)return 0;}}}}return 0;
}
注释1处使用poll来监测ufds数组中的每项是否有数据产生,没数据时休眠,有数据产生就会被唤醒。
由于ufds[0]的fd放入的是inotify_init得到的fd,而inotify是用来监测/dev/input 下是否有设备增加或者删除。注释2处,如果ufds[0]的数据是POLLIN,则表示此时是有设备增加或者删除,调用read_notify方法处理。而注释3处表示是某个设备有输入事件了,则调用read读取事件并使用print_event打印出来。
static int read_notify(const char *dirname, int nfd, int print_flags)
{int res;char devname[PATH_MAX];char *filename;char event_buf[512];int event_size;int event_pos = 0;struct inotify_event *event;res = read(nfd, event_buf, sizeof(event_buf));//读出数据strcpy(devname, dirname);filename = devname + strlen(devname);*filename++ = '/';while(res >= (int)sizeof(*event)) {event = (struct inotify_event *)(event_buf + event_pos);//printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");if(event->len) {strcpy(filename, event->name);if(event->mask & IN_CREATE) {//设备增加open_device(devname, print_flags);}else {close_device(devname, print_flags);}}event_size = sizeof(*event) + event->len;res -= event_size;event_pos += event_size;}return 0;
}
可以看出,在read_notify方法内,如果有设备增加,也是调用open_device来进行处理。open_device在前面分析过,主要是打开设备并将fd添加到ufds数组中。
总结
getevent 使用 inotify和poll机制来监测有无输入设备的增加和删除,以及哪个设备有数据产生。具体的工作流程如下图