研读DPDK框架代码时,发现有多处用到mmap和memfd_create()调用进行内存共享。若想深入理解这些功能代码,就很有两必要了解这种内存共享的实现方式。以下是对POSIX mmap文件映射和memfd_create()+fd跨进程共享的总结介绍。
一、POSIX mmap文件
POSIX open()/shm_open(),mmap文件来共享内存的使用方式比SYS V中的那些ftok、shmget、shmat、shmdt、shmctl等系列API,更方便,更易使用。
以下两个小程序,分别完成数据的写和读操作。
写端:
-
#include <sys/mman.h>
-
#include <sys/stat.h> /* For mode constants */
-
#include <fcntl.h> /* For O_* constants */
-
#include <unistd.h>
-
#include <stdio.h>
-
-
#define SHARE_MEMORY_NAME "POSIX_SHM"
-
-
typedef struct {
-
int data1;
-
int data2;
-
int data3;
-
int data4;
-
} share_data;
-
-
int main(void)
-
{
-
share_data *sdata;
-
int fd;
-
-
shm_unlink(SHARE_MEMORY_NAME);
-
fd = shm_open(SHARE_MEMORY_NAME, O_RDWR | O_CREAT, 0666);
-
if (fd == -1) {
-
printf("create posix shared memory failed.\n");
-
return -1;
-
}
-
ftruncate(fd, sizeof(share_data));
-
-
sdata = mmap(NULL, sizeof(share_data),
-
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-
if (sdata == MAP_FAILED) {
-
printf("mmap failed\n");
-
return -1;
-
}
-
-
sdata->data1 = 12;
-
sdata->data2 = 13;
-
sdata->data3 = 14;
-
sdata->data4 = 15;
-
munmap(sdata, sizeof(share_data));
-
-
return 0;
-
}
读端:
-
#include <sys/mman.h>
-
#include <sys/stat.h> /* For mode constants */
-
#include <fcntl.h> /* For O_* constants */
-
#include <stdio.h>
-
#include <unistd.h>
-
-
#define SHARE_MEMORY_NAME "POSIX_SHM"
-
-
typedef struct {
-
int data1;
-
int data2;
-
int data3;
-
int data4;
-
} share_data;
-
-
int main(void)
-
{
-
share_data *sdata;
-
int fd;
-
-
fd = shm_open(SHARE_MEMORY_NAME, O_RDONLY, 0666);
-
if (fd == -1) {
-
printf("open posix shared memory failed.\n");
-
return -1;
-
}
-
ftruncate(fd, sizeof(share_data));
-
-
sdata = mmap(NULL, sizeof(share_data), PROT_READ, MAP_SHARED, fd, 0);
-
if (sdata == MAP_FAILED) {
-
printf("mmap failed\n");
-
return -1;
-
}
-
-
printf("share memory: data1=%d, data2=%d, data3=%d, data4=%d\n",
-
sdata->data1, sdata->data2, sdata->data3, sdata->data4);
-
munmap(sdata, sizeof(share_data));
-
-
return 0;
-
}
编译和执行结果:
读端程序,获取到的数据为写端程序所写入的值。
运行完后,
会在/dev/shm/、/run/shm下面看到一个文件:
POSIX mmap方式,读写进程通过打开相同路径下的文件,得到共享文件的fd,再通过ftruncate()设置共享文件的大小,使用mmap的方式进行文件映射到进程得到进程中的虚拟地址 ,进程通过直接操作虚拟地址从而操作共享内存。以上生成的文件在OS中都有具体的文件名。
二、memfd_create匿名文件
使用memfd_create()不同于open()或者shm_open(),它会创建一个匿名文件,在OS下没有确切的文件,而是存在于RAM中。该调用会返回一个可引用的文件描述符,可进行类似于regular文件的操作。它的manual page描述如下图:
注意其中的描述,当所有的引用关闭后,它将自动释放。
当进行Linux应用编程时,需要一个fd类似于文件操作,但又不希望文件系统中可见,此时就可以使用memfd_create的方式。
有了这个指向的匿名内存的文件描述符,再加上UNIX Socket的控制消息cmsg,将此文件描述符传递给另外一个不相关的进程(可支持多个fd的传输)。从而另外一个进程对该fd进行mmap操作就得共享内存的虚拟地址,如此就可实现内存的共享。
使用UNIX Socket的控制消息传送多个描述符实现:
发送多个描述符接口:
接收多个描述符的接口:
注意:进程透过UNIX Socket把一个或多个fds发送给另一个进程。fd是一个进程级的概念,每个进程有自己的fds列表。当使用cmsg传递文件描述符时,若传送的文件描述符在另一个进程中已被占用,则该文件描述符可能会变成另一个未使用的文件描述符,其同样与该匿名内存关联。
memfd_create()+fd跨进程共享实操
共享内存写端:shmfd_create_write.c
-
#define _GNU_SOURCE
-
-
#include <sys/mman.h>
-
#include <sys/syscall.h>
-
#include <linux/memfd.h>
-
#include <sys/socket.h>
-
#include <sys/un.h>
-
-
#include <fcntl.h> /* For O_* constants */
-
#include <unistd.h>
-
#include <stdlib.h>
-
#include <string.h>
-
#include <stdio.h>
-
-
-
#define SHARE_MEMORY_NAME1 "MEMFD_SHM1"
-
#define SHARE_MEMORY_NAME2 "MEMFD_SHM2"
-
-
#define SOCKET_PATH "my-socket"
-
-
-
typedef struct {
-
int data1;
-
int data2;
-
int data3;
-
int data4;
-
} share_data;
-
-
static int send_fd(int socket, int *fds, int n)
-
{
-
char buf[CMSG_SPACE(n * sizeof(int))];
-
struct msghdr msg = {0};
-
struct cmsghdr *cmsg;
-
struct iovec io[2];
-
char data[2];
-
-
io[0].iov_base = &data[0];
-
io[0].iov_len = sizeof(char);
-
io[1].iov_base = &data[1];
-
io[1].iov_len = sizeof(char);
-
msg.msg_iov = io;
-
msg.msg_iovlen = 2;
-
-
/* TCP时name为NULL,长度为0,数据报套接字UDP需填充 */
-
msg.msg_name = NULL;
-
msg.msg_namelen = 0;
-
/*设置缓冲区和长度*/
-
msg.msg_control = buf;
-
msg.msg_controllen = sizeof(buf);
-
-
cmsg = CMSG_FIRSTHDR(&msg);
-
cmsg->cmsg_level = SOL_SOCKET;
-
cmsg->cmsg_type = SCM_RIGHTS; // 指明发送的消息类型为描述符
-
cmsg->cmsg_len = CMSG_LEN(n * sizeof(int));
-
-
memcpy((int *)CMSG_DATA(cmsg), fds, n * sizeof(int));
-
-
if (sendmsg(socket, &msg, 0) < 0) {
-
printf("send msg failed\n");
-
return -1;
-
}
-
-
return 0;
-
}
-
-
int main(void)
-
{
-
struct sockaddr_un addr = {0};
-
share_data *sdata;
-
int sfd, fd[2];
-
int ret;
-
-
fd[0] = syscall(__NR_memfd_create, SHARE_MEMORY_NAME1, MFD_CLOEXEC);
-
if (fd[0] == -1) {
-
printf("open posix shared memory failed.\n");
-
return -1;
-
}
-
ftruncate(fd[0], sizeof(share_data));
-
fd[1] = syscall(__NR_memfd_create, SHARE_MEMORY_NAME2, MFD_CLOEXEC);
-
if (fd[1] == -1) {
-
printf("open posix shared memory failed.\n");
-
return -1;
-
}
-
-
sdata = mmap(NULL, sizeof(share_data),
-
PROT_READ | PROT_WRITE, MAP_SHARED, fd[0], 0);
-
if (sdata == MAP_FAILED) {
-
printf("mmap failed\n");
-
return -1;
-
}
-
-
sdata->data1 = 125;
-
sdata->data2 = 130;
-
sdata->data3 = 140;
-
sdata->data4 = 150;
-
-
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
-
if (sfd == -1) {
-
printf("create socket failed\n");
-
return -1;
-
}
-
printf("cfd=%d fd[0]=%d fd[1]=%d\n", sfd, fd[0], fd[1]);
-
addr.sun_family = AF_UNIX;
-
strcpy(addr.sun_path, SOCKET_PATH);
-
-
ret = connect(sfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un));
-
if (ret == -1) {
-
printf("connect error\n");
-
return -1;
-
}
-
-
if (send_fd(sfd, fd, 2) < 0) {
-
printf("send fds failed\n");
-
return -1;
-
}
-
-
munmap(sdata, sizeof(share_data));
-
-
return 0;
-
}
共享内存读端:shmfd_create_read.c
-
#define _GNU_SOURCE
-
-
#include <sys/mman.h>
-
#include <sys/syscall.h>
-
#include <linux/memfd.h>
-
#include <sys/socket.h>
-
#include <sys/un.h>
-
-
#include <fcntl.h> /* For O_* constants */
-
#include <unistd.h>
-
#include <stdlib.h>
-
#include <string.h>
-
#include <stdio.h>
-
-
-
#define SHARE_MEMORY_NAME "MEMFD_SHM"
-
#define SOCKET_PATH "my-socket"
-
-
typedef struct {
-
int data1;
-
int data2;
-
int data3;
-
int data4;
-
} share_data;
-
-
static int* recv_fd(int socket, int n)
-
{
-
char buf[CMSG_SPACE(n * sizeof(int))];
-
struct msghdr msg = {0};
-
struct cmsghdr *cmsg;
-
struct iovec io;
-
char data[2];
-
int *fds;
-
-
io.iov_base = data;
-
io.iov_len = 1;
-
-
msg.msg_iov = &io;
-
msg.msg_iovlen = 1;
-
msg.msg_control = buf;
-
msg.msg_controllen = sizeof(buf);
-
-
if (recvmsg(socket, &msg, 0) < 0) {
-
printf("failed to receive messag\n");
-
return NULL;
-
}
-
cmsg = CMSG_FIRSTHDR(&msg);
-
-
fds = calloc(n, sizeof(int));
-
memcpy(fds, (int *)CMSG_DATA(cmsg), n * sizeof(int));
-
-
return fds;
-
}
-
-
static int create_socket(void)
-
{
-
struct sockaddr_un addr = {0};
-
int sfd;
-
-
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
-
if (sfd == -1) {
-
printf("create socket failed\n");
-
return -1;
-
}
-
-
unlink(SOCKET_PATH);
-
memset(&addr, 0, sizeof(struct sockaddr_un));
-
addr.sun_family = AF_UNIX;
-
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
-
-
if (bind(sfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
-
printf("failed to bind socket\n");
-
return -1;
-
}
-
-
if (listen(sfd, 5) == -1) {
-
printf("failed to listen socket.");
-
return -1;
-
}
-
-
return sfd;
-
}
-
-
int main(void)
-
{
-
share_data *sdata;
-
int sfd, cfd;
-
int *fd;
-
-
sfd = create_socket();
-
if (sfd == -1) {
-
printf("create socket failed\n");
-
return -1;
-
}
-
cfd = accept(sfd, NULL, NULL);
-
if (cfd == -1) {
-
printf("accept fd failed\n");
-
return -1;
-
}
-
-
printf("cfd=%d\n", cfd);
-
fd = recv_fd(cfd, 2);
-
if (fd == NULL) {
-
printf("receive fds failed\n");
-
return -1;
-
}
-
-
sdata = mmap(NULL, sizeof(share_data), PROT_READ, MAP_SHARED, fd[0], 0);
-
if (sdata == MAP_FAILED) {
-
printf("mmap failed\n");
-
return -1;
-
}
-
-
printf("fd[1]=%d, fd[1]=%d\n", fd[0], fd[1]);
-
printf("share memory from fd[0]=%d: data1=%d, data2=%d, data3=%d, data4=%d\n",
-
fd[0], sdata->data1, sdata->data2, sdata->data3, sdata->data4);
-
-
close(cfd);
-
munmap(sdata, sizeof(share_data));
-
free(fd);
-
-
return 0;
-
}
编译:
gcc memfd_create_write.c -o memfd_w
gcc memfd_create_read.c -o memfd_r
先运行读端,创建socket,再运行写端,写端向共享memory写入125、120、140和150。读端再从共享memory中读取数据,读取的结果为写进程的数据。
阅读(2193) | 评论(0) | 转发(0) |