Linux内核空间用来设置内存映射区域的缓存属性的pgprot_* 系列函数的详细介绍【CPU核心、Cache(高速缓存)、写缓冲器、主存之间的关系;Cache和写缓冲器的详细介绍】

目录

  • 原理示意图
  • Cache(高速缓存)的详细介绍
    • Cache(高速缓存)的概要介绍
    • Cache(高速缓存)的读操作原理和流程
    • Cache(高速缓存)的写操作原理和流程
      • 1. **Write-Through(写直达)**
      • 2. **Write-Back(写回)**
      • 3. **Write Combining(写合并)**
      • **写数据的典型流程(以 Write-Back 为例)**
      • **数据一致性问题**
      • **总结**
  • 写缓冲器的介绍
  • 重要概念:主存(内存)中的数据除了可以被 CPU 修改,也可能被其他硬件或设备修改
  • pgprot_* 系列函数的详细介绍
    • **1. `pgprot_cached`**
    • **2. `pgprot_noncached`**
    • **3. `pgprot_writecombine`**
    • **4. `pgprot_device`**
    • **5. `pgprot_writethrough`**
    • **6. `pgprot_writeback`**
    • **7. `pgprot_none`**
    • **8. `pgprot_exec`**
    • **9. `pgprot_user`**
    • **10. `pgprot_kernel`**
    • **11. `pgprot_modify`**
    • **12. `pgprot_val` 和 `__pgprot`**
    • **总结:缓存模式与实时性对比**
  • 非缓存模式和设备内存模式的区别
    • **1. 定义和用途**
      • **设备内存模式(Device Memory)**
      • **非缓存模式(Non-cached Memory)**
    • **2. 缓存行为**
      • **设备内存模式**
      • **非缓存模式**
    • **3. 写入行为**
      • **设备内存模式**
      • **非缓存模式**
    • **4. 内存屏障的使用**
      • **设备内存模式**
      • **非缓存模式**
    • **5. 典型应用场景**
      • **设备内存模式**
      • **非缓存模式**
    • **6. 总结对比**
    • **具体区别分析**
    • **总结**

原理示意图

Cache 和 写缓冲器 是 CPU 的核心组成部分,属于 CPU 微架构设计的一部分。

下图是CPU核心、Cache(高速缓存)、写缓冲器、主存之间的关系。

Cache(高速缓存) 相当于是一块高速内存;写缓冲器相当于一个 FIFO,可以把多个写操作集合起来一次写入内存。
Linux内核空间用来设置内存映射区域的缓存属性的pgprot_* 系列函数的详细介绍【CPU核心、Cache(高速缓存)、写缓冲器、主存之间的关系;Cache和写缓冲器的详细介绍】_第1张图片

Cache(高速缓存)的详细介绍

Cache(高速缓存)的概要介绍

Cache 是 CPU 内部(或者在某些情况下,靠近 CPU 的芯片上)的一种高速存储器,用来存储近期访问的数据或指令的副本。

  • 特性
    • Cache 通常分为多级(L1、L2、L3),L1 最靠近 CPU,速度最快,但容量最小。
    • Cache 数据通常以缓存行(Cache Line)为单位存储和管理。
    • 通过保持数据的局部性(时间局部性和空间局部性)来提高性能。

Cache(高速缓存)的读操作原理和流程

  • 当 CPU 需要访问内存中的某块数据时,会首先检查 Cache 中是否有该数据(称为 Cache 命中)。
  • 如果数据命中,则直接从 Cache 中读取,速度远高于从主内存读取。
  • 如果数据未命中(Cache Miss),则需要从主存(RAM)中加载数据,同时可能更新 Cache。

Cache(高速缓存)的写操作原理和流程

在启用了 Cache 的情况下,写数据通常会遵循以下流程:数据优先写入 Cache,然后根据 Cache 的策略再写入主存(内存)。以下是主要的写策略策的工作原理。


