分散输入和集中输出(Scatter-Gather I/O):readv()和writev()

news2024/9/20 18:41:30

readv()和writev()系统调用分别实现了分散输入和集中输出的功能。

NAME
       readv, writev, preadv, pwritev - read or write data into multiple buffers

SYNOPSIS
       #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);

这些系统调用并非只对单个缓冲区进行读写操作,而是一次即可传输多个缓冲区的数据。数组iov定义了一组用来传输数据的缓冲区。整型数iovcnt则指定了iov的成员个数。iov中的每个成员都是如下形式的数据结构。

struct iovec {
    void  *iov_base;    /* Starting address */
    size_t iov_len;     /* Number of bytes to transfer */
};

SUSv3标准允许系统实现对iov中的成员个数加以限制。系统实现可以通过定义<limits.h>文件中IOV_MAX来通告这一限额,程序也可以在系统运行时调用sysconf (_SC_IOV_MAX)来获取这一限额。(11.2节将介绍sysconf()。)SUSv3要求该限额不得少于16。Linux将IOV_MAX的值定义为1024,这是与内核对该向量大小的限制(由内核常量UIO_MAXIOV定义)相对应的。

然而,glibc对readv()和writev()的封装函数⑤还悄悄做了些额外工作。若系统调用因iovcnt参数值过大而失败,外壳函数将临时分配一块缓冲区,其大小足以容纳iov参数所有成员所描述的数据缓冲区,随后再执行read()或write()调用(参见后文对使用write()实现writev()功能的讨论)。

图5-3展示的是一个关于iov、iovcnt以及iov指向缓冲区之间关系的示例。

 

 图5-3:iovec数组及其相关缓冲区的示例

测试一:打印IOV_MAX的值:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/epoll.h>
#include <sys/uio.h>
#include <limits.h>

#define _DEBUG_INFO
#ifdef _DEBUG_INFO
#define DEBUG_INFO(format, ...) printf("%s:%d $$ " format "\n" \
,__func__,__LINE__ \
, ##__VA_ARGS__)
#else
#define DEBUG_INFO(format, ...)
#endif
int main(int argc, char **argv)
{
    DEBUG_INFO("IOV_MAX = %d",IOV_MAX);
    DEBUG_INFO("IOV_MAX = %d",sysconf(_SC_IOV_MAX));
    return 0;
}

执行结果:

main:27 $$ IOV_MAX = 1024
main:28 $$ IOV_MAX = 1024

分散输入

        readv()系统调用实现了分散输入的功能:从文件描述符fd所指代的文件中读取一片连续的字节,然后将其散置(“分散放置”)于iov指定的缓冲区中。这一散置动作从iov[0]开始,依次填满每个缓冲区。
        原子性是readv()的重要属性。换言之,从调用进程的角度来看,当调用readv()时,内核在fd所指代的文件与用户内存之间一次性地完成了数据转移。这意味着,假设即使有另一进程(或线程)与其共享同一文件偏移量,且在调用readv()的同时企图修改文件偏移量,readv()所读取的数据仍将是连续的。
        调用readv()成功将返回读取的字节数,若文件结束(EOF)将返回0。调用者必须对返回值进行检查,以验证读取的字节数是否满足要求。若数据不足以填充所有缓冲区,则只会占用(按IOV数组顺序)部分缓冲区,其中最后一个缓冲区可能只存有部分数据。

测试二:使用readv()执行分散输入readv.c

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/epoll.h>
#include <sys/uio.h>
#include <limits.h>

#define _DEBUG_INFO
#ifdef _DEBUG_INFO
#define DEBUG_INFO(format, ...) printf("%s:%d $$ " format "\n" \
,__func__,__LINE__ \
, ##__VA_ARGS__)
#else
#define DEBUG_INFO(format, ...)
#endif
#define STR_SIZE 100
static char *file_name = "hello.txt";
int main(int argc, char **argv)
{
    int fd;
    struct iovec iov[3];
    char a,b,c[3];
    char str[STR_SIZE];
    memset(c,0,sizeof(c));
    fd = open(file_name,O_RDONLY);
    if(fd < 0){
        perror("open");
        return -1;
    }
    DEBUG_INFO("fd = %d,open %s ok",fd,file_name);
    iov[0].iov_base = &a;
    iov[0].iov_len = 1;

    iov[1].iov_base = &b;
    iov[1].iov_len = 1;

    iov[2].iov_base = c;
    iov[2].iov_len = 2;

    int len = readv(fd,iov,3);
    if(len != 4){
        DEBUG_INFO("error:read len = %d",len);
        exit(0);
    }
    DEBUG_INFO("read ok: len = %d",len);
    DEBUG_INFO("a = %c b = %c c = %s",a,b,c);
    DEBUG_INFO("cur offset = %d",lseek(fd,0,SEEK_CUR));
    close(fd);
    DEBUG_INFO("bye bye");
    return 0;
}

测试:

创建文件hello.txt

echo "hello" > hello.txt

执行程序

./_build_/readv

如下图,红框中便是测试读出的值。 

 

 preadv测试:

将上面的int len = readv(fd,iov,3);替换为下面的代码,就是在偏移1的位置开始读。

 执行结果:

对比可知readv会改变偏移量,preadv不会改变偏移量,这和read与readv的区别是一样的。 

 集中输出

         writev()系统调用实现了集中输出:将iov所指定的所有缓冲区中的数据拼接(“集中”)起来,然后以连续的字节序列写入文件描述符fd指代的文件中。对缓冲区中数据的“集中”始于iov[0]所指定的缓冲区,并按数组顺序展开。
        像readv()调用一样,writev()调用也属于原子操作,即所有数据将一次性地从用户内存传输到fd指代的文件中。因此,在向普通文件写入数据时,writev()调用会把所有的请求数据连续写入文件,而不会在其他进程(或线程)写操作的影响下分散地写入文件。
如同write()调用,writev()调用也可能存在部分写的问题。因此,必须检查writev()调用的返回值,以确定写入的字节数是否与要求相符。


        readv()调用和writev()调用的主要优势在于便捷。如下两种方案,任选其一都可替代对writev()的调用。

  1.         编码时,首先分配一个大缓冲区,随即再从进程地址空间的其他位置将数据复制过来,最后调用write()输出其中的所有数据。
  2.         发起一系列write()调用,逐一输出每个缓冲区中的数据。

        尽管方案一在语义上等同于writev()调用,但需要在用户空间内分配缓冲区,进行数据复制,很不方便(效率也低)。
        方案二在语义上就不同于单次的writev()调用,因为发起多次write()调用将无法保证原子性。更何况,执行一次writev()调用比执行多次write()调用开销要小。

测试三 writev测试

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/epoll.h>
#include <sys/uio.h>
#include <limits.h>

#define _DEBUG_INFO
#ifdef _DEBUG_INFO
#define DEBUG_INFO(format, ...) printf("%s:%d $$ " format "\n" \
,__func__,__LINE__ \
, ##__VA_ARGS__)
#else
#define DEBUG_INFO(format, ...)
#endif
static char *file_name = "writev.txt";
int main(int argc, char **argv)
{
    int fd;
    struct iovec iov[3];
    char a[10],b[10],c[10];
    int write_len = 0;
    memcpy(a,"we",sizeof("we"));
    memcpy(b,"lc",sizeof("lc"));
    memcpy(c,"ome\n",sizeof("ome\n"));

    fd = open(file_name,O_WRONLY | O_CREAT | O_TRUNC,0666);
    if(fd < 0){
        perror("open");
        return -1;
    }
    DEBUG_INFO("fd = %d,open %s ok",fd,file_name);
    iov[0].iov_base = a;
    iov[0].iov_len = strlen(a);

    iov[1].iov_base = b;
    iov[1].iov_len = strlen(b);

    iov[2].iov_base = c;
    iov[2].iov_len = strlen(c);

    write_len += strlen(a);
    write_len += strlen(b);
    write_len += strlen(c);

    int len = writev(fd,iov,3);
    if(len != write_len){
        DEBUG_INFO("error:writev len = %d,write_len = %d",len,write_len);
        exit(0);
    }
    DEBUG_INFO("writev ok: len = %d",len);
    DEBUG_INFO("cur offset = %ld",lseek(fd,0,SEEK_CUR));
    close(fd);
    DEBUG_INFO("bye bye");
    return 0;
}

测试:

 查看生成的文件writev.txt,文件长度8字节。与调试输出一样。

$ ls -lsh writev.txt 
4.0K -rw-rw-r-- 1 lkmao lkmao 8 6月  28 20:19 writev.txt
$ cat writev.txt 
welcome

测试三 pwritev测试

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/epoll.h>
#include <sys/uio.h>
#include <limits.h>

