linux memfd 和udmabuf 机制详解

memfd与udmabuf零拷贝机制解析

概述

memfdudmabuf 是 Linux 内核提供的两个关键机制,用于实现用户空间与设备驱动之间的高效零拷贝内存共享。它们共同构成了现代 GPU 运行时(如 ROCm)实现 CPU-GPU 共享内存的基础设施。

1. memfd_create 机制

1.1 什么是 memfd

memfd_create 是 Linux 3.17 引入的系统调用,用于创建匿名文件(anonymous file):

int memfd = memfd_create(const char *name, unsigned int flags);

系统调用原型:

#include <sys/mman.h>

int memfd_create(const char *name, unsigned int flags);
// 返回:成功返回文件描述符,失败返回 -1

参数:

  • name:文件名称(仅用于调试,显示在 /proc/[pid]/fd/[fd]
  • flags:控制标志,常用的有:
    • MFD_CLOEXECexec() 时自动关闭
    • MFD_ALLOW_SEALING:允许文件密封(sealing)
    • MFD_HUGETLB:使用巨页(2MB/1GB)

1.2 核心特性

1.2.1 纯内存文件
传统文件系统:
User Space → VFS → 文件系统 → Block Layer → 磁盘
              ↓
           Page Cache

memfd:
User Space → VFS → tmpfs (纯内存)
              ↓
           Page Cache (无磁盘回写)

特点:

  • 文件完全存在于 RAM 中(基于 tmpfs)
  • 永远不会写入磁盘
  • 系统重启后自动消失
  • 可以超过 tmpfs 挂载点的大小限制
1.2.2 文件描述符接口

memfd 返回标准文件描述符,支持所有文件操作:

int fd = memfd_create("my_buffer", MFD_ALLOW_SEALING);

// 文件操作
ftruncate(fd, size);           // 设置大小
write(fd, data, len);          // 写入数据
read(fd, buf, len);            // 读取数据
lseek(fd, offset, SEEK_SET);   // 定位
fstat(fd, &st);                // 获取状态

// 内存映射
void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, 
                 MAP_SHARED, fd, 0);

// 传递给其他进程或设备
send_fd_over_socket(fd);       // Unix domain socket
ioctl(device_fd, CMD, fd);     // 传递给内核驱动
1.2.3 引用计数和生命周期
int fd = memfd_create("buffer", 0);    // refcount = 1
void *map1 = mmap(..., fd, ...);       // refcount = 2
void *map2 = mmap(..., fd, ...);       // refcount = 3
close(fd);                             // refcount = 2 (文件仍存在)
munmap(map1, size);                    // refcount = 1
munmap(map2, size);                    // refcount = 0 → 文件被销毁

内核引用计数:

  • 每个打开的 fd 增加引用
  • 每个 mmap 映射增加引用
  • 引用为 0 时,内核自动释放所有物理页
1.2.4 无路径名(Anonymous)
# 查看进程的 memfd
$ ls -l /proc/[pid]/fd/
lrwx------ 1 user user 64 Nov 26 10:00 3 -> /memfd:my_buffer (deleted)

# 无法通过文件系统路径访问
$ cat /memfd:my_buffer
cat: /memfd:my_buffer: No such file or directory

优势:

  • 避免文件系统命名冲突
  • 防止其他进程意外访问
  • 不占用 inode 配额
  • 安全性更高(需要 fd 才能访问)

1.3 文件密封 (Sealing)

1.3.1 什么是 Sealing

文件密封是一种不可逆的安全机制,用于限制文件的修改操作:

#include <linux/fcntl.h>

int fcntl(int fd, int cmd, int seals);
// cmd: F_ADD_SEALS (添加密封), F_GET_SEALS (获取密封状态)
1.3.2 密封类型
密封标志禁止的操作用途
F_SEAL_SHRINKftruncate() 缩小防止截断数据
F_SEAL_GROWftruncate() 扩大防止扩展文件
F_SEAL_WRITEwrite(), pwrite()禁止写入数据
F_SEAL_FUTURE_WRITE新的 writable mmap允许现有映射写,禁止新映射
F_SEAL_SEAL添加更多密封锁定密封状态
1.3.3 使用示例
int fd = memfd_create("sealed_buffer", MFD_ALLOW_SEALING);

