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

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

内容简介:这是一个开篇,包含前言和内容,内容部分讲了一点关于 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 次回眸 之 开篇》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Learning jQuery

Learning jQuery

Jonathan Chaffer、Karl Swedberg / Packt Publishing / 2007-7-7 / GBP 24.99

jQuery is a powerful JavaScript library that can enhance your websites regardless of your background. In this book, creators of the popular jQuery learning resource, learningquery.com, share the......一起来看看 《Learning jQuery》 这本书的介绍吧!

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具