输入输出的源头
- 在Android系统中,当我们按下按键或滑动屏幕的时候,系统是怎么知道这些操作的呢,这一切的源头都是
/dev/input目录下的设备节点
额外知识点
- FD是File Descriptor的缩写 是文件描述符
- 在Linux中,万物皆文件,不管是照片、键盘、屏幕、网络,系统都给他们看作为文件
- 所以当你想要操作一个文件的的时候,比如读取键盘按键,你不能直接抓着硬件操作
- 你应该要告诉内核,我要打开那个键盘设备
- 如果内核同意你打开,就会给你发送一个非负整数
- 这个数字就是FD。之后你找内核办事,这个数字给到内核,内核就知道你指的是哪个设备了
- 常见的固定FD
- 0 标准输入 —— 通常键盘
- 1 标准输出 —— 通常指屏幕
- 2 标准错误 —— 报错信息的输出
inotify epoll
- inotify 负责监控设备的增加与删除
- epoll 负责监控设备数据的产生
为什么需要inotify呢
- 在Linux中,万物皆是文件,每一个输入设备在插入的时候,都会在
/dev/input/目录下生成一个event0、event1这样的文件
- 那程序什么时候知道多了一个
event文件呢,如果用死循环不断的扫描目录,那么会极大的消耗CPU资源
- inotifyiu可以解决这个问题
- 他是一个异步文件系统监控机制,它允许应用程序监控文件系统事件(如创建、删除、移动),并在事件发生时通过读取文件描述符的方式告知用户
为什么需要epoll呢
- 一旦设备文件创建好了,那么问题是哪个设备有数据了?
- 假如说有10个输入设备
- 传统的select/poll 每次都要把10个文件描述符传给内核,内核轮询一边后告诉你有几个可读,你还得自己再找一遍具体是哪一个。时间复杂度是O(n)
- epoll 是采用
事件触发机制 只需把要监控的设备告诉内核。检测到事件后 事件查询红黑树查询。内核会在硬件产生数据的时候将其放入就绪链表,调用epoll_wait时 内核直接返回就绪的文件 时间复杂度是O(1)
inotify机制
核心流程
inotify_init():创建一个inotify实力 返回一个文件描述符
inotify_add_watch:告诉内核你要监控哪个目录 哪些动作
read(fd, ...):这是一个阻塞操作。当文件发生变化的时候,能读出一个或多个inotify_event结构体
示例代码
#include <sys/inotify.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
void handle_events(int fd) {
char buf[1024];
int len = read(fd, buf, sizeof(buf));
int pos = 0;
while (pos < len) {
struct inotify_event *event = (struct inotify_event *)&buf[pos];
if (event->len) {
if (event->mask & IN_CREATE) printf("检测到新设备: %s\n", event->name);
if (event->mask & IN_DELETE) printf("设备已移除: %s\n", event->name);
}
pos += sizeof(struct inotify_event) + event->len;
}
}
int main(int argc, char **argv) {
if (argc != 2) {
printf("用法: %s <监控目录>\n", argv[0]);
return -1;
}
int mINotifyFd = inotify_init();
inotify_add_watch(mINotifyFd, argv[1], IN_CREATE | IN_DELETE);
while (1) {
handle_events(mINotifyFd);
}
return 0;
}
epoll机制
核心流程
epoll_create(size):创建epoll句柄
epoll_ctl(epfd, op, fd, event):注册、修改或者删除的FD
epoll_wait(epfd, events, max, timeout):等待事件发生,返回就绪的FD的数量
示例代码
#include <sys/epoll.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
int epollFd = epoll_create(8);
struct epoll_event eventItem;
struct epoll_event mPendingEventItems[16];
for (int i = 1; i < argc; i++) {
int fd = open(argv[i], O_RDWR);
eventItem.events = EPOLLIN;
eventItem.data.fd = fd;
epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &eventItem);
}
while (1) {
int count = epoll_wait(epollFd, mPendingEventItems, 16, -1);
for (int i = 0; i < count; i++) {
char buf[512];
int len = read(mPendingEventItems[i].data.fd, buf, sizeof(buf));
buf[len] = '\0';
printf("从 FD %d 获取数据: %s\n", mPendingEventItems[i].data.fd, buf);
}
}
}
EventHub
- 在Android Framework中,
EventHub会同时开启inotify和epoll
- 用
inotify监控/dev/input
- 当
inotify发现新文件创建时,调用open打开它,并将其FD加入epoll的监控队列
- 当
inotofy发现文件删除时,将其FD从epoll队列中移除
epoll_wait统一管理所有的FD 包括inotify自己的FD和所有设备文件的FD
总结
inotify解决了输入设备动态识别的问题,避免了无效的目录循环 轮询
epoll解决了多路复用”的性能问题,保证了在有大量输入设备时,系统依然能以O(1)的效率响