打开文件实际上是做什么的?
在所有编程语言中(我至少使用过),在读取或写入文件之前,您必须先打开一个文件。
但是这个开放操作实际上做了什么?
典型功能的手册页实际上不会告诉你任何东西,除非它'打开一个文件来读/写':
http://www.cplusplus.com/reference/cstdio/fopen/
https://docs.python.org/2/library/functions.html#open
显然,通过使用这个函数,你可以告诉它涉及到创建某种对象,这有助于访问一个文件。
如果我要实现一个open
函数,另一种方法是,在Linux上需要做什么?
在几乎所有高级语言中,打开文件的函数都是相应的内核系统调用的包装。 它也可能做其他花哨的东西,但在当代操作系统中,打开文件必须始终通过内核。
这就是为什么fopen
库函数的参数或Python的open
参数与open(2)
系统调用的参数非常相似的原因。
除了打开文件外,这些功能通常会设置一个缓冲区,以便随后进行读/写操作。 此缓冲区的目的是确保无论何时您想要读取N个字节,相应的库调用都将返回N个字节,而不管对底层系统调用的调用是否返回较少。
我实际上并不想执行我自己的功能; 只是在了解到底是怎么回事......'超越语言',如果你喜欢。
在类Unix操作系统中,成功调用open
返回一个“文件描述符”,该文件描述符在用户进程的上下文中仅仅是一个整数。 这个描述符因此被传递给与打开文件交互的任何调用,并且在调用close
之后,描述符变为无效。
重要的是要注意, open
电话就像是进行各种检查的验证点。 如果不是所有条件都满足,则调用失败,返回-1
而不是描述符,错误的类型在errno
指示。 基本检查是:
在内核环境中,进程的文件描述符和物理打开的文件之间必须有某种映射。 映射到描述符的内部数据结构可能包含处理基于块的设备的另一个缓冲区,或者指向当前读取/写入位置的内部指针。
我建议你通过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 }
简而言之,下面是代码的功能:
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);
}
这有两点:
struct file
并返回它。 这个结构成为我之前提到的打开文件列表中的条目。 将返回的结构存储(“安装”)到进程的打开文件列表中。
read()
, write()
和close()
这样的文件操作函数。 它们中的每一个都会将控制交给内核,内核可以使用文件描述符在进程列表中查找相应的文件指针,并使用该文件指针中的信息来实际执行读取,写入或关闭。 如果你觉得雄心勃勃,可以将这个简化的例子与Linux内核中open()
系统调用的实现进行比较,该函数称为do_sys_open()
。 你不应该有任何困难找到相似之处。
当然,这只是调用open()
时发生的情况的“顶层” - 或者更确切地说,这是在打开文件的过程中调用的最高级别的内核代码片段。 高级编程语言可能会在其上添加额外的图层。 有很多事情在较低层次上进行。 (感谢Ruslan和pjc50的解释。)粗略地说,从上到下:
open_namei()
和dentry_open()
调用文件系统代码,它也是内核的一部分,用于访问文件和目录的元数据和内容。 文件系统从磁盘读取原始字节,并将这些字节模式解释为文件和目录树。 /dev/sda
等来访问块设备层的原始数据。) 由于缓存,这可能也有些不正确。 :-P认真地说,我遗漏了很多细节 - 一个人(不是我)可以写出多本书来描述整个过程如何工作。 但是这应该给你一个想法。
任何你想谈论的文件系统或操作系统都可以。 太好了!
在ZX频谱上,初始化LOAD
命令将使系统进入紧密循环,读取Audio In线路。
数据开始由恒定的音调表示,之后接着是一个长/短脉冲序列,其中短脉冲是二进制0
而较长脉冲是二进制1
(https://en.wikipedia)。组织/维基/ ZX_Spectrum_software)。 紧密加载循环会收集比特,直到它填充一个字节(8比特),将其存储到内存中,增加内存指针,然后循环返回以扫描更多比特。
通常情况下,加载器会读取的第一件事是一个简短的固定格式头文件,至少指示期望的字节数,并且可能还有其他信息,例如文件名,文件类型和加载地址。 读完这个简短的标题后,程序可以决定是继续加载大部分数据,还是退出加载例程并向用户显示适当的消息。
可以通过接收尽可能多的字节来识别文件结束状态(固定数量的字节,软件中的硬连线或诸如标题中指示的可变数字)。 如果加载回路没有在预期的频率范围内接收到一段时间的脉冲,则会引发错误。
关于这个答案的一点背景
所描述的程序从常规音频磁带加载数据 - 因此需要扫描音频输入(它与标准插头连接到磁带录音机)。 LOAD
命令在技术上与open
文件相同 - 但实际上与实际加载文件有关。 这是因为录音机不受电脑控制,您不能(成功)打开文件但不加载文件。
因为(1)CPU,一个Z80-A(如果内存服务),真的很慢:3.5 MHz,(2)Spectrum没有内部时钟! 这意味着它必须精确地保持每个T状态的计数(指令时间)。 单。 指令。 在该循环内部,只是为了保持准确的蜂鸣声时间。
幸运的是,低CPU速度具有明显的优势,您可以计算一张纸上的周期数,从而计算出它们所需的真实世界时间。