Linux 编辑器之神 vim 的 IO 存储原理
在这函数,会使用 mch_open 创建一个 backup 文件,名字后面带个 ~ ,比如 test.txt~ ,
bfd?=?mch_open((char?*)b
触发的回调是 ex_write ,核心的函数是 buf_write ,这个函数 1987 行。 在这函数,会使用 mch_open 创建一个 backup 文件,名字后面带个 ~ ,比如 test.txt~ ,
拿到 backup 文件的句柄,然后拷贝数据(就是一个循环喽), 每 8K 操作一次,从 test.txt 拷贝到 test.txt~ ,以做备份。 划重点:如果是 test.txt 是超大文件,那这里就慢了哦。 backup 循环如下:
我们看到,干活的是 buf_write_bytes ,这是 write_eintr 的封装函数,其实也就是系统调用 write 的函数,负责写入一个 buffer 的数据到磁盘文件。
backup 文件拷贝完成之后,就可以准备动原文件了。 思考:为什么要先文件备份呢? 留条后路呀,搞错了还有的恢复,这个才是真正的备份文件。 修改原文件之前的第一步,ftruncate 原文件到 0,然后,从 memline (内存 + swp)中拷贝数据,写回原文件。 划重点:这里又是一次文件拷贝,超大文件的时候,这里可能巨慢哦。
划重点:vim 并不是调用 pwrite/pread 这样的调用来修改原文件,而是把整个文件清空之后,copy 的方式来更新文件。涨知识了。 这样就完成了文件的更新啦,最后只需要删掉 backup 文件即可。
这个就是我们数据写入的完整流程啦。是不是没有你想的那么简单! 简单小结下:当修改了 test.txt 文件,调用 :w 写入保存数据的时候发生了什么? 人机交互,:w 触发调用 ex_write 回调函数,于 do_write -> buf_write 完成写入 ;具体操作是:先备份一个 test.txt~ 文件出来(全拷贝);接着,原文件 test.txt 截断为 0linux vim配置文件,从 memline( 即 内存最新数据 + .test.txt.swap 的封装)拷贝数据,写入 test.txt (全拷贝) ; 数据组织结构 之前讲的太细节,我们从数据组织的角度来解释下。vim 针对用户对文件的修改,在原文件之上,封装了两层抽象:memline,memfile 。分别对应文件 memline.c ,memfile.c 。 先说 memline 是啥? 对应到文本文件中的每一行,memline 是基于 memfile 的。 memline 基于 memfile,那 memfile 又是啥? 这个是一个虚拟内存空间的实现,vim 把整个文本文件映射到内存中,通过自己管理的方式。这里的单位为 block,memfile 用二叉树的方式管理 block 。block 不定长,block 由 page 组成,page 为定长 4k 大小。 这是一个典型虚拟内存的实现方案,编辑器的修改都体现为对 memfile 的修改,修改都是修改到 block 之上,这是一个线性空间,每个 block 对应到文件的要给位置,有 block number 编号,vim 通过策略会把 block 从内存中换出,写入到 swp 文件,从而节省内存。这就是 swap 文件的名字由来。 block 区分 3 种类型: block 0 块:树的根,文件元数据;pointer block:树的分支,指向下一个 block;data block:树的叶子节点,存储用户数据; swap 文件组织: block 0 是特殊块,结构体占用 1024 个字节内存,写到文件是按照 1 个page 对齐的,所以是 4096 个字节。如下图: block 其他两种类型:
这个 ID 相当于魔数,在 swp 文件中很容易识别出来,比如在下面的文件中第一个 4k 存储的是 block0,第二个 4k 存储的是 pointer 类型的 block。 第三,第四个 4k 存储的是一个 data 类型的 block ,里面存储了原文件数据。 当用户修改一行的时候,对应到 memline 的一个 line 的修改,对应到这行 line 在哪个 block 的修改,从而定期的刷到 swap 文件。 vim 特殊的文件 ~ 和 .swp ? 假设原文件名称:test.txt 。 1test.txt~ 文件 test.txt~ 文件估计很多人都没见过,因为消失的太快了。这个文件在修改原文件之前生成,修改原文件之后删除。作用于只存在于 buf_write ,是为了安全备份的。 划重点:test.txt~ 和 test.txt 本质是一样的,没有其他特定格式,是用户数据。 读者朋友试试 vim 一个 10 G的文件,然后改一行内容,:w 保存,应该很容易发现这个文件(因为备份和回写时间巨长 )。 2.test.txt.swp 文件 这个文件估计绝大多数人都见过,.swp 文件生命周期存在于整个进程的生命周期,句柄是一直打开的。很多人认为 .test.txt.swp 是备份文件,其实准确来讲并不是备份文件,这是为了实现虚拟内存空间的交换文件,test.txt~ 才是真正的备份文件。swp 是 memfile 的一部分,前面 4k 为 header 元数据,后面的为 一个个 4k 的数据行封装。和用户数据并不完全对应。 memfile = 内存 + swp 才是最新的数据。 思考解答 1vim 存储原理是啥? 没啥,就是用的 read,write 这样的系统调用来读写数据而已。 2vim 的过程有两种冗余的文件? test.txt~ :是真正的备份文件,诞生于修改原文件之前,消失于修改成功之后;.test.txt.swp :swap 文件,由 block 组成,里面可能由用户未保存的修改,等待:w 这种调用,就会覆盖到原文件; 3vim 编辑超大文件的时候为什么慢? 一般情况下,你能直观感受到,慢在两个地方: vim 打开的时候;修改了一行内容,:w 保存的时候; 先说第一个场景:vim 一个 10G 的文件,你的直观感受是啥? 我的直观感受是:命令敲下之后,可以去泡杯茶,等茶凉了一点,差不多就能看到界面了。为什么? 在进程初始化的时候,初始化窗口之前,create_windows -> open_buffer 里面调用 readfile 会把整个文件读一遍(完整的读一遍),在屏幕上展示编码过的字符。 划重点:初始化的时候,readfile 会把整个文件读一遍。 10 G的文件,你可想而知有多慢。我们可以算一下,按照单盘硬件 100 M/s 的带宽来算,也要 102 秒的时间。 再说第二个场景:喝了口茶,改了一个单词,:w 保存一下,妈呀,命令敲下之后,又可以去泡杯茶了?为什么? 先拷贝出一个 10G 的 test.txt~ 备份文件,102 秒就过去了;test.txt 截断为 0,再把 memfile( .test.txt.swp )拷贝回 test.txt ,数据量 10 G,102 秒过去了(第一次可能更慢哦); 4vim 编辑大文件的时候,会有空间膨胀? 是的,vim 一个 test.txt 10 G 的文件,会存在某个时刻,需要 >=30 G 的磁盘空间。 总结 vim 编辑文件并不没有用黑魔法,还是用的 read,write,朴实无华;vim 编辑超大文件,打开很慢,因为会读一遍文件( readfile ),保存的时候很慢,因为会读写两遍文件(backup 一次,memfile 覆盖写原文件一次);memfile 是 vim 抽象的一层虚拟存储空间(物理上由内存 block 和 swp 文件组成)对应一个文件的最新修改,存储单元由 block 构成。:w 保存的时候,就是从 memfile 读,写到原文件的过程;memline 是基于 memfile 做的另一层封装,把用户的文件抽象成“行”的概念;.test.txt.swp 文件是一直 open 的,memfile 会定期的交换数据进去,以便容灾恢复;test.txt~ 文件才是真正的备份文件,诞生于 :w 覆盖原文件之前,消失于成功覆写原文件之后;vim 基本都是整个文件的处理,并不是局部处理,大文件的编辑根本不适合 vim ,话说回来,正经人谁会用 vim 编辑 10 G 的文件?vim 就是个文本编辑器呀;一个 readfile 函数 2533 行,一个 buf_write 函数 1987 行代码。。。不是我打击各位的积极性,这。。。反正我不想再看见它了。。。 后记 对于 vim 的好奇让笔者撸了一遍源码,学习了下其中的 IO 知识,不想被动辄几千行一个的函数教育了一番。我再也不想撸它了。。你学 fei 了吗? - EOF - (编辑:成都站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |