Linux -- 如何减少IO过程中的CPU copy
四种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的更多相关文章
- 在Linux下安装PHP过程中,编译时出现错误的解决办法
在Linux下安装PHP过程中,编译时出现configure: error: libjpeg.(a|so) not found 错误的解决办法 configure: error: libjpeg.(a ...
- Linux系统在启动过程中mbr主引导程序被破坏的解决方案
首先,mbr主引导程序被破坏是指系统在启动过程中,磁头找不到/boot分区(windows的启动分区在c盘). 1)下面我们模拟主引导分区被破坏的情况:(在启动分区划分446M的存储大小) 2)重启( ...
- [C++]Linux之虚拟文件系统[/proc]中关于CPU/内存/网络/内核等的一些概要性说明
声明:如需引用或者摘抄本博文源码或者其文章的,请在显著处注明,来源于本博文/作者,以示尊重劳动成果,助力开源精神.也欢迎大家一起探讨,交流,以共同进步- 0.0 1.Linux虚拟文件系统 首先要明白 ...
- 工具运行过程中,CPU占用过高的分析定位
之前使用Java Swing开发了一款设备档案收集工具.支持多台设备同时收集,每个设备使用一个线程.在同时收集多台设备信息时,发现CPU占用率居然达到了97%,而且高居不下.显然这样的性能是令人无法忍 ...
- linux 在执行命令过程中,反单引号(`)这个符号代表的意义为何?
在一串命令中,在`之内的命令将会被先执行,而且执行出来的结果将作为外部的输入信息.例如:uname -r 会显示出目前的内核版本,而我们的内核版本在/lib/modules里面,因此.你可以先执行un ...
- Linux源码安装过程中选项—prefix的作用
源码的安装一般由3个步骤组成:配置(configure).编译(make).安装(make install),具体的安装方法一般作者都会给出文档,这里主要讨论配置(configure).Configu ...
- 压测过程中,CPU和内存占用率很高,案例简单分析
Q: 最近公司测试一个接口,数据库采用Mongo 并发策略:并发400个用户,每3秒加载5个用户,持续运行30分钟 数据量:8000条左右 压测结果发现: TPS始终在5左右 ...
- Linux 安装tomcat 及过程中遇到的问题
Linux 安装tomcat(tomcat能用的前提是系统已经安装jdk) 1.下载linux系统版tomcat,解压后通过ftp上传到Linux服务器 例:tomcat放在 /opt/tom ...
- Linux系统在启动过程中内核文件丢失的解决方法
在/boot目录下有两个重要的文件,分别是: vmlinuz-3.10.0-123.el7.x86_64 内核文件 initamfs-3.10.0-123.el7.x86_64.img ...
随机推荐
- 子标签和后代标签: .children 和 .descendants
昨天看书,没有用enumurate枚举的时候,直接print,完全发觉不了他们的区别,倍感困惑. 今天看了其他人写的教程,用了枚举法,终于,终于,发现它们之间的区别啦!敲锣打鼓,掌声响起来 还要注意, ...
- 1. vue如何实现双向数据绑定
- Django REST framework+Vue 打造生鲜电商项目(笔记七)
十.购物车.订单管理和支付功能 1.添加商品到购物车 (1)trade/serializer.py 这里的serializer不继承ModelSerializer,是因为自己写的Serializer更 ...
- 用于异步事件驱动的 P 语言 P Language
微软最近开源了P语言,致力于在Linux.macOS和Windows上编写安全的异步事件驱动程序. 微软将P描述为一种领域特定语言,对异步系统的组件间通信进行建模,例如嵌入式.网络或分布式系统.P程序 ...
- 20191029 牛客CSP-S提高组赛前集训营1
前一个小时看这几道题感觉要爆零 A. 仓鼠的石子游戏 分析一下发现a[i]>1a[i]>1a[i]>1时后先手必输,a[i]=1a[i]=1a[i]=1时先手必赢 然后直接看1的个数 ...
- 04_Tutorial 4: Authentication & Permissions 认证和权限
1.认证和权限 0.文档 https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/ https: ...
- Jenkins automate workflow
Now we will build an automate flow from code compiling to product delivery.The essential tools using ...
- P2637 第一次,第二次,成交!
题目描述 因为奶牛们的节食运动(奶牛还节食?)给农夫JOHN余下了一大批干草无法处理,所以他准备要开一个拍卖会去出售他的干草.他有N(1<=N<=1000)批干草(每批大约100捆).他的 ...
- bootstrap中tab切换的使用
文档地址:https://v3.bootcss.com/javascript/#tabs 简单实例: <!DOCTYPE html> <html lang="en" ...
- 【00NOIP提高组】单词接龙
#include<bits/stdc++.h> using namespace std; ; int n,length; int vis[N]; string str[N]; inline ...