buffer共享-1-memfd
一、简介
memfd 提供了一种跨进程传buffer的机制,依赖于进程间通信机制(binder/socket)传递生成的fd,传递过程涉及到内存buffer的拷贝。
二、man memfd_create
$ man memfd_create
名称:
memfd_create - 创建匿名文件
概要:
#include <sys/memfd.h> int memfd_create(const char *name, unsigned int flags);
描述:
memfd_create() 创建一个匿名文件并返回一个指向该文件的文件描述符。该文件的行为与普通文件类似,因此可以被修改、截断、进行内存映射等等。然而,与普通文件不同的是,它位于 RAM 中,并具有易失性后备存储。一旦所有对该文件的引用都被删除,它将自动释放。该文件的所有后备页面都将使用匿名内存。因此,由 memfd_create() 创建的文件与其他匿名内存分配(例如使用带有 MAP_ANONYMOUS 标志的 mmap(2) 分配的文件)具有相同的语义。
文件的初始大小设置为 0####。调用后,应使用 ftruncate(2) 设置文件大小####。(或者,可以通过调用 write(2) 或类似函数来填充文件。)
name 中提供的名称将用作文件名,并显示为目录 /proc/self/fd/ 中相应符号链接的目标####。显示的名称始终以 memfd: 为前缀,仅用于调试目的。名称不会影响文件描述符的行为,因此多个文件可以具有相同的名称而不会产生任何副作用。
以下值可以在 flags 中进行按位或运算,以更改 memfd_create() 的行为:
MFD_CLOEXEC
在新的文件描述符上设置执行时关闭 (FD_CLOEXEC) 标志。请参阅 open(2) 中 O_CLOEXEC 标志的描述,了解该标志的用途。
MFD_ALLOW_SEALING
允许对此文件进行密封操作。请参阅 fcntl(2) 中关于 F_ADD_SEALS 和 F_GET_SEALS 操作的讨论,以及下文的“注意事项”。初始密封集为空。
如果未设置此标志,则初始密封集将为 F_SEAL_SEAL,这意味着无法在文件上设置任何其他密封。
MFD_HUGETLB
匿名文件将在 hugetlbfs 文件系统中使用大页面创建。有关 hugetlbfs 的更多信息,请参阅 Linux 内核源文件 Documentation/vm/hugetlbpage.txt。hugetlbfs 文件系统不支持文件密封操作。因此,不允许在标志中同时指定 MFD_HUGETLB 和 MFD_ALLOW_SEALING。
MFD_HUGE_2MB、MFD_HUGE_1GB 等与 MFD_HUGETLB 结合使用,在支持多种 hugetlb 页面大小的系统上选择备选的 hugetlb 页面大小(分别为 2 MB、1 GB 等)。已知 hugetlb 页面大小的定义包含在头文件 <sys/memfd.h> 中。
有关未包含在头文件中的 hugetlb 页面大小的编码详细信息,请参阅 mmap(2) 中类似名称常量的讨论。
标志位中未使用的位必须为 0。
memfd_create() 返回一个可用于引用该文件的新文件描述符作为其返回值。该文件描述符以读写方式打开(O_RDWR),并设置 O_LARGEFILE 标志。
对于 fork(2) 和 execve(2),memfd_create() 创建的文件描述符的通常语义适用。fork(2) 生成的子进程将继承该文件描述符的副本,并指向同一个文件。除非设置了 close-on-exec 标志,否则该文件描述符在 execve(2) 过程中将保留####。
返回值:
成功时,memfd_create() 返回一个新的文件描述符。错误时,返回 -1,并设置 errno 以指示错误。
错误:
EFAULT 名称中的地址指向无效内存。
EINVAL 标志包含未知位。
EINVAL 名称太长。(限制为 249 个字节,不包括终止空字节。)
EINVAL 标志中同时指定了 MFD_HUGETLB 和 MFD_ALLOW_SEALING。
EMFILE 已达到每个进程打开文件描述符数量的限制。
ENFILE 已达到系统范围内打开文件总数的限制。
ENOMEM 内存不足,无法创建新的匿名文件。
版本:
memfd_create() 系统调用首次出现在 Linux 3.17 中;glibc 支持在 2.27 版本中添加。
符合:
memfd_create() 系统调用是 Linux 特有的。
注意事项:
memfd_create() 系统调用提供了一种简单的替代方案,可以手动挂载 tmpfs(5) 文件系统并在其中创建和打开文件。memfd_create() 的主要目的是创建文件及其关联的文件描述符,以便与 fcntl(2) 提供的文件密封 API 配合使用。
memfd_create() 系统调用也可以在没有文件密封的情况下使用(这就是为什么文件密封会被禁用,除非使用 MFD_ALLOW_SEALING 标志明确请求)。具体来说,它可以用作在 tmp 中创建文件的替代方案,或者在不打算将生成的文件实际链接到文件系统的情况下,用作使用 open(2) O_TMPFILE 的替代方案。
文件密封:
在没有文件密封的情况下,通过共享内存通信的进程必须相互信任,或者采取措施来应对不受信任的对等方可能以有问题的方式操纵共享内存区域的可能性。例如,不受信任的对等体可能随时修改共享内存的内容,或缩小共享内存区域。前一种可能性会使本地进程容易受到检查时间到使用时间竞争条件的影响(通常通过在检查和使用之前从共享内存区域复制数据来处理)。后一种可能性会使本地进程在尝试访问共享内存区域中现已不存在的位置时容易受到 SIGBUS 信号的攻击。(处理这种可能性需要使用 SIGBUS 信号的处理程序。)
处理不受信任的对等体会给使用共享内存的代码带来额外的复杂性。内存密封可以消除这种额外的复杂性,因为它允许进程在知道其对等体无法以非预期的方式修改共享内存的情况下安全地运行。
密封机制的使用示例如下:
1. 第一个进程使用 memfd_create() 创建一个 tmpfs(5) 文件。该调用会生成一个文件描述符,用于后续步骤。
2. 第一个进程使用 ftruncate(2) 调整上一步创建的文件的大小,使用 mmap(2) 映射文件,并将所需数据填充到共享内存中。
3. 第一个进程使用 fcntl(2) 的 F_ADD_SEALS 操作对文件进行一个或多个密封,以限制对文件的进一步修改。 (如果设置了 F_SEAL_WRITE 密封,则需要先取消映射上一步创建的共享可写映射。)
4. 第二个进程获取 tmpfs(5) 文件的文件描述符并进行映射。以下是可能的实现方式:
(1) 调用 memfd_create() 的进程可以通过 UNIX 域套接字将生成的文件描述符传输给第二个进程(参见 unix(7) 和 cmsg(3)####)。然后,第二个进程使用 mmap(2) 映射该文件。
(2) 第二个进程是通过 fork(2) 创建的,因此会自动继承文件描述符和映射。 (请注意,在本例和下例中,两个进程之间存在自然信任关系,因为它们使用相同的用户 ID 运行。因此,通常无需进行文件密封。)
(3) 第二个进程打开文件 /proc/<pid>/fd/<fd>,其中 <pid> 是第一个进程(调用 memfd_create() 的进程)的 PID,<fd> 是该进程调用 memfd_create() 返回的文件描述符编号。然后,第二个进程使用 mmap(2) 映射该文件。
5. 第二个进程使用 fcntl(2) 的 F_GET_SEALS 操作检索已应用于文件的密封位掩码。可以检查此位掩码以确定对文件修改施加了哪些类型的限制。如果需要,第二个进程可以应用进一步的密封以施加额外的限制(只要尚未应用 F_SEAL_SEAL 密封)。
示例:
下面展示了两个示例程序,演示了 memfd_create() 和文件密封 API 的用法。
第一个程序 t_memfd_create.c 使用 memfd_create() 创建一个 tmpfs(5) 文件,设置文件大小,将其映射到内存中,并可选地对文件进行密封。该程序最多接受三个命令行参数,其中前两个是必需的。第一个参数是与文件关联的名称,第二个参数是要设置的文件大小,第三个可选参数是一串字符串,用于指定要在文件上设置的密封。
第二个程序 t_get_seals.c 可用于打开通过 memfd_create() 创建的现有文件,并检查已应用于该文件的密封集。
以下 shell 会话演示了这些程序的使用方法。首先,我们创建一个 tmpfs(5) 文件并对其进行一些密封:
$ ./t_memfd_create my_memfd_file 4096 sw & [1] 11775 PID: 11775; fd: 3; /proc/11775/fd/3
此时,t_memfd_create 程序继续在后台运行。我们可以从另一个程序中,通过打开与 memfd_create() 打开的文件描述符对应的 /proc/[pid]/fd 文件,获取 memfd_create() 创建的文件的文件描述符。使用该路径名,我们检查 /proc/[pid]/fd 符号链接的内容,并使用 t_get_seals 程序查看已对文件进行的封印:
$ readlink /proc/11775/fd/3 /memfd:my_memfd_file (deleted) $ ./t_get_seals /proc/11775/fd/3 Existing seals: WRITE SHRINK
程序源代码:t_memfd_create.c
#include <sys/memfd.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdio.h> #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0) int main(int argc, char *argv[]) { int fd; unsigned int seals; char *addr; char *name, *seals_arg; ssize_t len; if (argc < 3) { fprintf(stderr, "%s name size [seals]\n", argv[0]); fprintf(stderr, "\t'seals' can contain any of the following characters:\n"); fprintf(stderr, "\t\tg - F_SEAL_GROW\n"); fprintf(stderr, "\t\ts - F_SEAL_SHRINK\n"); fprintf(stderr, "\t\tw - F_SEAL_WRITE\n"); fprintf(stderr, "\t\tS - F_SEAL_SEAL\n"); exit(EXIT_FAILURE); } name = argv[1]; len = atoi(argv[2]); seals_arg = argv[3]; /* Create an anonymous file in tmpfs; allow seals to be placed on the file */ fd = memfd_create(name, MFD_ALLOW_SEALING); if (fd == -1) errExit("memfd_create"); /* Size the file as specified on the command line */ if (ftruncate(fd, len) == -1) errExit("truncate"); printf("PID: %ld; fd: %d; /proc/%ld/fd/%d\n", (long)getpid(), fd, (long)getpid(), fd); /* Code to map the file and populate the mapping with data, omitted */ /* If a 'seals' command-line argument was supplied, set some seals on the file */ if (seals_arg != NULL) { seals = 0; if (strchr(seals_arg, 'g') != NULL) seals |= F_SEAL_GROW; if (strchr(seals_arg, 's') != NULL) seals |= F_SEAL_SHRINK; if (strchr(seals_arg, 'w') != NULL) seals |= F_SEAL_WRITE; if (strchr(seals_arg, 'S') != NULL) seals |= F_SEAL_SEAL; if (fcntl(fd, F_ADD_SEALS, seals) == -1) errExit("fcntl"); } /* Keep running, so that the file created by memfd_create() continues to exist */ pause(); exit(EXIT_SUCCESS); }
程序源码: t_get_seals.c
#include <sys/memfd.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0) int main(int argc, char *argv[]) { int fd; unsigned int seals; if (argc != 2) { fprintf(stderr, "%s /proc/PID/fd/FD\n", argv[0]); exit(EXIT_FAILURE); } fd = open(argv[1], O_RDWR); if (fd == -1) errExit("open"); seals = fcntl(fd, F_GET_SEALS); if (seals == -1) errExit("fcntl"); printf("Existing seals:"); if (seals & F_SEAL_SEAL) printf(" SEAL"); if (seals & F_SEAL_GROW) printf(" GROW"); if (seals & F_SEAL_WRITE) printf(" WRITE"); if (seals & F_SEAL_SHRINK) printf(" SHRINK"); printf("\n"); /* Code to map the file and access the contents of the resulting mapping omitted */ exit(EXIT_SUCCESS); }
SEE ALSO:
fcntl(2), ftruncate(2), mmap(2), shmget(2), shm_open(3)
四、实验
1. 只是创建fd
#include <sys/mman.h> #include <sys/syscall.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main() { // 创建 memfd 文件(名为 "test") int fd = memfd_create("test", MFD_CLOEXEC | MFD_ALLOW_SEALING); if (fd == -1) { perror("memfd_create"); return 1; } // 写入数据 write(fd, "Hello, memfd!", 13); // 映射到内存 char *addr = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 0); printf("Mapped content: %s\n", addr); sleep(120); // 清理 munmap(addr, 4096); close(fd); return 0; }
注: 这个版本的Ubuntu还不支持这个系统调用:
$ cat /proc/version
Linux version 5.4.0-216-generic (gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2))
注: Android-14也不支持这个系统调用(若内核支持了,估计得自己封装系统调用)
frameworks/native/cmds/memfd_test/memfd_test.c:9:14: error: call to undeclared function 'memfd_create'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] int fd = memfd_create("test", MFD_CLOEXEC | MFD_ALLOW_SEALING);
==> 但是将 .c 改为 .cpp 文件,就可以编译了!执行打印:
/data/local/tmp # ./memfd_test Mapped content: Hello, memfd! /proc/8036/fd # ls -l ... lrwx------ 1 root root 64 2025-08-25 22:21 3 -> /memfd:test (deleted)
参考:
linux 共享内存 memfd和dma_buf: https://zhuanlan.zhihu.com/p/1903445860911669696 //里面有通过socket传fd.
posted on 2025-08-25 17:54 Hello-World3 阅读(49) 评论(0) 收藏 举报