当前位置: 首页 > news >正文

【Linux庖丁解牛】— 文件系统!

1 引⼊"块"概念

其实硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,其实是不会⼀个个扇区地读取,这样 效率太低,⽽是⼀次性连续读取多个扇区,即⼀次性读取⼀个”块”(block)。

硬盘的每个分区是被划分为⼀个个的”块”。⼀个”块”的⼤⼩是由格式化的时候确定的,并且不可 以更改,最常⻅的是4KB,即连续⼋个扇区组成⼀个”块”。”块”是⽂件存取的最⼩单位。

注意:

• 磁盘就是⼀个三维数组,我们把它看待成为⼀个"⼀维数组",数组下标就是LBA,每个元素都是扇 区

• 每个扇区都有LBA,那么8个扇区⼀个块,每⼀个块的地址我们也能算出来。

• 知道LBA:块号=LBA/8

 • 知道块号:LAB=块号*8+n.(n是块内第⼏个扇区)

2 引⼊"分区"概念

其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有⼀块磁盘并且将 它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的⼀种格式化。但是Linux的设备 都是以⽂件形式存在,那是怎么分区的呢?

柱⾯是分区的最⼩单位,我们可以利⽤参考柱⾯号码的⽅式来进⾏分区,其本质就是设置每个区的起 始柱⾯和结束柱⾯号码。此时我们可以将硬盘上的柱⾯(分区)进⾏平铺,将其想象成⼀个⼤的平 ⾯,如下图所⽰:  

3 ext2⽂件系统

3.1 inode Table&Data Blocks

操作系统在给磁盘分区后,由于每个区的大小可能还是很大,为了方便管理,还会进行分组。我们知道文件=内容+属性,在Linux下,内容和属性是分开存储的,内容存储在Data Blocks中【以快4kb进行存储】。在Linux中,正常文件都要有自己的属性集合{文件大小,创建时间,权限,类型等},而这些属性则放在一个叫iNode的结构体【128字节】中

这里有一个特殊情况,文件名不会作为属性,保存在文件的iNode当中!为什么呢?

以我们现在肤浅的理解:inode节点的大小是固定的,而每个文件名大小是不定的,如果让inode保存文件名就会让这个节点处于一个很尴尬的地位。我们也不可能做一个定长的字符数组,太短不行,太长也势必会浪费很多空间。

在一个组中,有一个inode表,所有的inode节点都放在这张表中。内存读取数据是以快为单位,也就是一次读取4kb【一个数据块,会保存32个inode】。那我们访问一个inode时,其他inode是不是也加载到内存当中了呢?雀氏如此,有些inode我们可能不会访问,即使加载到内存当中。不过,这样做也有一定的原因,连在一起的inode一本来说都是一起创建有一定联系的文件。它们有更大的概率会被同时访问,这样它们就不用多次加载了,这也就是局部性原理,和vector扩容有异曲同工之妙。

在inode中没有存储文件名,那我们如何区分一个文件的唯一性呢?

答案就是int inode_number,每个inode都有一个编号来区分文件的唯一性。

我们用ls -li 就可以查看一个文件的inode编号了。

3.2 Block Bitmap&inode Bitmap

我们现在已经知道文件的属性和内容分别存储在inode表和数据块当中,但是,文件系统是如何管理这些区域呢,比如,文件系统是如何知道哪些数据块使用了,哪些没有使用,哪些inode节点使用了,哪些inode节点没有使用呢?

很简单,在一个分组中有两张位图Block Bitmapinode Bitmap,两张位图当中的每个比特位对应的就是每个数据块和节点。如果比特位是0则表示这块区域没有被使用,为1则表示已经被使用。

3.3 GDT(Group Descriptor Table)

块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描 述符存储⼀个块组的描述信息,如在这个块组中从哪⾥开始是inode Table从哪⾥开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有⼀份拷贝。

// 磁盘级blockgroup的数据结构 
/** Structure of a blocks group descriptor*/
struct ext2_group_desc
{__le32 bg_block_bitmap; /* Blocks bitmap block */__le32 bg_inode_bitmap; /* Inodes bitmap */__le32 bg_inode_table; /* Inodes table block*/__le16 bg_free_blocks_count; /* Free blocks count */__le16 bg_free_inodes_count; /* Free inodes count */__le16 bg_used_dirs_count; /* Directories count */__le16 bg_pad;__le32 bg_reserved[3];
};

