精彩导读:
一个人的快乐,快乐有可能是假的,一群人的快乐,快乐已经分不出真假。他们尽情挥霍着自己的青春,恨不得就此燃烧殆尽,那架势就像末日前的狂欢。
有许多朋友给我们发了信息询问各方面的问题,今天呆玛网将为大家来详细介绍“linux原理和方法”。希望对你们有所帮助!原创内容如下:
无意间用vim打开了一个10G的文件,改了一行内容,:w保存了一下,慢的我哟,耗费的时间够泡几杯茶了。这引起了我的奇怪,vim打开和保存到底做了啥?
vim—写器之神
vim号称写器之神,以极其厉害的扩展性和功能闻名。vi/vim作为标准的写器存在于Linux的几乎每一种发行版里。vim的学习曲线有那么一点陡峭的,前期一定有一个磨炼的过程。
vim是一个终端写器,在可视化的写器横行的今天,怎么vim还如此重要?
因为一些场景非它不可,例如线上服务器终端,除vi/vim这种终端写器,你别无选择。
vim的简史很悠久,Github有个文档归纳了vim的简史进程:vim简史,Github开源代码:代码仓库。
笔者今天不讲vim的用法,这种文章网络随便搜一大把。笔者将从vim的存储IO原理的角度来剖析下vim这种神器。
思考几个小问题,读者如果有兴趣,应该继续往下读哦:
vim写文件的原理是啥,用了啥黑科技吗?
vim打开一个10G的大型文件,怎么怎么这么慢,里面做了啥?
vim改写一个10G的大型文件,:w保存的时候,感觉更慢了?怎么?
vim貌似会发生多余的文件?~文件?.swp文件?都是做啥的呢?
划重要时机:由于vim的功能过于厉害,一篇共享开始说不完,本文文章聚焦IO,从存储的角度剖析vim原理。
vim的io原理声明,系统和Vim版本如下:操作面板系统版本:Ubuntu16.04.6LTSVIM版本:VIM–ViIMproved8.2(2019Dec12,compiledJul25202308:44:54)测试文件名:test.txt
vim只是一个二进制程序而已。读者朋友也应该Github安装,编译,自己调试哦,效果更优质。
往往一般使用vim写文件很无脑,只要vim后面跟文件名就可:
vimtest.txt这样就打开了文件,并且应该进行写。这种命令敲下去,往往一般状态下,我们就能很快在终端很观看到的文件的内容了。
Linux写器之神vim的IO存储原理
这种过程发生了什么?先明确下,vimtest.txt到底是啥意思?
本质只是运行一个叫做vim的程序,argv[1]参数是test.txt嘛。跟你曾经写的helloworld程序没啥不一样,只不过vim这种程序应该终端人机交互。
所以这种过程无非只是一个进程初始化的过程,由main开始,到main_loop(后台循环监听)。
1vim进程初始化vim有一个main.c的入口文件,main函数就定义在这里。首先会做一下操作面板系统有关的初始化(mch是machine的缩写):
mch_early_init();
之后会,做一下赋值参数,全局变量的初始化:
/*
*Variousinitialisationssharedwithtests.
*/
common_init(?ms);
举个举例test.txt这样的参数必定要赋值到全局变量中,因为未来是要总是使用的。
另外类似于命令的map表,是静态定义好了的:staticstructcmdname
{
char_u *cmd_name; //nameofthecommand
ex_func_T cmd_func; //functionforthiscommand
long_u cmd_argt; //flagsdeclaredabove
cmd_addr_T cmd_addr_type; //flagforaddresstype
}cmdnames[]={
EXCMD(CMD_write,"write",ex_write,
EX_RANGE|EX_WHOLEFOLD|EX_BANG|EX_FILE1|EX_ARGOPT|EX_DFLALL|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
ADDR_LINES),
}
划重要时机::w,:write,:saveas这样的vim命令,其实是对应到定义好的c回调函数:ex_write。ex_write函数是资料写入的核心函数。再例如,:quit对应ex_quit,用来退出的回调。
换句话说,vim里面支持的类似:w,的命令,其实在初始化的时候就确认了。人为的交互只是输入字符串,vim进程从终端读到字符串之后,寻找对应的回调函数,执行就可。再来,会初始化一些home目录,目前目录等变量。
init_homedir(); //findrealvalueof$HOME
//保存交互参数
set_argv_var(paramp->argv,paramp->argc);
配置一下跟终端窗口展现有关的东西,这部分往往一般是一些终端库有关的:
//初始化终端一些配置
termcapinit(params.term);//setterminalnameandgetterminal
//初始化光标地点
screen_start(); //don'tknowwherecursorisnow
//获取终端的一些消息
ui_get_shellsize(); //initsRowsandColumns
再来会加载.vimrc这样的配置文件,让你的vim与众不一样。
//Sourcestartupscripts.
source_startup_scripts(?ms);
还会加载一些vim插件source_in_path,使用load_start_packages加载package。
下面这种只是第一个交互了,等待客户敲下enter键:
wait_return(TRUE);
我们总是看见的:“PressENTERortypecommandtocontinue“只是在这里执行的。确认完,就说明你真的是要打开文件,并展现到终端了。
怎么打开文件?怎么展现字符到终端屏幕?
这一切都来自于create_windows这种函数。名字也较好理解,只是初始化的时候创建终端窗口来着。
/*
*Createtherequestednumberofwindowsandeditbuffersinthem.
*Alsodoesrecoveryif"recoverymode"set.
*/
create_windows(?ms);
这里其实涉及到两个方面:
把资料读出去,读到内存;
把字符渲染到终端;
怎么把资料从磁盘上读出去,只是IO。怎么渲染到终端这种我们不管,这种使用的是termlib或者ncurses等终端编程库来实现的,有兴趣的应该了解下。
这种函数会调用到我们的第一个核心函数:open_buffer,这种函数做两个时间:
creatememfile:创建一个memory+.swp文件的抽象层,读写资料都会过这一层;
readfile:读原始文件,并解码(用来展现到屏幕);
函数调用栈:->readfile
->open_buffer
->create_windows
->vim_main2
->main
真正干活的是readfile这种函数,评论一下,readfile是一个2533行的函数。。。。。。
readfile里面会择机创建swp文件(曾经一些话,应该用来复原资料),调用的是ml_open_file这种函数,文件创建好之后,size占用4k,里面往往一般是一些特殊的元资料(用来复原资料用的)。
划重要时机:.{文件名}.swp这种掩藏文件是有格式的,前4k为header,后面的内容也是根据一个个block团队的。
再往后走,会调用到read_eintr这种函数,读取资料的内容:
long
read_eintr(intfd,void*buf,size_tbufsize)
{
longret;
 $a['ruku_password']='www.ccvok.com';;for(;;){
ret=vim_read(fd,buf,bufsize);
if(ret>=0||errno!=EINTR)
break;
}
returnret;
}
这是一个最底层的函数,是系统调用read的一个封装,读出去之后。这里回答了一个关键问题:vim的存储原理是啥?
划重要时机:本质上调用read,write,lseek这样朴素的系统调用,而已。
readfile会把二进制的资料读出去,之后进行字符转变编码(根据配置的模式),编码不对只是乱码喽。每次都是根据一个特殊buffer读资料的,例如8192。
划重要时机:readfile会读完文件。这只是怎么当vim打开一个超大文件的时候,会超级慢的原因。
这里提一点题外话:memline这种封装是文件之上的,vim改写文件是改写到内存buffer,vim根据策略来syncmemfile到swp文件,一个是以免丢弃未保存的资料,第二是为了节省内存。
mf_write把内存资料写到文件。在.test.txt.swp中的只是这样的资料结构:
block0的header主要标识:vim的版本;
写文件的路径;
字符编码方法;
这里实现提一个重要知识点:swp文件里存储的是block,block的管理是以一个树形结构进行管理的。block有3种类别:
block0:头部4k,往往一般是存储一些文件的元资料,例如路径,编码模式,时间戳等等;
pointerblock:树形内部节点;datablock:树形叶子节点,存储客户资料;
2敲下:w背后的原理进程初始化我们讲完了,现在来看下:w触发的调用吧。客户敲下:w命令触发ex_write回调(初始化的时候配置好的)。全部的流程皆在ex_write,我们来看下这种函数做了什么。
先撇开代码实现来说,客户敲下:w命令其实只是想保存改写而已。
那么第一个问题?客户的改写在哪里?在memline的封装,只要没执行过:w保存,那么客户的改写就没改写到原文件上(小心哦,没保存曾经,一定没改写原文件哦),这时候,客户的改写可能在内存,也很有可能在swp文件。存储的资料结构为block。所以,:w其实只是把memline里面的资料刷到客户文件而已。怎么刷?
重要时机步骤如下(以test.txt举例):创建一个backup文件(test.txt~),把原文件拷贝出去;
把原文件test.txttruancate截断为0,等于清空原文件资料;
从memline(内存+.test.txt.swp)拷贝资料,从头开始写入原文件test.txt;
删除备份文件test.txt~;以上只是:w做的全部事件了,下面我们看下代码。
触发的回调是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的资料到磁盘文件。
longwrite_eintr(intfd,void*buf,size_tbufsize){
long ret=0;
long wlen;
while(ret
暂无评论,2210人围观