共享内存区是可用IPC形式中最快的。一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传递就不在涉及内核。
“不再涉及内核”含义:进程不再通过执行任何进入内核的系统调用来彼此传递数据。
管道、FIFO和消息队列这些IPC形式,当两个进程要交换信息时,这些信息必须经由内核传递。通过让两个或多个进程共享一个内存区,共享内存区就绕过内核,提高了速度。
mmap函数可以把一个文件或者一个POSIX共享内存区对象映射到调用进程的地址空间。
#include
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
//返回值:若成功,返回起始地址,若出错,返回MAP_FAILED
prot | 说明 |
---|---|
PROT_EXEC | 数据可被执行 |
PROT_READ | 数据可读 |
PROT_WRITE | 数据可写 |
PROT_NONE | 数据不可访问 |
- flags 必须指定MAP_SHARED或MAP_PRIVATE中的一个,并可有选择的或上MAP_FIXED,从可移植性考虑,不应该指定。
flags | 说明 |
---|---|
MAP_SHARED | 变动是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化 |
MAP_PRIVATE | 变动是私自的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去 |
MAP_FIXED | 准确地解释addr参数 |
- mmap成功返回后,fd参数可以关闭,该操作对于mmap建立的映射关系没有影响。
###2.2 munmap
删除某个进程的地址空间的映射关系,调用munmap函数
#include
int munmap(void *addr, size_t length);
//返回值:若成功返回0,若出错,返回-1
当flags参数为MAP_SHARED时,如果修改了处于内存映射到某个文件的内存区中某个位置,那么内核将在稍后某个时刻相应地更新文件。调用msync函数使磁盘上的文件和内存映射区中的内容一致。
#include
int msync(void *addr, size_t length, int flags);
//返回值:若成功返回0,若出错返回-1
常值 | 说明 |
---|---|
MS_ASYNC | 执行异步写(立即返回) |
MS_SYNC | 执行同步写(等待写操作完成后返回) |
MS_INVALIDATE | 使高速缓存的数据失效 |
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd;
int len;
char *ptr;
if(argc < 1) {
printf("./a.out filename\n");
exit(1);
}
fd = open(argv[1], O_RDWR);
if(fd < 0){
perror("open err");
exit(1);
}
len = lseek(fd, 0, SEEK_END);
ptr = mmap(NULL, len, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED){
perror("mmap err");
exit(1);
}
close(fd);
ptr[0] = 'a';
ptr[1] = 'b';
ptr[2] = 'c';
munmap(ptr, len);
return 0;
}
运行结果:
➜ IPC cat hello //hello文件内存
helloworld
➜ IPC ./a.out hello
➜ IPC cat hello //内存映射更改后的文件
abcloworld
Linux采用的是页式管理机制,对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。
#include "unpipc.h"
int main(int argc, char *argv[])
{
int fd, i;
char *ptr;
size_t filesize, mmapsize, pagesize;
if(argc != 4)
sys_err("./a.out filename filesize mmapsize\n");
filesize = atoi(argv[2]); //映射的文件大小
mmapsize = atoi(argv[3]); //映射内存的大小
fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE);
lseek(fd, filesize-1, SEEK_SET);
write(fd, "", 1);
ptr = mmap(NULL, mmapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
pagesize = sysconf(_SC_PAGESIZE);
printf("pagesize = %ld\n", (long)pagesize); //打印当前页大小
for(i = 0; i < max(filesize, mmapsize); i += pagesize) {
printf("ptr[%d] = %d\n", i , ptr[i]);
ptr[i] = 1;
printf("ptr[%ld] = %d\n", i + pagesize -1, ptr[i + pagesize - 1]);
ptr[i + pagesize -1] = 1;
}
for(int j = 0; j < 10; j++) {
printf("index = %d\n", j);
printf("ptr[%ld] = %d\n", i+pagesize*j-1 , ptr[i+pagesize*j-1]);
}
return 0;
}
运行:
➜ mmap ./test foo 5000 5000
pagesize = 4096
ptr[0] = 0
ptr[4095] = 0
ptr[4096] = 0
ptr[8191] = 0
index = 0
ptr[8191] = 1
index = 1
ptr[12287] = 0
index = 2
ptr[16383] = 0
index = 3
ptr[20479] = 0
index = 4
ptr[24575] = 0
index = 5
ptr[28671] = 0
index = 6
[1] 4261 segmentation fault (core dumped) ./test foo 5000 5000
➜ mmap ls -l foo
-rw-r--r-- 1 menwen menwen 5000 3月 3 09:23 foo
➜ mmap od -b -A d foo //-b 选项指定以八进制输出个字节,-A d 选项指定以十进制输出地址
0000000 001 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
0000016 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
*
0004080 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 001
0004096 001 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
0004112 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
*
0004992 000 000 000 000 000 000 000 000
0005000
由运行结果可知:
接下来我们试图将指定更大的内存映射区:
➜ mmap ./test foo 5000 8193
pagesize = 4096
ptr[0] = 0
ptr[4095] = 0
ptr[4096] = 0
ptr[8191] = 0
[1] 6000 bus error (core dumped) ./test foo 5000 8193
因此,超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程,如下图所示:
注:内存真正映射地址空间的大小可能和运行环境有关系,以上运行环境 64位 ubuntu 16.04LTS ,内存12个G。