继前三篇分析了进程间通信机制管道, 命名管道(FIFO), 消息队列后, 本文将介绍最后一种进程间通信机制, 也是进程间通信机制效率最高的一种 - 共享内存
1 共享内存
考虑前三种进程间通信机制, 一个客户 - 服务器文件复制程序将设计到一下步骤:
(1)服务器从输入文件读取该文件的数据由内核读入自己的内存空间, 然后从内核复制到服务器进程
(2)服务器往管道 FIFO 和消息队列以一条消息的形式写入这些数据这些 IPC 形式需要把进程中的数据复制到内核
(3)客户端从这些 IPC 通道中读取数据, 需要把内核数据复制到进程的地址空间
(4)客户端将进程空间内的数据再写入内核, 由内核写入文件
这些 IPC 形式的问题在于, 共享数据需要多次经过内核
共享内存的方式是让同一块物理内存被映射到进程 AB 各自的进程地址空间进程 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!\n");
- 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!\n");
- 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!\n");
- exit(-1);
- }
- else{
- printf("%c\n",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!\n");
- 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\n",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\n", 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\n", 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\n",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\n",ptr1, ptr2);
- *ptr1 = 128;
- waitpid(0, NULL, 0);
- }
- else{
- }
- return 0;
- }
代码运行结果如下:
可见, 在不同的进程中, 相同的共享内存区对象起始地址可以不一样
来源: https://www.cnblogs.com/alvin2010/p/8654758.html