C++ 与 内核内存映射:在 C++ 驱动层实现用户态空间对内核环形缓冲区(Ring Buffer)的直接读写访问

欢迎大家来到今天的技术讲座。今天,我们将深入探讨一个在高性能计算和系统编程领域至关重要的主题:如何在 C++ 驱动层实现用户态空间对内核环形缓冲区(Ring Buffer)的直接读写访问。这个技术方案旨在解决传统用户态与内核态数据传输效率低下的问题,为高吞吐量、低延迟的应用场景提供强有力的支持。

想象一下,你正在开发一个需要实时处理大量数据的应用程序,例如网络数据包捕获、传感器数据采集或高性能日志系统。传统上,用户态应用程序通过系统调用(如 read()/write()ioctl())与内核进行数据交互。每一次系统调用都涉及上下文切换、数据拷贝以及潜在的调度开销,这些都会显著增加延迟并降低吞吐量。当数据量巨大时,这些开销会成为系统性能的瓶颈。

我们今天要讨论的内存映射(Memory Mapping)技术,结合环形缓冲区(Ring Buffer)的设计模式,提供了一种优雅的解决方案。它允许用户态应用程序直接访问内核态分配的内存区域,从而避免了数据拷贝和频繁的上下文切换,极大地提升了数据传输效率。这不仅是性能的飞跃,更是系统设计理念的一次革新。

本次讲座将涵盖以下几个核心部分:

  1. 用户态与内核态通信基础及性能瓶颈:回顾传统通信方式的优缺点。
  2. 内存映射技术详解:深入理解虚拟内存、物理内存以及内存映射的机制。
  3. 环形缓冲区原理与设计:介绍环形缓冲区作为高效数据结构的优势。
  4. 基于内存映射的环形缓冲区架构设计:构建用户态与内核态共享的环形缓冲区模型。
  5. 内核驱动层实现(C/C++):如何分配内核内存、创建设备对象、处理 ioctl 请求以及实现内存映射接口。
  6. 用户态应用层实现(C++):如何打开设备、获取映射信息、进行内存映射以及实现环形缓冲区读写逻辑。
  7. 并发与同步机制:在共享内存环境中,如何确保数据一致性和线程安全。
  8. 性能优化与最佳实践:缓存对齐、内存屏障、无锁编程等进阶技巧。
  9. 安全考量与鲁棒性:讨论内存映射带来的安全风险及应对措施。

通过本次讲座,我希望大家能够不仅理解这项技术的原理,更能掌握其在实际系统开发中的应用细节和最佳实践。让我们一起开启这段技术探索之旅。


一、用户态与内核态通信基础及性能瓶颈

在现代操作系统中,为了保护系统核心资源和提高稳定性,内存被划分为用户态空间和内核态空间。用户态程序运行在受限的环境中,无法直接访问内核态的内存或执行特权指令。当用户态程序需要与硬件交互或访问受保护的系统资源时,必须通过系统调用(System Call)陷入内核态,由内核代理完成操作。

1. 传统通信方式

  • 系统调用 (System Calls):这是最基本的通信机制。用户态应用程序通过调用 read()write()ioctl() 等函数,请求内核执行特定操作。
    • read() / write():用于在用户态和内核态之间传输数据流。例如,从设备文件读取数据或向设备文件写入数据。
    • ioctl() (Input/Output Control):一种通用的设备控制接口,允许用户态应用程序向设备驱动程序发送控制命令或传递小块数据。

2. 传统通信方式的局限性

尽管这些方法行之有效,但在高吞吐量和低延迟场景下,它们存在显著的性能瓶颈:

  • 上下文切换 (Context Switch):每次系统调用都涉及从用户态到内核态,再从内核态返回用户态的上下文切换。这个过程包括保存当前进程的CPU寄存器、程序计数器等状态,加载内核态的状态,以及反向操作。上下文切换的开销虽然不大,但在频繁发生时会累积成可观的延迟。
  • 数据拷贝 (Data Copy):这是最主要也是最昂贵的开销。当用户态程序通过 read()/write() 传输数据时,数据通常需要从用户态缓冲区复制到内核态缓冲区,反之亦然。对于 ioctl(),如果涉及到数据传输,同样会发生拷贝。这些数据拷贝操作会消耗CPU周期和内存带宽,尤其是在传输大量数据时,其影响更为显著。
  • 缓存污染 (Cache Pollution):频繁的数据拷贝可能导致CPU缓存中无效数据的增加,降低缓存命中率。
  • 调度开销 (Scheduling Overhead):系统调用可能会导致进程阻塞或调度,进一步增加延迟。

这些瓶颈使得传统通信方式在处理每秒数GB甚至更高的数据流时力不从心。因此,我们需要一种更高效的机制来绕过这些开销,实现用户态和内核态之间的“零拷贝”数据传输。内存映射正是为此而生。


二、内存映射技术详解

内存映射是一种将文件或设备I/O区域直接映射到进程虚拟地址空间的技术。通过内存映射,进程可以直接访问文件或设备中的数据,而无需通过 read()/write() 等系统调用进行数据拷贝。

1. 虚拟内存与物理内存

在深入内存映射之前,我们首先需要理解现代操作系统的内存管理机制:

  • 物理内存 (Physical Memory):计算机中实际安装的RAM芯片,拥有唯一的物理地址。
  • 虚拟内存 (Virtual Memory):操作系统为每个进程提供的一个抽象的、独立的、连续的内存视图。每个进程都拥有自己的虚拟地址空间,其大小通常远大于实际的物理内存。
  • 页表 (Page Table):操作系统使用页表来记录虚拟地址到物理地址的映射关系。当CPU访问一个虚拟地址时,内存管理单元 (MMU) 会通过页表查找对应的物理地址。
  • 页 (Page):虚拟内存和物理内存都被划分为固定大小的块,称为页(通常为4KB)。页是内存管理的基本单位。
  • TLB (Translation Lookaside Buffer):一个高速缓存,用于存储最近使用的虚拟地址到物理地址的转换关系,以加速地址翻译过程。

用户态和内核态各自拥有独立的虚拟地址空间,但它们共享相同的物理内存。内核通过管理页表来隔离和保护各个进程的虚拟地址空间。

2. 内存映射机制

内存映射的核心思想是建立虚拟地址空间到物理地址空间的一对多或多对一映射,使得多个虚拟地址可以指向同一块物理内存。

对于用户态和内核态共享环形缓冲区而言,内存映射的原理是:

  1. 内核分配物理内存:驱动程序在内核态分配一块连续的物理内存区域,作为环形缓冲区的数据存储区。
  2. 内核映射自身:驱动程序将这块物理内存映射到内核自身的虚拟地址空间,以便驱动可以读写。
  3. 用户态映射:驱动程序提供一种机制(例如通过 ioctl 返回一个句柄或标识符),允许用户态应用程序将同一块物理内存区域映射到其自身的虚拟地址空间。
    • 在 Linux/POSIX 系统中,这通常通过 mmap() 系统调用实现,将一个文件描述符(通常是设备文件)的某个偏移量映射到进程的虚拟地址空间。
    • 在 Windows 系统中,这通常涉及创建 Section Object (NtCreateSection / ZwCreateSection),然后用户态调用 MapViewOfFileZwMapViewOfFile 将 Section 映射到其地址空间。

内存映射的优势:

  • 零拷贝 (Zero-Copy):数据无需在用户态和内核态之间进行拷贝,直接在共享的物理内存区域读写。这消除了数据拷贝带来的CPU和内存带宽开销。
  • 减少上下文切换:一旦内存映射建立,用户态应用程序可以直接访问这块内存,大部分时间无需进行系统调用,从而减少上下文切换的次数。
  • 共享内存:内存映射天然地实现了用户态和内核态之间的共享内存机制。
  • 页表管理:操作系统负责维护页表映射,当物理内存不足时,可以进行页交换。

3. 内核内存分配

在内核驱动中分配内存需要特别注意,因为内核内存与用户态内存管理有很大不同。

  • Linux 内核:

    • kmalloc():用于分配物理上连续的小块内存(通常小于128KB),常用于设备寄存器映射或DMA缓冲区。
    • vmalloc():用于分配虚拟地址上连续但在物理地址上不连续的大块内存。
    • __get_free_pages() / alloc_pages():直接分配一个或多个物理页。
      为了实现用户态内存映射,我们通常需要物理上连续的内存,或至少是物理页地址可知的内存。对于环形缓冲区,如果大小不是特别大,kmalloc()__get_free_pages() 结合 remap_pfn_range() 是常见的选择。
  • Windows 内核:

    • ExAllocatePoolWithTag():用于分配内核内存,可以指定内存池类型(PagedPool、NonPagedPool),但通常不保证物理连续。
    • MmAllocateContiguousMemorySpecifyCache():用于分配物理上连续的内存,常用于DMA缓冲区。这是我们创建可直接映射的环形缓冲区时的首选。
    • MmCreateMdl() / MmProbeAndLockPages():用于描述和锁定已经存在的内存区域(无论是物理连续还是不连续),以便进行DMA或映射。

重要考虑: 分配的内存需要进行页对齐,这是内存映射的基本要求。


三、环形缓冲区原理与设计

环形缓冲区(Ring Buffer),也称为循环缓冲区(Circular Buffer),是一种固定大小的缓冲区,其特点是当数据写入到缓冲区末尾时,会从缓冲区的开头重新开始写入,形成一个逻辑上的“环”。它是一种非常适合生产者-消费者模型的数据结构。

1. 环形缓冲区基本原理

一个典型的环形缓冲区包含以下核心元素:

  • 数据区 (Data Buffer):实际存储数据的内存区域。
  • 容量 (Capacity):缓冲区可以存储的最大数据量。
  • 头部指针 (Head Pointer):指向下一个可写入数据的位置,由生产者更新。
  • 尾部指针 (Tail Pointer):指向下一个可读取数据的位置,由消费者更新。

操作逻辑:

  • 写入 (Produce):生产者将数据写入 head 指针指向的位置,然后 head 指针向前移动。
  • 读取 (Consume):消费者从 tail 指针指向的位置读取数据,然后 tail 指针向前移动。
  • 当指针到达缓冲区末尾时,它会“环绕”回到缓冲区的开头。

状态判断:

  • 缓冲区为空 (Empty):当 head == tail 时,缓冲区为空。
  • 缓冲区为满 (Full):判断缓冲区是否为满有多种策略。最常见的一种是保留一个空闲位置,即当 (head + 1) % capacity == tail 时,缓冲区为满。另一种是使用一个额外的计数器来跟踪缓冲区中的元素数量。
  • 可用空间capacity - (head - tail + capacity) % capacity - 1 (保留一个位置的策略)。
  • 已用空间(head - tail + capacity) % capacity

2. 环形缓冲区在用户-内核通信中的优势

环形缓冲区与内存映射结合,为用户-内核数据传输带来了显著优势:

  • 高效利用内存:固定大小的缓冲区避免了动态内存分配和释放的开销。
  • 无锁或低锁设计:在单生产者、单消费者场景下,通过原子操作更新 headtail 指针,可以实现完全无锁的并发访问。即使是多生产者/多消费者,也能通过细粒度锁或原子操作将锁的粒度降到最低。
  • 连续数据流处理:非常适合处理连续不断的数据流,生产者可以源源不断地写入,消费者可以持续读取。
  • 流量平滑:环形缓冲区可以作为生产者和消费者之间速率不匹配时的缓冲,平滑数据流量。

3. 环形缓冲区数据结构设计

为了实现用户态和内核态共享,环形缓冲区需要一个清晰的数据结构定义。这个结构通常包括数据区本身,以及控制指针和容量等元数据。

// 环形缓冲区控制块结构体
// 注意:在实际内核代码中,通常使用C语言风格的结构体和函数。
// 这里的C++风格仅为演示方便,并假设在支持C++的内核环境(如部分Windows驱动)或用户态接口中。
struct RingBufferControlBlock {
    // 缓冲区容量,单位为字节
    size_t capacity;

    // 生产者写入指针,指向下一个可写入的字节位置
    // volatile 关键字确保编译器不会对这个变量的读写进行过度优化
    // std::atomic<size_t> 是更现代和安全的C++11同步机制
    volatile std::atomic<size_t> head;

    // 消费者读取指针,指向下一个可读取的字节位置
    volatile std::atomic<size_t> tail;

    // 缓冲区数据起始点的物理地址(对用户态映射很重要)
    // 或者一个句柄/标识符,用户态通过它来映射
    // 具体类型取决于操作系统和驱动实现
    uint64_t physical_address_or_handle; // 示例:物理地址,或Windows的HANDLE/LUID

    // 其他可能的元数据,例如:
    // bool producer_active;
    // bool consumer_active;
    // uint32_t flags;
};

volatilestd::atomic

  • volatile 关键字指示编译器,变量的值可能会在程序控制之外被改变(例如,由硬件或并发线程)。它阻止编译器对该变量的读写操作进行优化,确保每次访问都从内存中读取或写入内存。然而,volatile 不能保证多线程访问的原子性或内存顺序。
  • std::atomic<T>(C++11及以上)是专门为多线程并发访问设计的。它提供了原子操作(如加载、存储、交换、比较-交换),保证了操作的不可分割性,并且可以通过内存顺序(memory order)参数控制内存访问的可见性。在共享内存并发场景下,std::atomic 是比 volatile 更强大、更安全的同步机制。在内核驱动中,如果使用C语言,则需要依赖平台特定的原子操作宏或函数(如Linux的 atomic_t 或Windows的 InterlockedExchange 系列函数)。

在我们的设计中,headtail 指针将由用户态和内核态并发更新,因此使用 std::atomic(或其平台等效物)是至关重要的。


四、基于内存映射的环形缓冲区架构设计

现在我们来整合内存映射和环形缓冲区,设计一个完整的用户-内核直接访问架构。

1. 核心思想

  • 单一物理内存区域:内核分配一块连续的物理内存,作为环形缓冲区的数据存储区。
  • 双重映射
    • 内核将这块物理内存映射到其自身的虚拟地址空间,供驱动程序内部使用。
    • 用户态应用程序将同一块物理内存映射到其自身的虚拟地址空间,实现直接访问。
  • 共享控制块:环形缓冲区的元数据(headtailcapacity 等)也需要被共享。这可以通过两种方式实现:
    1. 将控制块也映射到共享内存区域中。
    2. 通过 ioctl 等系统调用获取或更新控制块的信息(适用于控制块很小且更新不频繁的场景)。
      为了实现真正的零拷贝和最高效率,将控制块也映射到共享内存是更优的选择,但这需要更精细的同步控制。

2. 架构图(概念性)

用户态进程 A (C++) 内核态驱动 (C/C++) 物理内存
pUserBuffer_Data pKernelBuffer_Data RingBuffer_Data_Physical
(虚拟地址) (虚拟地址) (物理地址)
pUserBuffer_Control pKernelBuffer_Control RingBuffer_Control_Physical
(虚拟地址) (虚拟地址) (物理地址)
操作: 操作:
1. 打开设备 1. DriverEntry / ModuleInit
2. ioctl 获取映射信息 2. DeviceCreate / cdev_add
3. mmap / MapViewOfFile 3. ioctl 处理函数
数据区和控制区 返回物理地址/句柄
4. 直接读写 pUserBuffer_Data 4. mmap / IRP_MJ_CREATE (Windows)
5. 原子更新 pUserBuffer_Control->head/tail 处理映射请求
5. 访问 pKernelBuffer_Data
6. 原子更新 pKernelBuffer_Control->head/tail

