内容简介:这是一个开篇,包含前言和内容,内容部分讲了一点关于 handler 的例子。 首先,Linux 内核是非常优秀的程序,同时,Linux 是一个优秀的操作系统,既然是操作系统,那么关于操作系统的知识,Linux 内核都会多少涉及到。
这是一个开篇,包含前言和内容,内容部分讲了一点关于 handler 的例子。
1、前言
首先,Linux 内核是非常优秀的程序,同时,Linux 是一个优秀的操作系统,既然是操作系统,那么
关于操作系统的知识,Linux 内核都会多少涉及到。
本人是一年的小小开发仔,从个人角度来说,Linux 内核是很值得一读学习的知识,结合相关的书籍,在
网站上下载代码,解压之后用 source insight,有时间瞧一瞧感兴趣的部分,挺不错的。比如呢:
1、当使用 java 编程的时候,遇到了使用到 ReentrantReadWriteLock 对于临界区进行必要的操作,
ReentrantReadWriteLock 的原理大家都比较清楚,这个时候,可能会更想知道临界区是啥?
linux 对于临界区的各种操作分别有什么?
2、当使用 handler 进行消息发送的时候,可能主线程阻塞甚至产生 ANR,这时候看下 traces 文件,可能发现
最后都走到了 nativePollOnce 方法,这是 MessageQueue 里面 的 next() 里面的一个调用的方法,sdk 很清楚地 注释了next() 方法 may be bocked ,那么,此时就可以去探索,nativePollOnce 里面使用了 linux 的 epoll,多 路IO复用的相关知识。
3、当使用 LRUCache的时候,显而易见,LRUCache 的原理都比较清楚,最近最少使用的知识也是操作系统的范 畴,那么可以联想到,在 Linux 内核中,使用了 LRU 链表 来实现这个算法,当然其中也涉及到了链表数据结构, 这也会让我们进一步探索。
4、当阅读线程池的源码时候,可能会感慨线程池的设计如此厉害,更多的可以很自然而然联想到,线程池架构与 CPU 三级缓存的分层的联系十分相似。
5、volatile 关键字保证可见性,那么我们也可以联系到 高速缓冲区、主存等等概念,更多可以联想到内核同步的内存屏障、 读写自旋锁等。
6、在分析 Android 的 Binder 通信的时候,可能你会更想知道Linux内核有什么通信方式,FIFO、Socket 等等, 进而又涉及到了队列的知识以及生产者消费者的问题,各种思想总是相通的。
7、在学习 java jvm 的知识时候,关于堆的管理等等,也许可能你进一步会去探索到树的知识等等
8、在学习 数据库的时候,可能会探索 B-Tree 和 磁盘的关系等等
。。。。。。
当然,也可能是在设计一个 SDK、或者做一些 缓存优化,当然甚至是做一个自定义 View,甚至学习三次握手的半 连接队列等等知识,Linux 内核我认为都是挺有帮助的,可能是比较直接可以借鉴,或者是潜移默化的。
萝莉八所了这么多前言 。。。总的来说,可以涉及到操作系统的知识,并且作为用户态和内核态的切换,又 涉及到了分层的思想,另外,关于各种数据结构的使用、缓存思想、内存管理等等都很值得学习。
2、内容
接下来的内容涉及的是 Handler的知识,主要是里面运用了 epoll 的知识。
Handler 作为一套通信框架,在 Android 中的使用十分广泛,在 ActivityThread.java 里面就通过 Handler 来 处理各种消息类型,包括Activity的启动等等 。个人观点,Android 是基于消息驱动的,那Handler的重要地位不 言而喻 。
Handler 的流程图 ( 画工不好~~):
画图工具 :asciiflow.com/
接着,带着一个比较常见的问题看下Handler。
postDelay 是 怎么工作的呢?
Handler 使用有很多方式,常用的发送消息最终都是调用了 sendMessageAtTime:
最后调用的是 MessageQueue.java 的 enqueueMessage 方法:
上面的 入队方法主要是来遍历链表,target 对象是我们在发送一条消息的时候,handler 将当前对象赋值给 Msg 的 target,即:
msg.target = this;
所以一个 Handler 发送多条消息都会被放到同一个线程的队列中中,在 Looper 中可以看到:
可以看到,每一个 Looper 都和当前线程绑定,得益于 ThreadLocal,所以 Handler 在 哪个线程发送消息,msg 就会被对应线程的 MessageQueue 入队,并且msg 的 target 都是执行发送的 Handler。
当 一个 Handler 发送多条 msg 时候,可以看到上面,用了一个无限循环对链表进行遍历,根据是否延时发送来调整节点位置,
所以 postDelay 不同的只是调用 sendMessageAtTime 的时候需要将 ( 延时的时长 + 当前时间戳) ,再赋值给 msg 的 when
上面注释还有一个 同步分割栏 barrier 的概念,
在 MessageQueue.java 中,可以通过调用 postSyncBarrier 和 removeSyncBarrier 来分别插入和移除一条同步分割栏:
用 token 来作为标识,从链表移除:
接下来看下,MessageQueue 里面的next() 方法:
其中,里面的 flushPendingCommands 将 当前线程挂起的 Binder 指令刷新到内核驱动
到此都是 java 层的分析,上面的 Handler 的流程图实际上还少了一点:
接下来看下 nativePollOnce 方法
nativePollOnce 是一个 native 方法,具体在
frameworks/base/core/jni/android_os_MessageQueue.cpp 调用:
同样,在同一个 cpp 文件中,也可以看到 nativeWake 的调用:
可以看到,nativePollOnce 和 nativeWake 最终分别调用了 mLooper 中的 pollOnce 和 wake 方法
mLooper 是 Looper.cpp 的实例,位于
system/core/libutils/Looper.cpp
具体看下 pollOnce 方法
外部将 timeoutMillis 传递进来,并且最后调用了 pollInner(int timeoutMillis)
int Looper::pollInner(int timeoutMillis) { #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis); #endif // Adjust the timeout based on when the next message is due. 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; } #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - next message in %" PRId64 "ns, adjusted timeout: timeoutMillis=%d", this, mNextMessageUptime - now, timeoutMillis); #endif } // Poll. 清空操作 int result = POLL_WAKE; mResponses.clear(); mResponseIndex = 0; // We are about to idle. mPolling = true; struct epoll_event eventItems[EPOLL_MAX_EVENTS]; 调用 epoll_wait 方法 int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); // No longer idling. 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, errno=%d", 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; } // Handle all events. #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount); #endif 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(); } else { ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents); } } else { 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: ; // Invoke pending message callbacks. mNextMessageUptime = LLONG_MAX; while (mMessageEnvelopes.size() != 0) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0); if (messageEnvelope.uptime <= now) { // Remove the envelope from the list. 持有一个Handler的强引用,直到调用 handleMessage 结束,然后移除掉 // We keep a strong reference to the handler until the call to handleMessage // finishes. Then we drop it so that the handler can be deleted *before* // we reacquire our lock. { // obtain handler sp<MessageHandler> handler = messageEnvelope.handler; Message message = messageEnvelope.message; mMessageEnvelopes.removeAt(0); mSendingMessage = true; mLock.unlock(); #if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d", this, handler.get(), message.what); #endif handler->handleMessage(message); } // 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(); // Invoke all response callbacks. 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; #if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p", this, response.request.callback.get(), fd, events, data); #endif // Invoke the callback. Note that the file descriptor may be closed by // the callback (and potentially even reused) before the function returns so // we need to be a little careful when removing the file descriptor afterwards. int callbackResult = response.request.callback->handleEvent(fd, events, data); if (callbackResult == 0) { removeFd(fd, response.request.seq); } // Clear the callback reference in the response structure promptly because we // will not clear the response vector itself until the next poll. response.request.callback.clear(); result = POLL_CALLBACK; } } return result; }复制代码
这个方法代码有点长,大部分可以看里面的英文注释,可以看到 pollInner 里面 调用了 kernel 的 epoll_wait
同时可以看到 pollInner 里面调用了一个 rebuildEpollLocked(); 方法,
可以看到,里面调用了 epoll_create 和 epoll_ctl,到此,可以看到,有 epoll 关键字的已经有三个方法了,分别是:
epoll_create、epoll_ctl 、epoll_wait
接下来把目光移到 epoll 的相关 tips:
epoll 模型:
epoll是Linux 2.6中引入的新系统调用,旨在替换 select 和 poll。与早期的系统调用(O(n))不同,epoll是一种O(1)算法,这意味着随着观察文件描述符数量的增加,它可以很好地扩展。对于:select使用线性搜索通过监视文件描述符列表,结果耗时O(n),而epoll使用的是内核文件结构中的回调。
epoll 的 API 很简洁,如下:
或者移步这里:
epoll_create : www.man7.org/linux/man-p…
epoll_ctl : www.man7.org/linux/man-p…
epoll_wait : www.man7.org/linux/man-p…
int epoll_create(int size); 复制代码
创建一个epoll对象并返回其文件描述符,flags参数允许修改epoll行为,
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 复制代码
控制(配置)此对象监视哪些文件描述符,以及哪些事件。 参数 op 可以是ADD,MODIFY 或 DELETE 。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 复制代码
等待epoll_ctl 注册的任何事件,至少有一次事件触发或发生超时的时候,就会返回。 最多返回maxevents 。
一般来说,步骤有三:
- 调用epoll_create建立一个epoll对象
- epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改的事件,返回0标识成功,返回-1表示失败
- epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件
一般说到 epoll,免不了和 select 来做一个比较,直观的看,select O(n) 的时间复杂度对于高并发的操作肯定不能接受,而 epoll 的高效率使用借助了链表和红黑树,当我们执行epoll_create时,会创建 epoll 对象,并且创建了红黑树和链表,在需要的时候对此对象进行操作,如执行epoll_ctl,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在插入到红黑书中,然后向内核注册回调函数,当中断事件来临时向链表中插入数据,后续执行epoll_wait时返回 链表里的数据即可,这样避免了轮询的耗时和阻塞。
- 还有一个问题没有讨论到,貌似我在网上也很少看到 ~~~~:
通常一条有延时的消息 msgA ,delay 200ms,入队的时候会阻塞,那么队列下一次唤醒的时机:
A:有一条新的消息入队
B:没有新的消息入队,200ms之后执行延时的消息
那么,没有新消息入队的时候,它怎么知道要200ms之后执行呢?
在调用epoll_create时,除了在内核缓存里建了个红黑树用于存储以后 epoll_ctl 传来的套接字,会再建立一个list 链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可,有的话会马上,没有的会进行休眠 , 直到timeout 时间到后即使链表没数据也返回。
ok,epoll 的 tips 到此~~
那么,如果要模仿实现一个 Handler 呢,用 java 的话我们可以用 notify、wait 或者 Lock 等来模拟,此处就不展开了。
捋一下:本文涉及到了如下知识点:
- 链表遍历:msg 入队时需要遍历
- ThreadLocal:泛型类型是 Looper
- 同步分割栏:asynchronous barrier
- epoll
关于epoll,有兴趣可以阅读下面的文章:
idea.popcount.org/2017-02-20-…
这是Handler 作为例子引出的 Linux 开篇,涉及了epoll,水平有限,内容可能有很多纰漏,欢迎指正。
ps:很多代码注释都很有用,比如 Scroller.java,类开头就将 Scroller 的使用三步曲都写出来了,比如 Binder.java,开头解释了 Binder 的挂起和跨进程通信,类似的还有 RecyclerView 啊等等。
以上所述就是小编给大家介绍的《Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。