天天看點

阿裡,騰訊,位元組等大廠Android面試必須掌握的知識點:從源碼角度看Handler

阿裡,騰訊,位元組等大廠Android面試必須掌握的知識點:從源碼角度看Handler

參加過大廠面試的朋友們應該都知道,像阿裡,騰訊,位元組跳動這樣的大公司最喜歡問一些底層原理的問題,下面給大家分享一篇技術文,幫助在學習Android或者準備面試的你更好地了解這些知識點。 原文位址:https://xiaozhuanlan.com/topic/9423185607

PS:針對Android技術的學習交流還有面試,我們建了一個高品質的交流圈子,有大牛也有普通碼農,大家會在群裡讨論技術上遇到的問題,也會交流找工作、跳槽和面試的經驗。群檔案會定期更新學習資料和面試真題解析,大家有什麼問題都可以在裡面【加入】讨論。

阿裡,騰訊,位元組等大廠Android面試必須掌握的知識點:從源碼角度看Handler

簡介

Handler這套線程異步通信架構在Android中的地位是不亞于Binder的,因為其基礎設計簡單、涉及的知識面廣、業務使用場景多等原因,十分适合應用層的國中級的工程師進行深入學習

這篇文章中我将分析Handler核心功能的源碼,分析将貫穿着framework, native和kernel的知識點:

  1. Handler發送異步消息原理
  2. Looper派發消息原理
  3. 消息分割欄的原理與視圖繪制的運用
  4. epoll_create, epoll_ctl, epoll_wait三部曲的源碼分析
  5. epoll中生産者與消費者模型的運用

同時按慣例,會在開篇給出總括全局的類圖與架構圖,方面閱讀中定位了解與閱讀後的回顧

設計圖

類圖

阿裡,騰訊,位元組等大廠Android面試必須掌握的知識點:從源碼角度看Handler
  • Message: 消息的抽象
  • Handler: 發送消息的工具類clo
  • MessageQueue: 主要維護了消息隊列,同時也是與native通信的中樞
  • Looper: 循環擷取消息并進行派發
  • Messenger: 可以跨程序傳輸的消息抽象

架構圖

阿裡,騰訊,位元組等大廠Android面試必須掌握的知識點:從源碼角度看Handler
  1. 一個APP中運作着多個線程,不同線程間可以互相拿到對方的Handler對象
  2. MessageQueue和native直接通信,native中又和kernel通信,這樣的調用鍊賦予了APP使用系統核心資源的能力
  3. epoll機制在kernel中維護了一個連結清單與一顆紅黑樹是它效率優于poll與select的基礎

發送跨線程異步消息: Handler.post()

使用Handler的前提是擷取到它的引用對象,然後才能夠在對應的MessageQueue的消息隊列插入消息。能夠這樣做的根本原因在于,線程之間的記憶體是可以互相通路的,這也是Handler能夠實作跨線程通信的基本原理之一

下面從最常用的

Handler.post()

方法入手,看看消息發送的實作原理,這裡需要說明的一點是,使用Handler發送消息的花樣很多,但最終都需要調用到

MessageQueue.enqueueMessage()

方法來實作,這裡就不一一介紹API的使用了

frameworks/base/core/java/android/os/Handler.java

public final boolean post(Runnable r)
{
 return  sendMessageDelayed(getPostMessage(r), 0);
}
​
public final boolean postAtTime(Runnable r, long uptimeMillis)
{
 return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
​
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
 // 這裡擷取到隊列,隊列是該線程唯一的,在Handler初始化時擷取
 MessageQueue queue = mQueue;
 ...
 return enqueueMessage(queue, msg, uptimeMillis);
}
​
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
 msg.target = this;
 // 判斷是否為異步消息,異步消息将不會受到分割欄的影響
 if (mAsynchronous) {
 msg.setAsynchronous(true);
 }
 // 最終調用到MessageQueue去插入消息
 return queue.enqueueMessage(msg, uptimeMillis);
}

           

可以看到Handler隻是一個類似于工具的類,最終的消息管理方面的操作還是需要委托給MessageQueue去做

frameworks/base/core/java/android/os/MessageQueue.java

