溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Linux零拷貝怎么實現(xiàn)

發(fā)布時間:2022-01-12 20:31:47 來源:億速云 閱讀:132 作者:iii 欄目:系統(tǒng)運維

本篇內容介紹了“Linux零拷貝怎么實現(xiàn)”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

為了迅速建立起零拷貝的概念,我們拿一個常用的場景進行引入。在寫一個服務端程序時(Web Server或者文件服務器),文件下載是一個基本功能。

這時候服務端的任務是:將服務端主機磁盤中的文件不做修改地從已連接的 Socket 發(fā)出去。

我們通常用下面的代碼完成:

while((n = read(diskfd, buf, BUF_SIZE)) > 0)     write(sockfd, buf , n);

基本操作就是循環(huán)的從磁盤讀入文件內容到緩沖區(qū),再將緩沖區(qū)的內容發(fā)送到 Socket。但是由于 Linux 的 I/O 操作默認是緩沖 I/O。

這里面主要使用的也就是 Read 和 Write 兩個系統(tǒng)調用,我們并不知道操作系統(tǒng)在其中做了什么。實際上在以上 I/O  操作中,發(fā)生了多次的數據拷貝。

當應用程序訪問某塊數據時,操作系統(tǒng)首先會檢查,是不是最近訪問過此文件,文件內容是否緩存在內核緩沖區(qū)。

如果是,操作系統(tǒng)則直接根據 Read 系統(tǒng)調用提供的 buf 地址,將內核緩沖區(qū)的內容拷貝到 buf 所指定的用戶空間緩沖區(qū)中去。

如果不是,操作系統(tǒng)則首先將磁盤上的數據拷貝的內核緩沖區(qū),這一步目前主要依靠 DMA 來傳輸,然后再把內核緩沖區(qū)上的內容拷貝到用戶緩沖區(qū)中。

接下來,Write 系統(tǒng)調用再把用戶緩沖區(qū)的內容拷貝到網絡堆棧相關的內核緩沖區(qū)中,最后 Socket 再把內核緩沖區(qū)的內容發(fā)送到網卡上。

說了這么多,不如看圖清楚:

Linux零拷貝怎么實現(xiàn)

數據拷貝

從上圖中可以看出,共產生了四次數據拷貝,即使使用了 DMA 來處理了與硬件的通訊,CPU 仍然需要處理兩次數據拷貝。

與此同時,在用戶態(tài)與內核態(tài)也發(fā)生了多次上下文切換,無疑也加重了 CPU 負擔。

在此過程中,我們沒有對文件內容做任何修改,那么在內核空間和用戶空間來回拷貝數據無疑就是一種浪費,而零拷貝主要就是為了解決這種低效性。

什么是零拷貝技術(zero-copy)?

零拷貝主要的任務就是避免 CPU 將數據從一塊存儲拷貝到另外一塊存儲。

主要就是利用各種零拷貝技術,避免讓 CPU 做大量的數據拷貝任務,減少不必要的拷貝,或者讓別的組件來做這一類簡單的數據傳輸任務,讓 CPU  解脫出來專注于別的任務。這樣就可以讓系統(tǒng)資源的利用更加有效。

我們繼續(xù)回到上文中的例子,我們如何減少數據拷貝的次數呢?一個很明顯的著力點就是減少數據在內核空間和用戶空間來回拷貝,這也引入了零拷貝的一個類型:讓數據傳輸不需要經過  user space。

使用 mmap

我們減少拷貝次數的一種方法是調用 mmap() 來代替 read 調用:

buf = mmap(diskfd, len); write(sockfd, buf, len);

應用程序調用 mmap(),磁盤上的數據會通過 DMA  被拷貝的內核緩沖區(qū),接著操作系統(tǒng)會把這段內核緩沖區(qū)與應用程序共享,這樣就不需要把內核緩沖區(qū)的內容往用戶空間拷貝。

應用程序再調用 write(),操作系統(tǒng)直接將內核緩沖區(qū)的內容拷貝到 Socket 緩沖區(qū)中,這一切都發(fā)生在內核態(tài),最后,Socket  緩沖區(qū)再把數據發(fā)到網卡去。

同樣的,看圖很簡單:

Linux零拷貝怎么實現(xiàn)

mmap

使用 mmap 替代 Read 很明顯減少了一次拷貝,當拷貝數據量很大時,無疑提升了效率。

但是使用 mmap 是有代價的。當你使用 mmap 時,你可能會遇到一些隱藏的陷阱。

例如,當你的程序 map 了一個文件,但是當這個文件被另一個進程截斷 (truncate) 時,Write 系統(tǒng)調用會因為訪問非法地址而被 SIGBUS  信號終止。

SIGBUS 信號默認會殺死你的進程并產生一個 coredump,如果你的服務器這樣被中止了,那會產生一筆損失。

通常我們使用以下解決方案避免這種問題:

①為 SIGBUS 信號建立信號處理程序

當遇到 SIGBUS 信號時,信號處理程序簡單地返回,Write 系統(tǒng)調用在被中斷之前會返回已經寫入的字節(jié)數,并且 errno 會被設置成  success,但是這是一種糟糕的處理辦法,因為你并沒有解決問題的實質核心。

②使用文件租借鎖