3. 生产-消费模型

  • 生产者 (可以是用户态或内核态)

    1. 检查环形缓冲区是否有足够的空间可写(通过比较 headtail)。
    2. 将数据写入缓冲区中 head 指针指向的虚拟地址。
    3. 原子地更新 head 指针,使其指向下一个可用位置。
    4. (可选)如果缓冲区从空变为非空,通知消费者有新数据可用(通过事件、信号量等)。
  • 消费者 (可以是用户态或内核态)

    1. 检查环形缓冲区是否有数据可读(通过比较 headtail)。
    2. 从缓冲区中 tail 指针指向的虚拟地址读取数据。
    3. 原子地更新 tail 指针,使其指向下一个待读位置。
    4. (可选)如果缓冲区从满变为不满,通知生产者有新空间可用。

这种设计使得数据流可以高效地在用户态和内核态之间传递,极大地降低了系统开销。


五、内核驱动层实现(C/C++)

内核驱动程序是实现内存映射环形缓冲区的核心。它负责内存分配、设备注册、处理用户态请求以及建立内存映射。由于操作系统差异,我们将分别探讨 Linux 和 Windows 的通用思路,并提供 C 风格的示例代码(因为多数内核代码是 C)。

1. 环形缓冲区控制结构与数据结构

首先,定义我们的环形缓冲区结构。为了在用户态和内核态之间共享,这个结构必须是 ABI 兼容的。

// ring_buffer_common.h (共享头文件,用户态和内核态都包含)

#ifndef RING_BUFFER_COMMON_H
#define RING_BUFFER_COMMON_H

#include <stddef.h> // For size_t
#include <stdint.h> // For uint64_t

// IOCTL 命令码定义(示例,实际中应使用系统提供的宏)
#define IOCTL_GET_RING_BUFFER_INFO   0x80000001
#define IOCTL_NOTIFY_PRODUCER_FULL   0x80000002 // 示例:生产者通知内核缓冲区满
#define IOCTL_NOTIFY_CONSUMER_EMPTY  0x80000003 // 示例:消费者通知内核缓冲区空

// 环形缓冲区元数据结构
// 这个结构会被映射到用户态,因此其内存布局需要稳定
struct RingBufferMetadata {
    size_t capacity; // 缓冲区总容量(字节)
    // 注意:在C内核中,使用volatile和平台原子类型
    // 在C++用户态,可以使用std::atomic
    volatile size_t head; // 生产者写入指针
    volatile size_t tail; // 消费者读取指针

    // 实际数据缓冲区的物理地址,用于用户态的mmap
    // 或者在Windows中,可能是SECTION句柄的LUID,需要通过ioctl传递
    uint64_t data_physical_address; // Linux: PFN << PAGE_SHIFT; Windows: Start of contiguous physical memory

    // 其他状态或同步信息
    // ...
};

#endif // RING_BUFFER_COMMON_H

2. 内核驱动初始化与内存分配

驱动程序的 DriverEntry (Windows) 或 module_init (Linux) 函数是关键。

2.1 Linux 内核驱动示例
// ring_buffer_driver.c (Linux Kernel Module)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>       // file_operations
#include <linux/cdev.h>     // cdev
#include <linux/slab.h>     // kmalloc, kfree
#include <linux/mm.h>       // mmap, vm_area_struct, remap_pfn_range
#include <linux/uaccess.h>  // copy_to_user, copy_from_user
#include <linux/io.h>       // ioremap
#include <asm/atomic.h>     // Linux kernel atomic operations

#include "ring_buffer_common.h" // 包含共享头文件

#define DEVICE_NAME "ringbuffer_dev"
#define RB_SIZE (1 * 1024 * 1024) // 1MB 环形缓冲区大小

static dev_t rb_dev_num;
static struct cdev rb_cdev;
static struct class *rb_class;

// 内核态的环形缓冲区结构实例
static char *rb_data_buffer_kernel_va; // 实际数据缓冲区的内核虚拟地址
static phys_addr_t rb_data_buffer_physical_addr; // 实际数据缓冲区的物理地址
static struct RingBufferMetadata *rb_metadata; // 元数据块的内核虚拟地址
static phys_addr_t rb_metadata_physical_addr; // 元数据块的物理地址

// mmap 操作处理函数
static int rb_mmap(struct file *filp, struct vm_area_struct *vma) {
    size_t size = vma->vm_end - vma->vm_start;
    unsigned long pfn;
    int ret = -EINVAL;

    // 用户请求映射的区域必须与我们的缓冲区对齐且大小匹配
    // 在更复杂的驱动中,可以根据vma->vm_pgoff来区分映射数据区还是元数据区
    if (size == RB_SIZE) {
        pfn = virt_to_phys(rb_data_buffer_kernel_va) >> PAGE_SHIFT;
    } else if (size == sizeof(struct RingBufferMetadata)) {
        pfn = virt_to_phys(rb_metadata) >> PAGE_SHIFT;
    } else {
        printk(KERN_WARNING "%s: Invalid mmap size %lun", DEVICE_NAME, size);
        return -EINVAL;
    }

    // remap_pfn_range 将物理页面映射到用户态虚拟地址空间
    // vma->vm_page_prot 设置内存属性,如读写、缓存策略等
    // pgprot_writecombine 可用于DMA缓冲区以避免缓存一致性问题
    // pgprot_noncached 禁用缓存
    ret = remap_pfn_range(vma,
                          vma->vm_start,
                          pfn,
                          size,
                          vma->vm_page_prot); // 使用默认保护,或 pgprot_writecombine(vma->vm_page_prot)
    if (ret) {
        printk(KERN_ERR "%s: Failed to mmap physical memory (ret=%d)n", DEVICE_NAME, ret);
        return ret;
    }
    printk(KERN_INFO "%s: Mapped %lu bytes to user space at 0x%lxn", DEVICE_NAME, size, vma->vm_start);
    return 0;
}

// IOCTL 操作处理函数
static long rb_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    struct RingBufferMetadata metadata_copy;
    long ret = 0;

    switch (cmd) {
        case IOCTL_GET_RING_BUFFER_INFO:
            // 填充元数据,并将其复制到用户态
            metadata_copy.capacity = rb_metadata->capacity;
            metadata_copy.head = atomic_read((atomic_t*)&rb_metadata->head); // 读取原子变量
            metadata_copy.tail = atomic_read((atomic_t*)&rb_metadata->tail); // 读取原子变量
            metadata_copy.data_physical_address = rb_data_buffer_physical_addr;

            if (copy_to_user((void __user *)arg, &metadata_copy, sizeof(struct RingBufferMetadata))) {
                ret = -EFAULT;
            }
            break;
        // 其他IOCTL命令...
        default:
            ret = -ENOTTY; // Not a typewriter, indicating unsupported IOCTL
            break;
    }
    return ret;
}

// 文件操作结构体
static const struct file_operations rb_fops = {
    .owner          = THIS_MODULE,
    .open           = NULL, // 简单设备,open/release可省略
    .release        = NULL,
    .unlocked_ioctl = rb_ioctl,
    .mmap           = rb_mmap,
};

// 模块初始化函数
static int __init ring_buffer_init(void) {
    int ret;

    printk(KERN_INFO "%s: Initializing ring buffer devicen", DEVICE_NAME);

    // 1. 动态分配设备号
    ret = alloc_chrdev_region(&rb_dev_num, 0, 1, DEVICE_NAME);
    if (ret < 0) {
        printk(KERN_ERR "%s: Failed to allocate char device regionn", DEVICE_NAME);
        return ret;
    }

    // 2. 创建cdev并注册
    cdev_init(&rb_cdev, &rb_fops);
    rb_cdev.owner = THIS_MODULE;
    ret = cdev_add(&rb_cdev, rb_dev_num, 1);
    if (ret < 0) {
        printk(KERN_ERR "%s: Failed to add cdevn", DEVICE_NAME);
        unregister_chrdev_region(rb_dev_num, 1);
        return ret;
    }

    // 3. 创建设备类和设备文件 (方便用户空间访问 /dev/ringbuffer_dev)
    rb_class = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(rb_class)) {
        printk(KERN_ERR "%s: Failed to create device classn", DEVICE_NAME);
        cdev_del(&rb_cdev);
        unregister_chrdev_region(rb_dev_num, 1);
        return PTR_ERR(rb_class);
    }
    device_create(rb_class, NULL, rb_dev_num, NULL, DEVICE_NAME);

    // 4. 分配物理连续的内核内存用于数据缓冲区 (使用__get_free_pages)
    // 注意:PAGE_ALIGN 确保大小是页的整数倍
    unsigned long rb_data_pages = (RB_SIZE + PAGE_SIZE - 1) / PAGE_SIZE;
    rb_data_buffer_kernel_va = (char *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, get_order(RB_SIZE));
    if (!rb_data_buffer_kernel_va) {
        printk(KERN_ERR "%s: Failed to allocate data buffern", DEVICE_NAME);
        goto err_cleanup_device;
    }
    rb_data_buffer_physical_addr = virt_to_phys(rb_data_buffer_kernel_va);
    printk(KERN_INFO "%s: Data buffer allocated at KVA 0x%lx, PADDR 0x%lxn",
           DEVICE_NAME, (unsigned long)rb_data_buffer_kernel_va, (unsigned long)rb_data_buffer_physical_addr);

    // 5. 分配物理连续的内核内存用于元数据 (使用kmalloc或__get_free_pages)
    // 元数据通常较小,kmalloc即可
    rb_metadata = (struct RingBufferMetadata *)kmalloc(sizeof(struct RingBufferMetadata), GFP_KERNEL);
    if (!rb_metadata) {
        printk(KERN_ERR "%s: Failed to allocate metadatan", DEVICE_NAME);
        goto err_cleanup_data_buffer;
    }
    rb_metadata_physical_addr = virt_to_phys(rb_metadata);
    printk(KERN_INFO "%s: Metadata allocated at KVA 0x%lx, PADDR 0x%lxn",
           DEVICE_NAME, (unsigned long)rb_metadata, (unsigned long)rb_metadata_physical_addr);

    // 6. 初始化环形缓冲区元数据
    rb_metadata->capacity = RB_SIZE;
    atomic_set((atomic_t*)&rb_metadata->head, 0); // 使用Linux内核原子操作
    atomic_set((atomic_t*)&rb_metadata->tail, 0);
    rb_metadata->data_physical_address = rb_data_buffer_physical_addr; // 将物理地址记录在元数据中

    printk(KERN_INFO "%s: Ring buffer device initialized successfullyn", DEVICE_NAME);
    return 0;

err_cleanup_data_buffer:
    if (rb_data_buffer_kernel_va) {
        free_pages((unsigned long)rb_data_buffer_kernel_va, get_order(RB_SIZE));
    }
err_cleanup_device:
    device_destroy(rb_class, rb_dev_num);
    class_destroy(rb_class);
    cdev_del(&rb_cdev);
    unregister_chrdev_region(rb_dev_num, 1);
    return -ENOMEM;
}

// 模块退出函数
static void __exit ring_buffer_exit(void) {
    printk(KERN_INFO "%s: Exiting ring buffer devicen", DEVICE_NAME);

    // 释放所有资源
    if (rb_metadata) {
        kfree(rb_metadata);
    }
    if (rb_data_buffer_kernel_va) {
        free_pages((unsigned long)rb_data_buffer_kernel_va, get_order(RB_SIZE));
    }

    device_destroy(rb_class, rb_dev_num);
    class_destroy(rb_class);
    cdev_del(&rb_cdev);
    unregister_chrdev_region(rb_dev_num, 1);
    printk(KERN_INFO "%s: Ring buffer device cleanup completen", DEVICE_NAME);
}

module_init(ring_buffer_init);
module_exit(ring_buffer_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A memory-mapped ring buffer driver");
2.2 Windows 内核驱动(概念性)

Windows 驱动模型(WDM/WDF)相对复杂。核心思路是:

  1. DriverEntry
    • 创建设备对象 (IoCreateDevice) 和符号链接 (IoCreateSymbolicLink)。
    • 分配物理连续内存 (MmAllocateContiguousMemorySpecifyCache) 作为环形缓冲区数据区。
    • 分配普通内存 (ExAllocatePoolWithTag) 作为元数据区。
    • 初始化元数据。
    • 为环形缓冲区数据创建一个 Section Object (ZwCreateSection / NtCreateSection),以便用户态可以映射。这个 Section Object 可以在 IRP_MJ_CREATEIRP_MJ_DEVICE_CONTROL 中返回给用户态。
  2. IRP_MJ_DEVICE_CONTROL (IOCTL)
    • 处理 IOCTL_GET_RING_BUFFER_INFO 请求,将元数据(包括 Section Handle 或其 LUID)复制到用户态。
    • 注意:直接传递内核句柄到用户态是危险的,通常需要通过 ObOpenObjectByPointer 等函数将内核句柄转换为用户态可用的句柄,或者传递一个 LUID 让用户态通过 NtOpenSection 重新获取句柄。
  3. IRP_MJ_CREATE / IRP_MJ_CLOSE
    • 用户态 CreateFile 时被调用。
    • 可以在这里处理用户态对 Section Object 的请求。
// ring_buffer_driver.cpp (Windows WDM/WDF Driver - 概念性C++风格,实际需C兼容)

#include <ntddk.h> // Windows Driver Kit headers
#include "ring_buffer_common.h"

#define DRIVER_TAG 'rbuf' // 用于ExAllocatePoolWithTag
#define RB_SIZE (1 * 1024 * 1024) // 1MB

// 全局变量
PDEVICE_OBJECT g_DeviceObject = NULL;
PVOID g_RingBufferDataVa = NULL; // 内核虚拟地址
PHYSICAL_ADDRESS g_RingBufferDataPa; // 物理地址
PMDL g_RingBufferMdl = NULL; // 内存描述符列表

RingBufferMetadata* g_RingBufferMetadata = NULL; // 元数据块

// Section Object,用于用户态映射
HANDLE g_SectionHandle = NULL;
PVOID g_SectionObject = NULL;

// 设备控制分发函数
NTSTATUS RingBuffer_DeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
    PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
    NTSTATUS status = STATUS_SUCCESS;
    ULONG inputBufferLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
    ULONG outputBufferLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
    ULONG ioControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
    PVOID ioBuffer = Irp->AssociatedIrp.SystemBuffer;
    ULONG_PTR information = 0;

    switch (ioControlCode) {
        case IOCTL_GET_RING_BUFFER_INFO:
            if (outputBufferLength < sizeof(RingBufferMetadata)) {
                status = STATUS_BUFFER_TOO_SMALL;
                break;
            }
            // 复制元数据到用户态
            // 注意:这里需要确保g_RingBufferMetadata->head/tail是原子地读取
            RtlCopyMemory(ioBuffer, g_RingBufferMetadata, sizeof(RingBufferMetadata));
            information = sizeof(RingBufferMetadata);
            break;
        default:
            status = STATUS_INVALID_DEVICE_REQUEST;
            break;
    }

    Irp->IoStatus.Status = status;
    Irp->IoStatus.Information = information;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return status;
}

// 驱动卸载函数
VOID RingBuffer_Unload(PDRIVER_OBJECT DriverObject) {
    UNREFERENCED_PARAMETER(DriverObject);
    DbgPrint("RingBuffer_Unload: Unloading drivern");

    // 关闭Section Object
    if (g_SectionObject) {
        ObDereferenceObject(g_SectionObject); // 减少引用计数
        g_SectionObject = NULL;
    }
    if (g_SectionHandle) {
        ZwClose(g_SectionHandle);
        g_SectionHandle = NULL;
    }

    // 释放内存
    if (g_RingBufferMetadata) {
        ExFreePoolWithTag(g_RingBufferMetadata, DRIVER_TAG);
        g_RingBufferMetadata = NULL;
    }
    if (g_RingBufferMdl) {
        MmUnlockPages(g_RingBufferMdl);
        IoFreeMdl(g_RingBufferMdl);
        g_RingBufferMdl = NULL;
    }
    if (g_RingBufferDataVa) {
        // 对于MmAllocateContiguousMemorySpecifyCache分配的,通常不需要MmUnmapIoSpace
        // 如果是MmMapIoSpace映射的,需要MmUnmapIoSpace
        // MmFreeContiguousMemorySpecifyCache(g_RingBufferDataVa); // WDK旧版本
        // 新版本驱动中,只要不调用MmMapIoSpace,就不需要Unmap,直接释放Mdl即可。
        // 实际上,MmFreeContiguousMemorySpecifyCache 已经废弃。
        // 最佳实践是使用 IoAllocateAdapterChannel / MapTransfer 等DMA机制。
        // 这里只是概念性地展示内存分配。
    }

    // 删除符号链接和设备对象
    UNICODE_STRING symLinkName;
    RtlInitUnicodeString(&symLinkName, L"\??\MyRingBufferDevice");
    IoDeleteSymbolicLink(&symLinkName);
    if (g_DeviceObject) {
        IoDeleteDevice(g_DeviceObject);
        g_DeviceObject = NULL;
    }
    DbgPrint("RingBuffer_Unload: Driver unloadedn");
}

