当前位置: 首页 > Linux

写了一个字节的文件后什么时候开始写磁盘IO

时间:2023-04-06 11:39:08 Linux

写完上一篇《read文件一个字节实际会发生多大的磁盘IO?》,想偷懒就通过读操作让大家了解一下LinuxIO栈的各个模块。但是很多同学让我再写一篇写操作的文章。既然很多人都有这个需求,那我就写一下吧。Linux内核实在是太复杂了。源代码行数从1.0版本的几万行变成了千万行。如果直接钻进去,很容易在各种眼花缭乱的叫声中迷失方向,再也出不来。我给大家分享一个我正在思考内核的方法。一般来说,我首先想到一个我很想弄清楚的问题。无论在代码中怎么跳来跳去,都要时刻记住自己的问题,尽量少发散不相关的部分,只要弄清楚自己的问题就行。现在想弄明白的问题是,在最常见的情况下,不带O_DIRECT或者O_SYNC(写文件的方式有很多种,有sync方式,direct方式,mmap内存映射方式),怎么写write。c的代码示例如下:#includeintmain(){charc='a';内部输出;out=open("out.txt",O_WRONLY|O_CREAT|O_TRUNC);写(输出,&c,1);...}为了进一步细化我的问题,写入函数在我们向未解决的问题写入一个字节后如何在内核中执行?数据什么时候才能真正写入磁盘?在我们讨论的过程中,难免会涉及到内核代码。我使用的内核版本是3.10.1。如果需要,您可以到这里下载。https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/。write函数的实现分析跟踪了很久write写入ext4文件系统时的各种调用和返回,大致搞出了一个交互图。当然,为了突出重点,我舍弃了很多细节,比如DIRECTIO、ext4日志记录等都没有体现出来,只提取了一些我认为是重点的调用。在上面的流程图中,所有的写操作在哪里结束?在最后的__block_commit_write中,makedirty即可。然后大部分时间你的函数调用返回(balance_dirty_pages_ratelimited稍后)。数据还在内存中的PageCache中,并没有真正写入硬盘。为什么要这样实现,而不是直接写入硬盘呢?原因是硬盘,尤其是机械硬盘的性能太慢了。服务器级别的万转盘,最差的随机访问平均延迟在毫秒级别,换算成IOPS也只有100多,不到200IOPS。想象一下,如果你后端接口的每个用户都需要一个随机的磁盘IO来访问,不管你的服务器有多强大,每秒200个qps会直接炸毁你的硬盘,我相信会是百万/千的你提供的数万/上亿用户接口,这绝对让你受不了。Linux这样做也有副作用。如果接下来服务器断电,内存中的所有内容都会丢失。所以Linux有另外一个“补丁”——延迟写入,来帮助我们缓解这个问题。请注意,我说的是缓解措施,而不是完整的解决方案。先说balance_dirty_pages_ratelimited,虽然大多数情况下是直接写入PageCache返回。但是在一种情况下,用户进程必须等待写入完成才能返回,即如果balance_dirty_pages_ratelimit的判断超过了限制。该函数判断当前脏页是否已经超过脏页上限dirty_bytes、dirty_ratio,超过则必须等待。这两个参数只有一个会生效,另一个为0。以dirty_ratio为例,如果设置为30,表示如果脏页的比例超过内存的30%,write函数调用必须等待写入完成才能返回。你可以在你机器下的/proc/sys/vm/目录下查看这两个配置。#cat/proc/sys/vm/dirty_bytes0#cat/proc/sys/vm/dirty_ratio30内核延迟写入内核什么时候真正向硬盘写入数据?为了快速摸清全貌,我想到的办法是用systemtap工具在内核写IO过程中找到一个关键函数,然后把里面的函数调用栈打出来。查了半天资料,决定使用do_writepages函数。#!/usr/bin/stapprobekernel.function("do_writepages"){printf("------------------------------------------------------\n");打印回溯();printf("------------------------------------------------------\n");}systemtab跟踪以后,打印信息如下:0xffffffff8118efe0:do_writepages+0x0/0x40[kernel]0xffffffff8122d7d0:__writeback_single_inode+0x40/0x220[kernel]0xffffffff8122e414:writeback_sb_inodes+0x1c4/0x490[kernel]0xffffffff8122e77f:__writeback_inodes_wb+0x9f/0xd0[kernel]0xffffffff8122efb3:wb_writeback+0x263/0x2f0[kernel]0xffffffff8122f35c:bdi_writeback_workfn+0x1cc/0x460[kernel]0xffffffff810a881a:process_one_work+0x17a/0x440[kernel]0xffffffff810a94e6:worker_thread+0x126/0x3c0[kernel]0xffffffff810b098f:kthread+0xcf/0xe0[kernel]0xffffffff816b4f18:ret_from_fork+0x58/0x90[kernel]从上面的输出我们可以看出真正的写文件进程是由worker内核线程发出的(和我们自己的应用进程没有关系,此时我们的应用是程序的写函数调用已经返回)。这个工作线程的writeback是周期性执行的,它的周期取决于内核参数dirty_writeback_centisecs的设置。根据参数名可以看出它的单位是百分之一秒。#cat/proc/sys/vm/dirty_writeback_centisecs500我查了下我的配置是500,也就是说每隔5秒会周期性的执行一次。回过头来看我们的问题,我们最关心的问题是什么时候写的,围绕这个的思路也没有太大的分歧。于是沿着这个调用栈一直跟踪跳转,最后找到了如下代码。在下面的代码中,我们可以看到如果是for_background模式,over_bground_thresh判断成功,就会开始回写。staticlongwb_writeback(structbdi_writeback*wb,structwb_writeback_work*work){work->older_than_this=&oldest_jif;...if(work->for_background&&!over_bground_thresh(wb->bdi))中断;...if(work->for_kupdate){oldest_jif=jiffies-msecs_to_jiffies(dirty_expire_interval*10);}else...}staticlongwb_check_background_flush(structbdi_writeback*wb){if(over_bground_thresh(wb->bdi)){...returnwb_writeback(wb,&work);那么over_bground_thresh函数判断的是什么呢?其实就是判断当前脏页是否超过了内核参数中dirty_background_ratio或者dirty_background_bytes的配置。不超过就不写了(代码位于fs/fs-writeback.c:1440,限于篇幅就不贴了)。这两个参数只有一个会真正生效,其中dirty_background_ratio配置的是比例,dirty_background_bytes配置的是字节。这两个参数在我的机器上配置如下,表示脏页比例超过10%,开始回写。#cat/proc/sys/vm/dirty_background_bytes0#cat/proc/sys/vm/dirty_background_ratio10如果脏页从未超过这个比例,你是否停止写入?不。在上面的wb_writeback函数中,我们可以看到如果是for_kupdate模式,它会记录一个过期标记到work->older_than_this,然后在下面的代码中写回满足这个条件的页面。dirty_expire_interval这个变量是从哪里来的?在kernel/sysctl.c中,我们找到了线索。哦,原来是来自/proc/sys/vm/dirty_expire_centisecs配置。1158{1159.procname="dirty_expire_centisecs",1160.data=&dirty_expire_interval,1161.maxlen=sizeof(dirty_expire_interval),1162.mode=0644,1163.proc_handler=proc_dointvec_minmax,1164.extra1=.16机器上的零&zero},,它的值为3000,单位是百分之一秒,所以30秒后,脏页会被内核线程认为需要写回磁盘。#cat/proc/sys/vm/dirty_expire_centisecs3000结束语我们demo代码中的写法,其实大部分情况都是写入PageCache返回,此时并没有真正写入磁盘。我们的数据会在以下3次真正写入磁盘IO请求:第一种情况,write系统调用,如果发现PageCache中的脏页比例过大,超过dirty_ratio或dirty_bytes,写入必须等待。第二种情况,write写入PageCache后已经返回。worker内核线程异步运行时,再次判断脏页比例。如果超过dirty_background_ratio或dirty_background_bytes,也发起回写请求。在第三种情况下,返回了相同的写入调用。worker内核线程异步运行时,虽然系统中的脏页没有超过dirty_background_ratio或dirty_background_bytes,但是脏页在内存中停留时间超过dirty_expire_centisecs,也会发起写入。如果对上面的配置不满意,可以通过修改/etc/sysctl.conf自行调整,修改后别忘了执行sysctl-p。最后,我们要认识到,这种写pagecache+write-back机制的首要目标是性能,而不是保证我们写的数据不会丢失。如果此时断电,时间还没有超过dirty_expire_centisecs的脏页就真的丢失了。如果你正在做一个非常重要的与钱有关的业务,并且你必须确保订单完成才能返回,那么你可能需要考虑使用fsync。练内功练硬盘特技:1、开盘:剥去机械硬盘的硬外衣!2.磁盘分区也暗示技术水平。3、机械硬盘速度慢,容易坏,如何解决?4.拆解SSD结构5.一个新的空文件占用多少磁盘空间?6.一个只有1字节的文件实际占用多少磁盘空间?7、为什么文件太多时ls命令会卡住?8.理解格式化原理9.读取文件一个字节实际会发生多少磁盘IO?10.文件写入一个字节后什么时候开始写磁盘IO?11、机械硬盘的随机IO比你想象的要慢。12、服务器配置固态硬盘比机械硬盘快多少?我的公众号是《练内功》。在这里,我不是简单地介绍技术理论,也不是只介绍实践经验。而是理论联系实际,用实践加深对理论的理解,用理论提高技术实践能力。欢迎关注我的公众号,分享给你的朋友吧~~~