【linux仓库】一文带你看透 Linux:文件系统与硬件的秘密通道
回顾基础IO
前面的章节主要围绕已打开文件的操作与理解展开,重点在于说明文件与进程之间的关系。我们知道,文件大体可以分为两类:已打开的文件和未打开的文件。
对于未打开的文件,它们并不驻留在内存中,而是被保存在磁盘等存储设备上。这就引出了一个新的话题——数据保存的方案。接下来,我们将带领大家深入了解未打开文件的特性和存储方式,并逐步展开相关内容的介绍。
理解硬件
机械磁盘是计算机中唯⼀的⼀个机械设备,优点是:容量⼤,价格便宜。缺点:慢。而在像腾讯、阿里这样的公司里面,就有自己的机房,机房里面存在上万个机械磁盘,用来存储数据,而多出的机械磁盘则可以外租到外面。
一个磁片是具有正反两个磁面的。磁头和盘面也是一对一的,即一面一个磁头。
磁面上实际上是有很多的磁道,其间隙很小。
对于一个磁道会分出很多个小区域,称为扇区(sector),而扇区之间也是具有间隙的。扇区:是磁盘存储数据的基本单位,512字节,块设备。
磁头:像 “笔” ,负责对磁盘进行数据读写。写入时,把电信号转化为磁信号记录在磁盘磁性涂层;读取时,将磁盘磁信号还原成电信号,让系统获取数据,是数据交互核心部件。
磁头臂:是 “支架” ,带着磁头移动,精准定位到磁盘不同磁道,确保磁头在读写数据时,能稳定、准确到达指定位置,配合主轴(马达)带动磁盘转动,实现全盘数据访问 。
柱面其实就是磁道,相同半径的磁道看做一个整体。
一面一个磁头,而磁头之间是共进退的!!!
既然扇区是磁盘存储数据的基本单位。那么如何定位⼀个扇区呢?
从磁盘存储结构上我们可以知晓:
确定磁头,即选择哪一面;
确定哪个磁道或者柱面;
确定是哪一个扇区,从而定位到一个扇区。
对磁盘特定位置进行寻址:
左右摆动,本质是在定位哪一个磁道(柱面);
盘片旋转的本质:是确定了那一个磁道(柱面),定位该磁道(柱面上)的哪一个扇区。
CHS地址定位
⽂件 = 内容+属性 都是数据,无非就是占据那几个扇区的问题!能定位⼀个扇区了,能不能定位多个扇区呢?
扇区是从磁盘读出和写⼊信息的最⼩单位,通常⼤⼩为 512 字节。
磁头(head)数:每个盘⽚⼀般有上下两⾯,分别对应1个磁头,共2个磁头
磁道(track)数:磁道是从盘⽚外圈往内圈编号0磁道,1磁道...,靠近主轴的同⼼圆⽤于停靠磁头,不存储数据
柱面(cylinder)数:磁道构成柱⾯,数量上等同于磁道个数
扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同
圆盘(platter)数:就是盘⽚的数量
磁盘容量=磁头数 × 磁道(柱⾯)数 × 每道扇区数 × 每扇区字节数
细节:传动臂上的磁头是共进退的(这点⽐较重要,后⾯会说明)
柱⾯(cylinder),磁头(head),扇区(sector),显然可以定位数据了,这就是数据定位(寻址)方式之⼀,CHS寻址⽅式。
CHS寻址:
对早期的磁盘⾮常有效,知道⽤哪个磁头,读取哪个柱⾯上的第⼏扇区就可以读到数据了。
但是CHS模式⽀持的硬盘容量有限,因为系统⽤8bit来存储磁头地址,⽤10bit来存储柱⾯地
址,⽤6bit来存储扇区地址,⽽⼀个扇区共有512Byte,这样使⽤CHS寻址⼀块硬盘最⼤容量
为256 * 1024 * 63 * 512B = 8064 MB(1MB = 1048576B)(若按1MB=1000000B来算就是
8.4GB)
磁盘逻辑结构
上面我们都是从磁盘的物理结构进行看待的.这里从零开始,构建对磁盘OS级别的理解抽象。
那么磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在⼀起的磁带,那么磁盘的逻辑存储结构我们也可以类似于:
如何定位一个扇区呢?
仔细一瞧,这不就是一维数组 sector_array[N] 吗,每个下标都存储着一个扇区.这样每⼀个扇区,就有了⼀个线性地址(其实就是数组下标),这种地址叫做LBA ( Logic Block Address).
磁道
某⼀盘⾯的某⼀个磁道展开:
我们把一个磁面拉开抽象成一个线性结构,一个磁面就具有无数个扇区.而每个磁面的扇区总量是相等的,扇区总数是s一个扇区假设有n个磁道,这样每个磁道的扇区个数都是s/n,
那如何通过LBA确定在物理结构的哪一个扇区呢?
假设一个磁面的扇区总数是100,每个磁道的扇区总数是10.此时LBA:124,那么124/100就能确定是哪一个磁头,即哪个磁面,124%100=24即能确定该磁头确定的磁面的扇区.此时24/10=2就能确定是哪一个磁道,24%10=4确定是该磁道的第4个扇区.
柱面
前面我们理解展开的时候,是以一面为单位进行展开的!但是,磁盘真实的展开,并不是以物理盘面展开的.是以"柱面"为单位进行展开的!
整个磁盘所有盘⾯的同⼀个磁道,即柱⾯展开:
柱⾯上的每个磁道,扇区个数是⼀样的
这不就二维数组sector_array[][]
而一个磁盘具有多个柱面,这不就意味着存在多个二维数组吗?不就是三维数组吗?
所有,寻址⼀个扇区:先找到哪⼀个柱⾯(Cylinder) ,在确定柱⾯内哪⼀个磁道(其实就是磁头位置,Head),在确定扇区(Sector),所以就有了CHS寻址。
我们之前学过C/C++的数组,在我看来,磁盘,就是一个以sector为单位的一维数组!!!
所以,每⼀个扇区都有⼀个下标,我们叫做LBA(Logical Block Address)地址,其实就是线性地址。那么怎么计算得到这个LBA地址呢?
OS只需要使⽤LBA就可以了!!LBA地址转成CHS地址(本质是:一维数组的下标,转换成为三个数字!!!),CHS如何转换成为LBA地址。谁做啊??磁盘⾃⼰来做!固件(硬件电路,伺服系统)
CHS && LBA 地址
CHS转成LBA:
磁头数*每磁道扇区数 = 单个柱⾯的扇区总数
LBA = 柱⾯号C*单个柱⾯的扇区总数 + 磁头号H*每磁道扇区数 + 扇区号S - 1
即:LBA = 柱⾯号C*(磁头数*每磁道扇区数) + 磁头号H*每磁道扇区数 + 扇区号S - 1
扇区号通常是从1开始的,而在LBA中,地址是从0开始的
柱⾯和磁道都是从0开始编号的
总柱⾯,磁道个数,扇区总数等信息,在磁盘内部会⾃动维护,上层开机的时候,会获取到这些参数。
LBA转成CHS:
柱⾯号C = LBA // (磁头数*每磁道扇区数)【就是单个柱⾯的扇区总数】
磁头号H = (LBA % (磁头数*每磁道扇区数)) // 每磁道扇区数
扇区号S = (LBA % 每磁道扇区数) + 1
"//": 表⽰除取整
所以:从此往后,在磁盘使⽤者看来,根本就不关心CHS地址,而是直接使⽤LBA地址,磁盘内部自己转换。
所以:从现在开始,磁盘就是⼀个 元素为扇区 的⼀维数组,数组的下标就是每⼀个扇区的LBA地址。OS使⽤磁盘,就可以⽤⼀个数字访问磁盘扇区了。
引入文件系统
"块"概念
其实硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,其实是不会⼀个个扇区地读取,这样效率太低,⽽是⼀次性连续读取多个扇区,即⼀次性读取⼀个”块”(block)。
硬盘的每个分区是被划分为⼀个个的”块”。⼀个”块”的⼤⼩是由格式化的时候确定的,并且不可以更改,最常⻅的是4KB,就是4096字节,一个扇区占512个字节,那么就是有4096/512=8个扇区.即连续⼋个扇区组成⼀个 ”块”。”块”是⽂件存取的最⼩单位。
注意:
磁盘就是⼀个三维数组,我们把它看待成为⼀个"⼀维数组",数组下标就是LBA,每个元素都是扇区
每个扇区都有LBA,那么8个扇区⼀个块,每⼀个块的地址我们也能算出来。
知道LBA:块号 = LBA/8
知道块号:LAB=块号*8 + n. (n是块内第几个扇区)
至此,我们完成了对磁盘的完整建模的过程!!!
磁盘:本质就是一个block arrar[N],块设备!!!
引入"分区"概念
其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有⼀块磁盘并且将它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的⼀种格式化。但是Linux的设备都是以⽂件形式存在,那是怎么分区的呢?
柱⾯是分区的最⼩单位,我们可以利⽤参考柱⾯号码的⽅式来进⾏分区,其本质就是设置每个区的起始柱⾯和结束柱⾯号码。 此时我们可以将硬盘上的柱⾯(分区)进⾏平铺,将其想象成⼀个大的平⾯,如下图所示:
柱⾯大小一致,扇区个位⼀致,那么其实只要知道每个分区的起始和结束柱⾯号,知道每
⼀个柱⾯多少个扇区,那么该分区多⼤,其实和解释LBA是多少也就清楚了.
引入"inode"概念
ext2文件系统
为了能解释清楚inode,在此之前,我们需要深⼊了解⼀下⽂件系统。
宏观认识
我们想要在硬盘上储⽂件,必须先把硬盘格式化为某种格式的⽂件系统,才能存储⽂件。⽂件系统的⽬的就是组织和管理硬盘中的⽂件。在Linux 系统中,最常见的是 ext2 系列的⽂件系统。(其早期版本为 ext2,后来⼜发展出 ext3 和 ext4。ext3 和 ext4 虽然对 ext2 进⾏了增强,但是其核⼼设计并没有发⽣变化,我们仍是以较⽼的 ext2 作为演示对象。)
ext2⽂件系统将整个分区划分成若⼲个同样⼤⼩的块组 (Block Group),实际上就是一个块组对块的组织形式.如下图所示。只要能管理⼀个分区就能管理所有分区,也就能管理所有磁盘⽂件。
Super Blcok(超级块)
存放⽂件系统本⾝的结构信息,描述整个分区的⽂件系统信息。记录的信息主要有:bolck 和 inode的总量,未使⽤的block和inode的数量,⼀个block和inode的⼤⼩,最近⼀次挂载的时间,最近⼀次写入数据的时间,最近⼀次检验磁盘的时间等其他⽂件系统的相关信息。Super Block的信息被破坏,可以说整个⽂件系统结构就被破坏了.
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];
};
那么该如何管理好一个小组group呢?
在ext2文件系统中就有一个Block Group,它里面含有Super Block超级块,Block块和块的位图,inode结点和inode位图以及inode Table,最后就是Data Blocks数据块,里面就是存储着我们写的数据.接下来小编带着大家理解它们之中的含义以及联系.
Data Block
我们说过文件 = 文件内容 +文件属性 -->都是数据 -->既然都是数据,那么都要存储
Linux中,文件的内容和属性是分开存储的!
OS文件系统中,和磁盘进行I0的基本单位数:4KB
数据区:存放⽂件内容,也就是⼀个⼀个的Block。根据不同的⽂件类型有以下⼏种情况:
对于普通⽂件,⽂件的数据存储在数据块中。
对于⽬录,该⽬录下的所有⽂件名和⽬录名存储在所在⽬录的数据块中,除了⽂件名外,ls -l命令看到的其它信息保存在该⽂件的inode中。
Block 号按照分区划分,不可跨分区
每一个数据块,都有唯一的编号
既然每一个数据块,都有唯一的编号 -->一个组可能会存在多个文件,那么该如何确定该数据块是否被其他文件所占用了呢?
Block Bitmap
Block Bitmap中记录着Data Block中哪个数据块已经被占⽤,哪个数据块没有被占⽤
比特位的位置,表示对应哪一个数据块
比特位的内容[0,1],表示对应的数据块[是否]被占用
inode结构体 Table(i节点表)
还是因为文件 = 文件内容 + 文件属性.既然是数据,那么文件属性也需要存储.
那么Linux中,该如何表达文件属性呢?
strcut inode{ }结构体!!!
struct inode 表示文件的属性 --> 其大小是固定的,一般是128或256字节,因为是一样的成员变量.换句话讲,任何文件,它们的inode结构体中包含多少种属性,是一样的,但是属性的内容不同!
扩展:OS读取文件时,会读取inode,并且一次是4KB读取.总共能读取多少个文件的inode呢?
4*1024个字节,而一个inode大小是固定的,假设是128.那么4*1024/128=32个文件的inode
一般而言,一个文件一个inode一个文件,可能对应0或者多个data block.那该如何标示这个文件的唯一性呢?
inode结构体里有 inode number
并且在Linux中文件名不能也不在inode保存!!!(看似不符合我们的期望,放在后面讲解)
inode Table(i节点表)
存放文件属性 如 文件大小,所有者,最近修改时间等
当前分组所有Inode属性的集合
inode编号以分区为单位,整体划分,不可跨分区
inode Bitmap
每个bit表示⼀个inode是否空闲可用.
比特位得的位置:表示第几个inode
比特位的内容[1/0]:表示对应的inode,[是否]被占用!
inode和datablock映射(弱化)
inode内部存在 __le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ ,EXT2_N_BLOCKS =15,就是⽤来进⾏inode和block映射的
这样⽂件=内容+属性,就都能找到了。
如果一个文件的内容不是很大,占的数据块不是很多.那么就用12个直接块指针,前12个映射的是内容,一个数据块占4KB,那么就可以存12*4KB=48KB的数据.
当文件内容过大时,就需要有一级、二级、三级间接块索引表指针:
一级间接块索引表指针:inode 里有一个一级间接块指针,它指向一个“索引块”.这个索引块里存放的是更多的数据块指针,每个指针再指向实际的数据块。一个数据块能存4KB,而每个块指针占4B字节,那么一个"索引块"就能存下4KB/4B=1024个指针,就能存下1024*4KB=4MB的数据.
二级间接块索引表指针:inode 里还有一个二级间接块指针,它指向一个“二级索引块”.二级索引块里存放的是一级索引块的指针,每个一级索引块再指向实际数据块。一个二级"索引块"同样也是4KB,存放着一级索引块的指针,就是4KB/4B=1024个一级索引块指针,那么就可以存放1024*1024*4KB=4GB的数据
三级间接块索引表指针:inode 里还有一个三级间接块指针,它指向一个“三级索引块”。三级索引块里存放的是二级索引块的指针,二级索引块再指向一级索引块,一级索引块再指向实际数据块。同理就可以存放1024*1024*1024*4KB=4TB的数据(使得文件系统可以支持极大的单个文件,比如几十GB甚至更大)
总结:有一级、二级、三级间接指针,是为了让文件系统既能高效管理小文件,又能支持超大文件。
数据块的分配是全局有效的
虽然磁盘被分成了多个“块组”(block group),每个组有自己的 inode table 和 data block 区域。
但数据块编号(block number)是全局唯一的,不局限于某个组.
inode table 和 data block 的映射关系
inode 结构体中的数据块指针,指向的是全局编号的数据块。
只要知道块号,就能定位到磁盘上的具体位置,无论它属于哪个组。
文件的数据块可以跨组分配
当一个文件很大,或者本组的数据块用完时,文件的数据块可以分配到其他组的空闲块.也就是说,一个文件的数据块可以分布在磁盘的任意位置,不一定都在同一个块组里。
既然有group就会存在两种情况:
1.inode 用完,block 没用完
这种情况下,你无法再在这个 group 里创建新文件或目录,因为没有多余的 inode 了。但这个 group 里的数据块还可以被其他 group 的文件使用(比如大文件跨组分配数据块)。
常见于大量小文件的场景(比如邮件服务器、图片存储等)。
2.inode 没用完,block 用完
无法再在这个 group 里为文件分配新的数据块,但还可以创建新文件(只要 inode 还有)。新文件的数据块可能会被分配到其他 group。
常见于少量大文件的场景(比如视频、数据库等)。
总结与问题
总结:
经过前面学习,将来我们就可以先拿到文件的inode号,那么如何找到该文件的内容呢?
只要通过inode编号,就能找到文件对应的inode属性,内部具有和数据块的对应关系,就能进一步找到文件的内容了!!
问题1:Super Blcok既然是描述一个分区的所有分组的整体情况,sb为什么会在一个块组中???
超级块(Super Block)在每个块组的开头都有⼀份拷贝(第⼀个块组必须有,后⾯的块组可以没有)。 为了保证⽂件系统在磁盘部分扇区出现物理问题的情况下还能正常⼯作,就必须保证⽂件系统的super block信息在这种情况下也能正常访问。为了实现上述目的,super block不仅仅在一个组里,可能会同时存在在多个block group里!(不一定所有组都有,但是几乎多个组会同时存在同样的super blcok).万一 super block 出了错误,多个组里面存多个 super block。即使搞坏了一个,可以用其他组的 super block 进行修复,是一种数据容斋的备份处理.
问题2:新建一个分区, Super Block 和 GDT一定是有效数据,为什么呢?
这是因为给特定分区写入管理信息,即写入文件系统和分区分组相关的的管理数据,对于文件数据可以暂时不用.(这不就相当于格式化吗!)
因此要使用一块硬盘:
分区
格式化(给当前分区,写入文件系统)
问题3:访问一个文件,在分区内,标识该文件的唯一性:inode编号!!!
新建一个文件:
在块组里查找inode bitmap,找到一个为0的比特位,由0置1,即申请inode。就可以在inode table里申请一个128字节的空间。
通过touch⼀个新⽂件来看看如何⼯作。
[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
263466 abc
创建⼀个新⽂件主要有以下4个操作:
1. 存储属性
内核先找到⼀个空闲的i节点(这⾥是263466)。内核把⽂件信息记录到其中。
2. 存储数据
该⽂件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第⼀块数据复制到300,下⼀块复制到500,以此类推。
3. 记录分配情况
⽂件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
4. 添加⽂件名到⽬录
新的⽂件名abc。linux如何在当前的⽬录中记录这个⽂件?内核将⼊⼝(263466,abc)添加到⽬录⽂件。⽂件名和inode之间的对应关系将⽂件名和⽂件的内容及属性连接起来。
删除一个文件:
拿着inode编号,在Inode bitmap找,确认是否有效,再通过inode编号读取inode结构,获取该文件的属性,判断文件的大小。如果不为0,通过inode结构体内的数据块指针数组找到具体的数据块编号,计算在Block Bitmap的位置,由1置为0,那么数据块就被释放了。
所以删除文件只改位图!!!→ 计算机删除数据,只要设置数据无效就行了 ->所以如果误删数据怎么办?啥都不做.
修改文件:
修改文件无疑就是修改文件的内容或者属性.根据文件的inode编号找到inode结构体,接着检查文件的权限,如果不通过则返回权限错误.若通过,用inode结构体的数据块指针数组读取数据块,确认是否需要新的数据块,如果需要,则在Block Bitmap 中查找为0的位置,申请Data Block数据块,更新inode数据块指针.再将数据写入到数据块中,更新inode元数据,最后同步元数据到磁盘中.
查文件:
查文件和修改文件的逻辑是类似的,这里就不过多赘述.
问题4:关于inode编号和Data Block编号
inode是全分区统一分配的,不是只在分组内有效。
inode不能跨区域,一个分区,一个文件系统,互相独立! --> 即inode和block编号是在整个区有效
那么我们是如何通过inode确定inode bitmap在哪个位置的呢?
inode:1234在sb中找到block_per_group:1000,1234/1000说明在块组1中;
找到inode_per_group:1000,1234%1000说明在inode bitmap中234位置中
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];
};
目录与文件名
问题5: 我们打开、删除、查找等操作都是通过文件名啊,那inode在哪存着呢?
文件名不在inode结构体内部,应该在哪里呢?
该如何看待目录呢?
目录也是文件,目录 = 目录内容 + 目录属性.
而目录自己的inode就在属性中存着.
那么目录的文件名又在哪呢?
实际上是在上一级目录的内容中.
那么文件内容存储着文件名有啥用呢?甚至我们打开、删除、查找等操作都是通过文件名啊,是如何找到inode编号的?
目录的文件内容存的是文件名和inode编号的映射关系啊,只要有了文件名就能确定其inode编号.
结论:文件系统中,从存储方式角度,存储普通文件和目录,有区别吗?没有
下面这段代码验证:目录是否确实是存的文件名与inode编号的映射关系
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
exit(EXIT_FAILURE);
}
DIR *dir = opendir(argv[1]); // 系统调⽤,⾃⾏查阅
if (!dir)
{
perror("opendir");
exit(EXIT_FAILURE);
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{ // 系统调⽤,⾃⾏查阅
// Skip the "." and ".." directory entries
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
{
continue;
}
printf("Filename: %s, Inode: %lu\n", entry->d_name, (unsigned long)entry->d_ino);
}
closedir(dir);
return 0;
}
所以,访问⽂件,必须打开当前⽬录,根据⽂件名,获得对应的inode号,然后进⾏⽂件访问
所以,访问⽂件必须要知道当前⼯作⽬录,本质是必须能打开当前⼯作⽬录⽂件,查看⽬录⽂件的内容!
由问题5我们就可以解决之前存在的未解决的问题:
为什么同一个目录下,文件名不能重复呢?
结论一:因为文件名和inode编号是具有映射关系的,重复的文件名确有多个inode编号,如何进行映射就是存在问题的,因此在同一目录下文件名不能重复.
结论二:在指定目录下,新建文件的本质:文件名->inode ,写入到当前目录的data block里,这就是为什么在当前目录下新建文件,需要该目录具有“w“权限.
结论三:读取一个文件的属性的话,需要当前目录具有“r“权限,是因为如果不可读文件内容,就找不到文件名和inode编号的映射,,就不能确定inode编号,最终无法读取一个文件的属性.
总结来说结论二、三是对目录设置rw本质是约束用户,访问目录的datablock
结论四:x权限比较特殊:允许我们是否打开该目录!
路径解析
问题6:为什么Linux访问文件,都必须带路径啊?但是我pwd指令又是如何得到路径的?
这就是为什么,以前open文件的时候,必须要有路径的原因!
这就是为什么Linux下访问任意文件,都需要路径访问!
这就是为什么我们访问文件,本质是进程访问,进程具有cwd的根本原因!
这就是为什么linux下,文件路径可以定位文件的根本原因!Linux下访问文件,都必须带路径(无论是显示的,还是隐式的!)
我们知道了:访问⽂件必须要有⽬录+⽂件名=路径的原因(根⽬录固定⽂件名,inode号,⽆需查找,系统开机之后就必须知道)
路径缓存
但这样也会出现一个问题,每次访问一个文件,都需要做路径解析,这样不是很慢吗?
实际上在linux系统里有一个目录树:
Linux系统中,当用户访问指定路径下的文件(包括路上目录,最终的目标文件在内)
Linux会在进行路径解析的过程中,在内核中形成目录树和路径缓存! --> 目录结构是内存级的
路径缓存:在做路径解析的时候,只有第一次是慢的,第2..n次的时候,路径解析会优先从dentry树结构中进行解析!
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_PROFILING
struct dcookie_struct *d_cookie; /* cookie, if any */
#endif
int d_mounted;
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};
注意:
每个⽂件其实都要有对应的dentry结构,包括普通⽂件。这样所有被打开的⽂件,就可以在内存中形成整个树形结构
整个树形节点也同时会⾪属于LRU(Least Recently Used,最近最少使⽤)结构中,进⾏节点淘汰
整个树形节点也同时会⾪属于Hash,⽅便快速查找
更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何⽂件,都在先在这棵树下根据路径进⾏查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径
挂载分区
一个磁盘,必须分区格式化,才能具有使用的前提;
一个分区,要真正的使用,必须挂在到指定的目录才可以.
那该如何把一个分区挂载到指定的目录呢?
/dev/vda 设备文件名,代表整个虚拟磁盘(如云主机的系统盘)
/dev/vda1 设备文件名,代表该磁盘的第一个分区
挂载到 / 目录下
dd命令 : 二进制写入,制作一个大的文件,就当做一个分区.
格式化写入文件系统
建立空目录
查看可以使用的分区
将分区挂载到指定的目录
/dev/loop0 在Linux系统中代表第⼀个循环设备(loop device)。循环设备,也被称为
回环设备或者loopback设备,是⼀种伪设备(pseudo-device),它允许将⽂件作为块设备
(block device)来使⽤。这种机制使得可以将⽂件(⽐如ISO镜像⽂件)挂载(mount)为
⽂件系统,就像它们是物理硬盘分区或者外部存储设备⼀样
卸载分区
我们已经能够根据inode号在指定分区找⽂件了,也已经能根据⽬录⽂件内容,找指定的inode了,在指定的分区内,我们可以为所欲为了。可是:
问题:inode不是不能跨分区吗?Linux不是可以有多个分区吗?我怎么知道我在哪⼀个分区???
分区写⼊⽂件系统,无法直接使用,需要和指定的⽬录关联,进⾏挂载才能使⽤。
所以,可以根据访问⽬标⽂件的"路径前缀"准确判断我在哪⼀个分区。
扩展
那么OS是如何将数据写到磁盘的呢?
实际上不只CPU具有寄存器,外设同样也可以有寄存器.OS对外设的控制,是通过对设备内部的寄存器进行数据写入的!
文件系统总结
本文从硬件到软件系统性地介绍了Linux下磁盘存储与文件系统的核心概念。首先讲解了机械磁盘的物理结构(磁头、磁道、柱面、扇区)和逻辑寻址方式(CHS与LBA转换),指出操作系统通过LBA线性地址访问磁盘。其次详细阐述了文件系统的关键组件:超级块记录文件系统元信息,块位图管理数据块分配,inode结构存储文件属性并通过多级索引映射数据块,目录则保存文件名与inode的映射关系。最后解释了文件访问路径解析机制(dentry缓存)和分区挂载原理,揭示了Linux通过路径前缀确定分区的设计思想。全文由浅入深地构建了对磁盘存储体系的完整认知框架。
————————————————
版权声明:本文为CSDN博主「egoist祈」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/egoist2023/article/details/151688693