概述
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据(如图)。
共享内存 VS. 其他IPC形式
用管道/消息队列传递数据
用共享内存传递数据
共享内存生成之后,传递数据并不需要再走Linux内核,共享内存允许两个或多个进程共享一个给定的存储区域,数据并不需要在多个进程之间进行复制,因此,共享内存的传输速度更快!
mmap内存映射
将文件/设备空间映射到共享内存区
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length);
参数:
addr: 要映射的起始地址, 通常指定为NULL, 让内核自动选择;
length: 映射到进程地址空间的字节数;
prot: 映射区保护方式(见下);
flags: 标志(见下);
fd: 文件描述符;
offset: 从文件头开始的偏移量;
prot | 说明 |
PROT_READ | 页面可读 |
PROT_WRITE | 页面可写 |
PROC_EXEC | 页面可执行 |
PROC_NONE | 页面不可访问 |
flags | 说明 |
MAP_SHARED | 变动是共享的 |
MAP_PRIVATE | 变动是私有的 |
MAP_FIXED | 准确解释addr参数, 如果不指定该参数, 则会以4K大小的内存进行对齐 |
MAP_ANONYMOUS | 建立匿名映射区, 不涉及文件 |
mmap返回值:
成功: 返回映射到的内存区的起始地址;
失败: 返回MAP_FAILED;
内存映射示意图:
(注意: 内存映射时, 是以页面(4K)作为单位)
/** 示例1: 写文件映射 将文件以可读,可写的方式映射到进程的地址空间, 然后向其中写入内容 **/ struct Student { char name[4]; int age; }; int main(int argc,char **argv) { if (argc != 2) err_quit("usage: ./main <file-name>"); int fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 0666); if (fd == -1) err_exit("file open error"); //为内存映射争取空间 if (lseek(fd, sizeof(Student)*5-1, SEEK_SET) == (off_t)-1) err_exit("lseek error"); write(fd, "", 1); Student *p = (Student *)mmap(NULL, sizeof(Student)*5, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) err_exit("mmap error"); // 此时:操纵文件就可以如同操纵内存一样了 char ch = 'a'; for (int i = 0; i < 5; ++i) { memcpy((p+i)->name, &ch, 1); (p+i)->age = 20+i; ++ ch; } cout << "file initialized!" << endl; if (munmap(p, sizeof(Student)*5) == -1) err_exit("munmap error"); cout << "process exit..." << endl; return 0; }
/**示例2: 读文件映射 **/ int main(int argc,char **argv) { if (argc != 2) err_quit("usage: ./main <file-name>"); //以只读方式打开 int fd = open(argv[1], O_RDONLY); if (fd == -1) err_exit("file open error"); void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); Student *p = (Student *)mmap(NULL, sizeof(Student)*5, PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) err_exit("mmap error"); // 从内存中读取数据(其实是从文件中读取) for (int i = 0; i < 5; ++i) { cout << "name: " << (p+i)->name << ", age: " << (p+i)->age << endl; } if (munmap(p, sizeof(Student)*5) == -1) err_exit("munmap error"); cout << "process exit..." << endl; return 0; }
map注意点:
1. 内存映射不能(也不可能)改变文件的大小;
2. 可用于进程间通信的有效地址空间不完全受限于映射文件的大小, 而应该以内存页面的大小为准(见下面测试);
3. 文件一旦被映射之后, 所有对映射区域的访问实际上是对内存区域的访问; 映射区域内容写会文件时, 所写内容不能超过文件的大小.
/** 测试: 注意点2 文件以40K的内容进行创建, 而以120K的内容进行写回 **/ //程序1: 写文件映射 int main(int argc,char **argv) { if (argc != 2) err_quit("usage: ./main <file-name>"); int fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 0666); if (fd == -1) err_exit("file open error"); // 注意: 此处我们的文件其实只有40个字节 if (lseek(fd, sizeof(Student)*5-1, SEEK_SET) == (off_t)-1) err_exit("lseek error"); write(fd, "", 1); // 但是我们却是以120个字节的方式进行映射 Student *p = (Student *)mmap(NULL, sizeof(Student)*15, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) err_exit("mmap error"); // 以120个字节的方式进行写入 char ch = 'a'; for (int i = 0; i < 15; ++i) { memcpy((p+i)->name, &ch, 1); (p+i)->age = 20+i; ++ ch; } cout << "file initialized!" << endl; // 以120字节的方式卸载该内存区 if (munmap(p, sizeof(Student)*15) == -1) err_exit("munmap error"); // 注意: 要故意暂停一会儿, 以便让read程序读取该共享内存的内容 sleep(20); cout << "process exit..." << endl; }
//程序2: 读文件映射 int main(int argc,char **argv) { if (argc != 2) err_quit("usage: ./main <file-name>"); //以只读方式打开 int fd = open(argv[1], O_RDONLY); if (fd == -1) err_exit("file open error"); void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // 以120字节的方式映射 Student *p = (Student *)mmap(NULL, sizeof(Student)*15, PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) err_exit("mmap error"); // 以120字节的方式读取 for (int i = 0; i < 15; ++i) { cout << "name: " << (p+i)->name << ", age: " << (p+i)->age << endl; } if (munmap(p, sizeof(Student)*15) == -1) err_exit("munmap error"); cout << "process exit..." << endl; }
msync函数
int msync(void *addr, size_t length, int flags);
对映射的共享内存执行同步操作
参数:
addr: 内存起始地址;
length: 长度
flags: 选项
flags | 说明 |
MS_ASYNC | 执行异步写 |
MS_SYNC | 执行同步写, 直到内核将数据真正写入磁盘之后才返回 |
MS_INVALIDATE | 使高速缓存的数据失效 |
返回值:
成功: 返回0;
失败: 返回-1;