Linux内核内存管理:缓存系统

高速缓存是将频繁访问或新写入的数据从一个小而快的内存中取出或写入的过程,这个过程称为高速缓存。

脏内存是数据支持的(例如文件支持的)内存,其内容已被已修改(通常在缓存中)但尚未写回磁盘。 缓存的版本数据比磁盘版本新,这意味着两个版本不同步。 将缓存数据写回磁盘(后备存储)的机制称为回写。 我们最终将更新磁盘版本,使两者同步。 干净的内存是文件支持的内存,其中的内容与磁盘同步。

Linux 延迟写入操作以加快读取过程,并通过仅在必要时写入数据来减少磁盘磨损均衡。 一个典型的例子是 dd 命令。 它的完整执行并不意味着将数据写入目标设备; 这就是为什么 dd 在大多数情况下链接到同步命令的原因。

什么是缓存?

缓存是临时的、小而快的内存,用于从较大且通常非常慢的内存中保存数据副本,通常放置在系统中,其中工作数据集的访问频率远高于其他数据集(例如,硬盘驱动器、 记忆)。

当第一次读取发生时,假设一个进程从较大且较慢的磁盘请求一些数据,请求的数据将返回给进程,并跟踪和缓存访问数据的副本。任何后续读取都将从缓存中获取数据。任何数据修改都将应用在缓存中,而不是主磁盘上。然后,内容已被修改且与磁盘版本不同(比磁盘版本更新)的缓存区域将被标记为dirty。当缓存运行满时,由于缓存的数据被添加,新的数据开始驱逐未被访问且闲置时间最长的数据,因此,如果再次需要它,它将不得不再次从大/慢的存储中提取。

CPU高速缓存-内存高速缓存

在现代的CPU上有三个缓存存储器,按大小和访问速度排序:

  • L1缓存拥有最小的内存(通常在1K到64K之间),并且可以在一个时钟周期内被CPU直接访问,这使得它也是最快的。经常使用的东西都在L1中,直到其他东西的使用频率比现有的多,L1中的空间变少到不够用为止。如果L1不够用了,它被移动到一个更大的L2。
  • L2缓存是中间层,与处理器相邻的内存数量较大(可达几兆字节),可以在少量的时钟周期内访问。这适用于从L2移动物体到L3。
  • L3缓存虽然比L1和L2慢,但速度可能是主存(RAM)的两倍。每个核心可能有自己的L1和L2缓存;因此,它们都共享L3缓存。大小(L1 < L2 < L3)和速度(L1 > L2 > L3)是每个缓存级别之间变化的主要标准。例如,原始内存访问可以是100 ns, L1缓存访问可以是0.5 ns。

一个现实生活中的例子是,图书馆为了方便快捷地获取最受欢迎的图书,可能会展示几本,但却有一个规模更大、可获得的藏书更多的档案,这很不方便,因为你不得不等待图书管理员去取。陈列柜类似于缓存,而存档则是大而慢的内存。

CPU缓存解决的主要问题是延迟,这间接地增加了吞吐量,因为访问未缓存内存可能需要一段时间。

Linux页面缓存-磁盘缓存

顾名思义,页面缓存是RAM中的页面缓存,其中包含最近访问的文件块。RAM充当驻留在磁盘上的页的缓存。换句话说,它是文件内容的内核缓存。缓存的数据可能是常规的文件系统文件、块设备文件或内存映射文件。每当调用read()操作时,内核首先检查数据是否驻留在页面缓存中,如果找到就立即返回。否则,将从磁盘读取数据。

如果一个进程需要在不涉及缓存的情况下写入数据,它必须使用O_SYNC标志,它保证write()命令在所有数据传输到磁盘之前不会返回,或者O_DIRECT标志,这只能保证数据传输不会使用缓存。也就是说,O_DIRECT实际上取决于所使用的文件系统,不推荐使用。

专用缓存(用户空间缓存)

  • 网络浏览器缓存: 它将经常访问的网页和图像存储到磁盘上,而不是从网络获取它们。尽管在线数据的第一次访问可能持续数百毫秒以上,但第二次访问将在仅10毫秒内从缓存(在本例中是磁盘)中获取数据。
  • libc或用户应用程序缓存: 内存和磁盘缓存实现将尝试猜测您接下来需要使用什么,而浏览器缓存保留一个本地副本,以备再次使用。

为什么延迟向磁盘写入数据?

主要有两个原因:

  • 更好地利用磁盘特性;这是效率
  • 允许应用程序在写入后立即继续;这是性能

例如,延迟磁盘访问和处理数据直到数据量达到一定的大小,可以提高磁盘性能,降低嵌入式系统的eMMC磨损水平。每个块的写合并为一个单独的连续的写操作。此外,写入的数据被缓存,允许进程立即返回,以便任何后续读取都将从缓存中获取数据,从而使程序响应更快。存储设备更倾向于少量的大操作,而不是一些小操作。

通过稍后执行对永久存储的写操作,我们可以消除这些磁盘带来的延迟问题,这些磁盘相对较慢。

写缓存策略

根据缓存策略的不同,可以列举出几个好处:

  • 降低数据访问延迟,从而提高应用程序性能
  • 提高存储的生命周期
  • 减少系统工作负载
  • 降低数据丢失的风险

缓存算法通常分为以下三种不同的策略:

  1. write-through cache 是任何写操作都会自动更新内存缓存和永久存储。对于不能容忍数据丢失的应用程序,以及写数据然后频繁地重新读取数据的应用程序(因为数据存储在缓存中,因此读延迟较低),这种策略是首选。
  2. write-around cache  write-through cache 类似,不同之处在于它会立即使缓存失效(这对系统来说也很昂贵,因为任何写都会导致缓存自动失效)。主要的结果是,任何后续的读取都将从磁盘获取数据,这是缓慢的,从而增加了延迟。它防止缓存被随后无法读取的数据淹没。
  3. Linux 采用第三种也是最后一种策略,称为回写缓存(write-back cache),它可以在每次发生变化时将数据写入缓存,而无需更新主存中相应的位置。相反,页面缓存中相应的页面被标记为dirty(该任务由MMU使用TLB完成),并被添加到一个由内核维护的所谓列表中。只有在指定的时间间隔或特定的条件下,数据才被写入到永久存储器中相应的位置。当页面中的数据与页面缓存中的数据一致时,内核将从列表中删除这些页面,它们不会被标记为dirty。
  4. 在Linux系统中,你可以在/proc/meminfo中找到Dirty:
cat /proc/meminfo | grep Dirty

刷新线程(flusher threads)

回写cache 延迟I/O数据在页cache中的操作。一组或内核线程(称为刷新线程)负责此工作。当满足下列任何一种情况时,脏页回写发生:

  • 当空闲内存低于指定的阈值以重新获得脏页所消耗的内存时。
  • 脏数据持续到指定时间段。将最老的数据写回磁盘,以确保脏数据不会无限期保持脏。
  • 当用户进程调用sync()和fsync()系统调用时。这是按需回写。

 

posted @ 2021-07-22 15:20  闹闹爸爸  阅读(631)  评论(0编辑  收藏  举报