1. Write-Through(写直达)

  • 工作原理
    • 数据被写入 Cache 的同时,立即写入主存(内存)。
    • Cache 和主存始终保持同步。
  • 优点
    • 数据一致性较高,主存始终是最新的数据。
    • 如果发生 Cache Miss,主存的数据仍然是有效的。
  • 缺点
    • 写操作速度较慢,因为每次写入都需要同时更新主存,无法充分利用 Cache 的性能。
  • 适用场景
    • 对数据一致性要求较高的场景。

2. Write-Back(写回)

  • 工作原理
    • 数据只写入 Cache,并标记该 Cache Line 为“已修改”(Dirty)。
    • 数据不会立即写入主存,只有在以下情况之一时才会写回主存:
      1. 该 Cache Line 被替换(即被新的数据占用时)。
      2. 主动使用指令或操作(如 flush)将数据刷新到主存。
  • 优点
    • 写操作效率更高,因为减少了对主存的直接写入次数。
    • 对多个连续写操作,可以在 Cache 中合并,从而提高性能。
  • 缺点
    • 数据一致性较难维护,主存中的数据可能是旧的。
    • 如果系统崩溃或电源中断,可能会导致 Cache 中的数据丢失。
  • 适用场景
    • 对性能要求高且能容忍一定数据一致性延迟的场景。

3. Write Combining(写合并)

  • 工作原理
    • 对多个连续的写操作进行合并,在写缓冲器中批量写入主存。
    • 通常用于图像处理、DMA 传输等场景。
  • 优点
    • 减少主存写操作次数,提高总的写性能。
  • 缺点
    • 数据一致性依赖于合并策略,可能会引入延迟。
  • 适用场景
    • 高吞吐量、低数据一致性要求的场景,如硬件设备缓冲区。

写数据的典型流程(以 Write-Back 为例)

  1. 写入 Cache

    • CPU 通过 Cache Controller 将数据存储到 Cache 中。
    • 如果目标地址的 Cache Line 已存在(命中),更新对应的数据。
    • 如果未命中,先从主存加载目标地址的 Cache Line,再写入新的数据。
  2. 标记 Dirty

    • 更新 Cache Line 的标志位,标记为“已修改”(Dirty)。
  3. 延迟写回

    • 数据不会立即写入主存。
    • 当 Cache Line 被替换或需要刷新的时候,Cache Controller 会将 Dirty 数据写回主存。

数据一致性问题

由于数据在 Cache 和主存之间可能存在延迟写入,所以可能会导致以下问题:

  • 数据一致性问题

    • 如果 CPU 或设备访问的是主存而不是 Cache,可能读取到旧数据。
    • 多核 CPU 或其他硬件设备共享同一内存区域时,需要特别处理一致性。
  • 解决方法

    1. 内存屏障(Memory Barrier)
      • 强制确保在屏障之前的所有操作完成后,才执行屏障之后的操作。
    2. Cache 刷新(Cache Flush)
      • 使用指令(如 clflush)将 Cache 数据强制写回主存。
    3. 禁止 Cache
      • 对某些关键内存区域(如设备寄存器),可以禁用 Cache 或使用 Non-Cached 模式。

总结

启用 Cache 后,写数据是先写入 Cache,然后再根据策略写入主存。具体的写行为取决于 Cache 策略(Write-Through、Write-Back 或 Write Combining):

  • Write-Through:实时同步到主存,数据一致性高,但效率较低。
  • Write-Back:延迟写回主存,性能更高,但需要注意一致性问题。
  • Write Combining:合并写入,适用于连续写操作,但一致性较弱。

写缓冲器的介绍

其实在看了上面的关于Cache(高速缓存)“写合并”策略,就知道了写缓冲器是怎么回事儿了。

重要概念:主存(内存)中的数据除了可以被 CPU 修改,也可能被其他硬件或设备修改

