文件流如何实际工作?

时间:2011-05-20 02:06:13

标签: c++ c assembly file-io fstream

我一直想知道,文件流的确切工作原理是什么?对于文件流,我的意思是访问文件的一部分而不将整个文件加载到内存中 我(相信)知道C ++类(i|o)fstream正是这样做的,但它是如何实现的?是否可以自己实现文件流? 它是如何在最低的C / C ++(或任何支持文件流的语言)级别下工作的? C函数fopenfclosefreadFILE*指针是否已经处理流式传输(即不将整个文件加载到内存中)?如果没有,您将如何直接从硬盘驱动器读取并且是否已在C / C ++中实现了这样的设施?

任何正确方向的链接,提示,指针都会非常有用。我用谷歌搜索了,但似乎谷歌并不太了解我追求的是什么......


Ninja-Edit :如果有人知道如何在程序集/机器代码级别工作,并且是否可以自己实现或者必须依赖在系统调用上,这将是非常棒的。 :)不是答案的要求,虽然正确方向的链接会很好。

2 个答案:

答案 0 :(得分:24)

在最低级别(至少对于用户区代码),您将使用系统调用。在类UNIX平台上,这些平台包括:

  • open
  • close
  • read
  • write
  • lseek

......和其他人。这些工作通过传递这些称为文件描述符的东西。文件描述符只是不透明的整数。在操作系统内部,每个进程都有一个文件描述符表,包含所有文件描述符和相关信息,例如它是哪个文件,文件类型等等。

还有类似于UNIX上的系统调用的Windows API调用:

Windows传递HANDLE,类似于文件描述符,但我相信,它的灵活性稍差。 (例如,在UNIX上,文件描述符不仅可以表示文件,还可以表示套接字,管道和其他东西)

C标准库函数fopenfclosefreadfwritefseek只是围绕这些系统调用的包装。

当您打开文件时,通常没有文件的内容被读入内存。使用freadread时,告诉操作系统将特定数量的字节读入缓冲区。这个特定的字节数可以是,但不一定是文件的长度。因此,如果需要,您只能将文件的一部分读入内存。

回答忍者编辑:

您在机器代码级别询问了它是如何工作的。我只能解释它在Linux和Intel 32位架构上是如何工作的。使用系统调用时,某些参数将放入寄存器中。将参数放入寄存器后,会引发中断0x80。因此,例如,要从stdin(文件描述符0)读取一千字节到地址0xDEADBEEF,您可以使用此汇编代码:

mov eax, 0x03       ; system call number (read = 0x03)
mov ebx, 0          ; file descriptor (stdin = 0)
mov ecx, 0xDEADBEEF ; buffer address
mov edx, 1024       ; number of bytes to read
int 0x80 ; Linux system call interrupt

int 0x80引发一个软件中断,操作系统通常会在中断向量表或中断描述符表中注册。无论如何,处理器将跳转到内存中的特定位置。在那里,通常操作系统将进入内核模式(如果需要),然后在switch上执行相当于C eax的操作。从那里,它将跳转到read的实现。在read中,它通常会从调用进程的文件描述符表中读取有关描述符的一些元数据。一旦它拥有所需的所有数据,它就会完成它的工作,然后返回到用户代码。

要“做它的东西”,让我们假设它是从磁盘读取,而不是管道或stdin或其他一些非物理位置。我们也假设它是从主硬盘读取的。另外,我们假设操作系统仍然可以访问BIOS中断。

要访问该文件,需要执行一堆文件系统操作。例如,遍历目录树以查找实际文件的位置。我不打算这么说,因为我打赌你可以猜到。

有趣的部分是从磁盘读取数据,无论是文件系统元数据,文件内容还是其他内容。首先,您获得逻辑块地址(LBA)。 LBA只是磁盘上数据块的索引。每个块通常是512个字节(尽管这个数字可能已过时)。仍假设我们可以访问BIOS并且操作系统使用它,然后它会将LBA转换为CHS表示法。 CHS(Cylinder-Head-Sector)表示法是引用硬盘驱动器部件的另一种方式。它曾经与物理概念相对应,但现在它已经过时了,但几乎每个BIOS都支持它。从那里,操作系统将数据填入寄存器并触发中断0x13,即BIOS的磁盘读取中断。

这是我能解释的最低级别,我确信在我假设操作系统使用BIOS之后的部分已经过时了。之前的一切都是它仍然有效,但我相信,如果不是简化的话。

答案 1 :(得分:2)

在最低级别,在POSIX平台上,打开的文件由用户空间中的“描述符”表示。文件描述符只是一个整数,在任何给定时间在打开的文件中都是唯一的。描述符用于标识在请求内核实际执行该操作时应该应用哪个操作的打开文件。因此,read(0, charptr, 1024)读取与描述符0相关联的打开文件(按照惯例,这可能是进程的标准输入)。

就用户空间而言,加载到内存中的文件的唯一部分是满足read之类操作所需的部分。要从文件中间读取字节,需要支持另一个操作 - “搜索”。这告诉内核重新定位特定文件中的偏移量。下一个read(或write)操作将使用该新偏移量中的字节。因此lseek(123, 100, SEEK_SET)将与123(我刚刚编写的描述符值)相关联的文件的偏移量重新定位到第100个字节位置。 123上的下一次读取将从该位置开始读取,而不是从文件的开头(或以前的偏移位置)开始读取。并且任何未读取的字节都不需要加载到内存中。

幕后有一点复杂性 - 磁盘通常读取不到一个“块”,通常是4096左右的2的幂;内核可能会做额外的缓存和称为“readahead”的东西。但这些都是优化,基本思想就是我上面所描述的。