Linux:何时使用散列/聚集IO(readv,writev)而不是使用fread的大缓冲区

89
scattergather(即 readvwritev)中,Linux 会读取多个缓冲区并从多个缓冲区写入。
例如,如果我有一个包含 3 个缓冲区的向量,我可以使用 readv,也可以使用一个单独的缓冲区,该缓冲区的大小等于 3 个缓冲区的总大小,并使用 fread
因此,我很困惑:应该在哪些情况下使用 scatter/gather,并且何时应该使用单个大缓冲区?

另一个你可能想考虑使用散列-聚合I/O的原因是当文件的文件描述符表条目被共享时(导致相同的寻址位置)。如果两个文件描述符由不同的进程/线程等更新,则可能会导致竞争条件。复制文件描述符可能是由进程fork或dup*系统调用引起的。 - sudeepdino008
1个回答

127
readv, writev 主要提供以下便利:
  1. 允许处理非连续的数据块,即缓冲区不需要是数组的一部分,而是单独分配的。
  2. 输入/输出(I/O)是“原子性”的。也就是说,如果您执行一个 writev 操作,向量中的所有元素将在一个连续的操作中被写入,并且其他进程执行的写操作不会发生在它们之间。
例如,假设您的数据自然地分段并来自不同的来源:
struct foo *my_foo;
struct bar *my_bar;
struct baz *my_baz;

my_foo = get_my_foo();
my_bar = get_my_bar();
my_baz = get_my_baz();

现在,这三个“缓冲区”都不是一个大的连续块。但是你想将它们连续地写入文件,出于某种原因(比如说,它们是文件格式的文件头中的字段)。

如果你使用 write ,你必须选择:

  1. 使用例如 memcpy 将它们复制到一个内存块中(开销),然后进行一次单独的 write 调用。那么写操作就是原子的。
  2. 进行三次单独的 write 调用(开销)。此外,来自其他进程的 write 调用可以插在这些写操作之间(不是原子的)。

如果你改用 writev,那就没问题了:

  1. 你只需要进行一次系统调用,并且不需要使用 memcpy 就可以从这三个缓冲区创建一个单一的缓冲区。
  2. 另外,这三个缓冲区作为一个块被原子性地写入。即使其他进程也在写入,这些写操作也不会在这三个向量的写操作之间发生。

所以你可以做如下操作:

struct iovec iov[3];

iov[0].iov_base = my_foo;
iov[0].iov_len = sizeof (struct foo);
iov[1].iov_base = my_bar;
iov[1].iov_len = sizeof (struct bar);
iov[2].iov_base = my_baz;
iov[2].iov_len = sizeof (struct baz);

bytes_written = writev (fd, iov, 3);

Sources:

  1. http://pubs.opengroup.org/onlinepubs/009604499/functions/writev.html
  2. http://linux.die.net/man/2/readv

4
在《Linux系统编程》一书中,他们说readvwritev可以遇到read()write()系统调用中的任何错误,并在接收到这些错误时设置相同的errno代码。那么readv会返回EINTR吗?如果在readv的原子读取期间发生信号会发生什么?它会被忽略还是排队处理? - nmxprime
@nmxprime 如果在 readv()writev() 过程中接收到信号,这些系统调用(取决于 SA_RESTART)将返回比请求的字节数少的数据。 - socketpair
我可以以非连续的方式将非连续缓冲区写入文件,而不是作为一个整块吗? - Shubham Pendharkar
使用writev和使用带有“MSG_MORE”标志的send有什么区别?使用writev是否更有利,因为它可以减少系统调用并实现原子写入? - bgura
1
@bvb:我不清楚你在问什么,但如果你在问为什么writev比write更有效率:这是因为writev只需要一个单一的上下文切换到内核模式。而且由于每个切换大约需要一微秒,一个向量大小为1,000的writev可能只需要1微秒,而1,000个写操作则需要1毫秒。 - Jay Sullivan
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接