Skip to content

Files

Latest commit

1153698 · Aug 15, 2022

History

History
234 lines (168 loc) · 9.4 KB

66.md

File metadata and controls

234 lines (168 loc) · 9.4 KB

文件系统,第 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'文件的时间

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

实际上有三个版本的stat;

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

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

   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编号,文件长度和所有者等信息。

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'版本来列出目录的内容。

#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'实现来列出目录的内容。

#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',可以使用以下代码。 (提示:你能发现这个错误吗?)

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打开的任何文件描述符和任何分配的内存都不会被释放。这意味着最终进程将耗尽资源,openopendir调用将失败。

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

使用 readdir 有什么问题?例如,递归搜索目录?

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

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

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

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

如何确定目录条目是否是目录?

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

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

   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。

来自维基百科

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函数达到相同的效果

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

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

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

当我rm(删除)文件时会发生什么?

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

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

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

我可以创建目录和常规文件的硬链接吗?

别闹!其实你并不是真的想这么做,对吧?POSIX标准说不,你不能!ln 命令将仅允许 root 执行此操作,并且仅当您提供 -d 选项时。但是,即使是root也可能无法执行此操作,因为大多数文件系统都会阻止它!

为什么?

为什么 POSIX 禁止目录的硬链接?

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