# 文件系统,第 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 也可能无法执行此操作,因为大多数文件系统都会阻止它!
为什么?
## 文件系统的完整性假设目录结构(我们稍后将讨论的软链接除外)是可从根目录访问的非循环树。如果允许目录链接,则强制执行或验证此约束会变得昂贵。打破这些假设可能导致文件完整性工具无法修复文件系统。递归搜索可能永远不会终止,目录可以有多个父级,但“..”只能引用单个父级。总而言之,一个坏主意。
- UIUC CS241 系统编程中文讲义
- 0. 简介
- #Informal 词汇表
- #Piazza:何时以及如何寻求帮助
- 编程技巧,第 1 部分
- 系统编程短篇小说和歌曲
- 1.学习 C
- C 编程,第 1 部分:简介
- C 编程,第 2 部分:文本输入和输出
- C 编程,第 3 部分:常见问题
- C 编程,第 4 部分:字符串和结构
- C 编程,第 5 部分:调试
- C 编程,复习题
- 2.进程
- 进程,第 1 部分:简介
- 分叉,第 1 部分:简介
- 分叉,第 2 部分:Fork,Exec,等等
- 进程控制,第 1 部分:使用信号等待宏
- 进程复习题
- 3.内存和分配器
- 内存,第 1 部分:堆内存简介
- 内存,第 2 部分:实现内存分配器
- 内存,第 3 部分:粉碎堆栈示例
- 内存复习题
- 4.介绍 Pthreads
- Pthreads,第 1 部分:简介
- Pthreads,第 2 部分:实践中的用法
- Pthreads,第 3 部分:并行问题(奖金)
- Pthread 复习题
- 5.同步
- 同步,第 1 部分:互斥锁
- 同步,第 2 部分:计算信号量
- 同步,第 3 部分:使用互斥锁和信号量
- 同步,第 4 部分:临界区问题
- 同步,第 5 部分:条件变量
- 同步,第 6 部分:实现障碍
- 同步,第 7 部分:读者编写器问题
- 同步,第 8 部分:环形缓冲区示例
- 同步复习题
- 6.死锁
- 死锁,第 1 部分:资源分配图
- 死锁,第 2 部分:死锁条件
- 死锁,第 3 部分:餐饮哲学家
- 死锁复习题
- 7.进程间通信&amp;调度
- 虚拟内存,第 1 部分:虚拟内存简介
- 管道,第 1 部分:管道介绍
- 管道,第 2 部分:管道编程秘密
- 文件,第 1 部分:使用文件
- 调度,第 1 部分:调度过程
- 调度,第 2 部分:调度过程:算法
- IPC 复习题
- 8.网络
- POSIX,第 1 部分:错误处理
- 网络,第 1 部分:简介
- 网络,第 2 部分:使用 getaddrinfo
- 网络,第 3 部分:构建一个简单的 TCP 客户端
- 网络,第 4 部分:构建一个简单的 TCP 服务器
- 网络,第 5 部分:关闭端口,重用端口和其他技巧
- 网络,第 6 部分:创建 UDP 服务器
- 网络,第 7 部分:非阻塞 I O,select()和 epoll
- RPC,第 1 部分:远程过程调用简介
- 网络复习题
- 9.文件系统
- 文件系统,第 1 部分:简介
- 文件系统,第 2 部分:文件是 inode(其他一切只是数据...)
- 文件系统,第 3 部分:权限
- 文件系统,第 4 部分:使用目录
- 文件系统,第 5 部分:虚拟文件系统
- 文件系统,第 6 部分:内存映射文件和共享内存
- 文件系统,第 7 部分:可扩展且可靠的文件系统
- 文件系统,第 8 部分:从 Android 设备中删除预装的恶意软件
- 文件系统,第 9 部分:磁盘块示例
- 文件系统复习题
- 10.信号
- 过程控制,第 1 部分:使用信号等待宏
- 信号,第 2 部分:待处理的信号和信号掩码
- 信号,第 3 部分:提高信号
- 信号,第 4 部分:信号
- 信号复习题
- 考试练习题
- 考试主题
- C 编程:复习题
- 多线程编程:复习题
- 同步概念:复习题
- 记忆:复习题
- 管道:复习题
- 文件系统:复习题
- 网络:复习题
- 信号:复习题
- 系统编程笑话