【Linux】进程间通讯——System V共享内存

火影  金牌会员 | 2024-9-16 09:02:11 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 851|帖子 851|积分 2553

目录
一、概念和原理
二、API先容与利用
2.1 shmget
2.2 ftok
2.3 shmat
2.4 shmdt
2.5 shmctl
三、开始通讯


一、概念和原理

之前提到,进程间通讯的本质就是让两个不同的进程能够看到同一份资源。因为进程具有独立性,不同进程间无法直接的进行数据的通报,需要操作体系提供对应的资源。
前面学习的管道,就是操作体系提供的资源,两个进程打开同一个管道,就能够进行通讯。
除了管道,共享内存也是进程间通讯的一种方式,并且是最快的方式。其原理是将一段物理内存映射到两个进程的地址空间,这样两个进程就能够共享这段内存,并通过共享内存来进行数据通报,并且不再涉及内核。
进程间数据的通报不再涉及内核,因此进程在通报数据时就无需再实验对应的体系调用进入内核了,直接通过操作共享内存来进行通讯,效率大大进步。

(操作体系起首申请一块共享内存,然后通过两个进程的页表将共享内存挂接到进程地址空间上)
共享内存可用于进程间通讯,那么物理内存中肯定存在不止一块共享内存。既然共享内存由操作体系提供,那么我们可以猜想:操作体系一定要对这些共享内存进行管理。怎样管理?先形貌再组织
因此内核中也有效于形貌共享内存的结构体,操作体系通过对结构体的组织进行共享内存的管理
System V进程间通讯方式除了System V共享内存,还有System V消息队列System V 信号量,这三种IPC方式的API和实现上都非常相似,包罗内核中相似的结构体,和命名、利用方式相似的函数
System V消息队列和System V 信号量在后面的文章中会讲到,这里先贴出System V共享内存的内核结构体,后续各人可以将其与别的两种方式的内核结构体进行对比
  1. struct shmid_ds {
  2.         struct ipc_perm shm_perm; /* operation perms */
  3.         int shm_segsz; /* size of segment (bytes) */
  4.         __kernel_time_t shm_atime; /* last attach time */
  5.         __kernel_time_t shm_dtime; /* last detach time */
  6.         __kernel_time_t shm_ctime; /* last change time */
  7.         __kernel_ipc_pid_t shm_cpid; /* pid of creator */
  8.         __kernel_ipc_pid_t shm_lpid; /* pid of last operator */
  9.         unsigned short shm_nattch; /* no. of current attaches */
  10.         unsigned short shm_unused; /* compatibility */
  11.         void* shm_unused2; /* ditto - used by DIPC */
  12.         void* shm_unused3; /* unused */
  13. };
复制代码
此中ipc_perm结构体:
  1. struct ipc_perm {
  2.     key_t          __key;    /* Key supplied to shmget(2) */
  3.     uid_t          uid;      /* Effective UID of owner */
  4.     gid_t          gid;      /* Effective GID of owner */
  5.     uid_t          cuid;     /* Effective UID of creator */
  6.     gid_t          cgid;     /* Effective GID of creator */
  7.     unsigned short mode;     /* Permissions + SHM_DEST and
  8.                                        SHM_LOCKED flags */
  9.     unsigned short __seq;    /* Sequence number */
  10. };
复制代码


二、API先容与利用

2.1 shmget

  1. #include <sys/ipc.h>
  2. #include <sys/shm.h>
  3. int shmget(key_t key, size_t size, int shmflg);
复制代码
shmget函数用于创建共享内存,若创建成功会返回shmid,即共享内存标识符; 创建失败返回-1
此中:


  • key:范例为key_t,内核中标识共享内存的唯一标识符,需要用户生成
  • size:共享内存的大小
  • shmflg:权限位。此中IPC_CREAT表现假如申请的共享内存不存在就创建,存在就获取并返回;IPC_CREAT|IPC_EXCL表现假如申请的共享内存不存在就创建,存在就堕落并返回 
key是一个整型变量,在内核中具有唯一性,两个进程通过key来访问同一块共享内存。key用于在操作体系中标识一块共享内存的唯一性,而shmid用于用户层的活动,只在进程内标识该共享内存唯一性
用户要创建key,可以利用ftok函数

2.2 ftok

  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. key_t ftok(const char *pathname, int proj_id);
