Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

栏目: 后端 · 发布时间: 5年前

内容简介:这是一个开篇,包含前言和内容,内容部分讲了一点关于 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引起的文章 --- Linux 内核 500 次回眸 之 开篇

接着,带着一个比较常见的问题看下Handler。

postDelay 是 怎么工作的呢?

Handler 使用有很多方式,常用的发送消息最终都是调用了 sendMessageAtTime:

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

最后调用的是 MessageQueue.java 的 enqueueMessage 方法:

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

上面的 入队方法主要是来遍历链表,target 对象是我们在发送一条消息的时候,handler 将当前对象赋值给 Msg 的 target,即:

msg.target = this;

所以一个 Handler 发送多条消息都会被放到同一个线程的队列中中,在 Looper 中可以看到:

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

可以看到,每一个 Looper 都和当前线程绑定,得益于 ThreadLocal,所以 Handler 在 哪个线程发送消息,msg 就会被对应线程的 MessageQueue 入队,并且msg 的 target 都是执行发送的 Handler。

当 一个 Handler 发送多条 msg 时候,可以看到上面,用了一个无限循环对链表进行遍历,根据是否延时发送来调整节点位置,

所以 postDelay 不同的只是调用 sendMessageAtTime 的时候需要将 ( 延时的时长 + 当前时间戳) ,再赋值给 msg 的 when

上面注释还有一个 同步分割栏 barrier 的概念,

在 MessageQueue.java 中,可以通过调用 postSyncBarrier 和 removeSyncBarrier 来分别插入和移除一条同步分割栏:

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

用 token 来作为标识,从链表移除:

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

接下来看下,MessageQueue 里面的next() 方法:

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

其中,里面的 flushPendingCommands 将 当前线程挂起的 Binder 指令刷新到内核驱动

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

到此都是 java 层的分析,上面的 Handler 的流程图实际上还少了一点:

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

接下来看下 nativePollOnce 方法

nativePollOnce 是一个 native 方法,具体在

frameworks/base/core/jni/android_os_MessageQueue.cpp 调用:

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

同样,在同一个 cpp 文件中,也可以看到 nativeWake 的调用:

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

可以看到,nativePollOnce 和 nativeWake 最终分别调用了 mLooper 中的 pollOnce 和 wake 方法

mLooper 是 Looper.cpp 的实例,位于

system/core/libutils/Looper.cpp

具体看下 pollOnce 方法

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

外部将 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(); 方法,

Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇

可以看到,里面调用了 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_createwww.man7.org/linux/man-p…

epoll_ctl : www.man7.org/linux/man-p…

epoll_waitwww.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,有兴趣可以阅读下面的文章:

man7.org/linux/man-p…

kovyrin.net/2006/04/13/…

idea.popcount.org/2017-02-20-…

这是Handler 作为例子引出的 Linux 开篇,涉及了epoll,水平有限,内容可能有很多纰漏,欢迎指正。

ps:很多代码注释都很有用,比如 Scroller.java,类开头就将 Scroller 的使用三步曲都写出来了,比如 Binder.java,开头解释了 Binder 的挂起和跨进程通信,类似的还有 RecyclerView 啊等等。


以上所述就是小编给大家介绍的《Handler引起的文章 --- Linux 内核 500 次回眸 之 开篇》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据压缩导论

数据压缩导论

萨尤得 / 2009-2 / 99.00元

《数据压缩导论(英文版·第3版)》是数据压缩方面的经典著作,介绍了各种类型的压缩模式。书中首先介绍了基本压缩方法(包括无损压缩和有损压缩)中涉及的数学知识,为常见的压缩形式打牢了信息论基础,然后从无损压缩体制开始,依次讲述了霍夫曼编码、算术编码以及字典编码技术等,对于有损压缩,还讨论了使用量化的模式,描述了标量、矢量以及微分编码和分形压缩技术,最后重点介绍了视频加密。《数据压缩导论(英文版·第3版......一起来看看 《数据压缩导论》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具