四种I/O方式的对比

1. Buffered I/O

read(file, tmp_buf, len);

write(socket, tmp_buf, len);

上下文切换:4次

CPU copy:2次

步骤1:read()系统调用使上下文从用户态切换到内核态。DMA engine从磁盘中读取文件内容,然后把数据保存在内核地址空间缓存。

步骤2:CPU从内核缓存复制数据到用户缓存,read()系统调用返回。Read()返回后,上下文从内核态切换到用户态。现在,数据储存在用户地址空间缓存。

步骤3:write()系统调用使上下文从用户态切换到内核态。CPU把用户缓存中的数据复制到内核缓存中。这个内核缓存通常与某个特定的socket关联。

步骤4:write()系统调用返回,使上下文从内核态切换到用户态。DMA engine把数据从内核缓存传递到protocal engine,这个过程是独立且异步的。独立且异步的意思是,write()返回不代表已经将所有数据写入到protocal engine,甚至不代表数据传输已经开始。write()返回仅仅表示Ethernet driver已经接受我们的数据传输,这项任务被置入一个队列。

这种IO被称为缓存IO(buffered io). 当应用程序访问某块数据的时候,操作系统内核会先检查这块数据是不是因为前一次对相同文件的访问而已经被存放在操作系统内核地址空间的缓冲区(页缓存)内,如果在内核缓冲区中找不到这块数据,Linux操作系统内核会先将这块数据从磁盘读出来放到操作系统内核的缓冲区里去。

2. Direct I/O

你可以对fd进行O_DIRECT的设置

fcntl(file, F_SETFD, O_DIRECT | O_SYNC | oldflags);

read(file, tmp_buf, len);

write(socket, tmp_buf, len);

直接IO会把磁盘上的数据直接复制到用户地址空间,而不经过内核地址空间。直接IO适合自缓存应用(self-caching applications)。某些应用程序有自己的数据缓存机制,不需要使用操作系统内核缓存,这种应用程序称为自缓存应用。

内核缓存区对读写磁盘数据做了优化,包括按顺序预读取,在成簇磁盘块上执行IO等等。因此在普通的应用中使用直接IO会降低性能。

一般会在数据库系统使用直接IO。数据库系统的高速缓存和IO优化机制均自成一体,无需内核消耗CPU时间和内存去完成相同的任务。

直接IO中read和write的行为必须是同步的,但是O_DIRECT不保证同步,因此O_DIRECT必须与O_SYNC连用来保证同步行为。

请慎用。

3. 内存映射:使用mmap()代替read()

tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);

上下文切换:4次

CPU copy:1次

步骤1:mmap()系统调用使上下文从用户态切换到内核态。DMA engine从磁盘中读取文件内容,然后把数据保存在内核地址空间缓存。CPU将该内核缓存区与用户进程共享。

步骤2:write()系统调用使上下文从用户态切换到内核态。 CPU把数据从原来的内核缓存复制到另一片与socket关联的内核缓存区。

步骤3:write()系统调用返回,使上下文从内核态切换到用户态。DMA engine把数据从内核缓存传递到protocal engine。

使用mmap()代替read()可以减少一次CPU copy,但增加了share动作。当复制的数据量很大,一次CPU copy的花费大于share的花费时,使用mmap()代替read()是能优化性能的。但是,mmap()+write()有一个陷阱:You will fall into one of them when you memory map a file and then call write while another process truncates the same file.write()系统调用会被信号SIGBUS中断,这个信号的默认动作是kill进程然后dump core。作为一个server程序,我们通常不希望被KILL。我们有两种方法解决这个问题:

第一个方法是修改SIGBUS的信号处理程序,将其改为简单的return。这样,SIGBUS信号就不会kill进程,write()会返回被信号中断前已经成功写入的字符数,并设置errno。但是当该进程因其他问题收到SIGBUS信号时,却也简单的return了,这会掩盖运行时出现的巨大问题。因此,不推荐使用方法一。

第二个方法是使用文件租借锁 (windows系统中称为opportunistic lock)。让进程a获得租借锁,当进程b对正在传输的文件进行截断时,内核会给进程a发送信号,进程a会被中断,以防止该进程访问到无效地址并被SIGBUS中断。进程a的write()会返回中断前写入的字符数,并设置errno。以下是租借锁的示例代码

