> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。
> 目标:理解进程通信——system V(共享内存 | 消息队列 | 信号量)
> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!
> 专栏选自:Linux初阶
> 望小伙伴们点赞👍收藏✨加关注哟💕💕
🌟前言
system V:同一主机内的进程间通信方案,在OS层面专门为进程间通信设计的方案
进程间通信的本质:让不同的进程看到同一份资源
system V标准下的三种通信方式
- 共享内存
- 消息队列
- 信号量
我们来看看 system V标准下的三种通信方式 。
⭐主体
学习【Linux学习】进程间通信——system V(共享内存 | 消息队列 | 信号量)咱们按照下面的图解:
🌙 共享内存
💫 共享内存的基本原理
概念:
共享内存:通过让不同的进程,看到通过一个内存块的方式就叫共享内存。
进程具有独立性:
内核数据结构包括对应的代码、数据与页表都是独立的。OS系统为了让进程间进行通信:
1.申请一块空间
2.将创建好的内存映射进进程的地址空间。
- 共享内存让不同的进程看到同一份的资源就是在物理内存上申请一块内存空间,如何将创建好的内存分别与各个进程的页表之间建立映射,然后在虚拟地址空间中将虚拟地址填充到各自页表的对应位置,建立起物理地址与虚拟地址的联系。
3.如果不想通信:取消进程和内存的映射关系,释放内存
- 而我们把创建好的内存称为共享内存,把进程和共享内存建立映射关系的操作称为挂接,把取消进程和内存的映射关系称为去关联
- 把释放内存称为释放共享内存。
- 共享内存的建立:在物理内存当中申请共享内存空间;将申请到的共享内存挂接到地址空间,即建立映射关系。
- 共享内存的释放:共享内存与地址空间去关联,即取消映射关系;释放共享内存空间,即将物理内存归还给系统。
对于共享内存的理解:
对比以前C语言的malloc也可以在物理内存申请空间,并把开辟好的空间经过页表映射到进程地址空间当中。
但是system V进程间通信是专门设计的,用来IPC;共享内存是一种通信方式,所有想通信的进程都可以进行使用;OS一定可能会同时存在很多的共享内存。
💫 共享内存的创建
shmget:用来创建共享内存
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); RETURN VALUE On success, a valid shared memory identifier is returned. On errir, -1 is returned, and errno is set to indicate the error.
参数讲解:
- shmflg:通常被设置成两个选项: IPC_CREAT、 IPC_EXCL
IPC_CREAT:共享内存不存在,则创建,如果存在则获取;IPC_EXCL:无法单独使用,IPC_CREAT|IPC_EXCL:如果不存在就创建,如果存在就出错返回
- size:共享内存的大小
- key:保证看到同一份共享内存,能进行唯一性标识。如何去形成key:ftok
ftok:形成key
ftok作用:是通过存在的路径名pathname以及设置的标识符proj_id来形成一个key值,通过shmget创建共享内存时,key值会被填充维护共享内存的数据结构当中。
理解key:
OS一定会存在很多的共享内存,共享内存本质就是在内存中申请一块空间,而key能进行唯一标识。OS申请的,自然要做管理,共享内存也是如此,如何管理:先描述,在组织。所以共享内存=物理内存块+共享内存的相关属性。进程如果在内存中创建了共享内存,为了让共享内存在系统中保证唯一的,通过key来进行标识,只要让另一个进程也看到同一个key。而key是在哪?key作为创建共享内存时共享内存的相关属性集合,描述共享内存时就有一个字段struct shm中有key。
共享内存数据结构的第一个成员是shm_perm,shm_perm是一个ipc_perm类型的结构体变量,每个共享内存的key值存储在shm_perm这个结构体变量当中,其中ipc_perm结构体的定义如下:
struct ipc_perm { key_t __key; /* Key supplied to shmget(2) */ uid_t uid; /* Effective UID of owner */ gid_t gid; /* Effective GID of owner */ uid_t cuid; /* Effective UID of creator */ gid_t cgid; /* Effective GID of creator */ unsigned short mode; /* Permissions + SHM_DEST and SHM_LOCKED flags */ unsigned short __seq; /* Sequence number */ };
💫 共享内存的控制
shmctl
:控制共享内存
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数讲解:
- shmid:共享内存id
- cmd:控制方式,这里我们只使用IPC_RMID 选项,表示删除共享内存
- buf:描述共享内存的数据结构
返回值:0表示成功,-1表示失败
共享内存的内核结构shmid_ds:
//man shmctl struct shmid_ds { struct ipc_perm shm_perm; /* Ownership and permissions */ size_t shm_segsz; /* Size of segment (bytes) */ time_t shm_atime; /* Last attach time */ time_t shm_dtime; /* Last detach time */ time_t shm_ctime; /* Last change time */ pid_t shm_cpid; /* PID of creator */ pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */ shmatt_t shm_nattch; /* No. of current attaches */ ... };
💫 共享内存的关联
shmat:
关联共享内存
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
参数讲解:
- shmid:共享内存id
- shmaddr:挂接地址(自己不知道地址,所以默认为NULL)
- shmflg:挂接方式,默认为0
返回值:挂接成功返回共享内存起始地址(虚拟地址),类似C语言malloc
💫 共享内存的去关联
shmdt
:去关联
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr); RETURN VALUE On success shmdt() returns 0; on error -1 is returned, and errno is set to indi‐cate the cause of the error.
参数讲解:
- shmaddr:去关联内存地址,即shmat返回值
- 返回值:调用成功返回0,失败返回-1
💫 查看IPC资源
概念:
对于管道:进程退出,文件描述符就会自动释放,但是对于共享内存不一样:共享内存的生命周期是随OS的,而不是随进程的,这是所有System V进程间通信的共性。
查看共享内存:ipcs -m
删除:ipcsrm -m + shmid
💫 代码实现通信
1.comm.hpp:
#ifndef __COMM_HPP_ #define __COMM_HPP_ #include <iostream> #include <sys/ipc.h> #include <sys/shm.h> #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; #define PATHNAME "." #define PROJ_JD 0x66 #define MAX_SIZE 4096 key_t getkey() { key_t k = ftok(PATHNAME,PROJ_JD); if(k <0) { cerr<<errno<<":"<<strerror(errno)<<endl; exit(1); } return k; } int getShmHelper(key_t k,int flags) { //k是要shmget,设置进入共享内存属性中的,用来标识 //该共享难内存在内核中的唯一性 //shmid与key: //fd inode int shmid = shmget(k,MAX_SIZE,flags); if(shmid<0) { cerr<<errno<<":"<<strerror(errno)<<endl; exit(2); } return shmid; } //获取 int getShm(key_t k) { return getShmHelper(k,IPC_CREAT); } //创建 int createShm(key_t k) { return getShmHelper(k,IPC_CREAT | IPC_EXCL|0600); } void delShm(int shmid) { if(shmctl(shmid,IPC_RMID,nullptr)==-1) { cerr<<errno<<":"<<strerror(errno)<<endl; } } void * attachShm(int shmid) { void*mem = shmat(shmid,nullptr,0); if((long long)mem==-1L)//64位系统,8个字节,L表示数字类型 { cerr<<errno<<"shmat:"<<strerror(errno)<<endl; exit(3); } return mem; } void detachShm(void * start) { if(shmdt(start)==-1) { cerr<<"shmdt:"<<errno<<":"<<strerror(errno)<<endl; } } #endif
2.server.cc:
#include "comm.hpp" #include <unistd.h> using namespace std; int main() { key_t k = getkey(); printf("key:%0x%x\n",k); int shmid = createShm(k); printf("shmid:%d\n",shmid); //sleep(5); char*start = (char*)attachShm(shmid); printf("attach success,address start:%p\n",start); //使用 while(true) { printf("client say:%s\n",start); struct shmid_ds ds; shmctl(shmid,IPC_STAT,&ds); printf("获取属性:size:%d,pid:%d,myself:%d",ds.shm_segsz,ds.shm_cpid); sleep(1); } //去关联 detachShm(start); sleep(10); //删除共享内存 delShm(shmid); return 0; }
3.client.cc:
#include "comm.hpp" #include <unistd.h> using namespace std; int main() { key_t k = getkey(); printf("key:%0x%x\n",k); int shmid = getShm(k); printf("shmid:%d\n",shmid); char*start = (char*)attachShm(shmid); printf("attach success,address start:%p\n",start); const char*message = "hello server,我是另一个进程,正在和你通信"; pid_t id = getpid(); int count = 1; //char buffer[1024]; while(true) { sleep(5); snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,count++); // snprintf(buffer,sizeof(buffer),"%s[pid:%d][消息编号:%d]",message,id,count++); // memcpy(start,buffer,strlen(buffer)+1); } detachShm(start); return 0; }
【Linux】进程间通信——system V(共享内存 | 消息队列 | 信号量)(下) https://developer.aliyun.com/article/1565753