复制代码
ftok函数用于创建共享内存的key,创建成功返回key,失败返回-1
此中pathname和proj_id由用户自由指定,函数内通过特定的算法对参数进行盘算并生成对应的具有唯一性的key。而因为参数都由用户指定,以是用户可以通过约定对应的pathname和proj_id,实如今两个进程中获取到雷同的key,从而访问到同一块共享内存。
接下来我们实践一下
shared.hpp:
  1. //shared.hpp
  2. #include <iostream>
  3. #include <sys/types.h>
  4. #include <sys/shm.h>
  5. #include <sys/ipc.h>
  6. #include <string>
  7. using namespace std;
  8. //用户自己约定
  9. const string path = "/home/Eristic";
  10. const int proj_id = 0x1234;
  11. key_t GetKey()
  12. {
  13.     key_t key = ftok(path.c_str(), proj_id); //创建key
  14.     if(key < 0)
  15.     {
  16.         perror("ftok error");
  17.         exit(1);
  18.     }
  19.     printf("create key success, key = %d\n", key); //打印key的值
  20.     return key;
  21. }
  22. int GetSharedMem()
  23. {
  24.     key_t key = GetKey(); //获取key
  25.     int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL); //创建共享内存
  26.     if(shmid < 0)
  27.     {
  28.         perror("shmget error");
  29.         exit(1);
  30.     }
  31.     printf("create shared memory success, shmid = %d\n", shmid); //打印shmid的值
  32.     return shmid;
  33. }
复制代码
processa.cc:
  1. //processa.cc
  2. #include "shared.hpp"
  3. int main()
  4. {
  5.     int shmid = GetSharedMem();
  6.     return 0;
  7. }
复制代码
编译并运行步伐,结果如下:

可以看到我们的key和共享内存已经创建成功了,并且key和shmid是不同的值
再次运行步伐,结果变为了:

此时key创建成功,但共享内存创建失败。这是因为我们创建共享内存时权限位设置为IPC_CREAT|IPC_EXCL,假如共享内存存在就堕落并返回,说明进程退出后共享内存仍然存在
实际上,共享内存的生命周期是随内核的,也就是除非内核重启,共享内存会不停存在直到用户自动关闭 
要查察体系中所有的共享内存,我们可以输入 ipcs -m

要删除共享内存,我们可以输入 ipcrm -m + shmid  

此中perms是共享内存的权限位,nattch是与该共享内存关联的进程数,size是共享内存大小
关于共享内存的大小,发起设置为4096的整数倍,因为操作体系分配内存是按照4KB分配的,纵然用户将共享内存的大小设置为4097,实际大小是4096*2(固然查察共享内存时照旧显示4097)
要设置共享内存的权限,我们可以在shmget函数的shmflg参数中加上,例如:
  1. int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
复制代码
删除原来的共享内存,重新编译并运行步伐后,再次查察体系中的共享内存:

可以看到随着我们对shmget函数的修改,perms变为了666
并且会发现每次创建共享内存,key是保持不变的,但shmid会改变

2.3 shmat

  1. #include <sys/types.h>
  2. #include <sys/shm.h>
  3. void *shmat(int shmid, const void *shmaddr, int shmflg);
复制代码
shmat函数用于将对应的共享内存连接到进程的地址空间,成功会返回一个地址,是共享内存映射的起始地址,失败则返回-1
此中:


  • shmaddr:可指定共享内存映射的位置,假如设置为nullptr则由体系自动决定
  • shmflg:设置为SHM_REMAP,假如共享内存待映射的地址已经有了映射,则重新映射,否则报错;设置为SHM_RDONLY代表以只读方式与共享内存建立关联;若设置为0则按照共享内存的默认权限
同样是得到一块内存空间,同样是返回值为void*,让你想到了什么?
是不是很像malloc,申请一段空间,具体的用途由用户决定,并按照用途来进行范例转换
实践一下:
  1. //processa.cc
  2. #include "shared.hpp"
  3. int main()
  4. {
  5.     int shmid = GetSharedMem();
  6.     char *shmaddr = (char *)shmat(shmid, nullptr, 0);
  7.     sleep(10); //便于在程序退出前观测现象
  8.     return 0;
  9. }
复制代码
编译并运行代码,在步伐未退出时观测共享内存的nattch:

可以看到此时进程已经与共享内存关联

2.4 shmdt

  1. #include <sys/types.h>
  2. #include <sys/shm.h>
  3. int shmdt(const void *shmaddr);
复制代码
shmdt函数用于去除进程与共享内存的关联,成功返回0,失败返回-1
我们只需要将关联共享内存时返回的地址传入shmdt函数即可
我们继续在步伐中加入shmdt函数,并观察现象:
  1. //processa.cc
  2. #include "shared.hpp"
  3. int main()
  4. {
  5.     int shmid = GetSharedMem();
  6.     char *shmaddr = (char *)shmat(shmid, nullptr, 0);
  7.     printf("shmat done\n");
  8.     sleep(5);
  9.     shmdt(shmaddr);
  10.     printf("shmdt done\n");
  11.     sleep(5);
  12.     return 0;
  13. }