/* l_type can be F_RDLCK F_WRLCK */
if(fcntl(fd, F_SETLEASE, l_type)){
    perror("kernel lease set type");
    return -1;
}

你应该在mapping file前获得租借锁,在你完成写之后释放租借锁。

fcntl(fd, F_SETLEASE, F_UNLCK)

4. zero copy : sendfile()

从Linux2.1开始,引入了sendfile()

sendfile(socket, file, len);

上下文切换:2次

CPU copy:1次或0次

sendfile()与前面3节相比,只进行一次系统调用。因此,把用户态和内核态之间的上下文切换从4次减少到2次。

CPU copy次数取决于硬件是否支持gather operation

如果硬件不支持gather operation:

步骤1:sendfile()系统调用使上下文从用户态切换到内核态。DMA engine把文件内容复制到内核缓存区。然后CPU把该内核缓存区的数据复制到另一片与socket关联的内核缓存区。

步骤2:sendfile()系统调用返回,使上下文从内核态切换到用户态。DMA engine把数据从内核缓存传递到protocal engine。

如果调用sendfile()时,文件被截断。sendfile()会在在访问到无效地址前返回,以防止被SIGBUS信号中断。

如果硬件支持gather operation,且Linux2.4以上:

数据不会从kernel buffer复制到sokcet buffer.仅会把包含kernel buffer地址和长度的信息的描述符append到socket buffer.DMA engine会把数据从kernel buffer直接传到protocol engine.

Sendfile()的一个问题是缺乏标准的实现。

Linux的sendfile()实现提供了file to file和file to socket的接口,但是solaris和HP-UX仅提供了file to socket.

Linux的sendfile()不支持vectored tranfers.

solaris和HP-Ux的sendfile()有额外的参数

Linux的sendfile() notes:

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

out_fd是待写的fd, in_fd是待读的fd。

在2.4-2.6.33,out_fd必须是一个socket。2.4以前和2.6.33后,out_fd可以是任何文件。

sendfile最多传输(2,147,479,552) bytes

当你使用sendfile()发送文件到TCP socket,但是需要在发送文件内容之前发送一些header,你可以对TCP socket设置TCP_CORK,以减少发送的数据包的数量。

TCP发送数据包的算法

默认:采用nagle算法,当TCP buffer中的数据超过一个MSS时,发送数据,否则等待收到ACK后再发送数据。

对socket设置TCP_NODELAY:禁用nagle算法,TCP buffer接受到数据后马上发送

对socket设置TCP_CORK:当TCP buffer中的数据超过一个MSS时,发送数据,否则等待时间到达200 毫秒后再发送数据。当这个选项被取消,那么所有被阻塞的数据也将发送出去。

TCP_CORK的用法是在写数据前设置TCP_CORK,在分别写完header和body后,取消TCP_CORK。这可以避免header+body长度小于一个MSS导致发送延时的情况

sendfile()如果想要做到zero copy,那么被读文件的未写入到被写文件的部分不能被修改

目前sendfile()由splice()实现的,是对splice()的包装。

splice() moves data between two file descriptors without copying between kernel address space and user address space.

但是两个fd中必须有一个是pipe。Stack over flow的Damon不建议使用splice(),因为现在的实现不够完善

番外小记

O_NONBLOCK对网络IO有效,对磁盘IO并没有作用

