使用 iovec 和 writev
简介
在 Unix 系统编程中,iovec 和 writev 提供了一种高效的方法来进行 I/O 操作,特别是在需要处理多个非连续缓冲区时。通过减少系统调用的次数,它们提高了 I/O 的性能。
iovec 结构体
iovec 是一个描述内存缓冲区的数据结构,定义在
struct iovec { void *iov_base; /* Pointer to data */ size_t iov_len; /* Length of data */ };
- iov_base 是指向数据的指针。
- iov_len 是数据的长度。
writev 系统调用
writev 是一种向文件描述符写入数据的系统调用,它允许从多个缓冲区一次性写入数据。其原型如下:
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
- fd:文件描述符,表示要写入的目标。
- iov:指向 iovec 结构体数组的指针。
- iovcnt:iovec 结构体数组中的元素数量。
writev 的实现原理
- 参数校验:检查 fd、iov 和 iovcnt 是否有效。
- 锁定内存:锁定用户态的 iovec 数组。
- 分配内核缓冲区:分配内存用于存储数据。
- 数据拷贝:将数据从用户态拷贝到内核缓冲区。
- 实际写操作:调用文件系统或设备驱动的写操作。
- 清理和返回:释放内存并返回写入的字节数。
使用示例
以下是一个使用 writev 的示例:
#include
#include #include #include #include int main() { int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror("open"); return -1; } struct iovec iov[2]; char *buf1 = "Hello, "; char *buf2 = "world!\n"; iov[0].iov_base = buf1; iov[0].iov_len = strlen(buf1); iov[1].iov_base = buf2; iov[1].iov_len = strlen(buf2); ssize_t nwritten = writev(fd, iov, 2); if (nwritten == -1) { perror("writev"); close(fd); return -1; } printf("Wrote %zd bytes\n", nwritten); close(fd); return 0; } 优化技术
分批处理
当需要处理大量数据时,可以将数据分成多个批次,以避免 iovec 数量超过系统限制。例如:
#include
#include #include #include #include #define MAX_IOVEC 1024 int main() { int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror("open"); return -1; } struct iovec iov[MAX_IOVEC]; int total_buffers = 5000; char *buffers[5000]; for (int i = 0; i < total_buffers; ++i) { buffers[i] = "data\n"; } for (int i = 0; i < total_buffers; i += MAX_IOVEC) { int batch_size = (total_buffers - i > MAX_IOVEC) ? MAX_IOVEC : (total_buffers - i); for (int j = 0; j < batch_size; ++j) { iov[j].iov_base = buffers[i + j]; iov[j].iov_len = strlen(buffers[i + j]); } ssize_t nwritten = writev(fd, iov, batch_size); if (nwritten == -1) { perror("writev"); close(fd); return -1; } } close(fd); return 0; } 资源管理
确保应用程序在使用系统资源时进行适当管理,避免一次性占用过多资源,导致系统资源不足或性能下降。
结论
通过合理使用 iovec 和 writev,可以有效提高 I/O 操作的性能,特别是在需要处理多个非连续缓冲区时。理解其实现原理和限制,有助于更好地优化应用程序的 I/O 性能。
Linux Kernel 中 writev 的实现
在 Linux 内核中,writev 的实现涉及多个层次,包括系统调用的入口、文件操作接口(file_operations)以及具体文件系统或设备驱动的实现。
系统调用入口
writev 系统调用的入口函数在内核中通常是 sys_writev。该函数首先会对传入的参数进行校验,然后调用内核内部的通用写入函数。
asmlinkage ssize_t sys_writev(unsigned long fd, const struct iovec __user *vec, unsigned long vlen) { ssize_t ret; struct file *file; struct iovec *iov = NULL; struct iov_iter iter; if (vlen > UIO_MAXIOV) return -EINVAL; ret = import_iovec(WRITE, vec, vlen, UIO_FASTIOV, &iov, &iter); if (ret < 0) return ret; file = fget(fd); if (file) { ret = vfs_writev(file, &iter, &pos, 0); fput(file); } else { ret = -EBADF; } kfree(iov); return ret; }
文件操作接口
sys_writev 最终调用的是 vfs_writev,后者是内核虚拟文件系统(VFS)层的函数。vfs_writev 会根据文件描述符对应的文件类型,调用具体文件系统或设备驱动的 write_iter 方法。
ssize_t vfs_writev(struct file *file, const struct iov_iter *iter, loff_t *pos, rwf_t flags) { if (file->f_op->write_iter) return file->f_op->write_iter(file, iter); return do_iter_write(file, iter, pos); }
file_operations 的 write_iter 方法
具体文件系统或设备驱动需要实现 file_operations 结构体中的 write_iter 方法。例如,对于 ext4 文件系统,这个方法如下:
const struct file_operations ext4_file_operations = { .read_iter = generic_file_read_iter, .write_iter = ext4_file_write_iter, ... };
ext4_file_write_iter 的实现如下:
ssize_t ext4_file_write_iter(struct kiocb *iocb, struct iov_iter *from) { return generic_file_write_iter(iocb, from); }
generic_file_write_iter 是一个通用的写入函数,它处理大部分通用逻辑,然后调用底层文件系统的具体写入操作。
底层实现
底层文件系统或设备驱动需要实现具体的写入操作,这通常涉及到将数据写入磁盘或其他存储介质。例如,对于块设备驱动,这个操作可能会涉及到直接与硬件交互。
小结
writev 系统调用从用户态到内核态的实现过程涉及多个层次:
- 用户态调用 writev 系统调用。
- 内核态的 sys_writev 函数处理参数并调用 vfs_writev。
- vfs_writev 调用具体文件系统或设备驱动的 write_iter 方法。
- 具体文件系统或设备驱动的 write_iter 方法实现数据的实际写入。
通过这种分层结构,Linux 内核实现了文件操作的高度可扩展性和可移植性。
还没有评论,来说两句吧...