Stories

Detail Return Return

你的程序為何卡頓?從LINUX I/O三大模式尋找答案 - Stories Detail

I/O交互流程
在LINUX中,內核空間和用户空間都位於虛擬內存中。LINUX採用兩級保護機制:0級供內核使用,3級供用户程序使用。每個進程都有獨立的用户空間(0~3G),對其他進程不可見,而最高的1G虛擬內核空間則由所有進程和內核共享。
操作系統和驅動程序運行在內核空間,應用程序運行在用户空間。由於LINUX使用虛擬內存機制,兩者之間不能直接通過指針傳遞數據。用户空間必須通過系統調用請求內核協助完成I/O操作。內核會為每個I/O設備維護緩衝區,而用户空間的數據可能被換出,因此內核無法直接使用用户空間的指針。
對於一個輸入操作,進程發起I/O系統調用後,內核會先檢查緩衝區是否有緩存數據。如果沒有,則從設備讀取數據;如果有,則直接將數據複製到用户空間。因此,網絡輸入操作通常分為兩個階段:
1)內核空間階段:內核通過協議棧和設備驅動程序接收數據,並將其存儲在內核緩衝區;
2)用户空間階段:數據從內核緩衝區複製到用户進程的緩衝區後,用户進程即可處理這些數據。

image

I/O操作方式
在操作系統中,通常有三種主要的I/O操作方式,每種方式都有其獨特的特性和適用場景。

阻塞I/O
阻塞I/O(Blocking I/O)是最簡單的I/O模型。當進程發起I/O操作(如read或write)時,當前線程會被阻塞,直到I/O操作完成。這種模型是標準的同步I/O實現,例如POSIX標準中的默認read和write系統調用。
阻塞I/O的優點是實現簡單,適合低併發的場景,因為內核已經對這些系統調用進行了高度優化。然而,在併發場景下,阻塞I/O的性能瓶頸會顯現出來:每個I/O操作都會阻塞一個線程,導致內核需要頻繁地進行線程切換,這會增加上下文切換的開銷,降低處理器緩存的利用率,並可能使依賴線程本地存儲(Thread-Local Storage, TLS)的代碼性能下降。

image

// 偽代碼: 阻塞I/O
socket = accept(); // 阻塞,直到新連接到達
data = read(socket); // 阻塞,直到數據被讀取
process(data);

非阻塞I/O
非阻塞I/O(Non-Blocking I/O)允許I/O操作在沒有數據可用時立即返回,而不會阻塞執行線程。在非阻塞I/O模式下,如果數據未準備好,系統通常會返回一個錯誤碼(如EAGAIN 或 EWOULDBLOCK),指示操作需要稍後重試。進程可以通過輪詢監控多個文件描述符的就緒狀態。
非阻塞I/O的優點是提高程序的併發性,因為它允許線程在等待I/O操作完成的同時,執行其他任務。然而,這種模式也帶來了更高的編程複雜度,程序需要不斷檢查文件描述符的狀態,以確保在數據可用時及時處理。這種輪詢機制不僅增加了代碼的複雜性,還可能導致處理器資源的浪費。

image

以下偽代碼,展示了非阻塞I/O的執行過程。

// 偽代碼: 非阻塞I/O
while (true) {
    data = read(socket);
    if (data != EAGAIN) {
        process(data);
        break;
    }
    // do other things...
}

異步 I/O
異步I/O (Asynchronous I/O)是一種真正的異步模型,進程在發起 I/O 操作後立即返回,並通過回調函數或事件通知機制在操作完成後得到通知。典型的實現包括Windows的OVERLAPPED和I/O完成端口(IOCP),以及LINUX的原生異步I/O(AIO)。需要注意的是,LINUX的原生AIO 僅對文件I/O有效,對網絡I/O的支持有限。
異步I/O的優點是能夠最大限度地提高併發性能,同時減少線程阻塞和上下文切換的開銷。然而,異步I/O的實現和調試複雜度較高,且在某些平台上的支持不夠完善。

image

// 偽代碼: 異步I/O
// 定義一個I/O操作完成後的回調函數
void on_read_complete(data, error) {
    if (error) {
        handle_error(error);
    } else {
        process(data);
    }
    // 可以在回調中發起下一次異步讀
    aio_read(socket, buffer, on_read_complete);
}

// 1. 主程序發起異步讀操作,並註冊回調函數
// aio_read會立即返回,不會阻塞
aio_read(socket1, buffer1, on_read_complete);
aio_read(socket2, buffer2, on_read_complete);

// 2. 主線程可以繼續執行其他任務,或進入一個等待退出的循環
do_other_work();
event_loop_wait_for_shutdown(); // 例如,等待信號

未完待續

很高興與你相遇!如果你喜歡本文內容,記得關注哦

Add a new Comments

Some HTML is okay.