草庐IT

【Linux】理解文件系统——软硬链接

平凡的人1 2023-04-21 原文

我们之前讨论的都是进程和被打开文件的关系,而如果一个文件是没有被打开呢?没有被打开的文件操作系统如何管理?

没有被打开的文件在磁盘上,所以磁盘上有大量的文件,这些文件要被静态管理起来,方便我们随时打开,而做这部分工作的我们称为文件系统

文章目录

一、了解磁盘

磁盘是计算机主要的存储介质,可以存储大量的二进制数据,并且断电后也能保持数据不丢失。下面,我们来简单了解一下磁盘的结构👇

1.磁盘的物理结构

磁盘是外设并且是我们计算机中唯一的机械结构(机械结构相对较慢)

磁盘的盘片/盘面就像光盘一样(盘面上存储数据),两面都有。而像刀子一样的是磁头(每一面都有一个磁头),磁头和盘面并没有接触,磁盘必须防止抖动。中间有马达,一旦盘片加电之后,盘片旋转,磁头摆动,马达可以控制磁头摆动,控制盘片旋转。对应的磁盘也有自己的硬件电路,通过硬件电路+伺服系统,给磁盘发二进制指令,让磁盘定位寻址某个区域。

2.磁盘的存储结构

磁盘寻址的时候,其基本单位既不是bit,也不是byte,而是扇区。

扇区的大小:512字节,512字节是硬件要求,对于外磁道和内磁道大小都是一样的,密度不一样。越靠近圆心的比特位越大,外侧的较小。

在单面上定位扇区:通过确认磁道,最终确定在对应磁道的哪一个扇区。确认磁道:每个磁道都有自己的编号,磁道周长不一样,但是存储大小是一样的,因为扇区大小一样,一个磁道有多少扇区也是固定的,所以每个磁道的扇区也有编址。所以定位到某个磁道上就能够找到。

机械硬盘的寻址方式:盘片不断旋转,磁头不断摆动,就是在确认在哪一个磁道,如何确认在哪个扇区?盘片也在旋转,就是让磁头定位扇区。

柱面:把一系列同心的磁道压在一起,宏观上看成一个整体。一般定位的时候:磁头、柱面、扇区====(磁头、磁道、扇区),这两个是等价的。柱面相当于磁头一起指向的位置就是柱面的边界位置。

在磁盘中定位任何一个扇区:先定位在哪一个磁道(哪个柱面),磁道确定,所有磁头共进退,此时磁头不动,在去定位磁头(定位盘面),最后在定位哪一个扇区。

总结:磁盘中定位任何一个扇区,采用的硬件级别定位方式(CHS定位法):柱面Cylinder——磁头Head——扇区Sector

3.磁盘的逻辑结构

类比于磁带,卷起来是圆形的,扯开是线性结构的。所以我们可以把磁盘盘片想象成线性结构。以OS为角度,则认为磁盘是线性结构,要访问某个扇区,只需要定位数组下标,也就是说知道这个扇区的下标就算定位了一个扇区。在操作系统内部,我们称这种地址为LBA(Logic Block Address)地址!而要写到物理磁盘上,我们就要把LBA地址转换成对应磁盘的三维地址CHS地址。总结来说,OS的地址为LBA地址,而对应的磁盘为CHS地址。

OS要进行逻辑抽象,不直接使用CHS:1.便于管理,管理数组与管理三维立体结构,数组比较好管理;2.不想让OS的代码和硬件强耦合!比如磁盘的基本大小改变,OS的源码有必要跟着变


二、文件系统与inode

文件在磁盘中是如何存储:文件是在磁盘中的,而逻辑结构是线性结构。磁盘的空间很大,虽然对应的磁盘的访问的基本单位是512字节,但是依旧很小,OS内的文件系统会定制的多个扇区的读取,以1KB,2KB,4KB为基本单位,所以即使读取/修改1bit,也必须将4KB加载到内存,进行读取或修改,在将其写回磁盘。我们采用分治的思想,来管理磁盘空间进行分区:大的磁盘空间->小空间,大事化小,给每个分区写入不同的文件系统

Boot Block是启动块,存在每个分区的开头,备份文件与启动相关的。

将剩下的空间继续分组,Block group 0…Block group n。管理好Block group0,其他的也能管理好,所以我们只需要了解Block group 0:

Super Block:保存的是整个文件系统的信息,为什么Super Block不像Boot Block一样存在每个分区的开头?Super Block保存在不同的分组里意味着备份,如果某个Super Block损坏,便可以通过拷贝其他分组的Super Block。

Linux的文件=内容+属性而Linux的文件内容是属性是分批存储的。保存文件属性的是inode,inode块的大小是固定的,为128字节。一个文件,一个inode,包含文件几乎所有的属性,文件名并不在inode中存储文件内容存储在data block块中,不同的文件文件内容不同,大小随着应用类型的变化大小也在变化。inode为了进行区分彼此,所以每一个inode都有自己的ID,这里我们利用指令ls -li查看inode编号:

inode table:保存了分组内部所有的可用(已经使用+没有使用)inode,前面说了,一般来说一个文件对应着一个inode

Data blocks:保存的是分组内部所有文件的数据块

inode Bitmap:inode对应的位图结构,统计inode的使用情况,位图中比特位的位置和当前文件对应的inode的位置是一样对应的,比特位为1,代表inode被占用,否则表示可用。