boolean enqueueMessage(Message msg, long when) {
 // 這裡會強制target成員的設定,分割欄的插入不是調用這個方法實作的
 if (msg.target == null) {
 throw new IllegalArgumentException("Message must have a target.");
​
 // 因為不同的線程都可以調用這個方法,是以使用類鎖保證消息隊列的異步安全
 synchronized (this) {
 msg.markInUse();
 msg.when = when;
 Message p = mMessages;
 boolean needWake;
 if (p == null || when == 0 || when < p.when) {
 msg.next = p;
 mMessages = msg;
 needWake = mBlocked;
 } else {
 needWake = mBlocked && p.target == null && msg.isAsynchronous();
 Message prev;
 // 周遊消息隊列,插入到合适的位置
 for (;;) {
 prev = p;
 p = p.next;
 if (p == null || when < p.when) {
 break;
 }
 if (needWake && p.isAsynchronous()) {
 needWake = false;
 }
 }
 msg.next = p; // invariant: p == prev.next
 prev.next = msg;
 }
​
 // 調用到native嘗試喚醒對端裝置
 if (needWake) {
 nativeWake(mPtr);
 }
 }
 return true;
 }
}
           

frameworks/base/core/jni/android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
 // 通過ptr擷取到NativeMessageQueue對象
 NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
 nativeMessageQueue->wake();
}
​
void NativeMessageQueue::wake() {
 // 委托給native層的Looper進行處理
 mLooper->wake();
}
           

system/core/libutils/Looper.cpp

void Looper::wake() {
 uint64_t inc = 1;
 // 向檔案描述符為mWakeEventFd裝置寫入,以此來喚醒監聽裝置的epoll
 ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
 ...
}
           

總結一下消息發送的主要工作:

  1. 在java層的消息隊列中根據時間插入消息
  2. 在native層,向對應的fd裝置寫入資料,以此來喚醒監聽該裝置的epoll

處理消息: Handler.handleMessage()

handleMessage完全是處于被動調用的狀态,每當消息到來時,會從kernel依次調用的native,再到java層的Looper

這裡直接看Looper是如何擷取到消息,并調用handleMessage的,省去了

Looper.prepare()

代碼的分析

frameworks/base/core/java/android/os/Looper.java

public static void loop() {
 // 從ThreadLocal中取出之前放入的Looper對象
 final Looper me = myLooper();
 // 擷取到隊列
 final MessageQueue queue = me.mQueue;
​
 Binder.clearCallingIdentity();
 final long ident = Binder.clearCallingIdentity();
​
 for (;;) {
 // 擷取下一個消息,這個方法在沒有消息時會産生阻塞,隻有在消息到來時才會觸發
 Message msg = queue.next(); // might block
​
 ...
​
 // 派發消息,最終會調用到Handler.handleMessage方法
 try {
 msg.target.dispatchMessage(msg);
 } finally {
 if (traceTag != 0) {
 Trace.traceEnd(traceTag);
 }
 }
​
 ...
​
 // 回收Message
 msg.recycleUnchecked();
 }
}

           

繼續看看MessageQueue.next的實作:

frameworks/base/core/java/android/os/MessageQueue.java

Message next() {
 // mPtr實際上是NativeMesssageQueue的引用,友善在native層查詢到MessageQueue
 final long ptr = mPtr;
​
 int pendingIdleHandlerCount = -1; // -1 only during first iteration
 int nextPollTimeoutMillis = 0;
 for (;;) {
 if (nextPollTimeoutMillis != 0) {
 Binder.flushPendingCommands();
 }
​
 // 調用native方法,之後調用到epoll_wait
 nativePollOnce(ptr, nextPollTimeoutMillis);
​
 synchronized (this) {
 final long now = SystemClock.uptimeMillis();
 Message prevMsg = null;
 Message msg = mMessages;
 if (msg != null && msg.target == null) {
 // 消息分割欄,後面會做分析
 do {
 prevMsg = msg;
 msg = msg.next;
 } while (msg != null && !msg.isAsynchronous());
 }
 if (msg != null) {
 // 如果目前時間沒有已經就緒的message,那麼重置poll事件的時間
 if (now < msg.when) {
 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
 } else {
 // Got a message.
 mBlocked = false;
 if (prevMsg != null) {
 prevMsg.next = msg.next;
 } else {
 mMessages = msg.next;
 }
 msg.next = null;
​
 msg.markInUse();
 // 如果該消息已經就緒,那麼将它傳回給Looper進行派發
 return msg;
 }
 } else {
 // No more messages.
 nextPollTimeoutMillis = -1;
 }
 }
​
 // 如果此次沒有處理消息,則用來處理IdleHandler的操作,把優先級不高的操作放到這裡去執行,盡量不浪費事件片
 for (int i = 0; i < pendingIdleHandlerCount; i++) {
 final IdleHandler idler = mPendingIdleHandlers[i];
 mPendingIdleHandlers[i] = null; // release the reference to the handler
​
 boolean keep = false;
 try {
 keep = idler.queueIdle();
 } catch (Throwable t) {
 Log.wtf(TAG, "IdleHandler threw exception", t);
 }
​
 if (!keep) {
 synchronized (this) {
 mIdleHandlers.remove(idler);
 }
 }
 }
 }
}
           

