1. inotify 和 epoll:
在日常使用电脑的时候,我们通常会遇到更换外设的情况,比如在使用笔记本时外接键盘等等;那么会有如下两个问题:
1.1 inotify:用来监测目录/文件的变化;(安卓中就采用此方案来监测输入事件)
(1)使用:
// 第一步:初始化
fd = inotify_init();
// 第二步:监测
inotify_add_watch(fd, "目录/文件 的名称", "事件: 创建/删除");
// 第三步:通过 read() 函数读取 目录/文件 的变化
// 没有变化时 read() 函数就会进入休眠,有变化时read() 函数就会返回
char event_buf[512];
res = read(fd, event_buf, sizeof(event_buf));
// 这里的返回值 res 是以下的结构体的个数(一个或者多个):
strucrt notify_event {
__s32 wd;
__u32 mask; // 表示文件发生了什么变化,是创建还是删除
__u32 cookie;
__u32 len; // name 的长度
// 字符数组,大小为0,
char name[0]; // 发生变化的文件
};
这里的数组 char name[0] 是一个字符数组,大小为0,其真实地址紧随结构体buffer之后,而这个地址就是结构体后面数据的地址(如果给这个结构体分配的内容大于这个结构体实际大小,后面多余的部分就是这个data的内容);使用长度为0的数组有以下好处:
(a)指针本身需要占用内存,而长度为0的数组不需要;
(b)长度为0的数组定义出的缓冲区可以和结构体处在同一片连续地址中,只要一次malloc操作和free操作。如果用指针,需要分别申请和释放结构体内存和指针指向的内存块,至少需要两次以上的内存操作;
(2)安卓源码中使用 inotify(简化代码):
// EventHub.cpp 中:
EventHub::EventHub(void) :
mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
mOpeningDevices(0), mClosingDevices(0),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false), mNeedToScanDevices(true),
mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
// 初始化
mINotifyFd = inotify_init();
// 监测文件创建/删除
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
// ...
}
status_t EventHub::readNotifyLocked() {
int res;
char devname[PATH_MAX];
char *filename;
char event_buf[512];
int event_size;
int event_pos = 0;
struct inotify_event *event;
ALOGV("EventHub::readNotify nfd: %d\n", mINotifyFd);
res = read(mINotifyFd, event_buf, sizeof(event_buf)); // read() 函数
if(res < (int)sizeof(*event)) {
if(errno == EINTR)
return 0;
ALOGW("could not get event, %s\n", strerror(errno));
return -1;
}
strcpy(devname, DEVICE_PATH);
filename = devname + strlen(devname);
*filename++ = '/';
while(res >= (int)sizeof(*event)) {
event = (struct inotify_event *)(event_buf + event_pos);
if(event->len) {
strcpy(filename, event->name);
if(event->mask & IN_CREATE) { // 如果是创建文件
openDeviceLocked(devname);
} else { // 否则是删除文件
ALOGI("Removing device '%s' due to inotify event\n", devname);
closeDeviceByPathLocked(devname);
}
}
event_size = sizeof(*event) + event->len;
res -= event_size;
event_pos += event_size;
}
return 0;
}
1.2 epoll:用来监测多个文件(有无数据供读出或者有无空将供写入);
(1)使用
// 1:创建文件句柄
int mEpollFd = epoll_create(EPOLL_SIZE_HINT);
// 2:对每个文件使用 epoll_ctl(),表示要监测它(的行为);
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
// 3:执行 epoll_wait() 等待某个文件可用;某个文件有数据可读/有空间可使用,此时epoll_wait() 就会返回
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// 4:不需要再监测某个文件时,同样使用 epoll_ctl()
epoll_ctl(mEpollFd, EPOLL_CTL_DEL, device->fd, NULL);
(2)安卓源码中使用epoll:
EventHub::EventHub(void) :
mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
mOpeningDevices(0), mClosingDevices(0),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false), mNeedToScanDevices(true),
mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
// 创建文件句柄
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
mINotifyFd = inotify_init();
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s. errno=%d",
DEVICE_PATH, errno);
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN; // EPOLLIN 表示文件有数据时就可以监测到
eventItem.data.u32 = EPOLL_ID_INOTIFY;
// 监测 mINotifyFd
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
int wakeFds[2];
result = pipe(wakeFds);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno);
eventItem.data.u32 = EPOLL_ID_WAKE;
// 监测 mWakeReadPipeFd
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
int major, minor;
getLinuxRelease(&major, &minor);
// EPOLLWAKEUP was introduced in kernel 3.5
mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);
}
2. 输入系统简介:socketpair()
用法:
(1) 这对套接字可以用于全双工通信,每一个套接字既可以读也可以写。例如,可以往fd[0]中写,从fd[1]中读;或者从fd[1]中写,从fd[0]中读;
(2) 如果往一个套接字(如fd[0])中写入后,再从该套接字读时会阻塞,只能在另一个套接字中(fd[1])上读成功;
(3)读、写操作可以位于同一个进程,也可以分别位于不同的进程,如父子进程。如果是父子进程时,一般会功能分离,一个进程用来读,一个用来写。因为文件描述符fd[0]和fd[1]是进程共享的,所以读的进程要关闭写描述符, 反之,写的进程关闭读描述符。
#include
#include
#include
#include
#include
#include
#include
#include
const char* str = "SOCKET PAIR TEST.";
int main(int argc, char* argv[]){
char buf[128] = {0};
int socket_pair[2];
pid_t pid;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, socket_pair) == -1 ) {
printf("Error, socketpair create failed, errno(%d): %s\n", errno, strerror(errno));
return EXIT_FAILURE;
}
pid = fork();
if(pid < 0) {
printf("Error, fork failed, errno(%d): %s\n", errno, strerror(errno));
return EXIT_FAILURE;
} else if(pid > 0) {
//关闭另外一个套接字
close(socket_pair[1]);
int size = write(socket_pair[0], str, strlen(str));
printf("Write success, pid: %d\n", getpid());
} else if(pid == 0) {
//关闭另外一个套接字
close(socket_pair[0]);
read(socket_pair[1], buf, sizeof(buf));
printf("Read result: %s, pid: %d\n",buf, getpid());
}
for(;;) {
sleep(1);
}
return EXIT_SUCCESS;
}
输入系统设计:一个进程用于读取分发输入事件,一个应用程序用于处理输入事件;
3. socketpair + Binder 任意进程间的双向通信
Linux 知识简单介绍:Linux 内核每个进程都有一个 task_struct 结构体,在 task_struct 中使用 files_struct 来管理打开的文件;
当使用 open 函数打开一个文件时,其过程为:操作系统生成一个新的 file 结构体来保存这个文件的信息,并在 files_struct 的文件数组 fdtable 中生成一个指向这个 file 结构体的指针,然后向进程返回这个指针的下标值,而这个下标值正是文件描述符 fd,在进程中也正是通过这个 fd 来访问目的文件。如下代码所示:
struct task_struct {
/* open file information */
struct files_struct *files;
// ...
}
struct files_struct {
struct fdtable *fdt;
// ...
}
struct fdtable {
struct file *fd;
// ...
}
3.1 使用 binder 传输文件句柄:
(1)App1 打开文件得到 fd1;
(2)通过 Binder 驱动,根据 fd1 得到一个 file 结构体:App1 -> files -> fdtable -> fd[fd1](这就是 file 结构体);
(3)从 App2 的 files -> fdtable -> 中取出一个空项(假设为fd2),让其指向 file 这个结构体;
相当于 App2 中的 files -> fdtable -> fd[fd2] = file;
(4)这样 App1 通过 fd1 + App2 通过 fd2 就可以访问同一份文件了;
3.2 安卓源码中的应用:
(1)建立 socket 通信:
InputChannel.openInputChannelPair(name) 是一个native方法,在 frameworks/base/core/jni/
android_view_InputChannel.cpp中:
// 该方法由 WMS 的 addWindow() 调用
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
jclass clazz, jstring nameObj) {
const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
String8 name(nameChars);
env->ReleaseStringUTFChars(nameObj, nameChars);
sp serverChannel;
sp clientChannel;
// 创建一对 socket 通信
status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
// 封装成java对象
jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
if (env->ExceptionCheck()) {
return NULL;
}
jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
new NativeInputChannel(serverChannel));
if (env->ExceptionCheck()) {
return NULL;
}
jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
new NativeInputChannel(clientChannel));
if (env->ExceptionCheck()) {
return NULL;
}
env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
return channelPair;
}
// inputTransport.cpp 中:
status_t InputChannel::openInputChannelPair(const String8& name,
sp& outServerChannel, sp& outClientChannel) {
int sockets[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) { // 调用 socketpair()
status_t result = -errno;
ALOGE("channel '%s' ~ Could not create socket pair. errno=%d",
name.string(), errno);
outServerChannel.clear();
outClientChannel.clear();
return result;
}
int bufferSize = SOCKET_BUFFER_SIZE;
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
String8 serverChannelName = name;
serverChannelName.append(" (server)");
outServerChannel = new InputChannel(serverChannelName, sockets[0]);
String8 clientChannelName = name;
clientChannelName.append(" (client)");
outClientChannel = new InputChannel(clientChannelName, sockets[1]);
return OK;
}
(2)binder 通信:现在只需要有一个概念,后续将会详细分析;
WindowMangerService.addView() ->
ViewRootImpl.setView() ->
WindowSession.addToDisplay() ->
//mWindowSession.addToDisplay() 就是 调用了Session的 addToDisplay() 方法:
// Session 的 addToDisplay() 方法:跨进程通信
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets,
InputChannel outInputChannel) {
// outInputChannel 是 2.2 中 mInputChannel = new InputChannel(),此时的指针还未赋值
// mService 就是 new Session 时传入的 WMS
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outInputChannel);
}
本节内容介绍了 inotify,epoll,socketpair 三个知识点,到这里就结束了;下一节我们将继续分析 input 事件分发的框架设计。