初学者经常会听到一句话“在 Linux/Unix 上一切都是文件”。那么目录是什么呢?它们与文件有何不同?
答案1
注意:最初这是为了支持我的回答而写的为什么命令中的当前目录ls
被标识为与自身链接?但我觉得这个话题值得单独提出来,所以才有这个问答。
理解 Unix/Linux 文件系统和文件:一切都是 inode
本质上,目录只是一个特殊的文件,其中包含条目列表及其 ID。
在开始讨论之前,区分几个术语并了解目录和文件真正代表什么很重要。您可能听说过 Unix/Linux 的“一切都是文件”这一说法。好吧,用户通常理解的文件是这样的:/etc/passwd
- 具有路径和名称的对象。实际上,名称(无论是目录还是文件,还是其他任何名称)只是一串文本 - 实际对象的属性。该对象称为索引节点或 I 编号,并存储在磁盘上的 inode 表中。开放程序也有 inode 表,但这不是我们现在关心的问题。
Unix 的目录概念就像 Ken Thompson 在1989 年采访:
...然后其中一些文件是仅包含名称和 I 编号的目录。
一个有趣的观察可以从丹尼斯·里奇 1972 年的演讲那
“...目录实际上只不过是一个文件,但其内容由系统控制,并且内容是其他文件的名称。(在其他系统中,目录有时被称为目录。)”
...但谈话中没有任何地方提到 inode。然而,1971 年手册关于format of directories
国家:
文件是否为目录,由其 i—节点条目的标志字中的一位表示。
目录条目长度为 10 个字节。第一个字为条目所代表的文件的 i 节点(如果非零);如果为零,则条目为空。
所以它从一开始就在那里。
目录和 inode 配对也解释于目录结构如何存储在 UNIX 文件系统中?目录本身是一种数据结构,更具体地说:指向有关这些对象(权限、类型、所有者、大小等)的列表的对象列表(文件和 inode 编号)。因此,每个目录都包含自己的 inode 编号,然后是文件名及其 inode 编号。最著名的是inode #2 是/
目录。(请注意,虽然/dev
和/run
是虚拟文件系统,因此由于它们是文件系统的根文件夹,它们将拥有适合各自文件系统中根目录的 inode 编号;即,一个 inode 在其自己的文件系统上是唯一的,但如果连接了多个文件系统,则 inode 就不唯一)。从链接问题借用的图表可能更简洁地解释了这一点:
stat()
根据 Linux,可以通过系统调用访问存储在 inode 中的所有信息man 7 inode
:
每个文件都有一个包含文件元数据的 inode。应用程序可以使用 stat(2)(或相关调用)检索此元数据,该调用返回一个 stat 结构,或使用 statx(2)(返回一个 statx 结构)。
是否可以仅知道文件的 inode 号来访问文件(参考文献1,参考文献2)?在某些 Unix 实现中这是可能的,但它会绕过权限和访问检查,因此在 Linux 上它没有实现,您必须遍历文件系统树(find <DIR> -inum 1234
例如通过)来获取文件名及其对应的 inode。
在源代码级别,它定义在Linux 内核源代码并且也被许多在 Unix/Linux 操作系统上运行的文件系统所采用,包括 ext3 和 ext4 文件系统(Ubuntu 默认)。有趣的是:由于数据只是信息块,Linux 实际上有inode_init_always 函数可以确定 inode 是否为管道 ( inode->i_pipe
)。是的,套接字和管道从技术上讲也是文件 - 匿名文件,可能在磁盘上没有文件名。先进先出和Unix 域套接字文件系统上确实有文件名。
数据本身可能是唯一的,但 inode 编号却不是唯一的。如果我们有一个名为 foobar 的指向 foo 的硬链接,它也会指向 inode 123。这个 inode 本身包含有关该 inode 实际占用的磁盘空间块的信息。从技术上讲,这就是您可以将其.
链接到目录文件名的方式。好吧,差不多:你不能自己在 Linux 上创建目录的硬链接.
但是文件系统可以以非常规范的方式允许到目录的硬链接,这使得只能具有和..
作为硬链接的限制。
目录树
文件系统将目录树实现为树形数据结构之一。具体来说,
- ext3 和 ext4 使用 HTree
- xfs 使用 B+ 树
- zfs 使用哈希树
这里的关键点是目录本身是树中的节点,而子目录是子节点,每个子节点都有一个指向父节点的链接。因此,对于目录链接,裸目录(链接到目录名称/home/example/
并链接到自身/home/example/.
)的 inode 计数至少为 2,并且每个附加子目录都是一个额外的链接/节点:
# new directory has link count of 2
$ stat --format=%h .
2
# Adding subdirectories increases link count
$ mkdir subdir1
$ stat --format=%h .
3
$ mkdir subdir2
$ stat --format=%h .
4
# Count of links for root
$ stat --format=%h /
25
# Count of subdirectories, minus .
$ find / -maxdepth 1 -type d | wc -l
24
图表发现于Ian D. Allen 的课程页面显示一个简化的非常清晰的图表:
WRONG - names on things RIGHT - names above things
======================= ==========================
R O O T ---> [etc,bin,home] <-- ROOT directory
/ | \ / | \
etc bin home ---> [passwd] [ls,rm] [abcd0001]
| / \ \ | / \ |
| ls rm abcd0001 ---> | <data> <data> [.bashrc]
| | | |
passwd .bashrc ---> <data> <data>
右图中唯一不正确的是,文件在技术上不被视为位于目录树本身上:添加文件对链接数没有影响:
$ mkdir subdir2
$ stat --format=%h .
4
# Adding files doesn't make difference
$ cp /etc/passwd passwd.copy
$ stat --format=%h .
4
像访问文件一样访问目录
去引用Linus Torvalds:
“一切皆文件”的要点不在于你拥有一些随机的文件名(事实上,套接字和管道表明“文件”和“文件名”彼此无关),而在于你可以使用常用工具来操作不同的东西。
考虑到目录只是文件的一个特例,自然需要有 API 允许我们打开/读/写/关闭以与常规文件类似的方式处理它们。
这就是dirent.h
C 库发挥作用的地方,它定义了dirent
结构,你可以在man 3 readdir:
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};
因此,在您的 C 代码中,您必须定义struct dirent *entry_p
,当我们使用 打开目录opendir()
并开始使用 读取它时readdir()
,我们会将每个项目存储到该entry_p
结构中。当然,每个项目都将包含dirent
上面显示的模板中定义的字段。
在我的回答中可以找到有关如何工作的实际示例如何列出当前工作目录中的文件及其 inode 编号。
请注意fdopen 上的 POSIX 手册指出“点和点-点的目录条目是可选的”,并且readdir 手册说明 struct dirent
仅要求有d_name
和d_ino
字段。
关于“写入”目录的注意事项:写入目录就是修改其条目“列表”。因此,创建或删除文件与目录写权限,而添加/删除文件是对该目录的写操作。