分散/聚集 I/O(scatter-gather I/O)

分類:IT技術 時間:2016-10-09

概述

分散/聚集 I/O是一種可以在單次系統調用中對多個緩沖區輸入輸出的方法,可以把多個緩沖區的數據寫到單個數據流,也可以把單個數據流讀到多個緩沖區中。其命名的原 因在於數據會被分散到指定緩沖區向量,或者從指定緩沖區向量中聚集數據。這種輸入輸出方法也稱為向量 I/O(vector I/O)。與之不同,標準讀寫系統調用(read,write)可以稱為線性I/O(linear I/O)。

與線性 I/O 相比,分散/聚集 I/O 有如下幾個優勢:

編碼模式更自然

如果數據本身是分段的(比如預定義的結構體的變量),向量 I/O 提供了直觀的數據處理方式。

效率更高

單個向量 I/O 操作可以取代多個線性 I/O 操作。

性能更好

除了減少了發起的系統調用次數,通過內部優化,向量 I/O 可以比線性 I/O 提供更好的性能。

支持原子性

和多個線性 I/O 操作不同,一個進程可以執行單個向量 I/O 操作,避免了和其他進程交叉操作的風險。

readv() 和 writev()

linux實現了POSIX 1003.1-2001中定義的一組實現分散/聚集 I/O機制的系統調用。該實現滿足了上面所述的所有特性。

readv() 函數從文件描述符 fd 中讀取 count 個段 (segment) (一個段即一個 iovec 結構體)到參數 iov 所指定的緩沖區中:

#include <sys/uio.h>

ssize_t readv (int fd,

                       const struct iovec *iov,

                       int count)

writev() 函數從參數 iov 指定的緩沖區中讀取 count 個段的數據,並寫入 fd 中:

#include <sys/uio.h>

ssize_t writev(int fd,

                       const struct iovec *iov,

                       int count)

 除了同時操作多個緩沖區外,readv() 函數和 writev() 函數的功能分別和 read(),write() 的功能一致。

每個 iovec 結構體描述一個獨立的,物理不連續的緩沖區,我們稱其為段(segment):

#include <sys/uio.h>

struct iovec {

       void      *iov_base;/* pointer to start of buffer */

       size_t   iov_len;/* size of buffer in bytes */

};

一組段的集合稱為向量 (vector)。每個段描述了內存中所要讀寫的緩沖區的地址和長度。readv() 函數在處理下個緩沖區之前,會填滿當前緩沖區的 iov_len 個字節。write() 函數在處理下個緩沖區之前,會把當前緩沖區所有 iov_len 個字節數據輸出,這兩個函數都會順序處理向量中的段,從 iov[0] 開始,接著是 iov[1],一直到 iov[count - 1] 。

返回值

操作成功 時,readv() 函數和 write() 函數分別返回讀寫的字節數。該返回值應該等於所有 count 個 iov_len 的和。出錯時,返回-1,並相應設置errno值。這些系統調用可能會返回任何 read() 和 write() 可能返回的錯誤,而且出錯時,設置的 errno 值也與 read(), write() 相同。此外,標準還定義了另外兩種錯誤場景。

 

第一種場景,由於返回值類型是 ssize_t , 如果所有 count 個iov_len 的和超出SSIZE_MAX, 則不會處理任何數據,返回-1,並把errno值設置為EINVAL。

 

第二種場景,POSIX 指出count值必須大於0,且小於等於IOV_MAX(IOV_MAX在文件<limits.h>定義。在Linux中,當前 IOV_MAX的值是1024。如果count為0,該系統調用會返回0。如果count大於IOV_MAX,不會處理任何數據,返回-1,並把 errno值設置為EINVAL。

 

優化count值

在向量 I/O 操作中,Linux內核必須分配內部數據結構來表示每個段(segment)。一般來說,是基於count的大小動態分配進行的。然而,為了優化,如果 count值足夠小,內核會在棧上創建一個很小的段數組,通過避免動態分配段內存,從而獲得性能上的一些提升。count 的閥值一般設置為8,,因此如果count值小於或等於8時,向量I/O操作會以一種高效的方式,在進程的內核棧中運行。

 

大多數情況下,無法選擇在指定的向量I/O操作中一次同時傳遞多少個段。當你認為可以調試一個較小值時,選擇8或更小的值肯定會得到性能的提升。

 

Linux內核把readv() 和writev() 作為系統調用實現,在內部使用分散/聚集 I/O模式。實際上,Linux內核中的所有I/O都是向量I/O,read() 和 write() 是作為向量 I/O來實現的,且向量中只有一個段。

 

例子

writev() 例子:

#include <stdio.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <string.h>  
#include <sys/uio.h>  
  
int  main()  
{  
    struct iovec iov[3];  
    ssize_t nr;  
    int fd, i;  
  
    char *buf[] = {  
        "Just because you can do it, doesn't mean that you have to.\n",  
        "Just because you can do it, doesn't mean that you have to.\n",  
        "Just because you can do it, doesn't mean that you have to.\n" };  
  
    fd = open("c++.txt", O_WRONLY | O_CREAT | O_TRUNC);  
    if (fd == -1) {  
        perror("open");  
    }  
  
    /* fill out therr iovec structures */  
    for (i = 0; i < 3; ++i) {  
        iov[i].iov_base = buf[i];  
        iov[i].iov_len  = strlen(buf[i]) + 1;  
    }  
  
    /* write a single call, write them all out */  
    nr = writev(fd, iov, 3);  
    if (nr != -1) {  
        perror("writev");  
        return 1;  
    }  
  
    if (close(fd)) {  
        perror("close");  
    }  
  
    return 0;  
}  

 

readv() 例子:

#include <stdio.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <sys/uio.h>  
  
int main()  
{  
    char foo[48], bar[50], baz[49];  
    struct iovec iov[3];  
    ssize_t nr;  
    int fd, i;  
  
    fd = open("c++.txt", O_RDONLY);  
    if (fd == -1) {  
        perror("open");  
        return 1;  
    }  
  
    /* set up our iovec structrues */  
    iov[0].iov_base = foo;  
    iov[0].iov_len = sizeof(foo);  
    iov[1].iov_base = bar;  
    iov[1].iov_len = sizeof(bar);  
    iov[2].iov_base = baz;  
    iov[2].iov_len = sizeof(baz);  
  
    /* read into the structures with a single call */  
    nr = readv(fd, iov, 3);  
    if (nr == -1) {  
        perror("readv");  
        return 1;  
    }  
  
    for (i = 0; i < 3; ++i) {  
        printf("%d: %s", i, (char*) iov[i].iov_base);  
    }  
  
    if (close(fd)) {  
        perror("close");  
        return 1;  
    }  
  
    return 0;  
} 

Tags: include 緩沖區 結構體 數據流 Linux

文章來源:


ads
ads

相關文章
ads

相關文章

ad