北京治白癜风的儿童医院 https://m-mip.39.net/woman/mipso_4780806.html作者 奇伢责编 欧阳姝黎 概述 Linux下有3种“拷贝”,分别是ln,cp,mv,这3个命令貌似都能copy出一个新的文件出来。细心的小伙伴看到我给“拷贝”打上了双引号?因为Linux的这3个命令有极大的区别,虽然用户看起来是拷贝出了新文件。 你是否曾经遇到过以下问题,想通原因了吗?: ln创建链接文件,软链接可以跨文件系统,硬链接跨文件系统会报错,为什么?;mv好像有时候快,有时候非常慢,有些时候还会残留垃圾,为什么?;cp拷贝数据有时快,有时候非常慢,源文件和目标文件所占物理空间竟然不一致?本篇文章看完,希望你以上问题不再有疑问,从容使用ln,mv,cp命令。 温馨提示: 以下我们只讨论文件的简单操作,关于目录操作或者复杂参数的操作不在我们本次主题以内,我们忽略;corutils库的代码版本用的是8.3;我们来看下简单的3个命令操作。首先在执行以下命令之前,准备一个不小的tst的普通文件(比如1G)。 "拷贝"命令一:ln #创建一个软链接文件ln-s./tst./tst_soft_link#创建一个硬连接文件ln./tst./tst_hard_link 你会发现当前目录出现了两个新文件tst_soft_link,tst_hard_link。并且你会发现拷贝速度好快?为什么呢? "拷贝"命令二:mv 把tst文件"拷贝"到./backup/目录 mv./tst./backup/ 更神奇的是,好像copy一个1G的文件,速度也贼快? “拷贝”命令三:cp 把tst文件"拷贝"到./backup/目录 cp./tst./backup/ 上面我们看到,好像ln,mv,cp这3个命令都是“拷贝”?好像都进行了数据复制出了新的文件? 答案:当然不是。这3个看起来都是复制出了新文件,但其实天壤之别。我们一个个来揭秘。 在揭秘这3个命令之前,我们必须先复习文件的基础知识点,Linux的文件和目录的关系。 Linux的文件和目录 在深度剖析Linuxcp的秘密一文中,我们详细剖析了文件系统的形态。有几个关键知识点: 文件系统内有3个关键区域:超级块区域,inod区域,数据block区域;其中一个inod和一个文件对应,包含了文件的元数据信息;一个inod有唯一的编号,可以理解成就是单调递增的整数。比如1,,3,4,5,6,,,,;关于上面,我们注意到inod其实标识的是一个平坦的结构,inod索引到数据data区域,每个inod都有唯一编号。 问题来了:Linux的目录是一个倒挂的树形结构呀,为什么上面说inod是平坦的结构?如下: Linux的文件确实是树形结构,inod也确实是平坦的结构。你会感觉到因为是因为之前故意忽略了一个几个东西:目录文件和dntry项。这是两个非常重要的概念,我们逐个解释下。 文件系统中其实有两种文件类型,分为: 普通文件(这里把链接文件包含在普通文件以内)目录文件可以通过inod-i_mod字段,使用S_ISREG,S_ISDIR这两个宏来判断是哪个类型。普通文件很容易理解,就是普通的数据文件,inod里面存储元数据,inod可以索引到block,block里面存储用户的数据。目录文件inod存储元数据,block里面存储的是目录条目。目录条目是什么样子的东西? 举个形象的例子:在当前tstdir目录下,有dir1,dir,dir3这三个文件。假设dir1的inod编号是,dir是,dir3是。 那么现实是这样的: tstdir这个目录首先会对应有一个inod,inod-i_mod的类型是目录,并且还会有block块,通过inod-i_blocks能索引到这些block;block里面存储的内容很简单,是一个个目录条目,内核的名字缩写为dirnt,每一个dirnt本质就是一个文件名字到inod编号的映射,所以,tstdir这个目录文件的block里存了3条记录[dir1,],[dir,],[dir3,];所以,目录到底是什么呢?就存储形态而已,目录也是文件,存储的是名字到inodnumbr的映射表。dirnt其实就是dirctoryntry的缩写。 好像还没讲到树形结构? 其实已经讲了一半了,树形结构的数据结构基础已经有了,就是目录文件和dirnt的实现。 假设叶子结点的为普通文件 针对开篇的图,其实磁盘上存储了3个目录文件 这个时候,读者朋友你是不是都可以用笔画出一个树形结构了,内存的树形结构也是这么来的。通过磁盘的映射数据构造出来。在内存中,这个树形结构的节点用dntry来表示(通常翻译成目录项,但是笔者认为这个翻译很容易让人误解)。 以下是笔者从内核精简出来的dntry结构体,通过这个总结到几个信息: dntry绑定到唯一一个inod结构体;dntry有父,子,兄弟的索引路径,有这个就足够在内存中构建一个树了,并且事实也确实如此;structdntry{//...structdntry*d_parnt;/*父节点*/structqstrd_nam;//名字structinod*d_inod;//inod结构体structlist_hadd_child;/*兄弟节点*/structlist_hadd_subdirs;/*子节点*/}; 所以,看到现在理解了吗?父、子指针,这就是经典的树形结构需要的字段呀。目录文件类型为树形结构提供了存储到磁盘持久化的一种形态,是一种map表项的形态,每一个表项我们叫做dirnt。文件树的结构在内存中以dntry结构体体现。 划重点:仔细理解下dirnt和dntry的概念和形态,仔细理解磁盘的数据形态和内存的数据结构形态,后面要考的。 ln命令 ln是Linux的基础命令之一,是link的缩写,顾名思义就是跟链接文件相关的一个命令。一般语法如下: ln[OPTION]...TARGETLINK_NAME ln可以用来创建一个链接文件,有趣的是,链接文件有两个不同的类别: 软链接文件硬链接文件1什么是软链接文件?无论是软链接还是硬链接都是“链接”文件,也就是说,通过这个链接文件都能找到背后的那个“源文件”。首先说结论: 软链接文件是一个全新的文件,有独立的inod,有自己的block,而这个文件类型是“链接文件”的类型而已;这个软链接文件的内容是一段path路径,这个路径直接指向源文件;所以,你明白了吗?软链接文件就是一个文件而已,文件里面存储的是一个路径字符串。所以软链接文件可以非常灵活,链接文件本身和源解耦,只通过一段路径字符串寻路。 所以,软链接文件是可以跨文件系统创建的。 有兴趣的小伙伴可以去看源码实现,在corutils库里,调用栈如下: main-do_link-forc_symlinkat-symlinkat 也就是说最终调用的是系统调用symlinkat来完成创建,而这个symlinkat系统调用在内核由不同的文件系统实现。举个例子,如果是minix文件系统,那么对应的函数就是minix_symlink。minix_symlink这个函数上来就是新建一个inod,然后在对应的目录文件中添加一个dirnt。来来来,我们看一眼minix_symlink的主干代码: staticintminix_symlink(structinod*dir,structdntry*dntry,constchar*symnam){//...//新建一个inod,inod类型为S_IFLNK链接类型inod=minix_nw_inod(dir,S_IFLNK ,rr);if(!inod)gotoout;//填充链接文件内容minix_st_inod(inod,0);rr=pag_symlink(inod,symnam,i);if(rr)gotoout_fail;//绑定dntry和inodrr=add_nondir(dntry,inod);//...} 划重点:软链接文件是新建了一个文件,文件类型是链接文件,文件内容就是一段字符串路径。分配新的inod,内存对应新的dntry,当然了,也新增了一个dirnt。软链文件可以跨越不同的文件系统。 什么是硬链接文件?现在我们知道了,软链接文件怎么找到源文件的?通过路径找到的,路径就存储在软链接文件中。硬链接文件又怎么办到的呢? 硬链接很神奇,硬链接其实是新建了一个dirnt而已。下面是重点: 硬链接文件其实并没有新建文件(也就是说,没有消耗inod和文件所需的block块);硬链接其实是修改了当前目录所在的目录文件,加了一个dirnt而已,这个dirnt用一个新的nam名字指向原来的inodnumbr;重点来了,由于新旧两个dirnt都是指向同一个inod,那么就导致了一个限制:不能跨文件系统。因为,不同文件系统的inod管理都是独立的。 感兴趣的同学可以试下,跨文件系统创建硬链接就会报告如下错误:Invalidcross-dviclink sh-4.4#ln/dv/shm/sourc.txt./dst.txtln:faildtocrathardlink./dst.txt=/dv/shm/sourc.txt:Invalidcross-dviclink 有兴趣的小伙伴可以去看源码实现,在corutils库里,调用栈如下: main-do_link-forc_linkat-linkat 也就是说最终调用的是系统调用linkat来完成创建,而这个linkat系统调用在内核由不同的文件系统实现。举个例子,如果是minix文件系统,那么对应的函数就是minix_link。这个函数从内存上来讲是把一个dntry和inod关联起来。从磁盘数据结构上来讲,会在对应目录文件中增加一个dirnt项。 划重点:硬链接只增加了一个dirnt项,只修改了目录文件而已。不涉及到inod数量的变化。新的nam指向原来的inod。 mv命令 mv是mov的缩写,从效果上来看,是把源文件搬移到另一个位置。 你是否思考过mv命令内部是怎么实现的呢? 是把源文件拷贝到目标位置,然后删除源文件吗?所以,说mv貌似也是“拷贝”? 其实,并不是,准确的说不完全是。 对于mv的讨论,要拆分成源和目的文件是否在同一个文件系统。 1源和目的在同一个文件系统mv命令的核心操作是系统调用rnam,rnam从内核实现来说只涉及到元数据的操作,只涉及到dirnt的增删(当然不同的文件系统可能略有不同,但是大致如是)。通常操作是删除源文件所在目录文件中的dirnt,在目标目录文件中添加一个新的dirnt项。 划重点:inodnumbr不变,inod不变,不增不减,还是原来的inod结构体,所以数据完全没有拷贝。 mv的调用栈如下,感兴趣的可以自己调试。 main-rnamatmain-movfil-do_mov-copy-copy_intrnal-rnamat 我们用例子来直观看下,首先准备好一个sourc.txt文件,用stat命令看下元数据信息: sh-4.4#statsourc.txtFil:sourc.txtSiz:0Blocks:0IOBlock:rgularmptyfilDvic:78h/10dInod:Links:1Accss:(/-rw-r--r--)Uid:(0/root)Gid:(0/root) 我们看到inod编号是:。然后执行mv命令: sh-4.4#mvsourc.txtdst.txt 然后stat看下dst.txt文件的信息: sh-4.4#statdst.txtFil:dst.txtSiz:0Blocks:0IOBlock:rgularmptyfilDvic:78h/10dInod:Links:1Accss:(/-rw-r--r--)Uid:(0/root)Gid:(0/root) 发现没?inod编号还是。 源和目的在不同的文件系统还记得之前我们提过,由于硬链接是直接在目录文件中添加一个dirnt,名字直接指向源文件的inod,不同文件系统都是独立的一套inod管理系统,所以硬链接不能跨文件系统。 那么问题来了,mv遇到跨文件系统的场景呢,怎么处理?是否还是rnam? 举个例子,如下命令,源和目的是不同的文件系统。我虚拟机的挂载点如下: sh-4.4#df-hFilsystmSizUsdAvailUs%Mountdonovrlay59G3.5G5G7%/tmpfs64MM0%/dvshm64MM0%/dv/shm 我故意挑选/hom/qiya/tstdir和/dv/shm/,这两个目录分别对应了"/"和"/dv/shm/"的挂载点的文件系统,分属两个不同的文件系统。我们先提前看下源文件的信息(主要是inod信息): sh-4.4#stat/dv/shm/sourc.txtFil:/dv/shm/sourc.txtSiz:0Blocks:0IOBlock:rgularmptyfilDvic:7fh/17dInod:Links:1Accss:(/-rw-r--r--)Uid:(0/root)Gid:(0/root) 我们执行以下mv命令: sh-4.4#mv/dv/shm/sourc.txt/hom/qiya/tstdir/dst.txt 然后看下目的文件信息: sh-4.4#statdst.txtFil:dst.txtSiz:0Blocks:0IOBlock:rgularmptyfilDvic:78h/10dInod:Links:1Accss:(/-rw-r--r--)Uid:(0/root)Gid:(0/root) 对比有没有发现,inod的信息是不一样的,inodnumbr是不一样的(是不是跟上面同一文件系统下的mv现象不一致)什么原因呢?我下面一一道来,从原理出剖析。 当系统调用rnam的时候,如果源和目的不在同一文件系统时,会报告EXDEV的错误码,提示该调用不能跨文件系统。 #dfinEXDEV18/*Cross-dviclink*/ 所以,rnam是不能用于跨文件系统的,这个时候怎么办? 划重点:这个时候操作分成两步走,先copy,后rmov。 第一步:走不了rnam,那么就退化成copy,也就是真正的拷贝。读取源文件,写入目标位置,生成一个全新的目标文件副本;这里调用的copy_rg的函数封装(要知道这个函数是cp命令的核心函数,在深度剖析Linuxcp的秘密有深入剖析过);ln,mv,cp是在corutils库里的命令,公用函数本身就是可以复用的;第二步:删除源文件,使用rm函数删除;思考问题:mv跨文件系统的时候,如果第一步成功了,第二步失败了(比如没有删除权限)会怎么样? 会导致垃圾。也就是说,目标处创建了一个新文件,源文件并没有删除。这个小实验有兴趣的可以试下。 cp命令 cp命令才是真正的数据拷贝命令,即拷贝元数据,也会拷贝数据。cp命令也是我之前花了万字篇幅分析的命令,详细可见:深度剖析Linuxcp的秘密。这里就不再赘述,下面提炼出关于拷贝的3种模式。 涉及到数据拷贝的,关键有个--spars参数,可以控制拷贝数据的IO次数。 1auto模式重点:跳过文件空洞。是cp默认的模式 cpsrc.txtdst.txtalways模式 重点:跳过文件空洞,还会跳过全0数据,是空间最省的模式。 cp--spars=alwayssrc.txtdst.txt 3nvr模式 重点:无脑拷贝,从头拷贝到尾,不识别物理空洞和全0数据,是速度最慢的一种模式。 cp--spars=nvrsrc.txtdst.txt复用之前画的这3张图,很形象的体现了cp的行为。 总结 目录文件是一种特殊的文件,可以理解成存储的是dirnt列表。dirnt只是名字到inod的映射,这个是树形结构的基础;常说目录树在内存中确实是一个树的结构,每个节点由dntry结构体表示;ln-s创建软链接文件,软链接文件是一个独立的新文件,有一个新的inod,有新的dntry,文件类型为link,文件内容就是一条指向源的路径,所以软链的创建可以无视文件系统,跨越山河;ln默认创建硬连接,硬链接文件只在目录文件里添加了一个新dirnt项新nam:原inod,文件inod还是和原文件同一个,所以硬链接不能跨文件系统(因为不同的文件系统是独立的一套inod管理方式,不同的文件系统实例对inodnumbr的解释各有不同);ln命令貌似创建出了新文件,但其实不然,ln只跟元数据相关,涉及到dirnt的变动,不涉及到数据的拷贝,起不到数据备份的目的;mv其实是调用rnam调用,在同一个文件系统中不涉及到数据拷贝,只涉及到元数据变更(dirnt的增删),所以速度也很快。但如果mv的源和目的在不同的文件系统,那么就会退化成真正的copy,会涉及到数据拷贝,这个时候速度相对慢一些,慢成什么样子?就跟cp命令一样;cp命令才是真正的数据拷贝命令,速度可能相对慢一些,但是cp命令有--spar可以优化拷贝速度,针对空洞和全0数据,可以跳过,从而针对稀疏文件可以节省大量磁盘IO;IBM能靠nm芯片翻身吗?上市10天就遭破解!AirTag还能买吗? 爷青结,Microsoft放弃Windows95时代的图标
|