通常我們使用這種方法,在文件描述符上使用租借鎖,我們?yōu)槲募騼群松暾堃粋€租借鎖。

當其他進程想要截斷這個文件時,內核會向我們發(fā)送一個實時的 RTSIGNALLEASE 信號,告訴我們內核正在破壞你加持在文件上的讀寫鎖。

這樣在程序訪問非法內存并且被 SIGBUS 殺死之前,你的 Write 系統(tǒng)調用會被中斷。Write 會返回已經寫入的字節(jié)數,并且置 errno 為  success。

我們應該在 mmap 文件之前加鎖,并且在操作完文件后解鎖:

if(fcntl(diskfd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {     perror("kernel lease set signal");     return -1; } /* l_type can be F_RDLCK F_WRLCK  加鎖*/ /* l_type can be  F_UNLCK 解鎖*/ if(fcntl(diskfd, F_SETLEASE, l_type)){     perror("kernel lease set type");     return -1; }

使用 sendfile

從 2.1 版內核開始,Linux 引入了 sendfile 來簡化操作:

#include<sys/sendfile.h> ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

系統(tǒng)調用 sendfile() 在代表輸入文件的描述符 infd 和代表輸出文件的描述符 outfd 之間傳送文件內容(字節(jié))。

描述符 outfd 必須指向一個套接字,而 infd 指向的文件必須是可以 mmap 的。

這些局限限制了 sendfile 的使用,使 sendfile 只能將數據從文件傳遞到套接字上,反之則不行。

使用 sendfile 不僅減少了數據拷貝的次數,還減少了上下文切換,數據傳送始終只發(fā)生在 kernel space。

Linux零拷貝怎么實現(xiàn)

sendfile 系統(tǒng)調用過程

在我們調用 sendfile 時,如果有其它進程截斷了文件會發(fā)生什么呢?假設我們沒有設置任何信號處理程序,sendfile  調用僅僅返回它在被中斷之前已經傳輸的字節(jié)數,errno 會被置為 success。

如果我們在調用 sendfile 之前給文件加了鎖,sendfile 的行為仍然和之前相同,我們還會收到 RTSIGNALLEASE 的信號。

目前為止,我們已經減少了數據拷貝的次數了,但是仍然存在一次拷貝,就是頁緩存到 Socket 緩存的拷貝。那么能不能把這個拷貝也省略呢?

借助于硬件上的幫助,我們是可以辦到的。之前我們是把頁緩存的數據拷貝到 Socket 緩存中。

實際上,我們僅僅需要把緩沖區(qū)描述符傳到 Socket 緩沖區(qū),再把數據長度傳過去,這樣 DMA  控制器直接將頁緩存中的數據打包發(fā)送到網絡中就可以了。

總結一下:sendfile 系統(tǒng)調用利用 DMA 引擎將文件內容拷貝到內核緩沖區(qū)去,然后將帶有文件位置和長度信息的緩沖區(qū)描述符添加 Socket  緩沖區(qū)去。

這一步不會將內核中的數據拷貝到 Socket 緩沖區(qū)中,DMA 引擎會將內核緩沖區(qū)的數據拷貝到協(xié)議引擎中去,避免了最后一次拷貝。

Linux零拷貝怎么實現(xiàn)

帶 DMA 的 sendfile

不過這一種收集拷貝功能是需要硬件以及驅動程序支持的。

使用 splice

sendfile 只適用于將數據從文件拷貝到套接字上,限定了它的使用范圍。

Linux 在 2.6.17 版本引入 splice 系統(tǒng)調用,用于在兩個文件描述符中移動數據:

#define _GNU_SOURCE         /* See feature_test_macros(7) */ #include <fcntl.h> ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);

splice 調用在兩個文件描述符之間移動數據,而不需要數據在內核空間和用戶空間來回拷貝。

他從 fdin 拷貝 len 長度的數據到 fdout,但是有一方必須是管道設備,這也是目前 splice 的一些局限性。

flags 參數有以下幾種取值:

  • SPLICEFMOVE:嘗試去移動數據而不是拷貝數據。這僅僅是對內核的一個小提示:如果內核不能從 pipe 移動數據或者 pipe  的緩存不是一個整頁面,仍然需要拷貝數據。

  • Linux 最初的實現(xiàn)有些問題,所以從 2.6.21 開始這個選項不起作用,后面的 Linux 版本應該會實現(xiàn)。

  • SPLICEFNONBLOCK:splice 操作不會被阻塞。然而,如果文件描述符沒有被設置為不可被阻塞方式的 I/O ,那么調用 splice  有可能仍然被阻塞。

SPLICEFMORE:后面的 splice 調用會有更多的數據。

splice 調用利用了 Linux 提出的管道緩沖區(qū)機制, 所以至少一個描述符要為管道。

以上幾種零拷貝技術都是減少數據在用戶空間和內核空間拷貝技術實現(xiàn)的,但是有些時候,數據必須在用戶空間和內核空間之間拷貝。

這時候,我們只能針對數據在用戶空間和內核空間拷貝的時機上下功夫了。

Linux 通常利用寫時復制(copy on write)來減少系統(tǒng)開銷,這個技術又時常稱作 COW。

“Linux零拷貝怎么實現(xiàn)”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI