共享内存技术之memfd_create()

  • Post author:
  • Post category:其他





前言

写这边博客的目的,在于对宋宝华老师的无线崇拜!在阅读了他的

世界上最好的共享内存

博客以后,正如原文所说,发现了一个尤物,不得已眼前一亮,让人把持不住。废话少说,直接上干货



跨进程分享文件描述符


以下文字均来自于宋宝华老师的共享内存博客


某年某月的某一天,人们发现,一个进程其实想访问另外一个进程的fd。当然,这只是目的不是手段。比如进程A有2个fd指向2片内存,如果进程B可以拿到这2个fd,其实就可以透过这2个fd访问到这2片内存。这个fd某种意义上充当了一个中间媒介的作用。有人说,那还不简单吗,如果进程A:fd = open();

open()如果返回100,把这个100告诉进程B不就可以了吗,进程B访问这个100就可以了。这说明你还是没搞明白fd是一个进程内部的东西,是不能跨进程的概念。你的100和我的100,不是一个东西。这些基本的东西你搞不明白,你搞别的都是白搭。

在这里插入图片描述

Linux提供一个特殊的方法,可以把一个进程的fd甩锅、踢皮球给另外一个进程(其实“甩锅”这个词用在这里不合适,因为“甩锅”是一种推卸,而fd的传递是一种分享)。我特码一直想把我的bug甩(分)锅(享)出去,却发现总是被人把bug甩锅过来。

那么如何甩(分)锅(享)fd呢?

Linux里面的甩锅需要借助cmsg,用于在socket上传递控制消息(也称Ancillary data),使用SCM_RIGHTS,进程可以透过UNIX Socket把一个或者多个fd(file descriptor)传递给另外一个进程。



memfd_create函数介绍


man手册查看


在这里插入图片描述

参数解析


const char* name:

表示的是因为文件的文件名(不必真实存在!!!)


int flags:

用来设置此段文件描述符的行为

在这里插入图片描述


从上可以看出,如果没有使用规定的flags参数,什么加密等,就默认设置为0,其返回的文件描述符,具备读,可读可写的权限。同时在fork或execve时,子进程会继承该文件描述符,且与父进程指向同一个空间



案例



数据采集端

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <sys/un.h> //sockaddr_un头文件
#include <sys/mman.h>
#include <time.h>
#include <sys/mman.h>
#include <linux/memfd.h>
#include <sys/syscall.h>
#include <errno.h>

typedef struct data{
    char temperature_1[8];
    char temperature_2[8];
    char temperature_3[8];
    char temperature_4[8];
    char temperature_5[8];
}Data;

//发送fd给接收函数,甩锅
static int send_fd(int socket,int *fds,int n){
    struct msghdr msg={0};
    struct cmsghdr *cmsg;
    char buf[CMSG_SPACE(n*sizeof(int))],data;
    memset(buf,'\0',sizeof(buf));
    struct iovec io={.iov_base=&data,.iov_len=1};

    msg.msg_iov=&io;
    msg.msg_iovlen=1;
    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)==-1){
        perror("[failed to send message]");
        return -1;
    }
    return 0;
}


