前言
写这边博客的目的,在于对宋宝华老师的无线崇拜!在阅读了他的
世界上最好的共享内存
博客以后,正如原文所说,发现了一个尤物,不得已眼前一亮,让人把持不住。废话少说,直接上干货
跨进程分享文件描述符
以下文字均来自于宋宝华老师的共享内存博客
某年某月的某一天,人们发现,一个进程其实想访问另外一个进程的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目录下,系统会在关机后将他清理掉,下一次开机就可以正常运行程序了,只是这里我设置在当前目录是为了方便观察