原文: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。它取决于特定文件系统在底层的实现方式。
从 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打开的任何文件描述符和任何分配的内存都不会被释放。这意味着最终进程将耗尽资源,open
或opendir
调用将失败。
解决方法是确保我们在每个可能的代码路径中释放资源。在上面的代码中,这意味着在return 1
之前调用closedir
。忘记释放资源是一个常见的 C 编程错误,因为C语言中没有任何机制来保障代码正常释放资源。
有两个主要问题和一个考虑因素: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。
来自维基百科:
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
或unlink
)时,将从目录中删除 inode 引用。但是,仍可以从其他目录引用 inode。为了确定是否仍然需要文件的内容,每个 inode 保持一个引用计数,只要创建或销毁新链接就会更新该引用计数。
硬链接的示例使用是在不同时间点有效地创建文件系统的多个档案。归档区域具有特定文件的副本后,未来的归档可以重新使用这些归档文件,而不是创建重复文件。 Apple 的“Time Machine”软件就是这样做的。
别闹!其实你并不是真的想这么做,对吧?POSIX标准说不,你不能!ln 命令将仅允许 root 执行此操作,并且仅当您提供 -d 选项时。但是,即使是root也可能无法执行此操作,因为大多数文件系统都会阻止它!
为什么?
文件系统的完整性假设目录结构(不包括我们稍后将讨论的软链接)是一个非循环树,可以从根目录访问。如果允许目录链接,则强制实施或验证此约束的成本会很高。打破这些假设可能会导致文件完整性工具无法修复文件系统。递归搜索可能永远不会终止,目录可以有多个父级,但只能指单亲。总而言之,这是一个坏主意。