int main(int argc ,char** argv){
    srand((unsigned int)time(NULL));
    int sfd,fd;
    struct sockaddr_un addr;
    //获取套接字
    sfd=socket(AF_UNIX,SOCK_STREAM,0);
    if(sfd==-1){
        perror("[Failed to create socket]");
        return -1;
    }
    memset(&addr,'\0',sizeof(struct sockaddr_un));
    addr.sun_family=AF_UNIX;
    strncpy(addr.sun_path,"/home/jacky/Work/Machine_room_monitoring/1.socket",sizeof(addr.sun_path)-1);

    //获取文件描述符
    fd=syscall(__NR_memfd_create,"shm",MFD_CLOEXEC);
    if(fd<0){
        perror("[syscall memfd_create error]");
    }
    printf("fd:%d\n",fd);
    //将文件扩大
    ftruncate(fd,0x1024);

    //初始化内存空间

    //连接接收数据端
    if(connect(sfd,(struct sockaddr *)&addr,sizeof(struct sockaddr_un))==-1){
        perror("[connect error]");
        return -1;
    }

    if(send_fd(sfd,&fd,1)==-1){
        exit(-1);
    }
    printf("OK\n");
    //映射fd
    Data* ptr=(Data *)mmap(NULL,sizeof(Data),PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(ptr==NULL){
        perror("[mmap error]");
        return -1;
    }
    float t;
    while (1){
        t=(float )(rand()%1000)/100.0+20.0;
        gcvt(t,4,ptr->temperature_1);
        printf("%s\n",ptr->temperature_1);
        t=(float )(rand()%1000)/100.0+20.0;
        gcvt(t,4,ptr->temperature_2);
        printf("%s\n",ptr->temperature_2);
        t=(float )(rand()%1000)/100.0+20.0;
        gcvt(t,4,ptr->temperature_3);
        printf("%s\n",ptr->temperature_3);
        t=(float )(rand()%1000)/100.0+20.0;
        gcvt(t,4,ptr->temperature_4);
        printf("%s\n",ptr->temperature_4);
        t=(float )(rand()%1000)/100.0+20.0;
        gcvt(t,4,ptr->temperature_5);
        printf("%s\n",ptr->temperature_5);
        sleep(2);
    }
    printf("OK\n");
    return 0;
}



数据接收端

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <unistd.h>
#include <sys/syscall.h>   /* For SYS_xxx definitions */
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <sys/un.h>
#include <stdio.h>
#include <errno.h>
#include <sys/mman.h>

typedef struct data{
    char temperature_1[8];
    char temperature_2[8];
    char temperature_3[8];
    char temperature_4[8];
    char temperature_5[8];
}Data;

static int recv_fd(int socket,int n){
    int fd;
    struct msghdr msg={0};
    struct cmsghdr *cmsg;
    char buf[CMSG_SPACE(n*sizeof(int))],data;
    memset(buf,0,sizeof(buf));
    struct iovec io={.iov_base=&data,.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){
        perror("[Failed to receive message]");
        return -1;
    }
    cmsg=CMSG_FIRSTHDR(&msg);
    memcpy(&fd,(int*)CMSG_DATA(cmsg),n*sizeof(int));
    return fd;
}
int main(int argc,char **argv){

    int rfd,cfd,fd;
    struct sockaddr_un addr;

    rfd=socket(AF_UNIX,SOCK_STREAM,0);
    if(rfd<0){
        perror("[socket error]");
        return -1;
    }
    if(unlink("/home/jacky/01.socket")==-1 && errno !=ENOENT){
        perror("[Removing socket file failed]");
        return -1;
    }
    memset(&addr,0,sizeof(struct sockaddr_un));
    addr.sun_family=AF_UNIX;
    strncpy(addr.sun_path,"/home/jacky/Work/Machine_room_monitoring/1.socket",sizeof(addr.sun_path)-1);

    if(bind(rfd,(struct sockaddr*)&addr,sizeof(struct sockaddr_un))==-1){
        perror("[Failed to bind socket]");
        return -1;
    }
    if(listen(rfd,5)==-1){
        perror("[Failed to listen socket]");
        return -1;
    }
    printf("listening......\n");
    cfd=accept(rfd,NULL,NULL);
    if(cfd==-1){
        perror("[Failed to accept incoming connection]");
    }
    printf("cfd:%d\n",cfd);
    fd=recv_fd(cfd,1);
    if(fd<0){
        return -1;
    }

    Data *ptr=(Data *)mmap(NULL,sizeof(Data),PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
    if(ptr==NULL){
        perror("[mmap fd error]");
        return -1;
    }
    while (1){
        printf("%s\n",ptr->temperature_1);
        printf("%s\n",ptr->temperature_2);
        printf("%s\n",ptr->temperature_3);
        printf("%s\n",ptr->temperature_4);
        printf("%s\n",ptr->temperature_5);
        sleep(3);
    }
    close(fd);
    close(cfd);
    return 0;
}



Makefile

SOURCE :=get_data.c
OBJ :=get_data.o

TARGET :=recv

CC :=gcc
CROSS_COMPILE :=
INCLUDE := -I .
DEFINES :=
CFLAGS := -Wall -g $(INCLUDE) $(DEFINES)
LDFLAGS :=

all:$(TARGET)
$(TARGET):$(OBJ)
	$(CROSS_COMPILE)$(CC) $(LDFLAGS) -o $@ $^
%.o:%.c
	$(CROSS_COMPILE)$(CC) $(CFLAGS) -c -o $@ $^
.PHONY:clean
clean:
	rm -rf *.o $(TARGET)



验证

在这里插入图片描述


成功


不用关心1.socket文件,因为你将他放在/tmp目录下,系统会在关机后将他清理掉,下一次开机就可以正常运行程序了,只是这里我设置在当前目录是为了方便观察



版权声明:本文为m0_56145255原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。