input中的inotify & epoll机制

inotify机制和epoll机制

Android设备可以同时连接多个输入设备,比如说触摸屏,键盘,鼠标等等。用户在任何一个设备上的输入就会产生一个中断,经由Linux内核的中断处理以及设备驱动转换成一个Event,并传递给用户空间的应用程序进行处理。每个输入设备都有自己的驱动程序,数据接口也不尽相同,如何在一个线程里(上面说过只有一个InputReader Thread)把所有的用户输入都给捕捉到? 这首先要归功于Linux 内核的输入子系统(Input Subsystem), 它在各种各样的设备驱动程序上加了一个抽象层,只要底层的设备驱动程序按照这层抽象接口来实现,上层应用就可以通过统一的接口来访问所有的输入设备。

驱动层处理过后的事件在/dev/input目录下体现
包括新设备的插入与移除,事件通过读取相应event节点获取

在这里插入图片描述

input底层使用inotify和epoll来监控/dev/input下的数据变化

  1. 使用intotify监听 文件 增加/删除的 变化,对应实际操作中设备的变化。
    inotify机制的步骤:
  • fd = inotify_init()
  • inotify_add_watch(监听 目录/文件,创建/删除)
  • read(fd )只要有文件发生变化(创建/删除)则返回,返回一个或多个inotify_event结构体
  1. 使用epoll监听文件中内容的变化,对应实际操作中设备中数据的变化。
    epoll机制的步骤
  • epoll_create
  • epoll_ctl(…, epoll_ctl_add, …)对某个文件,执行表示要检测它
  • epoll_wait(等待某个文件可用)
  • epoll_ctl(…, epoll_ctl_del, …)不再想检测某个文件

通过代码分析具体步骤
native/services/inputflinger/reader/EventHub.cpp

EventHub::EventHub(void)
      :  {

    //创建mEpollFd
    mEpollFd = epoll_create1(EPOLL_CLOEXEC);

    //创建mINotifyFd
    mINotifyFd = inotify_init();
    //将DEVICE_PATH(/dev/input)下的文件变化注册到mINotifyFd
    mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);

    eventItem.events = EPOLLIN | EPOLLWAKEUP;
    eventItem.data.fd = mINotifyFd;
    //将mINotifyFd变化监听注册到mEpollFd上
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
}

实际的入口函数其实是getEvents,当我们在InputReader中第一次调用getEvents时,他必定走到scanDevicesLocked
方法,此方法会扫描当前/dev/input目录下的节点情况,
然后根据扫描情况对节点添加监听,之后调用,他会进入while,等待read读取节点返回的原始输入事件,保存到RawEvent中,而新增设备和移除设备的动作也是通过RawEvent上报给InputReader的

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ALOG_ASSERT(bufferSize >= 1);

    AutoMutex _l(mLock);

    struct input_event readBuffer[bufferSize];

    RawEvent* event = buffer;
    size_t capacity = bufferSize;
    bool awoken = false;
    for (;;) {
        ...
        // 上报移除设备事件,封装RawEvent
        while (mClosingDevices) {
            Device* device = mClosingDevices;
            mClosingDevices = device->next;
            event->when = now;
            event->deviceId = (device->id == mBuiltInKeyboardId)
                    ? ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID
                    : device->id;
            event->type = DEVICE_REMOVED;
            event += 1;
        }
        ...
        //扫描目录下的节点,Input启动的第一次必定会走到这里面
        if (mNeedToScanDevices) {
            mNeedToScanDevices = false;
            scanDevicesLocked();
            mNeedToSendFinishedDeviceScan = true;
        }
        ...
        //上报新增设备事件,封装RawEvent
        while (mOpeningDevices != nullptr) {
            Device* device = mOpeningDevices;
            mOpeningDevices = device->next;
            event->when = now;
            event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
            event->type = DEVICE_ADDED;
            event += 1;
        }
        ...
        //上报扫描结束事件,封装RawEvent
        if (mNeedToSendFinishedDeviceScan) {
            mNeedToSendFinishedDeviceScan = false;
            event->when = now;
            event->type = FINISHED_DEVICE_SCAN;
            event += 1;
        }

        // 抓取事件,解析mPendingEventItems中的事件
        bool deviceChanged = false;
        while (mPendingEventIndex < mPendingEventCount) {
            const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];

            Device* device = getDeviceByFdLocked(eventItem.data.fd);
         
            // EPOLLIN表示输入事件,处理输入事件
            if (eventItem.events & EPOLLIN) {
            //通过read读取fd上的数据,封装成RawEvent上报
                int32_t readSize =
                        read(device->fd, readBuffer, sizeof(struct input_event) * capacity);
                    int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;

                    size_t count = size_t(readSize) / sizeof(struct input_event);
                    for (size_t i = 0; i < count; i++) {
                        struct input_event& iev = readBuffer[i];
                        event->when = processEventTimestamp(iev);
                        event->deviceId = deviceId;
                        event->type = iev.type;
                        event->code = iev.code;
                        event->value = iev.value;
                        event += 1;
                    }
                }
            } 
        }
        ...
        //通过epoll_wait获取注册到mEpollFd上的变化事件放到mPendingEventItems里
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    }
    return event - buffer;
}


