【操作系统】408核心考点深度解析
导读大家好,很高兴又和大家见面啦!!!
在前面的内容中,我们一同探讨了进程的“内心世界”:从进程作为程序执行实体的基本概念,到其动态变化的生命状态,以及操作系统如何通过进程控制(如创建、切换、终止)来精准地调度这些“任务单元”。我们看到了每个进程都拥有独立的内存空间,像一个戒备森严的私人办公室,这保证了系统的稳定与安全。
然而,一个显而易见的问题随之产生:如果所有进程都如此“与世隔绝”,我们电脑上那些需要默契配合的应用程序(比如一边听音乐一边写作)又将如何协同工作?这些独立的“办公室”之间,如何才能安全、高效地传递信息、协调步伐?
这正是我们接下来要揭晓的答案。进程的独立性恰恰构成了它们之间需要通信的根本原因。本文将深入讲解操作系统是如何搭建起一座座跨越隔离的桥梁,实现进程间通信(IPC) 的。现在,就让我们一同走进这座连接进程孤岛的精密工程。
一、定义进程通信(Inter-Process Communication, IPC)是指运行在不同进程之间的信息交换机制。
之所以不同进程之间的通信需要特定的交换机制,是因为进程的独立性。
操作系统在创建每一个进程时,都会为其分配一块独立的内存空间,并且其它的进步无法直接访问该空间。因此进程与进程之间想要进行通信,就需要通过特定的信息交换机制来实现。
根据交换信息量的多少和复杂度,进程通信可分为两类:
低级通信:主要传递状态标志或简单的整数值。
例如用于进程同步与互斥的信号量机制。低级通信虽然能传递信息,但效率较低,通常需要程序员处理复杂的同步细节。 高级通信:是指以较高的效率传输大量数据。
操作系统提供了封装好的通信命令(原语),使得通信过程对程序员更透明,简化了程序编写的复杂度。高级进程通信主要有三种经典模型:共享存储、消息传递 和管道通信。接下来,我们将进一步深入探讨这三种经典模型;
二、共享存储2.1 定义共享存储 指的是在通信的进程之间存在一块 可直接访问的共享空间,通过对这片共享空间进行读/写操作实现进程之间的信息交换。
代码语言:javascript复制flowchart LR
subgraph A[内存空间]
direction LR
a[进程1]
b[进程2]
c[共享空间]
a--->c--->a
b--->c--->b
end进程在对共享空间进行读/写操作时,需要使用同步互斥工具对共享空间的读/写进行控制。如 P操作 和 V操作。
2.2 分类共享存储分为两种:
低级方式的共享:基于数据结构的共享高级方式的共享:基于存储区的共享我们可以将这两种共享方式简单的理解为:
低级共享方式规定了共享空间的数据结构 当采取该共享方式进行通信时,进程之间在通信的过程中需要按照数据结构的要求来进行通信这种通信方式速度慢、限制多,因此是一种低级通信方式高级共享方式规定了共享空间的大小 当采取该共享方式进行通信时,数据的形式、存放的位置都是由进程控制,而不是操作系统这种通信方式使得通信双方更加的方便、快捷,因此是一种高级通信方式进程通过共享存储方式进行通信时,其核心在于由通信进程直接通过共享的存储区域进行数据交换,而数据交换的逻辑、时机与语义则由进程自身控制和解释
三、消息传递3.1 定义消息传递指的是进程之间以格式化的消息(Message) 为单位,通过操作系统提供的发送消息/接收消息这两个原语 进行数据交换。
代码语言:javascript复制flowchart LR
a[进程1]--->b[进程2]--->a这种通信方式隐藏了通信实现细节,使通信过程对用户透明,简化了通信程序的设计,是当前应用最广泛的进程间通信机制。
在微内核操作系统中,微内核与服务器之间的通信就采用了消息传递机制。该机制能很好地支持多 CPU 系统、分布式系统和计算机网络,因此也成为这些领域最主要的通信工具。
3.2 分类消息传递这种通信方式按照传递方式的不同,可以分为两类:
直接通信方式:发送进程直接将消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息
间接通信方式:发送进程将消息发送到某个中间实体,接收进程从中间实体取得消息。。
这种中间实体一般称为信箱该通信方式广泛应用于计算机网络中。简单的理解就是:
直接通信方式是进程与进程直接进行通信,其中间载体为接收方的消息队列: 发送方将格式化的消息发送到接收方的消息队列中接收方从消息队列中直接读取格式化的消息代码语言:javascript复制flowchart LR
subgraph a[进程1]
a1[消息队列]
end
subgraph b[进程2]
b1[消息队列]
end
a--->|格式化消息1|b1
b--->|格式化消息2|a1间接通信方式,同样是进程与进程之间直接通信,但是其中间载体为第三方载体——信箱代码语言:javascript复制flowchart LR
subgraph a[进程1]
end
subgraph b[进程2]
end
subgraph c[信箱]
end
a--->|格式化消息1|c--->|格式化消息1|b
b--->|格式化消息2|c--->|格式化消息2|a这两种通信方式从图示中就可以看到区别:
直接通信:消息直接发送到接收方间接通信:消息先发送到信箱,接收方再从信箱中读取消息这就好比小红与小明这两个朋友平时之间的通信方式都是通过书信的形式:
直接通信:小红在写好书信后,由邮递员直接将信送到了小明的手上间接通信:小红在写好书信后,由邮递员将信先送到了指定的邮箱中,再由小明自己主动去该指定邮箱中取出小红的书信四、管道通信4.1 定义管道 是一个特殊的共享文件,也称为 pipe 文件,数据在管道中遵循 先进先出 (First _ In _ First _ Out, FIFO)的原则。
管道通信 是指两个进程之间通过 管道 完成数据交换。该通信方式允许两个进程按照 生产者-消费者 的方式进行通信。
只要管道未被填满,发送方就能将信息发送到 管道 中只要管道非空,接收方就能从 管道 中接收信息代码语言:javascript复制flowchart LR
a[进程1]
subgraph b[管道]
b1[消息1]
b2[消息2]
b3[...]
end
c[进程2]
a--->|发送消息|b--->|接收消息|c在同一时间内,单个 管道 中只能够实现单向的通信,即管道通信只能实现半双工通信:
进程1发送信息到管道中,进程2从管道中读取信息进程2发生信息到管道中,进程1从管道中读取信息若同一时间内,进程1要向进程2发送信息,进程2也要向进程1发送信息,这时就需要两条互斥的管道完成:
代码语言:javascript复制flowchart LR
subgraph a[进程1]
direction LR
a1[发送信道]
a2[接收信道]
end
subgraph B[管道]
direction LR
subgraph b[管道1]
b1[消息1]
b2[消息2]
b3[...]
end
subgraph d[管道2]
d1[消息1]
d2[消息2]
d3[...]
end
end
subgraph c[进程2]
direction LR
c2[接收信道]
c1[发送信道]
end
a--->B--->c
c--->B--->a4.2 协调能力在管道通信中,为了协调双方的通信,管道机制必须满足三方面的协调能力:
互斥:当一个进程对管道进行 读/写 操作时,其它进程必须等待同步:读写进程保持同步 进程向管道中写入一定数量的数据后,写进程阻塞,直到读进程取走数据后,再将它唤醒读进程将管道中的数据取空后,读进程阻塞,直到写进程将数据写入管道后,才将其唤醒确定对方的存在。 管道通信不可能让一个进程与另一个根本不存在的进程进行通信。通信双方如果是以 管道通信 的方式实现的信息交换,那么必然满足下面的条件:
进程必须存在:通信双方都必须是正在运行的进程管道端点必须被打开:双方都必须持有管道的文件描述符连接必须建立:特别是对于命名管道,需要双方都打开管道4.3 Linux 中的管道通信在 Linux 中,管道是一种使用十分频繁的通信机制。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题:
限制管道的大小。 管道是一个固定大小的缓冲区,我们可以将其视为一个定长的循环队列在 Linux 中,管道的大小为 4KB ,这是的它的大小不像普通文件那样不加检验的增长。使用单个固定缓冲区也会带来问题: 管道被写满时,随后对管道的 write() 调用将会默认被阻塞,等待某些数据被读取,以便腾出足够的空间供 write() 调用读进程也可能工作得比写进场快。 当管道内不存在任何数据时,即管道变为了 空队列,此时随后的 read() 调用会被阻塞,等到某些数据的写入4.4 管道的特性标准的匿名管道只允许存在一个读进程与一个写进程。
不过对于满足 FIFO 这种特性的队列而言,它是能够天然的支持 多个写进程 ,这是因为写入的数据会按写入的顺序进行排列,并不会对管道造成什么影响。
但是当多个读进程来同时读取该管道时,就可能出现数据争抢的情况,多个读者读取同一份数据,这就会导致读取失败的问题。
这是因为管道中的数据被读取后,就会从管道中消失,为新的数据腾出空间,以便进行新的写入操作,即一个数据只能被一个读者获取,在这种情况下,其它读者就可能出现以下情况:
读取成功,其它读者读取到了正确的信息读取失败,其它读者未能读取到正确的信息: 可能读取到空,或者部分信息读取的信息不是当前读者需要的信息所以,我们可以得到结论:
同一个管道可以允许多个写进程,但只允许一个读进程;但是在实际的使用中,同一个管道是允许 多个写进程 写入数据,同时也允许 多个读进程 来读取数据。只不过在这种情况下,多个读进程读取同一条管道中的数据时,需要经过一些特殊处理,才能够正常的读取数据:
给管道进行命名,明确每一个写入者写入数据的管道,以及每一个读者需要读取数据的管道;通过广播式多读,将管道中的信息复制到其它管道中,每个读取者都会从对应的管道中读取到全部的信息;让多个读者轮流读取管道中的信息;不管是上述的哪种特殊处理,都能够实现管道的多写多读。
管道只能由创建创建进程所访问。
想要成功的创建一个管道,就必须保证:
通信的双方都是正在运行的进程双方都必须持有管道的文件描述符双方都需要与管道建立连接因此当一个父进程创建了一个管道后,子进程会继承父进程的管道,并可以利用该管道来与父进程进行通信。
结语今天的内容到这里就全部结束了。通过今天的学习,我们系统地揭开了进程间通信(IPC)的神秘面纱。进程的独立性是通信机制存在的根本原因,而操作系统则为我们提供了多种精巧的“桥梁”来跨越这一鸿沟。
我们深入探讨了三种经典的高级通信模型:
共享存储:如同为进程开辟了一个共享的“白板”,通信双方通过直接读写这块共享区域来交换数据,其核心挑战与关键在于如何通过同步互斥工具(如P/V操作)来协调双方的访问,确保数据的一致性。
消息传递:这是当前应用最广泛的机制,进程间通过发送和接收格式化的“消息包”进行通信。无论是消息直接送达的直接通信方式,还是通过“信箱”中转的间接通信方式,其优势都在于将复杂的通信细节封装成原语,对程序员透明,尤其适用于分布式系统。
管道通信:它模拟了现实中的“管道”,数据遵循FIFO(先进先出)的原则单向流动。我们了解了其作为生产者-消费者模型的典型应用,以及为保证通信正确性所必须满足的互斥、同步和确认对方存在三大协调能力。此外,还特别探讨了其在Linux环境下的具体实现与特性。
回顾全文,这三种机制各具特色,但它们共同解决了同一个核心问题:如何在保证进程独立性的前提下,安全、高效地实现数据交换与进程协同。
理解这些基础通信模型,不仅是掌握操作系统原理的关键一环,也为今后学习更复杂的分布式系统、网络编程等知识奠定了坚实的基石。
希望本篇内容能帮助您清晰地构建起进程通信的知识框架。