主存中的数据不仅可以被 CPU 修改,还可能通过以下途径被其他设备修改:

  1. DMA 设备(如硬盘、网卡、GPU 等)。
  2. 外设映射内存(Memory-Mapped I/O)。
  3. 多核 CPU
  4. GPU
  5. 硬件调试工具
  6. 虚拟内存系统
  7. 网络设备(如 RDMA)。

pgprot_* 系列函数的详细介绍

在搞清楚了Cache(高速缓存)的原理后,再来看pgprot_* 系列函数就要容易得多了。

pgprot_* 系列函数是用来设置内存映射区域的Cache(高速缓存)属性的函数。

以下是 pgprot_* 系列函数的完整介绍,包括每种模式对 数据实时性 的影响:


1. pgprot_cached

  • 功能:将内存设置为 缓存模式(Cacheable)。
  • 特性
    • 数据会存储在 CPU 缓存中,读取数据和写入数据都通过Cache进行,所以速度较快。
    • 存在数据一致性问题,即Cache中的数据和主存中的数据可能不一致。
  • 数据实时性较差,可能存在缓存延迟,不适合实时性要求高的场景。
  • 应用场景:普通内存区域,非实时性要求场景。
  • 用法
    vma->vm_page_prot = pgprot_cached(vma->vm_page_prot);
    

2. pgprot_noncached

  • 功能:将内存设置为 非缓存模式(Uncacheable)。
  • 特性
    • 禁用缓存,每次读取都直接访问物理内存。
    • 保证数据的实时一致性。
  • 数据实时性非常高,适合需要确保数据最新的场景。
  • 应用场景:设备寄存器映射、DMA 缓冲区等。
  • 用法
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
    
    注意: 非缓存模式(Uncacheable)和 设备内存模式(Device Memory)很相似,但是还是有差别的,详情见本博文后面的内容。

3. pgprot_writecombine

  • 功能:将内存设置为 写合并模式(Write Combine)。
  • 特性
    • 写入数据时,CPU 会暂时存储在写缓冲区中,允许合并多次写入以减少内存交互。
    • 提高写入效率,但读数据仍直接访问内存。
    • 读数据的时候是直接从主存中读取,而不是从Cache中读取。
  • 数据实时性
    • 写入:较差,写缓冲区可能导致写入延迟。
    • 读取:较高,因为是直接访问主存,而不是从Cache中读取数据,所以读取到值为主存中的最新值,当主存中的值被别的因素更新时,它读取到的值是最新值。
  • 应用场景:显存、帧缓冲区等需要高效写入的场景。
  • 用法
    vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
    

4. pgprot_device

  • 功能:将内存设置为 设备内存模式(Device Memory)。
  • 特性
    • 禁用缓存,确保所有读写操作按顺序执行(强序访问)。
    • 适用于设备寄存器或 I/O 操作。
  • 数据实时性非常高,完全禁用缓存,保证操作顺序和实时性。
  • 应用场景:设备寄存器、外设的强序访问。
  • 用法
    vma->vm_page_prot = pgprot_device(vma->vm_page_prot);
    
    注意: 非缓存模式(Uncacheable)和 设备内存模式(Device Memory)很相似,但是还是有差别的,详情见本博文后面的内容。

5. pgprot_writethrough

  • 功能:将内存设置为 写透缓存模式(Write-Through Cacheable)。
  • 特性
    • 写入数据时,会同时更新 CPU 缓存和物理内存。
    • 读取操作优先从缓存中获取,提高读取效率。
  • 数据实时性较高,写入实时同步到内存,数据一致性较好。
  • 应用场景:需要缓存性能且写入实时性要求高的场景。
  • 用法
    vma->vm_page_prot = pgprot_writethrough(vma->vm_page_prot);
    

6. pgprot_writeback

  • 功能:将内存设置为 写回缓存模式(Write-Back Cacheable)。
  • 特性
    • 写入数据时,仅更新 CPU 缓存,必要时才将数据写回到内存。
    • 最大化写入性能,但数据可能会滞留在缓存中。
  • 数据实时性较差,写回机制可能导致数据更新延迟。
  • 应用场景:高性能内存访问,不关心实时性的场景。
  • 用法
    vma->vm_page_prot = pgprot_writeback(vma->vm_page_prot);
    

