參加過大廠面試的朋友們應該都知道,像阿裡,騰訊,位元組跳動這樣的大公司最喜歡問一些底層原理的問題,下面給大家分享一篇技術文,幫助在學習Android或者準備面試的你更好地了解這些知識點。 原文位址:https://xiaozhuanlan.com/topic/9423185607
PS:針對Android技術的學習交流還有面試,我們建了一個高品質的交流圈子,有大牛也有普通碼農,大家會在群裡讨論技術上遇到的問題,也會交流找工作、跳槽和面試的經驗。群檔案會定期更新學習資料和面試真題解析,大家有什麼問題都可以在裡面【加入】讨論。
簡介
Handler這套線程異步通信架構在Android中的地位是不亞于Binder的,因為其基礎設計簡單、涉及的知識面廣、業務使用場景多等原因,十分适合應用層的國中級的工程師進行深入學習
這篇文章中我将分析Handler核心功能的源碼,分析将貫穿着framework, native和kernel的知識點:
- Handler發送異步消息原理
- Looper派發消息原理
- 消息分割欄的原理與視圖繪制的運用
- epoll_create, epoll_ctl, epoll_wait三部曲的源碼分析
- epoll中生産者與消費者模型的運用
同時按慣例,會在開篇給出總括全局的類圖與架構圖,方面閱讀中定位了解與閱讀後的回顧
設計圖
類圖
- Message: 消息的抽象
- Handler: 發送消息的工具類clo
- MessageQueue: 主要維護了消息隊列,同時也是與native通信的中樞
- Looper: 循環擷取消息并進行派發
- Messenger: 可以跨程序傳輸的消息抽象
架構圖
- 一個APP中運作着多個線程,不同線程間可以互相拿到對方的Handler對象
- MessageQueue和native直接通信,native中又和kernel通信,這樣的調用鍊賦予了APP使用系統核心資源的能力
- 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)));
...
}
總結一下消息發送的主要工作:
- 在java層的消息隊列中根據時間插入消息
- 在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,在技術上有進一步的突破,進階進階工程師。
高品質的交流圈子,群檔案會定期更新學習資料和面試真題解析,大家有什麼問題都可以在裡面讨論。想要探讨技術,交流學習的小夥伴就【加入】進來吧。