核心操作

nativePollOnce

同樣是在native層進行處理

frameworks/base/core/jni/android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    // 同樣也是委托Looper進行處理
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;
}
           

system/core/libutils/Looper.cpp

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        // 擷取操作實際上是從reponse隊列中拿取的
        ...

        if (result != 0) {
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }

        // 本質上在循環的調用pollInner方法,直到擷取到了結果
        result = pollInner(timeoutMillis);
    }
}

int Looper::pollInner(int timeoutMillis) {
    ...

    // Poll.
    int result = POLL_WAKE;
    // 清空response隊列
    mResponses.clear();
    mResponseIndex = 0;

    // We are about to idle.
    mPolling = true;

    // 初始化epoll_event
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    // 使用epoll_wait調用的kernel去請求事件,kernel擷取到事件後會通過mmap将epoll_event資訊傳回到native層
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // No longer idling.
    mPolling = false;

    // Acquire lock.
    mLock.lock();

    ...

    // 擷取到資訊後,接下來就是讀取了
    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 {
            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;
                // 插入到response隊列中供後續擷取
                pushResponse(events, mRequests.valueAt(requestIndex));
            } 
        }
    }
Done: ;

    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<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();
                // 首先處理native層的Message
                handler->handleMessage(message);
            } // release handler
        }
    }

    // Release lock.
    mLock.unlock();
    ...
    return result;
}
           

native層Looper.pollInner()方法是擷取Message的主要操作:

1.調用epoll_wait在kernel中擷取裝置事件,該方法後續會做分析

2.擷取到事件後進行解析并插入到response隊列中

3.處理native事件

可以看到,該方法會首先處理native層的Message,也就是說Handler這套架構對于native的消息是優先派發的

設定同步分割欄: MessageQueue.postSyncBarrier()

同步分割欄的原理其實很簡單,本質上就是通過建立一個target成員為NULL的Message并插入到消息隊列中,這樣在這個特殊的Message之後的消息就不會被處理了,隻有當這個Message被移除後才會繼續執行之後的Message

最經典的實作就是ViewRootImpl調用scheduleTraversals方法進行視圖更新時的使用:

frameworks/base/core/java/android/view/ViewRootImpl.java

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 執行分割操作後會擷取到分割令牌,使用它可以移除分割欄
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 發出一個有異步标志的Message,避免被分割
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ...
    }
}
           

在執行doTraversal方法後,才會移出分割欄:

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        performTraversals();

        ...
    }
}
           

這樣做的原因是,doTraversal的操作是通過Handler進行處理的,然而這個消息隊列卻是整個主線程公用的,比如說四大元件的各個生命周期的調用,然而doTraversal的内容是更新視圖UI,這個任務無疑是最高優先級的。

是以在這之前,需要確定隊列中其它同步消息不會影響到它的執行

這裡繼續跟一下MessageQueue.postSyncBarrier()的實作:

frameworks/base/core/java/android/os/MessageQueue.java

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
        // 注意這裡,并沒有為target成員進行初始化

        Message prev = null;
        Message p = mMessages;
        // 插入到隊列中
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}
           

可以看到,設定分割欄和普通的post Message是一樣的,不同的是target是空的

下面接着來看看分割欄真正起作用的地方:

frameworks/base/core/java/android/os/MessageQueue.java

Message next() {
    ...
    for (;;) {
        ...
        // 進行隊列周遊
           Message msg = mMessages;
        if (msg != null && msg.target == null) {
            do {
                prevMsg = msg;
                msg = msg.next;
            // 如果target為NULL,将會陷入這個循環,除非是有異步标志的消息才會跳出循環
            } while (msg != null && !msg.isAsynchronous());
        }
        ...
    }
}
           

跨程序異步消息: Messenger

Messenger是Android開發中算是比較冷門的一種跨程序通信方式,它的實作是依托着Binder+Handler這兩種技術的配合,使得跨程序調用後服務端任務能夠在指定的線程中被執行

這種方式其實就類似于ActivityThread的binder call接收後,使用指定Handler去post消息是一樣的,不同的在于Messenger的對其進行了封裝,使得開發者可以輕松的實作而不需要更多的代碼

原生有個典型實作的MessengerService,可以參考它的思路:

frameworks/base/core/tests/coretests/src/android/os/MessengerService.java

public class MessengerService extends Service {
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Message reply = Message.obtain();
            reply.copyFrom(msg);
            try {
                // 這裡的replyTo的mTarget實際上是對端的binder proxy,用以實作回調的功能
                msg.replyTo.send(reply);
            } catch (RemoteException e) {
            }
        }
    };

    // 建立一個Messenger,mTarget将會建立一個binder stub,服務方法運作在mHandler所線上程
    private final Messenger mMessenger = new Messenger(mHandler);

    public MessengerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // 傳回的是被建立的Messenger服務binder句柄
        return mMessenger.getBinder();
    }
}
           

1.服務端可以通過繼承MessengerService,定義自己的業務

2.用戶端通過bind該service,可以擷取Messenger的binder proxy句柄,進而實作通信。調用到服務端後,操作将會運作在mHandler所線上程

3.replyTo是用戶端指定的,用來接受服務端的回複

實作基礎: epoll

epoll和select和poll一樣都是I/O多路複用技術,和它們不同的是,epoll隻關心"活躍"的連結,不需要周遊全部的描述符集合,能夠處理大量的連結請求

以下是一個使用epoll的典型代碼示例,主要分為三部:

1.使用epoll_create建立epoll的fd

2.使用epoll_ctl将需要監聽的fd添加到epoll中

3.使用epoll_wait等待事件

struct epoll_event ev, events[10];
int listen_sock, conn_sock, nfds, epollfd;

// 建立epoll的檔案描述符  
epollfd = epoll_create1(0);
if (epollfd == -1) {
    perror("epoll_create1");
    exit(EXIT_FAILURE);
}

// 将我們需要監聽的檔案描述符和事件通過epoll_ctl加入到epoll描述符中
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

for (;;) {
    // 等待事件的發生
    nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
    if (nfds == -1) {
        perror("epoll_wait");
        exit(EXIT_FAILURE);
    }

    // 監聽到事件,處理事件
    ...
}
           

epoll的實作很簡單,簡單到所有的主要邏輯都在單個檔案中實作,檔案路徑在kernel/msm-3.18/fs/eventpoll.c

下面接着分析epoll各個重要函數的源碼

epoll 初始化

在使用epoll之前,系統需要在啟動期間對epoll的基礎設施進行初始化

static int __init eventpoll_init(void)
{
    struct sysinfo si;

    // 擷取系統記憶體狀态
    si_meminfo(&si);
    // 根據系統記憶體配置設定單個使用者可見監聽的最大epoll個數
    max_user_watches = (((si.totalram - si.totalhigh) / 25) << PAGE_SHIFT) / EP_ITEM_COST;

    // 初始化調用隊列,友善檢查是否超過最大嵌套次數
    ep_nested_calls_init(&poll_loop_ncalls);
    ep_nested_calls_init(&poll_safewake_ncalls);
    ep_nested_calls_init(&poll_readywalk_ncalls);

    // 建立用于配置設定epitem資料結構的的SLAB緩存
    epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),
            0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);

    // 建立用于配置設定eppoll_entry資料結構的SLAB緩存
    pwq_cache = kmem_cache_create("eventpoll_pwq",
            sizeof(struct eppoll_entry), 0, SLAB_PANIC, NULL);

    return 0;
}
// 在系統啟動過程中,init程序裝置初始化時對epoll進行初始化
fs_initcall(eventpoll_init);

           