Block Bitmap:数据块对应的位图结构,位图中的比特位位置和当前data block对应的数据块位置是一一对应位置

GDT(group descripteor table):块组描述表,对应分组的宏观的属性信息,已经使用多少,inode有多少个,已经被占用了多少个,还剩下多少个。

查找文件的时候,统一使用的是inode编号。

内容放在data blocks中,属性在inode table中,而inode内部保存了一个数组,保存对应块的编号,二者就联系起来了:

但是如果文件特别大?并不是所有的data block只能存放文件的数据,也可以存放其他块的块号,所以最终指向更多的block来存储

创建一个文件:在位图的比特位由0置为1,找到其inode table,属性填进去,文件的数据写到block里,在inode和block建立映射关系,最后返回inode编号,创建成功

查找一个文件:拿到inode找到inode table,在根据inode table找到对应的数据块,内容加属性就全找到了

删除一个文件:删除文件也需要用到inode,实际上删除一个文件时,我们只需要找到inode在inode bitmap当中的比特位,把比特位由1置为0就删除了。

所以删除一个文件根本不需要把数据属性和内容清空,只要把inode bitmap的1置为0,属性就删除了,这个文件也占着数据块,也把block的比特位也置为0。所以把文件删除是能够恢复的,一旦删除只是把bit位清掉了,想要恢复只要得到inode的编号,然后把inode bitmap里的比特位由0置为1,在去inode table对应的映射表,在block bitmap的0置为1。

如果在Linux中误删除一个文件,还是能恢复的,但是前提必须是inode和data block没有被占用,所以当误删除一个文件时,最好的办法就是什么都不做。而我们在Windows中删除文件到回收站,只是转移了目录,在回收站中删除才是真正的删除。

可是我们在查找一个文件的时候,用的不是inode,而是文件名?

任何一个文件都在一个目录下,但是这些文件并没有重复的文件名

目录是一个文件,也有自己的inode,有对应自己的data block,目录的数据块存放的是当前目录下的文件名和inode的映射关系,所以inode并不需要保存文件名。

所以我们在一个目录下新增一个文件,必须得有写入权限,新增一个文件的时候,要向当前目录的内容里去写文件名和inode的映射关系,所以必须得有写入权限。罗列当前的文件,要有读权限,想读的时候要根据文件名找到inode读取底下的所有文件的属性,读的是拿到文件名,必须得去访问数据块,所以要有读权限


三、软硬链接

1.软链接

创建软链接:(文件名不重要)

ln -s myfile.txt soft_file.link

有自己独立的inode的称为软链接,即软链接是独立的文件,独立的文件有独立的inode和对应的文件内容

所谓的软连接标定文件并不是用inode来标记的,我们删除myfile.txt,然后再去cat soft_file.link:

所以这个软链接并没有用目标文件对应的inode,而是用目标文件的文件名,把文件删了找不到,软链接的数据块里保存的是所指向的目标文件的路径,所以目标文件一删,软链接也就失效了。

删除软链接:既可以rm,也可以用unlink

unlink soft_file.link

软链接相当于快捷方式。软链接的作用:

当执行路径非常的深,导致路径非常的长,我们可能会忘记路径,所以我们可以通过软链接不用每次都输入长长的路径,可以通过软链接而快速找到:

这就相当于Windows的快捷方式了。

2.硬链接

创建硬链接:

ln myfile.txt hard_file.link

不带上s就是硬链接了。

软硬链接最重要的区别在于是否具有独立的inode,硬链接没有独立的inode,指向到其他文件的inode,不是独立的文件,**建立一个硬链接是在做什么?**当硬链接其中一个文件的大小、内容等发生变化,那硬链接的文件都会发生相应的变化,所以建立硬链接根本没有新增文件,因为没有给硬链接分配独立的inode,既然没有创建文件那么就没有自己的属性集合和内容集合,用的是别人的inode和内容。创建硬链接本质就是在指定的路径下,新增文件名和inode编号的映射关系!

inode可能会被多个文件指向,所以inode有一个计算器,count的引用计数,把引用计数称为硬链接数:

这也是我们为什么能看到1变为2的原因,因为有了新的文件名,inode被两个文件所指向,硬链接数变为2了:

如果把硬链接的文件删除,原始对应inode编号的文件:

所以一个文件真正的被删除:当一个文件的硬链接数变成0的时候才是真正的被删除。

硬链接的作用:

为什么创建一个普通文件的时候,硬链接数是1?

因为一个普通文件本身就有一个文件名和自己的inode,具有一个映射关系!

为什么创建一个目录的时候,硬链接数是2?

首先目录和本身的inode就是一组映射关系了,另外,第二个是目录内部的.(.表示当前目录,也是文件名。inode是一样的)和inode也是一组映射,所以是硬链接数是2

现在在empty目录下在创建一个目录dir,此时empty目录的硬链接数变为了3:

因为此时dir的…文件指向上级的目录empty,所以硬链接数为3

四、总结

我们大致了解磁盘的物理结构、存储结构、逻辑结构;然后就是文件系统与inode的认识。

最后是软硬链接,软硬链接本质区别就是有无独立的inode.

有关【Linux】理解文件系统——软硬链接的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  5. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  7. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  8. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  9. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  10. ruby - rspec 需要 .rspec 文件中的 spec_helper - 2

    我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只

随机推荐