学习完SystemV消息队列之后,接着来学习总结共享内存的相关知识,主要包括以下内容:共享内存的概念、示意图,管道、消息队列与共享内存传递数据对比,以及mmap等函数。
1.共享内存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
2共享内存示意图
(1)共享内存区是整块内存中特殊的一块区域。
(2)这块区域可以映射到不同的进程的地址空间中,如图进程A和B。
3.管道、消息队列与共享内存传递数据对比
(1)用管道或者消息队列传递数据
其中,涉及了四次系统调用,四次内核拷贝,效率相对较慢。
(2)用共享内存传递数据
使用共享内存,我们可以将共享内存区映射到服务器和客户端的地址空间中,然后通过共享内存区来传输数据。
对服务器来说,调用read,将数据拷贝到共享内存区。
对客户端来说,调用write,从共享内存区读取数据即可。
以上只涉及两次系统调用。
4.mmap函数
- 功能:将文件或设备空间映射到共享内存区
- 原型:
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); - 参数:
- addr:要映射的起始地址,通常指定为NULL,让内核自动选着
- len:映射到进程地址空间的字节数
- prot:映射区保护方式
- flags:标志
- fd:文件描述符
- offset:从文件头开始的偏移量
- 返回值:成功返回映射到的内存区的起始地址;失败返回-1
(1)prot参数有如下选择:
PROT_READ:页面可读
PROT_WRITE:页面可写
PROT_EXEC:页面可执行
PROT_NONE:页面不可访问
(2)flags参数有如下选择:
MAP_SHARED:变动是共享的
MAP_PRIVATE:变动是私有的
MAP_FIXED:准确解释addr参数
MAP_ANONYMOUS:建立匿名映射区,不涉及文件
(3)内存映射文件示意图
1)在创建内存映射区的时候,是以页面为单位来分配的,如果len小于一个页面的大小,实际上创建的共享内存区大小会大于len。至少是一个页面。
5.munmap函数
- 功能:取消mmap函数建立的映射
- 原型:
int munmap(void *addr, size_t len); - 参数:
- addr:映射的内存起始地址
- len:映射到进程地址空间的字节数
- 返回值:成功返回0;失败返回-1
6.示例
使用mmap和munmap函数实现一个简单的示例
mmap_write.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) typedef struct stu { char name[4]; int age; }STU; int main(int argc, char *argv[]) { if(argc != 2){ fprintf(stderr, "Usage:%s <file>\n", argv[1]); exit(EXIT_FAILURE); } int fd; fd = open(argv[1], O_CREAT | O_RDWR | O_TRUNC, 0666); if (fd == -1) ERR_EXIT("open"); lseek(fd , sizeof(STU)*5-1, SEEK_SET); write(fd, "", 1); STU *p; p =(STU*) mmap(NULL, sizeof(STU)*5, PROT_WRITE, MAP_SHARED, fd, 0); if (p ==NULL) ERR_EXIT("mmap"); char ch = 'a'; int i; for(i=0; i<5;i++){ memcpy((p+i)->name, &ch, 1); (p+i)->age = 20+i; ch++; } printf("initialize over\n"); munmap (p, sizeof(STU)*5); printf("exit...\n"); return 0; } |
编译后,指定一个文件进行映射:
1 2 3 |
[root@VM_198_209_centos 28_mmap]# ./mmap_write test initialize over exit... |
此时,已将文件映射到共享内存区,并且写入了一些数据,这里可以使用od -c命令查看:
1 2 3 4 5 |
[root@VM_198_209_centos 28_mmap]# od -c test 0000000 a \0 \0 \0 024 \0 \0 \0 b \0 \0 \0 025 \0 \0 \0 0000020 c \0 \0 \0 026 \0 \0 \0 d \0 \0 \0 027 \0 \0 \0 0000040 e \0 \0 \0 030 \0 \0 \0 0000050 |
接着编写mmap_read函数从共享内存区读取数据:
mmap_read.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) typedef struct stu { char name[4]; int age; }STU; int main(int argc, char *argv[]) { if(argc != 2){ fprintf(stderr, "Usage:%s <file>\n", argv[1]); exit(EXIT_FAILURE); } int fd; fd = open(argv[1], O_RDWR); if (fd == -1) ERR_EXIT("open"); STU *p; p =(STU*) mmap(NULL, sizeof(STU)*5, PROT_WRITE, MAP_SHARED, fd, 0); if (p ==NULL) ERR_EXIT("mmap"); int i; for(i=0; i<5;i++){ printf("name = %s age = %d\n", (p+i)->name, (p+i)->age); } munmap (p, sizeof(STU)*5); printf("exit...\n"); return 0; } |
编译运行:
1 2 3 4 5 6 7 |
[root@VM_198_209_centos 28_mmap]# ./mmap_read test name = a age = 20 name = b age = 21 name = c age = 22 name = d age = 23 name = e age = 24 exit... |
7.msync函数
该函数了解一下即可
- 功能:对映射的共享内存执行同步操作
- 原型:
int msync(void *addr, size_t len, int flags) - 参数:
- addr:内存起始地址
- len:长度
- flags:选项
- 返回值:成功返回0;失败返回-1
8.map注意点
(1)映射不能改变文件的大小
(2)可用于进程间通信的有效地址空间不完全受限于被映射文件的大小
(3)文件一旦被映射后,所有对映射区域的访问实际上是对内存区域的访问。映射区域内容写回文件时,所写内容不能超过文件的大小。