1.共享内存
共享内存是最快的IPC形式(inter-process communication),一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不在涉及到内核,即进程不再通过执行进入内核的系统调用来传递彼此的数据
两个进程的PCB各自维护着一个进程地址空间。当两个进程要进行通信时:
①.操作系统在内存中开辟一个内存块。
②.通过两个进程的页表,将内存中的内存块映射到两个进程的进程地址空间中。
③.此时两个进程便建立了连接。
④.进行通信时,两个进程只需要访问自己的进程地址空间即可,操作系统会通过页表访问内存中的内存块。
共享内存就是让不同的进程,看到同一块内存块。
挂接:**将内存中创建好的内存块映射到进程的地址空间中。** 去关联:不想通信时,取消进程和内存的映射关系。
2.共享内存函数
shmget函数
功能:用来创建共享内存
int shmget(key_t key,size_t size,int shmflg)
key:这个共享内存段名字
size:这个共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式是一样的
返回值:
创建成功返回共享内存的标识符,注意不是key值。
创建失败,返回-1。
①.关于key
管理共享内存并不是在管理内存块本身,而是在管理共享内存对应的结构体:
key值就是共享内存的标识,让想要通信的进程双方看到同一块公共资源。
系统中既然存在很多个共享内存,操作系统势必要将它们管理起来,管理也是使用先描述再组织的方式。
struct shm
{
key_t key;
size_t size;
//…
}
每创建一个共享内存,就会创建一个结构体对象,并且赋一个不同的key值。
所以这个key值就代表着一块唯一的共享内存。
如何让key值保持唯一?
函数ftok()
这就需要用到另一个函数ftok(),来生成一个独一无二的key值。
key_t ftok(const charpathname,int proj_id)
pathname:文件路径名,可以随意写,一般我们都写成当前路径"."。
proj_id:项目ID,同样可以自定义,但是不能为0。
返回值:独一无二的key值。
该函数会根据我们传的路径名和项目id值生成一个key值,具体实现是通过一些算法实现的,我们不需要在意,只需要得到key值就行。
所以,在开辟共享内存之前,必须先使用ftok函数来生成一个独一无二的key值,这样才能保证我们内存块的标识是唯一的。
当两个进程通过key值和共享内存挂接起来后,就可以进行通信了。
结论:key值就是用来标识共享内存的唯一性的。
②.size
size是用来指定开辟的共享内存是多大的,以字节为单位。
一般指定的大小是4KB的整数倍。也可以是任意值。
操作系统在开辟共享内存的时候是以4KB为单位的。每次开辟的共享内存,最小也是4KB的。
假设我们指定4097字节大小的共享内存,但是在内存中实际开辟的共享内存是24KB的。
但是在使用的时候只能使用4097字节的空间,剩下的空间用户无法使用,操作系统也不会用,就浪费掉了。
③.shmflg
常用的shmflg
IPC_CREAT:创建共享内存,如果不存在,创建新的,如果存在,获取相关信息。
IPC_EXCL:无法单独使用,必须与其他标志组合使用。
IPC_CREAT|IPC_EXCL:创建共享内存,如果不存在,则创建,如果存在,错误返回
④.返回值
shmid也是连续的小整数,它和文件描述符fd一样,也是让用户使用的。
用户层使用shmid而不是key值是为了让用户层和系统层解耦。
结论:shmid是供用户使用的共享内存标识符。
shmget系统调用就是用来让通信双方获取同一块共享内存的。
key:不要在应用层使用,只用来在内核中标识shm的唯一性 类似于fd
shmid:应用这个
shmctl函数
ipcs -m 是一个UNIX/Linux系统下的命令,用于显示共享内存段的信息。具体来说,它会列出系统中当前存在的共享内存段,并显示它们的标识符、键值、权限、大小等信息。
使用 ipcs -m 命令可以方便地查看系统中共享内存段的状态,包括它们的使用情况、占用的内存大小等。这对于诊断共享内存相关的问题或监视系统资源使用情况非常有用。
此时创建的5个共享内存就显示出来了。
此时,进程早已经结束了,但是共享内存还是存在,没有随进程的结束而消失。
共享内存的生命周期随内核,不随进程。
指令:ipcrm -m shimd 功能:删除指定shimd标识的共享内存。
此时所有的共享内存就被删除了,但是需要我们手动的一个个去删除。
由于指令也是shell上运行的进程,也是属于用户层,所以操作共享内存时,使用的时shmid,而不是key值。
用命令行的形式未免也太麻烦了,所以有系统调用shmctl也可以用来删除共享内存,而且是自动的。
考虑使用shmctl函数
功能:用于控制共享内存
int shmctl(int shmid,int cmd,struct shmid_ds*buf)
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作
IPC_STAT
上图所示就是共享内存的数据结构struct_shmid_ds,也就是用来描述共享内存的数据结构,这只是其中的一部分
该结构体的第一个成员变量是struct ipc_prem shm_prem,具体内容如红色箭头所指。可以看到,key值就在这里,共享内存就是通过它来标识唯一性的
当cmd是IPC_STAT时,我们就可以获取到共享内存的属性。
IPC_STAT
IPC_RMID 删除共享内存段
IPC_SET
在进程有足够权限的前提下,把共享内存的当前值设置为shmid_ds数据结构中给出的值
buf:指向一个保存着共享内存模式的模式状态和访问权限的数据结构
shmat函数
功能:将共享内存段挂接到进程地址空间中
voidshmat(int shmid,const voidshmaddr,int shmflg)
shmid:创建共享内存后返回的标识符
shmaddr:指定共享内存映射到进程地址空间中的地址,一般设置成NULL,让系统自动来设置
shmflg:它的两个可能取值是SHM_RND和SHM_RONLY
返回值:成功返回一个指针,指向共享内存的第一个节,失败返回-1
key_t key=ftok(NAME_PATH,PROJ_ID); //创建共享内存
int shmid=shmget(key,MEM_SIZE,IPC_CREAT|IPC_EXCL);
char*start=(char*)shmat(shmid,nullptr,0); //挂接共享内存
shmdt函数
功能:将共享内存段与当前进程脱离
int shmdt(const void*shmaddr)
shmaddr:由shmat所返回的指针
要去关联的共享内存映射在进程地址空间中的起始地址。
shmget-创建共享内存
shmat-将共享内存段挂接到进程地址空间中
shmdt-将共享内存段与当前进程脱离
shmctl-用于控制共享内存
#pragma once
#include <iostream>
#include <cstdlib>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string pathname = "/home/whb/109/109/lesson30";
const int proj_id = 0x11223344;
const std::string filename = "fifo";
// 共享内存的大小,强烈建议设置成为n*4096
const int size = 4096; // 4096*2
key_t GetKey()
{
key_t key = ftok(pathname.c_str(), proj_id);
if(key < 0)
{
std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
exit(1);
}
return key;
}
std::string ToHex(int id)
{
char buffer[1024];
snprintf(buffer, sizeof(buffer), "0x%x", id);
return buffer;
}
int CreateShmHelper(key_t key, int flag)
{
int shmid = shmget(key, size, flag);
if(shmid < 0)
{
std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
exit(2);
}
return shmid;
}
int CreateShm(key_t key)
{
return CreateShmHelper(key, IPC_CREAT|IPC_EXCL|0644);
}
int GetShm(key_t key)
{
return CreateShmHelper(key, IPC_CREAT/*0也可以*/);
}
bool MakeFifo()
{
int n = mkfifo(filename.c_str(), 0666);
if(n < 0)
{
std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
return false;
}
std::cout << "mkfifo success... read" << std::endl;
return true;
}
client.cc
int main()
{
key_t key = GetKey();
int shmid = GetShm(key);
char *s = (char*)shmat(shmid, nullptr, 0);
std::cout << "attach shm done" << std::endl;
int fd = open(filename.c_str(), O_WRONLY);
// sleep(10);
// TODO
// 共享内存的通信方式,不会提供同步机制, 共享内存是直接裸露给所有的使用者的,一定要注意共享内存的使用安全问题
//
char c = 'a';
for(; c <= 'z'; c++)
{
s[c-'a'] = c;
std::cout << "write : " << c << " done" << std::endl;
sleep(1);
// 通知对方
int code = 1;
write(fd, &code, sizeof(4));
}
shmdt(s);
std::cout << "detach shm done" << std::endl;
close(fd);
return 0;
}
serve.cc
int main(){
bool r=MakeFifo();
if(!r) return 1;
key_t key=Getkey();
int shmid=CreateShm(key);
char*s=(char*)shmat(shmid,nullptr,0);
int fd=open(filename.c_str(),O_RDONLY);
while(true){
int code=0;
ssize_t n=read(fd,&code,sizeof(code));
if(n>0){
std::cout<<n<<std::endl;
sleep(1);
}
else if(s==0){
break;
}
}
shmdt(s);
shmctl(shmid,IPC_RMID,nullptr);
}
3.共享内存的特征:
**共享内存的生命周期随内核
共享内存是所有进程间通信速度最快的的方式
共享内存可以提供较大的空间
共享内存的通信方式,不会提供同步机制,共享内存是直接裸露给所有的使用者的,一定要注意内存的使用安全问题
**