Linux/Unix 中文件有什么特征?
一个文件可以有多种类型:常规文件、目录、符号链接,设备文件,插座,管道,先进先出,还有更多我想念的。例如,符号链接:
$ sudo file /proc/22277/fd/23
/proc/22277/fd/23: broken symbolic link to socket:[7540288]
一个插座:
$ sudo ls -l /run/user/1001/systemd/notify
srwxrwxr-x 1 testme testme 0 Feb 6 16:41 /run/user/1001/systemd/notify
是一个具有以下特征的文件索引节点(某些文件系统中的索引节点,无论是在内存中还是在辅助存储设备中?)?所有文件类型的文件都有 inode 吗? (我想这两个问题都是肯定的。)
Linux的Internet域套接字、传输协议(TCP/UDP)的套接字和端口似乎说带有打开文件描述的东西是一个文件。具有打开文件描述的内容是否一定有索引节点?
打开文件描述是一个比文件更好的术语,您无法定义“文件”。网络套接字和Unix域套接字都是开放文件描述。 UDS 可能会关联磁盘上的某些内容(有很多条件会影响这一点)。 NS 从不关联磁盘上的任何内容。
谢谢。
答案1
长话短说
- 文件是一个对象,您可以在其上执行部分或全部基本操作(打开、读取、写入、关闭),并且将元数据存储在索引节点中。
- 文件描述符是对这些对象的引用
- 打开文件描述(是的,打开部分很重要)是如何文件(由至少一个文件描述符表示)已打开
文件作为抽象
我们来咨询一下POSIX 定义 2017,第 3.164 节至于File是如何定义的:
可以写入、读取或两者兼而有之的对象。文件具有某些属性,包括访问权限和类型。文件类型包括常规文件、字符特殊文件、块特殊文件、FIFO特殊文件、符号链接、套接字和目录。该实现可能支持其他类型的文件。
因此,文件是我们可以读取、写入或两者兼而有之的任何东西,它也包含元数据。大家——回家吧,案子结了!
嗯,没那么快。这样的定义为相关概念开辟了很大的空间,并且常规文件和管道之间存在明显的差异。 “一切皆文件”本身就是一个概念设计模式而不是字面上的陈述。基于该模式,目录、管道、设备文件、内存文件、套接字等文件类型 - 所有这些都可以通过一组系统调用进行操作,例如open()
、openat()
、write()
、 对于套接字recv()
和send()
,以一致的方式;以 USB 为例 - 您有很多不同的设备,但它们都连接到完全相同的 USB 端口(没关系,实际上有多种类型的 USB 端口类型,从 A 到 C,但您明白了)。
当然,必须有某种接口或以一致的方式引用实际数据才能发挥作用,那就是文件描述符:
每个进程唯一的非负整数,用于标识打开的文件以进行文件访问。新创建的文件描述符的值是从零到{OPEN_MAX}-1。
因此,我们可以write()
通过文件描述符 1 以与写入常规文件相同的方式访问 STDOUT /home/user/foobar.txt
。当您open()
访问文件时,您会获得文件描述符,并且可以使用相同的write()
函数写入该文件。这就是 Unix 最初的创建者试图解决的重点——极简和一致的行为。当您执行此操作时,command > /home/user/foobar.txt
shell 将复制引用的文件描述符foobar.txt
并将其作为文件描述符 1 ( echo 的 STDOUT )传递command
,或者更准确地说,它将执行命令dup2(3,1)
,然后execve()
执行命令。但无论如何,command
仍然会使用相同的写入系统调用到文件描述符 1 中,就好像什么也没发生一样。
当然,就大多数用户所认为的文件而言,他们认为是磁盘文件系统上的常规文件。这更符合常规文件定义,第 3.323 节:
一种文件,它是可随机访问的字节序列,没有系统强加的进一步结构。
相比之下,我们有插座:
用作进程间通信的通信端点的特定类型的文件,如 POSIX.1-2017 的系统接口卷中所述。
无论哪种类型,我们可以对不同文件类型执行的操作在概念上完全相同 - 打开、读取、写入、关闭。
所有文件都有索引节点
您应该在文件定义中注意到的是文件具有“某些属性”,这些属性存储在 inode 中。其实具体在Linux上,我们可以参考索引节点(7) 手册第一行:
每个文件都有一个包含该文件元数据的索引节点。应用程序可以使用 stat(2) (或相关调用)检索此元数据
繁荣。清晰、直接。我们最熟悉的索引节点是磁盘上的数据块和目录中存储的文件名之间的桥梁(因为目录就是文件名和相应索引节点的列表)。即使在虚拟文件系统例如内核中的pipefs和sockfs,我们可以找到inode。以这段代码片段为例:
static char *pipefs_dname(struct dentry *dent, char *buffer, int buflen)
{
return dynamic_dname(dentry, buffer, buflen, "pipe:[%lu]",
dentry->d_inode->i_ino);
}
打开文件说明
现在您已经完全困惑了,Linux/Unix 引入了称为“打开文件描述”的东西,并且为了使解释简单 - 这是另一个抽象。用斯蒂芬·查泽拉斯的话来说,
它更多的是关于文件如何打开的记录,而不是文件本身。
并且它符合POSIX 定义:
一个进程或一组进程如何访问文件的记录。每个文件描述符恰好引用一个打开的文件描述,但一个打开的文件描述可以由多个文件描述符引用。文件偏移、文件状态和文件访问模式是打开文件描述的属性。
现在如果我们也看看了解 Linux 内核书上,作者指出
Linux 将 BSD 套接字实现为属于袜子文件系统特殊的文件系统...更准确地说,对于每个新的 BSD 套接字,内核都会在袜子文件系统特殊的文件系统。
记住这一点套接字也由文件描述符引用因此内核中会有与套接字相关的打开文件描述,我们可以断定套接字是文件。
待续 。 。 。或许
答案2
什么是文件?
Linux 中的文件基本上只是一个可以交互的东西。总共有 7 种文件类型:
- 插座,
- 符号链接,
- 常规文件,
- 块设备,
- 目录,
- 字符设备,
- FIFO(又名管道)。
由于我们根据上下文以不同的方式谈论文件,因此出现了很多混乱。对于这个讨论,我们有两个不同的上下文:
- 磁盘上表示的文件(在文件系统中)
- Linux 中表示的文件(“内存中”)
在Linux中(在内存中)
在linux中(在内存中)每个文件都有(或者是?)一个索引节点。它需要一个,因为它是告诉 linux 这个文件是什么的 inode。为了将 inode 链接回有意义的内容(例如磁盘上的文件),inode 存储 3 条关键信息:
- 设备 ID - 引用负责该文件的文件系统或驱动程序
- inode 编号 - 由文件系统或驱动程序指定的唯一编号。如果两个 inode 具有不同的设备 ID,则它们可以具有相同的 inode 编号
- 类型 - 告诉 linux 这个文件实际上是什么。往上看。
与文件交互的方式取决于文件的类型。例如,您可以列出目录,但不能列出块设备。您可以连接到套接字,但无法连接到常规文件。
在磁盘上
不同的文件系统有很大不同。像 ext4 这样的文件系统是为 unix 和 linux 编写的,反映了 inode 的概念。所以内存中的索引节点几乎只是从磁盘上的索引节点读取。
但它们是不同的。例如,磁盘上的索引节点没有或不需要设备 ID。 Linux内存上的inode(内存中)确实需要记录文件数据在磁盘上的存储位置。 Linux 中的索引节点依靠驱动程序来解决这个问题。
磁盘上的索引节点号通常被驱动程序用作 Linux 中的索引节点号。因此磁盘上的 inode 经常被误认为与内存中的 inode 相同。
我们如何引用文件(索引节点)?
文件名
文件名是引用(查找)文件的最熟悉的方式。文件系统存储文件名树,Linux 使用mount
.树中的每个名称仅指向一个索引节点。
Linux 中的文件可以有多个文件名。只有当文件系统也支持它时,这才有可能。无论是在 Linux 中还是在磁盘上,多个文件名(硬链接)都是通过让多个名称指向同一 inode 来实现的。
删除文件只是删除其文件名。只有当所有文件名都被删除并且所有“文件描述符”关闭时,才能回收实际占用的空间。
因此,对于常规文件(在磁盘上),有三件事:文件名 --> inode --> 数据
文件描述符
当程序打开文件时,它会将文件名交换为文件描述符(数字)。这是指向没有名称或路径的 inode 的另一种链接。文件上的所有操作(例如“读”和“写”)都使用文件描述符而不是文件名。
文件描述符不必通过 open() 获取。一旦你有了一个文件描述符,它就可以被子进程继承(复制),甚至复制到一个完全不同的进程(通过unix域套接字)。
我认为这引起了一些混乱,因为OP引用的评论中使用了“文件描述”一词。我相信这些评论试图说明文件描述符不仅仅是一个数字。但他们以一种令人困惑的方式表达了这一点。
没有文件名或文件系统的文件
这个模型有一些怪癖。首先,如果您打开一个文件然后删除它(而不关闭该文件),则打开的文件描述符会阻止磁盘上的文件被回收。这会产生一个没有文件名的文件。
更奇怪的是,有些文件从来不属于文件系统。一个程序可以创建一个无名管道或者未命名的斯科科特。它们在 Linux 上有一个 inode,但永远不会直接附加到文件系统,因为它们只作为 Linux 内核中的一个东西存在。这些仍然是文件(尽管很奇怪)...它们具有引用 inode 的文件描述符。
未命名管道的一个常见示例是命令行程序的 STDIN、STDOUT。当您通过管道连接两个程序时 ( foo | bar
),它们之间的管道将是无名管道。
结束语
通常,我们将所有这些概念混为一谈,只用“文件”一词。通常,您可以“写入文件”,而不必关心这涉及到单独的文件名、将被转换为磁盘上的文件名的索引节点以及磁盘上的索引节点,并最终将内容写入磁盘。 “写入文件”一词意味着所有这些。
只有在特殊情况下,您才需要开始分离这些概念。
答案3
1) 在 Unix 上的大多数文件系统上,文件、fifo、目录等由 inode 描述。一个 inode 有许多字段,但本例中最有趣的是 i_mode 字段。在权限旁边,它包含 inode 指向的“文件”类型:
Constant Value Description
-- file format --
EXT2_S_IFSOCK 0xC000 socket
EXT2_S_IFLNK 0xA000 symbolic link
EXT2_S_IFREG 0x8000 regular file
EXT2_S_IFBLK 0x6000 block device
EXT2_S_IFDIR 0x4000 directory
EXT2_S_IFCHR 0x2000 character device
EXT2_S_IFIFO 0x1000 fifo
2)这取决于你如何看待它。对于每个打开的文件,无论它是“真实”文件还是其他构造(例如未命名管道),您都可以通过系统调用获取索引节点。但是当文件句柄关闭时,该索引节点将不可用。 (编辑第 2 节以消除事实不正确之处)