#define _DEBUG_INFO
#ifdef _DEBUG_INFO
#define DEBUG_INFO(format, ...) printf("%s:%d $$ " format "\n" \
,__func__,__LINE__ \
, ##__VA_ARGS__)
#else
#define DEBUG_INFO(format, ...)
#endif
static char *file_name = "writev.txt";
int main(int argc, char **argv)
{
    int fd;
    struct iovec iov[3];
    char a[10],b[10],c[10];
    int write_len = 0;
    memcpy(a," t",sizeof(" t"));
    memcpy(b,"o ",sizeof("o "));
    memcpy(c,"china\n",sizeof("china\n"));

    fd = open(file_name,O_WRONLY,0666);
    if(fd < 0){
        perror("open");
        return -1;
    }
    DEBUG_INFO("fd = %d,open %s ok",fd,file_name);
    iov[0].iov_base = a;
    iov[0].iov_len = strlen(a);

    iov[1].iov_base = b;
    iov[1].iov_len = strlen(b);

    iov[2].iov_base = c;
    iov[2].iov_len = strlen(c);

    write_len += strlen(a);
    write_len += strlen(b);
    write_len += strlen(c);

    int len = pwritev(fd,iov,3,7);
    if(len != write_len){
        DEBUG_INFO("error:writev len = %d,write_len = %d",len,write_len);
        exit(0);
    }
    DEBUG_INFO("writev ok: len = %d",len);
    DEBUG_INFO("cur offset = %ld",lseek(fd,0,SEEK_CUR));
    close(fd);
    DEBUG_INFO("bye bye");
    return 0;
}

$ cat writev.txt 
welcome to china

 实验解析

在偏移7的位置开始写,正好覆盖前一个测试的最后一个字符‘\n’,这样就能保证cat输出的时候,新添加的内容和旧的内容在同一行输出。

pwritev写完文件后,文件的位置不会发生变化,writev写完文件后,文件的位置会发生变化。这是write和pwrite是一样的。

小结

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/698234.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯专题-试题版-【数字游戏】【城市建设】【最大子阵】【蚂蚁感冒】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

数字人解决方案——基于真人视频的三维重建数字人源码与训练方法

前言 1.真人视频三维重建数字人源码是基于NeRF改进的RAD-NeRF&#xff0c;NeRF&#xff08;Neural Radiance Fields&#xff09;是最早在2020年ECCV会议上的Best Paper&#xff0c;其将隐式表达推上了一个新的高度&#xff0c;仅用 2D 的 posed images 作为监督&#xff0c;即…

MongoDB存储引擎

1、前言 存储引擎是数据库的组成部分&#xff0c;负责管理数据存储。 MongoDB支持的以下存储引擎&#xff1a; 存储引擎 描述 WiredTiger存储引擎 从MongoDB 3.2开始默认的存储引擎&#xff0c;新的版本MongoDB推荐使用WiredTiger存储引擎。 MMAPv1存储引擎 MMAPv1是Mon…

微信小程序使用vant组件样式未生效解决办法

1.删除小程序自带的样式 首先在app.json里面删除这一行 2.清除缓存 重新编译 3.重新构建npm 重新编译 在工具里面

nginx之rewrite

一、Rewrite跳转的场景二、Rewrite跳转实现三、Rewrite实际场景四、常用的 Nginx 正则表达式五、Rewrite命令、语法格式六、location的分类七、location的优先级八、rewrite与location的区别九、rewrite示例9.1 基于域名的跳转9.2 基于客户端 IP 访问跳转9.3 基于旧域名跳转到新…

chatgpt赋能python:Python迭代运算:概述、应用及效果分析

Python迭代运算&#xff1a;概述、应用及效果分析 在Python编程领域中&#xff0c;迭代运算是一项基础性操作。它不仅适用于循环遍历数据&#xff0c;还支持函数式编程中的高阶函数应用&#xff08;例如map、filter等&#xff09;。本文将从多个方面探讨Python迭代运算的应用和…

高性能计算开发软件培训班-选猿代码科技IT培训机构!

学习CPU并行程序性能优化的意义&#xff1a; 学习CPU计算是现代计算机科学中不可或缺的一部分。掌握CPU计算原理和应用&#xff0c;能够提高我们的编程技能和解决问题的能力&#xff0c;帮助我们更好地应对计算机科学领域中的挑战和机遇。此外&#xff0c;CPU计算在计算机体系结…

python写路径时候的问题————转载ningqingzy的文章

