Linux中的高级IO函数(二)readv writev sendfile mmap splice tee

news/2024/11/19 11:34:58/

Linux提供了很多高级的I/O函数。它们并不像Linux基础I/O函数(比如open和read)那么常用(编写内核模块时一般要实现这些I/O函数),但在特定的条件下却表现出优秀的性能。这些函数大致分为三类:

用于创建文件描述符的函数,包括pipe、socketpair、dup/dup2函数。
用于读写数据的函数,包括readv/writev、sendfile、mmap/munmap、splice和tee函数。
用于控制I/O行为和属性的函数,包括fcntl函数。

本节接着介绍第二类

一、readv函数与writev函数

readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数则将多块分散的内存数据一并写入文件描述符中,即集中写。它们的定义如下:

#include <sys/uio.h>ssize_t readv(int fd, const struct iovec* vector, int count)ssize_t writev(int fd, const struct iovec* vector, int count);
  • fd:文件描述符,用于指定读取或写入数据的文件或套接字。
  • vector:一个指向 iovec 结构体数组的指针,每个结构体描述一个缓冲区的位置和长度。
  • countiovec 结构体数组的长度,即缓冲区的数量。
  • 成功时返回操作的字节数,失败时返回-1并且置errno

struct iovec 结构体定义:

struct iovec {void  *iov_base; // 缓冲区的起始地址size_t iov_len;  // 缓冲区的长度
};

举个分散写的例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <unistd.h>int main() {// 打开文件int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);// 准备多个缓冲区char buf1[] = "This is the first buffer.\n";char buf2[] = "This is the second buffer.\n";char buf3[] = "This is the third buffer.\n";// 构建 struct iovec 数组struct iovec iov[3];iov[0].iov_base = buf1;iov[0].iov_len = strlen(buf1);iov[1].iov_base = buf2;iov[1].iov_len = strlen(buf2);iov[2].iov_base = buf3;iov[2].iov_len = strlen(buf3);// 使用 writev() 将多个缓冲区的内容一次性写入文件ssize_t bytes_written = writev(fd, iov, 3);printf("Total bytes written: %zd\n", bytes_written);// 关闭文件close(fd);return 0;
}

image-20240422111634350

二、sendfile函数

sendfile函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝。

#include <sys/sendfile.h>ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
  • out_fd:输出文件描述符,用于指定要写入数据的目标文件。
  • in_fd:输入文件描述符,用于指定从中读取数据的源文件。
  • offset:用于指定输入文件中的起始位置,如果为 NULL,表示从当前文件偏移量开始读取。
  • count:要传输的字节数。
  • sendfile成功时返回传输的字节数,失败则返回-1并设置errno。

这个函数就很简单,主要是因为他的高效率,不需要在用户空间操作。所以我们就不举例子了。

三、mmap函数与munmap函数

#include <sys/mman.h>void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void* start, size_t length);
  • start:指定映射的起始地址,通常设置为 NULL,让系统自动选择合适的地址。

  • length:指定映射区域的长度,以字节为单位。如果指定的长度超过文件的实际长度,系统会自动增加文件长度,但是并不会实际读写磁盘上的数据。

  • prot

    :指定映射区域的保护方式,可以是以下几种组合:

    • PROT_READ:映射区域可读。
    • PROT_WRITE:映射区域可写。
    • PROT_EXEC:映射区域可执行。
    • PROT_NONE:映射区域不可访问。
  • flags

    :指定映射区域的选项,可以是以下几种组合:

    • MAP_SHARED:映射区域可共享,对映射区域的修改会影响到文件内容。
    • MAP_PRIVATE:映射区域私有,对映射区域的修改不会影响到文件内容,而是在内存中进行。
    • MAP_FIXED:强制将映射区域放置到 start 参数指定的地址处,如果无法满足,则映射失败。
    • MAP_ANONYMOUS:不与任何文件关联,创建一个匿名映射区域,常用于创建共享内存区域。
  • fd:指定要映射的文件的文件描述符,如果不需要映射文件,可以设置为 -1

  • offset:指定文件中的偏移量,表示从文件的哪个位置开始映射数据,通常设置为 0,表示从文件的起始位置开始。

mmap() 函数成功调用后,将返回指向映射区域的指针,如果映射失败,则返回 MAP_FAILED 宏。需要注意的是,映射区域的大小和对齐方式取决于系统和硬件的限制,在使用时需要仔细考虑。此外,映射区域需要通过 munmap() 函数进行解除映射,以释放相关资源。

3.1、将一个文件映射到内存中

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main() {int fd;struct stat sb;void *mapped;// 打开文件fd = open("output.txt", O_RDWR);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 获取文件状态if (fstat(fd, &sb) == -1) {perror("fstat");exit(EXIT_FAILURE);}// 检查文件大小,文件大小不能为0,否则的话if (sb.st_size == 0) {fprintf(stderr, "File size is 0.\n");exit(EXIT_FAILURE);}// 将文件映射到内存中mapped = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapped == MAP_FAILED) {perror("mmap");exit(EXIT_FAILURE);}// 关闭文件close(fd);// 修改映射区域中的内容strcpy((char*)mapped, "Hello, memory mapped file!\n");// 输出映射区域的内容printf("%s", (char*)mapped);// 解除映射if (munmap(mapped, sb.st_size) == -1) {perror("munmap");exit(EXIT_FAILURE);}return 0;
}

image-20240422115246020

3.2、共享内存的映射

这个函数还有一个更大的作用就是将将共享内存区域映射到当前进程的地址空间中。以实现多进程之间的信息交换。这部分在后面的多进程中会提到,这里就先不讲了。

四、splice函数

splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作。

