一、简介:
System V进程通信(System V IPC)是一组在Unix和类Unix操作系统中用于进程间通信的机制。这些机制在System V Release 2中首次引入,并在POSIX标准中得到部分采纳。System V IPC主要包括以下几种通信方式:
消息队列(Message Queues):
消息队列允许一个或多个进程写入或读取消息。消息队列可以看作是一个消息链表,每个消息都有一个类型和一个优先级。进程可以向队列中添加消息,也可以从队列中读取消息。
信号量(Semaphores):
信号量是一种用于同步进程的机制。它可以用来控制多个进程对共享资源的访问。System V信号量分为两种:二进制信号量和计数信号量。信号量通过操作原语如P(等待)和V(信号)来工作。
共享内存(Shared Memory):
共享内存允许两个或多个进程共享一段内存区域。这是最快的IPC方式,因为数据不需要在客户和服务器进程间复制,而是直接在内存中共享。共享内存通常与信号量结合使用,以同步对共享内存的访问。
2、消息队列
1、相关原理的简单介绍
进程之间的通信都要让两个进程看到同一块资源,在消息队列这种通信方式下,就是让两个进程在内核当中看到同一个队列。两个进程只需要向队列中写入特定类型的数据,由另外一个进程读取这个类型的数据即可。
2、相关接口介绍
<1>msgget
申请一个消息队列,参数key和shmget的key作用一样,都是标识队列的唯一性,参数msgflg和shmget的参数shmflg是一样的,一般常用的有两种参数 IPC_CREAT和IPC_EXCL(具体作用不赘述),成功返回msgid,失败返回-1,其返回值msgid可以作为用户层面上区分队列的标识。
<2>msgctl
该系统调用用来对消息队列进行控制,msqid为msgget的返回值。cmd参数表示控制命令,常用的为以下三个:
IPC_STAT:获取消息队列的各种属性。
IPC_SET:对消息队列的中的属性进行设置。
IPC_RMID:删除消息队列。(如果在终端上,我们可以使用ipcrm -q + msgid来对消息队列进行删除)。
struct msqid_ds* buf是一个输出型参数,这个结构体和struct shmid_ds几乎完全一样,该结构体中的成员都是消息队列的一些属性进行描述。
其中在结构体struct ipc_perm中可以获取key值的大小。
<3>msgsnd&&msgrcv
msgsnd表示向消息队列中发送消息,msgrcv表示向消息队列中接受信息。
第一个参数就是msgget的返回值,我们直接传入即可。在第二个参数上,两个系统调用的传参稍稍有点差异,首先我们都需要定义一个struct msgbuf;
在这个结构体中,第一个成员是消息的类型,第二个成员是具体的消息数据。在msgsnd系统调用时我们需要将该结构体内的数据手动补充完成后,再传入数据,在msgrcv中不用,只要在定义好后,传入指针即可。第三个参数就是结构体中mtext数据的大小。而msgflg这个参数我们直接设置为0,表示默认就可以了。msgtyp表示需要接受的消息队列的类型。
3、信号量
1、简单的原理介绍
在正式介绍信号量之前,我们需要了解一些预备知识:
<1>对一个共享资源的保护,是在多执行流场景下的一个常见问题,这是因为在多个执行流访问同一块共享资源时,可能会造成一些问题,比如数据不一致等。目前,管道通信是系统帮我们将管道这个共享资源进行保护,而共享内存是不会被OS主动保护的,需要用户去手动保护。、
<2>为了保护共享资源,我们一般有两种机制,我们称为互斥和同步机制。互斥,我们可以理解成当一名同学去食堂打饭时,打饭的阿姨只能给这一名同学打饭,而不能给其他同学打的这种状态,我们就可以理解成互斥状态。而同步,我们可理解成一名同学打饭,打饭完成后立马吃完,一直站在窗口上,不断让阿姨打饭,这样就会造成其他同学压根打不到饭。所以规定一个同学打完饭后必须重新站到队尾,让每个同学都能打到饭,这种机制,我们就称为同步。
<3>被保护起来的,任意时刻只允许一个执行流访问的公共资源,我们称为临界资源,访问临界资源的代码,我们就叫做临界区,其他区域叫非临界区。
<4>原子性:操作对象的时候,只有两种情况,要么还没开始,要么已经结束。
注:由于信号量涉及的内容大多与多线程有关,所以下面只做简单的介绍。
我们以电影票买票来引入信号量的概念,当我们去看电影时,一般都需要购买电影票,只有买了电影票,才能在电影的时候拥有座位。这个座位实际上是就属于电影院的一种资源,我们买票的过程就是对资源进行预定,预订以后,虽然资源现在没有被我持有,但是在未来的时间段内,只要我想,就可以使用这一部分资源。
而电影院,就需要合理的卖票 -- 有多少资源,我们就卖多少票,而电影院需要确保每一份资源都不会出现并发访问的情况,也就是多人持有一张票的情况。虽然我们将电影院看成一个整体使用(也就是只有一张票)情况下,不容易出现问题,但是资源的利用效率太低。为了提高资源的利用率,我们就在电影院中安排了很多座位,也就是将电影院资源分成很多小资源。而当进程进来申请资源时,我们需要防止申请进程数大于资源数量的情况。为了避免该情况的发生,我们就需要限制进来的进程数,合理地分配资源。
如何限制进来地进程数呢?这就需要提到信号量了,信号量本质上是一个计数器,用于描述临界资源地一个计数器(我们用int count 这个变量来描述这个计数器)。
而我们的进程申请信号量,就相当于买票预定,只要信号量申请成功(count -- ),就一定有你的资源。如果我们申请失败,就必须继续等,只有等待申请成功,才能进行下一步。申请完成后,我们就可以进行访问资源,访问完成后,就可以释放信号量(count ++)。申请信号量和释放信号量的过程就是在保护临界资源。
这里有一个问题,那就是在多进程场景下中,我们可不可以用int来实现信号量呢? 答案是不行,因为在进程之间,整型变量无法共享。不同的进程需要看到同一块计数器资源。另一个原因是因为整型的加减不是原子的,在我们将整型的加减代码转换成汇编代码,我们就会发现加减代码回转成多条汇编语句,只有一条汇编语句才能绝对保证原子性(了解即可)。
上面我们申请信号量和释放信号量,都是多进程/线程必须遵守的规定。所有进程访问临界资源时,都必须先申请信号量,这就说明所有的进程都要看到同一个信号量资源,那么信号量就是一块共享资源,为了确保这个信号量的安全,OS就必须确保信号的申请和释放操作是原子的。而申请信号量我们称为P操作,释放信号量我们称为V操作,和起来我们称为PV操作。
当我们的信号为1时,这就是互斥,而这个二元信号量也是就是一把锁。
2、信号量的相关接口:
当我们申请信号量时,是可以申请申请多个信号量,注意区分申请一个大小为n的信号量。
<1>semget
该系统调用的接口用于创建一个信号量集,第一个参数key和消息队列、共享内存的key无异,第二个参数表示想申请几个信号量,第三个参数和消息队列的shmget与msgget中的第三个参数无异,我们一般使用IPC_CREAT和IPC_EXCL。如果成功返回一个非负整数(semid),用于标识信号量集,失败返回-1。
<2>semctl
该接口用于控制创建的信号量集,第一个参数,我们返回semid即可。第二个参数,表示要对信号集中哪一个参数进行调整。第三个参数表示要对哪些信号量进行哪些操作。常见的有IPC_STAT、IPC_RMID、IPC_SET等等,和shmctl和msgctl的选项功能一样。" .... "这是一个可选的参数,它是一个union semun
类型的变量,包含了与特定命令相关的附加信息。某些semctl
命令需要额外的数据来执行操作,比如设置信号量的值、获取信号量集的权限等。这个参数提供了这些数据。
<3>semop
该接口用于表示对特定信号量执行操作。
第一个参数表示要对哪一个信号集进行操作,第二个参数需要我们自己定义一个结构体struct sembuf,里面包含的成员有如下几个:
第一个成员表示要对信号量集(我们可以看成一个数组)的哪一个进行操作,该参数就是信号量集的下标,第二个成员表示要进行P操作还是V操作,-1表示要进行P操作,1表示进行V操作。第三个成员表示操作标识,默认设为0。
第三个参数指定信号操作的数量,也就是操作几次。
4、system V进程通信的原理
通过上面的介绍,我们其实可以发现,共享内存,消息队列这三个通信方式有很多的相似之处,这是OS故意为之,目的就是为了更好地管理这些IPC资源( Inter-Process Communication)。为了管理好这些资源,OS肯定需要通过结构体对这些资源进行描述和组织。
在内核中,存在一个ipc_ids的结构体管理ipc资源,其中包含了一个entries指针,这个指针指向ipc_id_ary,在ipc_id_ary中有一个柔性数组,存放了struct kern_ipc_perm* 的指针,struct kern_ipc_perm是在内核中管理这些ipc资源属性的结构体。每当我们创建ipc资源时,OS就把描述对应资源的结构体指针放入该柔性数组。msgid、shmid、semid其实就是对应的数组下标,如果下标过大,id会进行回绕。这里操作系统将所有的指针都看成kern_ipc_perm,当OS需要对特定ipc资源进行操作时,直接把对应的指针强转为对应的类型即可,这里访问ipc资源可以通过key来实现(key的地址就是每个结构体的地址,通过key就可以直接访问结构体)。为了获取对应的类型,OS会在ipc_perm的mode成员中加入数据类型。我们一般可以用位操作定义不同的宏,通过宏来表示不同的类型。
但是由于其设计理念和Liunx一切皆文件的设计初衷稍有偏差,导致这种通信方式已经被边缘化。感谢阅读,如有不对之处,还望各位大佬指正。