epoll 初始化期間主要完成的三個工作:

根據記憶體配置,初始化單個使用者最大可見聽epoll個數 初始化調用隊列,以此來判斷嵌套調用是超過上限 初始化SLAB記憶體緩存,以此來提升epoll使用記憶體的效率

epoll_create

在native Looper.cpp的實作中,epoll_create的調用函數用以建立一個epoll,但是在核心源碼中并沒有直接發現epoll_create之類的函數

查閱資料後發現,核心将epoll系列的函數都使用了SYSCALL_DEFINE宏調用來實作了,SYSCALL_DEFINE後的數字x代表了該函數擁有x個參數,之是以使用這種方式來聲明函數是為了修複64位Linux系統上的CVE-2009-2009漏洞

// 為了閱讀源碼的便捷性,可以直接将該宏調用看成 epoll_create(int flags);
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
    int error, fd;
    struct eventpoll *ep = NULL;
    struct file *file;

    // 初始化eventpoll結構
    error = ep_alloc(&ep);
    // 擷取一個沒有使用過的fd
    fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));

    // 建立eventpoll檔案,file_operations為eventpoll_fops
    // 同時将ep設定到file->private_data中
    file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
                 O_RDWR | (flags & O_CLOEXEC));

    ep->file = file;
    // 将fd與檔案進行關聯
    fd_install(fd, file);
    return fd;

out_free_fd:
    put_unused_fd(fd);
out_free_ep:
    ep_free(ep);
    return error;
}
           

epoll_create的主要做了兩個工作:

1.建立并初始化一個epollpoll結構

2.建立eventpoll檔案、擷取一個空閑fd,并将兩者進行關聯

epoll_ctl

// epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
        struct epoll_event __user *, event)
{
    int error;
    int full_check = 0;
    struct fd f, tf;
    struct eventpoll *ep;
    struct epitem *epi;
    struct epoll_event epds;
    struct eventpoll *tep = NULL;

    error = -EFAULT;
    if (ep_op_has_event(op) &&
        // 将epoll_event資訊使用mmap從native拷貝到kernel
        copy_from_user(&epds, event, sizeof(struct epoll_event)))
        goto error_return;

    // 擷取mEpollFd, mWakeEventFd對應檔案
    f = fdget(epfd);
    tf = fdget(fd);

    // 從檔案的private_data中擷取到eventpoll
    ep = f.file->private_data;

    mutex_lock_nested(&ep->mtx, 0);
    ...
    // 通過file, fd在eventpoll的紅黑樹中進行查詢,看是否存在已經儲存的eventitem
    epi = ep_find(ep, tf.file, fd);

    error = -EINVAL;
    // 進行op操作分流
    switch (op) {
    case EPOLL_CTL_ADD:
        if (!epi) {
            // 如果未查詢到,則建立并插入新的eventitem
            epds.events |= POLLERR | POLLHUP;
            error = ep_insert(ep, &epds, tf.file, fd, full_check);
        } else
            error = -EEXIST;
        break;
    case EPOLL_CTL_DEL:
        if (epi)
            // 如果查詢到,則移除該eventitem
            error = ep_remove(ep, epi);
        else
            error = -ENOENT;
        break;
    case EPOLL_CTL_MOD:
        if (epi) {
            // 如果查詢到,則進行替換
            epds.events |= POLLERR | POLLHUP;
            error = ep_modify(ep, epi, &epds);
        } else
            error = -ENOENT;
        break;
    }
    if (tep != NULL)
        mutex_unlock(&tep->mtx);
    mutex_unlock(&ep->mtx);

error_tgt_fput:
    if (full_check)
        mutex_unlock(&epmutex);

    fdput(tf);
error_fput:
    fdput(f);
error_return:

    return error;
}
           

epoll_ctl主要做了兩個工作:

1.将epoll_event資訊使用mmap拷貝到核心中

2.通過fd擷取到對應的file檔案,并通過private_data擷取到eventpoll

3.通過file,fd在eventpoll中進行查詢,看是否存在對應的eventitem

