概述
memfd 和 udmabuf 是 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_CLOEXEC:exec()时自动关闭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_SHRINK | ftruncate() 缩小 | 防止截断数据 |
F_SEAL_GROW | ftruncate() 扩大 | 防止扩展文件 |
F_SEAL_WRITE | write(), 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)。
| 维度 | dmabuf | udmabuf |
|---|---|---|
| 本质 | 内核级跨设备内存共享框架(抽象) | 基于 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 | 提升 |
|---|---|---|---|
| 1MB | 0.5ms | 0.05ms | 10x |
| 100MB | 50ms | 5ms | 10x |
| 1GB | 500ms | 50ms | 10x |
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 分配 |
|---|---|---|
| 物理位置 | DDR | GPU 显存 |
| 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)
| 特性 | udmabuf | CMA |
|---|---|---|
| 物理连续性 | 不保证 | 保证 |
| 分配来源 | 普通页 | 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_create | 3.17 | 4.14+ |
| 文件密封 | 3.17 | 4.14+ |
udmabuf 驱动 | 4.20 | 5.4+ |
UDMABUF_CREATE_LIST | 5.12 | 5.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μsftruncate:~10μsfcntl(F_ADD_SEALS):~2μsmmap:~20μs(延迟分配)
5.2 udmabuf 性能
优势:
- 零拷贝数据传输
- 批量 pin 页,避免 page fault
开销:
UDMABUF_CREATE:~100μs(1MB), ~1ms(100MB)- Pin 页:占主要时间
- 创建 scatter-gather 表
- IOMMU 映射:~50μs/MB
优化建议:
- 预分配大块内存,避免频繁创建
- 使用巨页减少页表条目
- 批量导入多个 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:
- 纯内存文件,零磁盘开销
- 文件描述符接口,易于传递
- 密封机制,DMA 安全保障
- 引用计数,自动生命周期管理
udmabuf:
- 零拷贝,高性能数据传输
- 统一接口,多设备共享
- Pin 物理页,防止交换
- DMA-BUF 框架,标准化同步
7.2 组合效果
memfd + udmabuf 提供了一套完整的用户空间 DMA 解决方案:
- 用户空间灵活分配内存(memfd)
- 安全可靠的 DMA 传输(密封 + pin)
- 高效的设备间共享(DMA-BUF)
- 零拷贝的 CPU-GPU 数据交换
这是现代 Linux 系统上 GPU 计算、视频处理、AI 推理等异构计算场景的基础设施。
memfd与udmabuf零拷贝机制解析
5979

被折叠的 条评论
为什么被折叠?



