先说一下作者大约7年前在一家热门应用分发初创公司的采访片段。公司安排了一个刚工作一年多的同学来面试我,说说我们的项目。配置文件里写了一个switch,同学跳出来说,你是读文件,每个用户多请求一次磁盘IO,性能肯定差。基于这个故事,我其实发现了一个问题。虽然我们大多数人都是计算机科班出身,但代码也非常好写。但在一些看似老生常谈的问题上,我们大多数人并不真正了解,或者了解得不够透彻。不管你用什么语言,C/PHP/GO,还是Java,相信大家都有读文件的经历。让我们思考两个问题,如果我们读取文件中的一个字节:磁盘IO会发生吗?如果是这样,Linux实际读取到磁盘的字节数是多少?为了理解问题,我们也列出c的代码:intmain(){charc;诠释;in=open("in.txt",O_RDONLY);读(在,&c,1);返回0;}如果不是搞c/c++开发的同学,想深入理解这道题真的没那么容易。因为目前常用的主流语言,比如PHP/Java/Go,封装程度都比较高,完全屏蔽了内核的很多细节。为了把上面两个问题弄清楚,有必要解剖一下Linux的内部,了解一下Linux的IO栈。LinuxIO栈介绍废话不多说,我们直接画一个简化版的LinuxIO栈:(官方IO栈可以参考这个Linux.IO.stack_v1.0.pdf)我们也分享了前面几篇讨论的硬件上图中的图层,以及文件系统模块。但是通过这个IO栈,我们发现我们对Linux文件IO的理解还远远不够,还有几个内核组件:IO引擎,VFS,PageCache,通用块管理层,IO调度层等模块不太了解。许多。不着急,下面一一说:如果我们IO引擎开发者要读写文件,lib库层有很多函数可以选择,比如read、write、mmap等,这个就是其实选择Linux提供的IO引擎。我们最常用的读写函数属于同步引擎。除了sync,还有map、psync、vsync、libaio、posixaio等,sync和psync都是同步的,libaio和posixaio是异步IO。当然,IO引擎还需要VFS、通用块层等底层支持才能实现。在sync引擎的read函数中,会进入VFS提供的read系统调用。VFS虚拟文件系统在内核层,第一个看到的就是VFS。VFS背后??的思想是抽象出一个通用的文件系统模型,为我们开发者或者用户提供一套通用的接口,这样我们就不用关心具体文件系统的实现了。VFS提供了四种核心数据结构,定义在内核源码的include/linux/fs.h和include/linux/dcache.h中。superblock:Linux用来标记具体安装的文件系统的信息inode:Linux中每个文件都有一个inode,可以把inode理解为文件的身份证file:内存中的文件对象,用来保存进程与磁盘文件的对应desty:目录项,是路径的一部分,所有的目录项对象串在一起构成Linux下的目录树。围绕这四种核心数据结构,VFS也定义了一系列的操作方法。比如inode的操作方法定义了inode_operations(include/linux/fs.h),里面定义了我们非常熟悉的mkdir和rename。structinode_operations{......int(*link)(structdentry*,structinode*,structdentry*);int(*unlink)(structinode*,structdentry*);int(*mkdir)(structinode*,structdentry*,umode_t);int(*rmdir)(structinode*,structdentry*);int(*rename)(structinode*,structdentry*,structinode*,structdentry*,unsignedint);......file对应的操作方法file_operations定义了我们常用的读写:structfile_operations{......ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);......int(*mmap)(structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*,fl_owner_tid);PageCache往下看VFS层,我们注意到了PageCache。它的中文翻译叫做pagecache,是Linux内核使用的主要磁盘缓存。它是一个纯内存工作组件,其作用是加快对速度相对较慢的磁盘的访问。如果要访问的文件块恰好存在于PageCache中,则不会发生实际的磁盘IO。如果不存在,则申请新的page,发出缺页中断,然后用从磁盘读取的block内容填充,下次直接使用。Linux内核使用搜索树来有效地管理大量页面。如果有特殊需求,想绕过PageCache,设置DIRECT_IO即可。有两种情况需要绕过:测试磁盘IO的真实性能以节省使用PageCache时系统调用陷入内核模式,以及将内核内存复制到用户进程内存的开销。我在之前的文章《新建一个空文件占用多少磁盘空间?》和《理解格式化原理》中讨论的文件系统都是具体的文件系统。文件系统中最重要的两个概念是inode和block,我们在之前的文章中都见过。一个块有多大?这是由格式化时的运维决定的。一般默认为4KB。每个文件系统除了inode和block之外,还定义了自己的实际操作函数。例如ext4中定义的ext4_file_operations和ext4_file_inode_operations如下:const_struc{.setattr=ext4_setattr,.getattr=ext4_file_getattr,......};通用块层通用块层是一个内核模块,负责处理系统中所有的块设备IO请求。它定义了一个名为bio的数据结构来表示一个IO操作请求(include/linux/bio.h)。那么bio页或扇区中对应的IO大小单位是多少呢?不是,是阿呆!每个生物可能包含多个部分。一个segment是一个完整的页面,或者是一个页面的一部分,详情请参考https://www.ilinuxkernel.com/files/Linux.Generic.Block.Layer.pdf。为什么想出这么个莫名其妙的东西?这是因为在磁盘上连续存储的数据到达内存PageCache时可能并不连续。出现这种情况是正常的。不能说我非得用连续的空间把磁盘中连续的数据缓存到内存中。段是为了让一个内存IODMA到内存中不连续的多个“段”地址。一个常见的sector/segment/page大小对比如下图所示:IO调度层当一般block层真正发出IO请求时,可能不会立即执行。因为调度层会从大局出发,尽量提高整体的磁盘IO性能。一般的工作方式是让磁头像电梯一样工作,先往一个方向走,最后再回来,这样磁盘效率会更高。具体算法包括noop、deadline和cfg。在您的机器上,使用dmesg|grep-ischeduler查看你的linux支持的算法,测试时可以选择其中一种。在读取文件的过程中,我们已经引入了LinuxIO栈中所有的内核组件。下面我们从头来过一遍读取文件的过程。lib中的read函数首先进入系统调用sys_read,然后进入VFS中的vfs_read、generic_file_read等函数。vfs中的generic_file_read会判断缓存是否命中,未命中则返回命中内核在PageCache中分配新的页框,发出缺页中断,内核向通用块层发起块I/O请求,块设备屏蔽了磁盘和U盘的区别,一般块层使用bio表示的I/O请求放入IO请求队列。IO调度层使用电梯算法对队列中的请求进行调度。驱动程序向磁盘控制器发送读取命令,DMA方式直接将新的页框填充到PageCache中。控制器向内核发送中断通知。将用户需要的1个字节填入用户内存,然后你的进程就被唤醒了。可以看到如果PageCache命中,根本就没有磁盘IO。因此,不要认为代码中有多个读写文件的逻辑会降低性能。操作系统已经为你做了很多优化。内存级别的访问延迟约为ns级别,比机械磁盘IO快2-3个数量级。如果你的内存够大,或者你的文件访问够频繁,其实这个时候的读操作很少有真正的磁盘IO。我们再来看第二种情况。如果PageCache未命中,Linux实际执行了多少字节的磁盘IO。整个IO过程涉及到几个内核组件。每个组件使用不同长度的块来管理磁盘数据。PageCache是??以页为单位的,Linux的页大小一般为4KB(为了避免吹毛求疵,先说Linux可以设置大内存页)。文件系统以块为单位进行管理。使用dumpe2fs查看。一般一个block默认是4KB。通用块层以段为单位处理磁盘IO请求。段是页面或页面的一部分。IO调度器通过DMA将N个扇区传输到内存中。扇区一般为512字节。硬盘也用“扇区”来管理和传输数据。可以看出,虽然从用户的角度我们只读取了1个字节(在一开始的代码中,我们只给这个磁盘IO预留了一个字节的缓冲区)。但在整个内核工作流程中,最小的工作单位是磁盘扇区,为512字节,远大于1字节。此外,块和页缓存等高级组件以更大的单元工作,因此实际的磁盘读取是用很多字节执行的。假设段是一个内存页,一个磁盘IO是4KB(8个扇区512字节)一起读。我们在Linux内核中没有谈到的是,有一套复杂的预读策略。因此,在实践中,可能多于8个扇区一起传输到内存中。最后,操作系统的初衷是为了让你简单可靠,尽可能让你把它当成一个黑盒子。如果你想要一个字节,它会给你一个字节,但它默默地做了很多工作。虽然我们国内的开发大多不低级,但是如果你很在意你的应用的性能,你应该了解操作系统什么时候悄悄提升了你的性能,以及如何提升。以便将来某个时刻当您的在线服务器即将出现故障时,您可以快速找出问题所在。让我们再次展开它。如果PageCachemiss,会不会有磁盘IO传到机械轴上?其实也不一定,为什么,因为当前磁盘本身就会有缓存。另外,目前的服务器会构建一个磁盘阵列,磁盘阵列中的核心硬件Raid卡也会集成RAM作为缓存。只有当所有的缓存都没有命中时,带有磁头的机械轴才会真正起作用。练内功练硬盘特技:1、开盘:剥去机械硬盘的硬外衣!2.磁盘分区也暗示技术水平。3、机械硬盘速度慢,容易坏,如何解决?4.拆解SSD结构5.一个新的空文件占用多少磁盘空间?6.一个只有1字节的文件实际占用多少磁盘空间?7、为什么文件太多时ls命令会卡住?8.理解格式化原理9.读取文件一个字节实际会发生多少磁盘IO?10.文件写入一个字节后什么时候开始写磁盘IO?11、机械硬盘的随机IO比你想象的要慢。12、服务器配置固态硬盘比机械硬盘快多少?我的是《练内功》。在这里我不是简单地介绍技术理论,也不是只是介绍实践经验。而是理论联系实际,用实践加深对理论的理解,用理论提高技术实践能力。欢迎关注我的,分享给你的朋友吧~~~
