✨ 彼时春杉少年郎,笑看风华不知愁 🌏
📃个人主页 :island1314
🔥个人专栏:Linux—登神长阶
一、文件系统 1. 磁盘结构 机械磁盘是计算机中唯一的一个机械设备 磁盘--- 外设 慢 容量大,价格便宜 1.1 物理结构 1.2 存储结构 扇区: 是从磁盘读出和写入信息的最小单位,通常大小为 512 字节磁头(head)数: 每个盘片一般有上下两面,分别对应1个磁头,共2个磁头磁道(track)数: 磁道是从盘片外圈往内圈编号0磁道,1磁道.,靠近主轴的同心园用于停靠磁头,不存储数餐柱面(cylinder)数: 磁道构成柱面,数量上等同于磁道个数扇区(sector)数: 每个磁道都被切分成很多扇形区域,每道的扇区数量相同圆盘(platter)数: 就是盘片的数量磁盘容量 = 磁头数 x 磁道(柱面)数 x 毎道扇区数 x 毎扇区字节数 细节: 传动臂上的磁头是共进退的(这点比较重要,后面会说明)1.3 扇区(了解) 扇区(Sector) 是磁盘存储的基本单位,是磁盘上最小的可寻址数据块
特点:
固定大小: 传统的磁盘扇区大小为512字节,但现代硬盘和固态硬盘越来越多地使用4096字节(也称为“高级格式”)的扇区
物理地址: 每个扇区都有一个唯一的物理地址,由磁头号、柱面号和扇区号组成(CHS 寻址方式),或者由逻辑块地址(LBA )表示
读写操作: 磁盘的读写操作通常以扇区为单位进行。即使只读取或写入少量数据,磁盘控制器也会读取或写入整个扇区的数据
作用:
数据存储: 扇区是磁盘上实际存储数据的基本单位文件系统管理: 文件系统将多个扇区组合成更大的块(如4KB、8KB等),以便更高效地管理和分配存储空间错误检测与校正: 每个扇区通常包含一些额外的字节用于存储校验和或其他错误检测信息,以确保数据的完整性 如何定位一个扇区:
找到指定的磁头。 Header 确定磁头要访问的磁道(柱面) Cylinder 找到指定的扇区 Sector CHS (地址定位法)
这种在硬件上定位某一个扇区的寻址方案叫CHS定址法,即需要三个参数。
文件其实就是在磁盘中占有几个扇区的问题。
💡 CHS 寻址
对早期的磁盘非常有效,知道用哪个磁头,读取哪个柱面上的第几扇区就可以读到数据了。 但是CHS模式支持的硬盘容量有限,因为系统用8bit来存储磁头地址,用10bit来存储柱面地址,用6bit来存储扇区地址,而一个扇区共有512Byte,这样使用CHS寻址一块硬盘最大容量为256*1024*63*512B=8064MB(1MB=1048576B)(若按1MB=1000000B来算就是8.4GB) 1.4 逻辑结构 我们把磁盘抽象成巨大的线性结构
假设是两片四面。尽管扇区大小有差异,但是每个扇区的内存大小都是一样的。每一面上都有很多扇区,最后就把磁盘抽象成数组 。 有了数组,就可以通过下标找到某个扇区,但是磁盘只认CHS,所以要通过算法将下标转换成CHS的地址 。 找到下标后交给磁盘,磁盘内部会把线性地址转换成CHS的地址, 进而定位到某一个扇区里。 注意: 一般而言,OS未来和磁盘交互的时候,基本单位为4KB ,而不是512字节(一个扇区512字节),因为一次读512字节太少了,因为要提高效率
所以4KB=8个连续的扇区 ,系统把这8个连续的扇区称为块大小(数据块)
8个扇区为一个块,块号*8 = 每一个块的第一个扇区的下标 ,连续往后读就能知道整个块的下标
对于OS而言: 未来读取数据就能以块为单位了,这里的每一个块号的起始地址称作LBA (逻辑区块地址) LBA 和 CHS 的相互转化(补充 了解)
2. 理解分区、格式化 🔖 假设磁盘的空间很大,需要分区管理,因此只要能管理好其中一个区,剩下的其他区就可以照搬它的管理方法,全部管理好。
🔥 如果这里管理其中一个区,这个区还是太大,就要在这个区里面继续分组。只要把一个组管理好了,这个区的每一个组就都能管理好了
上图下半部分是磁盘文件系统图。
Linux文件系统 特点:文件内容和文件属性 分开存储
Block Group: 文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成超级块(Super Block): 存放文件系统本身的结构信息,描述的是整个分区整体的文件系统的情况 记录的信息主要有: bolck 和 inode的总量 未使用的block和inode的数量 一个block和inode的大小 最近一次挂载的时间 最近一次写入数据的时间 最近一次检验磁盘的时间等其他文件系统的相关信息 Super Block 的信息被破坏,可以说整个文件系统结构就被破坏了超级块不止一个,可能有多个,在不同的块组里,这样可以让文件系统更稳定 GDT,Group Descriptor Table: 块组描述符,描述块组属性信息块位图(Block Bitmap): Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用inode位图(inode Bitmap): 每个bit表示一个inode是否空闲可用。inode表: 存放文件属性 如 文件大小,所有者,最近修改时间等数据区: 存放文件内容inode Table 里面存的是文件的属性,Data blocks 里面存的是文件的内容 Linux中文件的属性是一个大小固定的集合体,也就是inode 结构体
结构体里面没有文件名。内核层面,每一个inode 都有inode number ,通过inode 号标识一个文件。
通过inode 号找到inode 后,inode 里面还有一个数组,可以映射到对应数据块
3. inode 是什么 🌈 在Linux文件系统中, inode(索引节点) 是一个非常重要的概念。每个文件和目录在Linux文件系统中都有一个对应的 inode ,它包含了除文件名之外的所有元数据信息
以下是一些关于 inode 的关键点:
3.1 inode的定义 inode 是文件系统中的一个数据结构,用于存储文件的元数据(即文件的属性信息)每个文件或目录都有一个唯一的 inode 号,注意:一个文件inode是有可能有多个目录项的,比如给一个文件创建多个硬链接,因此文件 和 inode 不是 一一对应 inode 编号是以分区为单位的,一个分区内 inode 号不能重复,两个分区间可以,所以 inode 不能跨分区访问。3.2 inode包含的信息
文件类型:普通文件、目录、符号链接等 权限:文件的读、写、执行权限 所有者:文件的所有者和组 时间戳:文件的创建时间、修改时间和访问时间 文件大小:文件的实际大小 指向数据块的指针:文件数据在磁盘上的位置 硬链接数:指向该inode的目录项数量 3.3 inode的作用 唯一标识: inode 号是文件系统的唯一标识符,即使文件名改变, inode 号也不会改变。 元数据管理: inode 存储了文件的所有重要元数据,使得文件系统可以高效地管理和检索文件。 硬链接支持:多个目录项可以指向同一个 inode ,这就是硬链接的基础。 3.4 inode与文件名的关系 文件名实际上是目录中的一个条目,指向相应的 inode
可以有多个文件名(硬链接) 指向同一个 inode
删除一个文件名并不会删除 inode ,只有当所有的硬链接都被删除时,inode 才会被释放
5. 查看inode信息 你可以使用一些命令来查看inode信息:
ls -i:显示文件和目录的 inode 号。 stat :显示文件的详细信息,包括inode号和其他元数据。 df -i:显示文件系统的 inode 使用情况。 示例:
sh# 显示当前目录下文件和目录的inode号
ls -i
# 显示某个文件的详细信息
stat example.txt
3.6 inode限制 每个文件系统有一个固定的 inode 数量上限,这在创建文件系统时确定 如果文件系统中的 inode 用尽,即使还有可用的磁盘空间,也无法再创建新的文件或目录 通过理解和使用 inode ,你可以更好地管理Linux文件系统,并解决一些与文件和目录相关的问题。
4. Inode 、Inode Table 、Data Blocks 三者关系 Inode 是一个数据结构,用于存储文件的元数据(即文件的属性信息)。每个文件和目录都有一个唯一的 inode Inode Table 是文件系统中存储所有inode 的一个区域。它是一个固定大小的数组,每个 inode 占用固定的大小。作用如下: Inode Table 存储了文件系统中所有文件和目录的元数据
每个 inode 在 Inode Table 中有一个唯一的编号(inode号)
当文件系统创建时,Inode Table 的大小是预先分配好的,这意味着文件系统中可以存储的文件和目录数量有一个上限
Data Blocks 是实际存储文件内容的磁盘空间。文件的数据被分割成多个块,并存储在这些数据块中。作用如下: 文件的实际内容存储在Data Blocks 中
Inode 中的指针指向这些数据块的位置
数据块的大小通常是固定的,例如4KB或8KB,具体取决于文件系统的配置
三者的关系 Inode 存储文件的元数据,并包含指向数据块的指针。Inode Table 是存储所有inode 的区域,每个inode 在其中有一个唯一的编号。Data Blocks 存储文件的实际内容,通过inode 中的指针来访问。 举个例子:
假设你有一个文件 example.txt ,其内容如下:
Inode: 这个文件的inode可能包含以下信息:
文件类型:普通文件 权限:644(用户可读写,组和其他用户只读) 所有者:user 组:user 创建时间、修改时间和访问时间 文件大小:13字节 指向数据块的指针:假设文件内容存储在第1000号数据块 Inode Table: 假设example.txt的inode号是12345,那么在Inode Table中,第12345个位置会存储上述inode信息。
Data Blocks: 文件的实际内容“Hello, World!”存储在第1000号数据块中。
5. 理解 inode 、块号都是全局的 在Linux文件系统中,块号(block number)和 inode号(inode number) 都是全局唯一的标识符。这意味着它们在整个文件系统范围内是唯一的,而不是在某个特定目录或子目录内唯一。
全局唯一性的意义
(1)Inode的全局唯一性:在一个文件系统中,每个inode号是唯一的。无论文件或目录位于文件系统的哪个位置,其inode号在整个文件系统范围内都是唯一的
确保每个文件和目录都有一个唯一的标识符 允许多个目录项(硬链接)指向同一个inode,从而实现文件共享 使得文件系统可以高效地管理和检索文件。 (2)块号的全局唯一性:在一个文件系统中,每个数据块的块号也是唯一的。无论数据块存储的是哪个文件的数据,块号在整个文件系统范围内都是唯一的 确保每个数据块有一个唯一的标识符 使得文件系统可以有效地管理和分配磁盘空间 防止数据块被重复使用,从而避免数据损坏 问题:为什么任何一个文件都需要有路径 ❓
文件路径是文件在文件系统中的唯一位置标识,确保操作系统能够准确找到和访问该文件 6. 通过 inode 查找文件 我们平时都是用文件名找文件,inode 就是通过文件名找到的。 由于目录=文件属性+文件内容 ,目录的内容就是文件名和inode编号的映射关系 。所以同一目录下不能有同名文件 找文件的顺序:文件名 -> inode编号 目录的 r 权限,本质是是否允许我们读取目录的内容,即 文件名和inode的映射关系 目录的 w 权限,新建文件时,本质:建立文件名和inode编号的映射关系 要找到一个文件,就要先找到文件所在的目录,然后打开,通过文件名与inode号的映射关系找到目标文件的inode。 目录也是文件,因此就要继续往上查找,直到来到根目录下。 根目录是系统给我们的,是已知的,就可以找到目标文件了。 这种逆向的路径解析由OS完成。这也是为什么Linux定位一个文件时,都要有路径的原因。 分区与查找文件的关系?
要找到一个文件,需要inode,但更前提的是需要知道在哪个分区。 由于我们用的云服务器一般只有一个盘,盘里面有分区。要访问一个分区,就需要把该分区挂载 ( 挂载实际是把磁盘分区和文件系统的目录进行关联,未来进入该分区本质就是进入该目录) 一个文件起始在访问之前,都是先有目录的,文件都是带有路径的,目录也是。所以根据目录的路径,就可以找到对应哪个分区。 所以目录本身除了可以定位文件,还能确定是在哪个分区下的 7. 理解创建新文件 🎐 将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过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号的映射关系 8. 实验 -- 挂载分区 8.1 查看分区 8.2 制作分区 dd if=/dev/zero of=./disk.iso bs=1M count=5 # 制作大的磁盘块,就当作分区
8.3 格式化写入文件系统 mkfs.ext4 disk.iso # 临时分区
8.4 建立空目录&挂载 sudo mkdir /mnt/myvda2 # 建立空目录
sudo mount -t ext4 ./disk.iso /mnt/myvda2/ # 将分区挂载到指定目录
8.5 进入分区
8.6 卸载分区 9. 路径 解析 & 缓存 9.1 路径解析 问题 :打开当前工作目录文件,查看当前工作目录文件的内容?当前工作目录不也是文件吗?我们访问当前工作目录不也是只知道当前工作目录的文件名吗?要访问它,不也得知道当前工作目录的inode吗?
答案1:所以也要打开:当前工作目录的上级目录,额…,上级目录不也是目录吗??不还是上面的问题吗? 答案2:所以类似"递归",需要把路径中所有的目录全部解析,出口是"/"根目录。 最终答案3:而实际上,任何文件,都有路径,访问目标文件,比如:/home/whb/code/test/test/test.c都要从根目录开始,依次打开每一个目录,根据目录名,依次访问每个目录下指定的目录,直到访问到test.c。 上面这个过程叫做Linux路径解析
注意:
所以,我们知道了:访问文件必须要有目录+文件名=路径 的原因。 根目录固定文件名,inode号,无需查找,系统开机之后就必须知道 可是路径谁提供?
你访问文件,都是指令/工具访问,本质是进程访问,进程有CWD!进程提供路径。 当我们 open 文件,提供了路径 可是最开始的路径从哪里来?
所以Linux为什么要有根目录,根目录下为什么要有那么多缺省目录? 你为什么要有家目录,你自己可以新建目录? 上面所有行为:本质就是在磁盘文件系统中,新建目录文件。而你新建的任何文件,都在你或者系统指定的目录下新建,这不就是天然就有路径了嘛!
系统+用户共同构建Linux路径结构
9.2 路径缓存
问题1:Linux磁盘中,存在真正的目录吗?
问题2:访问任何文件,都要从/目录开始进行路径解析?
答案:原则上是,但是这样太慢,所以Linux会缓存历史路径结构 问题3:Linux目录的概念,怎么产生的?
答案:打开的文件是目录的话,由OS自己在内存中进行路径维护 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 RecentlyUsed,最近最少使用)结构中,进行节点淘汰 整个树形节点也同时会隶属于Hash,方便快速查找 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何文件,都在先在这棵树下根据路径进行查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径 二、软硬链接 软链接(符号链接)和硬链接是Linux文件系统中两种不同类型的链接,它们用于在文件系统中创建指向同一文件的多个引用
1. 硬链接(Hard Link) 定义: 硬链接不是独立的文件,是文件系统中的一个目录项,没有独立的inode,与原始文件共享相同的inode号和数据块。本质上就是一组文件名和已经存在的文件映射关系
特点:
硬链接与原始文件实际上是同一个文件,只是有不同的目录项指向同一个inode 删除一个硬链接不会删除文件的实际内容,只有当所有硬链接都被删除时,文件的数据块才会被释放 硬链接不能跨越不同的文件系统 硬链接只能用于普通文件,不能用于目录 案例:
ln file.txt file.hard.link
2. 软链接(Soft Link) 2.1 基本概念 定义: 软链接是一个特殊的文件,它包含指向另一个文件或目录的路径。软链接类似于Windows中的快捷方式
特点:
软链接是一个独立的文件,有自己的inode号 软链接的内容是目标文件的路径名 删除软链接不会影响目标文件,但删除目标文件会使软链接失效(变成“悬空链接”) 软链接可以跨越不同的文件系统 软链接可以指向文件或目录 2.2 使用 情况一: 给当前目录下文件进行软链接
ln -s file.txt file-soft.link
情况二: 指定路径进行软链接
情况三: 指定目录进行软链接
问题: 如果使用In命令将生成了一个指向文件old的符号链接new,如果你将文件old删除,是否还能够访问文件中的数据?
答案: 无法再被访问
原因:
软链接文件是一个独立的文件,有自己的inode节点,这个文件数据中保存的是源文件路径,通过保存的路径访问源文件,因此源文件被删除则无法再访问,通过路径将找不到源文件,这时候软链接就会失效。 3. 对比 动态库也叫运行时库,是运行时加载的库,将库中数据加载到内存中后,每个使用了动态库的程序都要根据加载的起始位置计算内部函数以及变量地址,因此动态链接动态库加载及运行速度相较静态链接是较为不如的,但是它也有好处,就是多个程序在内存中只需要加载一份动态库就可以共享使用。 静态链接,链接静态库,每个程序将自己在库中用到的指令代码单独写入自己可执行程序中,程序运行时无依赖,加载运行速度快,但是程序运行后有可能会有冗余代码在内存中
删除软链接不影响目标文件,删除目标文件使软链接失效
ln original_file hard_link
ln -s target_file symbolic_link
注意:相比于静态库, 加载动态库的程序运行速度相对较慢,因为动态库运行时加载,映射到虚拟地址空间后需要重新根据 映射起始地址 计算函数/变量地址
4. inode 的引用计数 4.1 基本概念 在Linux文件系统中,inode的引用计数(也称为链接计数)是一个非常重要的概念。它表示指向该inode的目录项(或硬链接)的数量,用于管理文件生命周期,当计数为0时文件被删除。
引用计数的主要作用是帮助文件系统管理文件的生命周期和确保数据的一致性 如下代码:
4.2 作用 文件删除: 当一个文件被删除时,实际上只是删除了指向该inode的一个目录项(即硬链接),引用计数减1。只有当引用计数变为0时,文件的实际数据块才会被释放,inode也会被标记为可用文件共享: 多个目录项可以指向同一个inode,形成硬链接。引用计数记录这些硬链接的数量数据一致性: 通过引用计数,文件系统可以确保在所有硬链接都被删除之前,文件的数据不会被意外删除(文件备份) Linux 文件备份如下:
4.3 查看 你可以使用 ls -i 和 stat 命令来查看文件的inode号和引用计数
5. 软硬链接的意义 软硬链接提供文件的多路径访问
硬链接共享inode,提高数据冗余和可靠性 软链接则创建指向文件的快捷方式,支持跨文件系统和指向目录,增加灵活性 6. 为什么不能对目录进行硬链接 6.1 基本概念 在大多数文件系统(如ext4)中,不允许对目录创建硬链接,主要是为了避免循环引用和文件系统的复杂性。如果允许对目录进行硬链接,可能会导致以下问题:
循环引用:硬链接会形成多个指向同一inode的路径,这可能导致循环引用,使得文件系统难以遍历和管理 复杂性增加:文件系统的管理和维护会变得更加复杂,因为需要处理更多的边界情况和一致性问题 6.2 案例 假设我们有一个文件系统结构如下:
/home
└── user
├── dir1
│ └── file1
└── dir2
如果我们允许对目录进行硬链接,可以创建一个硬链接 dir2 指向 dir1:
ln /home/user/dir1 /home/user/dir2
此时,文件系统的结构看起来是这样的:
/home
└── user
├── dir1
│ └── file1
└── dir2 -> dir1 (硬链接)
如果我们在 dir1 中再创建一个硬链接 subdir 指向 dir2:
ln /home/user/dir2 /home/user/dir1/subdir
这将形成一个循环引用:
/home
└── user
├── dir1
│ ├── file1
│ └── subdir -> dir2 (硬链接)
└── dir2 -> dir1 (硬链接)
此时就会导致遍历困难,文件系统在遍历时会陷入无限循环 ,无法正确处理这种结构
因此,得到结论:
为了防止循环引用和简化文件系统的管理,大多数文件系统不允许对目录进行硬链接 。 软链接则没有这些限制 ,因为它只是存储目标路径,不会形成实际的inode共享