3.4 超级块(Super Block)  


存放⽂件系统本⾝的结构信息描述整个分区的⽂件系统信息。记录的信息主要有:bolck 和inode的总量,未使⽤的block和inode的数量,⼀个block和inode的⼤⼩,最近⼀次挂载的时间,最近⼀次写⼊数据的时间,最近⼀次检验磁盘的时间等其他⽂件系统的相关信息。Super Block的信息被破坏,可以说整个⽂件系统结构就被破坏了超级块在每个块组的开头都有⼀份拷⻉(第⼀个块组必须有,后⾯的块组可以没有)。

为了保证⽂件系统在磁盘部分扇区出现物理问题的情况下还能正常⼯作,就必须保证⽂件系统的super block信息在这种情况下也能正常访问。所以⼀个⽂件系统的super block会在block group中进⾏备份,这些super block区域的数据保持⼀致。

3.5 一些细节

> 格式化的本质就是写入文件系统的管理信息。

> Super Block在分区中有多份备份,在某些分组中存在。为什么不在一个分区的开始记录一份,因为超级快实在太重要了,需要多份备份。

> inode和数据块是跨组编号的。【即在一个分区内,所有的inode编号和块号都是唯一的】

> inode和数据块不能跨分区。

> 在Linux中看待目录和文件一样,目录也有自己的inode和数据块。只不过,目录的数据块中存储的内容是该目录下的文件名和该文件inode的映射关系。我们之前说一个文件的文件名不会被看做属性被记录到inode当中,现在我们知道文件名是被记录到该文件所属目录的数据块当中了。但是,这里有一个问题:我们知道可以用一个文件的inode找到文件【也就是说,文件系统只认inode编号】,但是,从我们用户的角度来看,我们是用文件名来找到一个文件的。所以,文件系统是如何通过我们提供的文件名找到文件的呢?

有了上面的铺垫,我们也可以很好理解:当我们要查找一个磁盘文件时,文件系统先打开该文件所在的当前目录【也就是路径】,得到文件名和inode之间的映射关系,最后也是用该文件的inode找到该文件。所以,我们访问任何文件都必须要有路径,找文件的本质是:从根目录开始,进行路径解析,找到对应的文件。

3.6 路径缓存

我们访问任何路径都要从根目录下开始进行路径解析吗?这样不就是一直在做磁盘IO吗,效率是不是太低了??

操作系统在我们进行路径解析的时候,会把历史路径【目录】记录下来,形成一课多叉树【这颗多叉树和PCB一样是内存级别的】,进行保存。这也就是Linux的树状目录结构!

Linux中,在内核中维护树状路径结构的内核结构体叫做: struct dentry

