Linux基础 - 使用 `iovec` 和 `writev` 的技术整理

Linux基础 - 使用 `iovec` 和 `writev` 的技术整理

码农世界 2024-05-23 前端 68 次浏览 0个评论

使用 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 的实现原理

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

      使用示例

      以下是一个使用 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 系统调用从用户态到内核态的实现过程涉及多个层次:

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

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

转载请注明来自码农世界,本文标题:《Linux基础 - 使用 `iovec` 和 `writev` 的技术整理》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,68人围观)参与讨论

还没有评论,来说两句吧...

Top