如果Linux上的所有内容都是文件,那么目录是什么?
初学者经常会听到一句话“一切都是Linux / Unix上的文件”。 但是,目录是什么? 它们与文件有什么不同?
注意:最初这是为了支持我的答案而编写的。 为什么ls
命令中的当前目录被标识为链接到自身? 但我觉得这是一个值得独立的话题,因此这个Q&A 。
了解Unix / Linux文件系统和文件:一切都是inode
本质上,目录只是一个特殊文件,其中包含条目列表及其ID。
在我们开始讨论之前,重要的是要区分几个术语并理解哪些目录和文件真正代表什么。 您可能已经听过Unix / Linux中的“Everything is a file”一词。 那么,用户经常理解的文件是: /etc/passwd
– 具有路径和名称的对象。 实际上,名称(无论是目录或文件,还是其他任何名称)只是一个文本字符串 – 实际对象的属性。 该对象称为inode或I-number,并存储在inode表的磁盘上。 开放程序也有inode表,但这不是我们现在关注的问题。
Unix的目录概念就像Ken Thompson在1989年的采访中所说:
…然后其中一些文件是只包含名称和I号的目录。
可以从Dennis Ritchie在1972年的演讲中得到一个有趣的观察结果
“…目录实际上只是一个文件,但其内容由系统控制,内容是其他文件的名称。(一个目录有时被称为其他系统中的目录。)”
…但谈话中没有任何地方提到过inode。 但是, 1971年关于format of directories
手册指出:
文件是目录的事实由其i节点条目的标志字中的位指示。
目录条目长度为10个字节。 第一个字是由条目表示的文件的i节点,如果非零; 如果为零,则条目为空。
所以它从一开始就存在。
目录和inode配对也在UNIX文件系统中如何存储目录结构中进行了解释? 。 目录本身是一种数据结构,更具体地说:指向有关这些对象的列表(权限,类型,所有者,大小等)的对象列表(文件和inode编号)。 因此每个目录都包含自己的inode编号,然后是文件名及其inode编号。 最着名的是inode#2,即/
directory 。 (注意,尽管/dev
和/run
是虚拟文件系统,因此它们是文件系统的根文件夹, 它们也有inode 2 ;即inode在其自己的文件系统上是唯一的,但是附加了多个文件系统,你有非独特的inode)。 借用链接问题的图表可能更简洁地解释:
根据Linux man 7 inode
,可以通过stat()
系统调用访问存储在inode中的所有信息:
每个文件都有一个包含有关该文件的元数据的inode。 应用程序可以使用stat(2)(或相关调用)来检索此元数据,stat(2)返回stat结构,或statx(2),它返回statx结构。
知道其inode编号( ref1 , ref2 )是否可以访问文件? 在一些Unix实现上它是可能的,但它绕过权限和访问检查,所以在Linux上它没有实现,你必须遍历文件系统树(例如通过find
)来获取文件名及其相应的inode 。
在源代码级别,它在Linux内核源代码中定义,并且也被许多在Unix / Linux操作系统上运行的文件系统采用,包括ext3和ext4文件系统(默认为Ubuntu)。 有趣的是:由于数据只是信息块,Linux实际上有inode_init_always函数 ,可以确定inode是否为管道( inode->i_pipe
)。 是的,套接字和管道在技术上也是文件 – 匿名文件,它们可能没有磁盘上的文件名。 FIFO和Unix-Domain套接字在文件系统上都有文件名。
数据本身可能是唯一的,但inode数字并不是唯一的。 如果我们有一个名为foobar的foo的硬链接,那么它也将指向inode 123。 此inode本身包含有关该inode占用的实际磁盘空间块的信息。 从技术上讲,这是你可以拥有的.
链接到目录文件名。 好吧,差不多: 你不能自己在Linux上创建目录的硬链接,但是文件系统可以允许以非常规范的方式链接到目录,这仅限于使用.
和..
作为硬链接。
目录树
文件系统将目录树实现为树数据结构之一。 特别是,
- ext3和ext4使用HTree
- xfs使用B + Tree
- zfs使用哈希树
这里的关键点是目录本身是树中的节点,子目录是子节点,每个子节点都有一个返回父节点的链接。 因此,对于目录链接,对于裸目录(链接到父..
并链接到self),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 ======================= ========================== ROOT ---> [etc,bin,home] <-- ROOT directory / | \ / | \ etc bin home ---> [passwd] [ls,rm] [abcd0001] | / \ \ | / \ | | ls rm abcd0001 ---> | [.bashrc] | | | | passwd .bashrc --->
右图中唯一不正确的是文件在技术上不被认为是在目录树本身上:添加文件对链接数没有影响:
$ mkdir subdir2 $ stat --format=%h . 4 # Adding files doesn't make difference $ cp /etc/passwd passwd.copy $ stat --format=%h . 4
像访问目录一样访问目录
考虑到目录只是文件的特例,自然必须有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手册指出“[t] dot和dot-dot的目录条目是可选的”,而readdir手册说明 struct dirent
只需要有d_name
和d_ino
字段。
关于“写入”目录的注意事项:写入目录正在修改其条目的“列表”。 因此,创建或删除文件直接与目录写入权限相关联,并且添加/删除文件是对所述目录的写入操作。