继前三篇分析了进程间通信机制管道,命名管道(FIFO),消息队列后,本文将介绍最后一种进程间通信机制,也是进程间通信机制效率最高的一种-共享内存
1、共享内存
考虑前三种进程间通信机制,一个客户-服务器文件复制程序将设计到一下步骤:
(1)服务器从输入文件读取。该文件的数据由内核读入自己的内存空间,然后从内核复制到服务器进程。
(2)服务器往管道、FIFO和消息队列以一条消息的形式写入这些数据。这些IPC形式需要把进程中的数据复制到内核。
(3)客户端从这些IPC通道中读取数据,需要把内核数据复制到进程的地址空间。
(4)客户端将进程空间内的数据再写入内核,由内核写入文件。
这些IPC形式的问题在于,共享数据需要多次经过内核。
共享内存的方式是让同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制(Linux同步的机制系列文章)。同样是上面的例子,共享内存只需经过如下 流程:
这种形式的通信数据只需复制两次:一次从输入文件到共享内存,另一次是从共享内存到输出文件。
2、共享内存基本操作
2.1 打开/创建一个共享内存对象
#include <sys/mman.h>
int shm_open(const char* name, int oflag, mode_t mode);
成功返回非负描述符,若失败返回-1
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> int main(int argc, char* argv[]) { int flag = O_RDWR | O_CREAT | O_EXCL; int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; int fd = 0; fd = shm_open("/shm_test", flag, mode); if (-1 == fd) { printf("open shm failed! "); return 1; } ftruncate(fd, 1024); mmap(NULL,1024,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); return 0; }
2.2 删除一个共享对象的名字
#include <sys/mman.h>
int shm_unlink(const char* name);
成功返回0,失败返回-1
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> int main(int argc, char* argv[]) { shm_unlink("/shm_test"); return 0; }
2.3 ftruncate 和 fstat
处理mmap的时候,普通文件或共享内存对象的大小可以通过ftruncate函数修改:
#include <unistd.h>
int ftruncate(int fd, off_t length);
成功返回0,出错返回-1
当打开一个已存在的共享内存区对象时,我们可调用fstat来获取有关该对象的信息:
#include <sys/types.h>
#include <sys/stat.h>
int fstat(int fd, struct stat* stat);
成功返回0,失败返回-1
对于普通文件stat结构可以获得12个以上的成员信息,然而当fd指代一个共享内存区对象时,只有四个成员含有信息。
struct stat{
...
mode_t st_mode;
uid_t st_uid;
gid_t st_gid;
off_t st_size;
};
2.4 共享内存的读写
read端代码如下:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> int main(int argc, char* argv[]) { int flag = O_RDONLY; int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; int fd = 0; unsigned char* ptr, c; fd = shm_open("/shm_test", flag, mode); if (-1 == fd) { printf("open shm failed! "); return 1; } struct stat stat; fstat(fd,&stat); ptr = mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd, 0); close(fd); for (int i = 0; i < stat.st_size; i++) { if((c = *ptr++) != (i%256)) { printf("read error! "); exit(-1); } else{ printf("%c ",c); } } return 0; }
write端代码如下:
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> int main(int argc, char* argv[]) { int flag = O_RDWR; int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; int fd = 0; unsigned char* ptr, c; fd = shm_open("/shm_test", flag, mode); if (-1 == fd) { printf("open shm failed! "); return 1; } struct stat stat; fstat(fd,&stat); ptr = mmap(NULL, stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); for (int i = 0; i < stat.st_size; i++) { *ptr++ = i % 256; printf("write %d ",i); } return 0; }
这里与mqueue不同的是,read端读取的时候并不会阻塞等待,如果没有提前write内容到共享内存里,read读取到的内容为空。
3、共享内存的起始地址
同一共享内存区对象映射到不同进程的地址空间,起始地址可以不一样。
#include <unistd.h> #include <sys/types.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/mman.h> #include <stdio.h> #include <sys/wait.h> #include <errno.h> #include <string.h> int main(int argc, char* argv[]) { int fd1,fd2; unsigned char *ptr1, *ptr2; pid_t pid; int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; fd1 = shm_open("/shm_test",O_RDWR,mode); if (fd1 == -1) { printf("Message : %s ", strerror(errno)); return 1; } struct stat stat; fstat(fd1, &stat); fd2 = open("/dev/shm/shm_test1",O_RDWR | O_CREAT,mode); if (fd2 == -1) { printf("Message : %s ", strerror(errno)); return 1; } pid = fork(); if (pid == 0) { ptr2 = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd2,0); ptr1 = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, 0); printf("child: shm ptr=%p, mmf ptr=%p ",ptr1, ptr2); sleep(5); printf("share memory integer:%d", *ptr1); } else if (pid > 0) { ptr1 = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, 0); ptr2 = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd2,0); printf("parent: shm ptr=%p, mmf ptr=%p ",ptr1, ptr2); *ptr1 = 128; waitpid(0, NULL, 0); } else{ } return 0; }
代码运行结果如下:
可见,在不同的进程中,相同的共享内存区对象起始地址可以不一样。