// 设置大小
ftruncate(fd, 4096);

// 密封:禁止修改大小
if (fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW) < 0) {
    perror("fcntl F_ADD_SEALS");
    return -1;
}

// 以下操作将失败
ftruncate(fd, 8192);  // 返回 -1, errno = EPERM

// 可以查询密封状态
int seals = fcntl(fd, F_GET_SEALS);
if (seals & F_SEAL_GROW) {
    printf("File is sealed against growing\n");
}
1.3.4 DMA 安全保障

问题场景: 没有 sealing 时

时间 T0: GPU 开始 DMA 传输,物理地址 0x1000-0x2000
时间 T1: 用户调用 ftruncate(fd, 0)
时间 T2: 内核释放物理页 0x1000-0x2000
时间 T3: GPU 继续写入 → 访问已释放的内存 → 系统崩溃

使用 sealing 后:

时间 T0: fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW)
时间 T1: GPU 开始 DMA 传输
时间 T2: 用户调用 ftruncate(fd, 0) → 返回 EPERM(拒绝)
时间 T3: GPU 安全完成传输

1.4 memfd 内核实现

1.4.1 物理页分配
// 内核伪代码
struct file *memfd_create(...) {
    struct file *file;
    struct inode *inode;
    
    // 在 tmpfs 中创建匿名 inode
    inode = shmem_get_inode(sb, NULL, S_IFREG | S_IRWXUGO, 0, flags);
    
    // 分配 file 结构
    file = alloc_file_pseudo(inode, shm_mnt, name, O_RDWR, ...);
    
    // 设置标志
    if (flags & MFD_ALLOW_SEALING)
        file->f_seals = 0;  // 允许添加密封
    
    return file;
}
1.4.2 mmap 映射过程
用户调用: mmap(..., fd, offset)
    ↓
内核 VMA 分配: vm_area_struct
    ↓
页表条目标记为 PRESENT=0 (延迟分配)
    ↓
用户首次访问 → Page Fault
    ↓
内核分配物理页: alloc_pages()
    ↓
建立页表映射: PTE = 物理地址 | 权限位
    ↓
返回用户空间

共享映射(MAP_SHARED):

进程 A: mmap(fd) → VA1 → 物理页 P
进程 B: mmap(fd) → VA2 → 物理页 P (同一物理页)

1.5 memfd 的应用场景

1.5.1 进程间共享内存

传统方法: POSIX shared memory (shm_open)

int fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0666);
// 问题:需要全局唯一名称,可能冲突

使用 memfd:

int fd = memfd_create("shared", 0);
// 通过 Unix domain socket 传递 fd
send_fd_to_child_process(fd);
1.5.2 安全的临时文件

传统方法:

FILE *tmp = tmpfile();
// 问题:可能写入磁盘,有安全风险

使用 memfd:

int fd = memfd_create("temp", MFD_CLOEXEC);
// 保证纯内存,exec 后自动清理
1.5.3 沙箱和容器
// 容器创建只读代码段
int fd = memfd_create("code", MFD_ALLOW_SEALING);
write(fd, code, code_size);
fcntl(fd, F_ADD_SEALS, F_SEAL_WRITE | F_SEAL_SHRINK | F_SEAL_GROW);

// 传递给沙箱进程,确保代码不被篡改
exec_in_sandbox(fd);

2. udmabuf 机制

2.1 什么是 udmabuf

udmabuf (userspace DMA buffer) 是 Linux 4.20 引入的内核驱动,用于将用户空间内存转换为 DMA-BUF 对象:

/dev/udmabuf → udmabuf 驱动 → DMA-BUF 框架

设备文件:

