个人技术分享

使用 iovecwritev

简介

在 Unix 系统编程中,iovecwritev 提供了一种高效的方法来进行 I/O 操作,特别是在需要处理多个非连续缓冲区时。通过减少系统调用的次数,它们提高了 I/O 的性能。

iovec 结构体

iovec 是一个描述内存缓冲区的数据结构,定义在 <sys/uio.h> 头文件中。它的结构如下:

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 结构体数组的指针。
  • iovcntiovec 结构体数组中的元素数量。

writev 的实现原理

  1. 参数校验:检查 fdioviovcnt 是否有效。
  2. 锁定内存:锁定用户态的 iovec 数组。
  3. 分配内核缓冲区:分配内存用于存储数据。
  4. 数据拷贝:将数据从用户态拷贝到内核缓冲区。
  5. 实际写操作:调用文件系统或设备驱动的写操作。
  6. 清理和返回:释放内存并返回写入的字节数。

使用示例

以下是一个使用 writev 的示例:

#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>

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 <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>

#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;
}

资源管理

确保应用程序在使用系统资源时进行适当管理,避免一次性占用过多资源,导致系统资源不足或性能下降。

结论

通过合理使用 iovecwritev,可以有效提高 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_operationswrite_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 系统调用从用户态到内核态的实现过程涉及多个层次:

  1. 用户态调用 writev 系统调用。
  2. 内核态的 sys_writev 函数处理参数并调用 vfs_writev
  3. vfs_writev 调用具体文件系统或设备驱动的 write_iter 方法。
  4. 具体文件系统或设备驱动的 write_iter 方法实现数据的实际写入。

通过这种分层结构,Linux 内核实现了文件操作的高度可扩展性和可移植性。