进程打开的文件是否加载到 RAM 中?

sha*_*ant 24 memory lsof files

例如sed命令是程序,程序是文件内的编码逻辑,这些文件位于硬盘上的某处。然而,当命令正在运行时,它们的文件副本从硬盘被放入RAM 中,在那里它们变得生动起来,可以做一些事情,被称为进程

进程可以使用其他文件,读取或写入它们,如果它们这样做,这些文件称为打开文件。有一个命令可以列出所有正在运行的进程的所有打开的文件:lsof.

好的,所以我想知道一个命令的双重生命,一个在硬盘上,另一个在 RAM 中,是否也适用于其他类型的文件,例如那些没有逻辑编程的文件,但只是容器数据。

我的假设是,进程打开的文件也会加载到 RAM 中。我不知道这是不是真的,这只是一种直觉。

拜托,有人能理解吗?

Kus*_*nda 37

不,文件不会通过打开文件自动读入内存。那将是非常低效的。sed例如,像许多其他 Unix 工具一样,逐行读取其输入。它很少需要在内存中保留多于当前行的内容。

awk它是一样的。它一次读取一条记录,默认情况下是一行。如果您将部分输入数据存储在变量中,那将是额外的,当然是1

有些人习惯于做这样的事情

for line in $(cat file); do ...; done
Run Code Online (Sandbox Code Playgroud)

由于$(cat file)在运行for循环的第一次迭代之前,shell 必须完全扩展命令替换,这会将整个读file入内存(读入执行for循环的 shell 使用的内存)。这有点愚蠢,也不优雅。相反,应该做

while IFS= read -r line; do ...; done <file
Run Code Online (Sandbox Code Playgroud)

这将file逐行处理(但请阅读理解 "IFS= read -r line")。

尽管很少需要在 shell 中逐行处理文件,因为大多数实用程序无论如何都是面向行的(请参阅为什么使用 shell 循环来处理文本被认为是不好的做法?)。

我从事生物信息学工作,在处理大量基因组数据时,除非我只将绝对必要的数据位保留在内存中,否则我将无能为力。例如,当我需要从 VCF 文件中包含 DNA 变体的 1 TB 数据集中剥离可用于识别个人的数据位时(因为该类型的数据无法公开),我会逐行执行用一个简单的awk程序处理(这是可能的,因为 VCF 格式是面向行的)。我不会将文件读入内存,在那里处理它,然后再写回!如果文件被压缩,我会通过zcator馈送它gzip -d -c,因为gzip它进行数据流处理,也不会将整个文件读入内存。

即使那些文件格式面向行的,像JSON或XML,有流解析器,使得它能够处理大型文件,而无需将它存储所有在RAM中。

对于可执行文件,它稍微复杂一些,因为共享库可以按需加载,和/或在进程之间共享(例如,请参阅共享库的加载和 RAM 使用)。

缓存是我在这里没有提到的。这是使用 RAM 来保存经常访问的数据片段的操作。操作系统可能会缓存较小的文件(例如可执行文件),希望用户能够多次引用它们。除了第一次读取文件外,后续访问将访问 RAM 而不是磁盘。缓存,如输入和输出的缓冲通常对用户来说在很大程度上是透明的,用于缓存事物的内存量可能会根据应用程序分配的 RAM 量等动态变化。


1 从技术上讲,大多数程序可能一次读取一个输入数据块,要么使用显式缓冲,要么通过标准 I/O 库所做的缓冲隐式地读取,然后将该块逐行呈现给用户的代码。读取磁盘块大小的倍数比一次读取一个字符要高效得多。不过,这个块大小很少会超过几千字节。

  • `sed`、`awk` 和其他面向行的程序不会一次将一行读入内存,因为纯文本文件不包含行索引,文件系统 API 和低级存储硬件读取一个或多个一次“扇区”(通常为 512 或 1024 个字节)。如果在处理第一行之前操作系统将少于 8KB 的数据读入内存,我会感到惊讶。 (6认同)
  • 尽管像`sed`这样的实用程序一次只能将一行读入内存,但值得一提的是,操作系统将使用空闲内存来缓存文件,以便可以快速访问它们。如果您在较小的文件上运行 `sed`,操作系统将整个文件缓存在内存中并且操作将完全在 RAM 中完成是可行的。请参阅:https://en.wikipedia.org/wiki/Page_cache (5认同)
  • @sharkant 可以在内存中完全访问文件(请参阅另一个答案,mmap 是此处的关键字系统调用)。例如,为了方便和快速访问,数据库系统通常希望将整个数据库或至少一些索引映射到内存中。这并不一定意味着整个事情实际上都在内存中。操作系统可以自由地“假装”文件在内存中。它告诉应用程序“在这里,在这个内存范围内是你的文件”,并且只有在读取完成后(就像进程被换出时一样),数据才被真正读取。 (5认同)

Bas*_*tch 30

然而,当命令正在运行时,他们的文件从硬盘的副本被放入 RAM,

这是错误的(一般来说)。当程序执行时(通过execve(2) ...),进程(运行该程序)正在改变其虚拟地址空间,内核为此目的重新配置MMU。另请阅读有关虚拟内存的信息。请注意,应用程序可以使用mmap(2) & munmap& mprotect(2)更改它们的虚拟地址空间,动态链接器也使用它(参见ld-linux(8))。另见madvise(2) & posix_fadvise(2) & mlock(2)

内核将处理未来的页面错误以从可执行文件加载(延迟)页面。另请阅读有关颠簸的内容

内核维护一个大页面缓存。另请阅读关于copy-on-write。另请参阅预读(2)

好的,所以我想知道一个命令的双重生命,一个在硬盘上,另一个在 RAM 中,是否也适用于其他类型的文件,例如那些没有逻辑编程的文件,但只是容器数据。

对于像read(2) & write(2)这样的系统调用,页面缓存也被使用。如果要读取的数据在里面,就不会做磁盘IO。如果需要磁盘 IO,读取的数据很可能会放入页面缓存中。因此,在实践中,如果您两次运行相同的命令,可能会发生第二次没有对磁盘进行物理 I/O(如果您有一个旧的旋转硬盘 - 不是 SSD - 您可能会听到;或仔细观察您的硬盘 LED)。

我建议阅读像操作系统:三个简单的部分(可免费下载,每章一个 PDF 文件)这样的书,它解释了所有这些。

另请参见Linux的吃了我的RAM等,并运行命令xosviewtophtopcat /proc/self/mapscat /proc/$$/maps(见PROC(5) )。

附注。我专注于 Linux,但其他操作系统也有虚拟内存和页面缓存。


小智 6

不。虽然现在拥有大量 RAM 很棒,但曾经有一段时间 RAM 是一种非常有限的资源(我在具有 2MB RAM 的 VAX 11/750 上学习编程)并且 RAM 中唯一的东西是活动的可执行文件和数据页活动进程的数量,以及缓冲区缓存中的文件数据。
缓冲区缓存被刷新,数据页被换出。并且有时频繁。只读可执行页面被覆盖并标记了页表,因此如果程序再次触及这些页面,它们将从文件系统中分页。数据是从交换调入的。如上所述,STDIO 库以块为单位提取数据,并根据需要由程序获取:fgetc、fgets、fread 等。使用 mmap,可以将文件映射到进程的地址空间,例如使用共享库对象甚至常规文件。是的,如果它在 RAM 中与否 (mlock),您可能有一定程度的控制权,但它只能到此为止(请参阅 mlock 的错误代码部分)。