# 文件系统，第 2 部分：文件是 inode（其他一切只是数据...）

> 原文：<https://github.com/angrave/SystemProgramming/wiki/File-System%2C-Part-2%3A-Files-are-inodes>

大局观：忘记文件名：'inode'是文件。

大家通常将文件名视为“实际”文件。但是这是错误的观点！应该将inode视为文件。inode保存元信息（最后访问，所有权，大小）并指向用于保存文件内容的磁盘块。

## 那么问题来了，我们如何实现目录？

目录只是名称到inode编号的映射。 POSIX提供了一小组函数来读取每个条目的文件名和inode号（见下文）
让我们想想它在实际文件系统中的样子。从理论上讲，目录就像实际文件一样。磁盘块将包含目录条目或目录。这意味着我们的磁盘块可以看起来像这样的。

| inode_num | 名称 |
| --- | --- |
| 2043567 | hi.txt |


每个目录条目可以是固定大小，也可以是变量 c-string。它取决于特定文件系统在底层的实现方式。

## 如何找到文件的 inode 编号？

从 shell 中，将`ls`与`-i`选项一起使用

```
$ ls -i
12983989 dirlist.c      12984068 sandwich.c 
```

你也可以说使用C语言的stat函数（下面介绍）。

## 如何找到有关文件（或目录）的元信息？

使用stat函数。例如，要找出上次访问我的'notes.txt'文件的时间

```c
   struct stat s;
   stat("notes.txt", & s);
   printf("Last accessed %s", ctime(s.st_atime));
```

实际上有三个版本的`stat`;

```c
       int stat(const char *path, struct stat *buf);
       int fstat(int fd, struct stat *buf);
       int lstat(const char *path, struct stat *buf);
```

例如，如果您已经有与该文件关联的文件描述符，则可以使用`fstat`查找有关文件的元信息

```c
   FILE *file = fopen("notes.txt", "r");
   int fd = fileno(file); /* Just for fun - extract the file descriptor from a C FILE struct */
   struct stat s;
   fstat(fd, & s);
   printf("Last accessed %s", ctime(s.st_atime));
```

我们将在引入符号链接时讨论第三个调用'lstat'。

除了访问，创建和修改时间之外，stat这个数据结构还包括inode编号，文件长度和所有者等信息。

```c
struct stat {
               dev_t     st_dev;     /* ID of device containing file */
               ino_t     st_ino;     /* inode number */
               mode_t    st_mode;    /* protection */
               nlink_t   st_nlink;   /* number of hard links */
               uid_t     st_uid;     /* user ID of owner */
               gid_t     st_gid;     /* group ID of owner */
               dev_t     st_rdev;    /* device ID (if special file) */
               off_t     st_size;    /* total size, in bytes */
               blksize_t st_blksize; /* blocksize for file system I/O */
               blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
               time_t    st_atime;   /* time of last access */
               time_t    st_mtime;   /* time of last modification */
               time_t    st_ctime;   /* time of last status change */
           };
```

## 如何列出目录的内容？

让我们编写自己的'ls'版本来列出目录的内容。

```c
#include <stdio.h>
#include <dirent.h>
#include <stdlib.h>
int main(int argc, char **argv) {
    if(argc == 1) {
        printf("Usage: %s [directory]\n", *argv);
        exit(0);
    }
    struct dirent *dp;
    DIR *dirp = opendir(argv[1]);
    while ((dp = readdir(dirp)) != NULL) {
        puts(dp->d_name);
    }

    closedir(dirp);
    return 0;
}
```

## 如何读取目录的内容？

Ans：使用 opendir readdir closedir 例如，这是一个非常简单的'ls'实现来列出目录的内容。

```c
#include <stdio.h>
#include <dirent.h>
#include <stdlib.h>
int main(int argc, char **argv) {
    if(argc ==1) {
        printf("Usage: %s [directory]\n", *argv);
        exit(0);
    }
    struct dirent *dp;
    DIR *dirp = opendir(argv[1]);
    while ((dp = readdir(dirp)) != NULL) {
        printf("%s %lu\n", dp-> d_name, (unsigned long)dp-> d_ino );
    }

    closedir(dirp);
    return 0;
}
```

注意：在调用 fork（）之后，父级或子级（XOR）可以使用 readdir（），rewinddir（）或 seekdir（）。如果父项和子项都使用上述内容，则可能会抛出未定义错误。

## 如何检查文件是否在当前目录中？

例如，要查看特定目录是否包含文件（或文件名）'name'，可以使用以下代码。 （提示：你能发现这个错误吗？）

