Android窗口信息傳遞流程簡單筆記

以下為gemini回答記錄。
問:Android window信息怎么傳遞到inputflinger

好的,這是一個關(guān)于 Android 系統(tǒng)底層實(shí)現(xiàn)的經(jīng)典問題。要把這個問題講清楚,我們需要理解 Android 圖形和輸入系統(tǒng)的幾個核心組件以及它們之間的協(xié)作關(guān)系。

一句話概括:WindowManagerService (WMS) 負(fù)責(zé)管理所有窗口的狀態(tài),當(dāng)窗口信息(如位置、大小、層級、可觸摸區(qū)域等)發(fā)生變化時,WMS 會將這些信息打包成一個精簡的數(shù)據(jù)結(jié)構(gòu),主動推送給 InputFlinger。

下面我們來詳細(xì)分解這個過程。

核心角色介紹

  1. App Process (應(yīng)用進(jìn)程)

    • 這是我們編寫的應(yīng)用程序運(yùn)行的地方。當(dāng) App 需要顯示一個界面(如 Activity、Dialog、Toast)時,它會向系統(tǒng)請求創(chuàng)建一個窗口。
  2. WindowManagerService (WMS)

    • 運(yùn)行在 system_server 進(jìn)程中,是 Android 系統(tǒng)里所有窗口的“大管家”。
    • 職責(zé)
      • 管理所有窗口(Window)的生命周期、層級(Z-Order)、位置和大小。
      • 負(fù)責(zé)窗口動畫。
      • 與 SurfaceFlinger 協(xié)作,告訴 SurfaceFlinger 有哪些窗口(Surface)需要被合成并顯示在屏幕上。
      • 關(guān)鍵職責(zé):計(jì)算每個窗口的可觸摸區(qū)域,并將這些信息傳遞給 InputFlinger。
  3. InputFlinger

    • 一個獨(dú)立的系統(tǒng)本地(Native)進(jìn)程,是 Android 輸入系統(tǒng)的核心。
    • 職責(zé)
      • 從內(nèi)核的驅(qū)動節(jié)點(diǎn)(/dev/input/event*)讀取原始的輸入事件(觸摸、按鍵、鼠標(biāo)等)。
      • 處理和加工這些事件(例如,將多個觸摸點(diǎn)識別為多指手勢)。
      • 關(guān)鍵職責(zé):根據(jù) WMS 提供的信息,決定這個輸入事件應(yīng)該分發(fā)給哪個窗口。

信息傳遞的詳細(xì)流程

這個過程可以分為兩個階段:窗口信息更新輸入事件分發(fā)。你的問題主要關(guān)注第一個階段。

階段一:窗口信息更新 (Window Info Update)

這個流程的核心是 WMS 主動通知 InputFlinger。

  1. 窗口創(chuàng)建/變化

    • 當(dāng)一個應(yīng)用通過 WindowManager.addView() 添加一個新窗口時,這個請求通過 Binder IPC 會發(fā)送到 WMS。
    • 同樣,當(dāng)一個窗口的位置、大小、可見性、或者一些重要標(biāo)志(如 FLAG_NOT_TOUCHABLE)發(fā)生改變時,WMS 也會感知到。
  2. WMS 計(jì)算和整理信息

    • WMS 內(nèi)部維護(hù)了一個完整的窗口列表(WindowState 對象)。當(dāng)任何窗口狀態(tài)變化時,WMS 會重新計(jì)算整個屏幕的窗口布局。
    • 對于每個可見的、可交互的窗口,WMS 會計(jì)算出它的關(guān)鍵信息,包括:
      • Frame (坐標(biāo)和尺寸):窗口在屏幕上的絕對位置和大小。
      • Touchable Region (可觸摸區(qū)域):這是一個非常重要的信息。它不一定等于窗口的 Frame。例如,一個有圓角的窗口,它的可觸摸區(qū)域就是排除掉四個角之后的區(qū)域?;蛘叽翱诒黄渌翱诓糠终趽?,可觸摸區(qū)域就是未被遮擋的部分。
      • Flags (標(biāo)志位):比如 FLAG_NOT_TOUCHABLE (不可觸摸), FLAG_NOT_FOCUSABLE (不可獲得焦點(diǎn)), FLAG_WATCH_OUTSIDE_TOUCH (關(guān)心窗口外的觸摸事件) 等。
      • Z-Order (層級):窗口的堆疊順序,決定了哪個窗口在最上面。
      • Owner Info (歸屬信息):窗口所屬應(yīng)用的 UID 和 PID,用于權(quán)限檢查和事件歸屬。
      • Display ID:窗口所在的顯示屏 ID。
      • Input Channel:一個用于接收輸入事件的管道,稍后會講到。
  3. 打包信息并發(fā)送

    • WMS 不會把龐大的 WindowState 對象直接發(fā)給 InputFlinger。它會把上述的關(guān)鍵信息提取出來,打包成一個輕量級的、專門用于輸入系統(tǒng)的數(shù)據(jù)結(jié)構(gòu),通常叫做 InputWindowInfo。
    • WMS 通過調(diào)用 InputManagerService (IMS) 的接口(IMS 是 InputFlinger 在 Java 層的代理),最終通過 JNI 調(diào)用到 Native 層的 InputFlinger。
    • 調(diào)用的關(guān)鍵函數(shù)類似于 setInputWindows()。這個函數(shù)會把一個包含所有窗口信息的 InputWindowInfo 列表一次性地、原子性地更新到 InputFlinger 的 InputDispatcher 組件中。

總結(jié)一下信息更新流:
App 操作 -> WMS 感知變化 -> WMS 整理窗口信息 -> WMS 創(chuàng)建 InputWindowInfo 列表 -> WMS 通過 IMS/JNI 調(diào)用 InputFlinger -> InputFlinger 更新其內(nèi)部的窗口信息列表。


階段二:輸入事件分發(fā) (Input Event Dispatch)

