ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 文件系统,第 2 部分:文件是 inode(其他一切只是数据...) > 原文:<https://github.com/angrave/SystemProgramming/wiki/File-System%2C-Part-2%3A-Files-are-inodes> 好主意:忘记文件名:'inode'是文件。 通常将文件名视为“实际”文件。不是!而是将 inode 视为文件。 inode 保存元信息(最后访问,所有权,大小)并指向用于保存文件内容的磁盘块。 ## 那么......我们如何实现目录? 目录只是名称到 inode 编号的映射。 POSIX 提供了一小组函数来读取每个条目的文件名和 inode 号(见下文) 让我们考虑一下它在实际文件系统中的样子。从理论上讲,目录就像实际文件一样。磁盘块将包含 _ 目录条目 _ 或 _dirents_ 。这意味着我们的磁盘块看起来像这样 | 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 我正在编辑与'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。但是,即使 root 也可能无法执行此操作,因为大多数文件系统都会阻止它! 为什么? ## 文件系统的完整性假设目录结构(我们稍后将讨论的软链接除外)是可从根目录访问的非循环树。如果允许目录链接,则强制执行或验证此约束会变得昂贵。打破这些假设可能导致文件完整性工具无法修复文件系统。递归搜索可能永远不会终止,目录可以有多个父级,但“..”只能引用单个父级。总而言之,一个坏主意。