一、理解硬件
(1) 磁盘、服务器、机柜、机房
- 机械磁盘是计算机中唯⼀的⼀个机械设备
- 磁盘---外设
- 慢
- 容量大,价格便宜
(2) 磁盘的物理结构
(3) 磁盘的存储结构
扇区:是磁盘存储数据的基本单位,512字节,块设备
磁道由多个扇区组成
问:如何定位一个扇区?
- 可以先定位磁头(header)
- 确定磁头要访问哪⼀个柱⾯(磁道)(cylinder)
- 定位⼀个扇区(sector)
- CHS地址定位
问:⽂件=内容+属性都是数据,⽆⾮就是占据那几个扇区的问题!能定位⼀个扇区了,能不能定位多个扇区呢?
从这里我们可以了解到
- 磁盘总容量是40GB左右
- 一共由8000w左右的扇区
- 一个扇区的大小是512字节
- 每次IO的最小值是512字节
- 分区表类型GPT(不用太过在意)
- 有两个分区
各种组成部分概念
- 扇区是从磁盘读出和写入信息的最小单位,通常大小为 512 字节
- 磁头(head)数:每个盘片一般有上下两面,分别对应1个磁头,共2个磁头
- 磁道(track)数:磁道是从盘片外圈往内圈编号0磁道,1磁道...,靠近主轴的同心圆用于停靠磁头,不存储数据
- 柱面(cylinder)数:磁道构成柱面,数量上等同于磁道个数
- 扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同
- 圆盘(platter)数:就是盘片的数量
- 磁盘容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数
- 细节:传动臂上的磁头是共进退的(这点比较重要,后面会说明)
(4) 磁盘的逻辑结构
Ⅰ. 理解过程
磁带上面可以存储数据,我们可以把磁带"拉直",形成线性结构
那么磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在⼀起的磁带,那么磁盘的逻 辑存储结构我们也可以类似于:
这样每⼀个扇区,就有了⼀个线性地址(其实就是数组下标),这种地址叫做 LBA
Ⅱ. 真实过程
⼀个细节:传动臂上的磁头是共进退的
柱⾯是⼀个逻辑上的概念,其实就是每一面上,相同半径的磁道逻辑上构成柱面;所以,磁盘物理上分了很多面,但是在我们看来,逻辑上,磁盘整体是由"柱面"卷起来的
结论:
- 磁道:某一盘面的某一个磁道展开--->即一维数组
- 柱面:整个磁盘所有盘面的同⼀个磁道,即柱面展开--->即二维数组
- 整个磁盘--->即三维数组
结论:
- 所以,寻址⼀个扇区:先找到哪⼀个柱面(Cylinder),在确定柱面内哪⼀个磁道(其实就是磁头位置, Head),在确定扇区(Sector),所以就有了CHS
- 但是,OS只需要使用LBA就可以了!!LBA地址转成CHS地址,CHS如何转换成为LBA地址;谁做啊??磁盘自己来做!固件(硬件电路,伺服系统)
(5) 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使用磁盘,就可以用⼀个数字访问磁盘扇区了
二、引入文件系统
(1) 引入"块"的概念
其实硬盘是典型的"块"设备,操作系统读取硬盘数据的时候,其实是不会⼀个个扇区地读取,这样效率太低,而是⼀次性连续读取多个扇区,即⼀次性读取⼀个"块"(block);硬盘的每个分区是被划分为⼀个个的"块";⼀个"块"的大小是由格式化的时候确定的,并且不可 以更改,最常见的是4KB,即连续⼋个扇区组成⼀个"块";"块"是文件存取的最小单位
看下图:
相关字段
注意:
- 磁盘就是⼀个三维数组,我们把它看待成为⼀个"⼀维数组",数组下标就是LBA,每个元素都是扇区
- 每个扇区都有LBA,那么8个扇区⼀个块,每⼀个块的地址我们也能算出来
- 知道LBA:块号=LBA/8
- 知道块号:LAB=块号*8+n.(n是块内第几个扇区)
(2) 引入分区概念
问:其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有⼀块磁盘并且将它分区成C,D,E盘;那个C,D,E就是分区;分区从实质上说就是对硬盘的⼀种格式化;但是Linux的设备都是以文件形式存在,那是怎么分区的呢?
答:柱面是分区的最小单位,我们可以利用参考柱面号码的方式来进行分区,其本质就是设置每个区的起始柱面和结束柱面号码;此时我们可以将硬盘上的柱面(分区)进行平铺,将其想象成⼀个大的平面,如下图所示:
注意:
柱面大小⼀致,扇区个位⼀致,那么其实只要知道每个分区的起始和结束柱面号,知道每 ⼀个柱面多少个扇区,那么该分区多大,其实和解释LBA是多少也就清楚了.
(3) 引入"inode"的概念
之前我们说过文件=数据+属性 ,我们使用 ls -l 的时候看到的除了看到文件名,还能看到文件元数据(属性)
每行包含7列
- 模式
- 硬链接数
- 文件所有者
- 组
- 大小
- 最后修改时间
- 文件名
其实这个信息除了通过这种方式来读取,还有⼀个stat命令能够看到更多信息(在上面已经有图片了,这里就不再次打印了)
到这我们要思考⼀个问题,文件数据都储存在"块"中,那么很显然,我们还必须找到⼀个地方储存文件的元信息(属性信息),比如文件的创建者、文件的创建日期、文件的大小等等;这种储文件元信息的区域就叫做inode,中文译名为"索引节点"
每⼀个文件都有对应的inode,里面包含了与该文件有关的⼀些信息;为了能解释清楚inode,我们需要是深入了解⼀下文件系统
注意:
- Linux下文件的存储是属性和内容分离存储的
- Linux下,保存文件属性的集合叫做inode,⼀个文件,⼀个inode,inode内有⼀个唯⼀ 的标识符,叫做inode号
所以让我们见识一下inode(AI生成的简单版本)
cpp#include <linux/types.h> #include <linux/time.h> // 简化版 inode 结构体(模拟 Linux 内核实现) struct inode { // 基础标识 ino_t i_ino; // inode 编号(唯一标识) dev_t i_dev; // 所在设备号(主设备号+次设备号) umode_t i_mode; // 文件类型与权限(如 S_IFDIR|0775) nlink_t i_nlink; // 硬链接数 uid_t i_uid; // 所有者用户 ID gid_t i_gid; // 所有者组 ID dev_t i_rdev; // 若为设备文件,存储设备号 loff_t i_size; // 文件大小(字节) blkcnt_t i_blocks; // 占用的磁盘块数 unsigned int i_blkbits; // 块大小(以 bit 为单位,如 12 表示 4096 字节) // 时间戳 struct timespec64 i_atime; // 最后访问时间 struct timespec64 i_mtime; // 最后内容修改时间 struct timespec64 i_ctime; // 最后元数据修改时间 struct timespec64 i_birthtime; // 文件创建时间(部分文件系统支持) // 数据块指针(简化版,实际 ext4 等会更复杂) sector_t *i_block; // 指向数据块索引表的指针 // 内核管理相关 atomic_t i_count; // 引用计数 spinlock_t i_lock; // 保护 inode 的自旋锁 struct super_block *i_sb; // 指向所属超级块(文件系统全局信息) };注意:
- 文件名属性并未纳入到inode数据结构内部
- inode的大小⼀般是128字节或者256,我们后面统⼀128字节
- 任何文件的内容大小可以不同,但是属性大小⼀定是相同的
到目前为止,相信大家还有两个问题:
- 我们已经知道硬盘是典型的"块"设备,操作系统读取硬盘数据的时候,读取的基本单位 是"块";"块"又是硬盘的每个分区下的结构,难道"块"是随意的在分区上排布的吗?那要怎么找到"块"呢?
- 还有就是上面提到的存储文件属性的inode,又是如何放置的呢?
还有就是上面提到的存储⽂件属性的inode,又是如何放置的呢?
接下来的第三个大话题,exit2文件系统将为大家解答
三、exit2文件系统
(1) 宏观认识
- 所有的准备工作都已经做完,是时候认识下文件系统了;我们想要在硬盘上储⽂件,必须先把硬盘格式化为某种格式的文件系统,才能存储文件。文件系统的目的就是组织和管理硬盘中的文件;在Linux系统中,最常见的是ext2系列的文件系统;其早期版本为ext2,后来又发展出ext3和ext4。 ext3和ext4虽然对ext2进行了增强,但是其核心设计并没有发生变化,我们仍是以较老的ext2作为演示对象
- ext2文件系统将整个分区划分成若干个同样大小的块组(Block Group),如下图所示;只要能管理⼀个分区就能管理所有分区,也就能管理所有磁盘文件
上图中启动块(Boot Block/Sector)的大小是确定的,为1KB,由PC标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块;启动块之后才是ext2文件系统的开始
(2) Block Group
- ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成;政府管理各区的例⼦
(3) 块组内部构成
Ⅰ. 超级块(Super Block)
- 存放文件系统本身的结构信息,描述整个分区的文件系统信息;记录的信息主要有:block和inode的总量,未使用的block和inode的数量,⼀个block和inode的大小,最近⼀次挂载的时间,最近⼀次写入数据的时间,最近⼀次检验磁盘的时间等其他文件系统的相关信息;Super Block的信息被破坏,可以说整个文件系统结构就被破坏了
- 超级块在每个块组的开头都有⼀份拷贝(第⼀个块组必须有,后面的块组可以没有);为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常⼯作,就必须保证⽂件系统的super block信息在这种情况下也能正常访问;所以⼀个文件系统的super block会在多个block group中进行备份, 这些super block区域的数据保持⼀致
cpp#include <linux/types.h> #include <linux/spinlock.h> #include <linux/list.h> /* * 简化版 Linux 超级块结构体 * 作用:管理整个文件系统的全局信息 */ struct super_block { // 1. 设备与标识 dev_t s_dev; // 所在设备号(主设备号+次设备号,如 253,2) unsigned long s_flags; // 挂载标志(只读、同步等) // 2. 文件系统基本信息 unsigned long s_blocksize; // 磁盘块大小(字节,如 4096) unsigned char s_blocksize_bits; // 块大小的比特位(如 12 表示 2^12=4096) loff_t s_maxbytes; // 单个文件最大支持大小 // 3. Inode 全局管理 unsigned long s_ino; // 系统最大 inode 编号 unsigned long s_ninodes; // 总 inode 数量 unsigned long s_freeinodes; // 空闲 inode 数量 ino_t s_first_ino; // 第一个可用 inode // 4. 数据块全局管理 sector_t s_blocks; // 文件系统总数据块数 sector_t s_free_blocks; // 空闲数据块数 sector_t s_first_data_block; // 第一个数据块位置 // 5. 时间与状态 struct timespec64 s_wtime; // 超级块最后写入时间 unsigned char s_state; // 文件系统状态(干净/异常) // 6. 内核关联管理 atomic_t s_count; // 超级块引用计数 spinlock_t s_lock; // 保护超级块的自旋锁 struct list_head s_inodes; // 所有 inode 链表(挂载的文件索引) // 7. 文件系统类型 char s_type[16]; // 文件系统类型(ext4/xfs/btrfs 等) };Ⅱ. GDT(Group Descriptor Table)
- 块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符;每个块组描述符存储⼀个块组的描述信息,如在这个块组中从哪里开始是inode Table,从哪里开始是Data Blocks,空闲的inode和数据块还有多少个等等;块组描述符在每个块组的开头都有⼀份拷贝
cpp#include <stdint.h> // 1. GDT 段描述符(8 字节 = 64 位,x86 标准格式) struct gdt_entry { uint16_t limit_low; // 段界限 低 16 位 uint16_t base_low; // 基地址 低 16 位 uint8_t base_mid; // 基地址 中 8 位 uint8_t access; // 访问权限位(P、DPL、S、TYPE) uint8_t granularity; // 粒度位(G、D/B、L、AVL)+ 段界限 高 4 位 uint8_t base_high; // 基地址 高 8 位 } __attribute__((packed)); // 禁用内存对齐,保证严格 8 字节 // 2. GDTR 寄存器结构(6 字节,用于 lgdt 指令加载 GDT) struct gdtr { uint16_t limit; // GDT 表界限 = 总字节数 - 1 uint32_t base; // GDT 表的线性基地址 } __attribute__((packed));Ⅲ. 块位图
- Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
Ⅳ. inode位图
- 每个bit表示⼀个inode是否空闲可用
Ⅴ. i节点表
- 存放文件属性如文件大小,所有者,最近修改时间等
- 当前分组所有Inode属性的集合
- inode编号以分区为单位,整体划分,不可跨分区
Ⅵ. Data Block
数据区:存放文件内容,也就是⼀个⼀个的Block;根据不同的文件类型有以下几种情况:
- 对于普通文件,文件的数据存储在数据块中
- 对于目录,该目录下的所有文件名和目录名存储在所在目录的数据块中,除了文件名外,ls -l命令看到的其它信息保存在该文件的inode中
- Block号按照分区划分,不可跨分区
(4) inode和datablock映射(弱化)
- inode内部存在__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ , EXT2_N_BLOCKS =15,就是用来进行inode和block映射的
- 这样文件=内容+属性,就都能找到了
接下来让我们见识下inode
cpp#include <linux/types.h> // 常用宏定义 #define EXT2_N_BLOCKS 15 // 12个直接块 + 3个间接块 /* * EXT2 磁盘 inode 结构体 * 来源:Linux 内核 ext2_fs.h */ struct ext2_inode { __le16 i_mode; /* 文件类型与权限 (S_IFREG|0755 等) */ __le16 i_uid; /* 所有者 UID (低16位) */ __le32 i_size; /* 文件大小 (字节) */ __le32 i_atime; /* 最后访问时间 (Unix 时间戳) */ __le32 i_ctime; /* 最后元数据修改时间 */ __le32 i_mtime; /* 最后内容修改时间 */ __le32 i_dtime; /* 删除时间 (若未删除则为0) */ __le16 i_gid; /* 所属组 GID (低16位) */ __le16 i_links_count; /* 硬链接数 */ __le32 i_blocks; /* 文件占用的 512 字节块数 */ __le32 i_flags; /* 文件标志 (如 immutable、append-only) */ __le32 i_osd1; /* OS 特定字段 (Linux 下为保留) */ /* 数据块指针数组:12个直接块 + 1个一级间接 + 1个二级间接 + 1个三级间接 */ __le32 i_block[EXT2_N_BLOCKS]; __le32 i_version; /* 文件版本 (NFS 使用) */ __le32 i_file_acl; /* 扩展属性块地址 (EXT2 旧版) */ __le32 i_dir_acl; /* 目录 ACL (EXT2 旧版,新版用 i_size 高位) */ __le32 i_faddr; /* 碎片地址 (EXT2 废弃) */ /* OS 特定扩展字段 (共 12 字节) */ union { struct { __le32 l_i_reserved2[3]; } linux1; struct { __le32 h_i_translator; __le32 h_i_reserved1[2]; } hurd1; struct { __le32 m_i_reserved1[4]; } masix1; } i_osd2; } __attribute__((packed)); // 禁用对齐,保证磁盘上严格 128 字节问:请解释在知道inode号的情况下,在指定分区,对文件进行增、删、查、改是在做什么?
结论:
- 分区之后的格式化操作,就是对分区进行分组,在每个分组中写入SB、GDT、Block Bitmap、Inode Bitmap等管理信息,这些管理信息统称:文件系统
- 只要知道文件的inode号,就能在指定分区中确定是哪⼀个分组,进而在哪⼀个分组确定是哪⼀个inode
- 拿到inode文件属性和内容就全部都有了
下面,我们通过touch一个新文件来看看如何工作
具体可以简化成下图
创建⼀个新文件主要有以下4个操作:
- 存储属性
内核先找到⼀个空闲的i节点(这里是263466),内核把文件信息记录到其中
- 存储数据
该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800;将内核缓冲区的第⼀块数据复制到300,下⼀块复制到500,以此类推
- 记录分配情况
⽂件内容按顺序300,500,800存放;内核在inode上的磁盘分布区记录了上述块列表
- 添加文件名到目录
新的文件名abc;linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件;文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来
(5) 目录与文件名
问:
- 我们访问文件,都是用的文件名,没用过inode号啊?
- 目录是文件吗?如何理解?
答:
- 目录也是文件,但是磁盘上没有目录的概念,只有文件属性+文件内容的概念
- 目录的属性不用多说,内容保存的是:文件名和Inode号的映射关系
结论:
- 所以,访问文件,必须打开当前目录,根据文件名,获得对应的inode号,然后进行文件访问
- 所以,访问文件必须要知道当前⼯作目录,本质是必须能打开当前工作目录文件,查看目录文件的内容!
(6) 路径解析
问题:打开当前工作目录文件,查看当前工作目录文件的内容?当前工作目录不也是文件吗?我们访问当前工作目录不也是只知道当前工作目录的文件名吗?要访问它,不也得知道当前工作目录的inode吗?
- 答案1:所以也要打开:当前工作目录的上级目录,额....,上级目录不也是目录吗??不还是上面的问题吗?
- 答案2:所以类似"递归",需要把路径中所有的目录全部解析,出是"/"根目录。
- 最终答案3:而实际上,任何文件,都有路径,访问目标文件,比如: /home/whb/code/test/test/test.c 都要从根目录开始,依次打开每⼀个目录,根据目录名,依次访问每个目录下指定的目录,直到访问到test.c;这个过程叫做Linux路径解析
注意:
- 所以,我们知道了:访问文件必须要有目录+文件名=路径的原因
- 根目录固定文件名,inode号,无需查找,系统开机之后就必须知道
可是谁提供的路径?
- 你访问文件,都是指令/工具访问,本质是进程访问,进程有CWD!进程提供路径
- 你open文件,提供了路径可是最开始的路径从哪里来
- 所以Linux为什么要有根目录,根目录下为什么要有那么多缺省目录
- 你为什么要有家目录,你自己可以新建目录?
- 上面所有行为:本质就是在磁盘文件系统中,新建目录文件;而你新建的任何文件,都在你或者系统指定的目录下新建,这不就是天然就有路径了嘛!
- 系统+用户共同构建Linux路径结构
(7) 路径缓存
- 问题1:Linux磁盘中,存在真正的目录吗?
答案:不存在,只有文件;只保存文件属性+文件内容
- 问题2:访问任何文件,都要从/目录开始进行路径解析?
答案:原则上是,但是这样太慢,所以Linux会缓存历史路径结构
- 问题3:Linux目录的概念,怎么产生的?
答案:打开的⽂件是目录的话,由OS自己在内存中进行路径维护
cpp#include <linux/spinlock.h> #include <linux/list.h> #include <linux/rculist.h> #include <linux/lockref.h> #include <linux/seqlock.h> /* 前向声明 */ struct inode; struct vfsmount; struct dentry_operations; struct super_block; /** * struct dentry - 内核VFS目录项对象 * @d_lock: 保护目录项大部分字段的自旋锁 * @d_rcu: RCU延迟释放链表节点 * @d_parent: 父目录的目录项(当前目录的上一级) * @d_name: 目录项的文件名(name结构体) * @d_inode: 关联的inode(文件/目录元数据),NULL表示无关联 * @d_sb: 所属的超级块(文件系统实例) * @d_op: 目录项操作函数集(回调接口) * @d_flags: 目录项标志位(DCACHE_* 常量) * @d_lockref: 引用计数+锁,管理目录项生命周期 * @d_subdirs: 子目录项链表(当前目录下的所有子项) * @d_alias: 同一个inode的多个硬链接目录项链表 * @d_time: 目录项时间戳(用于缓存管理) * @d_fsdata: 文件系统私有数据指针 * @d_lru: LRU缓存链表节点(目录项回收使用) * @d_hash: 哈希表链表节点(快速查找目录项) * * 内核核心结构,用于文件名 <-> inode 映射,是路径查找的基础 */ struct dentry { /* RCU 相关字段 */ union { seqlock_t d_lock; /* 目录项自旋锁 */ struct rcu_head d_rcu; /* RCU释放节点 */ }; /* 核心关联字段 */ struct dentry *d_parent; /* 父目录项 */ struct qstr d_name; /* 文件名:name + len + hash */ struct inode *d_inode; /* 关联的inode,NULL=无效 */ struct super_block *d_sb; /* 所属超级块 */ const struct dentry_operations *d_op; /* 目录项操作函数 */ /* 状态与计数 */ unsigned int d_flags; /* 目录项标志 DCACHE_* */ struct lockref d_lockref; /* 引用计数 + 锁 */ /* 链表管理 */ struct hlist_node d_hash; /* 哈希表节点,用于快速查找 */ struct list_head d_subdirs; /* 子目录项链表头 */ struct hlist_node d_alias; /* inode别名(硬链接)链表 */ struct list_head d_lru; /* LRU回收链表节点 */ /* 扩展字段 */ unsigned long d_time; /* 缓存失效时间 */ void *d_fsdata; /* 文件系统私有数据 */ };注意:
- 每个文件其实都要有对应的dentry结构,包括普通文件;这样所有被打开的文件,就可以在内存中形成整个树形结构
- 整个树形节点也同时会隶属于LRU(Least Recently Used,最近最少使用)结构中,进行节点淘汰
- 整个树形节点也同时会⾪属于Hash,方便快速查找
- 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何文件,都在先在这 棵树下根据路径进行查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径
(8) 挂载分区
我们已经能够根据inode号在指定分区找文件了,也已经能根据目录文件内容,找指定的inode了,在指定的分区内,我们可以为所欲为了。可是:
问题:inode不是不能跨分区吗?Linux不是可以有多个分区吗?我怎么知道我在哪⼀个分区???
结论:
- 分区写入文件系统,无法直接使用,需要和指定的目录关联,进行挂载才能使用
- 所以,可以根据访问目标文件的"路径前缀"准确判断我在哪⼀个分区
(9) 文件系统总结
- 下面用几张图总结,⼀张是画的,其他都是在网上找的.主要想从不同角度说明.
四、软硬链接
(1) 硬链接
我们看到,真正找到磁盘上文件的并不是文件名,而是inode。其实在linux中可以让多个文件名对应于同⼀个inode
- abc和def的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode 263466的硬连接数为2
- 我们在删除文件时干了两件事情:
- 在目录中将对应的记录删除
- 将硬连接数-1,如果为0,则将对应的磁盘释放
(2) 软连接
硬链接是通过inode引用另外⼀个文件,软链接是通过名字引用另外⼀个文件,但实际上,新的文件和被引用的文件的inode不同,应用常见上可以想象成⼀个快捷方式;在shell中的做法
acm
下面解释⼀下文件的三个时间:
- Access最后访问时间
- Modify文件内容最后修改时间
- Change属性最后修改时间
(3) 软硬连接对比
- 软连接是独立文件
- 硬链接只是文件名和目标文件inode的映射关系
(4) 软硬连接的用途:
硬链接
- .和.. 就是硬链接
- 文件备份
软链接
- 快捷方式
补充:
- 硬链接(文件别名) (以小成本实现目录树与文件保护)
- 本质:就是在当前目录下新建一个新的字符串(文件名)与目标文件的映射
- 硬链接数:表明有多少个文件指向同一个inode 硬链接数-2=目录内又多少个文件
- 意义:备份文件、共享文件、节约存储空间,支撑目录树 只创建普通文件硬链接数为1
- 删除一个文件 ------》 先删除文件名与inode的映射关系
- 硬链接不能给你的目录建立硬链接(会造成死循环) (环形路径)
- 硬链接时目录/文件的别名;硬链接不能跨文件系统
- 问:.与..的本质是硬链接,怎么理解?
Ⅰ "."是当前目录的硬链接,指向该目录的inode
Ⅱ ".."是父目录的硬链接,指向当前目录的父目录的inode
※"."与".."的本质是硬链接,怎么理解
Ⅰ 它们不是独立的文件/目录,而是通过硬链接机制,从而构建出整个目录树的层级关系
Ⅱ find查找,遇到"."和".."忽略这两个目录(防止循环遍历) ------》正确与高效性
- 软连接(是一个独立的别名)
- 保存的是指向的目标的路径
- 类似于windows下的快捷方式










































