Android getevent命令详细分析

在调试Android 的输入事件时,经常使用 "getevent -lrt" 命令,来确认驱动上报数据是否正常。从源码的角度来详细的分析一下getevent 这个程序。

首先用ls命令来看一下getevent

c 复制代码
lrwxr-xr-x 1 root shell 7 2023-11-20 10:08 system/bin/getevent -> toolbox

可以看出,getevent 链接的是toolbox,来看一下toolbox的main方法

c 复制代码
//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为getevent
            return tools[i].func(argc, argv);//1
        }
    }

    printf("%s: no such tool\n", argv[0]);
    return 127;
}

tools是一个数组

c 复制代码
//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方法

c 复制代码
//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();//1
    ufds[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);//2
        res = scan_dir(device_path, print_flags); //3
	}
	
	//省略
}

注释1处进行inotify初始化,注释2处使用inotify监听/dev/input目录下是否有设备增加或者移除,注释3处扫描/dev/input目录。此时/dev/input目录下是已经存在的设备。来看一下scan_dir扫描操作

c 复制代码
//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目录下所有满足要求的设备。

c 复制代码
//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方法内,接着会进入一个循环

c 复制代码
//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) {//2
            read_notify(device_path, ufds[0].fd, print_flags);
        }
        for(i = 1; i < nfds; i++) {
            if(ufds[i].revents) {
                if(ufds[i].revents & POLLIN) {//3
                    res = 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);//4
                    if(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数组中的每项是否有数据产生,没数据时休眠,有数据产生就会被唤醒。

由于ufds0的fd放入的是inotify_init得到的fd,而inotify是用来监测/dev/input 下是否有设备增加或者删除。注释2处,如果ufds0的数据是POLLIN,则表示此时是有设备增加或者删除,调用read_notify方法处理。而注释3处表示是某个设备有输入事件了,则调用read读取事件并使用print_event打印出来。

c 复制代码
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机制来监测有无输入设备的增加和删除,以及哪个设备有数据产生。具体的工作流程如下图

相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen4 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone4 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui