24、内核内存管理与DMA直接内存访问详解

内核内存管理与DMA直接内存访问详解

1. 内核内存管理基础

1.1 内存映射相关操作

在进行内存映射时,需要调用 remap_pfn_range 函数,传入计算得到的物理页帧号(PFN)、大小和保护标志。示例代码如下:

if (remap_pfn_range(vma, vma->vm_start, pfn, size,
vma->vm_page_prot)) {
   return -EAGAIN;
}
return 0;

同时,需要将自定义的 mmap 函数传递给 struct file_operations 结构体,示例如下:

static const struct file_operations my_fops = {
   .owner = THIS_MODULE,
   [...]
   .mmap = my_mmap,
   [...]
};

1.2 Linux 缓存系统概述

缓存是将频繁访问或新写入的数据存储在一个小而快的内存(称为缓存)中的过程。其中,脏内存是指数据(如文件数据)内容已被修改(通常在缓存中)但尚未写回磁盘的内存。缓存中的数据版本比磁盘上的版本新,两者不同步。将缓存数据写回磁盘(后端存储)的机制称为写回。而干净内存是指内容与磁盘同步的文件支持内存。

Linux 会延迟写操作以加速读过程,并通过仅在必要时写入数据来减少磁盘磨损。例如, dd 命令执行完成并不意味着数据已写入目标设备,因此大多数情况下 dd 会与 sync 命令一起使用。

1.3 缓存的定义与工作原理

缓存是一种临时、小而快的内存,用于保存来自更大且通常非常慢的内存(如硬盘、主内存)的数据副本。当首次读取数据时,例如进程从大而慢的磁盘请求数据,请求的数据会返回给进程,同时访问的数据副本会被跟踪并缓存。后续的读取操作将从缓存中获取数据。任何数据修改都将在缓存中进行,而不是在主磁盘上。当缓存区域的内容被修改且与磁盘版本不同(比磁盘版本新)时,该区域将被标记为脏。当缓存满时,新数据会驱逐最长时间未被访问且闲置的数据,若后续需要这些数据,则需再次从大/慢存储中获取。

1.4 CPU 缓存 - 内存缓存

现代 CPU 上有三种缓存内存,按大小和访问速度排序如下:
| 缓存级别 | 内存大小 | 访问速度 | 特点 |
| ---- | ---- | ---- | ---- |
| L1 缓存 | 通常在 1K 到 64K 之间 | 单个时钟周期内可被 CPU 直接访问,速度最快 | 频繁使用的数据存储在此,直到其他数据使用更频繁且 L1 空间不足时,数据会移动到 L2 缓存 |
| L2 缓存 | 可达几兆字节 | 可在少量时钟周期内访问 | 中间级别缓存,数据从 L2 移动到 L3 缓存遵循此特性 |
| L3 缓存 | - | 比 L1 和 L2 慢,但可能比主内存(RAM)快两倍 | 每个核心可能有自己的 L1 和 L2 缓存,所有核心共享 L3 缓存 |

CPU 缓存主要解决的问题是延迟,间接提高了吞吐量,因为访问未缓存的内存可能需要一些时间。可以用图书馆的例子来类比,图书馆会将最受欢迎的书籍副本放在展示架上以便快速访问,而大量的藏书则存放在大型档案库中,需要图书管理员去取。展示架就类似于缓存,档案库则是大而慢的内存。

1.5 Linux 页面缓存 - 磁盘缓存

页面缓存是 RAM 中页面的缓存,包含最近访问文件的块。RAM 作为磁盘上页面的缓存,即内核的文件内容缓存。缓存的数据可以是常规文件系统文件、块设备文件或内存映射文件。当调用 read() 操作时,内核首先检查数据是否存在于页面缓存中,如果存在则立即返回,否则从磁盘读取数据。

如果进程需要在不涉及缓存的情况下写入数据,需要使用 O_SYNC 标志,该标志保证 write() 命令在所有数据传输到磁盘后才返回;或者使用 O_DIRECT 标志,该标志仅保证数据传输不使用缓存,但实际上 O_DIRECT 依赖于所使用的文件系统,不建议使用。

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

  • Web 浏览器缓存 :将频繁访问的网页和图像存储在磁盘上,而不是从网络获取。首次访问在线数据可能需要数百毫秒以上,而第二次访问将在仅 10 毫秒内从缓存(磁盘)中获取数据。
  • libc 或用户应用缓存 :内存和磁盘缓存实现会尝试猜测用户接下来需要使用的数据,而浏览器缓存则会保留本地副本以备再次使用。

1.7 延迟写入数据到磁盘的原因

延迟写入数据到磁盘主要有两个原因:
- 提高磁盘效率 :更好地利用磁盘特性,例如延迟磁盘访问并在数据达到一定大小时进行处理,可以提高磁盘性能并减少嵌入式系统中 eMMC 的磨损。每次小块写入会合并为一个连续的写入操作。
- 提升应用性能 :允许应用在写入后立即继续执行。写入的数据会被缓存,使得进程可以立即返回,后续的读取操作将从缓存中获取数据,从而使程序响应更迅速。存储设备更喜欢少量的大操作而不是多个小操作,通过延迟报告永久存储上的写入操作,可以避免磁盘引入的延迟问题。

1.8 写缓存策略

根据缓存策略,可以列举出以下几个好处:
- 减少数据访问延迟,从而提高应用性能
- 延长存储设备寿命
- 减少系统负载
- 降低数据丢失风险

缓存算法通常分为以下三种不同策略:
- 直写缓存 :任何写入操作都会自动更新内存缓存和永久存储。这种策略适用于不能容忍数据丢失的应用程序,以及写入后频繁重新读取数据的应用程序(因为数据存储在缓存中,读取延迟较低)。
- 绕写缓存 :与直写缓存类似,但每次写入操作会立即使缓存失效(这对系统来说成本也很高,因为每次写入都会自动使缓存失效)。主要后果是后续的读取操作将从磁盘获取数据,速度较慢,从而增加了延迟。它可以防止缓存被不会被后续读取的数据淹没。
- 回写缓存 :Linux 采用的策略,每次数据发生更改时将数据写入缓存,而不更新主内存中的相应位置。相反,页面缓存中的相应页面会被标记为脏(此任务由 MMU 使用 TLB 完成)并添加到内核维护的列表中。数据仅在指定的时间间隔或特定条件下写入永久存储的相应位置。当页面中的数据与页面缓存中的数据同步时,内核会将页面从列表中移除,不再标记为脏。在 Linux 系统中,可以通过以下命令查看脏页信息:

cat /proc/meminfo | grep Dirty

1.9 刷新线程

回写缓存会延迟页面缓存中的 I/O 数据操作,一组称为刷新线程的内核线程负责处理脏页写回。当满足以下任何一种情况时,会发生脏页写回:
- 当空闲内存低于指定阈值时,为了回收脏页占用的内存。
- 当脏数据持续到特定时间段时,将最旧的数据写回磁盘,以确保脏数据不会永远保持脏状态。
- 当用户进程调用 sync() fsync() 系统调用时,这是一种按需写回操作。

1.10 设备管理资源 - Devres

Devres 是一种内核工具,帮助开发人员在驱动程序中自动释放分配的资源,简化了 init/probe/open 函数中的错误处理。每个资源分配器都有其管理版本,会负责资源的释放。资源分配器分配的内存与设备相关联, devres 由与 struct device 关联的任意大小内存区域的链表组成。每个 devres 资源分配器会将分配的资源插入列表中,资源将一直可用,直到代码手动释放、设备从系统中分离或驱动程序卸载。每个 devres 条目都与一个释放函数关联,有不同的方式来释放 devres ,但无论如何,所有 devres 条目都会在驱动分离时释放,释放时会调用关联的释放函数,然后释放 devres 条目。

