inotify机制和epoll机制
Android设备可以同时连接多个输入设备,比如说触摸屏,键盘,鼠标等等。用户在任何一个设备上的输入就会产生一个中断,经由Linux内核的中断处理以及设备驱动转换成一个Event,并传递给用户空间的应用程序进行处理。每个输入设备都有自己的驱动程序,数据接口也不尽相同,如何在一个线程里(上面说过只有一个InputReader Thread)把所有的用户输入都给捕捉到? 这首先要归功于Linux 内核的输入子系统(Input Subsystem), 它在各种各样的设备驱动程序上加了一个抽象层,只要底层的设备驱动程序按照这层抽象接口来实现,上层应用就可以通过统一的接口来访问所有的输入设备。
驱动层处理过后的事件在/dev/input目录下体现
包括新设备的插入与移除,事件通过读取相应event节点获取
input底层使用inotify和epoll来监控/dev/input下的数据变化
- 使用intotify监听 文件 增加/删除的 变化,对应实际操作中设备的变化。
inotify机制的步骤:
- fd = inotify_init()
- inotify_add_watch(监听 目录/文件,创建/删除)
- read(fd )只要有文件发生变化(创建/删除)则返回,返回一个或多个inotify_event结构体
- 使用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;