举例

一般情况下,Read,write系统调用并不直接访问磁盘。这两个系统调用仅仅是在用户空间和内核空间的buffer之间传递目标数据。

举个例子,下面的write系统调用仅仅是把3个字节从用户空间拷贝到内核空间的buffer之后就直接返回了

write(fd,”abc”,3);

在以后的某个时间点上,内核把装着“abc”三个字节的buffer写入(flush)磁盘……

如果另外的进程在这个过程中想要读刚才被打开写的那个文件怎么办?答案是:内核会从刚才的buffer提供要读取的数据,而不是从磁盘读。

"buffer cache"

要理解"buffer cache"这个东西,需要澄清一下概念:

一般情况下,进程在io的时候,要依赖于内核中的一个buffer模块来和外存发生数据交换行为。另一个角度来说,数据从应用进程自己的buffer流动到外存,中间要先拷贝到内核的buffer中,然后再由内核决定什么时候把这些载有数据的内核buffer写出到外存。

"buffer cache"仅仅被内核用于常规文件(磁盘文件)的I/O操作。

介绍完“写出”,该介绍“读入”了。

当前系统上第一次读一个文件时,Read系统调用触发内核以block为单位从磁盘读取文件数据,并把数据blocks存入内核buffer,然后read不断地从这个buffer取需要的数据,直到buffer中的数据全部被读完,接下来,内核从磁盘按顺序把当前文件后面的blocks再读入内核buffer,然后read重复之前的动作…

一般的文件访问,都是这种不断的顺序读取的行为,为了加速应用程序读磁盘,unix的设计者们为这种普遍的顺序读取行为,设计了这样的机制----预读,来保证进程在想读后续数据的时候,这些后续数据已经的由内核预先从磁盘读好并且放在buffer里了。这么做的原因是磁盘的io访问比内存的io访问要慢很多,指数级的差别。

read,write从语义和概念上来说,本来是必须要直接和磁盘交互的,调用时间非常长,应用每次在使用这两个系统的时候,从表象上来说都是被卡住。而有了这些buffer,这些系统调用就直接和buffer交互就可以了,大幅的加速了应用执行。

内存上buffer cache多大?

Linux内核并没有规定"buffer cache"的尺寸上线,原则上来说,除了系统正常运行所必需和用户进程自身所必需的之外的内存都可以被"buffer cache"使用。而系统和用户进程需要申请更多的内存的时候,"buffer cache"的内存释放行为会被触发,一些长久未被读取,以及被写过的脏页就会被释放和写入磁盘,腾出内存,以便被需要的行为方使用。

何时内存上的buffer cache上的数据flush到磁盘上?

现在大体上你们已经知道了吧,"buffer cache"有五个flush的触发点:

  1. pdflush(内核线程)定期flush;
  2. 系统和其他进程需要内存的时候触发它flush;
  3. 用户手工sync,外部命令触发它flush;
  4. proc内核接口触发flush,"echo 3 >/proc/sys/vm/drop_caches;
  5. 应用程序内部控制flush。

这个"buffer cache"从概念上的理解就是这些了,实际上,更准确的说,linux从2.4开始就不再维护独立的"buffer cache"模块了,而是把它的功能并入了"page cache"这个内存管理的子系统了,"buffer cache"现在已经是一个unix系统族的普遍的历史概念了。

高性能写文件

写100MB的数据

场景1,1次写1个字节,总共write 100M次;

场景2,1次写1K个字节,总共write 100K次;

场景3,1次写4K个字节,总共write 25K次;

场景4,1次写16k个字节,总共write大约不到7K次。

以上4种写入方式,内核写磁盘的次数基本相同,因为写磁盘的单位是block,而不是字节。现在的系统默认的block都是4k。

第1种性能非常差,user time和system time执行时间都很长,既然写盘次数都差不多,那他慢在哪儿呢?答案是系统调用的次数太多

第2种,user time和system time都显著降低,不过system time降低幅度更大

第2种以后,性能差别就不是很高了,第3种和第4种性能几乎一样

有兴趣的朋友可以试一试,如果你的服务器很好,可以适当放大测试样本。

总而言之,得出的结论是以block的尺寸为write(fd, sizeof(buf),buf)的调用单位就可以了,再大对性能也没什么太大的提高。

题外话:一个衡量涉及IO程序的好坏的粗略标准

题外话:一个衡量涉及IO程序的好坏的粗略标准是“程序运行应该尽量集中在user time,避免大量的system time”以及“IO的时候肯定是需要一些应用层buf的,比如上述4个场景,匹配就可以了(比如场景3,场景1和场景2会导致系统调用次数太多,场景4使用的buf尺寸过于浪费)”

每个系统调用在返回的时候,会有一个从内核态向用户态切换的间隙,每次在这个间隙里面,系统要干两个事情----递送信号和进程调度,其中进程调度会重新计算全部RUN状态进程的优先级。

系统调用太多的话,递送信号和进程调度引起的计算量是不容忽视的。

精确地flush "buffer cache"

