内存映射
共享内存可以通过mmap()映射普通文件, 使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write。
mmap()的优点:
实现了用户空间和内核空间的高效交互方式
mmap函数定义:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
功能:
创建共享内存映射
函数返回值:
成功返回创建的映射区首地址,失败返回MAP_FAILED( ((void *) -1) ),设置errno值
参数:
addr:指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。
length:必须>0。映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。
prot:指定共享内存的访问权限。可取如下几个值的可选:PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行), PROT_NONE(不可访问)。
flags:由以下几个常值指定:MAP_SHARED(共享的) MAP_PRIVATE(私有的), MAP_FIXED(表示必须使用 start 参数作为开始地址,如果失败不进行修正),其中,MAP_SHARED , MAP_PRIVATE必选其一,而 MAP_FIXED 则不推荐使用。MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)
fd:表示要映射的文件句柄。如果匿名映射写-1。
offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。
mmap()映射的种类:
1基于文件的映射
2 匿名映射
适用于具有亲缘关系的进程之间,
释放内存映射
munmap函数
int munmap(void *addr, size_t length);
返回值:成功返回0,失败返回-1,并设置errno值。
函数参数:
addr:调用mmap函数成功返回的映射区首地址
length:映射区大小(即:mmap函数的第二个参数)
mmap函数实例
mmap_w.c
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
void *addr;
int fd;
fd =open("test",O_RDWR);
if(fd<0){
perror("open");
return 0;
}
int len = lseek(fd,0,SEEK_END);
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd);
int i=0;
while(i<2048){
memcpy((addr+i),"a",1);
i++;
sleep(1);
}
// printf("read=%s\n",(char*)(addr));
}
mmap_r.c
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
void *addr;
int fd;
fd =open("test",O_RDWR);
if(fd<0){
perror("open");
return 0;
}
int len = lseek(fd,0,SEEK_END);
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd);
// memcpy((addr),"99999999999999",15);
while(1){
printf("read=%s\n",(char*)(addr));
sleep(1);
}
}
mmap_n.c匿名映射
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(){
void *addr;
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
pid_t pid;
pid = fork();
if(pid<0){
perror("fork");
return 0;
}
else if(pid>0){
memcpy(addr,"1234567890",10);
wait(NULL);
}else {
sleep(1);
printf("read father val=%s\n",(char *)addr);
}
munmap(addr,2048);
}
注意事项:
(1) 创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。
(2) 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护),如果不满足报非法参数(Invalid argument)错误。
当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。
(3) 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。
(4) 用于映射的文件大小必须>0,当映射文件大小为0时,指定非0大小创建映射区,访问映射地址会报总线错误,指定0大小创建映射区,报非法参数错误(Invalid argument)
(5) 文件偏移量必须为0或者4K的整数倍(不是会报非法参数Invalid argument错误).
(6)映射大小可以大于文件大小,但只能访问文件page的内存地址,否则报总线错误 ,超出映射的内存大小报段错误
(7)mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
共享内存(System V)
System V IPC
IPC 对象包含:
共享内存、消息队列和信号灯集
每个IPC对象有唯一的ID 用Key关联
IPC对象创建后一直存在,直到被显式地删除
ipcs / ipcrm
共享内存
- 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
- 共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
- 由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用
system V 共享内存使用步骤:
1创建/打开共享内存
2映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
3 读写共享内存
4 撤销共享内存映射
5 删除共享内存对象
IPC 的key
System V IPC – ftok
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *path, int proj_id);
- 成功时返回合法的key值,失败时返回EOF
- path 存在且可访问的文件的路径
- proj_id 用于生成key的数字,范围1-255。
System V IPC - ftok – 示例
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
shmid = shmget(key,512,IPC_CREAT|0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
strcpy(buf,"hello world");
}
共享内存创建 – shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int shmflg);
- 成功时返回共享内存的id,失败时返回EOF
- key 和共享内存关联的key,IPC_PRIVATE 或 ftok生成
- shmflg 共享内存标志位 IPC_CREAT|0666
共享内存映射 – shmat
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 成功时返回映射后的地址,失败时返回(void *)-1
- shmid 要映射的共享内存id
- shmaddr 映射后的地址, NULL表示由系统自动映射
- shmflg 标志位 0表示可读写;SHM_RDONLY表示只读
共享内存撤销映射 – shmdt
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void *shmaddr);
- 成功时返回0,失败时返回EOF
- 不使用共享内存时应撤销映射
- 进程结束时自动撤销
共享内存控制 – shmctl
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 成功时返回0,失败时返回EOF
- shmid 要操作的共享内存的id
- cmd 要执行的操作 IPC_STAT IPC_SET IPC_RMID
- buf 保存或设置共享内存属性的地址
共享内存创建 – 示例
shm_w.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
shmid = shmget(key,512,IPC_CREAT|0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
strcpy(buf,"hello world");
}
shm_r.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
shmid = shmget(key,512,0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
// strcpy(buf,"hello world");
printf("share mem=%s\n",buf);
while(1){
sleep(1);
}
shmdt(buf);
shmctl(shmid, IPC_RMID, NULL);
// printf("detach mem=%s\n",buf);
}
共享内存 - 注意事项
每块共享内存大小有限制
ipcs -l
cat /proc/sys/kernel/shmmax
共享内存删除的时间点
添加删除标记
nattach 变成0时真正删除
查看共享内存命令ipcs