Linux 中直接 I/O 機制的介紹

直接 I/O 的動機

在介紹直接 I/O 之前,這一小節先介紹一下為什么會出現直接 I/O 這種機制,即傳統的 I/O 操作存在哪些缺點。

什么是緩存 I/O (Buffered I/O)

緩存 I/O 又被稱作標準 I/O,大多數文件系統的默認 I/O 操作都是緩存 I/O。在 Linux 的緩存 I/O 機制中,操作系統會將 I/O 的數據緩存在文件系統的頁緩存( page cache )中,也就是說,數據會先被拷貝到操作系統內核的緩沖區中,然后才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。緩存 I/O 有以下這些優點:

  • 緩存 I/O 使用了操作系統內核緩沖區,在一定程度上分離了應用程序空間和實際的物理設備。
  • 緩存 I/O 可以減少讀盤的次數,從而提高性能。

當應用程序嘗試讀取某塊數據的時候,如果這塊數據已經存放在了頁緩存中,那么這塊數據就可以立即返回給應用程序,而不需要經過實際的物理讀盤操作。當然,如果數據在應用程序讀取之前并未被存放在頁緩存中,那么就需要先將數據從磁盤讀到頁緩存中去。對于寫操作來說,應用程序也會將數據先寫到頁緩存中去,數據是否被立即寫到磁盤上去取決于應用程序所采用的寫操作機制:如果用戶采用的是同步寫機制( synchronous writes ), 那么數據會立即被寫回到磁盤上,應用程序會一直等到數據被寫完為止;如果用戶采用的是延遲寫機制( deferred writes ),那么應用程序就完全不需要等到數據全部被寫回到磁盤,數據只要被寫到頁緩存中去就可以了。在延遲寫機制的情況下,操作系統會定期地將放在頁緩存中的數據刷到磁盤上。與異步寫機制( asynchronous writes )不同的是,延遲寫機制在數據完全寫到磁盤上的時候不會通知應用程序,而異步寫機制在數據完全寫到磁盤上的時候是會返回給應用程序的。所以延遲寫機制本身是存在數據丟失的風險的,而異步寫機制則不會有這方面的擔心。

緩存 I/O 的缺點

在緩存 I/O 機制中,DMA 方式可以將數據直接從磁盤讀到頁緩存中,或者將數據從頁緩存直接寫回到磁盤上,而不能直接在應用程序地址空間和磁盤之間進行數據傳輸,這樣的話,數據在傳輸過程中需要在應用程序地址空間和頁緩存之間進行多次數據拷貝操作,這些數據拷貝操作所帶來的 CPU 以及內存開銷是非常大的。

對于某些特殊的應用程序來說,避開操作系統內核緩沖區而直接在應用程序地址空間和磁盤之間傳輸數據會比使用操作系統內核緩沖區獲取更好的性能,下邊這一小節中提到的自緩存應用程序就是其中的一種。

自緩存應用程序( self-caching applications)

對于某些應用程序來說,它會有它自己的數據緩存機制,比如,它會將數據緩存在應用程序地址空間,這類應用程序完全不需要使用操作系統內核中的高速緩沖存儲器,這類應用程序就被稱作是自緩存應用程序( self-caching applications )。數據庫管理系統是這類應用程序的一個代表。自緩存應用程序傾向于使用數據的邏輯表達方式,而非物理表達方式;當系統內存較低的時候,自緩存應用程序會讓這種數據的邏輯緩存被換出,而并非是磁盤上實際的數據被換出。自緩存應用程序對要操作的數據的語義了如指掌,所以它可以采用更加高效的緩存替換算法。自緩存應用程序有可能會在多臺主機之間共享一塊內存,那么自緩存應用程序就需要提供一種能夠有效地將用戶地址空間的緩存數據置為無效的機制,從而確保應用程序地址空間緩存數據的一致性。

對于自緩存應用程序來說,緩存 I/O 明顯不是一個好的選擇。由此引出我們這篇文章著重要介紹的 Linux 中的直接 I/O 技術。Linux 中的直接 I/O 技術非常適用于自緩存這類應用程序,該技術省略掉緩存 I/O 技術中操作系統內核緩沖區的使用,數據直接在應用程序地址空間和磁盤之間進行傳輸,從而使得自緩存應用程序可以省略掉復雜的系統級別的緩存結構,而執行程序自己定義的數據讀寫管理,從而降低系統級別的管理對應用程序訪問數據的影響。在下面一節中,我們會著重介紹 Linux 中提供的直接 I/O 機制的設計與實現,該機制為自緩存應用程序提供了很好的支持。

Linux 2.6 中的直接 I/O 技術

Linux 2.6 中提供的幾種文件訪問方式

所有的 I/O 操作都是通過讀文件或者寫文件來完成的。在這里,我們把所有的外圍設備,包括鍵盤和顯示器,都看成是文件系統中的文件。訪問文件的方法多種多樣,這里列出下邊這幾種 Linux 2.6 中支持的文件訪問方式。