```c
int exists(char *directory, char *name)  {
    struct dirent *dp;
    DIR *dirp = opendir(directory);
    while ((dp = readdir(dirp)) != NULL) {
        puts(dp->d_name);
        if (!strcmp(dp->d_name, name)) {
        return 1; /* Found */
        }
    }
    closedir(dirp);
    return 0; /* Not Found */
}
```

上面的代码有一个微妙的错误：它泄漏了资源！如果找到匹配的文件名，那么'closedir'函数永远不会被执行。由opendir打开的任何文件描述符和任何分配的内存都不会被释放。这意味着最终进程将耗尽资源，`open`或`opendir`调用将失败。

解决方法是确保我们在每个可能的代码路径中释放资源。在上面的代码中，这意味着在`return 1`之前调用`closedir`。忘记释放资源是一个常见的 C 编程错误，因为C语言中没有任何机制来保障代码正常释放资源。

## 使用 readdir 有什么问题？例如，递归搜索目录？

有两个主要问题和一个考虑因素：`readdir`函数返回“.”（当前目录）和“..”（父目录）。如果要查找子目录，则需要明确排除这些目录。

对于许多应用程序，在递归搜索子目录之前首先检查当前目录是合理的。这可以通过将结果存储在链表中，或重置目录结构以从头重新开始来实现。

最后一点需要注意：`readdir`不是线程安全的！对于多线程搜索，使用`readdir_r`，它要求调用者传入现有 dirent 结构的地址。

有关更多详细信息，请参见 readdir 的手册页。

## 如何确定目录条目是否是目录？

Ans：使用`S_ISDIR`检查存储在 stat 结构中的模式位

并检查文件是否是常规文件使用`S_ISREG`，

```c
   struct stat s;
   if (0 == stat(name, &s)) {
      printf("%s ", name);
      if (S_ISDIR( s.st_mode)) puts("is a directory");
      if (S_ISREG( s.st_mode)) puts("is a regular file");
   } else {
      perror("stat failed - are you sure I can read this file's meta data?");
   }
```

## 目录是否也有 inode？
是的！虽然更好的思考方式是目录（如文件）是一个inode（有一些数据 - 目录名称和inode内容）。它只是碰巧是一种特殊的inode。

来自[维基百科](http://en.wikipedia.org/wiki/Inode)：

> Unix 目录是关联结构的列表，每个关联结构包含一个文件名和一个 inode 编号。

请记住，inode 不包含文件名 - 只包含其他文件元数据。

## 如何在文件系统中的两个不同位置显示相同的文件？

首先要记住文件名和文件是两个不同的概念。可以将inode视为“文件”，将目录视为名称列表，每个名称都映射到 inode 编号。其中一些 inode 可能是常规文件 inode，其他可能是目录 inode。

如果我们已经在文件系统上有文件，我们可以使用'ln'命令创建指向同一 inode 的另一个链接

```
$ ln file1.txt blip.txt 
```
然而，blip.txt与原文件是同一个文件;如果我编辑blip.txt，等价于我编辑file1.txt文件！我们可以通过查看两个文件名引用相同的inode来证明这一点：

```
$ ls -i file1.txt blip.txt
134235 file1.txt
134235 blip.txt 
```

这些类型的链接（也叫目录条目）称为“硬链接”

也可以使用C语言的`link`函数达到相同的效果

```c
link(const char *path1, const char *path2);

link("file1.txt", "blip.txt");
```

为简单起见，上述示例在同一目录中创建了硬链接，你也可以在同一文件系统内的任何位置创建硬链接。

## 当我`rm`（删除）文件时会发生什么？

删除文件（使用`rm`或`unlink`）时，将从目录中删除 inode 引用。但是，仍可以从其他目录引用 inode。为了确定是否仍然需要文件的内容，每个 inode 保持一个引用计数，只要创建或销毁新链接就会更新该引用计数。

## 学习案例：最小化的备份文件来去重

硬链接的示例使用是在不同时间点有效地创建文件系统的多个档案。归档区域具有特定文件的副本后，未来的归档可以重新使用这些归档文件，而不是创建重复文件。 Apple 的“Time Machine”软件就是这样做的。

## 我可以创建目录和常规文件的硬链接吗？
别闹！其实你并不是真的想这么做，对吧？POSIX标准说不，你不能！ln 命令将仅允许 root 执行此操作，并且仅当您提供 -d 选项时。但是，即使是root也可能无法执行此操作，因为大多数文件系统都会阻止它！

为什么？

## 为什么 POSIX 禁止目录的硬链接？
文件系统的完整性假设目录结构（不包括我们稍后将讨论的软链接）是一个非循环树，可以从根目录访问。如果允许目录链接，则强制实施或验证此约束的成本会很高。打破这些假设可能会导致文件完整性工具无法修复文件系统。递归搜索可能永远不会终止，目录可以有多个父级，但只能指单亲。总而言之，这是一个坏主意。