在很多业务场景下,我们仅仅调用write()把需要写盘的数据推送至内核的"buffer cache"中,这是很不负责任的。或许我们应该不断地频繁地把"buffer cache"中的数据强制flush到磁盘,尽最大可能保证我们的业务数据尽量不因断电而丢失。

天下没有免费的午餐,既想要效率(写入内核buffer),又想要安全性(数据必须flush到外存介质中才安全),这似乎是很矛盾的。SUSv3(Single UNIX Specification Version 3)给了这种需求一个折中的解决方案,让OS尽量满足我们的苛刻的要求。介绍这个折中方案之前,有两个SUSv3提案的规范很重要,说明如下:

1.数据完整性同步(synchronized I/O data integrity)

一个常规文件所包含的信息有两种:文件元数据和文件内容数据。

文件元数据包括:文件所属用户、组、访问权限,文件尺寸,文件硬连接数目,最后访问时间戳,最后修改时间戳,最后文件元数据修改时间戳,文件数据块指针。

对于文件内容数据,大家应该都很清楚是什么东西。

对于写操作,这个规范规定了,写文件时保证文件内容数据和必要的文件元数据保持完整性即可。粗糙地举个例子来解释这个规范,某次flush内核中的数据到磁盘的时候,仅仅把文件内容数据写入磁盘即可,但是如果这次写文件导致了文件尺寸的变化,那么这个文件尺寸作为文件的元数据也需要被写入磁盘,必要信息保持同步。而其他的文件元数据,例如修改时间,访问时间一概略去,不需要同步。

2.文件完整性同步(synchronized I/O file integrity)

相对于数据完整性同步而言,这个规范规定了,所有内容数据以及元数据都要同步。

下面来介绍linux提供的几种flush内核缓冲数据的几种方案

相信看完之后,大家应该知道上述提及的折中方案是怎样的

1.

int fsync(int fd);

文件完整性同步;

2.

int fdatasync(int fd);

数据完整性同步。

fdatasync相对于fsync的意义在于,fdatasync大致仅需要一次磁盘操作,而fsync需要两次磁盘操作。举例说明一下,假如文件内容改变了,但是文件尺寸并没有发生变化,那调用fdatasync仅仅是把文件内容数据flush到磁盘,而fsync不仅仅把文件内容flush刷入磁盘,还要把文件的last modified time也同步到磁盘文件系统。last modified time属于文件的元数据,一般情况下文件的元数据和文件内容数据在磁盘上不是连续存放的,写完内容数据再写元数据,必然涉及到磁盘的seek,而seek又是机械硬盘速度慢的根源。。。

在某些业务场景下,fdatasync和fsync的这点微小差别会导致应用程序性能的大幅差异。

3.

sync_file_range()

这个接口是linux从2.6.17之后实现的,是linux独有的非标准接口。这个接口提供了比fdatasync更为精准的flush数据的能力。详细请参照man。

4.

void sync(void);

强制"buffer cache"中的数据全部flush到磁盘,并且要遵循文件完整性同步。

上面4种方式介绍完毕,open()系统调用的打开文件的标志位,比如O_DSYNC诸如此类的标志,对flush数据的影响和上面几个接口作用类似。

预读

上面介绍了写buffer以及如何控制buffer的flush,下面来讲一讲如何控制读cache的行为。

读cache这一块,基本上,我们可以控制的就是文件的预读。

我们从POSIX规定的一个接口来论述一下如何控制文件的预读以及控制它的意义。接口原型如下:

#include
int posix_fadvise(int fd, off_t offset, off_t len, int advice);

fd:打开文件的描述符其实;

offset和len:指明文件区域;

advice:预读的方式。预读方式及其意义如下:

  1. POSIX_FADV_NORMAL:内核默认的预读方式;
  2. POSIX_FADV_RANDOM:内核禁用预读。适合随机读文件的业务,每次按业务要求的量读取数据,不多读;
  3. POSIX_FADV_SEQUENTIALP:内核把默认的预读量(POSIX_FADV_NORMAL)扩大一倍;
  4. POSIX_FADV_WILLNEED:读取出来的内容会被应用程序多次访问(就是应用程序会不断的调用read()对这些内容不断的读);
  5. POSIX_FADV_NOREUSE:读取出来的内容只会被应用程序访问一次,访问一次之后就清理掉并且释放内存。cache服务器,比如memcache或者redis启动时,把文件内容加载到应用层cache,就是这个参数存在的典型场景;
  6. POSIX_FADV_DONTNEED:应用程序后续不打算访问指定范围中的文件内容,内核从"page cache(buffer cache)"中删除指定范围的文件内容,释放内存。

对于POSIX_FADV_WILLNEED这种方式,linux自己有一个特定接口,原型如下:

ssize_t readahead(int fd, off64_t offset, size_t count);

linux的"buffer cache"默认预读128k。

实际上,OS全局控制"buffer cache"的操作接口不仅仅是上面提及的几种,/proc/sys/vm/目录下还有几个参数可以从其他一些方面来控制"buffer cache"的行为,这部分内容在之后我整理笔记之后会介绍。