// 驱动入口函数
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
    UNREFERENCED_PARAMETER(RegistryPath);
    DbgPrint("DriverEntry: RingBuffer driver loadingn");

    NTSTATUS status;
    UNICODE_STRING deviceName, symLinkName;

    // 1. 创建设备对象
    RtlInitUnicodeString(&deviceName, L"\Device\MyRingBufferDevice");
    status = IoCreateDevice(DriverObject, 0, &deviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_DeviceObject);
    if (!NT_SUCCESS(status)) {
        DbgPrint("DriverEntry: IoCreateDevice failed with status 0x%xn", status);
        return status;
    }

    // 2. 创建符号链接
    RtlInitUnicodeString(&symLinkName, L"\??\MyRingBufferDevice");
    status = IoCreateSymbolicLink(&symLinkName, &deviceName);
    if (!NT_SUCCESS(status)) {
        DbgPrint("DriverEntry: IoCreateSymbolicLink failed with status 0x%xn", status);
        IoDeleteDevice(g_DeviceObject);
        return status;
    }

    // 3. 分配物理连续内存作为数据缓冲区
    // 确保页对齐
    ULONG bufferSize = RB_SIZE;
    PHYSICAL_ADDRESS lowAddress, highAddress, boundaryAddress;
    lowAddress.QuadPart = 0;
    highAddress.QuadPart = -1; // Any physical address
    boundaryAddress.QuadPart = 0; // No boundary restriction

    g_RingBufferDataVa = MmAllocateContiguousMemorySpecifyCache(
        bufferSize,
        lowAddress,
        highAddress,
        boundaryAddress,
        MmCached); // Use MmCached for general purpose memory
    if (!g_RingBufferDataVa) {
        DbgPrint("DriverEntry: MmAllocateContiguousMemorySpecifyCache failedn");
        IoDeleteSymbolicLink(&symLinkName);
        IoDeleteDevice(g_DeviceObject);
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    RtlZeroMemory(g_RingBufferDataVa, bufferSize);
    g_RingBufferDataPa = MmGetPhysicalAddress(g_RingBufferDataVa);
    DbgPrint("Ring buffer data allocated at KVA %p, PA %llxn", g_RingBufferDataVa, g_RingBufferDataPa.QuadPart);

    // 4. 为数据缓冲区创建MDL并锁定页面,以供后续创建Section
    g_RingBufferMdl = IoAllocateMdl(g_RingBufferDataVa, bufferSize, FALSE, FALSE, NULL);
    if (!g_RingBufferMdl) {
        DbgPrint("DriverEntry: IoAllocateMdl failedn");
        // MmFreeContiguousMemorySpecifyCache(g_RingBufferDataVa); // old style
        IoDeleteSymbolicLink(&symLinkName);
        IoDeleteDevice(g_DeviceObject);
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    MmBuildMdlForNonPagedPool(g_RingBufferMdl); // Mark MDL for non-paged pool, as we used contiguous mem
    // No need to MmProbeAndLockPages for contiguous memory unless it was from paged pool.

    // 5. 分配元数据内存
    g_RingBufferMetadata = (RingBufferMetadata*)ExAllocatePoolWithTag(NonPagedPool, sizeof(RingBufferMetadata), DRIVER_TAG);
    if (!g_RingBufferMetadata) {
        DbgPrint("DriverEntry: ExAllocatePoolWithTag for metadata failedn");
        IoFreeMdl(g_RingBufferMdl); g_RingBufferMdl = NULL;
        // MmFreeContiguousMemorySpecifyCache(g_RingBufferDataVa); // old style
        IoDeleteSymbolicLink(&symLinkName);
        IoDeleteDevice(g_DeviceObject);
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    RtlZeroMemory(g_RingBufferMetadata, sizeof(RingBufferMetadata));

    // 6. 初始化元数据
    g_RingBufferMetadata->capacity = RB_SIZE;
    // 使用Windows Interlocked系列函数进行原子操作
    InterlockedExchangeSizeT(&g_RingBufferMetadata->head, 0);
    InterlockedExchangeSizeT(&g_RingBufferMetadata->tail, 0);
    g_RingBufferMetadata->data_physical_address = g_RingBufferDataPa.QuadPart; // 记录物理地址

    // 7. 为数据缓冲区创建Section Object
    // 这一步是关键,它允许用户态通过MapViewOfFile映射这块物理内存
    OBJECT_ATTRIBUTES objAttr;
    InitializeObjectAttributes(&objAttr, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);

    status = ZwCreateSection(
        &g_SectionHandle,
        SECTION_ALL_ACCESS,
        &objAttr,
        NULL, // MaximumSize - default to MDL size
        PAGE_READWRITE,
        SEC_COMMIT,
        g_RingBufferMdl // Pass the MDL to the section
    );
    if (!NT_SUCCESS(status)) {
        DbgPrint("DriverEntry: ZwCreateSection failed with status 0x%xn", status);
        ExFreePoolWithTag(g_RingBufferMetadata, DRIVER_TAG); g_RingBufferMetadata = NULL;
        IoFreeMdl(g_RingBufferMdl); g_RingBufferMdl = NULL;
        // MmFreeContiguousMemorySpecifyCache(g_RingBufferDataVa); // old style
        IoDeleteSymbolicLink(&symLinkName);
        IoDeleteDevice(g_DeviceObject);
        return status;
    }
    // 获取Section Object指针,以便在卸载时释放
    status = ObReferenceObjectByHandle(g_SectionHandle, SECTION_ALL_ACCESS, *MmSectionObjectType, KernelMode, &g_SectionObject, NULL);
    if (!NT_SUCCESS(status)) {
        DbgPrint("DriverEntry: ObReferenceObjectByHandle failed with status 0x%xn", status);
        ZwClose(g_SectionHandle); g_SectionHandle = NULL;
        ExFreePoolWithTag(g_RingBufferMetadata, DRIVER_TAG); g_RingBufferMetadata = NULL;
        IoFreeMdl(g_RingBufferMdl); g_RingBufferMdl = NULL;
        // MmFreeContiguousMemorySpecifyCache(g_RingBufferDataVa); // old style
        IoDeleteSymbolicLink(&symLinkName);
        IoDeleteDevice(g_DeviceObject);
        return status;
    }

    // 8. 设置驱动分发函数
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = RingBuffer_DeviceControl;
    DriverObject->DriverUnload = RingBuffer_Unload;

    DbgPrint("DriverEntry: RingBuffer driver loaded successfullyn");
    return STATUS_SUCCESS;
}

关键点:

  • Linux remap_pfn_range: 这是将物理页帧号 (PFN) 映射到用户态虚拟地址空间的核心函数。它不进行数据拷贝,直接修改页表。
  • Windows ZwCreateSection: 这是为物理内存创建 Section Object 的关键。用户态应用程序通过 MapViewOfFile 映射这个 Section Object 来实现共享。
  • 原子操作: 在内核态,atomic_t (Linux) 或 Interlocked 函数 (Windows) 用于确保 head/tail 指针更新的原子性。

六、用户态应用层实现(C++)

用户态应用程序需要打开设备、获取映射信息,然后将内核分配的内存映射到自己的虚拟地址空间,并直接进行环形缓冲区操作。

// user_app.cpp (C++ User-mode Application)

#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>
#include <thread>
#include <chrono>
#include <atomic> // C++11 for std::atomic
#include <numeric> // For std::iota

// 平台特定的头文件和宏
#ifdef _WIN32
#include <windows.h>
#include <winioctl.h> // For DeviceIoControl
#else // Linux/POSIX
#include <fcntl.h>    // For open
#include <sys/ioctl.h> // For ioctl
#include <sys/mman.h> // For mmap
#include <unistd.h>   // For close
#include <cstring>    // For strerror
#endif

#include "ring_buffer_common.h" // 包含共享头文件

// 环形缓冲区类,封装用户态操作
class UserRingBuffer {
private:
    RingBufferMetadata* metadata_ptr; // 映射的元数据指针
    char* data_buffer_ptr;           // 映射的数据缓冲区指针
    size_t buffer_capacity;          // 缓冲区容量

#ifdef _WIN32
    HANDLE device_handle;
    HANDLE section_handle; // Windows下用于MapViewOfFile的section handle
#else
    int device_fd;
#endif

    // 获取可用的写入空间
    size_t get_available_write_space() const {
        size_t current_head = metadata_ptr->head.load(std::memory_order_acquire);
        size_t current_tail = metadata_ptr->tail.load(std::memory_order_relaxed); // tail由消费者更新,relaxed即可
        if (current_head >= current_tail) {
            return buffer_capacity - (current_head - current_tail) - 1; // 留一个空位
        } else {
            return (current_tail - current_head) - 1; // 留一个空位
        }
    }

    // 获取可用的读取数据量
    size_t get_available_read_data() const {
        size_t current_head = metadata_ptr->head.load(std::memory_order_acquire);
        size_t current_tail = metadata_ptr->tail.load(std::memory_order_relaxed); // head由生产者更新,relaxed即可
        if (current_head >= current_tail) {
            return current_head - current_tail;
        } else {
            return buffer_capacity - current_tail + current_head;
        }
    }

public:
    UserRingBuffer() : metadata_ptr(nullptr), data_buffer_ptr(nullptr), buffer_capacity(0) {
#ifdef _WIN32
        device_handle = INVALID_HANDLE_VALUE;
        section_handle = INVALID_HANDLE_VALUE;
#else
        device_fd = -1;
#endif
    }

    ~UserRingBuffer() {
        if (data_buffer_ptr) {
#ifdef _WIN32
            UnmapViewOfFile(data_buffer_ptr);
#else
            munmap(data_buffer_ptr, buffer_capacity);
#endif
        }
        if (metadata_ptr) {
#ifdef _WIN32
            UnmapViewOfFile(metadata_ptr); // 如果元数据也映射了
#else
            munmap(metadata_ptr, sizeof(RingBufferMetadata));
#endif
        }

#ifdef _WIN32
        if (section_handle != INVALID_HANDLE_VALUE) {
            CloseHandle(section_handle); // 关闭Section句柄
        }
        if (device_handle != INVALID_HANDLE_VALUE) {
            CloseHandle(device_handle);
        }
#else
        if (device_fd != -1) {
            close(device_fd);
        }
#endif
    }

    void initialize() {
#ifdef _WIN32
        device_handle = CreateFile(L"\\.\MyRingBufferDevice", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
        if (device_handle == INVALID_HANDLE_VALUE) {
            throw std::runtime_error("Failed to open device. Error: " + std::to_string(GetLastError()));
        }

        // 获取元数据和Section Handle
        RingBufferMetadata initial_metadata;
        DWORD bytesReturned;
        if (!DeviceIoControl(device_handle, IOCTL_GET_RING_BUFFER_INFO, NULL, 0, &initial_metadata, sizeof(RingBufferMetadata), &bytesReturned, NULL)) {
            CloseHandle(device_handle);
            throw std::runtime_error("Failed to get ring buffer info via IOCTL. Error: " + std::to_string(GetLastError()));
        }
        buffer_capacity = initial_metadata.capacity;
        std::cout << "Received buffer capacity: " << buffer_capacity << " bytes." << std::endl;
        std::cout << "Received data physical address (placeholder for handle): " << initial_metadata.data_physical_address << std::endl;

        // 这里需要从驱动获取实际的Section Handle来映射,
        // 假设驱动通过IOCTL返回了一个可用的Section Handle LUID,并需要用户态重新打开
        // 或者驱动直接在IOCTL_GET_RING_BUFFER_INFO中返回了一个可被MapViewOfFile直接使用的HANDLE。
        // 为了简化,我们假设 initial_metadata.data_physical_address 实际上是驱动返回的一个可用于 CreateFileMapping 的 Section Handle (LUID)
        // 真实的Windows驱动通常会直接在IOCTL中传递一个DUPLICATE_HANDLE复制的HANDLE,或者一个LUID
        // 更加实际的场景是驱动直接将 Section Handle (KernelMode) 复制给用户态进程 (UserMode)。
        // 简化起见,这里假设我们能直接从驱动获取到 SectionObject 的句柄。
        // 在我们概念性的Windows驱动示例中,我们创建了一个全局的 g_SectionHandle,用户态需要某种方式获取它。
        // 假设 driver 提供了另一个 IOCTL 来获取这个 Section Handle
        // 如果驱动通过IOCTL返回一个实际的HANDLE值,我们可以直接使用
        // 例如,如果IOCTL_GET_RING_BUFFER_INFO返回的initial_metadata.data_physical_address是HANDLE值
        section_handle = (HANDLE)initial_metadata.data_physical_address; // 假设data_physical_address直接是HANDLE

        // 映射数据缓冲区
        data_buffer_ptr = (char*)MapViewOfFile(section_handle, FILE_MAP_ALL_ACCESS, 0, 0, buffer_capacity);
        if (data_buffer_ptr == NULL) {
            CloseHandle(device_handle);
            throw std::runtime_error("Failed to map data buffer. Error: " + std::to_string(GetLastError()));
        }
        std::cout << "Data buffer mapped to user VA: " << (void*)data_buffer_ptr << std::endl;

        // 映射元数据 (假设元数据也通过另一个Section或同一Section的偏移映射)
        // 如果元数据是在用户态单独分配的,则不需要映射
        // 如果元数据也需要被映射,需要单独的Section或通过offset映射同一个Section
        // 为了简化,我们假设元数据也通过同一个方法映射到了一个独立的地址
        // 实际中,元数据可能通过另一个IOCTL获取,或者直接是数据缓冲区前部的一小块
        // 这里为了演示,我们假设元数据也作为一部分被映射,或者通过IOCTL返回后在用户态独立管理
        // 我们在驱动中假设元数据是独立分配的,这里直接通过指针管理
        // 最佳实践是元数据也映射,这样可以减少IOCTL调用
        // 假设元数据也映射到了一个独立的地址,或者我们直接使用IOCTL返回的值作为本地副本,并频繁更新
        // 为了支持原子操作,元数据也应该被映射。这里我们假设其在数据缓冲区之前或之后,
        // 或者通过一个单独的映射获取。
        // 简单起见,我们假设驱动返回的RingBufferMetadata是一个可直接操作的指针,
        // 并且该指针指向的内存区域也已被映射。
        // 更现实的 Windows 场景是:驱动返回一个 SECTION HANDLE 供数据区,
        // 另一个 SECTION HANDLE 供元数据区,或者元数据通过 IOCTL 获取。
        // 鉴于我们的驱动示例中,元数据是独立分配的,这里我们先不直接映射元数据,而是假设通过IOCTL获取的副本。
        // 但为了原子操作,我们必须映射它。
        // 修正:在Windows驱动中,创建了 g_SectionHandle 给数据,那么元数据也需要一个 Section Handle。
        // 或者将元数据也放在数据 Section 的开头。
        // 简化:为了演示原子操作,我们假设元数据在数据缓冲区的紧邻位置,并一起映射。
        // 或者通过另一个 IOCTL 获取元数据 Section Handle 并映射。
        // 为了和 Linux 示例保持一致,我们假设元数据也通过 mmap 获得,
        // 但 Windows 驱动的 ZwCreateSection 示例只对数据区做了。
        // 那么,我们这里暂时用 IOCTL 获取的副本,然后模拟原子操作。
        // 真正零拷贝原子操作,元数据也必须映射。
        // 实际应用中,元数据往往是数据缓冲区的一部分,或者是一个独立的、小而独立的映射区域。
        // 为了统一,我们假设驱动也为元数据提供了映射。
        // 在 Linux 示例中,元数据也是独立分配并 remap_pfn_range 的。
        // 所以这里我们应该也有一个元数据映射。
        // 暂时简化处理:我们先假设 metadata_ptr 指向一个本地副本,其 head/tail 值由 IOCTL 更新
        // 这就意味着 head/tail 需要IOCTL来更新,这不是零拷贝。
        // 正确做法:元数据也映射。

        // 为了实现真正的零拷贝原子操作,元数据必须映射。
        // 假设驱动也返回了元数据 Section Handle,或者元数据紧邻数据区。
        // 在 Linux 驱动中,元数据是单独的区域,可以单独映射。
        // 在 Windows 驱动中,如果元数据是独立分配的,也需要创建单独的 Section。
        // 这里我们为了演示,假设 metadata_ptr 是通过一个类似于 data_buffer_ptr 的映射方式获得的。
        // 假设驱动也返回了一个元数据 Section Handle
        HANDLE metadata_section_handle = INVALID_HANDLE_VALUE; // 假设驱动通过另一个IOCTL返回
        // 暂不实现元数据映射,因为Windows驱动示例未提供。
        // 如果要实现,需要驱动提供另一个 Section Handle。
        // 暂时用IOCTL获取的副本。这就不是原子操作了。
        // 这是一个设计上的权衡点。为了简化,我们暂时用 IOCTL 获取的副本,并强调实际应映射。
        // 修正:我们必须映射元数据,否则无法进行原子操作。
        // 假设驱动的 IOCTL_GET_RING_BUFFER_INFO 实际上返回了两个 HANDLE:一个给数据,一个给元数据。
        // 这样比较符合实际。
        // 鉴于目前的 common.h 结构,我们可以在 IOCTL_GET_RING_BUFFER_INFO 返回一个结构体,
        // 包含数据缓冲区的 Section Handle LUID 和元数据缓冲区的 Section Handle LUID。
        // 然后用户态通过 LUID 重新打开并映射。
        // 为了简化,我们假设 initial_metadata.data_physical_address 实际上是 Section HANDLE,
        // 且元数据也在这个 Section 的某个偏移处。

        // 最终修正:为了与Linux驱动示例保持一致,我们假设元数据是独立映射的,
        // 并且通过IOCTL_GET_RING_BUFFER_INFO可以获取到元数据部分的物理地址(或Section LUID)。
        // 假设 `initial_metadata.data_physical_address` 是数据区的物理地址或 Section Handle,
        // 那么我们也需要一个 `metadata_physical_address`。
        // 为了简化,我们只映射数据区,并将元数据作为数据区的一个特殊部分。
        // 这是常见的零拷贝环形缓冲区设计,即元数据放在数据缓冲区头部。

        // 考虑到驱动示例,元数据和数据区是分开的,所以我们需要映射两次。
        // 我们需要修改 IOCTL_GET_RING_BUFFER_INFO 返回的信息,使其包含元数据区域的映射信息。
        // 简化:我们假设元数据通过同样的 mmap 方式映射,但大小不同。
        // 在 Linux 示例中,mmap 回调通过 size 来区分映射的是数据区还是元数据区。
        // 所以在用户态,我们也要 mmap 两次。
        metadata_ptr = (RingBufferMetadata*)MapViewOfFile(device_handle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(RingBufferMetadata));
        if (metadata_ptr == NULL) {
             UnmapViewOfFile(data_buffer_ptr);
             CloseHandle(device_handle);
             throw std::runtime_error("Failed to map metadata. Error: " + std::to_string(GetLastError()));
        }

#else // Linux/POSIX
        device_fd = open("/dev/ringbuffer_dev", O_RDWR);
        if (device_fd < 0) {
            throw std::runtime_error("Failed to open device /dev/ringbuffer_dev. Error: " + std::string(strerror(errno)));
        }

        // 1. 获取元数据信息
        RingBufferMetadata initial_metadata;
        if (ioctl(device_fd, IOCTL_GET_RING_BUFFER_INFO, &initial_metadata) < 0) {
            close(device_fd);
            throw std::runtime_error("Failed to get ring buffer info via IOCTL. Error: " + std::string(strerror(errno)));
        }
        buffer_capacity = initial_metadata.capacity;
        std::cout << "Received buffer capacity: " << buffer_capacity << " bytes." << std::endl;
        std::cout << "Received data physical address: 0x" << std::hex << initial_metadata.data_physical_address << std::dec << std::endl;

        // 2. 映射数据缓冲区
        data_buffer_ptr = (char*)mmap(NULL, buffer_capacity, PROT_READ | PROT_WRITE, MAP_SHARED, device_fd, 0);
        if (data_buffer_ptr == MAP_FAILED) {
            close(device_fd);
            throw std::runtime_error("Failed to map data buffer. Error: " + std::string(strerror(errno)));
        }
        std::cout << "Data buffer mapped to user VA: " << (void*)data_buffer_ptr << std::endl;

        // 3. 映射元数据缓冲区 (假设元数据是独立映射的)
        // 在 Linux 驱动中,我们通过 mmap 的 size 来区分。
        metadata_ptr = (RingBufferMetadata*)mmap(NULL, sizeof(RingBufferMetadata), PROT_READ | PROT_WRITE, MAP_SHARED, device_fd, buffer_capacity); // 假设元数据从数据区之后开始映射
        // 注意:这里的 offset (buffer_capacity) 需要与驱动的 mmap 实现匹配。
        // 更好的做法是驱动通过 IOCTL 返回元数据在设备文件中的偏移量。
        // 或者元数据有自己的设备文件或 mmap 请求。
        // 在 Linux 示例中,mmap 的 pgoff 是 0,它通过 size 区分。
        // 所以这里应该调用两次 mmap,每次提供不同的 size。
        // 第一次 mmap for data_buffer_ptr (size = buffer_capacity, offset = 0)
        // 第二次 mmap for metadata_ptr (size = sizeof(RingBufferMetadata), offset = 0)
        // 这样的话,同一个设备文件描述符,映射两次,但请求的虚拟内存区域大小不同,
        // 驱动的 mmap 回调会根据大小来判断并提供不同的物理地址。
        metadata_ptr = (RingBufferMetadata*)mmap(NULL, sizeof(RingBufferMetadata), PROT_READ | PROT_WRITE, MAP_SHARED, device_fd, 0);
        if (metadata_ptr == MAP_FAILED) {
            munmap(data_buffer_ptr, buffer_capacity);
            close(device_fd);
            throw std::runtime_error("Failed to map metadata. Error: " + std::string(strerror(errno)));
        }
        std::cout << "Metadata mapped to user VA: " << (void*)metadata_ptr << std::endl;

#endif
        std::cout << "Ring buffer initialized successfully in user space." << std::endl;
    }

    // 生产者写入数据
    bool produce(const std::vector<char>& data) {
        size_t data_len = data.size();
        size_t current_head = metadata_ptr->head.load(std::memory_order_relaxed); // relaxed即可,因为它只读取
        size_t current_tail = metadata_ptr->tail.load(std::memory_order_acquire); // acquire确保能看到消费者最新的tail

        size_t free_space;
        if (current_head >= current_tail) {
            free_space = buffer_capacity - (current_head - current_tail) - 1; // 留一个空位判断满
        } else {
            free_space = (current_tail - current_head) - 1;
        }

        if (data_len > free_space) {
            // std::cout << "Ring buffer full or not enough space. Head: " << current_head << ", Tail: " << current_tail << ", Free: " << free_space << ", Need: " << data_len << std::endl;
            return false; // 缓冲区空间不足
        }

        // 分段写入,处理环绕
        size_t bytes_to_end = buffer_capacity - current_head;
        if (data_len <= bytes_to_end) {
            std::memcpy(data_buffer_ptr + current_head, data.data(), data_len);
        } else {
            std::memcpy(data_buffer_ptr + current_head, data.data(), bytes_to_end);
            std::memcpy(data_buffer_ptr, data.data() + bytes_to_end, data_len - bytes_to_end);
        }

        // 更新head指针,使用release语义确保数据在head更新前可见
        metadata_ptr->head.store((current_head + data_len) % buffer_capacity, std::memory_order_release);
        return true;
    }

    // 消费者读取数据
    std::vector<char> consume(size_t max_len) {
        size_t current_head = metadata_ptr->head.load(std::memory_order_acquire); // acquire确保能看到生产者最新的head
        size_t current_tail = metadata_ptr->tail.load(std::memory_order_relaxed); // relaxed即可,因为它只读取

        size_t data_available;
        if (current_head >= current_tail) {
            data_available = current_head - current_tail;
        } else {
            data_available = buffer_capacity - current_tail + current_head;
        }

        if (data_available == 0) {
            return {}; // 缓冲区为空
        }

        size_t bytes_to_read = std::min(max_len, data_available);
        std::vector<char> read_data(bytes_to_read);

        // 分段读取,处理环绕
        size_t bytes_to_end = buffer_capacity - current_tail;
        if (bytes_to_read <= bytes_to_end) {
            std::memcpy(read_data.data(), data_buffer_ptr + current_tail, bytes_to_read);
        } else {
            std::memcpy(read_data.data(), data_buffer_ptr + current_tail, bytes_to_end);
            std::memcpy(read_data.data() + bytes_to_end, data_buffer_ptr, bytes_to_read - bytes_to_end);
        }

        // 更新tail指针,使用release语义确保数据在tail更新前已被读取
        metadata_ptr->tail.store((current_tail + bytes_to_read) % buffer_capacity, std::memory_order_release);
        return read_data;
    }

    size_t get_capacity() const { return buffer_capacity; }
    size_t get_head() const { return metadata_ptr->head.load(std::memory_order_relaxed); }
    size_t get_tail() const { return metadata_ptr->tail.load(std::memory_order_relaxed); }
};

// 生产者线程函数
void producer_thread(UserRingBuffer& rb, int id, int num_messages, size_t message_size) {
    std::vector<char> data(message_size);
    for (int i = 0; i < num_messages; ++i) {
        // 填充数据,例如:id 和 消息序号
        std::iota(data.begin(), data.end(), static_cast<char>(id * 100 + i % 100)); // 简单填充
        while (!rb.produce(data)) {
            // std::cout << "Producer " << id << ": Buffer full, waiting..." << std::endl;
            std::this_thread::yield(); // 缓冲区满,让出CPU
        }
        if (i % 1000 == 0) {
             // std::cout << "Producer " << id << ": Wrote " << i << " messages. Head: " << rb.get_head() << std::endl;
        }
        // std::this_thread::sleep_for(std::chrono::microseconds(1)); // 模拟工作
    }
    std::cout << "Producer " << id << " finished writing " << num_messages << " messages." << std::endl;
}

// 消费者线程函数
void consumer_thread(UserRingBuffer& rb, int id, int num_messages_to_consume, size_t message_size) {
    int consumed_count = 0;
    while (consumed_count < num_messages_to_consume) {
        std::vector<char> data = rb.consume(message_size);
        if (!data.empty()) {
            consumed_count++;
            if (consumed_count % 1000 == 0) {
                // std::cout << "Consumer " << id << ": Read " << consumed_count << " messages. Tail: " << rb.get_tail() << std::endl;
            }
            // 简单验证数据
            // if (!data.empty() && data[0] < 100 && data[0] >= 0) { // 假设生产者id在0-99之间
            //     // std::cout << "Consumer " << id << ": Read data from producer " << (int)data[0]/100 << std::endl;
            // }
        } else {
            // std::cout << "Consumer " << id << ": Buffer empty, waiting..." << std::endl;
            std::this_thread::yield(); // 缓冲区空,让出CPU
        }
        // std::this_thread::sleep_for(std::chrono::microseconds(2)); // 模拟工作
    }
    std::cout << "Consumer " << id << " finished consuming " << consumed_count << " messages." << std::endl;
}

int main() {
    try {
        UserRingBuffer rb;
        rb.initialize();

        const int num_producers = 2;
        const int num_consumers = 2;
        const int messages_per_producer = 50000;
        const size_t message_size = 64; // 每个消息64字节

        std::vector<std::thread> producer_threads;
        std::vector<std::thread> consumer_threads;

        std::cout << "Starting producer threads..." << std::endl;
        for (int i = 0; i < num_producers; ++i) {
            producer_threads.emplace_back(producer_thread, std::ref(rb), i, messages_per_producer, message_size);
        }

        std::cout << "Starting consumer threads..." << std::endl;
        for (int i = 0; i < num_consumers; ++i) {
            consumer_threads.emplace_back(consumer_thread, std::ref(rb), i, (num_producers * messages_per_producer) / num_consumers, message_size);
        }

        for (auto& t : producer_threads) {
            t.join();
        }
        std::cout << "All producer threads joined." << std::endl;

        for (auto& t : consumer_threads) {
            t.join();
        }
        std::cout << "All consumer threads joined." << std::endl;

        std::cout << "Ring buffer test completed." << std::endl;

    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

用户态实现的关键点:

  • 平台特定 API
    • Linux/POSIX: 使用 open() 打开设备文件,ioctl() 获取元数据,mmap() 映射内存,munmap() 解除映射,close() 关闭文件描述符。
    • Windows: 使用 CreateFile() 打开设备,DeviceIoControl() 获取元数据,MapViewOfFile() 映射内存,UnmapViewOfFile() 解除映射,CloseHandle() 关闭句柄。
  • std::atomic:在 C++11 及更高版本中,std::atomic<size_t> 用于 headtail 指针,确保其读写操作是原子的,并提供内存顺序控制。
    • std::memory_order_acquire:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注