当前位置:首页>Linux>Linux DMA技术深度解析:原理拆解到实战落地

Linux DMA技术深度解析:原理拆解到实战落地

  • 2026-02-09 21:29:28
Linux DMA技术深度解析:原理拆解到实战落地
在计算机的世界里,数据传输就像是一场繁忙的物资搬运工作。
在早期,CPU 就如同一位勤劳的 “搬砖工”,亲力亲为地参与每一次数据的搬运。当外设(如网卡、磁盘等)需要与内存进行数据交换时,CPU 需要一条指令接着一条指令地将数据从内存读取到寄存器,再从寄存器写入到外设,反之亦然。这种传统的编程 I/O(PIO)方式,虽然能够完成数据传输任务,但效率极其低下。想象一下,CPU 本应专注于运行复杂的算法、处理系统调度等高价值任务,却不得不花费大量时间在重复性的数据搬运上,就如同让一位高级工程师天天去做体力搬运工作,实在是大材小用。

而 DMA 技术的出现,就像是为数据传输配备了一位 “专属快递员”。DMA,即 Direct Memory Access(直接内存访问),它允许外部设备绕过 CPU,直接与内存进行高速数据交换。当有数据传输需求时,CPU 只需向 DMA 控制器下达 “任务清单”,包括源地址、目标地址、传输数据量以及传输方向等参数,然后 DMA 控制器就会接管系统总线,独立完成数据的搬运工作。在这个过程中,CPU 可以去执行其他更重要的任务,大大提高了系统的整体效率。

以网卡为例,当我们的计算机通过网络接收数据时,千兆甚至万兆网络接口卡每秒要处理大量的数据包。如果没有 DMA,CPU 需要频繁地响应网卡中断,逐个读取数据包并写入内存,这将导致 CPU 负载急剧升高,系统性能大幅下降。而借助 DMA,网卡可以直接将接收到的数据包写入内核缓冲区,CPU 只需在传输完成后进行一些简单的处理,极大地减轻了 CPU 的负担,提高了网络通信的速度。

再看磁盘 I/O,磁盘控制器使用 DMA 来读写文件系统缓存。当我们从硬盘读取一个大文件时,DMA 可以直接在硬盘和内存之间传输数据,避免了 CPU 介入每个数据块的传输过程,使得文件读取速度大幅提升。同样,在图形处理领域,现代显卡通过 DMA 传输显存数据,能够快速地将渲染所需的数据从内存传输到显卡,从而实现流畅的图形显示和高效的视频编辑等操作。

可以说,DMA 技术是 Linux 高性能 I/O 的 “隐形功臣”,它在幕后默默地提升着系统的性能,使得各种高性能的应用场景成为可能。在接下来的内容中,我们将深入探讨 Linux DMA 技术的原理、实现机制以及在实际开发中的应用。

一、Linux DMA 的底层逻辑

1.1 DMA 的核心使命

Linux DMA 子系统肩负着一项核心使命,那就是在 CPU 可见的虚拟 / 物理地址与设备可见的总线地址(DMA 地址)之间,构建起一座稳固且高效的桥梁。这看似简单的任务,实则面临着诸多复杂而棘手的技术挑战,其中最为关键的有以下三大难题。

首先是地址转换难题。不同设备的寻址能力千差万别,它们通常只能访问有限的物理地址范围,这个范围由设备的 dma_mask 所定义。而内核在分配内存时,可能会将内存分配到高地址区域,这就导致设备无法直接访问这些内存。DMA 子系统必须精准地判断设备的寻址能力,通过巧妙的地址转换机制,确保提供给设备的地址在其可访问的范围之内。例如,在一些 32 位设备中,dma_mask 可能只能覆盖较低的 4GB 地址空间,如果内核分配的内存位于 4GB 以上的高地址区域,DMA 子系统就需要采取特殊的转换方式,让设备能够 “看到” 并访问这些内存 。

缓存一致性问题也不容小觑。在现代计算机系统中,CPU 为了提高数据访问速度,普遍配备了多级高速缓存。当 CPU 访问内存时,首先会在缓存中查找数据,如果缓存中存在(即缓存命中),就直接从缓存中读取或写入数据;只有在缓存未命中时,才会访问主存。而 DMA 传输是直接访问物理内存,这就可能导致一个问题:如果 CPU 修改了缓存中的数据,但尚未将这些数据写回到主存,此时设备通过 DMA 访问主存,读取到的就是旧数据,从而引发数据不一致的错误。为了解决这个问题,DMA 子系统必须提供有效的缓存一致性机制,确保 CPU 缓存和主存之间的数据同步。

内存连续性问题同样困扰着 DMA 的实现。一些老式设备或者对内存访问有特殊要求的设备,要求 DMA 缓冲区在物理内存上必须是连续的。然而,内核的页分配器(如伙伴系统)在系统运行一段时间后,由于内存的频繁分配和释放,会产生内存碎片,很难再分配出大块连续的物理内存。这就需要 DMA 子系统寻找其他解决方案,以满足设备对连续内存的需求。

1.2 四类 DMA 内存类型

为了应对上述复杂多样的挑战,满足不同设备和应用场景的需求,Linux 内核精心设计了四类 DMA 内存类型,每一种类型都犹如一把独特的 “钥匙”,精准适配特定的场景。

1.2.1 一致性 DMA 内存(Coherent DMA Memory)

一致性 DMA 内存就像是 CPU 和设备之间的 “实时共享空间”。它的工作原理基于非缓存(uncached)或写通(write - combining)模式。在这种模式下,无论是 CPU 对内存的写入,还是设备对内存的写入,都会立即对另一方可见,就好像两者直接共享了同一份数据,无需任何额外的同步操作,硬件自动保证了数据的一致性。

在代码实现上,我们可以通过 dma_alloc_coherent () 函数来分配一致性 DMA 内存,使用 dma_free_coherent () 函数进行释放。例如:

#include<linux/dma-mapping.h>#include<linux/device.h>staticintmy_probe(struct device *dev){    dma_addr_t dma_handle;    size_t size = 1024;    void *cpu_addr;    // 分配DMA缓冲区    cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);    if (!cpu_addr) {        dev_err(dev, "DMA alloc failed\n");        return -ENOMEM;    }    // 使用CPU地址和DMA地址    dev_info(dev, "CPU addr: %p, DMA addr: %pad\n", cpu_addr, &dma_handle);    // 释放DMA缓冲区    dma_free_coherent(dev, size, cpu_addr, dma_handle);    return 0;}

这种内存类型适用于一些小块数据且频繁交互的场景,比如设备的控制结构体、描述符环等。这些数据需要在 CPU 和设备之间频繁传递,并且对数据的一致性要求极高,一致性 DMA 内存能够很好地满足这些需求。不过,由于其非缓存的特性,数据访问速度相对较慢,这也是在使用时需要权衡的地方。

1.2.2 流式 DMA(Streaming DMA)

流式 DMA 是 Linux 系统中最为常用的 DMA 类型,它就像是一条高效的数据传输 “流水线”。内核为其分配的是普通的、可缓存的内存,这意味着它可以充分利用 CPU 缓存的优势,对于大块数据的传输性能表现尤为出色。

在进行 DMA 传输前和传输后,驱动程序需要扮演一个 “协调者” 的角色,显式地调用同步函数。在传输前,调用 dma_sync_single_for_device () 函数,通知 CPU 将缓存中的数据写回主存并失效缓存,确保设备读取到的是最新的数据;传输完成后,调用 dma_sync_single_for_cpu () 函数,通知 CPU 预取设备写入主存的数据到缓存,保证 CPU 后续访问的数据是最新的。通过这种方式,有效地解决了缓存一致性问题。

在 API 使用上,我们通过 dma_map_single () 函数来创建流式 DMA 映射,返回设备可访问的 DMA 地址;使用 dma_unmap_single () 函数来销毁映射。结合同步函数,其典型的使用方式如下:

dma_addr_t dma_handle;dma_handle = dma_map_single(dev, buf, size, direction);if (dma_mapping_error(dev, dma_handle)) {    // 错误处理}// 调用同步函数,准备设备访问dma_sync_single_for_device(dev, dma_handle, size, direction);// 使用dma_handle进行DMA传输// 传输完成后,调用同步函数,准备CPU访问dma_sync_single_for_cpu(dev, dma_handle, size, direction);dma_unmap_single(dev, dma_handle, size, direction);

流式 DMA 适用于许多场景,比如文件系统的读写操作、网络数据包的收发等。在这些场景中,往往需要传输大量的数据,流式 DMA 利用 CPU 缓存的特性,能够显著提高数据传输的效率。

1.2.3 直接映射(Direct Mapping)

直接映射堪称 DMA 操作的 “黄金路径”,一旦条件满足,它就能实现数据的高速、零拷贝传输。当设备的 dma_mask 足够大,能够覆盖整个物理内存时,物理地址(PA)就可以直接作为 DMA 地址(DA)使用,无需进行任何复杂的地址转换。这就好比在内存和设备之间建立了一条 “直达通道”,数据可以直接在两者之间快速传输,实现了真正意义上的零拷贝,大大提高了数据传输的性能。

要实现直接映射,设备必须具备足够宽的地址总线,通常是 64 位,这样才能支持对整个物理内存的寻址。或者,系统通过 I/O 内存管理单元(IOMMU)提供了平坦的 I/O 虚拟地址(IOVA)空间,同样可以满足直接映射的条件。在 Linux 内核中,直接映射的逻辑主要由 direct.c 模块负责处理。这种方式虽然性能极高,但对设备和系统的硬件条件要求也较为苛刻。

1.2.4 SWIOTLB:硬件 IOMMU 缺失时的 “后备方案”

当设备的 dma_mask 较小,例如只能访问低 32 位地址,而内核分配的内存却位于高地址时,直接映射就无法实现了。此时,SWIOTLB(Software I/O TLB)就像一位 “救火队员”,作为软件层面的 “bounce buffer”(弹跳缓冲区)机制介入。

SWIOTLB 会预先在低地址空间分配一块内存池,当需要映射高地址内存时,它会先将数据从高地址拷贝到低地址的 bounce buffer 中,然后将 bounce buffer 的地址交给设备进行访问。传输完成后,再根据数据传输的方向,将数据从 bounce buffer 拷贝回高地址内存。虽然这种方式解决了设备无法访问高地址内存的问题,但不可避免地带来了额外的数据拷贝开销,导致性能显著下降。因此,在实际应用中,应尽量避免使用 SWIOTLB,只有在硬件 IOMMU 不可用的情况下,才将其作为一种兼容性保障的后备方案。

1.3 核心数据结构:支撑 DMA 子系统的 “骨架”

在 Linux DMA 子系统中,有两个核心数据结构起着至关重要的作用,它们就像是支撑整个系统的 “骨架”,确保了 DMA 功能的高效实现。

1.3.1 struct device:设备 DMA 能力的 “信息库”

struct device 是 Linux 设备模型的核心结构,它就像是一个设备的 “信息宝库”,其中包含了丰富的 DMA 相关信息。在这个结构体中,dma_ops 字段是一个指向 dma_map_ops 结构的指针,它就像是一把 “钥匙”,决定了该设备使用哪种 DMA 实现方式,比如是直接映射、IOMMU 还是 SWIOTLB 等。dma_mask 和 coherent_dma_mask 字段则定义了设备能够寻址的最大物理地址,这对于 DMA 子系统在进行地址转换和内存分配时,判断设备的寻址能力至关重要。

cma_area 字段指向为该设备预留的连续内存分配器(CMA)区域,CMA 区域主要用于为设备提供大块连续的物理内存,满足一些对内存连续性有要求的设备需求。dma_io_tlb_mem 字段则指向 SWIOTLB 内存池,当设备需要使用 SWIOTLB 机制时,该字段就会发挥作用。总之,struct device 结构体承载了设备与 DMA 子系统交互的关键信息,是驱动程序与 DMA 子系统沟通的重要桥梁。

1.3.2 struct dma_map_ops:实现 DMA 多态性的 “函数指针表”

struct dma_map_ops 是 DMA 子系统实现多态性的关键所在,它就像是一个 “函数指针表”,定义了一系列标准的 DMA 操作接口,如 alloc、free、map_page、unmap_page、sync_single_for_cpu 等。不同的底层实现,比如 direct.c、swiotlb.c 或者 IOMMU 驱动,会根据自身的特点填充这个结构体,提供各自的实现方式。

上层驱动程序在进行 DMA 操作时,并不需要关心具体的硬件实现细节,只需要通过 dev->dma_ops 间接调用这些接口即可。这种设计模式实现了驱动程序与具体硬件的解耦,使得驱动开发者可以更加专注于功能的实现,而无需花费大量精力去了解底层硬件的差异,大大提高了代码的可移植性和可维护性。

1.4 Linux DMA 子系统目录结构:模块化设计一览

Linux 内核的 DMA 子系统采用了模块化的设计理念,其代码结构清晰,分工明确。在 Linux 6.6 内核中,DMA 子系统的核心代码主要位于 kernel/dma/ 目录下,各个文件各司其职,协同工作。

mapping.c 文件是 DMA 子系统的通用接口层,它就像是一个 “总调度员”,是驱动程序与 DMA 子系统直接交互的入口。所有对外的 dma_* API 都在这个文件中实现,并且它负责根据设备的 dma_ops 和配置信息,将驱动程序的请求准确无误地分发到正确的底层实现模块,无论是直接映射、SWIOTLB 还是 IOMMU。

direct.c 和 direct.h 文件则专注于直接映射的实现,它们处理物理地址可以直接作为 DMA 地址的 “快速路径”。同时,这两个文件中还包含了对 CMA 和 DMA 原子池的调用逻辑,为直接映射提供了必要的支持。

swiotlb.c 文件主要负责软件 IOTLB 的实现,它完整地实现了 bounce buffer 机制,包括内存池的管理、slot 的分配以及数据的拷贝(bounce)等操作,是 SWIOTLB 功能的核心实现文件。

coherent.c 文件承担着一致性内存管理的重任,它主要处理通过设备树(reserved - memory)或平台代码为特定设备预留的一致性内存池,确保一致性 DMA 内存的正确分配和使用。

contiguous.c 文件实现了 CMA(Contiguous Memory Allocator)的集成,提供了从 CMA 区域分配大块物理连续内存的接口,供 direct.c 等模块在需要时调用,以满足设备对连续内存的需求。

pool.c 文件负责 DMA 原子池的管理,为那些不能睡眠的原子上下文,比如中断处理程序,提供小块 DMA 内存的快速分配服务,确保在这些特殊场景下 DMA 操作的高效进行。

通过对这些文件的协同分析,我们可以清晰地看到 Linux DMA 子系统的模块化架构,各个模块之间既相互独立又紧密协作,共同构建了一个高效、灵活的 DMA 管理系统。

二、 核心框架解析:Linux DMA 的两大技术支柱

2.1 DMA Mapping:设备与内存的 “地址翻译官”

在 Linux 的 DMA 体系中,DMA Mapping 扮演着一个至关重要的角色,它就像是设备与内存之间的 “地址翻译官”。我们知道,CPU 使用虚拟地址来访问内存,这是一种经过内存管理单元(MMU)映射后的逻辑地址,主要是为了提供内存保护、地址空间隔离以及虚拟内存等功能,方便操作系统对内存进行高效管理。而设备(如网卡、硬盘控制器等)则是直接通过物理总线访问内存,它们只能识别总线地址(也称为 DMA 地址)。这就好比两个说不同语言的人,CPU 说的是 “虚拟地址语言”,设备说的是 “总线地址语言”,如果没有一个 “翻译官”,它们之间就无法顺畅地交流,数据传输也就无法完成 。

Linux 内核将 DMA Mapping 分为两大类,以适配不同的传输场景。

2.1.1 一致性映射(Coherent DMA Mapping)

一致性映射就像是为 CPU 和设备搭建了一座 “实时同步桥梁”。它会分配一块特殊的内存,这块内存同时被 CPU 和 DMA 设备访问,并且内核会保证该内存的缓存一致性。也就是说,无论 CPU 写入数据,还是 DMA 设备读取或写入数据,双方都能立即看到最新的数据,就好像它们在共享同一份实时更新的文档。

在代码实现上,我们可以使用 dma_alloc_coherent 函数来分配一致性 DMA 内存。例如:

#include<linux/dma-mapping.h>#include<linux/device.h>staticintmy_driver_probe(struct device *dev){    dma_addr_t dma_handle;    size_t size = 1024// 分配1KB内存    void *cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);    if (!cpu_addr) {        dev_err(dev, "Failed to allocate coherent DMA memory\n");        return -ENOMEM;    }    // 使用cpu_addr进行CPU访问,dma_handle进行设备访问    //...    // 释放内存    dma_free_coherent(dev, size, cpu_addr, dma_handle);    return 0;}

这种映射方式适用于一些对数据一致性要求极高的场景,比如 NVMe SSD 的提交队列(SQ)和完成队列(CQ)。这些队列长期存在,并且需要 CPU 与 NVMe SSD 控制器频繁交互,任何数据不一致都可能导致严重的错误,所以一致性映射能够很好地满足它们的需求。不过,由于一致性映射需要硬件支持缓存一致性协议,并且为了保证一致性,可能会有额外的性能开销,比如频繁地刷新缓存等。

2.1.2 流式映射(Streaming DMA Mapping)

流式映射则像是一个 “临时翻译服务”,它适用于一次性数据传输的场景,如文件读写、网络数据包收发等。在这种模式下,驱动程序在每次 DMA 传输前,动态地将一个已存在的内存缓冲区映射供 DMA 使用,并在传输结束后立即解除映射。

它不保证硬件 / CPU 缓存一致性,这就需要驱动程序在访问缓冲区之前,显式地调用同步函数来刷新缓存,从而同步缓冲区。例如,在数据从 CPU 传输到设备时,调用 dma_sync_single_for_device 函数,通知 CPU 将缓存中的数据写回主存,确保设备读取到的是最新数据;在数据从设备传输到 CPU 时,调用 dma_sync_single_for_cpu 函数,使 CPU 缓存失效,保证 CPU 读取的是设备写入主存的最新数据。

其 API 使用示例如下:

#include<linux/dma-mapping.h>#include<linux/device.h>staticintmy_driver_transfer_data(struct device *dev, void *buf, size_t size, enum dma_transfer_direction dir){    dma_addr_t dma_handle;    dma_handle = dma_map_single(dev, buf, size, dir);    if (dma_mapping_error(dev, dma_handle)) {        dev_err(dev, "DMA map single failed\n");        return -EINVAL;    }    // 调用同步函数,准备设备访问    dma_sync_single_for_device(dev, dma_handle, size, dir);    // 进行DMA传输    //...    // 传输完成后,调用同步函数,准备CPU访问    dma_sync_single_for_cpu(dev, dma_handle, size, dir);    dma_unmap_single(dev, dma_handle, size, dir);    return 0;}

流式映射的优点在于它可以使用普通的、可缓存的内存,能够充分利用 CPU 缓存的优势,对于大块数据的传输性能表现出色。同时,它的灵活性高,可以映射任意已有的内存缓冲区。但缺点是需要手动管理同步时机,如果忘记同步,很容易导致数据不一致的问题,并且其实现复杂度相对较高,需要开发者深入理解缓存一致性的原理。

2.2 DMA Engine:屏蔽硬件差异的 “通用抽象层”

DMA Engine 是 Linux 内核中为传统外设 DMA 传输设计的通用抽象框架,它就像是一个 “通用翻译器”,核心目标是屏蔽不同厂商 DMA 控制器的硬件差异,为上层外设驱动(如网卡、声卡、串口驱动)提供统一的 DMA 操作接口,极大地简化了驱动开发的过程,并提升了代码的复用性。

其代码实现位于内核源码的 drivers/dma/ 目录下,与 DMA Mapping 的代码位置(kernel/dma/)不同,这也从侧面反映了它们不同的职责和定位。在这个目录下,包含了各种具体 DMA 控制器的驱动代码,以及实现 DMA Engine 核心逻辑的文件。

以网卡驱动开发为例,在没有 DMA Engine 之前,不同厂商的网卡 DMA 控制器可能有不同的寄存器布局、操作方式和中断处理逻辑。驱动开发者需要针对每一款网卡,深入了解其硬件细节,编写大量与硬件紧密耦合的代码。而有了 DMA Engine 之后,驱动开发者只需要使用 DMA Engine 提供的统一接口,如 dma_request_channel 获取 DMA 通道、dmaengine_submit 提交 DMA 传输请求等,就可以实现数据的高效传输,无需关心底层硬件的具体实现。

例如,下面是一段简单的使用 DMA Engine 进行内存到内存数据拷贝的代码示例:

#include<linux/dmaengine.h>#include<linux/device.h>staticintmy_memcpy_with_dmaengine(struct device *dev, void *dst, void *src, size_t size){    struct dma_chan *chan;    struct dma_slave_config config;    struct dma_transaction tx;    int ret;    // 请求DMA通道    chan = dma_request_channel(DMA_MEMCPY, NULLNULL);    if (!chan) {        dev_err(dev, "Failed to request DMA channel\n");        return -ENODEV;    }    // 配置DMA从设备    memset(&config, 0sizeof(config));    config.direction = DMA_MEM_TO_MEM;    config.src_addr = (dma_addr_t)src;    config.dst_addr = (dma_addr_t)dst;    config.src_maxburst = 16;    config.dst_maxburst = 16;    config.src_addr_width = DMA_SLAVE_BUSWIDTH_32BIT;    config.dst_addr_width = DMA_SLAVE_BUSWIDTH_32BIT;    ret = dmaengine_slave_config(chan, &config);    if (ret) {        dev_err(dev, "Failed to configure DMA slave\n");        dma_release_channel(chan);        return ret;    }    // 提交DMA传输请求    memset(&tx, 0sizeof(tx));    tx.chained = false;    tx.src_addr = (dma_addr_t)src;    tx.dst_addr = (dma_addr_t)dst;    tx.len = size;    ret = dmaengine_submit(chan, &tx);    if (ret) {        dev_err(dev, "Failed to submit DMA transaction\n");        dma_release_channel(chan);        return ret;    }    // 启动DMA传输    dma_async_issue_pending(chan);    // 等待传输完成    dmaengine_terminate_sync(chan);    // 释放DMA通道    dma_release_channel(chan);    return 0;}

通过这段代码,我们可以清晰地看到 DMA Engine 提供的接口的使用方式,以及它如何将复杂的硬件操作抽象化,使得驱动开发变得更加简洁和高效。

对比 DMA Mapping 和 DMA Engine,DMA Mapping 主要关注的是如何为设备提供可访问的内存,并处理好地址转换和缓存一致性等问题;而 DMA Engine 则侧重于提供统一的 DMA 操作接口,屏蔽硬件差异,实现数据的高效传输。两者相互配合,共同构建了 Linux 强大的 DMA 体系。

2.3 补充知识点:DMA 的传输模式与地址类型

2.3.1 传输模式:Block DMA 与 Scatter-Gather DMA

在 DMA 的数据传输过程中,有两种主要的传输模式,它们各自有着独特的特点和适用场景,就像是不同类型的运输工具,适用于不同的货物运输需求。

Block DMA

Block DMA,也被称为传统的普通 DMA,它就像是一辆只能运输整箱货物的卡车,一次 DMA 传输只能处理一个物理上连续的内存区域。例如,当我们需要从磁盘读取一个文件时,如果文件在磁盘上的存储位置是连续的,并且文件大小不超过内存中连续可用空间的大小,那么就可以使用 Block DMA 来进行传输。它的硬件复杂度相对较低,因为它只需要处理单一的连续内存块,控制逻辑较为简单。在一些对数据传输要求不高、数据存储相对连续的场景中,Block DMA 能够很好地发挥作用,比如早期的软盘驱动器数据传输,由于软盘的数据存储方式相对简单,使用 Block DMA 就可以满足其基本的传输需求。

Scatter-Gather DMA

Scatter-Gather DMA 则像是一支拥有多辆卡车的运输车队,单次 DMA 传输可以处理多个物理上不连续的内存区域。这些不连续的内存区域通过一个链表来描述,DMA 控制器得到链表首地址后,会根据链表的指引,依次传输下一块物理上连续的数据,直到所有数据传输完毕。这种传输模式的出现,主要是为了应对现代计算机系统中内存碎片化的问题。随着系统的运行,内存会被频繁地分配和释放,导致内存空间变得碎片化,很难找到大块连续的内存区域。而 Scatter-Gather DMA 能够将分散在不同内存区域的数据,通过链表组织起来,一次性完成传输,大大提高了数据传输的效率。

在网络数据包的传输中,由于数据包在内存中的存储可能是分散的,使用 Scatter-Gather DMA 就可以避免将分散的数据先拷贝到一个连续的内存区域,然后再进行传输的繁琐过程,直接从分散的内存区域中读取数据并传输,减少了内存拷贝的开销,提高了网络传输的性能。在文件系统操作中,当读取或写入一个大文件时,如果文件片段分布在磁盘的不同位置,Scatter-Gather DMA 也可以有效地提高 I/O 操作的速度。

除了这两种基本的传输模式,DMA 还使用 channel(通道)来实现并行传输。每个通道互相独立,拥有自己的寄存器组、请求 / 响应逻辑以及中断信号通路,可以独立服务一个外设或一个外设的某类传输任务。通过这种方式,多个外设可以同时进行 DMA 传输,提高了系统的整体数据传输能力。一些高性能服务器中,多个网卡可以通过各自独立的 DMA 通道,同时进行网络数据的收发,大大提升了服务器的网络吞吐能力。

另外,还可以通过时间分片和上下文快速切换,实现虚拟通道技术。这种技术可以在一个物理通道上,通过分时复用的方式,实现多个虚拟通道的功能,进一步提高了通道的利用率。在一些资源有限的嵌入式系统中,虚拟通道技术可以在不增加硬件成本的前提下,满足多个外设对 DMA 通道的需求。

2.3.2 三大地址:虚拟地址、物理地址、总线地址

在 Linux DMA 的世界里,有三类重要的地址概念,它们就像是三把不同的 “钥匙”,分别开启不同的 “大门”,理解它们之间的关系是掌握 DMA 技术的关键前提。

虚拟地址

虚拟地址是 CPU 使用的地址,它是一种逻辑地址,就像是一个房间的门牌号。在现代操作系统中,为了实现内存保护、地址空间隔离以及虚拟内存等功能,每个进程都被分配了一个独立的虚拟地址空间。例如,在 32 位系统中,每个进程可以拥有 4GB 的虚拟地址空间,进程在运行时,所有的内存访问操作都是基于虚拟地址进行的。虚拟地址通过内存管理单元(MMU)进行转换,最终映射到物理地址。这就好比我们通过门牌号找到对应的房间,但实际进入房间时,需要通过一个 “转换机制”(MMU)来找到房间的实际位置(物理地址)。

物理地址

物理地址是内存芯片上存储单元的实际地址,它是内存的真实位置,就像是房间在大楼中的实际楼层和房间号。当 CPU 需要访问内存时,经过 MMU 的地址转换,将虚拟地址转换为物理地址,才能真正访问到内存中的数据。物理地址是内存硬件层面的地址,与内存的物理布局和芯片特性密切相关。

总线地址

总线地址是设备(如 DMA 设备)使用的地址,用于在设备和内存之间进行数据传输。在没有 I/O 内存管理单元(IOMMU)的情况下,总线地址通常与物理地址一致,或者存在一个固定的偏移量。这是因为设备直接通过物理总线访问内存,所以它所使用的地址需要与内存的物理地址相对应。而在有 IOMMU 的情况下,IOMMU 会对总线地址进行地址映射,将设备使用的总线地址转换为物理地址,实现设备对内存的正确访问。这就好比设备通过一个 “中介”(IOMMU)来找到内存的实际位置,即使内存的物理地址发生了变化,设备只需要使用 IOMMU 映射后的总线地址,就可以正确地访问内存。

理解这三类地址的概念和它们之间的转换关系,对于深入理解 Linux DMA 的工作原理至关重要。在 DMA 传输过程中,需要将 CPU 使用的虚拟地址转换为设备可识别的总线地址,确保数据能够在设备和内存之间准确无误地传输。

三、 实战:从驱动开发到 AXI-DMA 优化

3.1 手把手教你写基础 DMA 驱动:核心步骤拆解

在 Linux 系统中开发基础的 DMA 驱动,就像是搭建一座稳固的桥梁,需要精心规划每一个步骤,确保数据能够在设备和内存之间顺畅传输。下面我们将逐步拆解核心步骤,帮助你掌握 DMA 驱动开发的精髓。

3.1.1 驱动初始化:DMA 控制器与设备配置

驱动初始化是 DMA 驱动开发的第一步,也是至关重要的一步。在这一步中,我们需要调用 init_dma () 函数来配置 DMA 控制器的各项参数,使其能够正常工作。这个过程就像是为 DMA 控制器设置 “工作指南”,明确它的工作方式和职责。

我们需要根据设备的具体需求,设置 DMA 控制器的源地址、目标地址、传输数据长度以及传输方向等关键参数。这些参数就像是为 DMA 控制器规划的 “运输路线”,决定了数据从哪里来,到哪里去,以及运输的数量和方向。例如:

staticintinit_dma(struct device *dev){    struct dma_device *dma_dev;    struct dma_chan *chan;    // 查找并获取DMA设备    dma_dev = dma_find_device("my_dma_device", dev, NULLNULL);    if (!dma_dev) {        dev_err(dev, "DMA device not found\n");        return -ENODEV;    }    // 获取DMA通道    chan = dma_request_channel(dma_dev, NULLNULL);    if (!chan) {        dev_err(dev, "Failed to request DMA channel\n");        dma_release_device(dma_dev);        return -ENODEV;    }    // 设置dma_mask,指定设备的寻址能力    if (!dma_set_mask_and_coherent(chan->device->dev, DMA_BIT_MASK(32))) {        dev_err(dev, "DMA mask setting failed\n");        dma_release_channel(chan);        dma_release_device(dma_dev);        return -EIO;    }    // 绑定设备与DMA通道    dev_set_dma_ops(dev, &my_dma_ops, NULL);    return 0;}

在上述代码中,我们首先使用 dma_find_device 函数查找并获取指定的 DMA 设备,这就像是在众多设备中找到我们需要的那个 “运输工具”。然后,通过 dma_request_channel 函数获取 DMA 通道,这个通道就像是一条专门的数据运输通道,确保数据能够高效传输。接着,使用 dma_set_mask_and_coherent 函数设置 dma_mask,明确设备的寻址能力,避免出现地址访问错误。最后,通过 dev_set_dma_ops 函数将设备与 DMA 通道进行绑定,就像是将设备与运输通道连接起来,确保数据能够准确无误地传输到目标位置。

3.1.2 传输实现:参数设置与启停控制

完成驱动初始化后,接下来就是实现 DMA 传输的核心逻辑。这一步就像是启动一辆装满货物的卡车,需要精确设置各项参数,确保运输过程顺利进行。我们通过 start_dma_transfer () 函数来配置 DMA 传输的关键参数,包括源地址、目的地址和数据长度等。这些参数就像是为卡车规划的具体运输路线和货物数量,决定了数据传输的细节。例如:

staticvoidstart_dma_transfer(struct device *dev, void *src, void *dst, size_t len){    struct dma_chan *chan = dev_get_dma_chan(dev);    struct dma_slave_config config;    // 配置DMA从设备参数    memset(&config, 0sizeof(config));    config.direction = DMA_MEM_TO_MEM;    config.src_addr = (dma_addr_t)src;    config.dst_addr = (dma_addr_t)dst;    config.src_maxburst = 16;    config.dst_maxburst = 16;    config.src_addr_width = DMA_SLAVE_BUSWIDTH_32BIT;    config.dst_addr_width = DMA_SLAVE_BUSWIDTH_32BIT;    // 配置DMA从设备    if (dmaengine_slave_config(chan, &config)) {        dev_err(dev, "Failed to configure DMA slave\n");        return;    }    // 提交DMA传输请求    struct dma_transaction tx;    memset(&tx, 0sizeof(tx));    tx.chained = false;    tx.src_addr = (dma_addr_t)src;    tx.dst_addr = (dma_addr_t)dst;    tx.len = len;    if (dmaengine_submit(chan, &tx)) {        dev_err(dev, "Failed to submit DMA transaction\n");        return;    }    // 启动DMA传输    dma_async_issue_pending(chan);}

在上述代码中,我们首先通过 dev_get_dma_chan 函数获取设备的 DMA 通道,这是数据传输的 “主干道”。然后,使用 dmaengine_slave_config 函数配置 DMA 从设备的参数,包括传输方向、源地址、目的地址以及地址宽度和突发传输大小等,确保设备能够正确接收和发送数据。接着,使用 dmaengine_submit 函数提交 DMA 传输请求,就像是向运输公司提交运输任务。最后,通过 dma_async_issue_pending 函数启动 DMA 传输,让数据开始在设备和内存之间传输。

在 DMA 传输完成后,我们还需要处理中断信号,以通知 CPU 传输已经结束。这就像是卡车到达目的地后,需要向发货人发送到货通知。我们可以通过设置中断处理函数来实现这一功能,例如:

staticirqreturn_tmy_dma_irq_handler(int irq, void *dev_id){    struct device *dev = (struct device *)dev_id;    struct dma_chan *chan = dev_get_dma_chan(dev);    // 检查DMA传输状态    if (dmaengine_tx_status(chan, NULLNULL) == DMA_COMPLETE) {        // 传输完成,进行相应处理        dev_info(dev, "DMA transfer completed\n");    } else {        // 传输出错,进行错误处理        dev_err(dev, "DMA transfer error\n");    }    return IRQ_HANDLED;}

在用户空间,我们可以通过 open () 和 ioctl () 函数与内核驱动进行交互,发起 DMA 传输请求。例如:

#include<stdio.h>#include<fcntl.h>#include<sys/ioctl.h>#define MY_DMA_IOC_MAGIC 'Z'#define MY_DMA_IOC_START_TRANSFER _IOWR(MY_DMA_IOC_MAGIC, 0, struct my_dma_transfer)struct my_dma_transfer {    void *src;    void *dst;    size_t len;};intmain(){    int fd = open("/dev/my_dma_device", O_RDWR);    if (fd < 0) {        perror("open");        return 1;    }    struct my_dma_transfer transfer;    transfer.src = (void *)0x10000000;    transfer.dst = (void *)0x20000000;    transfer.len = 1024;    if (ioctl(fd, MY_DMA_IOC_START_TRANSFER, &transfer) < 0) {        perror("ioctl");    }    close(fd);    return 0;}

在上述代码中,我们定义了一个自定义的 ioctl 命令 MY_DMA_IOC_START_TRANSFER,用于发起 DMA 传输请求。在 main 函数中,我们打开设备文件 /dev/my_dma_device,然后使用 ioctl 函数向内核驱动发送传输请求,传递源地址、目的地址和数据长度等参数。内核驱动接收到请求后,会根据这些参数启动 DMA 传输。

3.1.3 驱动卸载:资源释放与清理

当驱动不再使用时,我们需要进行驱动卸载操作,释放所有占用的系统资源,确保系统的稳定性和资源的有效利用。这就像是在运输任务完成后,需要将卡车归还,并清理运输过程中产生的垃圾。在驱动卸载阶段,我们首先需要调用 dma_free_coherent () 函数释放 DMA 内存,这就像是归还占用的仓库空间。例如:

staticvoidmy_driver_remove(struct device *dev){    void *cpu_addr;    dma_addr_t dma_handle;    // 获取之前分配的内存地址和DMA句柄    // 假设之前的代码中已经将这些信息存储在设备的私有数据中    struct my_driver_private *priv = dev_get_drvdata(dev);    cpu_addr = priv->cpu_addr;    dma_handle = priv->dma_handle;    // 释放DMA内存    dma_free_coherent(dev, priv->buffer_size, cpu_addr, dma_handle);    // 解除地址映射    // 假设之前使用了dma_map_single进行映射    dma_unmap_single(dev, dma_handle, priv->buffer_size, DMA_TO_DEVICE);    // 注销DMA通道    struct dma_chan *chan = dev_get_dma_chan(dev);    dma_release_channel(chan);    // 注销中断    free_irq(dev->irq, dev);    // 清理设备私有数据    kfree(priv);}

在上述代码中,我们首先从设备的私有数据中获取之前分配的内存地址和 DMA 句柄,然后使用 dma_free_coherent 函数释放 DMA 内存,使用 dma_unmap_single 函数解除地址映射。接着,通过 dma_release_channel 函数注销 DMA 通道,就像是关闭数据运输通道。最后,使用 free_irq 函数注销中断,清理设备的私有数据,确保没有任何资源残留。规范释放资源对于驱动的稳定性和系统的健康运行至关重要,如果资源没有正确释放,可能会导致内存泄漏、系统崩溃等严重问题。

3.2 进阶实战:AXI-DMA 驱动优化的核心技巧

在嵌入式系统开发中,AXI-DMA(Advanced eXtensible Interface Direct Memory Access)因其高速的数据传输能力而被广泛应用。然而,要充分发挥 AXI-DMA 的性能优势,对其驱动进行优化是必不可少的。下面我们将深入探讨 AXI-DMA 驱动优化的核心技巧,帮助你提升系统的整体性能。

3.2.1 AXI-DMA 硬件架构:接口与缓冲区管理

AXI-DMA 的硬件架构是理解其工作原理和进行驱动优化的基础。它主要由 AXI 总线接口和 DMA 控制器组成,就像是一个高效的物流中心,负责在内存和外设之间快速传输数据。AXI 总线接口包括 AXI 读通道和 AXI 写通道,它们分别负责数据的读取和写入操作,就像是物流中心的进货通道和出货通道,分工明确,确保数据的高效传输。

在 AXI-DMA 的硬件架构中,流控制和缓冲区管理起着关键作用。流控制机制就像是交通警察,确保数据传输的顺序和速率,避免数据拥堵和丢失。缓冲区管理则涉及到缓冲区的创建、分配、映射以及在传输过程中的管理,直接影响着 DMA 操作的性能。缓冲区的大小、对齐方式等参数对传输效率有着重要影响。例如,较大的缓冲区可以减少数据传输的次数,但可能会占用更多的内存资源;而缓冲区的对齐方式不当,则可能导致数据访问错误,降低传输效率。因此,合理配置缓冲区是优化 AXI-DMA 驱动的基础。

3.2.2 性能调优三大方向:中断、内存与调度

在 Petalinux 环境下,我们可以从以下三个核心方向对 AXI-DMA 驱动进行性能调优,以实现数据的高效传输和系统资源的合理利用。

优化中断处理

在传统的 AXI-DMA 驱动中,每次数据传输完成都会产生一个中断信号,通知 CPU 进行处理。然而,频繁的中断响应会占用大量的 CPU 资源,降低系统的整体性能。为了优化这一问题,我们可以采用批量中断的方式,即将多个数据传输任务合并为一组,只有当这一组任务全部完成时,才产生一个中断信号。这样可以大大减少 CPU 的中断响应次数,提高系统的运行效率。

在驱动代码中,我们可以通过设置 DMA 控制器的相关寄存器,调整中断触发的条件和频率。例如,将多个较小的数据传输任务组合成一个较大的传输任务,在传输完成后统一触发中断。同时,我们还可以优化中断服务程序,减少其执行时间,使其能够快速处理中断事件,避免长时间占用 CPU 资源。

优化内存管理

内存管理是影响 AXI-DMA 性能的另一个重要因素。在 Linux 系统中,内存的分配和管理是一个复杂的过程,而 AXI-DMA 对内存的连续性和访问效率有着较高的要求。为了满足这一需求,我们可以利用连续内存分配器(CMA,Contiguous Memory Allocator)来分配连续的物理内存。CMA 就像是一个专门为 AXI-DMA 准备的内存仓库,它可以在系统启动时预留一块连续的内存区域,供 AXI-DMA 在需要时使用。

在 Petalinux 环境下,我们可以通过修改设备树文件,配置 CMA 内存区域的大小和属性。例如,在设备树文件中添加如下配置:

reserved-memory {    #address-cells = <1>;    #size-cells = <1>;    ranges;    cma_region: cma@0 {        compatible = "shared-dma-pool";        reg = <0x0 0x10000000>;  /* 1GB的CMA区域 */        no-map;    };};

这样,系统启动时就会预留 1GB 的连续内存作为 CMA 区域,供 AXI-DMA 使用。在驱动代码中,我们可以通过调用相关的内核函数,从 CMA 区域中分配内存,确保 AXI-DMA 能够使用连续的物理内存进行数据传输,提高传输效率。

优化多通道任务调度

AXI-DMA 通常支持多个通道,每个通道可以独立进行数据传输。在多通道应用场景下,合理的任务调度可以充分利用硬件资源,提高系统的整体性能。我们可以根据不同任务的优先级和数据传输需求,动态分配通道资源,确保重要任务能够优先得到处理。

例如,在一个同时进行视频采集和网络数据传输的系统中,视频采集任务对实时性要求较高,我们可以将其分配到优先级较高的通道上,确保视频数据能够及时传输;而网络数据传输任务对实时性要求相对较低,可以分配到优先级较低的通道上。在驱动代码中,我们可以通过设置 DMA 通道的优先级和调度策略,实现多通道任务的高效调度。

在衡量 AXI-DMA 驱动优化的效果时,我们可以使用传输速率、CPU 利用率等指标。通过对比优化前后这些指标的变化,我们可以直观地评估优化措施的有效性,进而不断调整优化策略,实现 AXI-DMA 驱动性能的最大化。

3.3 实战踩坑:常见问题与排查方法

在 DMA 驱动开发的过程中,难免会遇到各种各样的问题,这些问题就像是路上的绊脚石,阻碍着我们实现高效的数据传输。下面我们将总结一些常见问题,并提供相应的排查方法,帮助你快速解决问题,顺利完成驱动开发。

缓存一致性导致的数据错乱

在 DMA 传输过程中,由于 CPU 缓存和内存之间的一致性问题,可能会出现数据错乱的情况。当 CPU 修改了缓存中的数据,但尚未将这些数据写回到内存时,DMA 设备直接从内存中读取数据,就会读取到旧数据,导致数据不一致。

为了解决这个问题,我们需要在 DMA 传输前后,正确调用同步函数,确保缓存和内存之间的数据一致性。在使用流式 DMA 映射时,在数据传输前,调用 dma_sync_single_for_device 函数,将 CPU 缓存中的数据写回到内存,并使缓存失效,确保设备读取到的是最新数据;在数据传输完成后,调用 dma_sync_single_for_cpu 函数,使 CPU 缓存失效,确保 CPU 读取的是设备写入内存的最新数据。

地址映射失败

地址映射是 DMA 驱动开发中的一个关键环节,如果地址映射失败,DMA 设备将无法正确访问内存,导致数据传输失败。地址映射失败的原因可能有多种,其中 dma_mask 配置不正确是一个常见的原因。dma_mask 定义了设备能够寻址的最大物理地址范围,如果 dma_mask 设置过小,设备将无法访问某些内存地址,从而导致地址映射失败。

当遇到地址映射失败的问题时,我们首先要检查 dma_mask 的配置是否正确。可以通过查看设备的硬件手册,确定设备的寻址能力,然后根据实际情况调整 dma_mask 的设置。此外,还需要检查内存分配和映射的相关代码,确保内存地址的合法性和映射的正确性。

SWIOTLB 性能瓶颈

在一些情况下,当设备的 dma_mask 较小,无法覆盖整个物理内存时,Linux 内核会使用 SWIOTLB(Software I/O TLB)来进行地址转换。SWIOTLB 通过在低地址空间分配一块 bounce buffer,将高地址内存的数据拷贝到 bounce buffer 中,然后让设备访问 bounce buffer。然而,这种方式会带来额外的数据拷贝开销,导致性能瓶颈。

为了解决 SWIOTLB 的性能瓶颈问题,我们应尽量启用 IOMMU(I/O Memory Management Unit)。IOMMU 可以提供硬件层面的地址转换功能,避免了 SWIOTLB 的软件拷贝开销,大大提高了数据传输的效率。如果硬件支持 IOMMU,我们可以通过修改内核配置,启用 IOMMU 功能,从而提升系统性能。

在排查问题时,日志分析是一个非常有效的工具。我们可以通过查看内核日志,获取 DMA 驱动运行时的详细信息,包括地址映射、传输状态、中断处理等。通过分析这些日志信息,我们可以快速定位问题所在,并采取相应的解决措施。例如,如果日志中出现 “DMA mapping error” 的错误提示,我们就可以根据提示信息,检查地址映射的相关代码和配置,找出 ## 四、 进阶优化与避坑:打造高性能 DMA 应用

4.1 缓存一致性优化:从原理到实践方案

在 DMA 传输中,缓存一致性问题是影响性能的关键因素之一。当 CPU 修改了缓存中的数据,而设备直接访问内存时,就可能读取到旧数据,导致数据不一致。这就好比两个人同时编辑一份文档,一个人在本地修改后没有保存,另一个人直接从服务器读取,就会拿到旧版本的文档。

为了解决这个问题,我们可以采用硬件自动同步和软件显式同步两种方式。硬件自动同步通常依赖于一致性内存,这种内存采用非缓存或写通模式,硬件会自动保证 CPU 和设备对内存的访问是一致的,就像两个人使用实时在线文档,任何一方的修改都会立即同步给对方。

而软件显式同步则是在流式 DMA 中,通过驱动程序在传输前后显式调用同步函数来实现。在数据从 CPU 传输到设备前,调用 dma_sync_single_for_device 函数,将 CPU 缓存中的数据写回内存并使缓存失效,确保设备读取到最新数据;传输完成后,调用 dma_sync_single_for_cpu 函数,使 CPU 缓存失效,保证 CPU 读取的是设备写入内存的最新数据。这就像是在文档传输前,手动保存并上传最新版本,传输后再从服务器下载最新版本。

在实际应用中,我们需要根据数据传输的特点来选择合适的方式。对于高频小数据的传输,由于数据量小且传输频繁,使用一致性内存可以简化编程模型,确保数据的实时一致性,就像频繁修改的小文件使用实时在线文档更方便。而对于大块批量数据的传输,流式 DMA 结合合理的同步时机可以充分利用 CPU 缓存,提高传输效率,同时避免过度同步导致的性能损耗。比如在传输大文件时,先将数据缓存到内存中,在传输完成后再进行一次同步,而不是每次小数据传输都进行同步,这样可以减少同步操作的开销。

4.2 内存分配策略:CMA、原子池与普通内存的选型

内存分配策略的选择对于 DMA 性能也有着重要影响。在不同的场景下,我们需要根据内存的连续性、分配的原子性以及性能要求等因素,合理选择 CMA(Contiguous Memory Allocator)、原子池(pool.c)与普通内存。

当需要大块连续物理内存时,CMA 是首选。例如在视频采集、图像处理等场景中,需要连续的内存空间来存储一帧帧的图像数据或视频流数据,CMA 可以在系统启动时预留一块连续的内存区域,供设备在需要时使用。在 Petalinux 环境下,我们可以通过修改设备树文件来配置 CMA 内存区域的大小和属性,如:

reserved-memory {    #address-cells = <1>;    #size-cells = <1>;    ranges;    cma_region: cma@0 {        compatible = "shared-dma-pool";        reg = <0x0 0x10000000>;  /* 1GB的CMA区域 */        no-map;    };};

在驱动代码中,可以通过 dma_alloc_coherent 函数从 CMA 区域分配内存,确保设备能够使用连续的物理内存进行数据传输,提高传输效率。

对于中断等原子上下文场景,由于不能睡眠,需要快速分配内存,此时 DMA 原子池(pool.c)就发挥了作用。原子池预先分配了一些小块内存,当有原子上下文的内存分配请求时,可以快速从原子池中获取内存,避免了在中断处理中进行复杂的内存分配操作导致的延迟。在使用原子池时,需要注意其内存大小和分配策略,确保能够满足原子上下文的需求。

而在普通场景中,流式 DMA 的可缓存内存是常用的选择。这种内存分配方式灵活,能够利用 CPU 缓存提高数据访问速度。在进行流式 DMA 映射时,需要注意同步函数的调用,确保缓存一致性。

4.3 IOMMU 与 SWIOTLB 选型:性能与兼容性的权衡

在 DMA 应用中,IOMMU(I/O Memory Management Unit)和 SWIOTLB(Software I/O Translation Lookaside Buffer)的选型是一个需要谨慎考虑的问题,它涉及到性能与兼容性的权衡。

IOMMU 是一种硬件设备,它通过地址映射表将设备使用的 I/O 虚拟地址(IOVA)转换为物理地址,实现设备对内存的访问。这种硬件地址映射方式具有高性能的特点,能够实现大地址空间的访问,并且支持设备虚拟化和安全隔离。在服务器和高端嵌入式系统中,由于对性能要求较高,并且硬件通常支持 IOMMU,因此优先启用 IOMMU 是提升 DMA 性能的关键。在使用 IOMMU 时,需要配置 IOMMU 域,将设备附加到相应的域中,并进行地址映射操作。

而 SWIOTLB 是一种软件后备方案,当硬件不支持 IOMMU 或者 IOMMU 无法处理某些情况时,SWIOTLB 就会发挥作用。它通过在低地址空间预分配一块内存池作为 bounce buffer,当设备需要访问高地址内存时,先将数据从高地址拷贝到 bounce buffer 中,然后设备访问 bounce buffer,传输完成后再将数据拷贝回高地址内存。这种方式虽然保证了兼容性,但不可避免地带来了额外的数据拷贝开销,导致性能下降。在老旧硬件场景中,由于硬件不支持 IOMMU,只能使用 SWIOTLB 来保障设备的正常工作,但需要注意其性能瓶颈,尽量减少大数据量的传输操作。

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-02-10 03:09:54 HTTP/2.0 GET : https://f.mffb.com.cn/a/474652.html
  2. 运行时间 : 0.095767s [ 吞吐率:10.44req/s ] 内存消耗:4,510.30kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=837738e040561176b7bb70805392dd2d
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000745s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001274s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000652s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000436s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000689s ]
  6. SELECT * FROM `set` [ RunTime:0.000250s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000581s ]
  8. SELECT * FROM `article` WHERE `id` = 474652 LIMIT 1 [ RunTime:0.002420s ]
  9. UPDATE `article` SET `lasttime` = 1770664194 WHERE `id` = 474652 [ RunTime:0.007350s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000280s ]
  11. SELECT * FROM `article` WHERE `id` < 474652 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000473s ]
  12. SELECT * FROM `article` WHERE `id` > 474652 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.003247s ]
  13. SELECT * FROM `article` WHERE `id` < 474652 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.006137s ]
  14. SELECT * FROM `article` WHERE `id` < 474652 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.000901s ]
  15. SELECT * FROM `article` WHERE `id` < 474652 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.000821s ]
0.097258s