IO之内核buffer----"buffer cache"的更多相关文章

  1. IO之流程与buffer概览

    为了说明这个流程,还是用图来描述一下比较直观. 中间过程请参考 <IO之内核buffer----"buffer cache"> <IO之标准C库buffer> ...

  2. 缓存系列之一:buffer、cache与浏览器缓存

    缓存系列之一:buffer.cache与浏览器缓存 一:缓存是为了调节速度不一致的两个或多个不同的物质的速度,在中间对速度较快的一方起到一个加速访问速度较慢的一方的作用,比如CPU的一级.二级缓存是保 ...

  3. buffer和cache怎么让你们解释的那么难理解?

    对于一个即将踏上“系统运维”或者更加高大尚的工作“系统调优”,如果这不跟这两哥们搞好关系了,坑的不只有内存,更坑的是你拿着调优的钱却干着随时被调的活.因为作为一个系统运维人员来说监控和优化IO性能这是 ...

  4. linux下的缓存机制buffer、cache、swap - 运维总结 ["Cannot allocate memory"问题]

    一.缓存机制介绍 在Linux系统中,为了提高文件系统性能,内核利用一部分物理内存分配出缓冲区,用于缓存系统操作和数据文件,当内核收到读写的请求时,内核先去缓存区找是否有请求的数据,有就直接返回,如果 ...

  5. linux下的缓存机制buffer、cache、swap

    一.缓存机制介绍 在Linux系统中,为了提高文件系统性能,内核利用一部分物理内存分配出缓冲区,用于缓存系统操作和数据文件,当内核收到读写的请求时,内核先去缓存区找是否有请求的数据,有就直接返回,如果 ...

  6. 性能测试必备知识(11)- 怎么理解内存中的Buffer和Cache?

    做性能测试的必备知识系列,可以看下面链接的文章哦 https://www.cnblogs.com/poloyy/category/1806772.html 缓存 从 free 命令可以看到,缓存其实就 ...

  7. linux free命令中buffer与cache的区别

    linux free命令中buffer与cache的区别 2012-05-15      个评论       收藏    我要投稿 linux free命令中buffer与cache的区别   ~$ ...

  8. 【Linux】基于Linux的buffer和cache学习

    缓存(cached)是把读取过的数据保存起来,重新读取时若命中(找到需要的数据)就不要去读硬盘了,若没有命中就读硬盘.其中的数据会根据读取频率进行组织,把最频繁读取的内容放在最容易找到的位置,把不再读 ...

  9. 2015-01-27-从实验出发理解buffer与cache区别-吴伟顺

        通过du(find) 与 cat 体现buffer与cache差异实验: 实验表明: 1 通常 buffer << cache 2 "文件系统"相关内容(ino ...

随机推荐

  1. [置顶] iOS 应用程序内部国际化,不跟随系统语言

    前言:网络上关于iOS国际化的文章很多,但基本上都是基于跟随系统语言的国际化,笔者就不赘述了-0 – 今天要讲的是不跟随系统的切换语言版本方案,即程序内部的切换语言版本方案. 一.总则: 应用内部语言 ...

  2. Java 学习第一天

    java 学习路线 http://edu.csdn.net/main/studyline/heimaline.html?flz java 学习视频 —— 马士兵:毕向东

  3. 8个常用的Android开发工具

    周末发现一些比较有用的android开发常用的工具,里面大部分是自己经常用的,还有一些暂时很少用,暂且在这里记录下,以后一定同样会经常用到的. 1 The SDK and AVD Manager 这个 ...

  4. C#委托的详细使用

    代码如下: public delegate void GreetingDelegate(string name);//定义委托,它定义了可以代表方法的类型 class Program { public ...

  5. .NET基础拾遗(4)委托为何而生?

    生活中的例子: 你早上要吃包子作为早饭,那么你可能让你爸爸或者妈妈帮你做,那你就会调用 爸爸.要包子() 或妈妈.要包子() 返回包子对象. 但是如果你爸妈不在家的时候,你只能去街上买,问题是你根本不 ...

  6. POJ 1286 Necklace of Beads(项链的珠子)

    Necklace of Beads Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 7874   Accepted: 3290 ...

  7. PAT 64.Complete Binary Search Tree

    题目链接: http://pat.zju.edu.cn/contests/pat-a-practise/1064 思路分析: 1)先对数组排好序. 2)采用中序遍历的方式,将排好序的元素逐个插入在完全 ...

  8. mfc EDIT字体颜色

    改变Edit字体颜色: 1.CMyDlg类中添加成员变量: CBrush m_Brush; 2.OnInitDialog中初进行设置: m_brush.CreateSolidBrush(RGB(0,2 ...

  9. sql建立跨服务器链接

      select srvname,* from master.dbo.sysservers   //创建linkServer   exec sp_addlinkedserver 'srv_lnk',' ...

  10. Apache服务器安装配置(win版)

    登录http://httpd.apache.org/download.cgi 这个地址,找到2.4.10,如下图位置: 2 进入如下界面后,选择第一项ApacheHaus,这是个第三方下载平台,在它的 ...