以下是驱动程序可用的资源列表:
- 用于私有数据结构的内存
- 中断(IRQs)
- 内存区域分配( request_mem_region()
- 内存区域的 I/O 映射( ioremap()
- 缓冲区内存(可能带有 DMA 映射)
- 不同框架的数据结构:时钟、GPIO、PWM、USB 物理层、调节器、DMA 等

几乎本章讨论的每个函数都有其管理版本,大多数情况下,管理版本的函数名是在原函数名前加上 devm 前缀。例如, devm_kzalloc() kzalloc() 的管理版本。参数保持不变,但会向右移动,因为第一个参数是为其分配资源的 struct device 。对于非管理版本的函数已经在参数中包含 struct device 的情况除外。例如:

void *kmalloc(size_t size, gfp_t flags)
void * devm_kmalloc(struct device *dev, size_t size, gfp_t gfp)

当设备从系统中分离或设备驱动程序卸载时,内存会自动释放。如果不再需要内存,也可以使用 devm_kfree() 释放。以下是旧的中断注册方式和使用 devres 的正确方式对比:

// 旧方式
ret = request_irq(irq, my_isr, 0, my_name, my_data);
if(ret) {
    dev_err(dev, "Failed to register IRQ.\n");
    ret = -ENODEV;
    goto failed_register_irq; /* Unroll */
}

// 正确方式
ret = devm_request_irq(dev, irq, my_isr, 0, my_name, my_data);
if(ret) {
    dev_err(dev, "Failed to register IRQ.\n");
    return -ENODEV; /* Automatic unroll */
}

2. DMA - 直接内存访问

2.1 DMA 概述

DMA(直接内存访问)是计算机系统的一项功能,允许设备在无需 CPU 干预的情况下访问主系统内存 RAM,从而使 CPU 可以专注于其他任务。通常用于加速网络流量,但支持任何类型的复制操作。DMA 控制器是负责 DMA 管理的外设,常见于现代处理器和微控制器中。当需要传输一块数据时,处理器会将源地址、目标地址和总字节数提供给 DMA 控制器,DMA 控制器会自动将数据从源地址传输到目标地址,而不占用 CPU 周期。当剩余字节数达到零时,块传输结束。

2.2 设置 DMA 映射

对于任何类型的 DMA 传输,都需要提供源地址、目标地址以及要传输的字数。在外设 DMA 的情况下,外设的 FIFO 可以作为源或目标。当外设作为源时,内存位置(内部或外部)作为目标地址;当外设作为目标时,内存位置(内部或外部)作为源地址。根据传输方向,需要指定源或目标,即 DMA 传输需要合适的内存映射。

2.3 缓存一致性与 DMA

如前文所述,最近访问的内存区域副本会存储在缓存中,这也适用于 DMA 内存。实际上,两个独立设备共享的内存通常是缓存一致性问题的根源。缓存不一致是由于其他设备可能不知道写入设备的更新而导致的问题。而缓存一致性确保每个写入操作看起来是即时发生的,使得共享同一内存区域的所有设备看到完全相同的更改序列。

以下是一个缓存一致性问题的示例场景:假设一个配备缓存的 CPU 和一个可以通过 DMA 直接访问的外部内存。当 CPU 访问内存中的位置 X 时,当前值会存储在缓存中。后续对 X 的操作将更新缓存中的 X 副本,但不会更新外部内存中的 X 版本(假设是回写缓存)。如果在下一次设备尝试访问 X 之前缓存没有刷新到内存,设备将收到 X 的陈旧值。同样,如果设备向内存写入新值时缓存中的 X 副本没有失效,CPU 将对 X 的陈旧值进行操作。

解决这个问题有两种方法:
- 基于硬件的解决方案 :这样的系统是一致系统。
- 基于软件的解决方案 :操作系统负责确保缓存一致性,这样的系统称为非一致系统。

2.4 DMA 映射类型

任何合适的 DMA 传输都需要合适的内存映射。DMA 映射包括分配 DMA 缓冲区并为其生成总线地址,设备实际使用的是总线地址,总线地址是 dma_addr_t 类型的实例。DMA 映射分为两种类型:
- 连贯 DMA 映射 :可以在多次传输中使用,会自动解决缓存一致性问题,但成本较高。通常在驱动程序的生命周期内存在,适用于需要长期使用的缓冲区。
- 流式 DMA 映射 :有更多限制,不会自动解决一致性问题,但可以通过每次传输之间的几个函数调用来解决。通常在 DMA 传输完成后取消映射。

在代码中,处理 DMA 映射需要包含以下头文件:

#include <linux/dma-mapping.h>

2.5 连贯映射

使用以下函数设置连贯映射:

void *dma_alloc_coherent(struct device *dev, size_t size,
                     dma_addr_t *dma_handle, gfp_t flag)

该函数负责分配和映射缓冲区,并返回一个内核虚拟地址,该缓冲区大小为 size 字节,可由 CPU 访问。 dev 是设备结构体,第三个参数是指向关联总线地址的输出参数。为映射分配的内存保证是物理连续的, flag 决定了内存的分配方式,通常使用 GFP_KERNEL GFP_ATOMIC (如果处于原子上下文)。

这种映射具有以下特点:
- 一致性(连贯):为设备执行 DMA 分配无缓存、无缓冲的内存。
- 同步性:设备或 CPU 的写入操作可以立即被对方读取,无需担心缓存一致性问题。

使用以下函数释放映射:

void dma_free_coherent(struct device *dev, size_t size,
                 void *cpu_addr, dma_addr_t dma_handle);

其中, cpu_addr 对应 dma_alloc_coherent() 返回的内核虚拟地址。这种映射成本较高,最小分配单位是一个页面,实际上只分配 2 的幂次方数量的页面。可以使用 int order = get_order(size) 获取页面的阶数。应将这种映射用于设备生命周期内持续使用的缓冲区。

2.6 流式 DMA 映射

流式映射与连贯映射不同,具有以下特点:
- 需要与已经分配的缓冲区一起工作。
- 可以接受多个非连续和分散的缓冲区。
- 映射的缓冲区属于设备而不是 CPU,在 CPU 使用缓冲区之前,需要先取消映射(在 dma_unmap_single() dma_unmap_sg() 之后),这是为了缓存目的。
- 对于写事务(CPU 到设备),驱动程序应在映射之前将数据放入缓冲区。
- 需要指定数据移动的方向,并且数据只能根据该方向使用。

不能在缓冲区未取消映射时访问它的原因是 CPU 映射是可缓存的。用于流式映射的 dma_map_*() 系列函数会首先清理/使与缓冲区相关的缓存失效,并依赖 CPU 在相应的 dma_unmap_*() 之前不访问该缓冲区。之后,如果有任何推测性预取,会再次使缓存失效,然后 CPU 才能读取设备写入内存的数据。

流式映射有两种形式:
- 单缓冲区映射 :只允许一页映射。
- 分散/聚集映射 :允许传递多个(分散在内存中的)缓冲区。

综上所述,内核内存管理和 DMA 直接内存访问是计算机系统中非常重要的部分,它们相互配合,共同提高了系统的性能和效率。通过合理使用缓存策略、内存映射和 DMA 技术,可以优化系统资源的利用,减少 CPU 负载,提升应用程序的响应速度和整体性能。

2.7 DMA 映射操作流程总结

为了更清晰地理解 DMA 映射的操作流程,下面用 mermaid 流程图展示连贯映射和流式映射的主要操作步骤:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B{选择映射类型}:::decision
    B -->|连贯映射| C(调用 dma_alloc_coherent):::process
    C --> D(使用映射的缓冲区进行 DMA 操作):::process
    D --> E(调用 dma_free_coherent 释放映射):::process
    E --> F([结束]):::startend
    B -->|流式映射| G(准备已分配的缓冲区):::process
    G --> H{选择映射形式}:::decision
    H -->|单缓冲区映射| I(调用 dma_map_single):::process
    H -->|分散/聚集映射| J(调用 dma_map_sg):::process
    I --> K(进行 DMA 操作):::process
    J --> K
    K --> L(调用 dma_unmap_single 或 dma_unmap_sg 取消映射):::process
    L --> F

这个流程图展示了连贯映射和流式映射的基本操作流程。对于连贯映射,先分配映射,然后进行 DMA 操作,最后释放映射;对于流式映射,先准备缓冲区,选择映射形式,进行映射,完成 DMA 操作后取消映射。

2.8 DMA 映射的使用建议

在实际使用中,应根据具体需求选择合适的 DMA 映射类型:
- 选择连贯映射的情况 :当需要在多次 DMA 传输中使用同一缓冲区,并且对缓存一致性有严格要求,同时不介意较高的成本时,应选择连贯映射。例如,对于一些需要长期稳定运行的设备驱动程序,其缓冲区在设备的整个生命周期内都需要使用,此时连贯映射是一个不错的选择。
- 选择流式映射的情况 :当缓冲区已经分配好,并且可能需要处理多个非连续的缓冲区,对成本较为敏感,并且能够在每次传输之间进行额外的缓存管理操作时,应选择流式映射。例如,在一些数据处理场景中,数据可能分散在不同的内存区域,此时流式映射的分散/聚集功能就可以很好地发挥作用。

2.9 缓存策略与 DMA 的综合应用

在实际系统中,缓存策略和 DMA 技术通常需要综合应用,以达到最佳的性能和资源利用效果。以下是一些综合应用的建议:
- 结合回写缓存和 DMA :Linux 采用的回写缓存策略可以与 DMA 技术相结合。在进行 DMA 传输时,利用回写缓存可以减少对磁盘的直接写入操作,提高系统的写入性能。例如,当设备通过 DMA 将数据写入内存时,数据可以先存储在页面缓存中,标记为脏页,然后由刷新线程在合适的时机将数据写回磁盘。
- 根据缓存一致性问题调整 DMA 映射 :在存在缓存一致性问题的系统中,应根据具体情况选择合适的 DMA 映射类型。对于一致系统,可以使用连贯映射来自动解决缓存一致性问题;对于非一致系统,则需要在软件层面进行额外的缓存管理操作,如在流式映射中使用 dma_map_*() dma_unmap_*() 函数来清理和使缓存失效。

2.10 总结

本文详细介绍了内核内存管理和 DMA 直接内存访问的相关知识,包括内存映射、缓存系统、写缓存策略、Devres 机制以及 DMA 映射的类型和操作等内容。通过对这些知识的理解和应用,可以更好地优化计算机系统的性能和资源利用。

  • 内核内存管理 :通过合理使用缓存策略,如直写缓存、绕写缓存和回写缓存,可以减少数据访问延迟,提高应用性能,延长存储设备寿命,降低数据丢失风险。同时,Devres 机制可以简化驱动程序中的资源管理和错误处理。
  • DMA 直接内存访问 :DMA 技术允许设备在无需 CPU 干预的情况下访问主系统内存,提高了系统的吞吐量。通过选择合适的 DMA 映射类型,如连贯映射和流式映射,可以解决缓存一致性问题,并根据具体需求进行灵活的内存管理。

在实际应用中,应根据系统的具体需求和特点,综合考虑内核内存管理和 DMA 技术的应用,以达到最佳的性能和资源利用效果。例如,在嵌入式系统中,需要考虑设备的资源限制和性能要求,选择合适的缓存策略和 DMA 映射类型,以提高系统的稳定性和响应速度。

基于实时迭代的数值鲁棒NMPC双模稳定预测模型(Matlab代码实现)内容概要:本文介绍了基于实时迭代的数值鲁棒非线性模型预测控制(NMPC)双模稳定预测模型的研究Matlab代码实现,重点在于通过数值方法提升NMPC在动态系统中的鲁棒性稳定性。文中结合实时迭代机制,构建了能够应对系统不确定性外部扰动的双模预测控制框架,并利用Matlab进行仿真验证,展示了该模型在复杂非线性系统控制中的有效性实用性。同时,文档列举了大量相关的科研方向技术应用案例,涵盖优化调度、路径规划、电力系统管理、信号处理等多个领域,体现了该方法的广泛适用性。; 适合人群:具备一定控制理论基础和Matlab编程能力,从事自动化、电气工程、智能制造等领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①用于解决非线性动态系统的实时控制问题,如机器人控制、无人机路径跟踪、微电网能量管理等;②帮助科研人员复现论文算法,开展NMPC相关创新研究;③为复杂系统提供高精度、强鲁棒性的预测控制解决方案。; 阅读建议:建议读者结合提供的Matlab代码进行仿真实践,重点关注NMPC的实时迭代机制双模稳定设计原理,并参考文档中列出的相关案例拓展应用场景,同时可借助网盘资源获取完整代码数据支持。
UWB-IMU、UWB定位对比研究(Matlab代码实现)内容概要:本文介绍了名为《UWB-IMU、UWB定位对比研究(Matlab代码实现)》的技术文档,重点围绕超宽带(UWB)惯性测量单元(IMU)融合定位技术展开,通过Matlab代码实现对两种定位方式的性能进行对比分析。文中详细阐述了UWB单独定位UWB-IMU融合定位的原理、算法设计及仿真实现过程,利用多传感器数据融合策略提升定位精度稳定性,尤其在复杂环境中减少信号遮挡和漂移误差的影响。研究内容包括系统建模、数据预处理、滤波算法(如扩展卡尔曼滤波EKF)的应用以及定位结果的可视化误差分析。; 适合人群:具备一定信号处理、导航定位或传感器融合基础知识的研究生、科研人员及从事物联网、无人驾驶、机器人等领域的工程技术人员。; 使用场景及目标:①用于高精度室内定位系统的设计优化,如智能仓储、无人机导航、工业巡检等;②帮助理解多源传感器融合的基本原理实现方法,掌握UWBIMU互补优势的技术路径;③为相关科研项目或毕业设计提供可复现的Matlab代码参考实验验证平台。; 阅读建议:建议读者结合Matlab代码逐段理解算法实现细节,重点关注数据融合策略滤波算法部分,同时可通过修改参数或引入实际采集数据进行扩展实验,以加深对定位系统性能影响因素的理解。
本系统基于MATLAB平台开发,适用于2014a、2019b及2024b等多个软件版本,并提供了可直接执行的示例数据集。代码采用模块化设计,关键参数均可灵活调整,程序结构逻辑分明且附有详细说明注释。主要面向计算机科学、电子信息工程、数学等相关专业的高校学生,适用于课程实验、综合作业及学位论文等教学科研场景。 水声通信是一种借助水下声波实现信息传输的技术。近年来,多输入多输出(MIMO)结构正交频分复用(OFDM)机制被逐步整合到水声通信体系中,显著增强了水下信息传输的容量稳健性。MIMO配置通过多天线收发实现空间维度上的信号复用,从而提升频谱使用效率;OFDM方案则能够有效克服水下信道中的频率选择性衰减问题,保障信号在复杂传播环境中的可靠送达。 本系统以MATLAB为仿真环境,该工具在工程计算、信号分析通信模拟等领域具备广泛的应用基础。用户可根据自身安装的MATLAB版本选择相应程序文件。随附的案例数据便于快速验证系统功能性能表现。代码设计注重可读性可修改性,采用参数驱动方式,重要变量均设有明确注释,便于理解后续调整。因此,该系统特别适合高等院校相关专业学生用于课程实践、专题研究或毕业设计等学术训练环节。 借助该仿真平台,学习者可深入探究水声通信的基础理论及其关键技术,具体掌握MIMOOFDM技术在水声环境中的协同工作机制。同时,系统具备良好的交互界面可扩展架构,用户可在现有框架基础上进行功能拓展或算法改进,以适应更复杂的科研课题或工程应用需求。整体而言,该系统为一套功能完整、操作友好、适应面广的水声通信教学科研辅助工具。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值