4.根據查詢到的eventitem進行對應的操作 這裡詳細分析下ep_insert方法:

static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
             struct file *tfile, int fd, int full_check)
{
    int error, revents, pwake = 0;
    unsigned long flags;
    long user_watches;
    struct epitem *epi;
    struct ep_pqueue epq;

    // 使用SLAB記憶體配置設定器建立eventitem
    if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
        return -ENOMEM;

    // 初始化連結清單節點
    INIT_LIST_HEAD(&epi->rdllink);
    INIT_LIST_HEAD(&epi->fllink);
    INIT_LIST_HEAD(&epi->pwqlist);
    // 初始化基礎屬性
    epi->ep = ep;
    ep_set_ffd(&epi->ffd, tfile, fd);
    epi->event = *event;
    epi->nwait = 0;
    epi->next = EP_UNACTIVE_PTR;

    // 初始化poll表,并注冊回調函數ep_ptable_queue_proc
    epq.epi = epi;
    init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);

    // 調用被插入檔案的poll方法,最終會将目前epollitem放入到ready list并喚醒eventpoll中的等待程序
    revents = ep_item_poll(epi, &epq.pt);

    /* Add the current item to the list of active epoll hook for this file */
    spin_lock(&tfile->f_lock);
    // 将fllink加入到檔案的f_ep_links清單中作為子項
    list_add_tail_rcu(&epi->fllink, &tfile->f_ep_links);
    spin_unlock(&tfile->f_lock);

    // 将epollitem的rbn紅黑樹節點插入到eventpoll中的rbr紅黑樹中
    // 注意一點的是,節點的是根據epoll_filefd來比對的
    ep_rbtree_insert(ep, epi);

    spin_lock_irqsave(&ep->lock, flags);

    if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
        list_add_tail(&epi->rdllink, &ep->rdllist);
        ep_pm_stay_awake(epi);

        // 喚醒該隊列
        if (waitqueue_active(&ep->wq))
            wake_up_locked(&ep->wq);
        if (waitqueue_active(&ep->poll_wait))
            pwake++;
    }

    spin_unlock_irqrestore(&ep->lock, flags);

    atomic_long_inc(&ep->user->epoll_watches);

    // 喚醒并調用該隊列中的事件
    if (pwake)
        ep_poll_safewake(&ep->poll_wait);

    return 0;
...
}
           

epoll_ctl主要做了三個工作:

1.建立并初始化eventitem

2.為poll_wait函數初始化,當裝置喚醒時會執行回調函數

3.檢查目前裝置狀态,如果已經就緒那麼直接喚醒等待隊列

epoll_wait

// epoll_wait(int epfd, epoll_event *events, int maxevents, int timeout)
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout)
{
    int error;
    struct fd f;
    struct eventpoll *ep;

    // 通過fd擷取到檔案資訊
    f = fdget(epfd);

    // 通過private_data擷取到eventpoll
    ep = f.file->private_data;

    // 調用ep_poll擷取就緒的事件,并将它們派發到接收者的事件緩沖中
    error = ep_poll(ep, events, maxevents, timeout);

error_fput:
    fdput(f);
    return error;
}
           

epoll_wait的操作很簡單,通過fd檢索到相關eventpoll後馬上調用核心函數ep_poll,這裡詳細分析一下:

