Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🐧 Linux Kernel Development
    • Chapter 1 - Linux 内核简介
    • Chapter 2 - 从内核出发
    • Chapter 3 - 进程管理
    • Chapter 4 - 进程调度
    • Chapter 5 - 系统调用
    • Chapter 7 - 中断和中断处理
    • Chapter 9 - 内核同步介绍
    • Chapter 10 - 内核同步方法
    • Chapter 11 - 定时器和时间管理
    • Chapter 13 - 虚拟文件系统
    • Chapter 14 - 块 I/O 层
    • Chapter 16 - 页高速缓存和页回写

Chapter 16 - 页高速缓存和页回写

Created by : Mr Dk.

2019 / 10 / 30 22:13

Nanjing, Jiangsu, China


页高速缓存 (cache) 是 Linux 内核实现的磁盘缓存:

  • 减少对磁盘的 I/O 操作
  • 把磁盘中的数据缓存到物理内存中
  • 把对磁盘的访问变为对物理内存的访问

两个重要因素:

  1. 访问磁盘的速度远低于访问内存的速度 (差好几个数量级)
  2. 数据一旦被访问,就可能在短时间内再次被访问到——临时局部原理 (temporal locality)

16.1 缓存手段

页高速缓存的大小能够动态调整,占用空闲内存以扩张大小,自我收缩以缓解内存使用压力。首先检查需要的数据是否在页高速缓存中:

  • 如果在,则放弃访问磁盘,直接从内存读取 - 缓存命中
  • 未命中则调度块 I/O 操作从磁盘读取数据,将数据放入页缓存,之后的访问就可以命中了

系统不必将整个文件都缓存:可以缓存几个文件的不同页。到底该缓存谁取决于谁被访问到。

16.1.1 写缓存

当进程写磁盘后,缓存如何被使用?

  • Nowrite
    • 直接跳过缓存写磁盘,同时也使缓存中的数据失效
    • 后续读操作进行时,需要重新从磁盘中读取数据
  • Write-through cache
    • 写操作自动更新缓存,同时更新磁盘文件
    • 相当于穿透缓存到磁盘中
    • 对保持缓存一致性很有好处
  • Write-back
    • Linux 中使用
    • 写操作直接写到缓存中,后端存储不立刻更新
    • 将页高速缓存中的被写入页面标记为 dirty,并加入一个 dirty page 链表中
    • 由一个回写进程周期性地写回磁盘,并清理 dirty 标志

通过延迟写磁盘,能够在以后的时间内合并更多的数据并再次刷新。

16.1.2 缓存回收

缓存算法的核心:缓存的数据如何清除。为更重要的缓存项腾出位置,收缩缓存大小,腾出内存。Linux 的缓存回收是通过不脏的页进行简单替换。如果缓存中没有足够的干净页面,则强制进行回写回收。核心在于,如何决定什么页应该回收:

  • 最近最少使用 LRU
    • 回收最老时间戳的页面
    • 缓存的数据越久未被访问,越不大可能近期再被访问
    • 对于只被访问一次的文件,LRU 很失败
  • 双链策略
    • Linux 实现的一种改进版 LRU
    • 维护两个链表:活跃链表、非活跃链表
      • 活跃链表上的页面比较 hot,不会被换出
      • 非活跃链表上的页面可以被换出
    • 两个链表都是 FIFO 的
    • 两个链表要维护平衡

双链策略解决了 LRU 中仅访问文件一次的窘境。

16.2 Linux 页高速缓存

16.2.1 address_space 对象

Linux 页高速缓存的目标是缓存任何基于页的对象:

  • 各种类型的文件
  • 各种类型的内存映射

16.2.2 address_space 操作

16.3 缓冲区高速缓存

独立的磁盘块通过块 I/O 缓冲也要被存入页高速缓存。在更早的内核中,有两个独立的磁盘缓存:

  • 页高速缓存 - 缓存页面
  • 缓冲区高速缓存 - 缓存缓冲区

一个磁盘块可以同时存在于两个缓存中,因此必须同步操作两个缓存中的数据,还浪费内存。在 Linux 2.4 内核中,将两者进行统一。现在只有页高速缓存。

16.4 flusher 线程

在内存中积累起来的脏页最终必须被写回磁盘。时机:

  • 空闲内存低于一定阈值时
  • 脏页在内存驻留时间超过阈值
  • 用户进程调用 sync() 和 fsync() 系统调用时

当空闲内存低于阈值时,内核唤醒一个或多个 flusher 线程,该线程开始将脏页写回磁盘,直到满足两个条件:

  • 已有指定的最小数目的页被写到磁盘
  • 空闲内存数超过了阈值

Flusher 后台线程会被周期性唤醒,将驻留时间过长的脏页写出,确保内存中不会有长期存在的脏页。在系统启动时,内核初始化一个定时器,周期性唤醒 flusher 线程。将脏页写回后,再次初始化定时器。

16.4.1 膝上型计算机模式

特殊的 write-back 策略:将硬盘转动的机械行为最小化,允许硬盘尽可能长时间停滞,延长供电时间。

Flusher 需要找准磁盘运转的时机,完成回写磁盘,保证不会专门为了写磁盘而主动激活磁盘运行。回写延迟较长,保证膝上型计算机模式可以等到磁盘运转时再写入数据。关闭磁盘驱动器是节电的重要手段。这种模式的坏处:发生错误时容易丢失数据。多数 Linux 发布版会在接上电池时自动开启该模式。

16.4.2 历史上的 bdflush、kupdated 和 puflush

16.4.3 避免拥塞的方法:使用多线程

之前的 bdflush 的缺点:仅包含了一个线程。该线程可能堵塞在某个设备的请求队列上,而其它设备的请求队列却无法得到处理。如果系统有多个磁盘,内核应该使每个磁盘都处于忙状态,内核应当有多个回写线程并发执行。Linux 2.6 内核中的每个 flusher 线程可以独立地将脏页刷新回磁盘。如果每个线程在同一个拥塞的队列上挂起了怎么办呢?Flusher 线程模型和具体块设备关联,每个线程从每个给定设备的脏页链表写回磁盘,每个磁盘对应一个线程,在每个磁盘上都维护更高的吞吐量。

Edit this page on GitHub
Prev
Chapter 14 - 块 I/O 层