7. pgprot_none

  • 功能:设置内存区域为 无访问权限
  • 特性
    • 禁止对该内存区域的访问(读取、写入或执行)。
  • 数据实时性不适用,因为完全禁止访问。
  • 应用场景:保护内存区域、防止非法访问。
  • 用法
    vma->vm_page_prot = pgprot_none(vma->vm_page_prot);
    

8. pgprot_exec

  • 功能:设置内存区域为 可执行
  • 特性
    • 允许在该内存区域运行代码。
    • 主要用于代码段内存或动态分配的可执行区域。
  • 数据实时性无直接影响,与缓存属性无关。
  • 应用场景:执行代码段内存。
  • 用法
    vma->vm_page_prot = pgprot_exec(vma->vm_page_prot);
    

9. pgprot_user

  • 功能:设置内存区域为 用户态访问权限
  • 特性
    • 用户态进程可以访问该内存区域。
  • 数据实时性与具体缓存模式相关,无直接影响。
  • 应用场景:分配给用户态的内存。
  • 用法
    vma->vm_page_prot = pgprot_user(vma->vm_page_prot);
    

10. pgprot_kernel

  • 功能:设置内存区域为 仅内核态访问权限
  • 特性
    • 只有内核态代码可以访问该内存区域。
  • 数据实时性与具体缓存模式相关,无直接影响。
  • 应用场景:内核专用的内存区域。
  • 用法
    vma->vm_page_prot = pgprot_kernel(vma->vm_page_prot);
    

11. pgprot_modify

  • 功能:修改现有 pgprot_t 的缓存属性或权限。
  • 特性
    • 动态调整页面的缓存模式或访问权限。
  • 数据实时性:取决于修改后的缓存模式。
  • 应用场景:需要动态调整内存区域属性时。
  • 用法
    vma->vm_page_prot = pgprot_modify(vma->vm_page_prot, new_pgprot);
    

12. pgprot_val__pgprot

  • 功能
    • pgprot_val:从 pgprot_t 获取底层属性值。
    • __pgprot:将属性值转换回 pgprot_t
  • 特性:主要用于自定义或调试内存页面属性。
  • 数据实时性:与具体的缓存属性无关。
  • 应用场景:自定义页面保护或调试。
  • 用法
    unsigned long val = pgprot_val(vma->vm_page_prot);
    vma->vm_page_prot = __pgprot(val | custom_flags);
    

总结:缓存模式与实时性对比

函数 缓存属性 数据实时性 应用场景
pgprot_cached 缓存模式 较差 普通内存访问,高效读取。
pgprot_noncached 非缓存模式 非常高 设备寄存器或 DMA。
pgprot_writecombine 写合并模式 写:较差
读:高
显存或帧缓冲区高频写入。
pgprot_device 设备内存模式 非常高 外设寄存器、I/O 操作。
pgprot_writethrough 写透缓存模式 较高 实时性较高且需要缓存的场景。
pgprot_writeback 写回缓存模式 较差 高性能访问,但不关注实时性。
pgprot_none 无访问权限 不适用 禁止访问的内存区域。
pgprot_exec 可执行权限 无直接影响 动态分配的代码段或执行内存。
pgprot_user 用户态访问权限 无直接影响 用户态进程分配的内存。
pgprot_kernel 内核态访问权限 无直接影响 内核专用内存区域。

非缓存模式和设备内存模式的区别

设备内存模式(Device Memory)和非缓存模式(Non-cached Memory)在行为和使用场景上有一定区别,虽然两者都禁止普通缓存行为,但它们的设计目标和底层机制是不同的。以下是两者的区别及特点:


1. 定义和用途

