加入收藏 | 设为首页 | 会员中心 | 我要投稿 云计算网_宿迁站长网 (https://www.0527zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

Linux 编辑器之神 vim 的 IO 存储原理

发布时间:2022-10-20 14:32:00 所属栏目:Linux 来源:转载
导读: 触发的回调是 ex_write ,核心的函数是 buf_write ,这个函数 1987 行。
在这函数,会使用 mch_open 创建一个 backup 文件,名字后面带个 ~ ,比如 test.txt~ ,
bfd?=?mch_open((char?*)b

触发的回调是 ex_write ,核心的函数是 buf_write ,这个函数 1987 行。

在这函数,会使用 mch_open 创建一个 backup 文件,名字后面带个 ~ ,比如 test.txt~ ,

bfd?=?mch_open((char?*)backup

拿到 backup 文件的句柄,然后拷贝数据(就是一个循环喽), 每 8K 操作一次,从 test.txt 拷贝到 test.txt~ ,以做备份。

划重点:如果是 test.txt 是超大文件,那这里就慢了哦。

backup 循环如下:

//?buf_write?
?while?((write_info.bw_len?=?read_eintr(fd,?copybuf,?WRITEBUFSIZE))?>?0)
?{
??if?(buf_write_bytes(&write_info)?==?FAIL)
???//?如果失败,则终止
??//?否则直到文件结束
??}
?}

我们看到,干活的是 buf_write_bytes ,这是 write_eintr 的封装函数,其实也就是系统调用 write 的函数,负责写入一个 buffer 的数据到磁盘文件。

long?write_eintr(int?fd,?void?*buf,?size_t?bufsize)?{
????long????ret?=?0;
????long????wlen;

????while?(ret?<?(long)bufsize)?{
????????//?封装的系统调用?write?
????????wlen?=?vim_write(fd,?(char?*)buf?+?ret,?bufsize?-?ret);
????????if?(wlen?<?0)?{
????????????if?(errno?!=?EINTR)
????????????break;
????????}?else
????????????ret?+=?wlen;
????}
????return?ret;
}

backup 文件拷贝完成之后,就可以准备动原文件了。

思考:为什么要先文件备份呢?

留条后路呀,搞错了还有的恢复,这个才是真正的备份文件。

修改原文件之前的第一步,ftruncate 原文件到 0,然后,从 memline (内存 + swp)中拷贝数据,写回原文件。

划重点:这里又是一次文件拷贝,超大文件的时候,这里可能巨慢哦。

for?(lnum?=?start;?lnum?<=?end;?++lnum)
{
????//?从?memline?中获取数据,返回一个内存?buffer(?memline?其实就是内存和?swap?文件的一个封装)
????ptr?=?ml_get_buf(buf,?lnum,?FALSE)?-?1;

????//?将这个内存?buffer?写到原文件
????if?(buf_write_bytes(&write_info)?==?FAIL)
????{
????????end?=?0;????????//?write?error:?break?loop
????????break;
????}
????//?...
}

划重点:vim 并不是调用 pwrite/pread 这样的调用来修改原文件,而是把整个文件清空之后,copy 的方式来更新文件。涨知识了。

这样就完成了文件的更新啦,最后只需要删掉 backup 文件即可。

//?Remove?the?backup?unless?'backup'?option?is?set?or?there?was?a
//?conversion?error.
mch_remove(backup);

这个就是我们数据写入的完整流程啦。是不是没有你想的那么简单!

简单小结下:当修改了 test.txt 文件,调用 :w 写入保存数据的时候发生了什么?

人机交互,:w 触发调用 ex_write 回调函数,于 do_write -> buf_write 完成写入 ;具体操作是:先备份一个 test.txt~ 文件出来(全拷贝);接着,原文件 test.txt 截断为 0linux vim配置文件,从 memline( 即 内存最新数据 + .test.txt.swap 的封装)拷贝数据,写入 test.txt (全拷贝) ;

数据组织结构

linux配置vim_linux vim配置文件_vim linux配置

之前讲的太细节,我们从数据组织的角度来解释下。vim 针对用户对文件的修改,在原文件之上,封装了两层抽象:memline,memfile 。分别对应文件 memline.c ,memfile.c 。

vim linux配置_linux vim配置文件_linux配置vim

先说 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 文件组织:

vim linux配置_linux vim配置文件_linux配置vim

block 0 是特殊块,结构体占用 1024 个字节内存,写到文件是按照 1 个page 对齐的,所以是 4096 个字节。如下图:

linux vim配置文件_vim linux配置_linux配置vim

block 其他两种类型:

#define?DATA_ID????????(('d'?<<?8)?+?'a')???//?data?block?id
#define?PTR_ID????????(('p'?<<?8)?+?'t')???//?pointer?block?id

这个 ID 相当于魔数,在 swp 文件中很容易识别出来,比如在下面的文件中第一个 4k 存储的是 block0,第二个 4k 存储的是 pointer 类型的 block。

vim linux配置_linux配置vim_linux vim配置文件

第三,第四个 4k 存储的是一个 data 类型的 block ,里面存储了原文件数据。

vim linux配置_linux配置vim_linux vim配置文件

当用户修改一行的时候,对应到 memline 的一个 line 的修改,对应到这行 line 在哪个 block 的修改,从而定期的刷到 swap 文件。

linux配置vim_linux vim配置文件_vim linux配置

vim 特殊的文件 ~ 和 .swp ?

linux配置vim_linux vim配置文件_vim linux配置

假设原文件名称: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 才是最新的数据。

思考解答

linux配置vim_linux vim配置文件_vim linux配置

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 的磁盘空间。

总结

linux配置vim_linux vim配置文件_vim linux配置

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 行代码。。。不是我打击各位的积极性,这。。。反正我不想再看见它了。。。

后记

linux配置vim_linux vim配置文件_vim linux配置

对于 vim 的好奇让笔者撸了一遍源码,学习了下其中的 IO 知识,不想被动辄几千行一个的函数教育了一番。我再也不想撸它了。。你学 fei 了吗?

- EOF -

(编辑:云计算网_宿迁站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!