复制代码
可以看到shmat函数实验后,共享内存的nattch变为1,shmdt去关联后又变为0


2.5 shmctl

  1. #include <sys/ipc.h>
  2. #include <sys/shm.h>
  3. int shmctl(int shmid, int cmd, struct shmid_ds *buf);
复制代码
shmctl函数用于控制共享内存,在大部分cmd操作下成功返回0,失败返回-1
此中:


  • cmd:需要做的操作。IPC_STAT为将共享内存的结构体信息拷贝出来;IPC_SET可修改共享内存的uid、gid及mode;IPC_RMID将共享内存的引用计数减少,若引用计数为0则共享内存被删除
  • buf:范例为shmid_ds,即共享内存的内核结构体。IPC_RMID操作中可设置为nullptr
我们继续加入shmctl函数,在去关联后直接删除共享内存,看看现象怎样:
  1. //processa.cc
  2. #include "shared.hpp"
  3. int main()
  4. {
  5.     int shmid = GetSharedMem();
  6.     char *shmaddr = (char *)shmat(shmid, nullptr, 0);
  7.     printf("shmat done\n");
  8.     sleep(5);
  9.     shmdt(shmaddr);
  10.     printf("shmdt done\n");
  11.     sleep(5);
  12.     shmctl(shmid, IPC_RMID, nullptr);
  13.     printf("shmctl done\n");
  14.     sleep(5);
  15.     return 0;
  16. }
复制代码

可以看到,由于我们选择了IPC_RMID操作,shmctl函数实验完毕后共享内存被删除


三、开始通讯

到目前位置,我们已经开端了解怎样创建并关联共享内存了,接下来我们来简朴实现两个进程之间的通讯
processa.cc:
  1. #include "shared.hpp"
  2. int main()
  3. {
  4.     int shmid = CreateSharedMem();
  5.     char *shmaddr = (char *)shmat(shmid, nullptr, 0);
  6.     while(true)
  7.     {
  8.         cout << "process b say:" << shmaddr << endl;
  9.         sleep(1);
  10.     }
  11.     shmdt(shmaddr);
  12.     shmctl(shmid, IPC_RMID, nullptr);
  13.     return 0;
  14. }
复制代码
processb.cc:
  1. #include "shared.hpp"
  2. int main()
  3. {
  4.     int shmid = GetSharedMem();
  5.     char *shmaddr = (char *)shmat(shmid, nullptr, 0);
  6.     while(true)
  7.     {
  8.         cout << "Please enter# ";
  9.         fgets(shmaddr, 4096, stdin);
  10.     }
  11.     shmdt(shmaddr);
  12.     return 0;
  13. }
复制代码
因为创建和删除共享内存的工作在processa.cc中已经完成了,以是processb.cc中只需要获取共享内存即可,无需再进行创建和删除
shared.hpp
  1. #include <iostream>
  2. #include <sys/types.h>
  3. #include <sys/shm.h>
  4. #include <sys/ipc.h>
  5. #include <string>
  6. #include <unistd.h>
  7. using namespace std;
  8. //用户自己约定
  9. const string path = "/home/Eristic";
  10. const int proj_id = 0x1234;
  11. key_t GetKey()
  12. {
  13.     key_t key = ftok(path.c_str(), proj_id); //创建key
  14.     if(key < 0)
  15.     {
  16.         perror("ftok error");
  17.         exit(1);
  18.     }
  19.     printf("create key success, key = %d\n", key); //打印key的值
  20.     return key;
  21. }
  22. int SharedMem(int flag)
  23. {
  24.     key_t key = GetKey(); //获取key
  25.     int shmid = shmget(key, 4096, flag);
  26.     if(shmid < 0)
  27.     {
  28.         perror("shmget error");
  29.         exit(1);
  30.     }
  31.     printf("create shared memory success, shmid = %d\n", shmid); //打印shmid的值
  32.     return shmid;
  33. }
  34. int CreateSharedMem() //创建全新的共享内存
  35. {
  36.     return SharedMem(IPC_CREAT | IPC_EXCL | 0666);
  37. }
  38. int GetSharedMem() //获取已有的共享内存
  39. {
  40.     return SharedMem(IPC_CREAT);
  41. }
复制代码
编译并运行:

需要留意,共享内存是没有同步互斥之类的保护机制的,也就是说假如多个进程同时对共享内存进行读写操作可能导致辩论发生
因此,假如要实现对数据的保护,可以利用锁或者信号量等机制,或者与管道共同团结利用来让共享内存实现同步与互斥
完.

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

火影

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表