#include <fcntl.h>ssize_t splice(int fd_in, loff_t* off_in, int fd_out, loff_t* off_out, size_t len, unsigned int flags);
  • fd_in: 源文件描述符,从该文件描述符读取数据。
  • off_in: 源文件偏移量指针,指向源文件的读取偏移量, NULL:表示从当前偏移位置开始读取。
  • fd_out: 目标文件描述符,向该文件描述符写入数据。
  • off_out: 目标文件偏移量指针,指向目标文件的写入偏移量, NULL:表示从当前偏移位置开始写入。
  • len: 要移动的数据长度。
  • flags: 控制函数行为的标志,可以为 0 或以下标志的按位或:
    • SPLICE_F_MOVE: 合适的话,按照整页移动数据
    • SPLICE_F_NONBLOCK: 非阻塞的splice操作,但实际效果还是会受文件描述符本身的阻塞状态的影响.
    • SPLICE_F_MORE:给内核一个下面还有数据的提示

该函数可以在文件描述符之间移动数据而不涉及用户空间的数据拷贝,因此对于大量数据的传输操作可以提高效率。使用splice函数时,fd_in和fd_out必须至少有一个是管道文件描述符。

看一个回射服务器的示例:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <error.h>
#define __USE_GNU 1int main(int argc, const char *argv[])
{const char *ip = "127.0.0.1";const int port = 8080;int ret;struct sockaddr_in address;bzero(&address, sizeof(address));address.sin_family = AF_INET;inet_pton(AF_INET, ip, &address.sin_addr);address.sin_port = htons(port);int sockfd = socket(AF_INET, SOCK_STREAM, 0);bind(sockfd, (struct sockaddr *)&address, sizeof(address));listen(sockfd, 5);int connfd;while (1){struct sockaddr_in peer;bzero(&peer, sizeof(peer));socklen_t len = sizeof(peer);connfd = accept(sockfd, (struct sockaddr *)&peer, &len);if (connfd > 0){int pipefd[2];pipe(pipefd);/*将connfd上流入的客户端数据定向到管道中*/splice(connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);/*将管道中的数据定向到connfd的客户端文件描述符上*/splice(pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);}close(connfd);}close(sockfd);return 0;
}

image-20240422193639479

五、tee函数

tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作

#include <fcntl.h>ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
  • fd_infd_out必须都是管道文件描述符

  • 其他参数与splice函数一样

tee函数成功时返回在两个文件描述符之间复制的数据字节数。返回0表示没有复制任何数据。tee失败时返回-1并设置errno。


http://www.ppmy.cn/news/1443698.html

相关文章

提高分布式软件开发团队协作效率的策略和工具推荐

策略1:采用敏捷开发方法 步骤: 研究敏捷方法: 研究敏捷开发方法,如Scrum或Kanban,了解其原则和实践。培训团队: 为团队成员提供敏捷方法论和最佳实践的培训。选择工具: 选择适合敏捷开发的工具,如Jira、Trello或Asana。实施敏捷仪式: 定期举行站立会议、迭代评审和迭…

基于springboot实现在线课程管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现在线课程管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了在线课程管理系统的开发全过程。通过分析在线课程管理系统管理的不足&#xff0c;创建了一个计算机管理在线课程管理系…

iOS 模拟请求 (本地数据调试)

简介 在iOS 的日常开发中经常会遇到一下情况&#xff1a;APP代码已编写完成&#xff0c;但后台的接口还无法使用&#xff0c;这时 APP开发就可能陷入停滞。此时iOS 模拟请求就派上用场了&#xff0c;使用模拟请求来调试代码&#xff0c;如果调试都通过了&#xff0c;等后台接口…

IT大陆之:指定用户登入docker

这天&#xff0c;S老交给小k一个特殊的任务&#xff1a;以“nav”这个神秘身份&#xff0c;深入“my_dk”国度&#xff0c;探索其中的奥秘。小k心怀激动与忐忑&#xff0c;站在控制台前&#xff0c;深吸一口气&#xff0c;然后缓缓念出那串充满魔力的咒语&#xff1a;“sudo do…

12.7.1 实验7:实施路由器密码恢复

1、实验目的 通过本实验可以掌握&#xff1b; 路由器密码恢复原理。路由器密码恢复步骤。修改配置寄存器值的方法。 2、实验步骤 路由器密码恢复的过程如下所述。 &#xff08;1&#xff09;路由器冷启动。 1分钟内按【CtrlBreak】键进入ROM监控(ROM Monitor ) rommon模式…

Pixelmator Pro for Mac:简洁而强大的图像编辑软件

Pixelmator Pro for Mac是一款专为Mac用户设计的图像编辑软件&#xff0c;它集简洁的操作界面与强大的功能于一身&#xff0c;为用户提供了卓越的图像编辑体验。 Pixelmator Pro for Mac v3.5.9中文激活版下载 该软件支持多种文件格式&#xff0c;包括常见的JPEG、PNG、TIFF等&…

使用Python,结合Flask框架,创建一个可以处理交易、挖矿新区块、验证区块链有效性,并能在网络节点间同步的区块链网络。(持续更新)

目录 前言 二、代码注释 1.添加新交易到区块链 2.连接新节点 3、替换区块链为最长链 总结 前言 本篇文章将从一个实践者的角度出发&#xff0c;通过构建一个简单的区块链系统&#xff0c;揭开区块链技术的神秘面纱。我们将使用Python语言&#xff0c;结合Flask框架&…

Linux系统——Nginx常见面试题

目录 一、Nginx使用场景 二、Nginx的发展历史 三、Nginx没出现之前都存在过什么问题 四、Nginx的优点 五、正向代理和反向代理分别是什么 六、Nginx限流问题 七、Nginx动静分离 八、什么是负载均衡 九、Nginx负载均衡的策略有哪些 十、Nginx多进程模型 十一、为什么…