Linux -- 如何减少IO过程中的CPU copy的更多相关文章

  1. 在Linux下安装PHP过程中,编译时出现错误的解决办法

    在Linux下安装PHP过程中,编译时出现configure: error: libjpeg.(a|so) not found 错误的解决办法 configure: error: libjpeg.(a ...

  2. Linux系统在启动过程中mbr主引导程序被破坏的解决方案

    首先,mbr主引导程序被破坏是指系统在启动过程中,磁头找不到/boot分区(windows的启动分区在c盘). 1)下面我们模拟主引导分区被破坏的情况:(在启动分区划分446M的存储大小) 2)重启( ...

  3. [C++]Linux之虚拟文件系统[/proc]中关于CPU/内存/网络/内核等的一些概要性说明

    声明:如需引用或者摘抄本博文源码或者其文章的,请在显著处注明,来源于本博文/作者,以示尊重劳动成果,助力开源精神.也欢迎大家一起探讨,交流,以共同进步- 0.0 1.Linux虚拟文件系统 首先要明白 ...

  4. 工具运行过程中,CPU占用过高的分析定位

    之前使用Java Swing开发了一款设备档案收集工具.支持多台设备同时收集,每个设备使用一个线程.在同时收集多台设备信息时,发现CPU占用率居然达到了97%,而且高居不下.显然这样的性能是令人无法忍 ...

  5. linux 在执行命令过程中,反单引号(`)这个符号代表的意义为何?

    在一串命令中,在`之内的命令将会被先执行,而且执行出来的结果将作为外部的输入信息.例如:uname -r 会显示出目前的内核版本,而我们的内核版本在/lib/modules里面,因此.你可以先执行un ...

  6. Linux源码安装过程中选项—prefix的作用

    源码的安装一般由3个步骤组成:配置(configure).编译(make).安装(make install),具体的安装方法一般作者都会给出文档,这里主要讨论配置(configure).Configu ...

  7. 压测过程中,CPU和内存占用率很高,案例简单分析

    Q:  最近公司测试一个接口,数据库采用Mongo    并发策略:并发400个用户,每3秒加载5个用户,持续运行30分钟    数据量:8000条左右 压测结果发现:    TPS始终在5左右   ...

  8. Linux 安装tomcat 及过程中遇到的问题

    Linux 安装tomcat(tomcat能用的前提是系统已经安装jdk) 1.下载linux系统版tomcat,解压后通过ftp上传到Linux服务器     例:tomcat放在 /opt/tom ...

  9. Linux系统在启动过程中内核文件丢失的解决方法

    在/boot目录下有两个重要的文件,分别是: vmlinuz-3.10.0-123.el7.x86_64         内核文件 initamfs-3.10.0-123.el7.x86_64.img ...

随机推荐

  1. Markdown 语法介绍

    Markdown 语法介绍 from:https://coding.net/help/doc/project/markdown.html 文章内容 1 Markdown 语法介绍 1.1 标题 1.2 ...

  2. linux网络编程之socket编程(十)

    今天继续socket编程的学习,最近晚上睡觉都没有发热,没有暖气的日子还是种煎熬,快乐的十一也已经走来,幸福有暖气的日子也快啦,好了,回到正题~ ①close终止了数据传送的两个方向. ②shutdo ...

  3. 【最短路+较复杂处理】PAT-L3-005. 垃圾箱分布

    L3-005. 垃圾箱分布 大家倒垃圾的时候,都希望垃圾箱距离自己比较近,但是谁都不愿意守着垃圾箱住.所以垃圾箱的位置必须选在到所有居民点的最短距离最长的地方[此处为第一重排序选择的条件],同时还要保 ...

  4. Vue界面中关于APP端回调方法问题

    在混合开发中,HTML界面经常性的需要调用APP端提供的原生方法,而且在很多时候,APP端需要各种回调,如果将所有的回调方法写在内部,不是很方便,而且有些时候,APP端需要定义一些主动触发HTML界面 ...

  5. GitHub常用命令及使用

    GitHub使用介绍 摘要: 常用命令: git init 新建一个空的仓库git status 查看状态git add . 添加文件git commit -m '注释' 提交添加的文件并备注说明gi ...

  6. c语言之——整型的隐式转换与溢出检测

    溢出检测 我们可以通过下面的方式来检测溢出: if(a > INT_MAX - b) {     printf("overflow\n"); } 我们利用整型的最大值减去其中 ...

  7. jquery关于多个显示隐藏

    今天做了一个关于多个栏目的隐藏与显示,内容为初始化显示6个栏目,点击按钮显示所有的栏目,在次点击隐藏出现的栏目 <div class="ftlt_z_navigation acer&q ...

  8. POJ-2689-Prime Distance(素数区间筛法)

    链接: https://vjudge.net/problem/POJ-2689 题意: The branch of mathematics called number theory is about ...

  9. JSP数据交互(二)

    Application:当前服务器(可以包含多个会话):当服务器启动后就会创建一个application对象,被所有用户共享page.request.session.application四个作用域对 ...

  10. linux查看系统启动时间

    1.uptime命令 felix@felix-computer:~$ uptime :: up :, user, load average: 0.89, 0.74, 1.00 felix@felix- ...