標準訪問文件的方式

在 Linux 中,這種訪問文件的方式是通過兩個系統調用實現的:read() 和 write()。當應用程序調用 read() 系統調用讀取一塊數據的時候,如果該塊數據已經在內存中了,那么就直接從內存中讀出該數據并返回給應用程序;如果該塊數據不在內存中,那么數據會被從磁盤上讀到頁高緩存中去,然后再從頁緩存中拷貝到用戶地址空間中去。如果一個進程讀取某個文件,那么其他進程就都不可以讀取或者更改該文件;對于寫數據操作來說,當一個進程調用了 write() 系統調用往某個文件中寫數據的時候,數據會先從用戶地址空間拷貝到操作系統內核地址空間的頁緩存中去,然后才被寫到磁盤上。但是對于這種標準的訪問文件的方式來說,在數據被寫到頁緩存中的時候,write() 系統調用就算執行完成,并不會等數據完全寫入到磁盤上。Linux 在這里采用的是我們前邊提到的延遲寫機制( deferred writes )。

同步訪問文件的方式

同步訪問文件的方式與上邊這種標準的訪問文件的方式比較類似,這兩種方法一個很關鍵的區別就是:同步訪問文件的時候,寫數據的操作是在數據完全被寫回磁盤上才算完成的;而標準訪問文件方式的寫數據操作是在數據被寫到頁高速緩沖存儲器中的時候就算執行完成了。

內存映射方式

在很多操作系統包括 Linux 中,內存區域( memory region )是可以跟一個普通的文件或者塊設備文件的某一個部分關聯起來的,若進程要訪問內存頁中某個字節的數據,操作系統就會將訪問該內存區域的操作轉換為相應的訪問文件的某個字節的操作。Linux 中提供了系統調用 mmap() 來實現這種文件訪問方式。與標準的訪問文件的方式相比,內存映射方式可以減少標準訪問文件方式中 read() 系統調用所帶來的數據拷貝操作,即減少數據在用戶地址空間和操作系統內核地址空間之間的拷貝操作。映射通常適用于較大范圍,對于相同長度的數據來講,映射所帶來的開銷遠遠低于 CPU 拷貝所帶來的開銷。當大量數據需要傳輸的時候,采用內存映射方式去訪問文件會獲得比較好的效率。

直接 I/O 方式

凡是通過直接 I/O 方式進行數據傳輸,數據均直接在用戶地址空間的緩沖區和磁盤之間直接進行傳輸,完全不需要頁緩存的支持。操作系統層提供的緩存往往會使應用程序在讀寫數據的時候獲得更好的性能,但是對于某些特殊的應用程序,比如說數據庫管理系統這類應用,他們更傾向于選擇他們自己的緩存機制,因為數據庫管理系統往往比操作系統更了解數據庫中存放的數據,數據庫管理系統可以提供一種更加有效的緩存機制來提高數據庫中數據的存取性能。

數據傳輸不經過操作系統內核緩沖區

異步訪問文件的方式

Linux 異步 I/O 是 Linux 2.6 中的一個標準特性,其本質思想就是進程發出數據傳輸請求之后,進程不會被阻塞,也不用等待任何操作完成,進程可以在數據傳輸的時候繼續執行其他的操作。相對于同步訪問文件的方式來說,異步訪問文件的方式可以提高應用程序的效率,并且提高系統資源利用率。直接 I/O 經常會和異步訪問文件的方式結合在一起使用。

CPU 處理其他任務和 I/O 操作可以重疊執行

Linux 2.6 中直接 I/O 的設計與實現

在塊設備或者網絡設備中執行直接 I/O 完全不用擔心實現直接 I/O 的問題,Linux 2.6 操作系統內核中高層代碼已經設置和使用了直接 I/O,驅動程序級別的代碼甚至不需要知道已經執行了直接 I/O;但是對于字符設備來說,執行直接 I/O 是不可行的,Linux 2.6 提供了函數 get_user_pages() 用于實現直接 I/O。本小節會分別對這兩種情況進行介紹。

內核為塊設備執行直接 I/O 提供的支持

要在塊設備中執行直接 I/O,進程必須在打開文件的時候設置對文件的訪問模式為 O_DIRECT,這樣就等于告訴操作系統進程在接下來使用 read() 或者 write() 系統調用去讀寫文件的時候使用的是直接 I/O 方式,所傳輸的數據均不經過操作系統內核緩存空間。使用直接 I/O 讀寫數據必須要注意緩沖區對齊( buffer alignment )以及緩沖區的大小的問題,即對應 read() 以及 write() 系統調用的第二個和第三個參數。這里邊說的對齊指的是文件系統塊大小的對齊,緩沖區的大小也必須是該塊大小的整數倍。

