C 打开文件实际上是做什么的?

C 打开文件实际上是做什么的?,c,linux,C,Linux,在所有编程语言中(至少我使用过),必须先打开一个文件,然后才能读取或写入它 但是,这种开放式操作实际上是做什么的呢 典型功能的手册页实际上并没有告诉您除“打开文件进行读/写”之外的任何内容: 显然,通过使用该函数,您可以看出它涉及到创建某种对象,以便于访问文件 另一种说法是,如果我要实现一个open函数,它在Linux上需要做什么?在几乎所有高级语言中,打开文件的函数都是相应内核系统调用的包装器。它还可以做其他一些奇特的事情,但在现代操作系统中,打开文件必须始终经过内核 这就是为什么fope

在所有编程语言中(至少我使用过),必须先打开一个文件,然后才能读取或写入它

但是,这种开放式操作实际上是做什么的呢

典型功能的手册页实际上并没有告诉您除“打开文件进行读/写”之外的任何内容:

显然,通过使用该函数,您可以看出它涉及到创建某种对象,以便于访问文件


另一种说法是,如果我要实现一个
open
函数,它在Linux上需要做什么?

在几乎所有高级语言中,打开文件的函数都是相应内核系统调用的包装器。它还可以做其他一些奇特的事情,但在现代操作系统中,打开文件必须始终经过内核

这就是为什么
fopen
库函数或Python的
open
的参数与
open(2)
系统调用的参数非常相似

除了打开文件外,这些函数通常还设置一个缓冲区,以便在读/写操作中使用。此缓冲区的目的是确保每当您想要读取N个字节时,相应的库调用将返回N个字节,而不管对底层系统调用的调用是否返回较少的字节

实际上,我对实现自己的功能并不感兴趣;只要了解到底发生了什么…“超越语言”,如果你愿意的话

在类Unix操作系统中,成功调用
open
将返回一个“文件描述符”,在用户进程的上下文中它只是一个整数。因此,该描述符被传递给与打开的文件交互的任何调用,在对其调用
close
后,该描述符变为无效

需要注意的是,对
open
的调用就像一个进行各种检查的验证点。如果未满足所有条件,则调用将返回
-1
而不是描述符,从而失败,错误类型在
errno
中指示。基本检查包括:

  • 文件是否存在
  • 调用进程是否有特权以指定模式打开此文件。这是通过将文件权限、所有者ID和组ID与调用进程的相应ID相匹配来确定的

在内核上下文中,进程的文件描述符和物理打开的文件之间必须存在某种映射。映射到描述符的内部数据结构可能包含另一个处理基于块的设备的缓冲区,或者一个指向当前读/写位置的内部指针。

这取决于操作系统在打开文件时的具体情况。下面我将描述Linux中发生的事情,因为它让您了解打开文件时会发生什么,如果您对更多细节感兴趣,可以查看源代码。我不涉及权限,因为这会使这个答案太长


在Linux中,每个文件都由一个名为的结构识别。每个结构都有一个唯一的编号,每个文件只获得一个索引节点编号。此结构存储文件的元数据,例如文件大小、文件权限、时间戳和指向磁盘块的指针,但不存储实际文件名本身。每个文件(和目录)都包含一个文件名条目和用于查找的索引节点号。打开文件时,假设您具有相关权限,将使用与文件名关联的唯一inode编号创建文件描述符。由于许多进程/应用程序可以指向同一个文件,inode有一个link字段,用于维护指向该文件的链接总数。如果目录中存在文件,则其链接计数为1,如果有硬链接,则其链接计数为2,如果文件由进程打开,则链接计数将增加1

最核心的是,当打开阅读时,实际上不需要任何幻想。它需要做的只是检查文件是否存在,应用程序是否有足够的权限读取文件,并创建一个句柄,您可以在该句柄上向文件发出读取命令

实际读取将通过这些命令进行调度

操作系统通常会通过启动读取操作来填充与句柄相关联的缓冲区,从而在读取时获得领先优势。然后,当您实际执行读取时,它可以立即返回缓冲区的内容,而无需等待磁盘IO

要打开一个新文件进行写操作,操作系统需要在目录中为新文件(当前为空)添加一个条目。再次创建了一个句柄,您可以在其上发出写入命令。

您想要谈论的任何文件系统或操作系统都可以。很好


在ZX频谱上,初始化
加载
命令将使系统进入一个紧密循环,在线读取音频

数据的开始以恒定的音调表示,然后是一系列长/短脉冲,其中短脉冲用于二进制
0
,长脉冲用于二进制
1
()。紧加载循环收集位,直到填满一个字节(8位),将其存储到内存中,增加内存指针,然后循环回扫描更多位

通常,加载程序首先读取的是一个简短的固定格式头,至少指示预期的字节数,以及可能的附加信息,如文件名、文件类型和加载地址。读取此短头后,程序可以决定是继续加载主要数据块,还是退出加载例程并为用户显示适当的消息

通过接收与预期相同数量的字节(软件中硬连线的固定字节数或h中指示的可变数字),可以识别文件结束状态
0  int sys_open(const char *filename, int flags, int mode) {
1      char *tmp = getname(filename);
2      int fd = get_unused_fd();
3      struct file *f = filp_open(tmp, flags, mode);
4      fd_install(fd, f);
5      putname(tmp);
6      return fd;
7  }
struct file *filp_open(const char *filename, int flags, int mode) {
        struct nameidata nd;
        open_namei(filename, flags, mode, &nd);
        return dentry_open(nd.dentry, nd.mnt, flags);
}