Chinaunix首页 | 论坛 | 博客
  • 博客访问: 32763
  • 博文数量: 17
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 165
  • 用 户 组: 普通用户
  • 注册时间: 2022-11-22 23:41
个人简介

将分享技术博文作为一种快乐,提升自己帮助他人

文章分类

全部博文(17)

文章存档

2023年(9)

2022年(5)

我的朋友

分类: LINUX

2023-05-11 23:57:43

研读DPDK框架代码时,发现有多处用到mmap和memfd_create()调用进行内存共享。若想深入理解这些功能代码,就很有两必要了解这种内存共享的实现方式。以下是对POSIX mmap文件映射和memfd_create()+fd跨进程共享的总结介绍。

一、POSIX mmap文件

POSIX open()/shm_open(),mmap文件来共享内存的使用方式比SYS V中的那些ftok、shmget、shmat、shmdt、shmctl等系列API,更方便,更易使用。

以下两个小程序,分别完成数据的写和读操作。

写端:

点击(此处)折叠或打开

  1. #include <sys/mman.h>
  2. #include <sys/stat.h> /* For mode constants */
  3. #include <fcntl.h> /* For O_* constants */
  4. #include <unistd.h>
  5. #include <stdio.h>

  6. #define SHARE_MEMORY_NAME    "POSIX_SHM"

  7. typedef struct {
  8.     int data1;
  9.     int data2;
  10.     int data3;
  11.     int data4;
  12. } share_data;

  13. int main(void)
  14. {
  15.     share_data *sdata;
  16.     int fd;

  17.     shm_unlink(SHARE_MEMORY_NAME);
  18.     fd = shm_open(SHARE_MEMORY_NAME, O_RDWR | O_CREAT, 0666);
  19.     if (fd == -1) {
  20.         printf("create posix shared memory failed.\n");
  21.         return -1;
  22.     }
  23.     ftruncate(fd, sizeof(share_data));

  24.     sdata = mmap(NULL, sizeof(share_data),
  25.          PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  26.     if (sdata == MAP_FAILED) {
  27.         printf("mmap failed\n");
  28.         return -1;
  29.     }

  30.     sdata->data1 = 12;
  31.     sdata->data2 = 13;
  32.     sdata->data3 = 14;
  33.     sdata->data4 = 15;
  34.     munmap(sdata, sizeof(share_data));

  35.     return 0;
  36. }
读端:

点击(此处)折叠或打开

  1. #include <sys/mman.h>
  2. #include <sys/stat.h> /* For mode constants */
  3. #include <fcntl.h> /* For O_* constants */
  4. #include <stdio.h>
  5. #include <unistd.h>

  6. #define SHARE_MEMORY_NAME    "POSIX_SHM"

  7. typedef struct {
  8.     int data1;
  9.     int data2;
  10.     int data3;
  11.     int data4;
  12. } share_data;

  13. int main(void)
  14. {
  15.     share_data *sdata;
  16.     int fd;

  17.     fd = shm_open(SHARE_MEMORY_NAME, O_RDONLY, 0666);
  18.     if (fd == -1) {
  19.         printf("open posix shared memory failed.\n");
  20.         return -1;
  21.     }
  22.     ftruncate(fd, sizeof(share_data));

  23.     sdata = mmap(NULL, sizeof(share_data), PROT_READ, MAP_SHARED, fd, 0);
  24.     if (sdata == MAP_FAILED) {
  25.         printf("mmap failed\n");
  26.         return -1;
  27.     }

  28.     printf("share memory: data1=%d, data2=%d, data3=%d, data4=%d\n",
  29.         sdata->data1, sdata->data2, sdata->data3, sdata->data4);
  30.     munmap(sdata, sizeof(share_data));

  31.     return 0;
  32. }
编译和执行结果:

读端程序,获取到的数据为写端程序所写入的值。
运行完后,会在/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

点击(此处)折叠或打开

  1. #define _GNU_SOURCE

  2. #include <sys/mman.h>
  3. #include <sys/syscall.h>
  4. #include <linux/memfd.h>
  5. #include <sys/socket.h>
  6. #include <sys/un.h>

  7. #include <fcntl.h> /* For O_* constants */
  8. #include <unistd.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <stdio.h>


  12. #define SHARE_MEMORY_NAME1    "MEMFD_SHM1"
  13. #define SHARE_MEMORY_NAME2    "MEMFD_SHM2"

  14. #define SOCKET_PATH "my-socket"


  15. typedef struct {
  16.     int data1;
  17.     int data2;
  18.     int data3;
  19.     int data4;
  20. } share_data;

  21. static int send_fd(int socket, int *fds, int n)
  22. {
  23.     char buf[CMSG_SPACE(n * sizeof(int))];
  24.     struct msghdr msg = {0};
  25.     struct cmsghdr *cmsg;
  26.     struct iovec io[2];
  27.     char data[2];

  28.     io[0].iov_base = &data[0];
  29.     io[0].iov_len = sizeof(char);
  30.     io[1].iov_base = &data[1];
  31.     io[1].iov_len = sizeof(char);
  32.     msg.msg_iov = io;
  33.     msg.msg_iovlen = 2;

  34.     /* TCP时name为NULL,长度为0,数据报套接字UDP需填充 */
  35.     msg.msg_name = NULL;
  36.     msg.msg_namelen = 0;
  37.     /*设置缓冲区和长度*/
  38.     msg.msg_control = buf;
  39.     msg.msg_controllen = sizeof(buf);

  40.     cmsg = CMSG_FIRSTHDR(&msg);
  41.     cmsg->cmsg_level = SOL_SOCKET;
  42.     cmsg->cmsg_type = SCM_RIGHTS; // 指明发送的消息类型为描述符
  43.     cmsg->cmsg_len = CMSG_LEN(n * sizeof(int));

  44.     memcpy((int *)CMSG_DATA(cmsg), fds, n * sizeof(int));

  45.     if (sendmsg(socket, &msg, 0) < 0) {
  46.         printf("send msg failed\n");
  47.         return -1;
  48.     }

  49.     return 0;
  50. }

  51. int main(void)
  52. {
  53.     struct sockaddr_un addr = {0};
  54.     share_data *sdata;
  55.     int sfd, fd[2];
  56.     int ret;

  57.     fd[0] = syscall(__NR_memfd_create, SHARE_MEMORY_NAME1, MFD_CLOEXEC);
  58.     if (fd[0] == -1) {
  59.         printf("open posix shared memory failed.\n");
  60.         return -1;
  61.     }
  62.     ftruncate(fd[0], sizeof(share_data));
  63.     fd[1] = syscall(__NR_memfd_create, SHARE_MEMORY_NAME2, MFD_CLOEXEC);
  64.     if (fd[1] == -1) {
  65.         printf("open posix shared memory failed.\n");
  66.         return -1;
  67.     }

  68.     sdata = mmap(NULL, sizeof(share_data),
  69.          PROT_READ | PROT_WRITE, MAP_SHARED, fd[0], 0);
  70.     if (sdata == MAP_FAILED) {
  71.         printf("mmap failed\n");
  72.         return -1;
  73.     }

  74.     sdata->data1 = 125;
  75.     sdata->data2 = 130;
  76.     sdata->data3 = 140;
  77.     sdata->data4 = 150;

  78.     sfd = socket(AF_UNIX, SOCK_STREAM, 0);
  79.     if (sfd == -1) {
  80.         printf("create socket failed\n");
  81.         return -1;
  82.     }
  83.     printf("cfd=%d fd[0]=%d fd[1]=%d\n", sfd, fd[0], fd[1]);
  84.     addr.sun_family = AF_UNIX;
  85.     strcpy(addr.sun_path, SOCKET_PATH);

  86.     ret = connect(sfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un));
  87.     if (ret == -1) {
  88.         printf("connect error\n");
  89.         return -1;
  90.     }

  91.     if (send_fd(sfd, fd, 2) < 0) {
  92.         printf("send fds failed\n");
  93.         return -1;
  94.     }

  95.     munmap(sdata, sizeof(share_data));

  96.     return 0;
  97. }