這一節主要介紹三個函數:open(),read() 以及 write()。Linux 中訪問文件具有多樣性,所以這三個函數對于處理不同的文件訪問方式定義了不同的處理方法,本文主要介紹其與直接 I/O 方式相關的函數與功能.首先,先來看 open() 系統調用,其函數原型如下所示:

int open(const char *pathname, int oflag, … /*, mode_t mode * / ) ;

當應用程序需要直接訪問文件而不經過操作系統頁高速緩沖存儲器的時候,它打開文件的時候需要指定 O_DIRECT 標識符。

操作系統內核中處理 open() 系統調用的內核函數是 sys_open(),sys_open() 會調用 do_sys_open() 去處理主要的打開操作。它主要做了三件事情:首先, 它調用 getname() 從進程地址空間中讀取文件的路徑名;接著,do_sys_open() 調用 get_unused_fd() 從進程的文件表中找到一個空閑的文件表指針,相應的新文件描述符就存放在本地變量 fd 中;之后,函數 do_filp_open() 會根據傳入的參數去執行相應的打開操作。清單 1 列出了操作系統內核中處理 open() 系統調用的一個主要函數關系圖。

sys_open() 
  |-----do_sys_open() 
         |---------getname() 
         |---------get_unused_fd() 
         |---------do_filp_open() 
                    |--------nameidata_to_filp() 
                              |----------__dentry_open()

函數 do_flip_open() 在執行的過程中會調用函數 nameidata_to_filp(),而 nameidata_to_filp() 最終會調用 __dentry_open() 函數,若進程指定了 O_DIRECT 標識符,則該函數會檢查直接 I./O 操作是否可以作用于該文件。清單 2 列出了 __dentry_open() 函數中與直接 I/O 操作相關的代碼。

if (f->f_flags & O_DIRECT) { 
    if (!f->f_mapping->a_ops || 
       ((!f->f_mapping->a_ops->direct_IO) && 
       (!f->f_mapping->a_ops->get_xip_page))) { 
        fput(f); 
        f = ERR_PTR(-EINVAL); 
    } 
}

當文件打開時指定了 O_DIRECT 標識符,那么操作系統就會知道接下來對文件的讀或者寫操作都是要使用直接 I/O 方式的。

下邊我們來看一下當進程通過 read() 系統調用讀取一個已經設置了 O_DIRECT 標識符的文件的時候,系統都做了哪些處理。 函數 read() 的原型如下所示:

ssize_t read(int feledes, void *buff, size_t nbytes) ;

操作系統中處理 read() 函數的入口函數是 sys_read(),其主要的調用函數關系圖如下清單 3 所示:

sys_read() 
  |-----vfs_read() 
       |----generic_file_read() 
             |----generic_file_aio_read() 
                  |--------- generic_file_direct_IO()

直接 I/O 技術的特點

直接 I/O 的優點

直接 I/O 最主要的優點就是通過減少操作系統內核緩沖區和應用程序地址空間的數據拷貝次數,降低了對文件讀取和寫入時所帶來的 CPU 的使用以及內存帶寬的占用。這對于某些特殊的應用程序,比如自緩存應用程序來說,不失為一種好的選擇。如果要傳輸的數據量很大,使用直接 I/O 的方式進行數據傳輸,而不需要操作系統內核地址空間拷貝數據操作的參與,這將會大大提高性能。

直接 I/O 潛在可能存在的問題

直接 I/O 并不一定總能提供令人滿意的性能上的飛躍。設置直接 I/O 的開銷非常大,而直接 I/O 又不能提供緩存 I/O 的優勢。緩存 I/O 的讀操作可以從高速緩沖存儲器中獲取數據,而直接 I/O 的讀數據操作會造成磁盤的同步讀,這會帶來性能上的差異 , 并且導致進程需要較長的時間才能執行完;對于寫數據操作來說,使用直接 I/O 需要 write() 系統調用同步執行,否則應用程序將會不知道什么時候才能夠再次使用它的 I/O 緩沖區。與直接 I/O 讀操作類似的是,直接 I/O 寫操作也會導致應用程序關閉緩慢。所以,應用程序使用直接 I/O 進行數據傳輸的時候通常會和使用異步 I/O 結合使用。

總結

Linux 中的直接 I/O 訪問文件方式可以減少 CPU 的使用率以及內存帶寬的占用,但是直接 I/O 有時候也會對性能產生負面影響。所以在使用直接 I/O 之前一定要對應用程序有一個很清醒的認識,只有在確定了設置緩沖 I/O 的開銷非常巨大的情況下,才考慮使用直接 I/O。直接 I/O 經常需要跟異步 I/O 結合起來使用,本文對異步 I/O 沒有作詳細介紹,有興趣的讀者可以參看 Linux 2.6 中相關的文檔介紹

原文

https://www.ibm.com/developerworks/cn/linux/l-cn-directio/index.html

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容