在所有编程语言(我至少使用过)中,必须先打开文件才能读取或写入文件.
但这种开放式操作实际上做了什么?
典型功能的手册页实际上并没有告诉你除了"打开读/写文件"以外的任何内容:
http://www.cplusplus.com/reference/cstdio/fopen/
https://docs.python.org/3/library/functions.html#open
显然,通过使用该函数,您可以告诉它涉及创建某种有助于访问文件的对象.
另外一种方法是,如果我要实现一个open
函数,它需要在Linux上做什么?
Bla*_*iev 181
在几乎每种高级语言中,打开文件的函数都是相应内核系统调用的包装器.它也可以做其他奇特的东西,但在现代操作系统中,打开文件必须始终通过内核.
这就是为什么fopen
库函数的参数或Python open
与open(2)
系统调用的参数非常相似的原因.
除了打开文件之外,这些函数通常还会设置一个缓冲区,该缓冲区将与读/写操作一起使用.此缓冲区的目的是确保无论何时要读取N个字节,相应的库调用都将返回N个字节,无论对底层系统调用的调用是否返回较少.
我实际上并不想实现自己的功能; 只是在了解到底是怎么回事......如果你愿意的话,"超越语言".
在类Unix操作系统中,成功调用open
返回"文件描述符",它只是用户进程上下文中的整数.因此,该描述符被传递给与打开的文件交互的任何调用,并且在调用close
它之后,描述符变为无效.
重要的是要注意,调用open
行为类似于进行各种检查的验证点.如果不满足所有条件,则通过返回-1
而不是描述符来调用失败,并指示错误类型errno
.必要的检查是:
在内核的上下文中,进程的文件描述符和物理打开的文件之间必须存在某种映射.映射到描述符的内部数据结构可能包含另一个处理基于块的设备的缓冲区,或者指向当前读/写位置的内部指针.
Dav*_*d Z 82
我建议您通过简化版open()
系统调用来查看本指南.它使用以下代码片段,它代表打开文件时幕后发生的情况.
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 }
Run Code Online (Sandbox Code Playgroud)
简而言之,这是代码的作用,逐行:
该filp_open
功能有实现
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);
}
Run Code Online (Sandbox Code Playgroud)
这做了两件事:
struct file
有关inode的基本信息并返回它.这个结构成为我之前提到的打开文件列表中的条目.将返回的结构存储("安装")到进程的打开文件列表中.
read()
,write()
和close()
.其中的每一个都将控制交给内核,内核可以使用文件描述符在进程列表中查找相应的文件指针,并使用该文件指针中的信息实际执行读,写或关闭.如果您有野心,可以将这个简化示例open()
与Linux内核中的系统调用实现进行比较,这是一个名为的函数do_sys_open()
.找到相似之处你不应该有任何困难.
当然,这只是你调用时发生的事情的"顶层" open()
- 或者更准确地说,它是在打开文件的过程中调用的最高级别的内核代码.高级编程语言可能会在此基础上添加其他层.在较低级别上有很多事情发生.(感谢Ruslan和pjc50的解释.)粗略地,从上到下:
open_namei()
并dentry_open()
调用文件系统代码(也是内核的一部分)来访问文件和目录的元数据和内容.该文件系统从磁盘读取原始字节和解释这些字节模式作为文件和目录树./dev/sda
.)由于缓存,这也可能有些不正确.:-P严重的是,我遗漏了许多细节 - 一个人(不是我)可以写多本书来描述整个过程是如何运作的.但这应该会给你一个想法.
usr*_*301 68
您想要谈论的任何文件系统或操作系统都很好.太好了!
在ZX Spectrum上,初始化LOAD
命令将使系统进入紧密循环,读取音频输入行.
数据开始由恒定音调表示,然后是一系列长/短脉冲,其中短脉冲用于二进制,0
而较长脉冲用于二进制1
(https://en.wikipedia.org/ wiki/ZX_Spectrum_software).紧密加载循环收集位直到它填充一个字节(8位),将其存储到内存中,增加内存指针,然后循环返回扫描更多位.
通常,加载程序首先读取的是一个简短的固定格式标头,至少表示预期的字节数,以及可能的附加信息,如文件名,文件类型和加载地址.在读取该短标题之后,程序可以决定是继续加载主要数据,还是退出加载例程并为用户显示适当的消息.
可以通过接收与预期一样多的字节来识别文件结束状态(固定数量的字节,软件中的硬连线或诸如标题中指示的可变数字).如果加载循环在预期的频率范围内没有接收到一定时间的脉冲,则抛出错误.
关于这个答案的一点背景
所描述的过程从常规音频磁带加载数据 - 因此需要扫描音频输入(它与标准插头连接到磁带录音机).一个LOAD
命令在技术上是相同的open
文件-但它本质上的联系,以实际加载文件.这是因为录音机不受计算机控制,您无法(成功)打开文件但不能加载它.
提到"紧密循环"是因为(1)CPU,Z80-A(如果存储器服务),真的很慢:3.5 MHz,以及(2)Spectrum没有内部时钟!这意味着它必须准确地保持每个T状态的计数(指令时间).单.指令.在该循环内,只是为了保持准确的蜂鸣声时间.
幸运的是,低CPU速度具有明显的优势,您可以计算一张纸上的循环次数,从而计算它们将采用的实际时间.
Ale*_*lex 17
这取决于操作系统打开文件时到底发生了什么.下面我将介绍Linux中发生的情况,因为它可以让您了解打开文件时会发生什么,如果您对更多细节感兴趣,可以查看源代码.我没有覆盖权限,因为它会使这个答案太长.
在Linux中,每个文件都被称为inode的结构识别.每个结构都有一个唯一的编号,每个文件只有一个inode编号.此结构存储文件的元数据,例如文件大小,文件权限,时间戳和指向磁盘块的指针,但不是实际的文件名本身.每个文件(和目录)都包含一个文件名条目和用于查找的inode编号.当您打开文件时,假设您具有相关权限,则使用与文件名关联的唯一inode编号创建文件描述符.由于许多进程/应用程序可以指向同一个文件,因此inode有一个链接字段,用于维护指向该文件的链接总数.如果目录中存在文件,则其链接计数为1,如果它具有硬链接,则其链接计数为2,如果进程打开文件,则链接计数将增加1.
Lua*_*aan 11
记账,主要是.这包括各种检查,如"文件是否存在?" 和"我是否有权打开此文件进行写入?".
但这就是所有内核的东西 - 除非你实现自己的玩具操作系统,否则没有太多需要深入研究(如果你有,那就玩得开心 - 这是一次很棒的学习经历).当然,您仍然应该学习打开文件时可以收到的所有可能的错误代码,以便您可以正确处理它们 - 但这些通常都是很好的小抽象.
代码级别中最重要的部分是它为您提供了打开文件的句柄,您可以将其用于对文件执行的所有其他操作.难道你不能使用文件名而不是这个任意句柄?嗯,当然 - 但使用手柄可以带来一些好处:
read
从文件中的最后一个位置开始.通过使用句柄来标识文件的特定"开头",您可以将多个并发句柄放在同一个文件中,每个句柄都从它们自己的位置读取.在某种程度上,句柄充当文件的可移动窗口(以及发出异步I/O请求的方式,非常方便).您还可以执行其他一些操作(例如,在进程之间共享句柄以使用通信通道而不使用物理文件;在unix系统上,文件也用于设备和各种其他虚拟通道,因此这不是必需的),但它们并没有真正与open
操作本身联系在一起,所以我不打算深入研究它.
在开放阅读时,它的核心实际上不需要花哨.它需要做的就是检查文件是否存在,并且应用程序具有足够的权限来读取它并创建一个句柄,您可以在该句柄上向文件发出读取命令.
这些命令就是实际读数将被分派.
操作系统通常会通过启动读取操作来填充与句柄相关联的缓冲区,从而获得先行读取.然后,当您实际执行读取时,它可以立即返回缓冲区的内容,而不是需要等待磁盘IO.
要打开一个新文件进行写入,操作系统需要在目录中为新(当前为空)文件添加一个条目.然后再创建一个句柄,您可以在其上发出写入命令.
基本上,对open的调用需要找到文件,然后记录它需要的任何内容,以便以后的I/O操作可以再次找到它.这很模糊,但在我能立即想到的所有操作系统上都是如此.具体细节因平台而异.这里已经有很多答案谈论现代桌面操作系统.我已经对CP/M进行了一些编程,所以我将提供关于它如何在CP/M上工作的知识(MS-DOS可能以相同的方式工作,但出于安全原因,它通常不会像今天这样完成).
在CP/M上你有一个叫做FCB的东西(正如你提到的C,你可以把它称为结构;它实际上是一个包含各种字段的RAM中的35字节连续区域).FCB具有用于写入文件名的字段和用于标识磁盘驱动器的(4位)整数.然后,当您调用内核的Open File时,通过将其放在CPU的一个寄存器中来传递指向此结构的指针.一段时间后,操作系统返回结构略有变化.无论您对此文件执行何种I/O操作,都会将指向此结构的指针传递给系统调用.
CP/M对这个FCB做了什么?它会保留某些字段供自己使用,并使用它们来跟踪文件,因此最好不要在程序中触摸它们.打开文件操作在磁盘开头的表中搜索与FCB中的名称相同的文件('?'通配符与任何字符匹配).如果找到文件,它会将一些信息复制到FCB中,包括磁盘上文件的物理位置,以便后续的I/O调用最终调用BIOS,这可能会将这些位置传递给磁盘驱动程序.在这个级别,细节会有所不同.