【Linux】深入拆解Ext文件系统:从磁盘物理结构到Linux文件管理
目录
1、理解硬件
(1)磁盘
(2)磁盘的物理结构
(3)磁盘的存储结构
(4)磁盘的逻辑结构
(5)CHS && LBA地址
2、引入文件系统
(1)“块”概念
(2)“分区”概念
(3)“inode”概念
3、ext2文件系统
(1)宏观认识
(2)Block Group
(3)块组内部结构
① 超级块(Super Block)
② GDT(Group Descriptor Table)
③ 块位图(Block Bitmap)
④ inode位图(Inode Bitmap)
⑤ i 节点表(Inode Table)
⑥ 数据块(Data Block)
(4)inode和Data Block 映射关系
(5)目录与文件名
(6)路径解析
(7)路径缓存
(8)挂载分区
(9)文件系统总结
4、软硬链接
(1)硬链接
(2)软连接
(3)硬链接局限性
1、理解硬件
(1)磁盘
磁盘是计算机中用于存储数据的硬件设备(外设)。机械磁盘(HDD)是计算机唯一的的机械设备,使用旋转的磁性盘片和机械臂读写数据,特点是容量大、成本低但速度较慢。
★ 有时你可能会听到人们在谈论一些实际上根本不是磁盘的磁盘,比如固态硬盘(SSD)。固态硬盘并没有可以移动的部分,外形也不想唱片那样,并且数据是存储在存储器(闪存)中的。与磁盘唯一的相似之处就是他也存储了大量即使在电源关闭时也不会丢失的数据。
(2)磁盘的物理结构
1、马达带动盘片旋转。
2、盘片是数据存储的载体,表面覆盖磁性材料,通过磁化方向记录二进制数据(0和1)。
3、磁头臂将磁头移动到目标磁道上方。
4、磁头通过感应(读)或改变(写)盘片的磁性状态完成数据操作。【读:感应盘片上的磁场变化,转换为电信号。写:通过电磁铁改变盘片上的磁性方向。】
(3)磁盘的存储结构
① 单盘片视图:
· 磁道(Track):盘片上的同心圆环,数据存储在磁道上。每个磁道被划分为多个扇区(通常每扇区存储512字节或4KB)。外圈磁道周长更长,但存储密度与内圈相同(现代硬盘可能采用区域位记录技术优化容量)。
· 扇区(Sector):磁道的最小存储单元,是读写数据的基本单位。包含间隙(Gap):分隔扇区的空白区域,用于同步和纠错。扇区头:记录扇区地址和状态信息。
· 主轴(Spindle):驱动所有盘片同步旋转的核心部件。
② 多盘片视图:
· 盘片(Platter):多个磁性盘片叠放在主轴上,正反两面均可存储数据。通常从盘片0开始(对应面0和面1,依此类推)
· 柱面(Cylinder):所有盘片上相同半径的磁道组成的虚拟圆柱体。例如:柱面k = 所有盘片的第k条磁道。
· 磁头(Head):每个盘面对应一个磁头,由磁头臂控制。编号:面0对应磁头0,面1对应磁头1,依此类推。
磁盘容量 = 磁头数×磁道(柱面)数×每道扇区数×每扇区字节数
细节:转动臂上的磁头是共进退的(重要)
类比总结:
盘片 ≈ 一本书的多页。
磁道 ≈ 页面上的一行文字。
扇区 ≈ 行中的一个单词。
柱面 ≈ 所有页面相同行号的集合(如所有书的第10行)。
③ CHS寻址方式
已知柱面、磁头、扇区显然就可以定位数据了,这就是数据定位(寻址)方式之一,CHS寻址。
步骤1:磁头臂移动到目标柱面(所有盘片的同一磁道)。
步骤2:激活对应磁头(选择具体盘面)。
步骤3:等待盘片旋转至目标扇区下方,完成读写。
局限性:CHS模式支持的磁盘容量有限,早期BIOS使用24位存储CHS参数(10位柱面 + 8位磁头 + 6位扇区)。最大支持 504 MiB(≈528MB)的磁盘(1024×255×63×512B)。需预先知道硬盘的实际物理参数(柱面/磁头/扇区数),不同硬盘可能不同。
(4)磁盘的逻辑结构
① 理解过程
磁带上面可以存储数据,我们可以把磁带“拉直”,形成线性结构!。
那么磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象为卷在一起的磁带,那么磁盘的逻辑存储结构我们也可以类似于:
这样每一个扇区,就有了一个线性地址(其实就是数组下标),这种地址叫做LBA(逻辑块寻址)。
② 真实过程
已知,转动臂上的磁头是共进退的。柱面是一个逻辑上的概念,其实就是每一面上,相同半径的磁道逻辑上构成柱面。所以磁盘物理上分了很多面(圆柱面),但在我们看来,逻辑上,磁盘整体是由“柱面”卷起来的。
所以,磁盘的真实情况是:
· 磁道:一维数组。某一个盘面的某一磁道展开:
· 柱面:二维数组。柱面上的每个磁道的扇区个数是一样的。柱面展开:
· 整盘:多张二维的扇区数组表。(三维数组)
所以,寻找一个扇区:先找到哪一个柱面(Cylinder),再确定柱面内的那一个磁道(其实就是磁头位置Head),然后确定扇区(Sector),所以有了CHS寻址。其实对应的是三维数组中的三个下标。
结合我们之前学过的数组,站在柱面的视角(以柱面为基本单位),我们可以重新将整个磁盘划分成线性结构(一维数组)!
每一个扇区都以一个下标,我们叫做LBA地址,其实就是线性地址。所以怎么计算得到这个LBA地址呢?可以从CHS转换得到LBA。
OS只需要使用LBA就可以了!LBA地址与CHS地址的互相转换由磁盘自己来做!固件(硬件电路,伺服系统)
(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 使用磁盘,就可以用一个数字访问磁盘扇区了。
2、引入文件系统
(1)“块”概念
其实硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,其实是不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个“块”(block)。
硬盘的每个分区是被划分为一个个的“块”。一个“块”的大小是由格式化的时候确定的,并且不可以更改,最常见的是 4KB,即连续八个扇区组成一个“块”。“块”是文件存取的最小单位。
• 磁盘就是一个“一维数组”,数组下标就是LBA,每个元素都是扇区。
• 每个扇区都有LBA,8个扇区一个块,每一个块的地址也能算出来。
• 知道LBA:块号 = LBA/8。
• 知道块号:LBA = 块号 × 8 + n(n是块内第几个扇区)。
(2)“分区”概念
其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有一块磁盘并且将它分区成C、D、E盘。那个C、D、E就是分区。分区从实质上说就是对硬盘的一种格式化。但是Linux的设备都是以文件形式存在,那是怎么分区的呢?
柱面是分区的最小单位,我们可以利用参考柱面号码的方式来进行分区,其本质就是设置每个区的起始柱面和结束柱面号码。此时我们可以将硬盘上的柱面(分区)进行平铺,将其想象成一个大的平面。如图:
注:柱面大小一致,扇区个位一致,那么其实只要知道每个分区的起始和结束柱面号,知道每一个柱面多少个扇区,那么该分区多大,那么就可以直接计算出该分区的LBA范围了:
起始LBA:从磁盘开头到分区起始柱面的扇区总数。
结束LBA:起始LBA + 分区扇区数 - 1。
(3)“inode”概念
以前我们说:【文件 = 内容 + 属性】,我们执行【ls -l】不光能看到文件名,还能看到看到文件元数据(属性)。
[root@localhost linux]# ls -l
总⽤量 12
-rwxr-xr-x. 1 root root 7438 "9⽉ 13 14:56" a.out
-rw-r--r--. 1 root root 654 "9⽉ 13 14:56" test.c
【ls -l】读取存储在磁盘上的文件信息,然后显示出来。每行包含7行:模式、硬链接数、文件所有者、组、大小、最后修改时间、文件名。
或者用stat命令查看更多信息:
到这我们要思考一个问题,文件数据都储存在“块”中,那么很显然,我们还必须找到一个地方储存文件的元信息(属性信息),比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做 inode,中文译名为“索引节点”。每个文件都有对应的inode,里面包含了与该文件有关的信息。
• Linux下文件的属性和内容是分离存储的。
• Linux下,保存文件属性的集合叫做 inode,一个文件,一个 inode,inode内有一个文艺的标识符,叫做 inode 号。
下面展示ext2 文件系统磁盘上的 inode 结构体定义(struct ext2_inode),它描述了 ext2 文件系统中 inode 在磁盘上的物理存储格式。
/** Structure of an inode on the disk (ext2 文件系统的磁盘 inode 结构)*/
struct ext2_inode {__le16 i_mode; /* 文件类型和权限 (16位) */__le16 i_uid; /* 所有者UID的低16位 */__le32 i_size; /* 文件大小 (字节) */__le32 i_atime; /* 最后访问时间 */__le32 i_ctime; /* inode创建/状态变更时间 */__le32 i_mtime; /* 最后修改时间 */__le32 i_dtime; /* 文件删除时间 */__le16 i_gid; /* 所属GID的低16位 */__le16 i_links_count; /* 硬链接计数 */__le32 i_blocks; /* 文件占用的512字节块数 */__le32 i_flags; /* 文件标志 (如immutable、append-only等) *//* 操作系统依赖字段 (OSD) */union {struct {__le32 l_i_reserved1;} linux1;struct {__le32 h_i_translator;} hurd1;struct {__le32 m_i_reserved1;} masix1;} osd1;__le32 i_block[EXT2_N_BLOCKS]; /* 数据块指针数组 (共15个) */__le32 i_generation; /* 文件版本号 (用于NFS) */__le32 i_file_acl; /* 文件ACL块指针 */__le32 i_dir_acl; /* 目录ACL块指针 */__le32 i_faddr; /* 碎片地址 *//* 扩展的OSD字段 */union {struct {__u8 l_i_frag; /* 碎片编号 */__u8 l_i_fsize; /* 碎片大小 */__u16 i_pad1;__le16 l_i_uid_high; /* UID高16位 */__le16 l_i_gid_high; /* GID高16位 */__u32 l_i_reserved2;} linux2;struct {__u8 h_i_frag;__u8 h_i_fsize;__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;__u8 m_i_fsize;__u16 m_pad1;__u32 m_i_reserved2[2];} masix2;} osd2;
};/* 数据块指针类型常量 */
#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) /* 总块指针数 (15) */备注:EXT2_N_BLOCKS = 15
注意:
• 文件名属性并未纳入到inode数据结构内部
• 文件名保存在当前文件所属目录的数据内容当中,后面会讲
• inode的大小一般是128字节或256,我们后面统一128字节
• 任何文件的内容大小可以不同,但是属性大小一定是相同的
思考:“块” 是硬盘的每个分区下的结构,难道 “块” 是随意的在分区上排布的吗?那要怎么找到 “块” 呢? 还有就是上面提到的存储文件属性的 inode,又是如何放置的呢?
文件系统就是为了组织管理这些的!!
3、ext2文件系统
(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 区域的数据保持一致。
/** Structure of the super block*/
struct ext2_super_block {/* 基础文件系统信息 */__le32 s_inodes_count; /* Inodes 总数 */__le32 s_blocks_count; /* 块总数 */__le32 s_r_blocks_count; /* 保留块数(root 保留) */__le32 s_free_blocks_count; /* 空闲块数 */__le32 s_free_inodes_count; /* 空闲 Inodes 数 */__le32 s_first_data_block; /* 第一个数据块号(通常为 1) */__le32 s_log_block_size; /* 块大小(以 2^N 计算,如 0=1KB, 1=2KB, 2=4KB) */__le32 s_log_frag_size; /* 片段大小(通常等于块大小) */__le32 s_blocks_per_group; /* 每块组包含的块数 */__le32 s_frags_per_group; /* 每块组包含的片段数 */__le32 s_inodes_per_group; /* 每块组包含的 Inodes 数 *//* 文件系统状态信息 */__le32 s_mtime; /* 最后一次挂载时间 */__le32 s_wtime; /* 最后一次写入时间 */__le16 s_mnt_count; /* 挂载次数 */__le16 s_max_mnt_count; /* 最大挂载次数(超过需 fsck) */__le16 s_magic; /* 魔数(标识 ext2,值为 0xEF53) */__le16 s_state; /* 文件系统状态(干净/错误) */__le16 s_errors; /* 检测到错误时的行为(继续/只读/panic) */__le16 s_minor_rev_level; /* 次要版本号 *//* 维护信息 */__le32 s_lastcheck; /* 最后一次一致性检查时间 */__le32 s_checkinterval; /* 两次检查的最大间隔时间 */__le32 s_creator_os; /* 创建文件系统的 OS 类型 */__le32 s_rev_level; /* 主版本号(0=原始,1=动态版本) */__le16 s_def_resuid; /* 保留块的默认用户 ID */__le16 s_def_resgid; /* 保留块的默认组 ID *//* 动态版本(EXT2_DYNAMIC_REV)特有字段 */__le32 s_first_ino; /* 第一个非保留 Inode 号(通常为 11) */__le16 s_inode_size; /* Inode 结构大小(字节) */__le16 s_block_group_nr; /* 当前超级块所在的块组号 */__le32 s_feature_compat; /* 兼容性功能标志 */__le32 s_feature_incompat; /* 非兼容性功能标志 */__le32 s_feature_ro_compat; /* 只读兼容性功能标志 */__u8 s_uuid[16]; /* 文件系统 UUID(128 位) */char s_volume_name[16]; /* 卷标名称 */char s_last_mounted[64]; /* 最后一次挂载的目录路径 *//* 性能优化相关 */__le32 s_algorithm_usage_bitmap; /* 压缩算法位图 */__u8 s_prealloc_blocks; /* 预分配的块数 */__u8 s_prealloc_dir_blocks; /* 目录预分配的块数 */__u16 s_padding1; /* 填充对齐 *//* 日志支持(EXT3 特性) */__u8 s_journal_uuid[16]; /* 日志超级块的 UUID */__u32 s_journal_inum; /* 日志文件的 Inode 号 */__u32 s_journal_dev; /* 日志文件的设备号 */__u32 s_last_orphan; /* 待删除 Inode 链表的起始 *//* 哈希和挂载选项 */__u32 s_hash_seed[4]; /* HTREE 哈希种子 */__u8 s_def_hash_version; /* 默认哈希版本 */__u8 s_reserved_char_pad;__u16 s_reserved_word_pad;__le32 s_default_mount_opts; /* 默认挂载选项 */__le32 s_first_meta_bg; /* 第一个元块组号 *//* 保留字段 */__u32 s_reserved[190]; /* 填充至块末尾 */
};
★ 新创建的文件系统会写入完整的元数据结构(超级块、块组描述符、位图、inode表等),但数据块区域(用户文件存储区)是空的。部分系统 inode(如根目录)会被初始化,但绝大多数inode和数据块未被占用。★ 格式化:本质是写入文件系统的管理信息(元数据)
② GDT(Group Descriptor Table)
块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储一个块组的描述信息,如在这个块组中从哪里开始是 inode Table,从哪里开始是 Data Blocks,空闲的 inode 和数据块还有多少个等等。块组描述符在每个块组的开头都有一份拷贝。
/** Structure of a block group descriptor (块组描述符结构)* 描述一个块组(Block Group)的元数据分布和状态*/
struct ext2_group_desc {__le32 bg_block_bitmap; /* [块号] 本块组的块位图所在块 */__le32 bg_inode_bitmap; /* [块号] 本块组的inode位图所在块 */__le32 bg_inode_table; /* [块号] 本块组的inode表起始块 */__le16 bg_free_blocks_count; /* 本块组中空闲块的数量 */__le16 bg_free_inodes_count; /* 本块组中空闲inode的数量 */__le16 bg_used_dirs_count; /* 本块组中目录inode的数量(用于orlov算法分配目录) */__le16 bg_pad; /* 填充字段(对齐用途) */__le32 bg_reserved[3]; /* 保留字段(未来扩展用) */
};
③ 块位图(Block Bitmap)
Block Bitmap 中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。如果数据块被占用就将对应位图的bit位置为1,释放数据块就将对应位图bit位清0。
100000个数据块可以被 100000 / (8bits * 4096字节) ≈ 3.05 => 4 个位图块表示。
④ inode位图(Inode Bitmap)
每一个bit表示一个inode是否空闲可用。
⑤ i 节点表(Inode Table)
• 存放文件属性,如文件大小,所有者,最近修改时间等。
• 当前分组所有inode属性的集合。
• inode编号以分区为单位,整体划分,不可跨分区。
• 文件内容 通过 inode 的块指针映射到磁盘数据块。块指针它记录了数据块在磁盘上的物理位置。所以拿到inode,就能得到文件的内容和属性。(块指针不是块位图,不要混淆了)
⑥ 数据块(Data Block)
Data Block(数据块) 是存储文件实际内容的最小单位,是以4KB为单位划分的无数个数据块。以4KB为单位把文件内容保存在Data Block若干文件块当中。根据不同的文件类型有以下几种情况:
• 对于普通文件,文件的数据存储在数据块中。
• 对于目录,该目录下的所有文件名和目录名存储在所在目录的数据块中,除了文件名外,ls -l命令看到的其他信息保存在该文件的inode中。
• Block号按照分区划分,不可跨分区。
注意:inode和数据块都是跨组编号,不能跨分区。
---> 同一个分区内,inode与块号是唯一的。
(4)inode和Data Block 映射关系
前面我们查看ext2 文件系统磁盘上的 inode 结构体定义(struct ext2_inode),注意到ext2文件系统的inode包含15个数据块指针,分为4种类型:
/* 数据块指针类型常量 */
#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) /* 总块指针数 (15) */备注:EXT2_N_BLOCKS = 15
直接块(0-11): 直接指向存储文件数据的物理块
一级间接块(12): 指向一个包含256个(假设块大小1KB)块指针的块
二级间接块(13): 指向一个包含256个一级间接块的块
三级间接块(14): 指向一个包含256个二级间接块的块
知道inode号的情况下,在指定分区,对文件进行增、删、查、改是在做什么?
结论:
• 分区之后的格式化操作,就是对分区进行分组,在每个分组中写入SB、GDT、Block Bitmap、Inode Bitmap等管理信息,这些管理信息统称:文件系统。
• 只要知道文件的inode号,就能在指定分区中确定是哪一个分组,进而在哪一个分组确定是哪一个inode。
• 拿到inode文件属性和内容就全部都有了。
所以,创建一个新文件主要有以下4个操作:
-
存储属性
内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。 -
存储数据
该文件需要存储在三个磁盘块,内核找到了三个空闲块:300, 500, 800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。 -
记录分配情况
文件内容按顺序300, 500, 800存放。内核在inode上的磁盘分布区记录了上述块列表。 -
添加文件名到目录
新的文件名是abc
。Linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。
(5)目录与文件名
前面介绍inode时说过,文件名不会作为属性保存在inode中,但是我们查找文件时一直都用的是文件名,没有用过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) { // 系统调用,读取目录条目// 跳过当前目录"."和上级目录".."if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {continue;}// 打印文件名和inode号printf("Filename: %s, Inode: %lu\n", entry->d_name, (unsigned long)entry->d_ino);}// 关闭目录closedir(dir);return 0;
}
程序类似于简化版的ls
命令,但额外显示了inode信息。
zyt@iZ2vcf9wvlgcetfeub9f11Z:~/linux-journey-log/code_25_5_9$ ./code ~/linux-journey-log/code_25_5_9/
Filename: code.c, Inode: 946052
Filename: Makefile, Inode: 946062
Filename: code, Inode: 946083zyt@iZ2vcf9wvlgcetfeub9f11Z:~/linux-journey-log/code_25_5_9$ ll
total 32
drwxrwxr-x 2 zyt zyt 4096 May 9 12:38 ./
drwxrwxr-x 22 zyt zyt 4096 May 9 12:33 ../
-rwxrwxr-x 1 zyt zyt 16288 May 9 12:38 code*
-rw-rw-r-- 1 zyt zyt 994 May 9 12:37 code.c
-rw-rw-r-- 1 zyt zyt 57 May 9 12:38 Makefile
• 访问文件,必须打开当前目录,根据文件名获得对应的inode号,然后进行文件访问。
• 访问文件必须知道当前工作目录,本质是必须能打开当前工作目录文件,查看目录文件的内容。
(6)路径解析
打开当前工作目录文件,查看当前工作目录文件的内容?当前工作目录不也是文件吗?我们访问当前工作目录不也是只知道当前工作目录的文件名吗?要访问它,不也得知道当前工作目录的inode吗?
而实际上,任何文件,都有路径,访问目标文件,比如:/home/zyt/code/test/test/test.c,都要从根目录开始,依次打开每一个目录,根据目录名,依次访问每个目录下指定的目录,直到访问到test.c。这个过程叫做Linux路径解析。
注意:
• 所以,我们知道了:访问文件必须要有目录+文件名=路径的原因。
• 根目录固定文件名,inode号,无需查找,系统开机之后就必须知道。
可是路径谁提供?
• 你访问文件,都是指令/工具访问,本质是进程访问,进程有CWD!进程提供路径。
• 你open文件,提供了路径。
可是最开始的路径从哪里来?
在计算机系统中,路径是用来定位文件或目录的。路径的起点是根目录(/
)。根目录是文件系统树的最顶层,所有其他目录和文件都从根目录开始。当你访问一个文件时,你必须从根目录开始,逐步定位到目标文件。
所以Linux为什么要有根目录,根目录下为什么要有那么多缺省目录?
-
根目录(
/
):根目录是文件系统树的最顶层,是所有路径的起点。它必须存在,因为它是文件系统的基础。 -
缺省目录:根目录下有许多缺省目录(如
/bin
、/etc
、/home
、/usr
等),这些目录是系统正常运行所必需的。
你为什么要有家目录,你自己可以新建目录?
-
家目录(
/home/username
):每个用户都有一个家目录,这是用户的工作空间。家目录是用户存储个人文件和配置的地方。 -
新建目录:用户可以根据需要在自己的家目录或其他有权限的目录下新建目录。这些新建的目录会自动成为文件系统的一部分,并且有唯一的路径。
★ 上面所有行为:本质就是在磁盘文件系统中,新建目录文件。而你新建的任何文件,都在你或者系统指定的目录下新建,这不就是天然就有路径了嘛!
也就是说:当你在某个目录下新建文件或目录时,这些新创建的文件或目录会自动继承其父目录的路径。例如,你在/home/zyt目录下新建一个文件test.txt,它的路径就是 /home/zyt/test.txt。
• 系统+用户共同构建Linux路径结构。
-
系统角色:系统负责创建和管理根目录和缺省目录,确保系统的基本功能和结构。
-
用户角色:用户可以在自己的家目录或其他有权限的目录下创建和管理自己的文件和目录。用户的行为会扩展和丰富文件系统的结构。
(7)路径缓存
访问任何文件,都要从/目录开始进行路径解析? 原则上是,但是这样太慢,所以Linux会缓存历史路径结构。
Linux目录的概念,怎么产生的? 答案:打开的文件是目录的话,由OS自己在内存中进行路径维护。
Linux中,在内核中维护树状路径结构的内核结构体叫做: struct dentry
struct dentry {atomic_t d_count; // 引用计数unsigned int d_flags; // 标志位,受 d_lock 保护spinlock_t d_lock; // dentry 自旋锁/* 关联的 inode 结构(NULL 表示负 dentry) */struct inode *d_inode; /* 以下三个字段由 __d_lookup 使用,放在同一缓存行以提高性能 */struct hlist_node d_hash; // 哈希链表节点(用于快速查找)struct dentry *d_parent; // 父目录的 dentrystruct qstr d_name; // 文件名(快速字符串结构)struct list_head d_lru; // LRU 链表节点(用于缓存管理)/* d_child 和 d_rcu 共用内存空间 */union {struct list_head d_child; // 父目录的子 dentry 链表struct rcu_head d_rcu; // RCU 回调头(用于安全释放)} d_u;struct list_head d_subdirs; // 子目录链表(当前目录下的所有 dentry)struct list_head d_alias; // inode 别名链表(硬链接场景)unsigned long d_time; // 用于 d_revalidate 的时间戳struct dentry_operations *d_op; // dentry 操作函数表struct super_block *d_sb; // 所属的超级块(文件系统根)void *d_fsdata; // 文件系统私有数据#ifdef CONFIG_PROFILINGstruct dcookie_struct *d_cookie; // 调试用的 cookie(可选)
#endifint d_mounted; // 是否是挂载点(1=是, 0=否)/* 短文件名内联存储(避免额外分配内存) */unsigned char d_iname[DNAME_INLINE_LEN_MIN];
};
• 每个文件其实都要有对应的dentry结构,包括普通文件。这样所有被打开的文件,就可以在内存中形成整个树形结构。
• 整个树形节点也同时会隶属于LRU(Least Recently Used,最近最少使用)结构中,进行节点淘汰。
• 整个树形节点也同时会隶属于Hash,方便快速查找。
• 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何文件,都在先在这棵树下根据路径进行查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径。
(8)挂载分区
我们已经能够根据inode号在指定分区找文件了,也已经能根据目录文件内容,找指定的inode了,在指定的分区内,我们可以为所欲为了。
可是问题是:inode不是不能跨分区吗?Linux不是可以有多个分区吗?我怎么知道我在哪一个分区?
① 分区必须挂载才能使用
-
原始分区:一个新格式化的分区(如
/dev/sdb1
)只是存储设备上的一个“裸容器”,无法直接通过路径访问。 -
挂载(Mount):
将分区关联到文件系统树中的某个目录(称为挂载点)。
mount /dev/sdb1 /mnt/data # 将分区sdb1挂载到目录/mnt/data
② 为什么需要挂载?
-
统一命名空间:Linux所有文件(无论来自哪个分区)都必须在单一的目录树下呈现(根目录
/
为起点)。 -
隔离与组织:
-
不同分区可以挂载到不同目录(如
/home
、/var
),实现物理存储的隔离。 -
例如:将SSD挂载到
/
,HDD挂载到/home
,优化性能与容量。
-
总结:分区写入文件系统无法被直接使用,需要和指定的目录关联,进行挂载才能使用。挂载就是将分区关联到文件系统树中的某个目录,使得目录(路径前缀)成为访问该分区的入口。
③ 如何通过“路径前缀”确定分区?
【df -h】查看当前系统的分区挂载情况,我们来分析如何通过这些信息理解路径前缀与分区的对应关系。
zyt@iZ2vcf9wvlgcetfeub9f11Z:~$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 168M 1.2M 167M 1% /run
efivarfs 256K 7.3K 244K 3% /sys/firmware/efi/efivars
/dev/vda3 49G 6.4G 41G 14% /
tmpfs 839M 0 839M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
/dev/vda2 197M 6.2M 191M 4% /boot/efi
tmpfs 168M 12K 168M 1% /run/user/0
tmpfs 168M 12K 168M 1% /run/user/1001
Filesystem | Mounted on(挂载点) | 说明 |
---|---|---|
/dev/vda3 | / | 根分区,所有未明确挂载的路径默认归属于此(如/etc 、/usr ) |
/dev/vda2 | /boot/efi | EFI系统分区,存放启动文件 |
tmpfs | /run 、/dev/shm 等 | 内存虚拟文件系统(非物理分区) |
efivarfs | /sys/firmware/efi/efivars | EFI变量存储 |
通过“路径前缀”确定分区:挂载点是分区在目录树中的“入口”,路径中最长的挂载点前缀决定文件所在分区。示例:
路径
/home/user/file
没有单独挂载
/home
,因此匹配最长前缀/
→ 属于根分区/dev/vda3
。路径
/boot/efi/grub.cfg
匹配前缀
/boot/efi
→ 属于/dev/vda2
。
内核实现:挂载表(Mount Table):内核维护一个全局挂载表,记录所有分区的挂载点信息。
路径解析时:从根目录(/)开始逐级匹配路径。当遇到某个目录是挂载点时,切换到对应分区的文件系统继续查找。
④ 实验
• 创建虚拟磁盘文件 :【dd if=/dev/zero of=./disk.img bs=1M count=5 】生成一个5MB的空文件,模拟物理磁盘分区。
root@iZ2vcf9wvlgcetfeub9f11Z:~# dd if=/dev/zero of=./disk.img bs=1M count=5
5+0 records in
5+0 records out
5242880 bytes (5.2 MB, 5.0 MiB) copied, 0.00871276 s, 602 MB/s
• 格式化为ext4文件系统:【mkfs.ext4 disk.img】
将文件初始化为ext4格式,写入文件系统元数据(超级块、inode表等)。
root@iZ2vcf9wvlgcetfeub9f11Z:~# mkfs.ext4 disk.img
mke2fs 1.47.0 (5-Feb-2023)Filesystem too small for a journal
Discarding device blocks: done
Creating filesystem with 1280 4k blocks and 1280 inodesAllocating group tables: done
Writing inode tables: done
Writing superblocks and filesystem accounting information: done
• 创建挂载点目录 【sudo mkdir /mnt/mydisk】(/mnt目录通常属于root)。
root@iZ2vcf9wvlgcetfeub9f11Z:~# mkdir /mnt/mydisk
root@iZ2vcf9wvlgcetfeub9f11Z:~# df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 168M 1.2M 167M 1% /run
efivarfs 256K 7.3K 244K 3% /sys/firmware/efi/efivars
/dev/vda3 49G 6.4G 41G 14% /
tmpfs 839M 0 839M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
/dev/vda2 197M 6.2M 191M 4% /boot/efi
tmpfs 168M 12K 168M 1% /run/user/0
tmpfs 168M 12K 168M 1% /run/user/1001
• 挂载虚拟磁盘【sudo mount -t ext4 ./disk.img /mnt/mydisk/】
将disk.img挂载到/mnt/mydisk,此时访问该目录即访问虚拟磁盘内容。
root@iZ2vcf9wvlgcetfeub9f11Z:~# sudo mount -t ext4 ./disk.img /mnt/mydisk/
root@iZ2vcf9wvlgcetfeub9f11Z:~# df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 168M 1.2M 167M 1% /run
efivarfs 256K 7.3K 244K 3% /sys/firmware/efi/efivars
/dev/vda3 49G 6.4G 41G 14% /
tmpfs 839M 0 839M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
/dev/vda2 197M 6.2M 191M 4% /boot/efi
tmpfs 168M 12K 168M 1% /run/user/0
tmpfs 168M 12K 168M 1% /run/user/1001
/dev/loop0 4.7M 24K 4.4M 1% /mnt/mydisk
/dev/loop0 在 Linux 系统中代表第一个循环设备(loop device)。循环设备,也被称为回环设备或者 loopback 设备,是一种伪设备(pseudo-device),它允许将文件作为块设备(block device)来使用。这种机制使得可以将文件(比如 ISO 镜像文件)挂载(mount)为文件系统,就像它们是物理硬盘分区或者外部存储设备一样。
• 卸载虚拟磁盘,解除关联后,/mnt/mydisk恢复为空目录。
root@iZ2vcf9wvlgcetfeub9f11Z:~# sudo umount /mnt/mydisk
root@iZ2vcf9wvlgcetfeub9f11Z:~# df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 168M 1.2M 167M 1% /run
efivarfs 256K 7.3K 244K 3% /sys/firmware/efi/efivars
/dev/vda3 49G 6.4G 41G 14% /
tmpfs 839M 0 839M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
/dev/vda2 197M 6.2M 191M 4% /boot/efi
tmpfs 168M 12K 168M 1% /run/user/0
tmpfs 168M 12K 168M 1% /run/user/1001
(9)文件系统总结
4、软硬链接
(1)硬链接
硬链接本质不是一个独立的文件,没有独立的inode,而是一组新的的文件名与目标inode nummber的映射关系。【只能链接普通文件,不能链接目录】
abc和def的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,每一个inode节点都有一个链接计数,其值是指向该inode节点的目录项数。inode 946085 的硬连接数为2。
$ ln abc def
$ ls -li
total 0
946085 -rw-rw-r-- 2 zyt zyt 0 May 9 16:00 abc
946085 -rw-rw-r-- 2 zyt zyt 0 May 9 16:00 def
★ 我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬连接数-1,如果为0,则将对应的磁盘释放。
用途:
① 隐藏文件【.】和【..】就是硬链接② 用于文件备份
只有当链接计数减少至0时,才可以删除该文件(也就是释放该文件占用的数据块),这就是为什么“解除对一个文件的链接”操作并不总是意味着“释放该文件占用的数据块”的原因。这也是为什么删除一个目录项的函数被称为unlink而不是delete的原因。
(2)软连接
软连接是通过inode引用另外一个文件,实际上新文件和被引用的文件的inode不同。也就是说:软链接本质是一个独立的文件,里面的内容是保存目标文件的路径。
用abc.s链接abc后我们发现,它们的inode号是不同的,证明了软链接生成的是一个独立的文件。
$ ln -s abc abc.s # 用abc.s链接abc
$ ls -li
total 0
946085 -rw-rw-r-- 1 zyt zyt 0 May 9 16:00 abc
946086 lrwxrwxrwx 1 zyt zyt 3 May 9 16:00 abc.s -> abc
用途:类比成windows下的快捷方式。
(3)硬链接局限性
① 硬链接通常要求链接和文件位于同一个文件系统中。
② 只有超级用户才能创建指向目录的硬链接。(在底层文件系统支持的情况下)
③ 使用软链接可能在文件系统中引入循环。大多数查找路径名的函数在该情况发生时都将出错返回,但都是好消除的,可是对于硬链接就很难了。
$ mkdir foo # 创建一个新目录
$ touch foo/a # 创建一个长度为0的文件
$ ln -s ../foo foo/testdir # 创建一个软链接
$ ls -li foo
total 0
946073 -rw-rw-r-- 1 zyt zyt 0 May 9 17:31 a
946074 lrwxrwxrwx 1 zyt zyt 6 May 9 17:32 testdir -> ../foo
这里我们创建了一个目录foo,它包含了一个名为a的文件以及指向foo的软链接。下图显示了这种结果(圆表示目录,正方形表示一个文件)。
如果我们写一段简单程序,使用ftw(3)函数以降序遍历文件结构,打印每个遇到的路径名,其输出是:
foo
foo/a
foo/testdir
foo/testdir # 进入链接
foo/testdir/a # 重新遍历
foo/testdir/testdir # 再次进入
foo/testdir/testdir/a # 无限循环...
...
[最终因堆栈溢出或 ELOOP (errno) 错误终止]
这样一个循环是很好消除的,因为unlink并不跟随软链接,所以可以unlink文件foo/testdir。但如果创建了一个构成这种循环的硬链接,就很难消除了。这就是为什么link函数不允许构造指向目录的硬链接的原因(除非进程有超级用户权限)。
相关文章:
【Linux】深入拆解Ext文件系统:从磁盘物理结构到Linux文件管理
目录 1、理解硬件 (1)磁盘 (2)磁盘的物理结构 (3)磁盘的存储结构 (4)磁盘的逻辑结构 (5)CHS && LBA地址 2、引入文件系统 (1&…...
linux ptrace 图文详解(八) gdb跟踪被调试程序的子线程、子进程
目录 一、gdb跟踪被调试程序的fork、pthread_create操作 二、实现原理 三、代码实现 四、总结 (代码:linux 6.3.1,架构:arm64) One look is worth a thousand words. —— Tess Flanders 相关链接: …...
【列表类型】
1、按索引取值 索引可正向存取,也可反向存取 l [111, paipai, cat] # 正向取值 print(l[1]) # 方向取值 print(l[-1]) # 通过索引给列表重新赋值,前提:索引存在 l[0] ccat print(l) # 索引不存在的情况下,取值or重新赋值的情况下都会报错 …...
MySQL 8.0 OCP 英文题库解析(二)
Oracle 为庆祝 MySQL 30 周年,截止到2025.07.31 之前。所有人均可以免费考取 原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题6~15。 试题6: …...
数据分析与逻辑思维:六步解决业务难题;参考书籍《数据分析原理:6步解决业务分析难题 (周文全, 黄怡媛, 马炯雄)》
文章目录 一、懂业务:业务背景与逻辑前提1.1 明确业务目标与问题定义1.2 培养批判性思维与高于业务视角 二、定指标:构建科学的指标体系2.1 指标拆解与维度分析2.2 典型指标体系案例:用户与业务视角 三、选方法:匹配业务需求的分析…...
人力资源管理系统如何有效提高招聘效率?
在传统招聘模式下,企业招聘常常陷入 “泥潭”。HR 每天需要花费大量时间在海量简历中 “大海捞针”,手动筛选、电话沟通、安排面试,流程繁琐且效率低下。好不容易邀约到候选人,却因面试安排冲突、信息传递不及时等问题,…...
FAISS 与机器学习、NLP 的关系
FAISS(Facebook AI Similarity Search)是一个用于高效相似性搜索和密集向量聚类的开源库,由 Facebook AI Research 开发。它在机器学习(特别是自然语言处理,NLP)领域中扮演着重要角色,主要解决大…...
文件包含2
远程文件包含与本地文件包含的区别 对比 对比项本地文件包含(LFI)远程文件包含(RFI)定义攻击者包含服务器本地的文件攻击者包含远程服务器(如HTTP/FTP)上的文件依赖条件不需要特殊配置需要allow_url_incl…...
嵌入式系统架构验证工具:AADL Inspector v1.10 全新升级
软件架构建模与早期验证是嵌入式应用的关键环节。架构分析与设计语言(AADL)是专为应用软件及执行平台架构模型设计的语言,兼具文本与图形化的双重特性。AADL Inspector是一款轻量级的独立工具: 核心处理能力包括 √ 支持处理AA…...
软考高级系统架构设计师备考分享:操作系统核心知识点整理
在备战软考高级系统架构设计师的过程中,操作系统作为核心考点之一,需要系统性地掌握其核心原理。本文将从操作系统分类、进程状态模型、同步互斥机制、死锁问题及存储管理五大模块展开梳理,结合考试高频考点和实际案例进行解析。 一、操作系统…...
22、城堡防御工事——React 19 错误边界与监控
一、魔法护盾:错误边界机制 1. 城墙结界(Error Boundary) // 客户端错误边界use client function useErrorBoundary() {const [error, setError] useState(null);const handleError useCallback((error, errorInfo) > {setError(erro…...
有关SOA和SpringCloud的区别
目录 1. 定义 2. 架构风格 3. 技术栈 4. 服务交互 5. 适用场景 前言 面向服务架构(SOA)是一种软件设计风格,它将应用程序的功能划分为一系列松散耦合的服务。这些服务可以通过标准的通信协议进行交互,通常是HTTP或其他消息传…...
大数据——Mac环境DataSpell集成Jupyter
1、设置 2、添加新的解释器 3、解释器类型选择Conda 4、进入选中全部,然后重启 5、dataspell右下角会显示当前项目的运行环境 6、创建Jupyter Notebook文件 7、测试 8、查看当前配置 (1)本地模式安装使用 (2)…...
解锁健康养生新境界
在追求高品质生活的当下,健康养生早已超越 “治未病” 的传统认知,成为贯穿全生命周期的生活艺术。它如同精密的交响乐,需饮食、运动、心理与生活习惯多维度协奏,方能奏响生命的强音。 饮食养生讲究 “顺时、适性”。遵循二十四节…...
WORD压缩两个免费方法
日常办公和学习中,Word文档常常因为包含大量图片、图表或复杂格式而导致文件体积过大,带来诸多不便,比如 邮件发送受限:许多邮箱附件限制在10-25MB,大文件无法直接发送 存储空间占用:大量文档占用硬盘或云…...
Zabbix监控 RabbitMQ 指定消息队列名称(pull_alarms )的消费者
✅ 1. 编写 RabbitMQ 队列监控脚本 创建脚本文件 /usr/local/bin/zbx_rabbitmq_metric.sh 并写入以下内容: #!/bin/bash # /usr/local/bin/zbx_rabbitmq_metric.shQUEUE$1 METRIC$2 USER$3 PASS$4if [[ -z "$QUEUE" || -z "$METRIC" || -z &q…...
RabbitMQ ②-工作模式
RabbitMQ 工作模式 官方提供了七种工作模式 Simple(简单模式) P:生产者,发布消息到队列C:消费者,从队列中获取消息并消费Queue:消息队列,存储消息。 一个生产者,一个…...
《探索React Native社交应用中WebRTC实现低延迟音视频通话的奥秘》
WebRTC,全称为Web Real-Time Communication,是一项开创性的开源技术,为Web和移动应用开启了实时通信的大门。它打破了传统通信的束缚,使得应用之间无需依赖繁琐的中间服务器,就能实现直接的点对点通信,这是…...
UI设计公司兰亭妙微分享:汽车 MHI 设计的界面布局创新法则
在汽车人机界面(MHI)设计中,界面布局犹如建筑蓝图,奠定了用户与汽车交互体验的基础。合理创新的布局能提升驾驶安全性与便捷性,融合极简美学与高效操作则成为现代汽车 MHI 界面布局设计的核心追求。 驾驶场景中&…...
【递归,搜索与回溯算法篇】专题(一) - 递归
文章目录 面试题 08.06. 汉诺塔问题21. 合并两个有序链表206. 反转链表24. 两两交换链表中的节点50. Pow(x, n) 面试题 08.06. 汉诺塔问题 题目链接: 面试题 08.06. 汉诺塔问题 题目描述: 在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的…...
B站pwn教程笔记-9
前言:可以去一些开源镜像站下载libc老的乌班图镜像,因为堆题的libc可能比较老,没有新的一些保护措施和机制。 格式化字符串漏洞 归根结底,可以读写任意地址内存。 泄露栈数据/任意地址数据 主要问题就是printf不知道自己有没有…...
NVR(网络视频录像机) 和 网络摄像机(IPC,IP Camera)
NVR(网络视频录像机) 和 网络摄像机(IPC,IP Camera) 是网络监控系统的两个核心组件,但功能定位完全不同。以下是它们的核心区别: 1. 功能角色 组件网络摄像机(IPC)NVR&a…...
数智读书笔记系列032《统一星型模型--一种敏捷灵活的数据仓库和分析设计方法》
引言 在当今数字化时代,数据仓库作为企业数据管理的核心基础设施,承担着整合、存储和提供企业数据的关键角色。随着商业环境的快速变化和业务需求的日益复杂,数据仓库的设计方法也在不断演进,以适应新的挑战和要求。 背景与意义 数据仓库领域长期存在着两种主流方法论之…...
互联网大厂Java求职面试:基于RAG的智能问答系统设计与实现
互联网大厂Java求职面试:基于RAG的智能问答系统设计与实现 场景背景 在某互联网大厂的技术面试中,技术总监张总正在面试一位名为郑薪苦的求职者。郑薪苦虽然对技术充满热情,但回答问题时总是带着幽默感,有时甚至让人哭笑不得。 …...
[C#]Task.Run()和Task.Factory.StartNew()对比(腾讯元宝)
Task.Run和Task.Factory.StartNew都是用来创建并启动任务的方法,但它们的内部实现和使用场景有所不同。两者的主要区别:默认调度器、配置选项、异常处理、适用场景。建议用户大多数情况下使用Task.Run,除非需要StartNew的高级配置,…...
Java游戏服务器开发流水账(3)游戏数据的缓存简介
简介 游戏服务器数据缓存是一种在游戏服务器运行过程中,用于临时存储经常访问的数据的技术手段,旨在提高游戏性能、降低数据库负载以及优化玩家体验。游戏开发中数据的缓存可以使用Java自身的内存也可以使用MemCache,Redis,注意M…...
PostgreSQL可见性映射VM
1.可见性映射 清理过程的代价高昂,为了减小清理的开销,在PostgreSQL 8.4版中引入了VM。 VM的基本概念很简单。 每个表都拥有各自的可见性映射,用于保存表文件中每个页面的可见性。 页面的可见性确定了每个页面是否包含死元组。清理过程可以…...
集成电路流片随笔26:tinyriscv的三级流水线细则pc
include "defines.v"// PC寄存器模块 module pc_reg(input wire clk,input wire rst,input wire jump_flag_i, // 跳转标志input wire[InstAddrBus] jump_addr_i, // 跳转地址input wire[Hold_Flag_Bus] hold_flag_i, // 流水线暂停标志input wire…...
如何解决Jmeter中的乱码问题?
在 JMeter 中遇到乱码问题通常是由于字符编码不一致导致的,常见于 HTTP 请求响应、参数化文件读取、报告生成等场景。以下是系统化的解决方案: 1. HTTP 请求响应乱码 原因: 服务器返回的字符编码(如UTF-8、GBK)与 J…...
TextRNN 模型实现微博文本情感分类
在自然语言处理(NLP)领域,文本情感分类是一项极具应用价值的任务。它能帮助企业分析用户反馈、社交媒体舆情监测等。本文将通过一段实际代码,带大家了解如何利用 PyTorch 框架和 TextRNN 模型,完成微博文本的情感分类工…...
支付宝 SEO 优化:提升小程序曝光与流量的完整指南
在拥有庞大用户基数的支付宝平台上,小程序已成为商家触达用户、提供服务的重要渠道。然而,随着平台上小程序数量的快速增长,如何在激烈的竞争中脱颖而出,获得更多的曝光和流量,成为每个开发者和运营者必须面对的关键挑…...
基于WSL用MSVC编译ffmpeg7.1
在windows平台编译FFmpeg,网上的大部分资料都是推荐用msys2mingw进行编译。在win10平台,我们可以采用另一种方式,即wslmsvc 实现window平台的ffmpeg编译。 下面将以vs2022ubuntu22.04 为例,介绍此方法 0、前期准备 安装vs2022 &…...
高并发内存池(二):项目的整体框架以及Thread_Cache的结构设计
目录 前言 一,项目整体框架设计 二,thread cache结构设计 模拟定长内存池的设计思路 采用一定的对齐规则设计 thread cache大致框架 申请内存Allocate方法 1,thread cache 哈希桶的内存对齐规则 2,内存对齐规则代码实现 …...
K8S扩缩容及滚动更新和回滚
目录: 1、滚动更新1、定义Deployment配置2、应用更新 2、版本回滚1. 使用kubectl rollout undo命令 3、更新暂停与恢复1、暂停更新2、更新镜像(例如,使用kubectl set image命令)3、恢复更新 4、弹性扩缩容1、扩容命令2、缩容命令3…...
K8S - GitLab CI 自动化构建镜像入门
一、引言 在现代持续交付(CI/CD)体系中,容器镜像的自动化构建与推送已成为交付链条的重要一环。 GitLab CI/CD 作为 GitLab 平台的原生集成功能,提供了声明式、可扩展的流水线机制,使得开发者可以在代码生命周期内实…...
万兴PDF-PDFelement v11.4.13.3417
万兴PDF专家(Wondershare PDFelement)是一款国产PDF文档全方位解决方案.万兴PDF编辑器软件万兴PDF中文版,专注于PDF的创建,编辑,转换,签名,压缩,合并,比较等功能.万兴PDF专业版PDF编辑软件,以简约风格及强大的功能在国外名声大噪,除了传统功能外,还提供OCR扫描,表格识别,创建笔…...
4.2【LLaMA-Factory实战】金融财报分析系统:从数据到部署的全流程实践
【LLaMA-Factory实战】金融财报分析系统:从数据到部署的全流程实践 一、引言 在金融领域,财报分析是投资决策的核心环节。传统分析方法面临信息提取效率低、风险识别不全面等挑战。本文基于LLaMA-Factory框架,详细介绍如何构建一个专业的金…...
Vue Router 3 使用详解:从零构建嵌套路由页面
Vue Router 是 Vue.js 官方的路由管理器,常用于构建单页面应用(SPA)。本文将手把手带你完成 vue-router3.6.5 的基本配置,并实现一个带有嵌套路由的页面结构。本文适用于 Vue 2.x 项目 一、安装 vue-router3.6.5 npm install vue…...
ChatGPT深度研究功能革新:GitHub直连与强化微调
目录 一、ChatGPT深度研究功能迎来革命性更新 1.1 GitHub直连功能详解 1.2 强化微调(RTF)正式发布 二、GitHub直连功能深度体验 2.1 实际应用场景演示 2.2 技术实现原理探讨 三、强化微调技术解析 3.1 RTF技术核心优势 3.2 适用场景分析 四、开发者反馈与行业影响 4…...
【Ansible】模块详解
一、ansible概述 1.1 ansible介绍 Ansible 是一个基于 Python 开发的配置管理和应用部署工具,近年来在自动化管理领域表现突出。它集成了许多传统运维工具的优点,几乎可以实现 Pubbet 和 Saltstack 所具备的功能。 1.2 ansible能做什么 批量处理。An…...
深入理解C/C++内存管理:从基础到高级优化实践
一、内存区域划分与基础管理机制 栈(Stack) 栈由系统自动管理,用于存储函数调用时的局部变量、参数及返回地址。其特点是高效但空间有限(通常1-8MB),遵循后进先出(LIFO)…...
两台服务器之前共享文件夹
本文环境 服务器A:ubuntu24.22系统 IP:10.0.8.1 服务器B:ubuntu24.22系统 IP:10.0.8.10 本操作旨在将服务器B的/opt/files目录共享给服务器A得/opt/files 在 B 服务器上设置共享 安装 NFS 服务: sudo apt -y install nfs-kernel-server编辑/etc/exports文件&…...
stm32之USART
目录 1.引入1.1 通信接口1.2 串口 2.USART2.1 简介2.2 框图2.3 基本机构图2.4 数据帧2.5 波特率发生器2.6 数据包2.6.1 数据模式2.6.2 HEX数据包2.6.3 文本数据包2.6.4 HEX数据包接收2.6.5 文本数据包接收 3.结构体和相关API3.1 结构体3.2 API3.2.1 **初始化相关函数**void USA…...
使用 systemd 管理 Linux 服务:配置与自动重启指南
使用 systemd (推荐,适用于大多数 Linux 发行版) systemd 是现代 Linux 系统中最常用的服务管理器。它能可靠地管理进程,并在进程崩溃时自动重启。 创建 systemd 服务文件: 创建一个文件,例如 /etc/systemd/system/app.service…...
【计算机视觉】Car-Plate-Detection-OpenCV-TesseractOCR:车牌检测与识别
Car-Plate-Detection-OpenCV-TesseractOCR:车牌检测与识别技术深度解析 在计算机视觉领域,车牌检测与识别(License Plate Detection and Recognition, LPDR)是一个极具实用价值的研究方向,广泛应用于智能交通系统、安…...
《Spring Boot 3.0全新特性详解与实战案例》
大家好呀!今天让我们轻松掌握Spring Boot 3.0的所有新特性!🚀 📌 第一章:Spring Boot 3.0简介 1.1 什么是Spring Boot 3.0? Spring Boot 3.0就像是Java开发者的"超级工具箱"🧰&…...
二叉树的深度
二叉树的深度是指从根节点到叶子节点的最长路径上的节点数。 一、最大深度 104. 二叉树的最大深度 - 力扣(LeetCode) 最大深度是指从根节点到最远叶子节点的最长路径上的节点数。 //递归法 /*** Definition for a binary tree node.* public class T…...
科技创业园共享会议室线上预约及智能密码锁系统搭建指南
为科技创业园区的运营管理者,我深知高效利用会议室资源的重要性。2023年第三季度,我们园区启动会议室智能化改造项目,经过三个月的实践,成功将32间共享会议室升级为"线上预约智能门锁"管理模式。现将改造经验分享如下&a…...
自定义prometheus exporter实现监控阿里云RDS
# 自定义 Prometheus Exporter 实现多 RDS 数据采集## 背景1. Prometheus 官网提供的 MySQL Exporter 对于 MySQL 实例只能一个进程监控一个实例,数据库实例很多的情况下,不方便管理。 2. 内部有定制化监控需求,RDS 默认无法实现,…...
LeetCode 3342.到达最后一个房间的最少时间 II:dijkstra算法(和I一样)
【LetMeFly】3342.到达最后一个房间的最少时间 II:dijkstra算法(和I一样) 力扣题目链接:https://leetcode.cn/problems/find-minimum-time-to-reach-last-room-ii/ 有一个地窖,地窖中有 n x m 个房间,它们呈网格状排布。 给你一…...