最近完成了一个编程作业,大致功能是给定一个文件名,给出该文件所在目录和其本身所占用的簇号等信息。笔者选用了Linux的ext系列文件系统作为实验对象,通过实验对ext2文件系统的存储和索引有了一个较为细致的了解,在这里记录学习到的知识,以备查阅和参考。
(这里记录的是笔者作业过程中的收获,故而主要细节集中在Linux ext2文件系统的存储布局和如何进行文件数据读取上,其他诸如访问控制等信息则略过)
(本文的参考来源,更详细的ext2 文件系统的细节文本,包括结构的成员和功能的描述:The Second Extended File System,Internal Layout,这也是帮助笔者完成作业的主要文件,在本文中没有详细介绍的结构、字段等信息,在该文章中均有描述)
目录
1.概述
2.基本概念
3.文件系统概念
3.1 superblock
3.2 block
3.3 block descriptor table
3.4 inode/block bitmap
3.5 inode table
3.6 inode
3.7 directory
4. ext2文件系统布局
5. 文件系统相关操作
5.1 如何获得 inode/block 号对应的数据
5.2 如何定位一个文件
5.3 文件系统的根目录与根目录 /
5.4 硬链接与符号链接
6. 参考资料
概述
文件系统是一种对计算机数据的组织和存储的方式,它通过特定的格式对系统上的信息和数据(狭义的理解,可以认为就是目录和文件)进行管理和控制,并方便操作系统在使用时对目标资源进行索引和查找。文件系统在格式化分区(硬盘)时被创建,为操作系统提供独立于磁盘物理结构的逻辑结构。目前用户日常接触的主流的文件系统格式包括 ext系列(Linux),NTFS(Windows) 和 FAT16/32(U盘等),这里介绍的是Linux环境下的ext2文件系统。
Linux的ext系列文件系统使用superblock结构描述整个文件系统的配置数据,为操作系统使用文件系统提供基本信息。所有的数据均被视为文件进行管理,每个文件有且仅有一个inode结构进行描述,其中存放有与该文件相关的属性、数据存放的block号等控制信息,但文件名却不包含在其中。整个文件系统以block为管理的基本单位,文件的数据均存放在block中。目录被视为文件,拥有自己的inode结构,该文件的block块中存放着称为目录项的结构,每个目录项记录该目录下对应成员的文件名和对应的inode号等信息。为便于索引,将文件系统中的inode和block分成组(group)进行管理。
基本概念
superblock:描述文件系统的基本结构,包含了文件系统的基本配置信息,其中包括该文件系统的inode和block数量、inode和block的固定大小、inode和block的组织方式等信息。
block:文件系统中进行数据存放的基本单位,每个文件占用整数个block,即不同文件无法将数据存放在同一个block中。block的大小和数量在文件系统格式化时即被确定,常见的block大小有1KB、2KB、4KB和8KB等。
inode: 文件系统中描述文件的基本结构,每个文件有且仅有一个inode与其对应,inode记录了一个文件除文件数据(存放在block中)和文件名(位于所在目录的数据块记录的目录项中)的所有信息,包括时间相关信息,访问控制信息,文件使用的block块信息等。
文件系统结构
superblock
superblock包含了对一个文件系统的基本配置信息的描述,其总是自文件系统中偏移量为 1024 bytes 处的位置开始,并占据1024bytes的空间。
superblock中记录了与文件系统相关的配置信息,其中与文件访问相关的部分字段的信息如图所示:
superblock中的偏移(offset) | 字段长度(bytes) | 字段名称 | 字段描述 |
0 | 4 | s_inodes_count | 该文件系统中inode节点的总个数 |
4 | 4 | s_blocks_count | 该文件系统中block节点的总个数 |
24 | 4 | s_log_block_size | 若该字段记录值为n(非负),则可通过1024<< n 得到block的大小。 如n=1,则说明一个block大小为1024bytes |
32 | 4 | s_blocks_per_group | 记录每个group中block的个数 |
40 | 4 | s_inodes_per_group | 记录每个group中inode的个数 |
88 | 2 | s_inode_size | 记录文件系统中inode的固定大小 |
完整的superblock结构的介绍可以参见这里。superblock的结构在ext2/3/4中基本是一致的。
操作系统在读取superblock的数据后,即获得文件系统的具体参数信息,以便进行文件系统操作。Linux系统提供命令供用户查看superblock及其相关信息。
dumpe2fs [options] 设备文件名 //输出文件系统的superblock及其块分组信息 -h //仅输出文件系统 superblock 的信息
用户可通过 df 命令获得当前挂载的文件系统的设备文件名,再通过 dumpe2fs -h 设备文件名(如/dev/sda1) 获得对应文件系统的superblock信息。
用户也可以编写程序,读取原始的二进制磁盘数据,根据上述 superblock 结构字段的偏移和含义对原始数据进行分析,从而获得 superblock 记录的信息。笔者的一个简单实现如下:
#include#include #include #include #include //自定义的头文件,存放相关偏移的宏 super_block root_block; int main(int argc , char *argv[]) { int fd = open( FILE_SYSTEM ,O_RDONLY); if(!fd) { err_msg("failed to read the disk"); } getSuperBlock(fd); close(fd); return 0; } void getSuperBlock( int fd ) //get superblock information { unsigned char super_block_buffer[super_block_size]; //存放superblock的缓冲区 if( lseek( fd , offset_super_block , SEEK_SET )== -1 ) //设置文件偏移至super_block所在起始位置 err_msg("failed to used lseek in superblock reading"); if( read( fd , super_block_buffer , super_block_size )!= super_block_size ) //读取superblock数据至缓冲区 err_msg("failed to read superblock"); root_block.inode_size = parse_num( super_block_buffer + offset_s_inode_size , 2); //inode size root_block.block_size =( (unsigned int)1024 )<< parse_num(super_block_buffer + offset_s_log_block_size , 4 ); //block size root_block.num_of_inode = parse_num(super_block_buffer + offset_s_inodes_count , 4 ); //inode number root_block.num_of_block = parse_num( super_block_buffer + offset_s_blocks_count , 4); //block number printf("num of inode:%u \t inode size:%u\n", root_block.num_of_inode , root_block.inode_size ); printf("num of blocks:%u \t block_size:%u\n",root_block.num_of_block , root_block.block_size ); root_block.inode_per_grp = parse_num( super_block_buffer + offset_s_inodes_per_group , 4 ); //inode_per_group root_block.block_per_grp = parse_num( super_block_buffer + offset_s_blocks_per_group , 4 ); //block_per_group printf("num of inodes per group:%u\n", root_block.inode_per_grp ); printf("num of blocks per group:%u\n", root_block.block_per_grp ); printf("\n\n"); } void err_msg( char *msg) // print error message and exit { fprintf( stderr , "%s\n" , msg ); exit(0); } unsigned int parse_num( unsigned char *temp , int length ) //解释小端法存放的数据 { int i; unsigned int count = 0; for( i = 0 ; i < length ; i++ ) { unsigned int k = temp[i]; k = k << ( i * 8 ); count = count + k ; } return count; }
#define FILE_SYSTEM "/dev/sda1" #define super_block_size 1024 #define offset_super_block 1024 //superblock offsets #define offset_s_inodes_count 0 #define offset_s_blocks_count 4 #define offset_s_first_data_block 20 #define offset_s_log_block_size 24 #define offset_s_blocks_per_group 32 #define offset_s_inodes_per_group 40 typedef struct{ unsigned int inode_per_grp; unsigned int block_per_grp; unsigned int inode_size; unsigned int block_size; unsigned int num_of_inode; unsigned int num_of_block; } super_block ; void err_msg( char *msg); //for error message unsigned int parse_num( unsigned char *temp , int length );
上述代码可通过 gcc -I path_of_def.h main.c 编译,并以root权限执行( sudo ./a.out ),即可获得superblock结构存储的部分信息。注意,Linux环境下以小端法存储数据,即低有效位字节存放在低地址偏移处,直接读出的数据需要经过处理解析出正确的值,该功能可借助简单的 parse_num 函数完成。
在笔者Ubuntu 16.04上运行的结果如图所示,可以看到笔者文件系统的inode和block数,inode和block大小等信息,供文件索引使用。
block
block 是 ext 系列文件系统中数据存放的基本单位。不论是普通文件的数据(文本、二进制数据)还是目录的数据(目录项) 均存放在 block 中,一个文件占用的所有 block 以 block号的形式记录在文件对应的 inode 节点中。block的大小在文件系统格式化时即被确定,可通过 superblock 中的 s_log_block_size 计算得出。ext系列文件系统中,实际的存储介质全部被划分为block,block从 0 开始编号。以 1KB的 block为例,block 0 即为文件系统偏移为 0 字节至偏移为 1023 字节处的1024字节的空间,block 1 即为文件系统偏移1024字节至偏移 2047 字节处的1024字节的空间。想要读取一个 block 的数据,则应自偏移为 block号 * block_size 字节处开始,读取连续的 block_size 字节的数据。
block group descriptor table(块分组描述符表)
在linux环境下,文件系统被格式化后,其中的inode和block的参数包括其大小和数量均已确定。对于一个具备一定容量的存储设备而言,其中包含有大量的 inode 和 block 。ext系列文件系统通过分组对这些 inode 和 block 进行组织,从而方便进行文件的索引和管理,所有的 inode 和 block 被平均分配至组中。
块分组描述符表( block group descriptor table )是一个块分组描述符( block group descriptor )的数组,每个描述符记录了文件系统一个分组的具体信息,包括 inode bitmap、 block bitmap 和 inode table的位置信息,该位置信息以对应结构的数据的起始 block 号的形式给出。一个分组描述符的结构如下表所示,在 ext2 系统下,每个描述符表项占据 32 bytes的空间。
描述符中的偏移(offset) | 字段长度(bytes) | 字段名称 | 字段描述 |
0 | 4 | bg_block_bitmap | 记录该分组的block bitmap数据的起始block号 |
4 | 4 | bg_inode_bitmap | 记录该分组的inode bitmap数据的起始block号 |
8 | 4 | bg_inode_table | 记录该分组的inode table 数据的起始block号 |
12 | 2 | bg_free_blocks_count | 记录该分组中未使用的block数目 |
14 | 2 | bg_free_inodes_count | 记录该分株中未使用的inode数目 |
16 | 2 | bg_used_dirs_count | 记录该分组中被用于目录的inode节点数目 |
18 | 2 | bg_pad | 用于实现4字节对齐 |
20 | 12 | bg_reserved | 保留字段,用于后续拓展 |
在ext系列文件系统中,块分组描述符表的数据总是从superblock所在block的下一个block中开始,具体占据的block数取决于分组个数,在格式化后即固定。举例来说,superblock总是从文件系统偏移为 1024bytes 处开始,占据 1024 bytes,若 block 大小为 1KB,则superblock占据 block 1( block 编号总是从零开始 ),则块分组描述符表的数据占据 block 2 开始的一个或多个block,也就是自文件系统起始偏移为2048 bytes 处;若 block 大小为 4KB,则superblock位于block 0,块描述符表的数据从 block 1 开始存放,也就是自文件系统起始偏移为 4096 bytes处。这一点在进行数据读取时需格外注意。
inode/block bitmap
在ext系列文件系统中使用 bitmap(位图) 来记录inode和block的使用情况。其中每一位表示该分组中inode/block的使用情况,对应位置上 bit = 0 ,表示 inode/block 未使用,bit = 1 ,则表示对应的inode/block以被使用。一个分组的第一个 block 对应 block bitmap 中 byte 0 的 bit 0 (字节最低有效位), 第八个block对应 block bitmap 中 byte0 的 bit 7(字节最高有效位)。inode bitmap 情况与上述类似。每个group的 inode/block bitmap 的起始block号可由块组对应的组描述符的 bg_block_bitmap 和 bg_inode_bitmap 字段获得。
inode table
inode table为连续分布的 inode 结构的数组,即每个 group 中的 inode table 由所有属于该分组的 inode 节点构成。inode 的大小是由superblock的 s_inode_size 字段给出。每个 group 的 inode table 的数据的起始 block号由 group descriptor table 中对应的描述符项的 bg_inode_table 条目给出,每个 group 的 inode table 根据其中 inode 节点个数、inode和 block 大小等参数占据若干个 block 块。
ext系列文件系统的存储布局
Linux的 ext2 文件系统以 block 作为数据存储的基本单位,以 inode 作为标志一个文件的结构,以 inode/block table 对相应结构的分配和使用进行管理。同时, ext2 通过分组的方式将磁盘划分为更易于管理的区域,并使用 group descriptor 对其进行描述。而superblock则描述了一个文件系统的基本配置信息。所有这些将完整的存储介质规划成了一个结构相对规律和容易寻址的磁盘布局。
(1) superblock 数据总是自文件系统起始偏移 1024 bytes 处开始,并占据 1024 bytes 的空间,group descriptor table 则始于superblock所在的block随后的block中。group 0 总是从 superblock 所在的块开始;
(2) 由于 superblock 和 group descriptor table 中包含有描述文件系统的最基本和重要的信息,这两个结构在其他块组中存在备份。最初的 ext 系列文件系统在每个分组中均存放上述结构的备份,后续的 ext系列文件系统允许以 “疏” 超级块特性( sparse superblock feature )进行存储备份,即将 superblock 和 block descriptor table 存放在 group 0 以及所有组号为 x3 、x5、 x7的组中( x = 1 , 2 , 3 ...);
(3) 在每个块组中,存在 inode/block bitmap 和 inode table 等结构,对该分组内的 inode 和 block 进行管理。
根据上述描述,一个典型的 ext 系列的文件系统的存储布局( block size = 4K )情况如图所示:
其中,inode/block bitmap 和 inode table 的具体起始位置( 起始block号 ) 应通过 group descriptor table 中对对应组的描述符来获得。
inode
inode为文件系统中描述文件的基本结构,每个文件(目录)有且仅有一个inode结构与之对应。inode记录了一个文件基本上所有的属性信息,包括访问控制信息、创建/修改/访问/删除日期、所有者/所在组等,但inode节点中不包含文件名,文件名和对应的inode号一起存放在文件所属的目录的目录项中(也就是目录文件的数据)。同时,inode节点以记录block号的方式记录了文件存储所使用的所有的block的信息,获得一个inode结构数据后,即可根据其中存放的block号获得文件存储的数据。文件系统中的inode编号从0开始。某些inode号,如 inode 2 记录的是文件系统根目录的信息,无法用作一般用途。
inode节点的大小在格式化时即固定,并记录在 superblock 的 s_inode_size 字段中。inode结构中部分字段的偏移和含义如下表所示:
inode中的偏移(offset) | 字段长度(bytes) | 字段名称 | 字段描述 |
0 | 2 | i_mode | 记录文件类型和简单的访问控制权限 |
28 | 4 | i_blocks | 记录文件存储占用的 512 bytes 块的数量,文件实际占用的 block 的数量可通过 ( i_blocks * 512 ) / block_size 计算得出 |
40 | 15*4 | i_block | 记录文件存储所使用的block号的结构,共有 15 个项 |
i_mode
inode节点的 i_mode 字段以 bit 的方式记录了文件类型和简单的访问控制信息,也就是用户日常使用 ls -l file_path 命令时显示出来第一列信息,文件类型包括目录、常规文件符号连接等,而访问控制权限则是对应于文件的所有者、所有者所在的组和其他人所规定的读、写和执行权限。
i_blocks
这里的 i_blocks 字段并不是文件存储使用的block的数量,而是文件存储所使用的512 bytes块的数量。也就是说,文件存储实际占用的block数量可通过 ( i_blocks * 512 ) / block_size 求出。
i_block
ext2文件系统中,以 block号的形式记录了文件存储使用的所有block,并记录在 i_block 字段中。该字段共有 15 个条目,每个条目占据 4 字节空间,条目内容均为一个block号,但其具体含义并不完全相同。其中,前12个条目为直接记录项,即条目的内容为存放文件数据的block号;第13项为间接索引项,条目内容指向一个block,该 block 存放的是文件存储使用的block号;第14项为双重间接索引项,条目内容指向一个block,该block中存放的内容为与第13项条目作用相同的block号;第15项为三重简介索引项,条目内容指向一个block,该block存放的是与第14个条目作用相同的block号。以 1K大小的block举例,文件足够大时,文件数据使用的第13至第268个block的block号均存放在第13项条目所指向的那个block中。而接下来的 256 * 256 个block的block号则可以通过第14个条目去寻找得到。当记录中某个block号为零时,表示已没有后续的block号记录,相应的后续的条目值也应该被置为0.
i_block的基本结构示意图:
注意,以上的文件block号记录结构适用于ext2系统,在ext4中,还存在其他形式的记录组织。
directory
Linux系统下,目录被视为文件,通过 inode 记录其相关属性,通过 block记录其数据。目录的数据由一个个目录项组成,一个目录项包含有该目录下文件的文件名、对应的inode号等信息。
对于一个目录项,典型的结构如下表所示:
目录项中的偏移(offset) | 字段长度(bytes) | 字段名称 | 字段描述 |
0 | 4 | inode | 文件对应的inode号 |
4 | 2 | rec_len | 自当前目录项起始处到下一目录项起始处的偏移 |
6 | 1 | name_len | 记录文件名的长度 |
7 | 1 | file_type | 记录文件类型 |
8 | 0-255 | name | 记录文件的名字 |
在最初的ext2版本中,目录项以类似链表的形式存放,后续版本则支持通过索引直接访问目录项,后者保持对前者的兼容。这里仅对链表形式的目录项进行介绍,具体的indexed directory形式的介绍可参见这里。
inode
记录目录项所代表的文件的inode号。当此项为0时,表示该目录项未被使用(这样“空”目录项可用于实现上述索引结构目录项对链表形式目录项的兼容)。用户可以通过 ls -i 命令查看文件的 inode 号。
rec_len
从当前目录项的起始地址到下一个目录项的起始位置的偏移字节数,因而该字段的值至少为当前目录项的长度。同时所有的目录项在存储时必须遵循4字节对齐的规则,也就是rec_len可能会大于当前目录项的长度。如果当前目录项被删除时,当前目录项的前一目录项的rec_len字段就应该被修改,使其指向当前目录项的下一目录项的起始处;而若是该block中的第一个目录项被删除了,则可以创建一个空的目录项( inode = 0 ),使其指向之前第二个目录项的位置。若某目录项为当前block中存放的最后一项,则其 rec_len 应该使得“下一目录项”的偏移为block的最后,从而表示当前block已没有目录项数据,应至该目录的下一block中去查找。
name_len
8 bits字段表示文件名长度。注意存在这种情况,就是当文件名修改后,该目录项长度变大,使得当前block无法完整存放该目录项,则需要在新的目录项位置(甚至新的block)中存放该目录项(一个完整的目录项必须存放在一个block中),同时其前一个目录项的rec_len字段需要做相应的修改。表示当前block中已不存在目录项,需至下一个存放目录数据的block中进行查找。注意实际存在的文件名仅由name_len 字段指定,文件名本身是不带有结束符号的(某些情况下为了遵循 4 字节对齐原则可能文件名后会以0填充,但这并不是一定存在的)。
file_type
在最初的文件系统中,该字段为name_len的高8bits,但由于一般系统实现均将文件名限制在255字节以内,故而该字段一般为0.所以后续的系列文件系统将该字段用于记录文件的类型,从而可以不读取inode结构的数据即获得文件类型信息( inode结构中记录的类型信息是在 i_mode 字段中 )。
注意:在Linux文件系统的目录下,存在两个隐藏目录项 . 和 .. ,其中 . 目录项的 inode 字段记录的是当前目录的 inode 号,而 .. 目录项的 inode 字段记录的是当前目录的上一级目录的 inode 号。如对目录 /home ,其 block 数据中中含有 . 和 .. 两个目录项,其中 . 目录项的 name 字段为 ".",inode字段则为 /home目录这个文件的 inode号,而
文件系统相关的操作
通过 inode/block 号获得具体的 inode/block 数据
block 号
由于 block 号是从 0 开始编号的,所以某个文件系统中 block n 的数据为自偏移 block_size * n 处开始的 block_size 字节的数据。其中 block_size 可通过 superblock中的 s_log_block_size 字段求出;
inode 号
inode号是从 1 开始编号的,且被平均分配至每个 group 中,每个 group 中的 inode 结构连续排列形成 inode table.
对于inode 号 n,inode 所在的 group 号为 gno = ( n - 1 ) / inode_per_group ,从而可在 group descriptor table 中找到对应组的 group descriptor,获得该组中 inode table 的起始 block n,即可知道 gno 组的 inode table 是从偏移 block_size * n 处开始的,通过 index = ( n - 1 ) / inode_per_group 获得 inode n在该组的 inode table 中的索引,即在上述偏移的基础上加上偏移量 inode_size * index ,便是 inode n 的数据的起始位置。上述的 inode_size、inode_per_group等数据均可在 superblock 的对应字段中获得。
从上述过程中也可看出,基础的数据信息如 inode/block 的大小,每组中含有的 inode 号等信息在文件查找过程中是十分重要的,这也是为什么 superblock 需要进行多次备份的原因。
如何定位一个文件
给定一个文件的绝对路径,要想在一个文件系统中找到某一个文件,就需要从文件系统的根目录( inode号为2 )开始搜索,通过该 inode 节点获得文件系统根目录使用的block块,目录文件的block块中存放的均为目录项,之后在这些目录项中查找是否有与当前的待查找对象匹配的目录项,如果有,则获得(目录项中记录的)一个新的inode号,再根据该inode号进行下一步操作。
如当前文件系统的根目录为 / ,则其对应的 inode 号为 2 ,若我们想寻找文件 /etc/fstab , 则首先通过 inode 号 2 获得 / 目录的block数据,其中的数据为所有位于 / 目录下的文件的目录项,这里需要找到目录项中 name 字段为 etc 的目录项,从而获得其对应的inode号 n ,由于/etc文件为一个目录,故而通过 inode 号 n 去获得 /etc 目录文件的数据块 block,并在其数据块( 存放的也是目录项 )中寻找 name 字段为 fstab 的目录项,最终若找到能与之匹配的结果,则该目录项的 inode 字段记录的即为文件 /etc/fstab 的 inode 号,根据该 inode 号可获得目标文件的实际数据。(这是一个简化的例子,文件系统的根目录就是 / ,且不存在其他文件系统的挂载的情况)
文件系统的根目录与Linux下的 / 目录
文件系统的根目录和Linux系统中的根目录 / 含义并不是相同的。一般来说,一个分区即为为一个完整的文件系统,其根目录的 inode 号总是该文件系统中的 2 号,作为一个目录,文件系统根目录中存放的是所有位于该目录中的文件的目录项。这里给出他们关系的感性认识,不给出具体的原理和描述。
笔者通过 df 命令获得笔者系统的挂载情况(最后一行为挂载的目录名),笔者系统中分出了多个分区如 /dev/sda9 、/dev/sda11等,每个分区是一个完整的 ext 文件系统,而根据挂载点的不同其中存放的数据也不同。
通过 ls -ild 命令查看 / 、/boot、/home目录的 inode 号,可以看到这三个目录所使用的 inode 号均为 2,但这些 inode 是位于不同文件系统的 inode。
这里,分区 /dev/sda10 的根目录即为 / ,其使用的是 /dev/sda10 分区的文件系统中的 inode 2,该目录文件的内容为所有位于 / 目录下的文件的目录项。分区 /dev/sda9 的根目录为 /boot ,其使用的是 /dev/sda9 分区的文件系统中的 inode 2,该目录文件的内容为所有位于 /boot 目录下的文件的目录项,而所有 /boot 文件夹下的数据均存放在该文件系统中(不考虑其他挂载)。
Linux 操作系统通过挂载的概念将目录树结构与多个文件系统相结合,从而将数据存放至不同的分区中。如寻找文件 /home/yh 时,文件 home 的目录项位于 / 目录中,即系统会首先在 / 所在的分区 /dev/sda10 中查找到 name 字段为 home 目录项,得到文件目录 /home的目录项信息,然后系统会根据一些标志位得知 /home 是挂载至另一个文件系统( /dev/sda11分区的文件系统 )的,所以系统会到新的分区 /dev/sda11 中去寻找 /home 文件( 因为是该文件系统的根目录,所以使用 inode 节点号为 2 )的数据,并会在其中找到 yh 文件对应的目录项,从而获得 /home/yh 文件的 inode 号 。
硬链接( hard link ) 与符号链接( symbolic link)
硬链接( hard link )
硬链接相当于为一个文件取了一个别名。通过之前的介绍可以知道,每一个文件均有一个 inode 节点与之对应,其中包含有整个文件除名字以外的几乎所有信息,而每个文件均在该文件所在的目录中存在一个目录项,其中存放了文件对应的 inode 号、name等信息。若存在文件 /home/yh/test,则当我们为其建立一个硬链接 /home/yh/kkk 时,在目录 /home/yh/ 下即会增加一个目录项 ,其 name 字段为 kkk , inode字段则存放的是 /home/yh/test 文件的 inode ,相应的,文件 /home/yh/test 的 inode 结构中的 i_links_count 的值会加一,表示指向该 inode 节点的条目增加了一个。建立硬链接之后,显然当我们访问文件 /home/yh/kkk 时,我们实际访问的文件即为 /home/yh/test。也就相当于为原文件增加了一个别名条目,建立硬链接时,没有新的 inode 节点被分配。
同时,硬链接也有一些特殊性质 (1)硬链接不能跨文件系统,且硬链接也不能链接至目录上; (2)当删除硬链接时,对源文件没有影响,删除硬链接只是删除了对应目录项,并且将源文件 inode 的 i_links_count 的值减一,不会对源文件有影响,一个原始文件的链接数为 1 ,只有当文件的链接数为 1 时,删除文件操作才是真正的将文件删除了,而不是简单的删除目录项数据;(3)一个目录在建立时其中便存在两个硬链接 . 和 .. ,它们分别指向当前目录和当前目录上一级目录;
符号链接( symbolic link)
符号链接与 Windows 环境下的快捷方式类似。符号链接可以被视为一种特殊的文件,其文件内容为其指向的文件的路径名。如对 /home/yh/test 文件建立一个符号链接 /home/yh/kkk,则 /home/yh/kkk 是一个新建的文件,其文件数据即为它指向的文件的路径,在这里也就是 /home/yh/test 。符号链接是一个独立于其指向的源文件的特殊文件,其会被分配一个新的inode,且由于其中存放的是完整的目标文件路径,所以符号链接是可以跨文件系统的。当我们对符号链接进行操作时,系统实际会将符号链接记录的数据(文件路径)取出,并直接操作该文件路径所对应的文件。
作为一种特殊的文件,符号链接可以不占用block,当其指向文件的路径小于60个字节时,符号链接的数据可直接存放在其 inode 的 i_block 字段中,从而不需要分配一个完整的 block。当符号链接被删除时,对源文件没有影响,而当源文件被删除时,对应的符号链接仍存在,但当系统根据其记录的信息去访问文件时,会产生无法找到对应文件的错误。
Linux环境下可以通过 ln 命令建立硬链接和符号链接。
ln [ options ] 源文件路径 生成的链接文件路径
//当不指定生成的链接文件的路径时,则会在当前目录下建立一个硬链接 -s //在不使用 -s 参数时,ln 命令会建立硬链接,-s参数则指定生成一个符号链接
关于Linux文件系统的描述,可以参考的资源有:
(1)对Linux ext2文件系统的总体性描述: 鸟叔的Linux私房菜,Linux磁盘与文件系统管理(ext2)
(2) 本文的参考来源,更详细的ext2 文件系统的细节文本,包括结构的成员和功能的描述:The Second Extended File System,Internal Layout
(3)对ext2文件系统有一定了解后,可以了解关于目前主流的ext4文件系统的描述:Ext4 Disk Layout