前面我们讲解了进程间通信之管道,这段我们讲解我们的共享内存
共享内存是所有进程间通信方式最快的一种。内存共享模型就像下面的图一样,就是将物理内存映射到我们进程的虚拟地址上,我们就可以直接操作我们虚拟地址空间就直接将我们的物理地址上的内容进行更改。
为什么它时最快的:因为共享内存是将一块物理内存映射到我们虚拟地址空间中,通过虚拟地址空间就可以将我们的物理内存进行修改,相对于管道来说,管道是在内核中开辟了一块物理,在我们操作的时候是将我们的数据从用户态到内核态进行拷贝,我们取出的时候也是从内核到用户态进行拷贝。共享内存就少了两次用户态到内核态的拷贝。所以共享内存是所有进程间通信是最快的。
shm系列
步骤:
- 创建共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key:内存标识符(可以使用一个随机32位数字或者使用
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
得到一个内存表示符。
size:缓冲区的大小
shmflg:操作选项(shm-mode,IPC_CREAT,IPC_EXCL);
成功返回一个标识符(是一个操作句柄),失败返回-1
- 将共享内存映射到我们的虚拟地址空间
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:创建共享内存返回的标识符
shmaddr:映射首地址(NULL表示由操作系统去分配)
shmflg:映射到内存中的权限(SHM_RDONLY:只读权限,0:可读写权限)
成功:返回映射首地址 失败:返回-1
- 直接对这块内存进行操作
- 解除共享内存关系
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
shmaddr:映射返回的首地址
- 删除共享内存首地址
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:操作句柄,第一步返回的标识符
cmd:操作类型(IPC_RMID(删除),IPC_SET)
buf:设置或者获得共享信息(一般设置NULL)
例子:
向内存中写入数据
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
int main(){
char* filename = "pipe.fifo";
int ret = mkfifo(filename,664);
if(ret == -1){
if(errno != EEXIST){
perror("mkfifo error\n");
return -1;
}
}
//创建fifo成功
int fd = open(filename,O_WRONLY);
if(fd == -1){
perror("open error\n");
return -1;
}
printf("打开文件成功\n");
while(1){
char buf[1024];
scanf("%s",buf);
write(fd,buf,strlen(buf));
}
close(fd);
return 0;
}
向内存中读取数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
int main(){
char * filename = "./pipe.fifo";
int ret = mkfifo(filename,664);
if(ret == -1){
if(errno != EEXIST){
perror("mkfifo error\n");
return -1;
}
}
printf("打开fifo文件成功\n");
int fd = open(filename,O_RDONLY);
if(fd == -1){
perror("read error\n");
return -1;
}
while(1){
sleep(1);
char* buf[1024];
ret = read(fd,buf,sizeof(buf));
if(ret == 0){
printf("写入端已经关闭\n");
exit(0);
}
if(ret == -1){
perror("read error\n");
return -1;
}
printf("buf:[%s\n]",buf);
}
close(fd);
return 0;
}
mmap共享内存
mmap函数
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);
返回:成功:返回创建的映射区首地址;失败:MAP_FAILED宏
参数:
addr: 建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
length: 欲创建映射区的大小
prot: 映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags: 标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
MAP_SHARED: 会将映射区所做的操作反映到物理设备(磁盘)上。
MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。
fd: 用来建立映射区的文件描述符
offset: 映射文件的偏移(4k的整数倍)
munmap函数
同malloc函数申请内存空间类似的,mmap建立的映射区在使用结束后也应调用类似free的函数来释放。
int munmap(void *addr, size_t length); 成功:0; 失败:-1
借鉴malloc和free函数原型,尝试装自定义函数smalloc,sfree来完成映射区的建立和释放。思考函数接口该如何设计?
匿名映射
通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。 可以直接使用匿名映射来代替。其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。
使用MAP_ANONYMOUS (或MAP_ANON), 如:
int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
int main()
{
int fd = open("test.txt",O_RDWR);
if(fd == -1)
{
perror("open err:");
exit(1);
}
char *p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(p == MAP_FAILED)
{
perror("mamp err:");
exit(1);
}
strcpy(p,"1111");
close(fd);
munmap(p,4);
return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)