上一篇博客中我介绍了system V进程间通信中的内存共享,但是其中还有两
种通信方式:消息队列、和信号量,接下来我将简单介绍一下,消息队列和
信号量以及操作系统是如何看待system V进程间通信的。
1. 消息队列
a. 大致介绍
消息队列会维护一个队列,供两个进程进行进程间通信:它与共享内存不同的就是它的数据是存储在一个节点中,再次存储数据时再创建一个节点链入到顺序表或者链表构成的队列中,关于它的数据存储格式是在一个结构体中,但是这个结构体由用户自定义:
其中第一个成员是用户自定义的类型(例如我要自定义A进程存储进去的数据是A类型ATYPE,我们可以使用宏定义来进行)第二个是用户的存储数据,这个可以是一个数组也可以是一个结构体,而对于数据的访问方式,那自然是由第一个成员的类型来让用户决定的。所以假如我们要让两个进程实现进程间通信的话,使用内存共享我们可能不太方便区分共享内存中的数据究竟是谁要接收,使用消息队列的话我们可以自定义数据的访问方式,当我们需要A进程提取B进程的消息时,我们就可以在队列中遍历寻找数据类型为B进程所时使用的类型,这样就可以区分数据是谁发送供谁使用了:
b. 函数接口
消息队列的函数接口大致与共享内存的接口形式上差不多,依旧是:
msgget
消息队列的数据发送:
消息队列的删除:
其中删除消息队列的方式仍旧是IPC_RMID。
命令行中使用ipcs -q可以查看系统中的消息队列
ipcrm -q + msgid可以删除消息队列
操作系统中也会存在许多的消息队列,那么消息队列也需要被管理仍旧是先描述后组织。
以上就是消息队列的大致认识
2. 简单认识信号量
我们在认识共享内存的时候我们发现共享内存是不安全的,它没有管道那样的同步机制,只要进程可以挂接上它,那么那个进程就可以随意的访问共享内存。这就会导致可能这个进程还在往进写数据时,另一个进程就开始读了。这样使得两个进程做的工作都没有了意义。
所以共享资源的使用是需要被保护起来的,就有了互斥和同步。
互斥:任何一个时刻只允许一个执行流访问共享资源
同步:多个执行流访问共享资源时,按照一定顺序访问
保护共享资源,其实也可以理解为保护代码中使用共享资源的代码段,只要将这一段代码维护好了,那么共享资源也就被维护好了,因为共享资源的使用也不就是通过代码来使用的嘛。所以我们将共享资源叫做临界资源,而访问临界资源的代码叫做临界区。对临界资源的保护就转化成了临界区的保护。
在接口中信号量的创建接口中,第二个参数是创建多少个信号量,因为我们的共享资源申请时虽然是一大片,但是使用时可能是多个部分,所以需要多个信号量。创建出来的多个信号量,我们也叫信号量集。
信号量的本质其实就是一个计数器,你可以理解为信号量中有一个整型,它表示了共享资源中可使用资源的数量。当有进程访问这个整形就–,当使用完成该部分共享资源时这个整形就会++。
这样当我们使用信号量保护我们的共享资源之后,如果有进程需要访问我们的共享资源,我们首先需要看到信号量中的那个整形是否大于0,如果大于0,那么我们这个进程就可以开始访问共享资源,同时整形–。如果整形等于0,那么此时访问信号量的进程就会被阻塞挂起,直到共享资源中有可用的资源。
这个时候我们就可以保护共享资源了:
而当信号量中的整形最大是1,意味着该整形只有两种状态0或1,那就表示该信号量所保护的资源同一时间只能由一个进程访问使用,而这种保护机制就叫互斥,也叫加锁,加了互斥锁。
上面说到,信号量可以理解为其中有一个整形,用来记录可访问共享资源的数目,那么这个整形也就意味着必须能够被不同进程能够看到,那么就说明它不是属于某一个进程中的资源。而是操作系统提供的资源。不同进程看到了同一份资源,这不就是进程间通信嘛。所以信号量也是进程间通信,也是共享资源。那么它也需要被保护啊。所以我们对信号量的操作是原子性的,在对他的操作中只有++和–,–只有成功和不成功。也就是对信号量的操作中只有不执行和执行后必须完成两种状态这种原子操作,这样也算是保护了自己。
通过使用信号量,可以有效地控制对共享资源的访问,避免了数据竞争和冲突,确保了线程或进程之间的安全协作。
3. 操作系统对system V进程间通信的看法
经过以上的认识我们可以看到,system V进程间通信不像管道一样使用了旧的文件系统的接口来实现的,它是全新的一个部分,是独立于内存管理、文件管理、驱动管理、进程管理的。当我们观察三个通信控制接口时,都会发现,当使用特定命令操作时,它们都会提供各自的暴露给用户的数据结构:
共享内存:
消息队列:
信号量:
前面说了这些通信资源也是需要被管理的,那么管理的方式依旧是通过将它们抽象成结构体,然后通过数据结构组织起来,而以上的各个结构体是操作系统暴露给用户供用户使用的对资源的属性或者内容修改的部分。这些结构体是内核数据结构中的子集。而在这些结构体中我们发现它们后缀为_ds的结构体中第一个成员都是一个类型为struct ipc_perm的结构体,而这其中就记录着一个属性key,这个key就是用户所传入的参数key,也是操作系统识别通信资源的依据。
而在操作系统内核中,有一类型为struct ipc_ids的结构体,其中有一个成员entries,它是一个指针,它的类型是struct ipc_id_ary。struct ipc_id_ary中有一个柔性指针数组,这个数组中存储着一个结构体,它的类型是struct kernel_ipc_perm看起来和向上面的通信资源结构体的ipc_perm很像,其实内核中的管理通信资源的结构体也是像以上的设计,它们各种通信方式的结构体的开头成员都是kernel_ipc_perm,这样就可以通过柔性数组访问到任何一种通信方式的结构体,从而对通信资源实行管理。而访问通信资源的结构体我们只需要通过key遍历柔性数组的元素,找到之后判断出它是哪种通信方式,然后对这个kernel_ipc_perm结构体取地址,强转成对应的通信资源结构体
,这样就可以访问到目标通信资源结构体了。
这样假如这个数组中的第二个元素是共享内存的kennel_perm,我们要访问它就可以这样:
(struct shmid_ds*)p[1]->...
这种方式不就是C语言版的多态吗?
这样就能访问到每一个通信资源结构体了。操作系统也就是使用这样的结构来通过一个数组管理好所有的通信结构体。