readv(2) - Linux man page
名称
readv, writev, preadv, pwritev - 读取或写入多个缓冲区的数据
概要
#include <sys/uio.h> ssize_t readv(int fd, const struct iovec *iov, int iovcnt); ssize_t writev(int fd, const struct iovec *iov, int iovcnt); ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset); ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);glibc 的功能测试宏要求(参见 feature_test_macros(7))
- preadv(), pwritev(): _BSD_SOURCE
描述
readv() 系统调用从与文件描述符 fd 关联的文件中读取 iovcnt 个缓冲区,并将数据读入由 iov 描述的缓冲区中(“分散输入”)。
writev() 系统调用将由 iov 描述的 iovcnt 个缓冲区的数据写入与文件描述符 fd 关联的文件中(“集中输出”)。
指针 iov 指向一个 iovec 结构的数组,该结构在 <sys/uio.h> 中定义如下
-
struct iovec { void *iov_base; /* Starting address */ size_t iov_len; /* Number of bytes to transfer */ }; - readv() 系统调用的工作方式与 read(2) 相同,只是会填充多个缓冲区。
writev() 系统调用的工作方式与 write(2) 相同,只是会写入多个缓冲区。
缓冲区按数组顺序处理。这意味着 readv() 会先完全填充 iov[0],然后才处理 iov[1],依此类推。(如果数据不足,则可能无法填充 iov 指向的所有缓冲区。) 同样,writev() 会先写入 iov[0] 的所有内容,然后才处理 iov[1],依此类推。
readv() 和 writev() 执行的数据传输是原子的:writev() 写入的数据作为一个单独的块写入,不会与其他进程中的写入输出混合(但请参阅 pipe(7) 以获取例外情况);类似地,readv() 保证从文件中读取一个连续的数据块,无论其他线程或进程执行的读取操作如何,这些线程或进程都具有引用相同打开文件描述符的文件描述符(请参阅 open(2))。
preadv() 和 pwritev()
- preadv() 系统调用结合了 readv() 和 pread(2) 的功能。它执行与 readv() 相同的任务,但添加了第四个参数 offset,它指定要执行输入操作的文件偏移量。
pwritev() 系统调用结合了 writev() 和 pwrite(2) 的功能。它执行与 writev() 相同的任务,但添加了第四个参数 offset,它指定要执行输出操作的文件偏移量。
这些系统调用不会更改文件偏移量。与 fd 关联的文件必须能够进行寻址。
返回值
成功时,readv() 和 preadv() 返回读取的字节数;writev() 和 pwritev() 返回写入的字节数。发生错误时,返回 -1,并且 errno 会被设置为适当的值。
错误
错误与 read(2) 和 write(2) 中给出的错误相同。此外,preadv() 和 pwritev() 也可能由于与 lseek(2) 相同的原因而失败。 此外,定义了以下错误
- EINVAL
iov_len 值的总和溢出了 ssize_t 值。或者,向量计数 iovcnt 小于零或大于允许的最大值。
版本
preadv() 和 pwritev() 首先出现在 Linux 2.6.30 中;库支持是在 glibc 2.10 中添加的。
符合
readv(), writev(): 4.4BSD (这些系统调用首次出现在 4.2BSD 中), POSIX.1-2001. Linux libc5 使用 size_t 作为 iovcnt 参数的类型,并使用 int 作为返回值类型。
preadv(), pwritev(): 非标准,但现代 BSD 系统中也存在。
说明
Linux 注意
- POSIX.1-2001 允许实现对 iov 中传递的项目数量设置限制。实现可以通过在 <limits.h> 中定义 IOV_MAX,或者通过 sysconf(_SC_IOV_MAX) 的返回值来声明其限制。在 Linux 上,这些机制声明的限制是 1024,这正是内核的真实限制。但是,glibc 包装函数如果检测到底层内核系统调用失败是因为超过了这个限制,会执行一些额外的工作。对于 readv(),包装函数会分配一个足够大的临时缓冲区来容纳 iov 指定的所有项目,然后将该缓冲区传递给 read(2),将数据从缓冲区复制到 iov 元素的 iov_base 字段指定的位置,然后释放缓冲区。writev() 的包装函数使用临时缓冲区和对 write(2) 的调用执行类似的任务。
错误
不建议将对操作文件描述符的 readv() 或 writev() 调用与 stdio 库中的函数混合使用;结果将是不确定的,可能不是您想要的。
示例
以下代码示例演示了 writev() 的使用
-
char *str0 = "hello "; char *str1 = "world\n"; struct iovec iov[2]; ssize_t nwritten; iov[0].iov_base = str0; iov[0].iov_len = strlen(str0); iov[1].iov_base = str1; iov[1].iov_len = strlen(str1); nwritten = writev(STDOUT_FILENO, iov, 2);