在前两篇文章中为大家讲述了,无名管道和有名管道的通信,本文为大家讲述共享内存的通信方式。
共享内存通过在进程当中直接修改内核区域来实现两个没有亲缘关系的不同进程间的信息的通信交互,内核创建共享内存与进程的信息存在一一映射的关系,像“镜子”一样可以实现通信内容信息的交互。
由于在内核当中直接修改信息,没有经过管道之类的外在媒质传输,共享内存也是最快的一种进程间通信的方式,下文将为大家详细介绍共享内存的创建以及通信的方式。
框架:获取key值 ==》申请对象 ==》映射对象==》读写对象 ==》撤销映射 ==》删除对象
1.获取key值 ftok
key_t ftok(const char *pathname, int proj_id);
- 功能:通过该函数可以将pathname指定的路径用来以proj_id生成唯一的临时键值。(证明光共享哪段内存)
- 参数:
- pathname 路径 ,任意路径,只要不会被删除即可。但是读写端路径指向必须一致
- proj_id 整形的数字,一般用ASCII码的单字符
- 返回值:
- 成功 返回唯一键值(key)
- 失败 -1
2.申请对象 shmget
int shmget(key_t key, size_t size, int shmflg);
- 功能: 创建共享内存
- 参数:
- key:键值
- size:共享内存大小(大小为向上扩展为页大小的整数倍(4096的整数倍)最多不超过500M)
- shmflg:(shmflg后面要|权限(0666))
- IPC_CREAT 创建
- IPC_EXCL 检测是否存在
- 返回值:
- 成功返回共享内存ID(shmid)
- 失败返回-1
3.映射对象shmget
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 功能:将指定shmid对应的共享内存映射到本地内存。(创建本地内存映射到共享内存当中)
- 参数:
- shmid 要映射的共享内存的ID (在内核中)
- shmaddr 本地可用的地址,如果不确定则用NULL,表示由系统自动分配。(在本地中)
- shmflg
- 0或(!SHM_RDONLY),表示读写
- SHM_RDONLY,只读
- 返回值:
- 成功 返回映射的地址,一般等于shmaddr
- 失败 (void*)-1
4.读写对象
读写对象没有特定函数,具体实现方式是把shmget创建出来的映射对象的指针指向我们想写的内容上或者是想读下来的载体上,通常使用strcpy和memcpy来实现以上的读写对象的方式
5.解除映射shmdt
int shmdt(const void *shmaddr);
- 功能:将本地内存与共享内存断开映射关系。
- 参数:shmaddr 要断开的映射地址。
- 返回值:
- 成功 0
- 失败 -1
在读写的操作完成之后,我们要进行解除映射的操作,把本地内存和内核内存上的映射关系解除,通过shmdt函数的操作,可以实现把本地创建的ptr内存和共享内存的映射关系解除
6.删除对象shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 功能:修改共享内存属性,也可以删除指定的共享内存对象。
- 参数:
- shmid 要删除的共享内存对象
- cmd
- IPC_STAT -----获得信息
- IPC_SET-----设置权限
- IPC_RMID-----删除
- buff:NULL 表示只删除对象。
- 返回值:
- 成功 0
- 失败 -1
代码示例:
//读端口 int main(int argc, const char *argv[]) { key_t key = ftok("./", '!'); //创建key if(-1 == key) { perror("fail to ftok\n"); return -1; } int shmid = shmget(key, 4096, IPC_CREAT|0666); //获取shmid if(-1 == shmid) { perror("fail to shmget\n"); return -1; } void *ptr = shmat(shmid, NULL, (!SHM_RDONLY)); //创建本地的映射的内存 if(NULL == ptr) { perror("fail to shmat"); return -1; } char buff[1024] = {0}; //读操作 strcpy(buff, ptr); printf("buff = %s\n", buff); shmdt(ptr); //关闭映射 shmctl(shmid, IPC_RMID, NULL); //删除对象 return 0; }
//写端操作 int main(int argc, const char *argv[]) { key_t key = ftok("./", '!'); if(-1 == key) { perror("fail to ftok"); return -1; } int shmid = shmget(key, 4096, IPC_CREAT|0664); if(-1 == shmid) { perror("fail to shmget"); return -1; } void *ptr = shmat(shmid, NULL, !SHM_RDONLY); if((void *)-1 == ptr) { perror("fail to shmat"); return -1; } strcpy(ptr, "hi"); shmdt(ptr); return 0; }
以上代码实现了从一个进程往本地共享内存写入hi的操作,hi字符串映射到了内核的共享内存,而读端口的本地共享内存可以看到hi的字符串,从而打印出来
代码需要先运行写段,先在共享内存中写入,再读取
区分:管道和共享内存
管道属于复制剪切操作感觉,一端口读取内容,一端口写内容,读写操作后,内容不在管道中。
共享内存则是复制粘贴的操作方式,两边读写共享内存的内容后,内容仍然在共享内存当中,只有通过映射内存ptr的修改才可以修改共享内存中的内容,并且两边均可进行读写修改共享内存,主要看具体应用而定。
上文就是进程间的通信方式共享内存的讲解,通过以上方式大家可以大致的了解到共享内存的操作方式以及信息是如何通过共享内存进行交互的,在上面半双工的方法之后大家可以结合进程以及信号等等操作考虑实现全双工的方法进行共享内存的方式的通信,这样可以把之前学习的知识融会贯通,如果有任何问题可以评论区讨论,感谢大家支持!