(学习总结33)Linux Ext2 文件系统与软硬链接
Linux Ext2 文件系统与软硬链接
- 理解硬件
- 磁盘、服务器、机柜、机房
- 磁盘物理结构
- 磁盘的逻辑结构
- 实际过程
- CHS 与 LBA 地址转换
- 引入文件系统
- 引入 " 块 " 概念
- 引入 " 分区 " 概念
- 引入 " inode " 概念
- ext2 文件系统
- 宏观认识
- Block Group 块组与其内部构成
- Super Block 超级块
- GDT(Group Descriptor Table)块组描述符表
- Block Bitmap 与 Inode Bitmap 位图
- Inode Table
- Data Block
- inode 和 Data Block 映射
- i_block 寻址
- 具体创建文件过程
- 目录与文件名
- 部分系统接口介绍
- 打开目录函数 opendir
- 关闭目录函数 closedir
- 读取目录函数 readdir
- 目录项结构体 struct dirent
- 路径解析
- 路径缓存
- 文件的 ACM 时间戳属性
- 挂载分区
- 软硬链接
- 创建文件链接命令 ln
- 硬链接
- 软链接
- 软硬链接对比与其用途
- 硬链接与路径环问题
理解硬件
磁盘、服务器、机柜、机房
机械磁盘 HDD(Mechanical Hard Disk Drive) 在 冯·诺依曼体系结构 中属于外设,也是计算机中唯一的一个机械设备。其特点为:访问慢、容量大、价格便宜等等。
固态硬盘 SSD(Solid-State Drive)也是外存。与 HDD 相比,访问速度快、容量相对小、价格高等等。两者为互补关系,不过随着技术发展,SSD 将进一步替代 HDD。
服务器会有专门的插槽,用来插入磁盘存储数据。多个服务器会放入机柜当中存放,机柜又会存放在专门的机房中统一管理,形成了明显的层级结构:
层级 | 组件 | 功能 | 关系 |
---|---|---|---|
第一层 | 磁盘(HDD/SSD) | 数据存储的基本物理单元 | 安装在服务器或存储设备内部 |
第二层 | 服务器 | 提供计算、存储、网络服务 | 多块磁盘组成服务器存储系统 |
第三层 | 机柜 | 集中放置和管理服务器/设备 | 多台服务器垂直安装在机柜中 |
第四层 | 机房 | 容纳 IT 基础设施的环境 | 多个机柜排列在机房内 |
磁盘物理结构
磁盘相关知识:
-
扇区是从磁盘读出和写入信息的最小单位,通常大小为 512 字节。
-
磁头(head)数:每个盘片一般有上下两面,分别对应 1 个磁头,共 2 个磁头。
-
磁道(track)数:磁道是从盘片外圈往内圈编号 0 磁道、1 磁道 …,靠近主轴的同心圆用于停靠磁头,不存储数据。
-
柱面(cylinder)数:磁道构成柱面,数量上等同于磁道个数。
-
扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同。
-
圆盘(platter)数:就是盘片的数量。
-
磁盘容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数。
-
注意传动臂上的磁头是共进退的
磁盘存储的基本单元是扇区,通过读写扇区将数据进行存储与修改,那怎样定位一个扇区呢?
-
定位访问哪一个柱面(磁道)(cylinder)
-
定位磁头(header)
-
定位一个扇区(sector)
这就是 CHS(Cylinder-Head-Sector)地址定位方式,通过这三个参数,可以唯一确定磁盘上的一个物理扇区。
CHS 寻址对早期的磁盘非常有效,知道用哪个磁头,读取哪个柱面上的第几扇区就可以读到数据了。
而 文件 = 内容 + 属性
其都是数据,即便占据多个扇区,只要我们能定位一个扇区,那就能定位多个关联的扇区。
但是 CHS 模式支持的硬盘容量有限,因为系统用 8 bit 来存储磁头地址,用 10 bit 来存储柱面地址,用 6 bit 来存储扇区地址,而一个扇区共有 512 Byte,这样使用 CHS 寻址一块硬盘最大容量为 256 × 1024 × 63 × 512 Byte = 8064 MB(若按 1 MB = 1000000 Byte 来算就是 8.4 GB)。现代硬盘和操作系统普遍使用 LBA(逻辑块地址),直接通过线性编号访问扇区。
磁盘的逻辑结构
磁盘存储单元本质上是弧形的,但逻辑上我们可以把磁盘的磁道 " 拉直 " 形成线性结构,这样每一个扇区,就有了一个线性地址(其实就是数组下标),这种地址叫做逻辑块地址 LBA(Logical Block Addressing):
实际过程
之前提到过传动臂上的磁头是共进退的,其可以在不同柱面同一个磁道同一个扇区进行访问。
柱面是一个逻辑上的概念,在每一面上相同半径的磁道逻辑上构成柱面。而磁盘物理上分了很多面,但在我们看来,逻辑上磁盘整体是由 " 柱面 " 卷起来的。
所以,磁盘的真实情况是某一盘面的某一个磁道展开即为一维数组,整个磁盘所有盘面的同一个磁道,即柱面展开为二维数组,整个磁盘就是多张二维的扇区数组表,即三维数组(圆筒形纸举例):
则寻址一个扇区顺序:先找到哪一个柱面(Cylinder),再确定柱面内哪一个磁道(磁头 Head),最后确定扇区(Sector)。
从 C/C++ 的数组角度来说,不管是几维的,本质都是一维数组。每一个扇区都有一个下标,其叫做 LBA 就是线性地址。
操作系统只需要使用 LBA 即可,而磁盘在内部需要将 LBA 地址与 CHS 地址相互转换。
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 地址。操作系统使用磁盘时可以只用一个数字访问磁盘扇区。
引入文件系统
引入 " 块 " 概念
硬盘是典型的 " 块 " 设备,操作系统读取硬盘数据的时候,考虑到效率问题,使用的是一次性连续读取多个扇区,即一次性读取一个 " 块 "(block)。
硬盘的每个分区是被划分为一个个的 " 块 “。一个 " 块 " 的大小是由格式化的时候确定的,并且不可以更改,最常见的是 4 KB,即连续八个扇区组成一个 " 块 "。” 块 " 是文件存取的最小单位。
注意:
-
磁盘就是一个三维数组,我们把它看待成为一个 " 一维数组 " ,数组下标则是 LBA,每个元素都是扇区
-
每个扇区都有 LBA,那么 8 个扇区一个块,每一个块的地址我们也能算出来。
-
知道 LBA,则块号 = LBA / 8
-
知道块号,则 LAB = 块号 × 8 + n ( n 是块内第几个扇区)
引入 " 分区 " 概念
磁盘可以被分成多个分区(partition)。以 Windows 观点来看,一块磁盘可能会将它分成 C、D、E 盘。则 C、D、E 就是分区。分区从实质上说就是对硬盘的一种格式化。但是Linux 的设备都是以文件形式存在,那是怎么分区的呢?
柱面是分区的最小单位,我们可以利用参考柱面号码的方式来进行分区,其本质就是设置每个区的起始柱面和结束柱面号码。 此时可以将硬盘上的柱面(分区)进行平铺,将其想象成一个大的平面。
柱面大小一致,扇区个位一致,只要知道每个分区的起始和结束柱面号,就知道每一个柱面多少个扇区,该分区多大也能得出。
引入 " inode " 概念
在上篇文章中我们说过 文件 = 数据 + 属性
,使用 ls -l
命令的时候除了文件名,还能看到文件元数据(属性):
每行包含 7 列:
-
权限模式
-
硬链接数
-
文件所有者
-
文件所属组
-
文件大小
-
最后修改时间
-
文件名
ls -l
实际是读取存储在磁盘上的文件信息,然后在用户层显示出来。
其实这个信息除了通过这种方式来读取,还有 stat 命令能够看到更多信息。
到这我们需要思考一个问题,文件数据都储存在 " 块 " 中。那么很显然,我们还必须找到一个地方储存文件的元信息(属性信息),比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做 inode(索引节点):
每一个文件都有对应的 inode,里面包含了与该文件有关的一些信息。为了能解释清楚inode,我们需要了解一下文件系统。
注意:
-
Linux 下文件的存储是属性和内容分离存储的
-
Linux 下,保存文件属性的集合叫做 inode,一个文件有一个 inode。inode 内有一个唯一的标识符,叫做 inode 号。
我们可以看看文件的属性 inode 的具体代码:
/*
* Constants relative to the data blocks
*/
#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)/*
* Structure of an inode on the disk
*/
struct ext2_inode {__le16 i_mode; /* File mode */__le16 i_uid; /* Low 16 bits of Owner Uid */__le32 i_size; /* Size in bytes */__le32 i_atime; /* Access time */__le32 i_ctime; /* Creation time */__le32 i_mtime; /* Modification time */__le32 i_dtime; /* Deletion Time */__le16 i_gid; /* Low 16 bits of Group Id */__le16 i_links_count; /* Links count */__le32 i_blocks; /* Blocks count */__le32 i_flags; /* File flags */union {struct {__le32 l_i_reserved1;} linux1;struct {__le32 h_i_translator;} hurd1;struct {__le32 m_i_reserved1;} masix1;} osd1; /* OS dependent 1 */__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */__le32 i_generation; /* File version (for NFS) */__le32 i_file_acl; /* File ACL */__le32 i_dir_acl; /* Directory ACL */__le32 i_faddr; /* Fragment address */union {struct {__u8 l_i_frag; /* Fragment number */__u8 l_i_fsize; /* Fragment size */__u16 i_pad1;__le16 l_i_uid_high; /* these 2 fields */__le16 l_i_gid_high; /* were reserved2[0] */__u32 l_i_reserved2;} linux2;struct {__u8 h_i_frag; /* Fragment number */__u8 h_i_fsize; /* Fragment size */__le16 h_i_mode_high;__le16 h_i_uid_high;__le16 h_i_gid_high;__le32 h_i_author;} hurd2;struct {__u8 m_i_frag; /* Fragment number */__u8 m_i_fsize; /* Fragment size */__u16 m_pad1;__u32 m_i_reserved2[2];} masix2;} osd2; /* OS dependent 2 */
};
-
文件名属性并未纳入到 inode 数据结构内部
-
inode 的大小一般是 128 字节或者 256,这里后面统一标准为 128 字节
-
任何文件的内容大小可以不同,但是属性大小一定是相同的
到目前为止还有两个问题:
-
我们已经知道硬盘是典型的 " 块 " 设备,操作系统读取硬盘数据的时候,读取的基本单位是 " 块 “。” 块 " 又是硬盘的每个分区下的结构,难道 " 块 " 是随意的在分区上排布的吗?那要怎么找到 " 块 " 呢?
-
还有就是上面提到的存储文件属性的 inode,又是如何放置的呢?
而文件系统就是为了组织管理这些问题的。
ext2 文件系统
宏观认识
我们想要在硬盘上储文件,必须先把硬盘格式化为某种格式的文件系统,才能存储文件。文件系统的目的就是组织和管理硬盘中的文件。
在 Linux 系统中,最常见的是 ext2 系列的文件系统。其早期版本为 ext2,后来又发展出 ext3 和 ext4。ext3 和 ext4 虽然对 ext2 进行了增强,但是其核心设计并没有发生变化,我们这里以较老的 ext2 作为演示对象。
ext2 文件系统将整个分区划分成若干个同样大小的块组 (Block Group),如下图所示:
只要能管理一个分区就能管理所有分区,也就能管理所有磁盘文件。
上图中启动块(Boot Block/Sector)的大小是确定的,为 1 KB,由 PC 标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。启动块之后才是 ext2 文件系统的开始。
Block Group 块组与其内部构成
ext2 文件系统会根据分区的大小划分为 N 个 Block Group。而每个 Block Group 都有着相同的结构组成。
Super Block 超级块
存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:
- bolck 和 inode 的总量
- 未使用的 block 和 inode 的数量
- 一个 block 和 inode 的大小
- 最近一次挂载的时间
- 最近一次写入数据的时间
- 最近一次检验磁盘的时间等其它文件系统的相关信息。
若 Super Block 的信息被破坏,可以说整个文件系统结构就被破坏了。
超级块在每个块组的开头都有一份拷贝(第一个块组必须有,后面的块组可以没有)。 为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常工作,就必须保证文件系统的 Super Block 信息在这种情况下也能正常访问。所以一个文件系统的 Super Block 会在多个 Block Group 中进行备份,这些 Super Block 区域的数据保持一致。
/*
* Structure of the super block
*/
struct ext2_super_block {__le32 s_inodes_count; /* Inodes count */__le32 s_blocks_count; /* Blocks count */__le32 s_r_blocks_count; /* Reserved blocks count */__le32 s_free_blocks_count; /* Free blocks count */__le32 s_free_inodes_count; /* Free inodes count */__le32 s_first_data_block; /* First Data Block */__le32 s_log_block_size; /* Block size */__le32 s_log_frag_size; /* Fragment size */__le32 s_blocks_per_group; /* # Blocks per group */__le32 s_frags_per_group; /* # Fragments per group */__le32 s_inodes_per_group; /* # Inodes per group */__le32 s_mtime; /* Mount time */__le32 s_wtime; /* Write time */__le16 s_mnt_count; /* Mount count */__le16 s_max_mnt_count; /* Maximal mount count */__le16 s_magic; /* Magic signature */__le16 s_state; /* File system state */__le16 s_errors; /* Behaviour when detecting errors */__le16 s_minor_rev_level; /* minor revision level */__le32 s_lastcheck; /* time of last check */__le32 s_checkinterval; /* max. time between checks */__le32 s_creator_os; /* OS */__le32 s_rev_level; /* Revision level */__le16 s_def_resuid; /* Default uid for reserved blocks */__le16 s_def_resgid; /* Default gid for reserved blocks *//** These fields are for EXT2_DYNAMIC_REV superblocks only.** Note: the difference between the compatible feature set and* the incompatible feature set is that if there is a bit set* in the incompatible feature set that the kernel doesn't* know about, it should refuse to mount the filesystem.** e2fsck's requirements are more strict; if it doesn't know* about a feature in either the compatible or incompatible* feature set, it must abort and not try to meddle with* things it doesn't understand...*/__le32 s_first_ino; /* First non-reserved inode */__le16 s_inode_size; /* size of inode structure */__le16 s_block_group_nr; /* block group # of this superblock */__le32 s_feature_compat; /* compatible feature set */__le32 s_feature_incompat; /* incompatible feature set */__le32 s_feature_ro_compat; /* readonly-compatible feature set */__u8 s_uuid[16]; /* 128-bit uuid for volume */char s_volume_name[16]; /* volume name */char s_last_mounted[64]; /* directory where last mounted */__le32 s_algorithm_usage_bitmap; /* For compression *//** Performance hints. Directory preallocation should only* happen if the EXT2_COMPAT_PREALLOC flag is on.*/__u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/__u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */__u16 s_padding1;/** Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.*/__u8 s_journal_uuid[16]; /* uuid of journal superblock */__u32 s_journal_inum; /* inode number of journal file */__u32 s_journal_dev; /* device number of journal file */__u32 s_last_orphan; /* start of list of inodes to delete */__u32 s_hash_seed[4]; /* HTREE hash seed */__u8 s_def_hash_version; /* Default hash version to use */__u8 s_reserved_char_pad;__u16 s_reserved_word_pad;__le32 s_default_mount_opts;__le32 s_first_meta_bg; /* First metablock block group */__u32 s_reserved[190]; /* Padding to the end of the 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];
};
Block Bitmap 与 Inode Bitmap 位图
Block Bitmap 中每个 bit 表示一个 Data Block 中哪个数据块已经被占用,哪个数据块没有被占用。
Inode Bitmap 同理,每个 bit 表示一个 inode 是否空闲可用。
Inode Table
-
存放文件属性如:文件大小、所有者、最近修改时间等。
-
其为当前分组所有 Inode 属性的集合
-
inode 编号以分区为单位整体划分,不可跨分区
Data Block
数据区,存放文件内容,也就是一个一个的 Block。根据不同的文件类型有以下几种情况:
-
对于普通文件,文件的数据存储在数据块中。
-
对于目录,该目录下的所有文件名和目录名存储在所在目录的数据块中。除了文件名外,
ls -l
命令看到的其它信息保存在该文件的 inode 中。 -
Block 号按照分区划分,不可跨分区
inode 和 Data Block 映射
i_block 寻址
在 inode 内部中的 i_block
是用来进行 inode 和 block 映射的:
// ......#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)// ......__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */// ......
而 i_block
中的 EXT2_N_BLOCKS
是一个宏,值为 15,其中前 12 个指针为直接块指针,用于直接指向存储文件内容的普通数据块(即磁盘上的物理存储位置)。
如果文件较大,直接块指针不足以存储文件所有内容的地址,i_block
会通过多级间接块索引表指针扩展寻址能力:
-
一级间接块索引表指针:第 13 个(i_block[12])指向一个块,这个块指向多个直接块指针。
-
二级间接块索引表指针:第 14 个(i_block[13])指向一个块,这个块指向多个一级间接块索引表指针。
-
三级间接块索引表指针:第 15 个(i_block[14])指向一个块,这个块指向多个二级间接块索引表指针。
注意,多级间接块指针本身指向的是也是数据块,大小同普通数据块一样是 4 KB,只是内容存储的是低一级的块指针。
这里统计一下 i_block
寻址能力,假设数据块大小为 4KB,指针为 4 字节(Byte):
则直接块指针:12 block -> 12 × 4KB = 48KB
的文件内容
一级间接块指针:1 block -> 4KB / 4B = 1024 个直接块指针 -> 1024 × 4KB = 4MB
内容
二级间接块指针:1024 × 1024 个直接块指针 -> 4GB
内容
三级间接块指针:1024 × 1024 × 1024 个直接块指针 -> 4TB
内容
这样通过 inode 存储属性,使用 i_block
寻找内容,则 文件 = 内容 + 属性
都能找到。
结论:
-
分区之后的格式化操作,就是对分区进行分组,在每个分组中写入 Super Block、GDT、Block、Bitmap、Inode 等管理信息,这些管理信息统称即为文件系统。
-
只要知道文件的 inode 号,就能在指定分区中确定是哪一个分组,在哪一个分组确定是哪一个 inode。
-
拿到 inode 后文件属性和内容就全部都有了。
具体创建文件过程
创建一个新文件主要有以下 4 个操作:
-
存储属性,内核先找到一个空闲的 i 节点(下面是 272273)。然后把文件信息记录到其中。
-
存储数据,该文件需要存储在三个磁盘块,内核找到了三个空闲块:300、500、800。将内核缓冲区的第一块数据复制到 300,下一块复制到 500,以此类推。
-
记录分配情况,文件内容按顺序 300、500、800 存放。内核在 inode 上的磁盘分布区记录了上述块列表。
-
添加文件名到目录。
新的文件名 test.txt,Linux 内核只需在当前的目录中记录这个文件,将入口(272273,test.txt)添加到目录文件,然后以 文件名 和 inode 之间的对应关系将文件名和文件的内容及属性连接起来。
目录与文件名
我们访问文件,都是用的文件名,没用过 inode 号。但其实 Linux 会使用文件名找到其对应的 inode 。
目录也是文件,但是磁盘上没有目录的概念,只有文件的 属性 + 内容 的概念。而目录的属性同其它文件一样,内容则保存的是 内部的文件名 和 Inode 号 的映射关系。
我们可以使用系统接口打印目录里的内容,函数等介绍留到下一部分:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>int main(int argc, char* argv[])
{if (argc != 2){ fprintf(stderr, "参数错误");exit(EXIT_FAILURE);} DIR* dir = opendir(argv[1]);if (dir == NULL){ perror("opendir");exit(EXIT_FAILURE);} struct dirent* entry;while ((entry = readdir(dir)) != NULL){ // 跳过 "." 和 ".." 目录项if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0){ continue;} printf("文件名: %-10s, inode: %lu\n", entry->d_name, (unsigned long)entry->d_ino); } closedir(dir);return 0;
}
-
所以访问文件必须打开当前目录,根据目录中的文件名获得对应的 inode 号,然后进行文件访问。
-
所以访问文件必须要知道当前工作目录,本质是必须要打开当前工作目录文件,查找目录文件的内容。
部分系统接口介绍
打开目录函数 opendir
opendir 返回值:成功时返回一个指向 DIR 结构的指针,失败时返回 NULL,并设置 errno 来指示错误类型
opendir 参数 name :要打开的目录路径名
关闭目录函数 closedir
closedir 返回值:成功时返回 0,失败时返回 -1,并设置 errno 来指示错误类型
closedir 参数 dirp :接收由 opendir() 返回的目录流指针
读取目录函数 readdir
readdir 返回值:成功时返回一个指向 struct dirent 的指针,包含下一个目录项的信息。到达目录末尾或出错时返回 NULL(到达末尾不改 errno,出错时设置 errno)
readdir 参数 dirp :接收由 opendir() 返回的目录流指针
目录项结构体 struct dirent
struct dirent {ino_t d_ino; // Inode 号off_t d_off; // 到下一个 dirent 的偏移unsigned short d_reclen; // 本记录的长度unsigned char d_type; // 文件类型char d_name[256]; // 文件名
};
d_ino (inode number):表示文件的 inode 编号
d_off :表示到下一个目录项的偏移量
d_reclen :表示本目录项记录的总长度,主要用于内部处理
d_type :文件类型信息(非 POSIX 标准,但大多数现代 UNIX 系统支持)
d_name :表示文件名,以 null 结尾
路径解析
Linux 中所有目录都是文件,查看目录内容时需要用上一级目录打开,而上一级又要从上上级的目录中打开,类似 " 递归 " 一样,直到遇到 /
根目录。
而访问目标文件时,需要把路径中所有的目录全部解析,比如:/home/user1/test_file/test3/test.c
要从根目录开始依次打开每一个目录,根据目录名依次访问每个目录下指定的目录,直到访问到 test.c 停止。这个过程就叫做 Linux 的路径解析。
所以我们知道访问文件必须要有 目录 + 文件名 = 路径
的这一原因,而根目录是固定的文件名,其 inode 号无需查找,系统开机之后就一定会知道。
但我们注意到访问文件时需要路径,可路径又由谁提供呢?
-
用户访问文件都是 指令/工具 访问,其本质是进程访问,进程有 CWD,则进程提供路径。
-
在磁盘文件系统中,用户新建的任何文件,都在用户本身(家目录)或者系统指定的目录下新建,这是天然就有的路径。
-
Linux 系统本身有根目录,根目录下又有众多缺省目录。
-
可以说 系统 + 用户 共同构建 Linux 路径结构。
路径缓存
Linux 磁盘中,不存在真正的目录,只有文件。其只保存文件 属性 + 内容
。
原则上访问任何文件,都要从 /
根目录开始进行路径解析。但这样效率太慢,Linux 会缓存历史路径结构,加快访问。
打开的文件如果是目录,则需操作系统自己在内存中进行路径维护,这也是 Linux 产生目录这一概念的原因。
在 Linux 内核维护树状路径结构的结构体叫做 struct dentry(Linux 内核 2.6.32):
注意:
-
每个文件其实都有对应的 dentry 结构,包括普通文件。这样所有被打开的文件,就可以在内存中形成整个树形结构。
-
整个树形节点也属于 LRU(Least Recently Used) 结构中,进行节点淘汰。
-
整个树形节点也会隶属于 Hash,方便快速查找。
-
更重要的是,这个树形结构整体构成了 Linux 的路径缓存结构,访问任何文件都需要先在这棵树下根据路径进行查找,找到就返回属性 inode 和 内容,没找到就从磁盘加载路径,添加 dentry 结构到内存中,缓存新路径。
文件的 ACM 时间戳属性
下面解释一下文件的三个时间:
- Access :最后一次访问文件内容的时间
- Modify :文件内容最后一次修改的时间
- Change 文件内容(如权限、所有者)最后一次修改的时间
挂载分区
我们已经能够根据 inode 号在指定分区找文件,也已经能根据目录文件内容,找指定的 inode,在指定的分区内,我们可以准确查找到目标文件。
可是 inode 不能跨分区,而 Linux 有多个分区,寻找文件的 inode 时怎么知道用户本身在哪一个分区呢?
-
事实上,在 Linux 系统中的
/dev/loop0
代表第一个循环设备(loop device)。 -
而循环设备也被称为回环设备或者 loopback 设备,是一种伪设备(pseudo-device),它允许将文件作为块设备(block device)来使用。
-
这种机制可以将文件(比如 ISO 镜像文件)挂载(mount)为文件系统,就像它们是物理硬盘分区或者外部存储设备一样。
综上:
-
分区写入文件系统时,无法直接使用,需要和指定的目录关联,进行挂载才能使用。
-
所以可以根据访问目标文件的 " 路径前缀 " 准确判断用户在哪一个分区。
软硬链接
创建软硬链接可以使用 ln
命令,这里先简单介绍这个命令。
创建文件链接命令 ln
语法:ln [选项] [源文件] [目标文件]
功能:创建文件链接
常用选项:
- -s :创建软链接(默认是硬链接)
- -f :强制覆盖已存在的目标文件
- -i :覆盖前提示确认
- -v :显示详细操作信息
- -n :将软链接视为普通文件(避免递归链接目录)
硬链接
上部分我们知道真正找到磁盘上文件的并不是文件名,而是 inode。其实在 Linux 中可以让多个文件名对应于同一个 inode。
使用 ln [源文件] [目标文件]
命令可以创建硬链接:
myFile.txt 和 myFile.txt 的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个链接数,可以看到两者的 inode 相同且硬链接数为 2。
事实上,我们在删除文件时干了两件事情:
- 在目录中将对应的记录删除
- 将硬链接数 -1,如果为 0,则将对应磁盘相关 Bitmap 标记为 0,即删除文件内容。
软链接
硬链接是通过 inode 引用另外一个文件,而软链接是通过路径名称引用另外一个文件。但新的文件和被引用的文件的 inode 不同,这算是一个快捷方式。
Windows 中桌面图标就是使用软链接的方式,并且将图标 " 卸载 ",只是将软链接删掉而已,应用本身还存在于文件系统中,通过寻找对应的 .exe
后缀还可以点击运行的。
使用命令 ln -s [源文件] [目标文件]
可以创建软链接:
软硬链接对比与其用途
对比:
硬链接:
- 硬链接不是一个独立的文件,只是文件名和目标文件 inode 的映射关系。
- 硬链接与目标文件共享相同的 inode,删除目标文件后硬链接仍能访问数据。
- 硬链接不能跨文件系统(分区)。
- 不能对目录创建硬链接。
- 硬链接和目标文件完全平等,修改其中任一文件会影响其它所有文件。
软链接:
-
软链接是独立文件,因为它有独立的 inode 号。
-
软链接可以跨文件系统。
-
可以对目录创建软链接。
-
删除源文件后,软链接会失效,成为 " 悬空链接 " 。
用途:
硬链接:
- Linux 文件中的
.
和..
就是硬链接 - 可以用于文件备份
软链接:
- 类似快捷方式
硬链接与路径环问题
我们注意到,用户不可以对目录使用硬链接:
这是因为用户可能将上级目录硬链接到当前目录,造成路径环问题。而有些命令与工具需要遍历 Linux 的树状目录结构(如 find、tree 等命令),为了安全的考量,Linux 不允许用户对目录硬链接。
而软链接本身的文件类型被重新划分为 l
文件类型,遍历针对 d
类型文件(即目录),并不会涉及软链接的目录。
我们注意到 Linux 中的 .
和 ..
本身就是对目录的硬链接,但两者是由 Linux 自己创建的,其作用是方便命令行操作。本身名字特殊,遍历 Linux 树状目录结构只需特殊处理即可(例如本文章的 ext2 文件系统 - 目录与文件名
当中的演示代码处理)。
相关文章:
(学习总结33)Linux Ext2 文件系统与软硬链接
Linux Ext2 文件系统与软硬链接 理解硬件磁盘、服务器、机柜、机房磁盘物理结构磁盘的逻辑结构实际过程 CHS 与 LBA 地址转换 引入文件系统引入 " 块 " 概念引入 " 分区 " 概念引入 " inode " 概念 ext2 文件系统宏观认识Block Group 块组与其内…...
LeetCode算法题(Go语言实现)_36
题目 给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点…...
牛客华为机试--HJ48 从单向链表中删除指定值的节点C++
题目描述 示例1 示例2 该题的核心是每来一组数据,都要从头开始找,找到数据后再插入。而不是直接在尾部插入数据。 上代码 #include <iostream> using namespace std;struct ListNode {int val;ListNode *next;ListNode(int x) : val(x), next(nu…...
Jmeter 插件【性能测试监控搭建】
1. 安装Plugins Manager 1.1 下载路径: Install :: JMeter-Plugins.org 1.2 放在lib/ext目录下 1.3 重启Jmeter,会在菜单-选项下多一个 Plugins Manager菜单,打开即可对插件进行安装、升级。 2. 客户端(Jmeter端) 2.1 安装plugins manager…...
从攻防演练到AI防护:网络安全服务厂商F5的全方位安全策略
随着AI和云原生技术的蓬勃兴起,多云架构的广泛采用,企业内部IT系统正经历着翻天覆地的变化。在这个转型期,传统的攻击手段和防守策略正面临着巨大的挑战。基于此,用户需要跳出传统的思维模式,采取新的视角,…...
【Introduction to Reinforcement Learning】翻译解读5
4 核心算法 我们将算法分为三类:基于价值的方法、基于策略的方法和混合算法。 4.1 基于价值的方法Value-based 一个重要的突破是Q-learning的引入,它是一种无模型算法,被视为off-policy时间差分(TD)学习。TD学习无疑…...
Jmeter中的bzm-concurrency thread group 与普通线程组的区别
在 JMeter 中,bzm - Concurrency Thread Group(由 BlazeMeter 提供)和标准的 Thread Group 是两种不同的线程组实现,主要区别在于 并发控制模型 和 负载调节方式。以下是详细对比: 1. 核心区别 特性bzm - Concurrency Thread Group标准 Thread Group负载模型基于并发数(C…...
VBA将Word文档内容逐行写入Excel
如果你需要将Word文档的内容导入Excel工作表来进行数据加工,使用下面的代码可以实现: Sub ImportWordToExcel()Dim wordApp As Word.ApplicationDim wordDoc As Word.DocumentDim excelSheet As WorksheetDim filePath As VariantDim i As LongDim para…...
ubuntu22部署 3d-tiles-tools
安装fnm curl -fsSL https://fnm.vercel.app/install | bash安装nodejs 20.17.0LTS版本 https://nodejs.org/zh-cn/download/package-manager安装依赖包 # Download and install nvm: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash# in…...
WebStrom关闭模板字符串自动转换
WebStrom关闭模板字符串自动转换 Editor > General > smart Keys > JavaScript > Automatically replace string literal with template string on typing "${"...
【零基础入门unity游戏开发——动画篇】新动画Animator的使用 —— AnimatorController和Animator的使用
考虑到每个人基础可能不一样,且并不是所有人都有同时做2D、3D开发的需求,所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】:主要讲解C#的基础语法,包括变量、数据类型、运算符、…...
npx vite 可以成功运行,但 npm run dev 仍然报错 Missing script: “dev“
npx vite 可以成功运行,但 npm run dev 仍然报错 Missing script: "dev",说明问题可能出在 npm 的脚本解析 或 项目配置 上。以下是具体解决方案: 1. 检查 package.json 的物理位置 可能原因: 你当前运行的目录下可能有一个 无效的 package.json,而真正的 packa…...
Java 泛型的逆变与协变:深入理解类型安全与灵活性
泛型是 Java 中强大的特性之一,它提供了类型安全的集合操作。然而,泛型的类型关系(如逆变与协变)常常让人感到困惑。 本文将深入探讨 Java 泛型中的逆变与协变,帮助你更好地理解其原理和应用场景。 一、什么是协变与…...
C语言核心知识点整理:结构体对齐、预处理、文件操作与Makefile
目录 结构体的字节对齐预处理指令详解文件操作基础Makefile自动化构建总结 1. 结构体的字节对齐 字节对齐原理 内存对齐:CPU访问内存时,对齐的地址能提高效率。操作系统要求变量按类型大小对齐。对齐规则: 每个成员的起始地址必须是min(成…...
深度学习|注意力机制
一、注意力提示 随意:跟随主观意识,也就是指有意识。 注意力机制:考虑“随意线索”,有一个注意力池化层,将会最终选择考虑到“随意线索”的那个值 二、注意力汇聚 这一部分也就是讲第一大点中“注意力汇聚”那个池化…...
特权FPGA之乘法器
完整代码如下: timescale 1ns / 1ps// Company: // Engineer: // // Create Date: 23:08:36 04/21/08 // Design Name: // Module Name: mux_16bit // Project Name: // Target Device: // Tool versions: // Description: // // Dependencies: …...
安全的企业局域网聊天工具哪个好用?
在当今数字化时代,企业对于局域网聊天工具的需求日益增长,尤其是在对数据安全和定制化服务有较高要求的大中型政企单位中。安全的企业局域网聊天工具哪个好用?虽然市面上有很多即时通讯软件,今天来介绍一下已经拥有十年行业经验的…...
如何应对客户频繁变更需求
如何应对客户频繁变更需求?要点包括: 快速响应、深入沟通、灵活规划、过程记录、风险管控。这些策略既能降低项目失控风险,也能帮助团队在变动环境中保持高效率。其中深入沟通尤为关键,它不仅能够让团队第一时间了解客户意图&…...
R语言进行聚类分析
目录 简述6种系统聚类法 实验实例和数据资料: 上机实验步骤: 进行最短距离聚类: 进行最长距离聚类: 进行中间距离聚类: 进行类平均法聚类: 进行重心法聚类: 进行ward.D聚类:…...
1.6-抓包技术(Burp Suite\Yakit抓包\Web、APP、小程序)
1.6-抓包技术(Burp Suite\Yakit抓包\Web、APP、小程序) 如果要使用抓包软件,基本上第一步都是要安装证书的。原因如下: 客户端(浏览器或应用)会检测到证书不受信任,并弹出 证书错误࿰…...
DAPP实战篇:使用web3.js连接合约
说明 本系列内容目录:专栏:区块链入门到放弃查看目录 如果你还没有创建好项目请先查看:《DApp实战篇:先用前端起个项目》,如果你还不知道web3.js是什么请先查看:《DApp实战篇:前端技术栈一览》。 安装 点此查看web3.js官方文档 打开项目根目录,并唤起终端: 键入w…...
用 Python 构建一个简单的本地视频流媒体服务器
你是否曾经想过在本地网络上轻松地将电脑上的视频分享给手机或平板电脑观看?也许你下载了一部电影,想在客厅的智能电视上播放,却不想费力地拷贝文件。今天,我们将深入分析一个 Python 脚本,它使用 wxPython 创建图形用…...
汇丰xxx
1. Spring Boot 的了解,解决什么问题? 我的理解: Spring Boot 是一个基于 Spring 框架的快速开发脚手架,它简化了 Spring 应用的初始搭建和开发过程。解决的问题: 简化配置: 传统的 Spring 应用需要大量的…...
ruby基础语法
以下是 Ruby 基础语法的简明总结,适合快速入门: 一、变量与常量 局部变量 小写字母或下划线开头,作用域为当前代码块。 name "Alice" _age 20实例变量 以 开头,属于对象实例。 name "Bob"类变量 以 开头…...
智体OS-V3.1版:新增了rt-datalink底层数据链通讯,实现【无网络】本机使用
##智体OS-V3.1版本发布 更新简介 dtns.os智体OS-V3.1版:新增了rt-datalink底层数据链通讯(使用本地局域网的websocket端口通讯),解决了本机【无网络】正常使用的问题。 更新内容 dtns.connector支持使用新的rt-datalink与智体…...
Windows系统安装Git以及Git常用命令介绍
本文主要介绍Windows系统安装Git的方法,以及Git常用命令介绍。 一、下载Git 官网: Git - Downloads (git-scm.com) 根据自己的系统选择 我的是64位的Windows系统,选择对应的安装包,点击后开始下载 等待下载完成 二、安装Git 双…...
HTML 开发者的智能助手:通义灵码在 VSCode 中的应用
引言 在 HTML 开发领域,提高编码效率和质量是每位开发者追求的目标。通义灵码,作为一款由阿里云技术团队开发的智能编码助手,能够通过其强大的 AI 能力,为 HTML 开发者提供包括代码自动补全、智能注释、代码优化等多方面的支持。…...
MySQL随机获取记录之方法(The Method of Randomly Obtaining Records in MySQL)
MySQL中如何随机获取一条记录 随机获取一条记录是在数据库查询中常见的需求,特别在需要展示随机内容或者随机推荐的场景下。在 MySQL 中,有多种方法可以实现随机获取一条记录,每种方法都有其适用的情况和性能特点。在本文中,我们将…...
ngx_core_module 的 create_conf
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_core_module-CSDN博客 定义在 src\core\nginx.c ngx_module_t ngx_core_module {NGX_MODULE_V1,&ngx_core_module_ctx, /* module context */ngx_core_commands, /* module directives */…...
41--华为IPSec主备链路实验:当加密隧道遇上“双保险“
🚦 华为IPSec主备链路实验:当加密隧道遇上"双保险" “如果你的IPSec隧道只有一条路,那就像走钢丝不系安全带——刺激但危险!” —— 本文将用华为设备打造主备双加密通道,结合IP-link智能检测,让…...
Reactive编程框架与工具
文章目录 6.2 后端 Reactive 框架6.2.1 Spring WebFlux核心架构核心组件实际应用高级特性性能优化适用场景与限制 6.2.2 Akka(Actor模型)Actor模型基础基本用法高级特性响应式特性实现性能优化实际应用场景优势与挑战 6.2.3 Vert.x(事件驱动&…...
vi/vim常用快捷键
那么今天我们继续昨天没有介绍完的vi编辑器,来看看常用的一些快捷键,方便我们对文件的编辑. 1.拷贝当前行yy,拷贝当前行向下的5行5yy,并粘贴(输入p) 2.删除当前行dd,删除当前行向下的5行5d 3.在文件中查找某个单词[命令模式/关键字,回车查找,输入n就是查找下一个] ⭐️&…...
初始JavaEE篇 —— SpringBoot 统一功能处理
找往期文章包括但不限于本期文章中不懂的知识点: 个人主页:我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏:JavaEE 目录 前言 拦截器 基本使用 拦截器的路径配置 统一数据返回格式 统一异常处理 前言 在实际开发中,某些功能需要强…...
Spring AI Alibaba 文档检索使用
一、文档检索 (Document Retriever)简介 1、核心概念 文档检索(DocumentRetriever)是一种信息检索技术,旨在从大量未结构化或半结构化文档中快速找到与特定查询相关的文档或信息。文档检索通常以在线(online)方式运行。 DocumentRetriever通…...
遍历算法及其应用详解
李升伟 整理 什么是遍历? 遍历是指按照某种规则或顺序,系统地访问数据结构(如树、图等)中的每个节点一次且仅一次的过程。遍历是算法设计中的基本操作,用于访问、检查或修改数据结构中的所有元素。 主要遍历算法 1…...
.NET-EFCore基础知识
.NET EF Core(Entity Framework Core)是微软开发的一款开源的对象关系映射(ORM)框架,用于在.NET 应用程序中与数据库进行交互。以下是一些.NET EF Core 的基础知识: 1. 什么是 EF Core EF Core 是.NET 平…...
R语言基础包可视化(一:axis函数)
R语言基础包可视化(一:axis函数) 背景axis函数(坐标轴函数)各参数的图片示例hadj和padjline和poslty,lwd,lwd.ticksgap.axis总结背景 之前在介绍正态Q-Q图的过程中,画过标准正态分布的随机数、分数数、分布函数、密度函数的图像,相关的文章连接参考此处:R语言正态Q-Q图…...
Axure疑难杂症:垂直菜单折叠与展开(玩转垂直菜单)
亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! 课程主题:垂直菜单折叠与展开 主要内容:折叠与展开效果 应用场景:PC后台菜单、动态下拉菜单、商品分类选择等折叠与展开场景 案例展示: 案例视频: 垂直菜单折叠与展开效果 正文内容: 关于垂直菜单的折叠与…...
docker 中跑faster-whisper 教程(1050显卡)
之前我本地机器运行faster-whisper 会报错类似 Could not load library libcudnn_ops_infer.so.8github 上也有类似的情况 :https://github.com/SYSTRAN/faster-whisper/issues/516#issuecomment-2785038635 缺少.so.8 文件,我通过以下方式,…...
MySQL 在 CentOS 7 环境安装完整步骤
1. 卸载已有环境(MariaDB/旧版MySQL) 1.停止 MariaDB 服务 systemctl stop mariadb.service 2.检查并卸载 MariaDB/MySQL 安装包 rpm -qa | grep mariadb # 检查 MariaDB 相关包 rpm -qa | grep mysql # 检查 MySQL 相关包 sudo yum remo…...
下一代智能爬虫框架:ScrapeGraphAI 详解
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、ScrapeGraphAI 概述1.1 ScrapeGraphAI介绍1.2 核心特点1.3 工作流程1.4 关键模块1.5 对比传统爬虫框架1.6 安装二、基础操作2.1 自定义解析规则2.2 数据后处理2.3 分布式爬取三、高级功能3.1 多步骤交互采集3.2 动态…...
C++-ffmpeg-2-3-工厂模式封装SDL-9-7
1.接口设计 2.窗口渲染器和材质初始化 3.渲染Draw并测试渲染YUV 4.渲染画面随窗口大小自动缩放并抗锯齿 5.清理接口和接收窗口退出事件 1.接口设计:原则 主要的实现步骤: main的流程: 1打开文件 yuv_file.open("400_300_25.yuv&quo…...
下载极客漫画——Beautiful Soup实用案例
文章目录 一、背景介绍 二、实现思路 三、效果图 四、构思 五、实现细节 1. 第一步下载网页 2. 寻找和下载漫画图像 3. 保存图像,找到前⼀张漫画 六、完整代码 七、程序输出 八、附录 九、总结 一、背景介绍 XKCD网站是一个关于浪漫、隐喻、数字、以及…...
【大模型理论篇】SWIFT: 可扩展轻量级的大模型微调基础设施
1. 背景 大模型(LLM)和多模态大模型(MLLM)利用基于Transformer的架构获得了很迅速的发展。为满足对这些模型的训练和轻量级微调需求,目前已有一些开源框架,如LLaMA-Factory、Firefly、FastChat、Axolotl和LMFlow等。但这些框架在支持的模型、技术和功能上…...
利用 schedule 模块在每日上午每 3 秒执行任务
一、schedule 模块基础原理与功能概述 schedule 模块维护了一个任务队列,每个任务都关联着一个特定的时间触发器和对应的执行函数。当系统时间到达任务设定的触发时间时,模块会从队列中取出相应的任务并执行其关联的函数。这种设 计模式使得开发者无需过多关注底层的时间处理…...
ruby超高级语法
以下是 Ruby 中一些 极度硬核 的语法和底层特性,涉及元编程的深渊、虚拟机原理、语法黑魔法等,适用于追求极限的 Ruby 开发者: 一、语法核弹级操作 1. 动态修改继承链 class A; def foo; "A"; end end class B; def foo; "B…...
Java Stream API:现代化集合处理的艺术
Java Stream API:现代化集合处理的艺术 引言 在Java 8中引入的Stream API彻底改变了我们处理集合数据的方式。它不仅仅是一个新的工具集,更代表了一种声明式、函数式的编程范式。本文将深入探讨Java Stream的核心概念、使用场景和最佳实践。 一、什么是Stream? Stream(…...
ruby高级语法
以下是 Ruby 高级语法的详细总结,涵盖元编程、模式匹配、闭包、并发模型等核心主题: 一、元编程(Metaprogramming) 1. 动态定义方法 class DynamicClass# 使用 define_method 动态定义方法["foo", "bar"].e…...
特权FPGA之UART串口
0.简介 通用异步收发器(Universal Asynchronous Receiver/Transmitter,UART)可以和各种标准串行接口,如RS 232和RS 485等进行全双工异步通信,具有传输距离远、成本低、可靠性高等优点。一般UART由专用芯片如8250,1645…...
oracle 索引失效
在 Oracle 11g 中,索引失效的常见原因包括函数修改列、隐式类型转换、统计信息过时等,解决方法需结合版本特性(如虚拟列、索引跳跃扫描)。通过执行计划分析、统计信息维护和合理使用提示(Hints),…...