現(xiàn)在 InputFlinger 已經(jīng)有了最新的“窗口地圖”,當(dāng)一個觸摸事件發(fā)生時,它就能正確地找到目的地。

  1. 事件產(chǎn)生

    • 用戶觸摸屏幕,內(nèi)核生成輸入事件。
    • InputFlinger 的 InputReader 線程讀取到這個原始事件。
  2. 尋找目標(biāo)窗口

    • InputReader 將事件交給 InputDispatcher 線程。
    • InputDispatcher 會遍歷它從 WMS 收到的 InputWindowInfo 列表。這個遍歷是從上到下的,即按照 Z-Order 從最頂層的窗口開始檢查。
    • 對于每個窗口,InputDispatcher 會進(jìn)行判斷:
      • 觸摸點(diǎn)的坐標(biāo) (x, y) 是否落在該窗口的 Touchable Region 內(nèi)?
      • 該窗口的 Flags 是否允許它接收觸摸事件?(例如,沒有設(shè)置 FLAG_NOT_TOUCHABLE)。
    • 一旦找到第一個符合條件的窗口,這個窗口就是本次事件的目標(biāo)(Target)。
  3. 分發(fā)事件

    • 在窗口創(chuàng)建時,WMS 已經(jīng)為它創(chuàng)建了一對 InputChannel(一個匿名管道)。一端由 InputFlinger 持有,另一端通過 Binder 傳遞給了應(yīng)用程序進(jìn)程。
    • InputDispatcher 將觸摸事件通過目標(biāo)窗口對應(yīng)的 InputChannel 發(fā)送出去。
  4. 應(yīng)用接收事件

    • 應(yīng)用進(jìn)程的 UI 線程有一個 Looper 在不斷地監(jiān)聽它的 InputChannel。
    • 當(dāng)讀到事件后,經(jīng)過一系列包裝,最終會調(diào)用到我們熟悉的 Activity.dispatchTouchEvent()View.dispatchTouchEvent() 方法。

圖解流程

+----------------+      1. addView()       +-------------------------+
|   App Process  | ----------------------> |  WindowManagerService   |
+----------------+      (via Binder)      |     (in system_server)  |
        ^                                 +-------------------------+
        |                                             | 2. 計(jì)算布局, 創(chuàng)建/更新
        | 6. dispatchTouchEvent()                     |    InputWindowInfo 列表
        |                                             |