$ ls -l /dev/udmabuf
crw------- 1 root root 10, 125 Nov 26 10:00 /dev/udmabuf

2.2 DMA-BUF 框架

DMA-BUF的具体原理和实现请参见:Linux dma-buf 框架原理、实现与应用

2.2.1 什么是 DMA-BUF

DMA-BUF 是 Linux 内核的通用缓冲区共享框架,用于在不同设备驱动之间共享物理内存:

┌─────────────────────────────────────────┐
│         DMA-BUF 框架                    │
│  ┌──────────────────────────────────┐   │
│  │     struct dma_buf               │   │
│  │  - file (文件描述符)              │   │
│  │  - ops (操作回调)                 │   │
│  │  - attachments (设备列表)         │   │
│  │  - sgt (scatter-gather 表)       │   │
│  └──────────────────────────────────┘   │
└─────────────────────────────────────────┘
         ↓           ↓           ↓
    ┌────────┐  ┌────────┐  ┌────────┐
    │  GPU   │  │  VPU   │  │Display │
    └────────┘  └────────┘  └────────┘
2.2.2 核心概念

1. Exporter(导出者)

// GPU 驱动导出 VRAM 缓冲区
struct dma_buf *dmabuf = gpu_export_dmabuf(bo);
int fd = dma_buf_fd(dmabuf, O_CLOEXEC);
// 返回文件描述符,可传递给其他设备

2. Importer(导入者)

// 显示控制器导入 GPU 缓冲区
struct dma_buf *dmabuf = dma_buf_get(fd);
struct dma_buf_attachment *attach = dma_buf_attach(dmabuf, display_dev);
struct sg_table *sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
// 获取物理页列表,建立 IOMMU 映射

3. Fence(栅栏)同步

// GPU 完成渲染后设置 fence
dma_fence_signal(fence);

// 显示控制器等待 fence
dma_fence_wait(fence, true);
// 确保 GPU 完成后再扫描显示

2.3 udmabuf 工作原理

2.3.1 数据结构
struct udmabuf_create {
    __u32 memfd;     // memfd 文件描述符
    __u32 flags;     // UDMABUF_FLAGS_CLOEXEC
    __u64 offset;    // memfd 中的偏移
    __u64 size;      // DMA-BUF 大小
};

struct udmabuf_create_list {
    __u32 flags;
    __u32 count;
    struct udmabuf_create_item list[];
};
2.3.2 ioctl 接口
#include <linux/udmabuf.h>

int udmabuf_fd = open("/dev/udmabuf", O_RDWR);

struct udmabuf_create create = {
    .memfd = memfd,
    .flags = UDMABUF_FLAGS_CLOEXEC,
    .offset = 0,
    .size = buffer_size
};

int dmabuf_fd = ioctl(udmabuf_fd, UDMABUF_CREATE, &create);
// 返回 DMA-BUF 文件描述符
2.3.3 内核实现流程
// 简化的内核实现
static long udmabuf_ioctl(struct file *file, unsigned int cmd, 
                          unsigned long arg) {
    struct udmabuf_create create;
    struct udmabuf *ubuf;
    struct dma_buf *dmabuf;
    struct file *memfd_file;
    int ret;
    
    copy_from_user(&create, arg, sizeof(create));
    
    // 1. 获取 memfd 的 file 结构
    memfd_file = fget(create.memfd);
    
    // 2. 分配 udmabuf 对象
    ubuf = kzalloc(sizeof(*ubuf), GFP_KERNEL);
    ubuf->memfd = memfd_file;
    ubuf->offset = create.offset;
    ubuf->size = create.size;
    
    // 3. Pin 住物理页
    ubuf->pagecount = (create.size + PAGE_SIZE - 1) >> PAGE_SHIFT;
    ubuf->pages = kmalloc_array(ubuf->pagecount, sizeof(*ubuf->pages));
    
    for (i = 0; i < ubuf->pagecount; i++) {
        struct page *page;
        // 从 memfd 获取物理页
        page = shmem_read_mapping_page(memfd_file->f_mapping, 
                                       (create.offset >> PAGE_SHIFT) + i);
        get_page(page);  // 增加引用计数
        ubuf->pages[i] = page;
    }
    
    // 4. 创建 DMA-BUF 对象
    exp_info.ops = &udmabuf_ops;
    exp_info.size = create.size;
    exp_info.priv = ubuf;
    dmabuf = dma_buf_export(&exp_info);
    
    // 5. 返回文件描述符
    ret = dma_buf_fd(dmabuf, create.flags);
    return ret;
}
2.3.4 Pin 物理页的重要性