static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, int maxevents, long timeout)
{
    int res = 0, eavail, timed_out = 0;
    unsigned long flags;
    long slack = 0;
    wait_queue_t wait;
    ktime_t expires, *to = NULL;

    if (timeout > 0) {
        struct timespec end_time = ep_set_mstimeout(timeout);

        slack = select_estimate_accuracy(&end_time);
        to = &expires;
        *to = timespec_to_ktime(end_time);
    } else if (timeout == 0) {
        // 如果未設定逾時事件,則直接擷取事件,不進行阻塞
        timed_out = 1;
        spin_lock_irqsave(&ep->lock, flags);
        goto check_events;
    }

fetch_events:
    spin_lock_irqsave(&ep->lock, flags);

    if (!ep_events_available(ep)) {
        // 使用隊列進行睡眠,直到ep_poll_callback被調用,隊列被喚醒,才會接着執行下去
        init_waitqueue_entry(&wait, current);
        __add_wait_queue_exclusive(&ep->wq, &wait);

        for (;;) {
            set_current_state(TASK_INTERRUPTIBLE);
            // 如果事件到來則退出循環
            if (ep_events_available(ep) || timed_out)
                break;
            if (signal_pending(current)) {
                res = -EINTR;
                break;
            }

            spin_unlock_irqrestore(&ep->lock, flags);
            if (!freezable_schedule_hrtimeout_range(to, slack,
                                HRTIMER_MODE_ABS))
                timed_out = 1;

            spin_lock_irqsave(&ep->lock, flags);
        }
        __remove_wait_queue(&ep->wq, &wait);

        set_current_state(TASK_RUNNING);
    }
check_events:
    // 是否存在入隊的事件
    eavail = ep_events_available(ep);

    spin_unlock_irqrestore(&ep->lock, flags);

    // 取出事件并發送到用戶端程序
    if (!res && eavail &&
        !(res = ep_send_events(ep, events, maxevents)) && !timed_out)
        goto fetch_events;

    return res;
}
           

ep_poll的主要操作在于從eventpoll中取出已經準備就緒的eventitem,随後調用ep_send_events,通過timeout參數判斷是否需要阻塞以及需要阻塞的時間

static int ep_send_events(struct eventpoll *ep,
              struct epoll_event __user *events, int maxevents)
{
    struct ep_send_events_data esed;

    esed.maxevents = maxevents;
    esed.events = events;

    // ep_send_events_proc 為掃描回調函數
    return ep_scan_ready_list(ep, ep_send_events_proc, &esed, 0, false);
}

static int ep_scan_ready_list(struct eventpoll *ep,
                  int (*sproc)(struct eventpoll *,
                       struct list_head *, void *),
                  void *priv, int depth, bool ep_locked)
{
    int error, pwake = 0;
    unsigned long flags;
    struct epitem *epi, *nepi;
    LIST_HEAD(txlist);
    ...
    // 調用ep_send_events_proc進行回調
    error = (*sproc)(ep, &txlist, priv);
    ...
    return error;
}

static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head, void *priv)
{
    struct ep_send_events_data *esed = priv;
    int eventcnt;
    unsigned int revents;
    struct epitem *epi;
    struct epoll_event __user *uevent;
    struct wakeup_source *ws;
    poll_table pt;

    init_poll_funcptr(&pt, NULL);
    for (eventcnt = 0, uevent = esed->events;
         !list_empty(head) && eventcnt < esed->maxevents;) {
        // 從rdlink清單中擷取到epollitem
        epi = list_first_entry(head, struct epitem, rdllink);

        ws = ep_wakeup_source(epi);
        if (ws) {
            if (ws->active)
                __pm_stay_awake(ep->ws);
            __pm_relax(ws);
        }

        list_del_init(&epi->rdllink);

        // 從裝置中讀取事件
        revents = ep_item_poll(epi, &pt);

        if (revents) {
            // 使用mmap将事件發送到用戶端
            if (__put_user(revents, &uevent->events) ||
                __put_user(epi->event.data, &uevent->data)) {
                list_add(&epi->rdllink, head);
                ep_pm_stay_awake(epi);
                return eventcnt ? eventcnt : -EFAULT;
            }
            ...
        }
    }

    return eventcnt;
}
           

epoll中的生産者消費者模型

生産者: 由epoll_ctl最終調用的ep_insert觸發,在驅動喚醒等待隊列後會調用ep_poll_callback方法将epollitem添加到epollevent的rdllist中

消費者: 由epoll_wait最終調用的ep_poll觸發,在等待隊列響應後會繼續執行,從rdllist中取出epoll_item,随後讀取裝置

最後

通過和身邊的一些Android朋友的交流發現,其實很多都是半路出家,從自己原來的工作轉入這個行業的,技術操作能夠勉強應付目前的工作,但是很多時候,這些底層的東西都是不求甚解。

希望這篇分享能夠幫助大家更好的學習Android,在技術上有進一步的突破,進階進階工程師。

高品質的交流圈子,群檔案會定期更新學習資料和面試真題解析,大家有什麼問題都可以在裡面讨論。想要探讨技術,交流學習的小夥伴就【加入】進來吧。