分散/聚集I/O

时间:2022-12-31 08:26:07

分散/聚集I/O

分散/聚集I/O是一种可以在单词系统调用对多个缓冲区输入输出的方法,可以把多个缓冲区的数据写到单个数据流中,也可以把单个数据流读到多个缓冲区中。其命名的原因在于数据会被分散到指定缓冲区向量,或者从指定缓冲区向量中聚集数据。这种输入输出方法也称为向量I/O(vector I/O)。标准读写系统调用可以称为线性I/O(linear I/O)。

优点
  • 编码模式更自然:如果数据本身是分段的(比如预定义的结构体变量),向量I/O提供了直观的数据处理方式。
  • 效率更高:单个向量I/O操作可以取代多个线性I/O操作。
  • 性能更好:除了减少发起的系统调用次数,通过内部优化,向量I/O可以比线性I/O提供更好的性能。
  • 支持原子性:和多个线性I/O操作不同,一个进程可以执行单个向量I/O操作,避免了和其他进程交叉操作的风险。
Linux 系统调用

Linux中定义了一组实现满足上述所有特性的分散/聚集I/O的系统调用。它们分别为:

// readv()函数从文件描述符fd中读取count个段到参数iov所指定的缓冲区中。
// 返回读取的字节个数
#include <sys/uio.h>
ssize_t readv (int id,
               const struct iovec *iov,
               int count);
// writev()函数将iov指定的缓冲区中读取count个段的数据,并写入fd中。
// 返回写入的字节个数
#include <sys/uio.h>
ssize_t writev (int id,
                const struct iovec *iov,
                int count);

其中结构体iovec的定义如下:

struct iovec {
    void *iov_base;
    size_t iov_len;
};

每个段描述了内存中所要读写的缓冲区的地址和长度。readv()函数在处理下一个缓冲区之前,会填满当前缓冲区的iov_len个字节。writev()函数在处理下一个缓冲区之前,会把当前缓冲区的iov_len个字节数据输出。这两个函数都会顺序处理向量中的段,从0到count-1。

注意

  • 由于返回值类型是ssize_t,如果所有count个iov_len的和超过SSIZE_MAX,则不会处理任何数据,返回-1,并把errno值设为EINVAL。
  • POSIX指出count的值必须大于0,且小于IOV_MAX(在文件
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <cstdio>
using namespace std;

int main(int argc, char *argv[])
{
    struct iovec iov[3];
    int count = 3;

    string buffer[3] = {
        "The term buccaneer comes from the word boucan.\n",
        "A boucan is a wooden frame used for cooking meat.\n",
        "Buccaneer is the West Indives name for a pirate.\n"
    };
    int fd;
    fd = open("buccaneer.txt", O_WRONLY | O_CREAT);
    if (-1 == fd) {
        perror("open failed");
        return 1;
    }

    for (int i=0; i<3; i++) {
        iov[i].iov_len = buffer[i].size() + 1;
        iov[i].iov_base = const_cast<char *> (buffer[i].c_str());
        cout << iov[i].iov_len << endl;
    }

    ssize_t nr;
    nr = writev(fd, iov, 3);
    if (-1 == nr) {
        perror("writev error");
        return 1;
    }

    cout << "wrote " << nr << " bytes" << endl;

    if (close(fd)) {
        perror("close");
        return 1;
    }

    return 0;
}
#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;

int main(int argc, char *argv[])
{
    struct iovec iov[3];
    ssize_t nr;
    int fd;

    fd = open("buccaneer.txt", O_RDONLY);
    if (-1 == fd) {
        perror("open error");
        return 1;
    }

    iov[0].iov_base = new char[48];
    iov[0].iov_len = 48;
    iov[1].iov_base = new char[51];
    iov[1].iov_len = 51;
    iov[2].iov_base = new char[50];
    iov[2].iov_len = 50;

    // read into the structure
    nr = readv(fd, iov, 3);
    if (-1 == nr) {
        perror("readv error");
        return 1;
    }

    for (int i=0; i<3; i++) 
        cout << i << ": " << static_cast<char *> (iov[i].iov_base) << endl;

    if (close(fd)) {
        perror("close error");
        return 1;
    }

    return 0;
}