为什么需要 Pin:

未 Pin:
用户进程分配内存 → 物理页 P
    ↓
进程空闲 → 内核 swap out → P 换出到磁盘
    ↓
GPU DMA 访问 → 读取到磁盘数据 → 错误

Pin 后:
用户进程分配内存 → 物理页 P
    ↓
get_page(P) → P 的引用计数 +1
    ↓
SetPageReserved(P) → 标记为内核保留页
    ↓
内核不会 swap out → GPU DMA 安全
2.3.5 udmabuf与dmabuf的关系

正如其名字所示,userspace dma buffer是将用户空间申请的一片存储空间(userspace buffer object)绑定到dmabuf上,封装为 dmabuf 文件描述符(dmabuf fd)。dmabuf是绑定内核分配的buffer object(通常是drm 子系统里的drm_gem_object)。

维度dmabufudmabuf
本质内核级跨设备内存共享框架(抽象)基于 dmabuf 框架的 “用户态内存封装工具”
内存来源可绑定内核 / 用户态内存仅封装用户态申请的内存(memfd / 文件映射等)
核心作用提供统一的内存共享 fd,让不同驱动(GPU / 视频 / 显示)访问同一块物理内存突破 dmabuf “传统以内核内存为主” 的限制,让用户态内存也能通过 dmabuf 共享
典型关联对象内核:drm_gem_object、dma-buf pool用户态内存(如 memfd、匿名 mmap)→ 转为 dmabuf fd

2.4 udmabuf 的优势

2.4.1 零拷贝数据传输

传统方法:

User Space Buffer
    ↓ (copy_from_user)
Kernel Staging Buffer
    ↓ (DMA)
GPU Memory

使用 udmabuf:

User Space Buffer (memfd + udmabuf)
    ↓ (DMA 直接访问)
GPU Memory

性能对比:

传输大小传统拷贝udmabuf提升
1MB0.5ms0.05ms10x
100MB50ms5ms10x
1GB500ms50ms10x
2.4.2 统一的设备共享接口
// 创建共享缓冲区
int memfd = memfd_create("shared", MFD_ALLOW_SEALING);
ftruncate(memfd, size);

int dmabuf_fd = ioctl(udmabuf_fd, UDMABUF_CREATE, &create);

// 导入到多个设备
ioctl(gpu_fd, GPU_IMPORT_DMABUF, dmabuf_fd);
ioctl(vpu_fd, VPU_IMPORT_DMABUF, dmabuf_fd);
ioctl(display_fd, DISPLAY_IMPORT_DMABUF, dmabuf_fd);

// 所有设备访问同一物理内存
2.4.3 安全隔离
进程 A (Untrusted):
    创建 memfd → 密封 → 传递 dmabuf_fd 给进程 B

进程 B (Trusted):
    导入 dmabuf → GPU 访问
    
进程 A 无法修改内存(已密封)
进程 B 无法访问进程 A 的其他内存

2.5 udmabuf vs 其他方法

2.5.1 vs VRAM 分配
特性udmabuf (系统内存)VRAM 分配
物理位置DDRGPU 显存
CPU 访问速度快 (~50GB/s)慢 (~10GB/s, PCIe)
GPU 访问速度中 (~30GB/s, PCIe)快 (~500GB/s)
容量大 (系统内存)小 (显存)
适用场景CPU-GPU 交互GPU 专用计算
2.5.2 vs 传统 ioctl

