#前言
前文详细分析了Java层的消息循环机制的工作原理,在分析MessageQueue的过程中,我们遇到了nativePollOnce()
和nativeWake()
方法的调用,下面我们就深入到Native层的消息机制来看看它背后的运作原理。
#Native层的消息机制
一、NativeMessageQueue的相关逻辑
1、NativeMessageQueue的构建
首先,我们来看看Java层的MessageQueue的构造函数:
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
这里调用了一个native方法,实际上这里初始化了native层的消息队列,并返回了该消息队列的头部指针地址。由于这个是native方法,这里利用了JNI机制调用本地代码,JNI的相关知识笔者在前面的文章也有说到,这里就不再赘述。我们直接来看本地代码:(frameworks/base/core/jni/android_os_MessageQueue.cpp):
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); //创建了一个本地的消息队列
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env); //增加强引用指针计数,这里与RefBase,类似于智能指针的概念
return reinterpret_cast(nativeMessageQueue); //强制类型转换,返回jlong类型,实际上是指针的值
}
这里引入了NativeMessageQueue
,顾名思义,这应该是native层的消息队列,我们用UML类图来看看它的类结构:
其中,RefBase是基类,它的作用比较特殊,在Android的native代码中,大部分的类都是继承自RefBase,作用有点类似于Java中的Object。实际上,它的作用与实现智能指针有关,便于native层的垃圾回收的实现(因为C++没有GC机制,我们需要手动实现对象的创建和回收操作)。这里不对RefBase做过多的深究,我们把关注点放回NativeMessageQueue
。
NativeMessageQueue
是native层的消息队列,虽然它称作消息队列,但实际上它是一个空壳,它内部并没有维护消息的队列或者链表,它把涉及消息的相关操作都交给了Looper
去处理。与Java层一样的是,一条线程只会有一个Looper
,这是因为native层也有着一套线程独立变量的机制,当前线程的变量只与当前线程有关。我们来看看NativeMessageQueue
的构造方法:
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread(); //获取当前线程的Looper对象
if (mLooper == NULL) { //如果没有,那么初始化Looper对象
mLooper = new Looper(false);
Looper::setForThread(mLooper); //Looper与当前线程绑定
}
}
通过NativeMessageQueue
的构造方法可以看出,这里实例化了一个Looper
,而这个Looper
则是与线程相关的,因此我们可以推测Looper
实际上承担了消息队列的实际功能,NativeMessageQueue
对外表现为消息队列,也有相关的方法,但实际上它把核心逻辑都移交给了Looper
去处理。
小结:Java层的MessageQueue
被创建的时候,同时会创建一个Native层的NativeMessageQueue
,并初始化一个native的Looper
。经过JNI的调用返回,NativeMessageQueue
对象的指针地址会返回给Java层的MessageQueue
,保存在mPtr
这个成员变量内。
2、NativeMessageQueue#pollOnce
经过上面的NativeMessageQueue的初始化后,我们就能正常使用它了。还记得我们在Java层的MessageQueue#next()
方法内曾经看到过这个调用吗?nativePollOnce(ptr, nextPollTimeoutMillis)
,这里调用了native的方法,我们来看看它的nativec层的源码:
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
方法很简单,这里传进来的ptr参数实际上就是MessageQueue.mPtr
变量,经过强制类型转换后,把ptr转换成了NativeMessageQueue指针,然后调用这个对象的pollOnce(args)
方法。这个过程可以理解为:在某一线程内,Java层持有Native层的NativeMessageQueue的指针地址值,经过JNI调用把该地址传递过来,native层根据这个地址找到了这个对象,强制类型转换成了NativeMessageQueue
对象。简单地说,Java层的MessageQueue对应了Native层的NativeMessageQueue。
下面,我们来看nativeMessageQueue->pollOnce(env, obj, timeoutMillis)
:
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis); //把逻辑交给Looper去处理
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj); //env是JNI环境,这里的异常会抛给Java层
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
逻辑很简单,这里调用了mLooper->pollOnce(timeoutMillis)
方法,并把超时时间传递了进去,结合函数名字,我们可以合理推测:该函数内部在超时时间内进行native的消息处理,达到了超时时间就会JNI调用返回,以便处理Java层的消息。我们先记住这个推测,待会分析Looper的时候再来看这个推测是否正确。
3、NativeMessageQueue#wake
同样地,我们之前在讨论MessageQueue#enqueueMessage()
的时候,会发现进行了nativeWake()
调用,那么我们直接看源码:
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr);
nativeMessageQueue->wake();
}
void NativeMessageQueue::wake() {
mLooper->wake();
}
显然,根据ptr
找到对应的NativeMessageQueue
,然后调用了mLooper->wake()
,把逻辑交给了Looper去处理。那么到目前为止,一切问题的关键都指向了Looper
,我们开始探究native的Looper吧。
二、native Looper的工作原理
Looper是整个Native层消息机制的核心所在,大部分功能都是Looper完成的。要准确理解native Looper,我们首先就要对它的类结构有个整体的认识,下面笔者给出Looper的UML类图方便读者的理解。
1、native Looper的整体认识
Looper的类结构被定义在/system/core/libutils/include/utils/Looper.h
,源码地址为Looper.h。结合类结构,我们可以清晰地画出如下的UML类图:
结合Looper.h
的源码和上面的UML类图,下面先列举几个与Looper有关的类或成员变量,以便后面的源码阅读。
①mWakeEventFd:用于事件通知的文件描述符,在这里表示唤醒Looper的文件描述符。实际上它指向一个eventfd
对象,该对象可以实现事件的等待和通知机制。
②mEpollFd:epoll
的句柄,指向一个epoll
对象,有关epoll
的使用都要经过该句柄。epoll
简单来说,是Linux下的一种I/O事件通知机制。epoll
监听了eventfd
,当该文件描述符的缓冲区为空时,epoll
发出可写信号,当文件描述符的缓冲区不空时,发出可读信号。利用这样的机制,能实现线程间的通信。
③MessageEnvelop:内部封装了Message
、MessageHandler
和uptime
。其中Message
是native层发送的消息,而MessageHandler
内部有一个回调函数,当消息接收方接收并处理消息后就会回调该函数。uptime
是消息的触发时间。
④Request:封装了fd
、events
、callback
等参数,实际上这是Looper监听的一种特殊消息,可以称之为事件,它是以文件描述符形式存在的。通过addFd(args)
把fd添加到epoll
的监听队列中,然后封装成Request
对象,加入到Looper.mRequest
内,然后等待事件的发生。
⑤Response:当epoll
侦测到某一fd的缓冲池发生了改变后,Looper会找到该fd对应的Request,把它进一步封装成Response,然后加入mResponse
等待Looper处理该消息。
2、Looper的创建与初始化
上面详细讲述了与Looper有关的一些知识,在此基础上我们继续探索Looper的原理,首先,我们从Looper的构造方法看起。(源码位置在:/system/core/libutils/Looper.cpp)
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); //创建一个用于唤醒Looper的文件描述符
LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s",
strerror(errno));
AutoMutex _l(mLock);
rebuildEpollLocked(); //重建一个epoll
}
void Looper::rebuildEpollLocked() {
// 如果已经有一个epoll了,那么关闭它
if (mEpollFd >= 0) {
#if DEBUG_CALLBACKS
ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endif
close(mEpollFd);
}
// 通过Linux系统调用 创建一个epoll实例
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
struct epoll_event eventItem; //epoll_event 封装了fd、event等
memset(& eventItem, 0, sizeof(epoll_event)); // 初始化为0
eventItem.events = EPOLLIN; //event:EPOLLIN 表示读事件,即对应的连接可读(缓冲区有值)
eventItem.data.fd = mWakeEventFd;
// epoll_ctl 系统调用,这里epoll添加了对mWakeEventFd的监听,
// 当epoll监听到mWakeEventFd的eventItem出现时,就会通知Looper
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
strerror(errno));
// 对mRequest内的所有fd重新添加epoll监听
for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
request.initEventItem(&eventItem);
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
if (epollResult < 0) {
ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
request.fd, strerror(errno));
}
}
}
Looper的创建和初始化过程的代码逻辑很清晰,主要是做了以下几件事情:
(1)调用eventfd(args)
方法创建一个用于唤醒Looper的文件描述符mWakeEventFd
。
(2)进行epoll_create(int)
系统调用,创建一个epoll
,用于轮询IO以及通知Looper有关的fd是否发生了改变,并把epoll
句柄保存在mEpollFd
内。
(3)进行epoll_ctl(args)
系统调用,把mWakeEventFd
添加到epoll的监听列表中。
(4)遍历mRequests
,把所有通过addFd(args)
添加进来的文件描述符fd再次加到这个新的epoll的监听列表内。(旧的epoll已经被close了)
3、发送消息/添加事件监听
我们知道,native层的Looper主要处理两种消息:一个是Message
,另一个是eventItem
(epoll监听文件描述符fd的变化)。下面我们来了解以下消息的发送过程或事件的添加过程。
(1)Looper#sendMessage
void Looper::sendMessage(const sp& handler, const Message& message) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
sendMessageAtTime(now, handler, message);
}
void Looper::sendMessageDelayed(nsecs_t uptimeDelay, const sp& handler,
const Message& message) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
sendMessageAtTime(now + uptimeDelay, handler, message);
}
void Looper::sendMessageAtTime(nsecs_t uptime, const sp& handler,
const Message& message) {
#if DEBUG_CALLBACKS
ALOGD("%p ~ sendMessageAtTime - uptime=%" PRId64 ", handler=%p, what=%d",
this, uptime, handler.get(), message.what);
#endif
size_t i = 0;
{ // acquire lock
AutoMutex _l(mLock);
size_t messageCount = mMessageEnvelopes.size();
// 在mMessageEnvelopes内寻找消息要插入的位置,条件是msg的uptime要比后者的小,但比前者大
// 这说明在这个消息列表内,下标越小,消息的触发时间越早
while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {
i += 1;
}
MessageEnvelope messageEnvelope(uptime, handler, message);
mMessageEnvelopes.insertAt(messageEnvelope, i, 1);
// 如果Looper正在发送消息,那么直接返回。因为Looper在发送完消息后,
// 会计算下一条消息的触发时间进而处理该消息
if (mSendingMessage) {
return;
}
} // release lock
// 如果消息被插入在消息列表头部,立刻唤醒looper
if (i == 0) {
wake();
}
}
发送一个消息时,不但要有Message
实例,同时也要有MessageHandler
,这是消息被处理的时候进行回调的方法。类似于Java层的Messaeg.callback
。紧接着,在把消息添加到消息列表的时候,会进行加锁,防止出现并发错误。如果当前Looper没有在处理消息并且插入的消息放在了列表头部,则需要去唤醒Looper。
(2)Looper#addFd
上面是发送一个消息然后等待Looper的处理,而这个方法则是添加一个文件描述符fd,然后让epoll去监听它的变化,如果产生了变化则会得到Looper的处理。
int Looper::addFd(int fd, int ident, int events, const sp& callback, void* data) {
#if DEBUG_CALLBACKS
ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident,
events, callback.get(), data);
#endif
//省略...
{ // acquire lock
AutoMutex _l(mLock);
// 封装成Request对象
Request request;
request.fd = fd;
request.ident = ident;
request.events = events;
request.seq = mNextRequestSeq++;
request.callback = callback;
request.data = data;
if (mNextRequestSeq == -1) mNextRequestSeq = 0; // reserve sequence number -1
struct epoll_event eventItem;
request.initEventItem(&eventItem);
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex < 0) {
// 如果mRequest内没有该Request,那么添加到epoll监听列表内
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
if (epollResult < 0) {
ALOGE("Error adding epoll events for fd %d: %s", fd, strerror(errno));
return -1;
}
mRequests.add(fd, request);
} else {
// 如果mRequest已经有该Request,那么修改epoll监听列表对应的fd的内容
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
if (epollResult < 0) {
// 异常状态处理
// 省略...
}
mRequests.replaceValueAt(requestIndex, request); //更新mRequest
}
} // release lock
return 1;
}
首先将fd、LooperCallback、*data等封装成了Request对象,然后经过epoll_ctl
系统调用,添加到epoll
监听列表内,此时epoll
就会对fd进行轮询,如果发生了变化就能得到通知。所以可以通过文件描述符的形式添加到Looper,然后我们在别的线程改变它,那么Looper所在的线程就能就能知道它的改变,然后调用回调方法,这样就实现了线程的切换。
4、Looper的唤醒操作
Looper的唤醒操作指的是,往mWakeEventFd
的缓冲区写入一个数字,然后Looper的pollOnce(args)
就会获取到该事件的发生,然后可以开始处理消息,否则该方法将会阻塞直到超时。
我们回忆下上一篇文章,在调用Message#enqueueMessage
插入一条消息的时候,如果Java的消息队列处于阻塞状态、队列头部是消息屏障以及马上有一个异步消息要处理,那么就会进行nativeWake()
的本地调用,然后进一步调用Looper#wake()
以唤醒Looper。当native的消息处理完成后,会导致Java层的nativePollOnce()
的调用返回,从而让Java层处理消息。因此,Looper#wake
和Looper#pollOnce
起着重要的桥梁作用,它让Java层和native层的消息机制得以联系和连续运作。
下面,我们来看Looper#wake
:
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s",
mWakeEventFd, strerror(errno));
}
}
}
首先定义了一个64位的整型变量为1,然后通过write()
方法,往mWakeEventFd
写入1。此时epoll
会侦测到这个写入事件,然后就会通知Looper了。
5、Looper处理消息的过程
如果Looper想要运转起来,还需要调用一个方法,那就是pollOnce
。该方法驱动了native Looper的消息循环。我们先来看源码:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
// 先处理被添加到mResponses列表的事件,这些事件都没有callback,
// 因此只能返回给调用者根据ident去处理
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
if (outFd != NULL) *outFd = fd;
if (outEvents != NULL) *outEvents = events;
if (outData != NULL) *outData = data;
return ident;
}
}
if (result != 0) {
if (outFd != NULL) *outFd = 0;
if (outEvents != NULL) *outEvents = 0;
if (outData != NULL) *outData = NULL;
return result;
}
result = pollInner(timeoutMillis); //进一步处理,并传入超时时间
}
}
int Looper::pollInner(int timeoutMillis) {
// 调整超时时间,根据当前超时时间和mNextMessageUptime来判断
// 如果传递进来的超时时间大于mNextMessageUptime,就要把超时时间改成这个触发时间
// 否则,下一条Message将不能在准确时间内得到处理
if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
if (messageTimeoutMillis >= 0
&& (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
timeoutMillis = messageTimeoutMillis;
}
}
// Poll.
int result = POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
// 表示正在轮询
mPolling = true;
// 创建epoll_event数组,以保存epoll结果
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
// 进行系统调用,阻塞式等待,收集在epoll监控的事件中已经发生的事件,超过timeoutMillis则返回
// 实际上这里epoll监听所有已添加的fd的改变,比如写入了“1”,那么就能收集到该事件
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// 取消轮询状态
mPolling = false;
// Acquire lock.
mLock.lock();
// Rebuild epoll set if needed.
if (mEpollRebuildRequired) {
mEpollRebuildRequired = false;
rebuildEpollLocked();
goto Done;
}
// Check for poll error.
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
ALOGW("Poll failed with an unexpected error: %s", strerror(errno));
result = POLL_ERROR;
goto Done;
}
// Check for poll timeout.
if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - timeout", this);
#endif
result = POLL_TIMEOUT;
goto Done;
}
// 处理所有在epoll收集到的事件
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd) {
if (epollEvents & EPOLLIN) {
awoken(); //从mWakeEventFd中读取“1”,以便下一次的wake()操作,即清空缓冲区
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
}
} else {
// 根据已触发的fd事件,找到相应的Request,然后添加到mResponses内
// 表示该事件已经被监听到,等待处理
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
pushResponse(events, mRequests.valueAt(requestIndex));
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
"no longer registered.", epollEvents, fd);
}
}
}
Done: ;
// 处理所有的Message 这里只处理触发时间小于当前时间的,
// 如果触发时间还没到的消息,等待下一次pollOnce()再处理
mNextMessageUptime = LLONG_MAX;
while (mMessageEnvelopes.size() != 0) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
if (messageEnvelope.uptime <= now) {
{ // obtain handler
sp handler = messageEnvelope.handler;
Message message = messageEnvelope.message;
mMessageEnvelopes.removeAt(0);
mSendingMessage = true;
mLock.unlock();
handler->handleMessage(message); //利用MessageHandler来处理消息,即回调函数
} // release handler
mLock.lock();
mSendingMessage = false;
result = POLL_CALLBACK;
} else {
// The last message left at the head of the queue determines the next wakeup time.
mNextMessageUptime = messageEnvelope.uptime;
break;
}
}
// Release lock.
mLock.unlock();
// 处理所有的Response,即已触发的fd事件
for (size_t i = 0; i < mResponses.size(); i++) {
Response& response = mResponses.editItemAt(i);
if (response.request.ident == POLL_CALLBACK) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
// 回调callback
int callbackResult = response.request.callback->handleEvent(fd, events, data);
if (callbackResult == 0) {
removeFd(fd, response.request.seq);
}
response.request.callback.clear();
result = POLL_CALLBACK;
}
}
return result;
}
一眼看去,整个pollOnce
和pollInner
的代码非常长,但实际上它的逻辑非常地清晰明了,详细的分析已经写在了注释里面,这里简单地总结以下它所作的工作:
(1)在pollOnce
方法中,首先会对所有Response中没有callback的事件进行处理,这会返回ident给调用者,让调用者自己去处理这个消息。
(2)在pollInner
方法内,首先超时时间的调整操作,这要兼顾到一个常规Message的触发,不能让epoll
阻塞过久。
(3)紧接着,进行了epoll_wait
系统调用,阻塞式等待epoll
返回事件,epoll
中监听到的事件会保存在eventItems[EPOLL_MAX_EVENTS]
内。如果超时时间到了,就返回。
(4)对所有监听到的事件进行处理,根据事件找到相应的Request,然后封装成Response,等待Looper的处理。
(5)处理所有到达触发事件的常规Message,进行handler的回调。
(6)处理所有Response,进行callback回调。
#总结
上面完整地分析了Looper的工作原理和运行机制,现在整理一下所有相关的知识点。
①native Looper在初始化的时候,会创建epoll
用以监听文件描述符fd的改变,同时实例化了一个mWakeEventFd
,这是用来唤醒Looper的fd,然后把它添加到epoll
监听队列内。
②Java层的Looper在loop()的过程中,会调用MessageQueue#next()
方法,进而调用nativePollOnce()
方法,而该方法又交给Looper#pollOnce
处理。这表示Java层的消息在处理之前,会先处理native层的消息,直到JNI调用返回。
③在native Looper的循环过程中,会使用epoll_wait
系统调用,阻塞式等待相关的fd事件的发生。然后再处理Message和fd event。
④Looper在阻塞的时候,可以通过wake()
方法来唤醒它,通过往mWakeEventFd
写入"1",会导致epoll_wait
返回,从而取消轮询状态。