设备内存模式(Device Memory)

  • 定义:主要用于映射硬件设备寄存器或内存区域(如 MMIO,Memory-Mapped I/O)。
  • 用途
    • 提供严格的访问顺序。
    • 确保对设备寄存器的读写操作按预期执行。
    • 适用于硬件设备的控制和数据访问。

非缓存模式(Non-cached Memory)

  • 定义:内存区域不经过 CPU 的缓存,所有的读写直接访问主存。
  • 用途
    • 适合需要频繁与主存交互且对性能要求较低的场景。
    • 适用于需要与 DMA 或硬件设备共享内存的场景,确保数据一致性。

2. 缓存行为

设备内存模式

  • 无缓存:禁止普通的缓存行为,不能将数据暂存到 CPU 的数据缓存(如 L1、L2 Cache)。
  • 强序列化:对设备内存的读写严格按照程序顺序进行,不能被 CPU 或编译器重新排序。
    • 读取:每次读取操作都会直接访问设备内存,确保数据是实时的。
    • 写入:写入操作通常立即生效,不允许延迟。

非缓存模式

  • 无缓存:和设备内存一样,不使用普通的 CPU 数据缓存。
  • 弱序列化:允许一定程度的访问重排序(取决于架构和实现),但提供了一些保证:
    • 读取:直接从主存读取数据。
    • 写入:写入可能会被合并,但不会缓存。
    • 通过内存屏障可以强制保证顺序访问。

3. 写入行为

设备内存模式

  • 不可写合并(No Write Combining)
    • 每次写操作会立即发出到设备。
    • 写入行为对设备是完全可见的,每次写入设备都会实时处理。

非缓存模式

  • 可能支持写合并(Write Combining)
    • 在某些平台上,可以对多个写操作进行合并,提高写性能。
    • 需要注意的是,写合并可能导致设备看不到中间状态(即等待合并完成后才写入)。

4. 内存屏障的使用

设备内存模式

  • 通常不需要额外的内存屏障,因为设备内存本身已经强制了严格的访问顺序。

非缓存模式

  • 可能需要插入内存屏障(如 wmb()rmb())来保证读写的顺序。
  • 例如,在与 DMA 设备交互时,内存屏障可以确保数据在主存中的可见性。

5. 典型应用场景

设备内存模式

  • 用于硬件设备寄存器或 MMIO 区域。
  • 适合对设备寄存器的严格控制,确保访问顺序和数据的实时性。
  • 例如:
    • 写入一个寄存器后,立即读取设备状态。
    • 网络适配器、GPU、音频控制器等设备的寄存器映射。

非缓存模式

  • 用于共享内存区域(例如,CPU 和 DMA 设备之间的内存)。
  • 适合对访问顺序要求不高的场景,但需要数据一致性。
  • 例如:
    • 大量数据传输区域(缓冲区)。
    • 视频或音频流的处理。

6. 总结对比

特性 设备内存模式 非缓存模式
缓存行为 禁用普通缓存 禁用普通缓存
读写顺序 强序列化,严格按程序顺序访问 弱序列化,需要屏障确保顺序
写合并 不支持写合并 可能支持写合并
应用场景 设备寄存器映射,适合小范围的精确控制 DMA 或共享内存区域,适合大块数据传输
内存屏障需求 通常不需要 需要明确使用内存屏障

具体区别分析

  • 实时性:设备内存模式优于非缓存模式,因为它确保写入立即生效,读取返回最新值。
  • 性能:非缓存模式可能通过写合并优化性能,但实时性较差。
  • 复杂性:非缓存模式需要更多手动控制(如屏障),设备内存模式使用更简单。

总结

两者的主要区别在于访问顺序控制和写合并支持。如果你的需求是严格控制硬件设备的行为,应该选择设备内存模式;如果你的需求是高效传输大量数据,同时允许一定程度的顺序优化,可以选择非缓存模式

你可能感兴趣的:(嵌入式,Linux系统,linux,Cache,写缓冲器,内存映射)