共享内存读端:shmfd_create_read.c

点击(此处)折叠或打开

  1. #define _GNU_SOURCE

  2. #include <sys/mman.h>
  3. #include <sys/syscall.h>
  4. #include <linux/memfd.h>
  5. #include <sys/socket.h>
  6. #include <sys/un.h>

  7. #include <fcntl.h> /* For O_* constants */
  8. #include <unistd.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <stdio.h>


  12. #define SHARE_MEMORY_NAME    "MEMFD_SHM"
  13. #define SOCKET_PATH "my-socket"

  14. typedef struct {
  15.     int data1;
  16.     int data2;
  17.     int data3;
  18.     int data4;
  19. } share_data;

  20. static int* recv_fd(int socket, int n)
  21. {
  22.     char buf[CMSG_SPACE(n * sizeof(int))];
  23.     struct msghdr msg = {0};
  24.     struct cmsghdr *cmsg;
  25.     struct iovec io;
  26.     char data[2];
  27.     int *fds;

  28.     io.iov_base = data;
  29.     io.iov_len = 1;

  30.     msg.msg_iov = &io;
  31.     msg.msg_iovlen = 1;
  32.     msg.msg_control = buf;
  33.     msg.msg_controllen = sizeof(buf);

  34.     if (recvmsg(socket, &msg, 0) < 0) {
  35.         printf("failed to receive messag\n");
  36.         return NULL;
  37.     }
  38.     cmsg = CMSG_FIRSTHDR(&msg);

  39.     fds = calloc(n, sizeof(int));
  40.     memcpy(fds, (int *)CMSG_DATA(cmsg), n * sizeof(int));

  41.     return fds;
  42. }

  43. static int create_socket(void)
  44. {
  45.     struct sockaddr_un addr = {0};
  46.     int sfd;

  47.     sfd = socket(AF_UNIX, SOCK_STREAM, 0);
  48.     if (sfd == -1) {
  49.         printf("create socket failed\n");
  50.         return -1;
  51.     }

  52.     unlink(SOCKET_PATH);
  53.     memset(&addr, 0, sizeof(struct sockaddr_un));
  54.     addr.sun_family = AF_UNIX;
  55.     strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);

  56.     if (bind(sfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
  57.         printf("failed to bind socket\n");
  58.         return -1;
  59.     }

  60.     if (listen(sfd, 5) == -1) {
  61.         printf("failed to listen socket.");
  62.         return -1;
  63.     }

  64.     return sfd;
  65. }

  66. int main(void)
  67. {
  68.     share_data *sdata;
  69.     int sfd, cfd;
  70.     int *fd;

  71.     sfd = create_socket();
  72.     if (sfd == -1) {
  73.         printf("create socket failed\n");
  74.         return -1;
  75.     }
  76.     cfd = accept(sfd, NULL, NULL);
  77.     if (cfd == -1) {
  78.         printf("accept fd failed\n");
  79.         return -1;
  80.     }

  81.     printf("cfd=%d\n", cfd);
  82.     fd = recv_fd(cfd, 2);
  83.     if (fd == NULL) {
  84.         printf("receive fds failed\n");
  85.         return -1;
  86.     }

  87.     sdata = mmap(NULL, sizeof(share_data), PROT_READ, MAP_SHARED, fd[0], 0);
  88.     if (sdata == MAP_FAILED) {
  89.         printf("mmap failed\n");
  90.         return -1;
  91.     }

  92.     printf("fd[1]=%d, fd[1]=%d\n", fd[0], fd[1]);
  93.     printf("share memory from fd[0]=%d: data1=%d, data2=%d, data3=%d, data4=%d\n",
  94.         fd[0], sdata->data1, sdata->data2, sdata->data3, sdata->data4);

  95.     close(cfd);
  96.     munmap(sdata, sizeof(share_data));
  97.     free(fd);

  98.     return 0;
  99. }
编译:
gcc memfd_create_write.c -o memfd_w
gcc memfd_create_read.c -o memfd_r


先运行读端,创建socket,再运行写端,写端向共享memory写入125、120、140和150。读端再从共享memory中读取数据,读取的结果为写进程的数据。


阅读(1534) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~