+----------------+                                    v
| InputChannel   | <--- 5. 寫入事件 --- +-------------------------+
+----------------+                      |      InputFlinger       |
                                        +-------------------------+
                                                     ^
                                                     | 4. 尋找目標(biāo)窗口
                                                     |
                                        +-------------------------+
                                        |  Kernel (/dev/input/*)  |
                                        +-------------------------+
                                                     ^
                                                     | 3. 用戶觸摸
                                                +---------+
                                                |  Screen |
                                                +---------+

總結(jié)

  • 誰是信息源?
    WindowManagerService (WMS) 是窗口信息的唯一權(quán)威來源。
  • 誰是信息消費(fèi)者?
    InputFlinger 是窗口信息的消費(fèi)者,它本身不管理窗口,只使用這些信息來決策事件的分發(fā)。
  • 溝通方式?
    單向推送。WMS 在窗口狀態(tài)變化時,主動將精簡后的 InputWindowInfo 列表推送給 InputFlinger。這是一種高效的同步機(jī)制,避免了 InputFlinger 每次都需要向 WMS 查詢。
  • 核心數(shù)據(jù)結(jié)構(gòu)?
    InputWindowInfo,它就像一張“窗口名片”,包含了 InputFlinger 分發(fā)事件所需的所有關(guān)鍵信息。

問:android輸入事件分發(fā)是怎樣的

好的,這個問題是緊接著上一個問題的自然延伸,非常棒!我們已經(jīng)知道了 InputFlinger 是如何從 WMS 獲取“窗口地圖”的,現(xiàn)在我們來詳細(xì)看看 InputFlinger 拿到輸入事件后,是如何一步步把它送到你的 App 手里的。

整個 Android 輸入事件的分發(fā)流程可以概括為一個清晰的、自下而上的旅程:從硬件到內(nèi)核,再到系統(tǒng)原生服務(wù),最后抵達(dá)應(yīng)用程序。

核心參與者

我們再回顧一下主角們,這次我們更關(guān)注他們在事件分發(fā)中的角色:

  1. 硬件 (Hardware):觸摸屏、鍵盤、鼠標(biāo)等。
  2. Linux 內(nèi)核 (Kernel):通過驅(qū)動程序(Drivers)將硬件信號轉(zhuǎn)化為標(biāo)準(zhǔn)的輸入事件,并暴露為設(shè)備文件(例如 /dev/input/event*)。
  3. InputFlinger:Android 輸入系統(tǒng)的核心中樞,一個獨(dú)立的 Native 進(jìn)程。它包含兩個關(guān)鍵線程:
    • InputReader:負(fù)責(zé)讀取內(nèi)核的原始事件,并將其解碼、翻譯成 Android 標(biāo)準(zhǔn)的輸入事件(如 MotionEvent, KeyEvent)。
    • InputDispatcher:負(fù)責(zé)將 InputReader 翻譯好的事件分發(fā)給正確的窗口。這是整個分發(fā)邏輯的核心。
  4. WindowManagerService (WMS):雖然不直接參與分發(fā),但它為 InputDispatcher 提供了至關(guān)重要的“窗口地圖”(InputWindowInfo 列表),告訴它每個窗口的位置、層級和觸摸區(qū)域。
  5. 應(yīng)用程序 (App Process):輸入事件的最終消費(fèi)者。

事件分發(fā)的詳細(xì)步驟

讓我們以一次觸摸事件 (Touch Event) 為例,追蹤它的完整旅程:

第 1 步:硬件 -> 內(nèi)核 (最底層)

  1. 用戶觸摸屏幕
  2. 觸摸屏的控制器(Controller)檢測到物理觸摸,并將其轉(zhuǎn)換為電信號。
  3. Linux 內(nèi)核中的觸摸屏驅(qū)動程序接收到這些信號。
  4. 驅(qū)動程序按照 Linux 的輸入子系統(tǒng)協(xié)議,將這些信號封裝成標(biāo)準(zhǔn)的輸入事件(包含事件類型、編碼和值),然后寫入到對應(yīng)的設(shè)備節(jié)點(diǎn)文件,比如 /dev/input/event2

第 2 步:內(nèi)核 -> InputReader (事件的誕生)

  1. InputFlinger 進(jìn)程中的 InputReader 線程通過 EventHub 組件,使用 epoll 機(jī)制實(shí)時監(jiān)聽所有 /dev/input/* 設(shè)備文件的變化。
  2. 當(dāng)觸摸屏的設(shè)備文件有新數(shù)據(jù)寫入時,InputReader 會被喚醒。
  3. InputReader 讀取這些原始的、零散的事件數(shù)據(jù)(例如,一個觸摸點(diǎn)移動可能會產(chǎn)生多個事件:X 坐標(biāo)更新、Y 坐標(biāo)更新、同步信號等)。
  4. InputReader 將這些原始數(shù)據(jù)進(jìn)行解碼和組裝,轉(zhuǎn)換成 Android 系統(tǒng)能夠理解的、結(jié)構(gòu)化的 MotionEvent 對象。這個對象包含了完整的觸摸信息:動作類型(按下、移動、抬起)、觸摸點(diǎn)坐標(biāo)、發(fā)生時間、設(shè)備 ID 等。

第 3 步:InputReader -> InputDispatcher (尋找目標(biāo))

  1. InputReader 將封裝好的 MotionEvent 對象放入一個隊(duì)列,并通知 InputDispatcher 線程有新事件需要處理。
  2. InputDispatcher 線程從隊(duì)列中取出事件,開始執(zhí)行其核心職責(zé):尋找目標(biāo)窗口 (Find Target)
  3. InputDispatcher 會查詢它內(nèi)部維護(hù)的、由 WMS 同步過來的窗口列表 (InputWindowInfo list)。
  4. 它會從 Z-Order 最高(最頂層)的窗口開始,向下遍歷。
  5. 對于列表中的每一個窗口,它會進(jìn)行檢查:
    • 命中測試 (Hit Test):觸摸事件的坐標(biāo) (x, y) 是否落在這個窗口的可觸摸區(qū)域 (Touchable Region) 內(nèi)?
    • 權(quán)限檢查:這個窗口是否允許接收觸摸事件?(例如,窗口的 FLAG_NOT_TOUCHABLE 標(biāo)志位沒有被設(shè)置)。
  6. 第一個通過所有檢查的窗口,就被確定為這次觸摸事件的目標(biāo)窗口 (Target Window)。一旦找到目標(biāo),遍歷就停止。

關(guān)鍵點(diǎn):這個過程是極其高效的,因?yàn)樗窃?InputFlinger 自己的內(nèi)存中完成的,不需要與 WMS 進(jìn)行任何實(shí)時通信。

第 4 步:InputDispatcher -> 應(yīng)用程序 (事件的投遞)

  1. 當(dāng) InputDispatcher 確定了目標(biāo)窗口后,它需要把事件發(fā)送給這個窗口所屬的應(yīng)用程序。
  2. 這個通信是通過一個叫做 InputChannel 的機(jī)制完成的。InputChannel 本質(zhì)上是一個基于 Unix Domain Socket 的匿名管道,它非常輕量和高效。
  3. 在窗口創(chuàng)建時,WMS 就已經(jīng)為它創(chuàng)建了一對 InputChannel:一端由 InputFlinger 持有,另一端通過 Binder 傳遞給了應(yīng)用程序進(jìn)程。
  4. InputDispatcherMotionEvent 寫入目標(biāo)窗口對應(yīng)的 InputChannel 的服務(wù)端。
  5. 等待確認(rèn) (ANR 機(jī)制):在發(fā)送事件后,InputDispatcher 并不會立即處理下一個事件,而是會等待應(yīng)用程序的“回執(zhí)”。如果應(yīng)用程序在規(guī)定時間內(nèi)(通常是 5 秒)沒有處理完事件并給出響應(yīng),InputDispatcher 就會認(rèn)為該應(yīng)用發(fā)生了 ANR (Application Not Responding),并向系統(tǒng)報(bào)告。

第 5 步:應(yīng)用程序內(nèi)部的分發(fā) (事件的消費(fèi))

  1. 在應(yīng)用程序的 UI 線程中,有一個 Looper 在不斷循環(huán)。這個 Looper 監(jiān)聽著 InputChannel 的客戶端文件描述符。
  2. 當(dāng) InputChannel 中有數(shù)據(jù)(即觸摸事件)時,Looper 會被喚醒。
  3. InputEventReceiver 會從 InputChannel 中讀取數(shù)據(jù),并在應(yīng)用的內(nèi)存中重新構(gòu)建出 MotionEvent 對象。
  4. 然后,事件被送入 View 系統(tǒng)的分發(fā)體系中,開始我們所熟知的旅程:
    • Activity.dispatchTouchEvent()
    • PhoneWindow.superDispatchTouchEvent()
    • DecorView.dispatchTouchEvent()
    • ViewGroup.dispatchTouchEvent() (這里會進(jìn)行攔截 onInterceptTouchEvent() 和分發(fā))
    • View.dispatchTouchEvent() (這里會處理 onTouchEvent())
  5. 當(dāng)事件處理完畢(例如 onTouchEvent 返回 truefalse),應(yīng)用會通過 InputChannelInputDispatcher 發(fā)送一個“處理完畢”的回執(zhí)信號。InputDispatcher 收到后,才會繼續(xù)分發(fā)下一個事件。

圖解流程

+----------------+      5. 事件在View體系中分發(fā)     +-------------------+
|      App       | <---------------------------- |  Activity/View    |
| (UI Thread)    |      (dispatchTouchEvent)       +-------------------+
+----------------+
       ^
       | 4. 通過 InputChannel 接收事件
       |    (Looper 喚醒)
+----------------+      3. 寫入事件到目標(biāo)Channel     +-------------------+
|  InputChannel  | <---------------------------- | InputDispatcher   |
+----------------+                               | (Finds Target)    |
                                                +-------------------+
                                                       ^
                                                       | 2. 遞交翻譯好的事件
                                                       |
                                                +-------------------+
                                                |   InputReader     |
                                                | (Decodes Event)   |
                                                +-------------------+
                                                       ^
                                                       | 1. 讀取原始數(shù)據(jù)
                                                       |
+----------------+                               +-------------------+
|  Linux Kernel  | ----------------------------> |      EventHub     |
| (/dev/input/*) |      (via epoll)              +-------------------+
+----------------+

總結(jié)

  • 分層與解耦:整個流程清晰地分為了內(nèi)核層、原生服務(wù)層和應(yīng)用層,各司其職。
  • 單向數(shù)據(jù)流:事件從底層硬件單向流向頂層應(yīng)用。
  • 效率優(yōu)先
    • 使用高效的 epoll 監(jiān)聽硬件事件。
    • InputDispatcher 使用預(yù)先緩存的窗口信息,避免了與 WMS 的實(shí)時 IPC。
    • InputChannel 提供了低延遲的跨進(jìn)程事件傳遞通道。
  • 可靠性保障:ANR 機(jī)制確保了系統(tǒng)的流暢性,能夠及時發(fā)現(xiàn)并處理無響應(yīng)的應(yīng)用。

這就是 Android 輸入事件分發(fā)的完整流程,一個設(shè)計(jì)精巧、高效且可靠的系統(tǒng)。

問:詳細(xì)講講InputFlinger吧

當(dāng)然,非常樂意為您詳細(xì)講解 InputFlinger。它是 Android 系統(tǒng)中一個極其重要但又常?!半[身”的組件。理解了它,就等于掌握了 Android 輸入系統(tǒng)的半壁江山。

一、 什么是 InputFlinger?

InputFlinger 是 Android 系統(tǒng)的一個原生 (Native) 系統(tǒng)服務(wù),它以一個獨(dú)立的進(jìn)程 (/system/bin/inputflinger) 運(yùn)行。

簡單來說,它的核心使命是:接收來自所有輸入設(shè)備(觸摸屏、鍵盤、鼠標(biāo)、手柄等)的原始信號,并將它們處理、翻譯、最終準(zhǔn)確無誤地分發(fā)給當(dāng)前應(yīng)該接收這些事件的應(yīng)用程序窗口。

可以把它想象成整個 Android 系統(tǒng)的“神經(jīng)中樞”。

二、 為什么需要 InputFlinger?

如果沒有 InputFlinger,每個需要處理輸入的應(yīng)用程序都必須自己去監(jiān)聽內(nèi)核設(shè)備、自己解碼原始信號、自己判斷事件是否屬于自己。這將導(dǎo)致巨大的混亂和低效:

  1. 無序競爭:所有應(yīng)用都會爭搶同一個輸入設(shè)備,無法確定誰應(yīng)該響應(yīng)。
  2. 缺乏仲裁:當(dāng)一個觸摸事件同時落在兩個重疊的窗口上時,誰來決定哪個窗口響應(yīng)?
  3. 效率低下:每個應(yīng)用都做重復(fù)的事件解碼工作,浪費(fèi) CPU 資源。
  4. 體驗(yàn)不一致:不同應(yīng)用對硬件的解析方式可能不同,導(dǎo)致操作體驗(yàn)千差萬別。

InputFlinger 的存在就是為了解決以上所有問題,它提供了一個集中式、有序、高效的輸入事件處理和分發(fā)模型。

三、 InputFlinger 的核心架構(gòu)與組件

InputFlinger 內(nèi)部并非鐵板一塊,它主要由兩個協(xié)同工作的關(guān)鍵線程和一個輔助模塊構(gòu)成。

(這是一個簡化的概念圖)

1. EventHub (事件集線器)

  • 角色:InputFlinger 與 Linux 內(nèi)核之間的橋梁。
  • 職責(zé)
    • 設(shè)備發(fā)現(xiàn):通過 inotify 監(jiān)視 /dev/input/ 目錄,當(dāng)有新的輸入設(shè)備(如插入 USB 鍵盤)連接或斷開時,它能動態(tài)地識別和管理。
    • 事件監(jiān)聽:使用 I/O 多路復(fù)用技術(shù)(epoll)高效地監(jiān)聽所有已打開的輸入設(shè)備節(jié)點(diǎn) (/dev/input/event*)。當(dāng)任何一個設(shè)備產(chǎn)生數(shù)據(jù)時,EventHub 會立即被喚醒。
    • 原始數(shù)據(jù)讀取:從設(shè)備節(jié)點(diǎn)讀取最原始的、未經(jīng)處理的 input_event 數(shù)據(jù)結(jié)構(gòu)。這些數(shù)據(jù)非常底層,例如“X 坐標(biāo)變?yōu)?200”、“Y 坐標(biāo)變?yōu)?400”、“同步信號”。

2. InputReader 線程

  • 角色事件翻譯官組裝工。
  • 職責(zé)
    • 獲取原始事件:不斷地從 EventHub 獲取原始的 input_event 數(shù)據(jù)。
    • 解碼與翻譯InputReader 擁有針對不同設(shè)備類型(觸摸屏、鍵盤、手柄等)的“解碼器”(InputMapper)。它將 EventHub 傳來的零散的、底層的事件碼(如 ABS_MT_POSITION_X)翻譯成 Android 上層能夠理解的、結(jié)構(gòu)化的事件對象。
    • 事件組裝:將多個相關(guān)的原始事件組合成一個完整的、有意義的 Android 事件。例如,它會把一系列代表多點(diǎn)觸摸的原始數(shù)據(jù)組裝成一個包含所有觸摸點(diǎn)信息的 MotionEvent 對象。同樣,它會把按鍵的 scan codekey code 轉(zhuǎn)換為 KeyEvent
    • 狀態(tài)管理:跟蹤設(shè)備狀態(tài),比如鍵盤上的哪些修飾鍵(Shift, Ctrl)被按下了。
    • 輸出:將組裝好的 Android 事件(如 MotionEvent, KeyEvent)放入一個隊(duì)列中,準(zhǔn)備交給 InputDispatcher 處理。

3. InputDispatcher 線程

  • 角色智能交通調(diào)度員,這是 InputFlinger 最核心、最智能的部分。
  • 職責(zé)
    1. 獲取待處理事件:從 InputReader 準(zhǔn)備好的隊(duì)列中取出事件。
    2. 尋找目標(biāo)窗口 (Find Target):這是它的核心算法。
      • 它會查詢內(nèi)部維護(hù)的一份“窗口地圖”,這份地圖是由 WindowManagerService (WMS) 在窗口布局發(fā)生變化時主動推送過來的 InputWindowInfo 列表。
      • 它會按照窗口的 Z-Order(層級,從最上層開始)遍歷這份地圖。
      • 對于觸摸事件,它進(jìn)行命中測試 (Hit Test),判斷事件坐標(biāo)是否落在窗口的可觸摸區(qū)域內(nèi)。
      • 對于按鍵事件,它會尋找當(dāng)前擁有輸入焦點(diǎn) (Focused Window) 的窗口。
      • 它還會檢查窗口的標(biāo)志位(如 FLAG_NOT_TOUCHABLE)來決定該窗口是否可以接收事件。
    3. 事件分發(fā) (Dispatch)
      • 一旦找到目標(biāo)窗口,它會通過一個名為 InputChannel 的高效 IPC 管道,將事件發(fā)送給該窗口所在的應(yīng)用程序進(jìn)程。
      • InputChannel 是一個基于 SocketPair 的全雙工管道,專為低延遲的事件傳遞而設(shè)計(jì)。
    4. ANR 監(jiān)控 (Application Not Responding)
      • InputDispatcher 在發(fā)出一個事件后,會啟動一個超時計(jì)時器(觸摸事件通常是 5 秒)。
      • 如果應(yīng)用程序在規(guī)定時間內(nèi)沒有處理完事件并通過 InputChannel 返回“處理完畢”的信號,InputDispatcher 就會認(rèn)為該應(yīng)用已無響應(yīng)。
      • 此時,它會收集相關(guān)信息,并通知 WMS,最終由 WMS 彈出我們熟悉的“應(yīng)用無響應(yīng)”對話框。
    5. 特殊事件處理:處理一些系統(tǒng)級的交互,比如“觸摸探索”(無障礙模式下)、“手勢攔截”等。

四、 InputFlinger 的工作流程總結(jié)

讓我們把所有部分串起來,看一次完整的觸摸事件之旅:

  1. 硬件層:用戶觸摸屏幕。
  2. 內(nèi)核層:驅(qū)動將觸摸信號轉(zhuǎn)換為 input_event 并寫入 /dev/input/eventX。
  3. EventHubepoll 監(jiān)聽到數(shù)據(jù),讀取原始的 input_event。
  4. InputReader
    • EventHub 獲取原始事件。
    • 調(diào)用 TouchInputMapper 解碼器。
    • 將零散的坐標(biāo)、壓力等信息組裝成一個完整的 MotionEvent 對象。
    • MotionEvent 放入分發(fā)隊(duì)列。
  5. InputDispatcher
    • 從隊(duì)列中取出 MotionEvent。
    • 查詢 WMS 提供的窗口列表,從上到下查找。
    • 發(fā)現(xiàn)事件坐標(biāo) (x,y) 命中了窗口 A 的可觸摸區(qū)域。
    • 通過窗口 A 對應(yīng)的 InputChannelMotionEvent 發(fā)送出去。
    • 啟動 ANR 計(jì)時器。
  6. 應(yīng)用層
    • 應(yīng)用進(jìn)程的 UI 線程從 InputChannel 讀到事件。
    • 事件被封裝并傳遞給 Activity.dispatchTouchEvent(),開始在 View 樹中分發(fā)。
    • 事件處理完畢后,通過 InputChannel 通知 InputDispatcher。
  7. InputDispatcher:收到“處理完畢”的信號,取消 ANR 計(jì)時器,然后處理下一個事件。

總結(jié)

InputFlinger 是一個設(shè)計(jì)精良的系統(tǒng),它通過清晰的職責(zé)劃分(讀取、翻譯、分發(fā))和高效的機(jī)制(epoll, InputChannel, 緩存窗口信息),實(shí)現(xiàn)了以下目標(biāo):

  • 解耦 (Decoupling):將硬件驅(qū)動和應(yīng)用程序徹底分開。
  • 集中化管理 (Centralization):統(tǒng)一處理所有輸入,解決了沖突和仲裁問題。
  • 高效 (Efficiency):使用 Native 代碼和優(yōu)化的 IPC 機(jī)制,延遲極低。
  • 可靠性 (Reliability):通過 ANR 機(jī)制保證了系統(tǒng)的流暢性和響應(yīng)性。

它是 Android 能夠提供流暢、精確、可靠交互體驗(yàn)的幕后英雄。

問:請給我講講epoll吧

好的,我們來詳細(xì)聊聊 epoll。它是在高性能網(wǎng)絡(luò)編程和系統(tǒng)編程領(lǐng)域一個繞不開的話題,也是理解像 InputFlinger 這樣的系統(tǒng)組件如何高效工作的一把鑰匙。

一、 什么是 epoll

epoll 是 Linux 內(nèi)核提供的一種 I/O 事件通知機(jī)制。它的全稱是 "event poll"。

簡單來說,它的核心功能是:讓你能夠高效地、同時監(jiān)視多個文件描述符(File Descriptor, FD),并在其中任何一個文件描述符準(zhǔn)備好進(jìn)行 I/O 操作(例如,可讀、可寫)時,能夠立即得到通知。

這里的“文件描述符”是一個非常廣泛的概念,它可以是:

  • 網(wǎng)絡(luò)套接字(Socket)
  • 管道(Pipe)
  • 設(shè)備文件(如 /dev/input/event*
  • 普通文件

二、 為什么需要 epoll?—— 從 selectpoll 的痛點(diǎn)說起

epoll 出現(xiàn)之前,Linux 系統(tǒng)主要使用 selectpoll 來實(shí)現(xiàn) I/O 多路復(fù)用。它們雖然能完成任務(wù),但在處理大量并發(fā)連接時存在嚴(yán)重的性能瓶頸。

1. select 的問題

  • 文件描述符數(shù)量限制select 使用一個 fd_set 位圖來存儲要監(jiān)視的文件描述符,這個位圖的大小是固定的(通常是 1024)。這意味著一個進(jìn)程最多只能同時監(jiān)視 1024 個文件描述符。對于需要處理成千上萬連接的服務(wù)器來說,這完全不夠用。
  • 重復(fù)的內(nèi)存拷貝:每次調(diào)用 select 時,你都需要把完整的 fd_set 從用戶空間拷貝到內(nèi)核空間。當(dāng)監(jiān)視的 FD 很多時,這個拷貝開銷會非常大。
  • 線性掃描select 返回后,它只會告訴你“有 FD 準(zhǔn)備好了”,但不會告訴你具體是哪一個。你必須自己遍歷整個 fd_set(從 0 到 1023),逐個檢查哪個 FD 的狀態(tài)發(fā)生了變化。當(dāng)只有少數(shù)幾個 FD 活躍時,這種線性掃描是巨大的浪費(fèi)。

2. poll 的改進(jìn)與問題

poll 解決了 select 的第一個問題:

  • 無數(shù)量限制poll 使用一個 pollfd 結(jié)構(gòu)體數(shù)組來代替 fd_set,沒有了 1024 的硬性限制。

但是,poll 仍然存在 select 的后兩個問題:

  • 重復(fù)的內(nèi)存拷貝:每次調(diào)用 poll,仍然需要把整個 pollfd 數(shù)組從用戶空間拷貝到內(nèi)核空間。
  • 線性掃描poll 返回后,你仍然需要遍歷整個 pollfd 數(shù)組來找出哪些 FD 是活躍的。

總結(jié) select/poll 的核心痛點(diǎn):隨著監(jiān)視的文件描述符數(shù)量(N)的增加,每次調(diào)用的開銷(拷貝+掃描)都會呈 O(N) 級別增長,即使活躍的連接非常少。

三、 epoll 的革命性設(shè)計(jì)

epoll 的設(shè)計(jì)思想完全不同,它徹底解決了 select/poll 的痛點(diǎn)。epoll 將整個過程分成了三個步驟/API:

1. epoll_create(size)

  • 作用:在內(nèi)核中創(chuàng)建一個 epoll 實(shí)例,并返回一個指向該實(shí)例的文件描述符(我們稱之為 epfd)。
  • 核心思想:這相當(dāng)于在內(nèi)核里開辟了一塊專屬的內(nèi)存空間,用于存儲你感興趣的事件。這個空間由內(nèi)核維護(hù),只需要創(chuàng)建一次。這塊空間內(nèi)部通常包含兩個關(guān)鍵數(shù)據(jù)結(jié)構(gòu):
    • 紅黑樹 (Red-Black Tree):用于高效地存儲和管理所有被監(jiān)視的文件描述符(FD)。增、刪、改、查的效率都是 O(logN)。
    • 就緒列表 (Ready List):一個雙向鏈表,用于存放那些已經(jīng)準(zhǔn)備好 I/O 的文件描述符。

2. epoll_ctl(epfd, op, fd, event)

  • 作用:向 epoll 實(shí)例中添加 (ADD)、修改 (MOD)刪除 (DEL) 你要監(jiān)視的文件描述符 fd 以及你關(guān)心的事件類型(如 EPOLLIN 表示可讀)。
  • 核心思想:這是 epollselect/poll 的根本區(qū)別。你只需要告訴內(nèi)核一次“請幫我監(jiān)視這個 FD”。之后,這個信息就一直保存在內(nèi)核的紅黑樹中,不需要在每次查詢時都重復(fù)傳遞。這個操作的復(fù)雜度是 O(logN)。

3. epoll_wait(epfd, events, maxevents, timeout)

  • 作用:這是主循環(huán)中唯一需要調(diào)用的阻塞函數(shù)。它會等待,直到 epoll 實(shí)例中有任何一個被監(jiān)視的 FD 準(zhǔn)備好 I/O,或者超時。
  • 核心思想
    • 沒有內(nèi)存拷貝:調(diào)用 epoll_wait 時,你不需要傳遞任何 FD 列表。內(nèi)核已經(jīng)在 epoll_ctl 時知道了所有信息。
    • 沒有線性掃描:當(dāng)一個 FD 準(zhǔn)備好 I/O 時,內(nèi)核會通過一個回調(diào)機(jī)制,自動將這個 FD 添加到 epoll 實(shí)例的“就緒列表”中。epoll_wait 所做的事情,僅僅是檢查這個“就緒列表”是否為空。如果不為空,它就把就緒列表中的 FD 直接拷貝給用戶,并返回實(shí)際就緒的 FD 數(shù)量。
    • 高效返回epoll_wait 返回時,events 數(shù)組里填充的全都是已經(jīng)就緒的 FD,你不需要再自己遍歷檢查。

四、 epoll 的工作模式

epoll 還提供了兩種工作模式,可以通過 epoll_ctl 設(shè)置:

1. 水平觸發(fā) (Level Triggered, LT) - 默認(rèn)模式

  • 行為:只要文件描述符的緩沖區(qū)中還有數(shù)據(jù)可讀(或可寫),epoll_wait 每次被調(diào)用都會返回這個 FD。
  • 比喻:就像一個水位報(bào)警器。只要水位高于警戒線,報(bào)警器就一直響。
  • 優(yōu)點(diǎn):編程更簡單,容錯性好。即使你這次沒有把緩沖區(qū)的數(shù)據(jù)讀完,下次調(diào)用 epoll_wait 還會提醒你。
  • 缺點(diǎn):可能會導(dǎo)致不必要的重復(fù)喚醒。

2. 邊緣觸發(fā) (Edge Triggered, ET)

  • 行為:只有當(dāng)文件描述符的狀態(tài)發(fā)生變化(例如,從不可讀變?yōu)榭勺x)時,epoll_wait 才會返回這個 FD,而且只通知一次
  • 比喻:就像一個門鈴。有人按了一下,門鈴響一次。如果你不去開門,它不會再響了,直到下一個人再按。
  • 優(yōu)點(diǎn):效率極高,避免了 LT 模式下的重復(fù)喚醒。非常適合高性能場景。
  • 缺點(diǎn):編程更復(fù)雜。因?yàn)橹煌ㄖ淮?,你必須在收到通知后?strong>一次性地將緩沖區(qū)的數(shù)據(jù)全部讀完/寫完(通常使用非阻塞 I/O 配合循環(huán) read/write 直到返回 EAGAIN),否則剩余的數(shù)據(jù)可能會“丟失”(即你再也收不到關(guān)于這些數(shù)據(jù)的通知了)。

五、 epoll 如何應(yīng)用于 InputFlinger?

現(xiàn)在我們回到 InputFlinger 的場景:

  1. InputFlinger 啟動時,EventHub 會調(diào)用 epoll_create 創(chuàng)建一個 epoll 實(shí)例。
  2. 當(dāng) EventHub 發(fā)現(xiàn)一個新的輸入設(shè)備(如 /dev/input/event2),它會 open 這個設(shè)備文件得到一個 FD,然后調(diào)用 epoll_ctl 將這個 FD 以 EPOLLIN (可讀) 事件添加epoll 實(shí)例中。
  3. InputReader 線程的主循環(huán)中,會調(diào)用 epoll_wait 進(jìn)行阻塞等待。
  4. 當(dāng)用戶觸摸屏幕時,內(nèi)核驅(qū)動向 /dev/input/event2 寫入數(shù)據(jù)。
  5. 內(nèi)核檢測到這個 FD 變?yōu)榭勺x狀態(tài),立即將其放入 epoll 實(shí)例的“就緒列表”中,并喚醒正在 epoll_waitInputReader 線程。
  6. epoll_wait 立即返回,并且告訴 InputReader:“event2 這個 FD 準(zhǔn)備好了!”。
  7. InputReader 就直接去讀取 /dev/input/event2 的數(shù)據(jù),而不需要去檢查其他幾十個可能存在的設(shè)備文件(鍵盤、鼠標(biāo)等)。

這就是 epoll 的威力:即使系統(tǒng)連接了幾十個輸入設(shè)備,InputReader 也能在任何一個設(shè)備產(chǎn)生輸入的瞬間被喚醒,并且精確地知道是哪個設(shè)備,開銷與設(shè)備總數(shù)無關(guān),只與活躍設(shè)備數(shù)量有關(guān)。

總結(jié)

特性 select / poll epoll
數(shù)據(jù)結(jié)構(gòu) 位圖 / 數(shù)組 (用戶空間) 紅黑樹 + 鏈表 (內(nèi)核空間)
FD 傳遞 每次 wait 都需要完整拷貝 只需通過 ctl 設(shè)置一次
工作方式 輪詢 (Polling) 回調(diào) (Callback-based)
返回結(jié)果 返回后需遍歷整個列表找活躍 FD 直接返回活躍 FD 列表
性能 O(N),N 為監(jiān)視的總 FD 數(shù) O(k),k 為活躍的 FD 數(shù)
模式 僅水平觸發(fā) 支持水平觸發(fā) (LT) 和邊緣觸發(fā) (ET)

epoll 是構(gòu)建高性能、高并發(fā)服務(wù)器和系統(tǒng)服務(wù)的基石,它的設(shè)計(jì)思想——將一次性設(shè)置和重復(fù)性等待分離,利用內(nèi)核回調(diào)避免無用輪詢——是理解現(xiàn)代 I/O 模型的關(guān)鍵。

問:給我講講 I/O 模型吧

回答:好的,非常樂意為您講解 I/O 模型。這是一個非?;A(chǔ)且核心的計(jì)算機(jī)科學(xué)概念,尤其在網(wǎng)絡(luò)編程和系統(tǒng)編程中至關(guān)重要。理解不同的 I/O 模型,能幫助我們明白為什么有些程序快、有些慢,以及如何設(shè)計(jì)高性能的系統(tǒng)。

一、 什么是 I/O?

首先,我們來定義一下 I/O(Input/Output)。在計(jì)算機(jī)中,I/O 操作指的是數(shù)據(jù)在不同存儲介質(zhì)之間的流動過程。最典型的就是數(shù)據(jù)在內(nèi)核空間 (Kernel Space)用戶空間 (User Space) 之間的傳輸。

  • 用戶空間:應(yīng)用程序運(yùn)行的地方,我們寫的代碼大部分都在這里。
  • 內(nèi)核空間:操作系統(tǒng)內(nèi)核運(yùn)行的地方,它直接管理硬件(如網(wǎng)卡、硬盤)。

一個典型的網(wǎng)絡(luò) I/O 讀取操作(如 read 系統(tǒng)調(diào)用)通常包含兩個階段:

  1. 等待數(shù)據(jù)準(zhǔn)備 (Waiting for data to be ready)

    • 內(nèi)核等待網(wǎng)絡(luò)上的數(shù)據(jù)包到達(dá)。
    • 數(shù)據(jù)到達(dá)后,內(nèi)核將其從網(wǎng)卡緩沖區(qū)拷貝到內(nèi)核緩沖區(qū)。
    • 這個階段數(shù)據(jù)還在內(nèi)核空間。
  2. 數(shù)據(jù)從內(nèi)核拷貝到用戶空間 (Copying data from kernel to user space)

    • 內(nèi)核將數(shù)據(jù)從其內(nèi)部緩沖區(qū)拷貝到我們應(yīng)用程序指定的緩沖區(qū)(比如 read 函數(shù)傳入的 buffer)。
    • 這個階段完成后,read 函數(shù)才返回,我們的應(yīng)用程序才能處理這些數(shù)據(jù)。

I/O 模型的核心區(qū)別,就在于應(yīng)用程序如何處理這兩個階段,特別是第一個階段的“等待”。

二、 五種經(jīng)典的 I/O 模型

根據(jù) UNIX 網(wǎng)絡(luò)的定義,主要有五種 I/O 模型。我們從最簡單到最復(fù)雜來逐一講解。

1. 阻塞 I/O (Blocking I/O - BIO)

這是最簡單、最常見、也是默認(rèn)的 I/O 模型。

  • 流程

    1. 應(yīng)用程序調(diào)用一個 I/O 函數(shù)(如 recvfrom)。
    2. 如果內(nèi)核的數(shù)據(jù)還沒準(zhǔn)備好(階段一),那么應(yīng)用程序的整個線程將被內(nèi)核掛起(阻塞),無法做任何其他事情。
    3. 直到數(shù)據(jù)準(zhǔn)備好,并且從內(nèi)核拷貝到用戶空間(階段二)完成,recvfrom 函數(shù)才會返回。
    4. 應(yīng)用程序線程解除阻塞,繼續(xù)執(zhí)行。
  • 比喻:你去餐廳吃飯,點(diǎn)完餐后,你就坐在座位上什么也不干,一直等到服務(wù)員把菜端到你面前,你才開始吃飯。在等菜的整個過程中,你被“阻塞”了。

  • 優(yōu)點(diǎn):編程模型最簡單,邏輯清晰。

  • 缺點(diǎn)性能極差。一個線程在任意時刻只能處理一個連接的 I/O。如果想同時處理多個連接,就需要為每個連接創(chuàng)建一個線程,這會導(dǎo)致大量的線程創(chuàng)建開銷和上下文切換開銷,嚴(yán)重限制了并發(fā)能力。

2. 非阻塞 I/O (Non-blocking I/O - NIO)

為了解決阻塞 I/O 的問題,非阻塞 I/O 出現(xiàn)了。

  • 流程

    1. 應(yīng)用程序?qū)?I/O 設(shè)備設(shè)置為非阻塞模式。
    2. 應(yīng)用程序調(diào)用 recvfrom。
    3. 如果內(nèi)核數(shù)據(jù)還沒準(zhǔn)備好,recvfrom 會立即返回一個錯誤碼 (如 EWOULDBLOCKEAGAIN),而不是阻塞線程。
    4. 應(yīng)用程序可以去做別的事情。
    5. 但應(yīng)用程序不知道數(shù)據(jù)什么時候準(zhǔn)備好,所以它必須反復(fù)地、主動地去調(diào)用 recvfrom 詢問內(nèi)核:“數(shù)據(jù)好了嗎?”、“數(shù)據(jù)好了嗎?”... 這種行為被稱為輪詢 (Polling)。
    6. 當(dāng)某次輪詢發(fā)現(xiàn)數(shù)據(jù)準(zhǔn)備好了,recvfrom 就不會返回錯誤了。此時,它會阻塞住,等待數(shù)據(jù)從內(nèi)核拷貝到用戶空間(階段二),然后返回成功。
  • 比喻:你去餐廳吃飯,點(diǎn)完餐后,你每隔一分鐘就跑去廚房問廚師:“我的菜好了嗎?”。在問的間隙,你可以玩手機(jī)(做別的事),但你需要不停地去問,非常耗費(fèi)精力。

  • 優(yōu)點(diǎn):線程不會被 I/O 等待阻塞,可以在等待期間執(zhí)行其他任務(wù)。

  • 缺點(diǎn)輪詢會大量消耗 CPU 時間。即使大部分時間數(shù)據(jù)都沒準(zhǔn)備好,你的程序也在不停地做無用的系統(tǒng)調(diào)用,造成 CPU 資源浪費(fèi)。

3. I/O 多路復(fù)用 (I/O Multiplexing)

這是實(shí)踐中用得最多、最重要的一種模型,也是 select, poll, epoll 的用武之地。它旨在解決非阻塞 I/O 中“盲目輪詢”的問題。

  • 流程

    1. 應(yīng)用程序先調(diào)用一個特殊的函數(shù)(如 select, poll, epoll_wait),并將一批感興趣的文件描述符(FDs)傳遞給它。
    2. 這個調(diào)用是阻塞的,但它不是阻塞在某個具體的 I/O 操作上,而是阻塞在“等待多個 FD 中任意一個就緒”這個事件上。
    3. 內(nèi)核會同時監(jiān)視這一批 FD。當(dāng)其中任何一個或多個 FD 的數(shù)據(jù)準(zhǔn)備好了(階段一完成),select 函數(shù)就會返回,并告訴應(yīng)用程序哪些 FD 已經(jīng)就緒。
    4. 然后,應(yīng)用程序再針對那些已經(jīng)確定就緒的 FD,調(diào)用 recvfrom 進(jìn)行數(shù)據(jù)讀取。
    5. 因?yàn)閿?shù)據(jù)已經(jīng)準(zhǔn)備好了,所以這次 recvfrom 調(diào)用通常不會阻塞在階段一,而是直接進(jìn)入階段二(數(shù)據(jù)拷貝),然后很快返回。
  • 比喻:你去餐廳吃飯,但你不是一個人,而是和一大群朋友一起。你們點(diǎn)完餐后,派一個代表(select)守在出菜口。這個代表就一直等著,什么也不干(阻塞)。當(dāng)任何一道菜(不分是誰的)做好時,出菜口的服務(wù)員會通知這個代表。然后代表再告訴對應(yīng)的朋友:“你的菜好了,快去拿!”。

  • 優(yōu)點(diǎn)可以用一個線程處理大量的并發(fā)連接。線程只在 select/epoll 上阻塞一次,而不是在每個連接上都阻塞。大大提高了系統(tǒng)的并發(fā)能力。

  • 缺點(diǎn):編程模型比阻塞 I/O 復(fù)雜。以 select 為例,性能仍有瓶頸(我們在上一個回答中已詳細(xì)討論)。epoll 則是對這一模型的終極優(yōu)化。

4. 信號驅(qū)動 I/O (Signal-driven I/O)

這是一種不常用的模型,了解即可。

  • 流程

    1. 應(yīng)用程序開啟套接字的信號驅(qū)動功能,并通過 sigaction 系統(tǒng)調(diào)用安裝一個信號處理函數(shù)。
    2. 應(yīng)用程序的進(jìn)程可以去做其他事情,不會被阻塞。
    3. 當(dāng)內(nèi)核數(shù)據(jù)準(zhǔn)備好時(階段一完成),內(nèi)核會為該進(jìn)程生成一個 SIGIO 信號。
    4. 應(yīng)用程序在之前安裝的信號處理函數(shù)中,調(diào)用 recvfrom 來讀取數(shù)據(jù)。
  • 比喻:你去餐廳吃飯,點(diǎn)完餐后,你給前臺留了你的手機(jī)號,然后你就去逛街了。當(dāng)你的菜做好時,服務(wù)員會給你打電話(發(fā)信號)。你接到電話后,再回到餐廳去取菜。

  • 優(yōu)點(diǎn):線程在等待數(shù)據(jù)期間不會被阻塞。

  • 缺點(diǎn):在信號量很大的情況下,信號處理的開銷會很大;而且它只適用于 UDP,對于 TCP 這種流式協(xié)議,信號的觸發(fā)時機(jī)和數(shù)據(jù)量不好控制。

5. 異步 I/O (Asynchronous I/O - AIO)

這是最理想化的 I/O 模型,也是“真·異步”。

  • 流程

    1. 應(yīng)用程序調(diào)用一個異步 I/O 函數(shù)(如 aio_read),并把緩沖區(qū)地址、大小、文件描述符以及一個完成后的通知方式(如信號或回調(diào)函數(shù))都告訴內(nèi)核。
    2. 這個調(diào)用會立即返回,應(yīng)用程序的線程完全不被阻塞,可以去做任何其他事情。
    3. 內(nèi)核會獨(dú)立完成所有工作:它會自己等待數(shù)據(jù)準(zhǔn)備好(階段一),然后自己將數(shù)據(jù)從內(nèi)核空間拷貝到應(yīng)用程序指定的緩沖區(qū)(階段二)。
    4. 當(dāng)兩個階段都全部完成后,內(nèi)核才會通過之前指定的方式(如調(diào)用回調(diào)函數(shù))來通知應(yīng)用程序:“你的數(shù)據(jù)已經(jīng)準(zhǔn)備好了,并且已經(jīng)放在你指定的 buffer 里了,你可以直接用了”。
  • 比喻:你去餐廳吃飯,點(diǎn)完餐后,你告訴服務(wù)員:“菜好了之后,請直接送到我家(指定緩沖區(qū)),送到后給我發(fā)個短信(回調(diào)通知)就行了?!?然后你就直接回家了。你不需要等待,也不需要自己去取,整個過程由餐廳(內(nèi)核)全權(quán)代勞。

  • 優(yōu)點(diǎn)實(shí)現(xiàn)了真正的非阻塞,應(yīng)用程序在 I/O 的兩個階段都無需關(guān)心,并發(fā)性能理論上是最高的。

  • 缺點(diǎn):編程模型最復(fù)雜。在 Linux 下,原生的 AIO(libaio)支持并不完善,通常只對本地文件 I/O 有較好支持,對網(wǎng)絡(luò) I/O 的支持不佳。因此,在實(shí)踐中,很多所謂的“異步框架”(如 Node.js, Netty)其實(shí)是基于 I/O 多路復(fù)用(epoll)在應(yīng)用層模擬出的異步效果。

總結(jié)與對比

I/O 模型 等待數(shù)據(jù)階段 (Phase 1) 拷貝數(shù)據(jù)階段 (Phase 2) 并發(fā)能力
阻塞 I/O 阻塞 阻塞 差 (1線程:1連接)
非阻塞 I/O 非阻塞 (需輪詢) 阻塞 中 (受CPU輪詢限制)
I/O 多路復(fù)用 阻塞在 select/epoll 阻塞 好 (1線程:N連接)
信號驅(qū)動 I/O 非阻塞 阻塞
異步 I/O (AIO) 非阻塞 非阻塞 極好 (真異步)

關(guān)鍵區(qū)分同步 I/O vs 異步 I/O

  • 同步 I/O:指應(yīng)用程序必須自己參與到 I/O 操作中,并等待或主動查詢 I/O 操作的完成。前四種模型(阻塞、非阻塞、多路復(fù)用、信號驅(qū)動)都屬于同步 I/O。因?yàn)樵跀?shù)據(jù)從內(nèi)核拷貝到用戶空間的階段(階段二),應(yīng)用程序的線程都是被阻塞的。
  • 異步 I/O:指應(yīng)用程序發(fā)起 I/O 請求后就可以立即返回做其他事,由內(nèi)核完成所有 I/O 操作后,再通知應(yīng)用程序。只有 AIO 是真正的異步 I/O。

在現(xiàn)代高性能服務(wù)器開發(fā)中,I/O 多路復(fù)用(特別是 epoll)是絕對的主流和基石

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容