目录
一、System V通信
二、共享内存
代码板块
总结
一、System V通信
System V IPC(inter-process communication),是一种进程间通信方式。其实现的方法有共享内存、消息队列、信号量这三种机制。
本文着重介绍共享内存这种方式。
二、共享内存
进程间通信的本质就是让不同进程看到同一份资源,比如匿名管道是利用了父子进程之间继承机制,命名管道是利用路径找到同一个文件。
共享内存本质也是让不同进程看到同一份资源。
学习进程的通信方法,本质都是在学习是如何让不同进程使用到同一份资源的。
- 共享内存机制
第一步,由操作系统在内存中开辟内存空间。
第二步:操作系统把共享内存的地址通过页表映射到进程地址空间中。
另一个进程同样如此。
进程通过这块内存通信,通信完毕后,还有移除映射、释放共享内存等步骤。
- 共享内存的细节
实际上,会有多个进程都采用共享内存的方式通信,因此会有多个被开辟的小内存空间,用来通信,操作系统同样要对这些个共享内存作管理,同样要标识唯一的共享内存,这个关键字在Linux下的类型命名为key_t。
于是,我们要理解共享内存的本质,就是要理解,两个进程究竟是通过怎样的方式拿到同一个key_t的。
先来认识一下,Linux下创建共享内存的系统调用接口。
man 2 shmget
第一个参数,key_t key ,这个参数就是用来标识唯一的共享内存空间的,由用户传入,并不是由操作系统指定。
原因:当一个进程想要和另一个进程通信,A进程是无法得知B进程的一切信息的,它只能自己申请创建一块共享内存空间,然后想办法让B进程也能访问到这个特定的内存空间,但是在操作系统看来,它管理这多个这样的内存空间,操作系统是不知道A和B是想要通信的,也就无法将这个唯一标识传给B进程。
因此这个参数key只能由用户设置。
但是,直接设置这样一个标识符可能会大概率和其他共享内存空间的标识符冲突,因此有专门的一个函数,可以根据用户设置的字符串来生成唯一且随机的一个标识符。
man 3 ftok
想要通信的两个进程,到时候在源代码中约定一样的字符串,根据这个函数生成共享内存空间的唯一标识符,由其中一个进程向操作系统申请创建这块内存空间,至此,两个进程都能拿到同一块共享内存空间了,这便是System V共享内存的实质。
第二个参数,指定共享内存空间的大小
第三个参数,用来指定特殊标志,传参选项有IPC_CREAT、IPC_EXCL,
传参形式有三种:
只传IPC_CREAT:如果要创建的共享内存空间不存在,就创建它,如果已经存在,则直接使用它。
只传IPC_EXCL:无意义。
传参IPC_CREAT | IPC_EXCL: 如果要创建的共享内存空间不存在,就创建它,如果已经存在,则报错!!
代码板块
- 1.先实现获取key
//可以随机定义
const char* pathname = "/home/utocoo/Desktop/linux/241221";
const int pri_id = 0x67;
//获取key
key_t CreatKeyOrDie()
{key_t key = ftok(pathname,pri_id);if(key < 0){cerr << "ftok error,errno->" << errno<<"->" << strerror(errno) << endl;exit(1);}return key;
}
- 2.再根据key申请共享内存
//根据Key申请共享内存
int CreatShmOrDie(key_t key,size_t size,int flag)
{int shmid = shmget(key,size,flag);if(shmid < 0){cerr << "shmget error,errno->" << errno << "->" << strerror(errno) << endl;exit(2);}return shmid;
}
然而,在通信的用户看来,创建共享内存时,只需要指定key和内存大小即可,所有可以把这个函数再次封装。
并且对于要创建内存的进程而言,比如A进程,创建内存时,如果不存在就创建,如果存在则报错;而对B进程而言,如果内存已经存在,只需要获取它的shmid即可。
//A进程创建
int CreatShm(key_t key,size_t size)
{CreatShmOrDie(key,size,IPC_CREAT | IPC_EXCL | 0666);
}
//B进程不用再创建,只需要获取即可
int GetShm(key_t key,size_t size)
{CreatShmOrDie(key,size,IPC_CREAT);
}
再来认识一下创建共享内存空间函数shmget的返回值,int shmid,这个返回值同样可以标识唯一的一块内存空间,它和另一个标识符key又有什么样的关联?
key这个关键字,是在内核级别,帮助操作系统标识唯一的共享内存空间。
而shmget的返回值,是帮助用户来管理这块内存空间,作为用户来讲,往往是通过shmid来使用各种各样的接口。
- 释放共享内存空间
进程退出后,如果用户不主动释放申请的共享内存,那么这块内存的生命周期是一直跟随着操作系统的,只有重启才会重新初始化这块内存。
释放共享内存的接口为shmctl+特定参数
man 2 shmctl
第三个参数的类型就是操作系统用来描述共享内存的结构体,cmd的取值范围在man手册中也有说明。
//释放共享内存
void DeleteShm(int shmid)
{int r = shmctl(shmid,IPC_RMID,nullptr);if(r < 0){cerr << "delete shm error,errno->" << errno << "->" << strerror(errno) << endl;exit(3);}else {cout << "delete shm->" << shmid << "success" << endl;}
}
关于释放共享内存,上面介绍的是在代码中我们利用系统调用接口实现,也可以在命令行中通过指令完成释放。
ipcs指令查看SystemV通信的所有介质。
ipcs
ipcs -m则只查看所有的共享内存。
ipcs -m
ipcrm -m 指定的shmid则在命令行中删除指定shmid的共享内存。
ipcrm -m shmid
- 将内存空间挂载或者说映射到进程的虚拟地址空间中
所用到的接口是shmat,
第二个参数,shmaddr用来指定要将指定shmid的共享内存映射到虚拟地址空间的哪个地方,第三个参数shmflag默认传0即可,特殊用途可以传特殊参数,这些在man手册中均有说明。
需要说明的是返回值
shmat会返回虚拟地址空间段地址,否则返回(void*)-1
//映射到进程上面
void* ShmAttah(int shmid)
{char* addr = (char*)shmat(shmid,nullptr,0);if((int64_t)addr == -1){cerr << "ShmAttach error,errno->" << errno << "->" << strerror(errno) << endl;return nullptr;}return addr;
}
- 既然有挂载,也就应该有取消挂载
相应的接口为shmdt,dt是detach的缩写,
总结
共享内存的特征:由于共享内存,当写进程不再向内存中写数据的时候,读进程还是会一直从内存中读数据,共享内存并不提供进程间通信的同步机制,这一点不同于管道通信,这是它的缺点。
然而,正因为共享内存的缘故,当写进程向内存中写完数据后,读进程可以立马从内存中读取到数据,这个过程又不同于管道通信,因为管道通信是不断的拷贝,因此,共享内存的通信方式却是最快的通信方式,这是它的优点。
因为共享内存块注定这种通信方法无法提供通信同步机制,因此,可以在共享内存通信的基础上,提供一个管道,利用管道来同步、利用共享内存来通信。