非同步與高效能輸入輸出

零複製 I/O(zero-copy;sendfile、splice)

/ sendfile -> SEND-file; splice -> splyss /

假設你要把一個包裹從一輛卡車搬到另一輛。浪費的做法是把它搬進倉庫辦公室、放在桌上、再搬回去放上第二輛車——無端多了兩趟。直接的做法是車對車交接。一般的 I/O 就是那浪費的做法:透過通訊端供應一個檔案,通常會把位元組從核心複製進你程式的緩衝區,再把它們複製回核心去送出。零複製 I/O 是那直接的做法——在核心內部搬移資料,完全不把它拖過使用者空間。

走一遍靜態檔案伺服器的正常路徑:read() 把磁碟快取的位元組從核心複製到你的緩衝區(複製一),接著 write()/send() 把它們從你的緩衝區複製回通訊端的核心緩衝區(複製二),來回還各有兩次模式切換。核心提供了捷徑。sendfile(out_fd, in_fd, ...) 叫核心把位元組從一個檔案描述符直接搬到一個通訊端描述符,完全不經使用者空間緩衝區。splice() 在核心內部於檔案或通訊端與管線之間搬移資料,讓你能組出免複製的管線。記憶體映射檔案(mmap)讓你把檔案內容當成記憶體看待、遞出指向它的指標而非複製。而 send() 上的 MSG_ZEROCOPY 讓核心直接從你使用者緩衝區的分頁傳送,並在可安全重用它們時通知你。每一種都去掉一個或兩個複製以及一些模式切換。

為何重要:對大量傳輸——檔案伺服器、影片串流、代理伺服器——那些多餘的複製主宰了 CPU 與記憶體頻寬的成本,所以零複製能讓吞吐量倍增。誠實的提醒:「零複製」往往其實是「少複製」,它主要在大型傳輸上發光(極小的酬載收效甚微、卻多了複雜度),MSG_ZEROCOPY 是非同步的、所以你的緩衝區必須一直不被碰到、直到核心說它做完,而這些介面多半是 Linux 特有的,對允許哪些描述符類型與偏移量各有限制。

/* 透過通訊端供應一個檔案,不經使用者空間複製 */ off_t off = 0; ssize_t sent = sendfile(client_sock, file_fd, &off, file_size);

sendfile() 在核心內把檔案位元組直接搬到通訊端,省去「先讀進緩衝區再寫出」的來回。

「零複製」通常指「較少的複製與較少的模式切換」,而非真的一次都沒有,且主要在大型傳輸上划算。使用 MSG_ZEROCOPY 時送出是非同步的:緩衝區的分頁必須一直不被修改,直到核心張貼完成通知,所以天真地立刻重用緩衝區會破壞仍在傳送途中的資料。

又稱
copy-free data transfer零拷貝(zero-copy)