status_t EventHub::scanDirLocked(const char* dirname) {
    //循环注册/dev/input下的所有路径到mEpollFd上,一步一步调用到registerFdForEpoll方法
    while ((de = readdir(dir))) {
        strcpy(filename, de->d_name);
        openDeviceLocked(devname);
    }
    return 0;
}

status_t EventHub::openDeviceLocked(const char* devicePath) {
    ...
    
      // 获取设备的名字,如果成功获取到设备的名字,把它存入InputDeviceIdentifier中
     if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {

     } else {
         buffer[sizeof(buffer) - 1] = '\0';
         identifier.name.setTo(buffer);
     }
     ...
 
     //构造EventHub所需要的对象Device,这里的fd是刚刚打开的设备的文件描述符
     int32_t deviceId = mNextDeviceId++;//从这里可以看出,deviceId是与设备无关的,和打开顺序有关
     Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
 
     // 测试设备能够产生的事件的类型,这里就是Android支持的事件类型,是Kernel的一个子集
     ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);
     ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);
     ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
     ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
     ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
     ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
     ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);
     ...
     /*根据前面获取到的设备属性,检测设备是鼠标,键盘,手柄等,然后把这些信息继续存入Device
该函数打开扫描到的/dev/input下的设备文件,如果是INPUT_DEVICE_CLASS_KEYBOARD 这个类型,则调用loadKeyMapLocked来加载kl文件*/
    status_t keyMapStatus = NAME_NOT_FOUND;
    if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
        // Load the keymap for the device.
        keyMapStatus = loadKeyMapLocked(device);
    }

    if (registerDeviceForEpollLocked(device) != OK) {
        delete device;
        return -1;
    }
}

status_t EventHub::registerDeviceForEpollLocked(Device* device) {
    ...
    status_t result = registerFdForEpoll(device->fd);
    return result;
}

//对给到的fd进行监听
status_t EventHub::registerFdForEpoll(int fd) {
    struct epoll_event eventItem = {};
    eventItem.events = EPOLLIN | EPOLLWAKEUP;
    eventItem.data.fd = fd;
    if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
        ALOGE("Could not add fd to epoll instance: %s", strerror(errno));
        return -errno;
    }
    return OK;
}
//取消对给到的fd进行监听
status_t EventHub::unregisterFdFromEpoll(int fd) {
    if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, nullptr)) {
        ALOGW("Could not remove fd from epoll instance: %s", strerror(errno));
        return -errno;
    }
    return OK;
}

status_t EventHub::loadKeyMapLocked(Device* device) {
    return device->keyMap.load(device->identifier, device->configuration);
}

这里简单描述下device->keyMap.load查找逻辑:

  • configuration不为空,即存在idc文件,则根据idc文件中指定的keyboard.layout和keyboard.characterMap来加载指定的kl和kcm文件
  • configuration为空,则根据deviceIdenfifier来加载kl和kcm文件(这个会根据vendor,product,version三个属性来决定加载哪个文件)
  • deviceIdenfifier无法找到配置文件,则根据名字Generic来加载文件,即Generic.kl和Generic.kcm文件
  • Generic.kl和Generic.kcm无法找到,则根据名字Virtual来加载文件,即Virtual.kl和Virtual.kcm文件

这里对于按键类设备,驱动层有一组 对按键的定义,而android在上层又有一组对按键值的定义,这是为什么呢?

这是因为Android 系统是建立在Linux内核基础上的,但如果Linux内核中 输入事件的定义值发生了变化,android系统也要跟着改,而这样就很被动,因此android系统采用了配置文件*.kl 和*.kcm,这样如果Linux有变动,Android侧只要修改对应配置文件就可以了。kl文件位置

/system/usr/keylayout/Generic.kl

key 114   VOLUME_DOWN
key 115   VOLUME_UP
key 116   POWER

kernel报上来的键值就是114,115,116,而kl文件中对这个键值的映射,对应上层就是KEYCODE_VOLUME_UP、KEYCODE_VOLUME_DOWN,KEYCODE_POWER

base/core/java/android/view/KeyEvent.java

    /** Key code constant: Volume Up key.
     * Adjusts the speaker volume up. */
    public static final int KEYCODE_VOLUME_UP       = 24;
    /** Key code constant: Volume Down key.
     * Adjusts the speaker volume down. */
    public static final int KEYCODE_VOLUME_DOWN     = 25;
    /** Key code constant: Power key. */
    public static final int KEYCODE_POWER           = 26;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值