Linux文件路径,目录项,inode号关联

一、文件路径

在 Linux 中,文件路径是用字符串表示的,由多个目录名和文件名组成,目录名和文件名之间用斜杠(/)分隔。Linux 中的文件路径可以分为两种类型:绝对路径和相对路径。

绝对路径是从根目录(/)开始的完整路径,它可以唯一地标识一个文件或目录。例如,/home/user/file.txt 是一个绝对路径,它表示根目录下的 home 目录下的 user 目录下的 file.txt 文件。在绝对路径中,每个目录名和文件名都是从根目录开始计算的,因此它们的位置是固定的,不受当前工作目录的影响。

相对路径是相对于当前工作目录的路径,它不能唯一地标识一个文件或目录,而是根据当前工作目录的不同而变化。例如,如果当前工作目录是 /home/user,那么 file.txt 是一个相对路径,它表示当前工作目录下的 file.txt 文件。在相对路径中,每个目录名和文件名都是相对于当前工作目录计算的,因此它们的位置是可变的,受当前工作目录的影响。

在 Linux 中,可以使用一些命令来操作文件路径,例如 cd(change directory) 命令可以改变当前工作目录,pwd 命令可以显示当前工作目录的路径,ls 命令可以列出指定目录下的文件和子目录等。此外,Linux 还提供了一些 C 语言的系统调用函数,例如 chdir 函数可以改变当前工作目录,getcwd 函数可以获取当前工作目录的路径,access 函数可以判断文件是否存在或是否有指定的权限等。

二、文件目录项

2.1 简介

文件系统的目录项(directory entry)是一个数据结构,用于建立文件名和文件的 inode 号之间的映射关系。每个目录都是一个文件,它包含了多个目录项,每个目录项包含了一个文件名和一个 inode 号,以及一些其他的元数据信息,如文件类型、权限、所有者、所属组、大小、创建时间、修改时间等。

目录项的结构如下:

struct dirent
 {
   __ino_t d_ino;		/* inode 号 */
   __off_t d_off;		/* 文件在目录中的偏移量 */
   unsigned short int d_reclen;		/* 目录项长度 */
   unsigned char d_type;			/* 文件类型 */
   char d_name[256];		/* 文件名 We must not include limits.h! */
 };

其中,d_ino 字段表示文件的 inode 号,d_off 字段表示文件在目录中的偏移量,d_reclen 字段表示目录项的长度,d_type 字段表示文件的类型,d_name 字段表示文件名。在 Linux 中,文件的类型可以是以下几种:

DT_REG:普通文件
DT_DIR:目录文件
DT_FIFO:命名管道
DT_SOCK:套接字文件
DT_CHR:字符设备文件
DT_BLK:块设备文件
DT_LNK:符号链接文件
/* File types for `d_type'.  */
enum
  {
    DT_UNKNOWN = 0,
# define DT_UNKNOWN	DT_UNKNOWN
    DT_FIFO = 1,
# define DT_FIFO	DT_FIFO
    DT_CHR = 2,
# define DT_CHR		DT_CHR
    DT_DIR = 4,
# define DT_DIR		DT_DIR
    DT_BLK = 6,
# define DT_BLK		DT_BLK
    DT_REG = 8,
# define DT_REG		DT_REG
    DT_LNK = 10,
# define DT_LNK		DT_LNK
    DT_SOCK = 12,
# define DT_SOCK	DT_SOCK
    DT_WHT = 14
# define DT_WHT		DT_WHT
  };

目录项的索引是通过目录文件内部的哈希表(hash table)或 B+ 树实现的。当用户打开一个目录时,系统会读取目录文件中的目录项,并将它们缓存在内存中。当用户访问某个文件时,系统会根据文件名查找对应的目录项,获取该文件的 inode 号,然后根据 inode 号读取文件的内容。

需要注意的是,一个文件可以有多个硬链接,即多个不同的目录项指向同一个 inode 号。这意味着,文件的实际路径可能不止一个(一个inode号可以对应多个文件路径,但一个文件路径只有一个inode号),用户可以通过任意一个路径来访问该文件。在 Linux 中,通过 stat 系列函数可以获取文件的硬链接数量。

2.2 例子

下面是一个使用 struct dirent 结构体的示例程序,可以列出指定目录下的所有文件和子目录的名称和 inode 号:

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>

int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
        exit(1);
    }

    char *dirpath = argv[1];
    DIR *dirp;
    struct dirent *direntp;

    if ((dirp = opendir(dirpath)) == NULL) {
        perror("opendir error");
        exit(1);
    }

    while ((direntp = readdir(dirp)) != NULL) {
        printf("%s (inode=%lu)\n", direntp->d_name, direntp->d_ino);
    }

    closedir(dirp);

    return 0;
}

在这个示例程序中,我们通过命令行参数传递了要列出文件和子目录的目录路径。在主函数中,我们首先调用 opendir 函数打开目录,如果返回值为 NULL,说明打开目录失败,我们将打印出错信息并退出程序。如果 opendir 函数返回值不为 NULL,说明打开目录成功,我们将通过 readdir 函数读取目录中的目录项,并打印出文件名和 inode 号。最后,我们调用 closedir 函数关闭目录。

需要注意的是,direntp->d_name 只包含文件名,不包括路径。如果要获取文件的完整路径,可以将文件名和路径拼接起来。另外,当读取完所有的目录项后,readdir 函数会返回 NULL,表示目录读取结束。

其中:

/* This is the data type of directory stream objects.
   The actual structure is opaque to users.  */
typedef struct __dirstream DIR;

在 Linux 中,DIR 数据结构是一个指向目录流的指针,是由 opendir 函数返回的,用于表示一个打开的目录。目录流是一个抽象的概念,指的是与目录相关联的文件描述符和目录缓存,它可以用于读取目录中的目录项。

DIR 数据结构的定义如下:

typedef struct {
    int fd;                 /* 目录文件的文件描述符 */
    struct dirent *dirent;  /* 当前读取的目录项 */
} DIR;

其中,fd 字段是目录文件的文件描述符,dirent 字段是一个指向当前读取的目录项的指针。

在 Linux 中,目录流的底层实现是通过文件描述符和目录缓存来实现的。当用户调用 opendir 函数打开一个目录时,系统会创建一个文件描述符,然后将目录文件映射到该文件描述符上,并创建一个目录缓存,用于存储读取的目录项。每次调用 readdir 函数,系统会从目录缓存中读取一个目录项,并将 direntp 指针指向该目录项。当读取完所有的目录项后,系统会关闭文件描述符,并释放目录缓存。

需要注意的是,DIR 数据结构是不透明的,用户不能直接访问其内部结构,而是通过 opendir 函数打开目录,并通过 readdir 函数读取目录项。在使用完 DIR 数据结构后,用户应该调用 closedir 函数关闭目录,以释放资源。

三、文件inode号

3.1 简介

在 Linux 中,每个文件和目录都有一个唯一的 inode 号(inode number),用于标识该文件或目录的实体。inode 号是一个整数值,通常是一个非负整数,具体取值范围取决于文件系统的实现。

每个inode号都会对应一个i节点(inode结构体),i节点是文件系统中用于表示文件和目录的数据结构。每个文件和目录都有一个对应的i节点,它包含了关于文件或目录的元数据信息,如文件类型、权限、所有者、所属组、大小、创建时间、修改时间等,以及指向文件数据块的指针。

数据块则保存了文件的数据。每个文件有且仅有一个i节点,但可以有0、1或多个数据块。i节点最重要的作用作为寻址文件数据的出发点,因此i节点中需要保存包含文件数据的数据块编号。

如下图所示:
在这里插入图片描述
inode编号可以让文件系统(比如ext4)用来快速搜索磁盘上inode表中的inode节点。
比如:
在这里插入图片描述
一个文件inode编号 = 13021,假设每个块组包含4096个节点,文件inode编号13021的文件在磁盘上的地址 =

13021 = 4096 * 3 + 733

索引节点在第三个块组,其磁盘地址存放在相应 inode table索引节点表的第733个表项。

inode节点占用硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode表(inode table),存放inode所包含的信息。
每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。

比如ext4文件系统的inode结构体:

// linux-5.4.18/fs/ext4/ext4.h

/*
 * Structure of an inode on the disk
 */
struct ext4_inode {
	__le16	i_mode;		/* File mode */
	__le16	i_uid;		/* Low 16 bits of Owner Uid */
	__le32	i_size_lo;	/* Size in bytes */
	__le32	i_atime;	/* Access time */
	__le32	i_ctime;	/* Inode Change 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_lo;	/* Blocks count */
	__le32	i_flags;	/* File flags */
	......
	__le32	i_block[EXT4_N_BLOCKS];/* Pointers to blocks */

其中i_block 数组存储了指向文件数据块的指针,用于定位和访问文件的实际数据。

inode也能够将数据存储在inode本身i_block 数组中。这叫做Inlining。这种存储方法具有节省空间的优点,因为不需要数据块。它还通过避免更多的磁盘访问来获取数据,从而增加了查找时间。

像ext4这样的一些文件系统有一个名为inline_data的选项。启用后,它允许操作系统以这种方式存储数据。由于大小限制,内联只适用于非常小的文件。如果大小不超过60字节,Ext2和更高版本通常会以这种方式存储软链接信息。

内嵌存储(Inlining)是一种特定文件系统(如ext4)提供的功能,它允许将小量的数据直接存储在inode本身,而无需额外的数据块。这种存储方式具有以下特点:
(1)节省空间:通过在inode内部直接存储数据,文件系统避免了分配额外的数据块的需要。这可以节省空间,特别是对于数据量较小的文件。它消除了为这些文件分配和管理单独的数据块的开销。

(2)减少磁盘访问:使用内嵌存储,数据可以直接从inode中访问,而无需进行额外的磁盘读取。这可以提高小文件的查找速度,因为数据直接在inode结构中可用。

(3)大小限制:内嵌存储有一定的大小限制,因为inode具有固定的大小。可以内嵌存储的数据量取决于具体的文件系统及其配置。例如,在ext4中,inline_data选项允许将小文件内嵌存储,通常限制在60字节以内。如果文件超过大小限制,则会回退到传统的使用数据块的方式。

(4)使用场景:内嵌存储通常用于存储小文件,例如配置文件、小型脚本或符号链接信息。这些文件通常符合内嵌存储的大小限制,并从其节省空间和提高性能的优势中受益。

每个文件系统都有一个 inode 表(inode table),用于存储该文件系统中所有文件和目录的 inode节点。当用户创建一个新文件或目录时,系统会分配一个新的 inode 号,并将文件或目录的元数据信息写入 inode结构体中, inode结构体保存在inode表中。当用户访问一个文件或目录时,系统会根据文件名或目录名查找对应的 inode 号,然后根据 inode 号,从inode 表获取到对应的inode结构体,根据inode结构体的数据块地址来找到文件数据所在的block,读取文件或目录的内容。

inode 号可以用于实现硬链接(hard link)。硬链接是指通过多个目录项来共享一个 inode 号的文件,这样多个不同的路径可以指向同一个文件。当用户创建一个硬链接时,系统会创建一个新的目录项,并将 inode 号复制到该目录项中,这样该目录项就可以指向原文件的内容。在 Linux 中,使用 ln 命令可以创建硬链接。

需要注意的是,不同的文件系统的 inode 号是独立的,即同一个 inode 号在不同的文件系统中可能会指向不同的文件或目录。此外,inode 号是文件系统内部使用的标识符,用户不能直接访问或修改 inode 号。

3.2 文件名查找inode号

一个文件只对应一个inode号。

ls查看文件名的inode号:

ls -i filename
//Trace   all   system   calls   which   take   a   file  name  as  an  argument.
strace -e trace=file ls -i filename

在 Linux 中,可以使用 stat 系列函数来获取文件的 inode 号。stat 函数用于获取文件的元数据信息,例如文件类型、权限、所有者、所属组、大小、创建时间、修改时间等,其中包括 inode 号。

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        exit(1);
    }

    char *filepath = argv[1];
    struct stat filestat;

    if (stat(filepath, &filestat) < 0) {
        perror("stat error");
        exit(1);
    }

    printf("Inode number of %s is %lu\n", filepath, filestat.st_ino);

    return 0;
}

stat 函数的第二个参数是一个指向 struct stat 结构体的指针,用于存储获取到的文件元数据信息。

在 Linux 中,struct stat 结构体用于存储文件的元数据信息,包括文件类型、权限、所有者、所属组、大小、创建时间、修改时间、访问时间等。该结构体定义在 sys/stat.h 头文件中,其定义如下:

struct stat {
    dev_t st_dev;         /* 文件所在的设备编号 */
    ino_t st_ino;         /* 文件的 inode 号 */
    mode_t st_mode;       /* 文件的类型和权限 */
    nlink_t st_nlink;     /* 文件的硬链接数 */
    uid_t st_uid;         /* 文件所有者的用户 ID */
    gid_t st_gid;         /* 文件所有者的组 ID */
    dev_t st_rdev;        /* 如果文件是设备文件,则为其设备编号 */
    off_t st_size;        /* 文件的大小,以字节为单位 */
    blksize_t st_blksize; /* 文件系统的块大小 */
    blkcnt_t st_blocks;   /* 文件所占用的块数 */
    time_t st_atime;      /* 文件的最后访问时间 */
    time_t st_mtime;      /* 文件的最后修改时间 */
    time_t st_ctime;      /* 文件的最后状态改变时间 */
};

struct stat 结构体中的各个字段含义如下:

st_dev:文件所在的设备编号;
st_ino:文件的 inode 号;
st_mode:文件的类型和权限;
st_nlink:文件的硬链接数;
st_uid:文件所有者的用户 ID;
st_gid:文件所有者的组 ID;
st_rdev:如果文件是设备文件,则为其设备编号;
st_size:文件的大小,以字节为单位;
st_blksize:文件系统的块大小;
st_blocks:文件所占用的块数;
st_atime:文件的最后访问时间;
st_mtime:文件的最后修改时间;
st_ctime:文件的最后状态改变时间。

在实际编程中,我们可以通过调用 stat 或 lstat 函数来获取一个文件的元数据信息,并将其存储在 struct stat 结构体中。需要注意的是,stat 函数和 lstat 函数的区别在于,当文件为符号链接时,stat 函数返回链接目标文件的元数据信息,而 lstat 函数返回链接文件本身的元数据信息。

3.3 inode号查找文件

一个inode号可以对应多个文件。
在 Linux 中,可以使用 inode 号来查找文件。下面介绍两种基本的方法。

(1)使用 find 命令
find 命令可以用于在指定目录下查找满足条件的文件或目录,其中可以使用 -inum 选项指定 inode 号来查找文件。例如,下面的命令可以在 /home/user 目录下查找 inode 号为 123 的文件:

find /home/user -inum 123

(2)使用 debugfs 工具
debugfs 是一个用于调试文件系统的工具,它可以让用户直接访问文件系统的数据结构,包括 inode 表。使用 debugfs 工具可以通过 inode 号查找文件,并且可以查看文件的元数据信息。下面是一个使用 debugfs 工具查找文件的示例:

debugfs /dev/sda1  # 进入 debugfs 工具
debugfs: inode <123>  # 查找 inode 号为 123 的文件
debugfs: ls -l  # 查看文件的元数据信息

在上面的示例中,我们首先使用 debugfs 命令进入 debugfs 工具,然后使用 inode 命令查找 inode 号为 123 的文件。最后,我们使用 ls -l 命令查看该文件的元数据信息。

需要注意的是,使用 debugfs 工具需要具有管理员权限,同时也需要谨慎操作,避免对文件系统造成损坏。

总结

在 Linux 中,文件路径、目录项和 inode 号之间的关系可以用下图来表示:

文件路径 --> 目录项 --> 文件名 --> inode 号

因此,文件路径和inode号之间的关系是通过目录项建立的。

目录项是指文件系统中的一个目录条目,包含了文件名和文件的 inode 号。在 Linux 中,每个目录中都包含了若干个目录项,其中每个目录项对应着文件系统中的一个文件或目录。因此,我们可以通过目录项来查找文件或目录的 inode 号,根据inode号可以获取到inode节点,从而获取文件或目录的元数据信息,根据元数据信息的指向文件数据块的指针寻址到对应的文件数据块,文件数据块保存着实际的文件内容。

目录:
	目录项1  文件名1 -- >inode号1 
	目录项2  文件名2 -- >inode号2
	目录项3  文件名3 -- >inode号3
	目录项4  文件名4 -- >inode号4
	目录项5  文件名5 -- >inode号5
	......

然后根据inode号来获取文件的内容:

inode号 --> inode节点 --> 文件数据块指针寻找  --> 文件数据块1
										  --> 文件数据块2
										  --> 文件数据块n

根据 inode 号可以找到磁盘上对应块组中inode表中inode结构体。
一个文件会有多个文件数据块。

具体来说,当我们在 Linux 中打开一个文件时,系统会先通过文件路径找到该文件所在的目录,然后根据文件名在目录中查找对应的目录项,最后获取目录项中存储的 inode 号。通过 inode 号,系统可以查找文件的元数据信息,并访问文件的内容。因此,文件路径、目录项和 inode 号是 Linux 文件系统中的三个重要概念,它们共同构成了文件系统的基本结构。

文件名和inode号之间的关系是通过目录项(directory entry)来建立的。目录项是一个数据结构,它包含了文件名和文件的inode号之间的映射关系。每个目录都是一个文件,它包含了多个目录项,每个目录项包含了一个文件名和一个inode号。当用户在文件系统中打开一个文件时,系统会根据文件名查找对应的目录项,获取该文件的inode号,根据inode号可以获取到inode节点,然后根据inode节点获取文件的元数据信息中的文件数据块地址,根据文件数据块地址寻址到实际的文件数据块,从文件数据块读取文件的内容。

如果用户移动或重命名文件,系统会更新目录项中的文件名,但不会改变文件的inode号,因为inode号是唯一标识文件的标识符。因此,即使文件名发生了改变,只要inode号没有改变,用户仍然可以通过修改后的路径访问该文件。

移动或重命名都是调用的 rename系列的系统调用。

一个文件可以有多个目录项,也称为硬链接。在 Linux 和其他类 Unix 操作系统中,硬链接是指指向与原始文件相同的 inode 的目录项。这意味着可以从多个目录路径访问该文件,并且对文件的修改将反映在所有硬链接中。
硬链接只能针对文件而不是目录创建,并且必须在同一文件系统中创建。此外,只要该文件仍有任何硬链接存在,删除任何硬链接都不会删除该文件本身,因为该文件仍然可以通过其余硬链接访问。

在文件系统重新挂载后,文件的路径可能会发生变化,这取决于重新挂载时挂载点的位置是否改变。挂载点是指将一个文件系统挂载到另一个文件系统中的目录。当一个文件系统被挂载到某个目录下时,该目录就成为了挂载点,文件系统中的文件和目录就可以通过该挂载点访问。

如果重新挂载时挂载点的位置没有改变,那么文件的路径不会发生变化,它们仍然可以通过原来的路径访问。但是,如果重新挂载时挂载点的位置改变了,那么文件的路径就会发生变化,它们可能无法通过原来的路径访问,需要根据新的挂载点来访问。在这种情况下,由于文件的 inode 号不会改变,因此仍然可以通过 inode 号来访问文件。

对于inode号,一个文件的路径修改后以及文件系统重新挂载后,该文件的inode都不会变。
但是一台机器会有多个文件系统,因此文件的inode可能重复。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值