struct dentry {atomic_t d_count;unsigned int d_flags; /* protected by d_lock */spinlock_t d_lock; /* per dentry lock */struct inode *d_inode; /* Where the name belongs to - NULL is* negative *//** The next three fields are touched by __d_lookup. Place them here* so they all fit in a cache line.*/struct hlist_node d_hash; /* lookup hash list */struct dentry *d_parent; /* parent directory */struct qstr d_name;struct list_head d_lru; /* LRU list *//** d_child and d_rcu can share memory*/union {struct list_head d_child; /* child of parent list */struct rcu_head d_rcu;} d_u;struct list_head d_subdirs; /* our children */struct list_head d_alias; /* inode alias list */unsigned long d_time; /* used by d_revalidate */struct dentry_operations *d_op;struct super_block *d_sb; /* The root of the dentry tree */void *d_fsdata; /* fs-specific data */
#ifdef CONFIG_PROFILINGstruct dcookie_struct *d_cookie; /* cookie, if any */
#endifint d_mounted;unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};

注意:
• 每个⽂件其实都要有对应的dentry结构,包括普通⽂件。这样所有被打开的⽂件,就可以在内存中
形成整个树形结构
• 整个树形节点也同时会⾪属于LRU(Least Recently Used,最近最少使⽤)结构中,进⾏节点淘汰
• 整个树形节点也同时会⾪属于Hash,⽅便快速查找 
• 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何⽂件,都在先在这
棵树下根据路径进⾏查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry
结构,缓存新路径

3.7 挂载分区

我们已经可以通过inode在指定分区中找到文件了,也可以通过目录文件内容找到inode,但是inode不是不能跨分区吗,我们怎么知道这个文件在哪个分区呢?

在Linux中,某个分区是无法直接访问的,这时候就需要将目录挂载到指定分区。也就是说,分区写入文件系统,需要和指定的目录关联,进行挂载才能使用。

如此一来,我们就可以根据访问目标文件的“路径前缀”来准确判断我在哪一个分区

至此,我们也可以更深刻的理解fopen时,操作系统干了什么了:首先拿到当前文件的路径,再根据文件名和inode的映射关系得到inode,拿着inode在磁盘中进行索引找到当前文件,如果有可能,也会在dentry树中索引提高效率。接着系统会在内存中创建struct file对象,将文件的属性信息拷贝到file对象中的inode字段中,将文件的全部内容或部分内容写入文件内核缓冲区当中。同时,系统也会创建对应的文件描述符表,并返回对应的文件描述符!

3.8 文件系统相关总结图表

4. 软硬链接 

4.1 软连接

我们先来看一个现象:

可以看出,软连接是一个独立的文件,因为它有独立的inode number。 

那软连接有什么用呢?

在一些大型工程当中,我们的可执行文件可能被藏在一些比较深的路径,直接找到这个路径可能比较麻烦,这时候,使用软链接我们就可以更方便的执行该程序,如下:

这就相当于Windows当中的快捷键,而软链接文件的内容就是目标文件的路径。当然,软链接还有其他的作用,以后再说。 

4.2 硬链接

我们先来看一个现象:

我们直接使用了命令ln不带选项就是硬链接,我们发现这两个文件的inode编号都是相同的,所以硬链接文件就是不是独立的文件,而是一组新的文件名和inode的映射关系。 

我们再看到这个数字,这个数字其实就是硬链接数。当这个硬链接数为2的时候,说明这个文件有两个,也就是说硬链接可以帮我们完成一个文件的备份,当我们以一个文件名删除文件时,这个文件的内容和属性其实还在,我们通过另一个文件名就可以得到。这也就是硬链接的作用!

上面操作创建了一个目录dir,这个目录的硬链接数为什么是2呢,我们明明没有注定完成硬链接操作,原因就是,在dir目录下的影藏文件【.】就是当前路径【也就是代表dir目录】,所以 . 就是dir目录的硬链接! .. 也是同样的道理啊。

至此,我们也明白了.和..的本质就是硬链接!

但是,在Linux中,我们用户却并不可以对目录进行硬链接!但是,系统当中的.和..不就是目录的硬链接文件吗!这不是只许州官放火,不许百姓点灯吗?事实就是如此,但是为什么系统不让用户自定义硬链接文件,却又自己设计.和..的硬链接文件呢?

如果,我们在树状的目录结构当中,其中一个文件对根目录进行硬链接,那这就容易形成路径换问题。那我们在目录查找文件时就容易出现问题。而对于.和..硬链接文件虽然也形成了路径换,但是由于名字特殊,做特殊处理即可!.和..存在的意义也是为了用户方便进行命令操作。

还有个问题,软链接不也形成路径环了吗,但是我们要看到改文件的类型是被l标识的!做特殊判断即可!所以,在Linux中有些东西有其存在的考量和并不存在的理由。

相关文章:

  • 什么是RAG检索生成增强?
  • 利用deepseek学术搜索
  • 「Java案例」华氏摄氏温度转换
  • XIP (eXecute In Place)
  • 双指针的用法
  • Nginx漏洞处理指南
  • [database] Closure computation | e-r diagram | SQL
  • llama.cpp学习笔记:后端加载
  • VMware设置虚拟机为固定IP
  • Java--可变参数--作用域--构造器--this
  • Qwen-VL系列全面解析:从技术突破到应用实践
  • OSPF(开放最短路径优先)
  • ROS常用的路径规划算法介绍
  • Excel之将一堆姓名拆成一列4
  • 1.认识Docker
  • 第十二节:Vben Admin 最新 v5.0 (vben5) 快速入门 - 两种权限控制方式(附前后端代码)
  • 《伴时匣》app开发技术分享--表单提交页(5)
  • STM32H723ZGT6-修改内存分布以定义很大的数组
  • HarmonyOS 公共事件机制介绍以及多进程之间的通信实现(9000字详解)
  • FPGA实现CameraLink视频解码,基于Xilinx ISERDES2原语,提供4套工程源码和技术支持