传统方法:

// 用户空间分配
void *buf = malloc(size);
memcpy(buf, data, size);

// 传递给内核
ioctl(device_fd, CUSTOM_IOCTL, buf);
// 内核需要 copy_from_user → 开销大

udmabuf 方法:

// memfd 分配
int memfd = memfd_create("buf", 0);
void *buf = mmap(..., memfd, ...);
memcpy(buf, data, size);

// 转换为 dmabuf
int dmabuf_fd = ioctl(udmabuf_fd, UDMABUF_CREATE, ...);

// 导入设备
ioctl(device_fd, IMPORT_DMABUF, dmabuf_fd);
// 零拷贝,直接 DMA
2.5.3 vs CMA (Contiguous Memory Allocator)
特性udmabufCMA
物理连续性不保证保证
分配来源普通页CMA 保留区
灵活性
IOMMU 依赖需要(scatter-gather)不需要
适用场景有 IOMMU 的现代 GPU无 IOMMU 的嵌入式设备

3. memfd + udmabuf 组合使用

3.1 典型工作流程

// 1. 创建 memfd
int memfd = memfd_create("gpu_buffer", MFD_ALLOW_SEALING);

// 2. 设置大小
ftruncate(memfd, buffer_size);

// 3. 密封(可选,但推荐)
fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW);

// 4. CPU 映射
void *cpu_ptr = mmap(NULL, buffer_size, PROT_READ | PROT_WRITE,
                     MAP_SHARED, memfd, 0);

// 5. CPU 写入数据
memcpy(cpu_ptr, input_data, buffer_size);

// 6. 转换为 DMA-BUF
int udmabuf_dev = open("/dev/udmabuf", O_RDWR);
struct udmabuf_create create = {
    .memfd = memfd,
    .flags = UDMABUF_FLAGS_CLOEXEC,
    .offset = 0,
    .size = buffer_size
};
int dmabuf_fd = ioctl(udmabuf_dev, UDMABUF_CREATE, &create);

// 7. 导入到 GPU
ioctl(gpu_fd, GPU_IMPORT_DMABUF, dmabuf_fd);

// 8. GPU 处理数据(零拷贝)
ioctl(gpu_fd, GPU_EXECUTE_KERNEL, ...);

// 9. CPU 读取结果(零拷贝)
process_results(cpu_ptr);

// 10. 清理
munmap(cpu_ptr, buffer_size);
close(dmabuf_fd);
close(memfd);
close(udmabuf_dev);

3.2 完整示例:图像处理

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/udmabuf.h>

#define WIDTH 1920
#define HEIGHT 1080
#define SIZE (WIDTH * HEIGHT * 4)  // RGBA

int main() {
    // 创建 memfd
    int memfd = memfd_create("image_buffer", MFD_ALLOW_SEALING);
    if (memfd < 0) {
        perror("memfd_create");
        return 1;
    }
    
    // 设置大小
    if (ftruncate(memfd, SIZE) < 0) {
        perror("ftruncate");
        return 1;
    }
    
    // 密封
    if (fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW) < 0) {
        perror("fcntl");
        return 1;
    }
    
    // CPU 映射
    void *image = mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
                       MAP_SHARED, memfd, 0);
    if (image == MAP_FAILED) {
        perror("mmap");
        return 1;
    }
    
    // 加载图像数据
    load_image_from_file(image, "input.png");
    
    // 创建 udmabuf
    int udmabuf_dev = open("/dev/udmabuf", O_RDWR);
    if (udmabuf_dev < 0) {
        perror("open /dev/udmabuf");
        return 1;
    }
    
    struct udmabuf_create create = {
        .memfd = memfd,
        .flags = UDMABUF_FLAGS_CLOEXEC,
        .offset = 0,
        .size = SIZE
    };
    
    int dmabuf_fd = ioctl(udmabuf_dev, UDMABUF_CREATE, &create);
    if (dmabuf_fd < 0) {
        perror("UDMABUF_CREATE");
        return 1;
    }
    
    // 导入到 GPU 并处理
    int gpu_fd = open("/dev/dri/renderD128", O_RDWR);
    struct gpu_import_args import = {
        .dmabuf_fd = dmabuf_fd,
        .width = WIDTH,
        .height = HEIGHT
    };
    ioctl(gpu_fd, GPU_IMPORT_DMABUF, &import);
    
    // 执行 GPU 滤镜
    ioctl(gpu_fd, GPU_APPLY_FILTER, FILTER_BLUR);
    
    // CPU 读取处理后的图像(零拷贝)
    save_image_to_file(image, "output.png");
    
    // 清理
    munmap(image, SIZE);
    close(dmabuf_fd);
    close(memfd);
    close(udmabuf_dev);
    close(gpu_fd);
    
    return 0;
}

