第二十八章 共享内存介绍
共享内存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
共享内存示意图
管道、消息队列与共享内存传递数据对比
mmap函数
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
功能:
将文件或者设备空间映射到共享内存区;分配地址空间时,是以页面为单位的,即使len小于一个页面,也会分配一个页面
参数:
addr : 要映射的起始地址,通常指定为NULL,让内核自动选择
len : 映射到进程地址空间的字节数
prot :映射区保护方式
PROT_READ : 页面可读
PROT_WRITE :页面可写
PROT_EXEC : 页面可执行
PROT_NONE : 页面不可访问
flags :标志
MAP_SHARED : 变动是共享的
MAP_PRIVATE : 变动是私有的
MAP_FIXED : 准确解释addr参数
MAP_ANONYMOUS : 建立匿名映射区(具有亲缘关系的进程之间通信),不涉及文件
fd : 文件描述符
offset:从文件头开始的偏移量
决定了要映射的内容 : fd + offset
决定了要映射的区域 : fd + offset + len
返回值:
成功 : 返回映射到的内存区的起始地址
失败 : MAP_FAILED
munmap函数
int munmap(void *addr, size_t length);
功能:
取消mmap函数建立的映射
参数:
addr : 映射的内存起始地址
len : 映射到进程地址空间的字节数
返回值:
成功 : 0
失败 : -1
mmap_write.c
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
typedef struct str
{
char name[4];
int age;
}STU;
int main(int argc, char* argv[])
{
if(argc != 2)
{
fprintf(stderr, "Usage : %s <file>\n", argv[0]);
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_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED)
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("init over!\n");
munmap(p, sizeof(STU)*5);
printf("exit\n");
return 0;
}
mmap_read.c
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
typedef struct str
{
char name[4];
int age;
}STU;
int main(int argc, char* argv[])
{
if(argc != 2)
{
fprintf(stderr, "Usage : %s <file>\n", argv[0]);
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_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED)
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;
}
msync函数
int msync(void *addr, size_t length, int flags);
功能:
对映射的共享内存执行同步操作
参数:
addr : 内存起始地址
length : 长度
flags : 选项
MS_ASYNC : 执行异步写(高速内核需要将高速缓冲的数据立刻写到磁盘,然后立刻返回,这时内核可能还没执行成功)
MS_SYNC : 执行同步写(高速内核需要将高速缓冲的数据立刻写到磁盘,等待内核执行完了再返回)
MS_INVALIDATE : 使高速缓存的数据失效
返回值:
成功 : 0
失败 : -1
mmap注意点
- 映射不能改变文件的大小
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
typedef struct str
{
char name[4];
int age;
}STU;
int main(int argc, char* argv[])
{
if(argc != 2)
{
fprintf(stderr, "Usage : %s <file>\n", argv[0]);
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_READ|PROT_WRITE, MAP_SHARED, fd, 0);
p = (STU *)mmap(NULL, sizeof(STU)*10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED)
ERR_EXIT("mmap");
char ch = 'a';
int i;
//for(i=0; i<5; i++)
for(i=0; i<10; i++)
{
memcpy((p+i)->name, &ch, 1);
(p+i)->age = 20 + i;
ch++;
}
printf("init over!\n");
sleep(10);
munmap(p, sizeof(STU)*5);
printf("exit\n");
return 0;
}
结果
ls -l 2
-rw-rw-r-- 1 dw dw 40 9月 24 19:03 2
./mmap_write 2
ls -l 2
-rw-rw-r-- 1 dw dw 40 9月 24 19:07 2
- 可用于进程间通信的有效地址空间不完全受限于被映射文件的大小
- 虽然文件的大小是40字节,但是映射的字节是80字节,mmap_write延迟调用munmap,mmap_read依然可以读到80个字节
- 原因是因为映射的共享内存区域大于文件的大小,对于进程间通信,可以使用的地址空间可以大于4G的文件大小空间,因为映射的时候是基于页面的,基于页面来分配的;我们映射40个字节,可能分配了4k的地址空间,只要在这4K的地址空间访问就不会出错;如果超过4K的地址空间,可能会产生一个SIGBUS信号,如果访问的地址空间超过了更多的4K空间,可能会出现其他错误,段错误SIGSEGV,这取决于超过地址空间的大小
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
typedef struct str
{
char name[4];
int age;
}STU;
int main(int argc, char* argv[])
{
if(argc != 2)
{
fprintf(stderr, "Usage : %s <file>\n", argv[0]);
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_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED)
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;
}
结果
mmap_write 进程sleep的时候
./mmap_write 2
name : a age = 20
name : b age = 21
name : c age = 22
name : d age = 23
name : e age = 24
name : f age = 25
name : g age = 26
name : h age = 27
name : i age = 28
name : j age = 29
mmap_write进程退出后,共享内存空间不存在了,只能从文件中读取
name : a age = 20
name : b age = 21
name : c age = 22
name : d age = 23
name : e age = 24
name : age = 0
name : age = 0
name : age = 0
name : age = 0
name : age = 0
- 文件一旦被映射后,所有对映射区域的访问实际上是对内存区域的访问。映射区域内容写回文件时,所写内容不能超过文件的大小。