目录
一、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的缩写,
总结
共享内存的特征:由于共享内存,当写进程不再向内存中写数据的时候,读进程还是会一直从内存中读数据,共享内存并不提供进程间通信的同步机制,这一点不同于管道通信,这是它的缺点。
然而,正因为共享内存的缘故,当写进程向内存中写完数据后,读进程可以立马从内存中读取到数据,这个过程又不同于管道通信,因为管道通信是不断的拷贝,因此,共享内存的通信方式却是最快的通信方式,这是它的优点。
因为共享内存块注定这种通信方法无法提供通信同步机制,因此,可以在共享内存通信的基础上,提供一个管道,利用管道来同步、利用共享内存来通信。
三、信号量
信号量理论
- 同步和互斥。
互斥机制:像共享内存这样的通信方式中,写进程在写的过程中,如果读进程可以随时随地的读,最终可能造成数据不一致的情况,因此,为了避免这种数据不一致的情况,引入互斥机制,让一份资源只能由一个进程在享用,另一个进程想要享用,必须要排队等待。
同步机制:引入互斥机制后,个别进程可能长时间享用一份资源,导致其他进程在一段时间内都无法享用该资源,因此,引入同步机制,来解决个别进程长时间占用资源的问题。
- 临界资源
被保护的资源,进程之间互斥访问的资源,称为临界资源。
用来访问临界资源的代码,被称为临界区,同时,其他代码被称为非临界区,而保护公共资源是互斥访问本质就是在保护临界区,显然,如何保护临界区,是由程序员来实现。
- 什么是操作的原子性
对临界资源的操作,只有两种状态,要么还没开始访问,要么已经结束访问。
- 临界资源的访问
将临界资源,也就是一块内存,视为一个整体,这个时候一个进程访问,其他进程必须等待,考虑到效率问题,将这块内存划分为多个小内存,比如100MB的共享内存划分为多个4KB的小内存,让多进程互斥的访问小内存,可以提高效率。
这种设计需要满足:
1.被划分的小内存数量是有限的,因此,必须限制多进程数量,只允许一定数量的进程访问。
2.如何合理分配小内存给多个进程。
- 什么是信号量
“信号量”往往是在说信号量机制,我们已经知道信号量机制是SystemV通信方式的一种,是用来帮助进程通信的,那么要如何理解。
信号量本质是一个计数器,用来表述临界资源的数量,计数器有加减操作来表示临界资源数量的变化,某一个进程对这个计数器可能做加或者减操作,造成计数器值的改变要求其他进程也能看到,符合“让不同的进程看到同一份资源”这一原理,因此,信号量资源本质也是共享资源。
- 信号量不能用整型变量表示
一是因为整型变量不能被共享,二是因为整型变量的加减操作不满足原子性。
- 信号量机制
进程在访问临界资源的时候,
通常是先要申请资源,信号量做减法,也称P操作。
申请成功则表示对资源的预订,不一定立刻访问,申请失败表示资源数不够,进程必须等待
资源访问完毕后进程释放资源,信号量做加法,也称V操作。
PV操作就是进程在保护临界资源的过程。
- 信号量本身就是临界资源
信号量本身就是临界资源,必须要有申请、释放信号量的过程。
信号量接口
- 申请信号量
semget。
- 释放信号量
semctl。
- PV操作
semop