4. 内核版本要求

特性最低内核版本推荐版本
memfd_create3.174.14+
文件密封3.174.14+
udmabuf 驱动4.205.4+
UDMABUF_CREATE_LIST5.125.15+

检查内核支持:

# 检查 memfd
$ grep CONFIG_MEMFD_CREATE /boot/config-$(uname -r)
CONFIG_MEMFD_CREATE=y

# 检查 udmabuf
$ grep CONFIG_UDMABUF /boot/config-$(uname -r)
CONFIG_UDMABUF=y

# 运行时检查
$ ls /dev/udmabuf
/dev/udmabuf

5. 性能考虑

5.1 memfd 性能

优势:

  • 纯内存操作,无磁盘 I/O
  • Page cache 直接命中
  • 支持巨页(THP),减少 TLB Miss

开销:

  • memfd_create:~5μs
  • ftruncate:~10μs
  • fcntl(F_ADD_SEALS):~2μs
  • mmap:~20μs(延迟分配)

5.2 udmabuf 性能

优势:

  • 零拷贝数据传输
  • 批量 pin 页,避免 page fault

开销:

  • UDMABUF_CREATE:~100μs(1MB), ~1ms(100MB)
    • Pin 页:占主要时间
    • 创建 scatter-gather 表
  • IOMMU 映射:~50μs/MB

优化建议:

  1. 预分配大块内存,避免频繁创建
  2. 使用巨页减少页表条目
  3. 批量导入多个 dmabuf

6. 安全性考虑

6.1 memfd 安全

优势:

  • 匿名文件,无全局命名空间冲突
  • 密封机制防止意外修改
  • MFD_CLOEXEC 防止 fd 泄漏

注意事项:

  • 仍然可以通过 /proc/[pid]/fd/ 访问
  • 需要正确设置 sealing 标志
  • 大量分配可能耗尽内存

6.2 udmabuf 安全

优势:

  • 设备驱动验证 fd 合法性
  • Pin 页防止 use-after-free
  • DMA-BUF 框架提供引用计数

注意事项:

  • 需要 root 或 CAP_SYS_ADMIN 权限(部分系统)
  • Pin 的页无法 swap,可能导致内存压力
  • 多设备共享需要正确的 fence 同步

7 总结

7.1 关键优势

memfd:

  1. 纯内存文件,零磁盘开销
  2. 文件描述符接口,易于传递
  3. 密封机制,DMA 安全保障
  4. 引用计数,自动生命周期管理

udmabuf:

  1. 零拷贝,高性能数据传输
  2. 统一接口,多设备共享
  3. Pin 物理页,防止交换
  4. DMA-BUF 框架,标准化同步

7.2 组合效果

memfd + udmabuf 提供了一套完整的用户空间 DMA 解决方案:

  • 用户空间灵活分配内存(memfd)
  • 安全可靠的 DMA 传输(密封 + pin)
  • 高效的设备间共享(DMA-BUF)
  • 零拷贝的 CPU-GPU 数据交换

这是现代 Linux 系统上 GPU 计算、视频处理、AI 推理等异构计算场景的基础设施。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值