作为笔记总结学习&#xff0c;如有侵权&#xff0c;立马删除。 总结有三种方法&#xff1a; 更换为绝对路径的写法&#xff1a;func1(“C:\Users\renyc”)显式声明字符串不用转义&#xff08;加r&#xff09;&#xff1a;func1(r"C:\Users\renyc")使用Linux的路径&a…

workbench 链接mysql 报错 authentication plugin caching_sha2_password

用workbench连接MySQL出现Authentication plugin ‘caching_sha2_password’ cannot be loaded的问题&#xff0c;如下图 原因 出现这个问题的原因是由于Navicat和MySQL的版本问题&#xff0c; mysql8 之前&#xff0c;加密规则是mysql_native_password&#xff1b; mysql8 之后…

leetcode97. 交错字符串(动态规划-java)

交错字符串 leetcode97. 交错字符串题目描述解题思路代码演示&#xff1a; 动态规划加状态压缩代码演示 动态规划专题 leetcode97. 交错字符串 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/interleaving-string 题目描…

线性代数高级--矩阵的秩--SVD分解定义--SVD分解的应用

目录 矩阵的秩 概念 k阶子式 矩阵的秩的定义 矩阵的秩的性质 SVD分解 概念 注意 SVD的分解过程 SVD分解的应用 矩阵的秩 概念 矩阵的秩是线性代数中的一个重要概念&#xff0c;用于描述矩阵的行&#xff08;或列&#xff09;向量的线性无关程度。矩阵的秩可以通过…

chatgpt赋能python:Python遍历0到100的使用场景及方法

Python遍历0到100的使用场景及方法 Python是一种简洁、高效的脚本语言&#xff0c;广泛用于各种领域的开发。本文介绍Python遍历0到100的使用场景以及方法&#xff0c;旨在帮助读者更加了解Python的强大之处。 遍历0到100的背景和意义 遍历0到100是一种常见的问题&#xff0…

网络安全合规-银行业数据治理架构体系搭建(一)

为引导银行业金融机构加强数据治理&#xff0c;充分发挥数据价值&#xff0c;全面向高质量发展转变&#xff0c;银监会于2018年发布了《银行业金融机构数据治理指引》&#xff0c;主要内容如下&#xff1a; 近年来银行业金融机构在业务快速发展过程中&#xff0c;积累了客户数…

JavaScript 手写代码 第六期(重写数组方法三) 用于遍历的方法

文章目录 1. 为什么要手写代码&#xff1f;2. 手写代码2.1 forEach2.1.1 基本使用2.1.2 手写实现 2.2 map2.2.1 基本使用2.2.2 手写实现 2.3 filter2.3.1 基本使用2.3.2 手写实现 2.4 every2.4.1 基本使用2.4.2 手写实现 2.5 some2.5.1 基本使用2.5.2 手写实现 2.6 reduce2.6.1…

虚幻引擎(UE5)-大世界分区WorldPartition教程(二)

文章目录 前言一、OFPA怎么用二、OFPA怎么用总结 上一篇&#xff1a;虚幻引擎(UE5)-大世界分区WorldPartition教程(一) 前言 在UE4版本中中&#xff0c;Actor是保存在关卡文件中的&#xff0c;也就是说&#xff0c;如果要编辑关卡中的某些Actor&#xff0c;需要签出关卡文件进…

STM32单片机(七)ADC模拟数字转换器----第二节:ADC模数转换器练习2(AD多通道)

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

SNMP 计算机网络管理 实验2(二) SNMP服务与常用网管命令之任务三:对同学的计算机进行网络管理 任务四:查询计算机网卡的相关信息

⬜⬜⬜ &#x1f430;&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;(*^▽^*)欢迎光临 &#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;&#x1f430;⬜⬜⬜ ✏️write in front✏️ &#x1f4dd;个人主页&#xff1a;陈丹宇jmu &am…

Windows下通过FastGithub加速国内GitHub访问

有时候在国内访问GitHub会非常慢&#xff0c;有时候直接打不开&#xff0c;无法访问&#xff0c;最近了解到了FastGithub 可以解决以下几个问题: github加速神器&#xff0c;解决github打不开、用户头像无法加载、releases无法上传下载、git-clone、git-pull、git-push失败等问…

刷题第二天 数组 leetcode 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

977.有序数组的平方 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&#xff1a;平方后&…

Use ELK with Django Log

What is ELK? The ELK Stack is a collection of three open source products: ElasticsearchLogstashKibana When to use ELK? ELK is designed to allow us to take data from any source, in any format, and to search, analyze, visualize data in real time. At t…