哪些是linux进程间通信机制 是冷的! 张三没能正确回答“进程间通信”,被面试官拒绝了……

进程间的通信机制_进程通信机制有几种_哪些是linux进程间通信机制

每天都在花钱

前言

开场故事

进程间的通信机制_进程通信机制有几种_哪些是linux进程间通信机制

炎热的夏天,张三骑着一个小时的自行车去面试,一路上大汗淋漓。

结果,面试过程只用了5分钟就结束了。 采访结束的时候,天还亮着,我只好顶着烈日跑了一个小时回来。

采访持续了五分钟,车程花了两个小时。

你看,因为张三没有做好面试准备,所以他只有5分钟的时间开空调,来回的路上在阳光下晒了2个小时。 你觉得惨不惨?

因此,在炎热的夏季,为了延长吹空调的时间,我们在面试前应该多做一些准备。 吹空调的时间必须要我们自己争取。

很明显,在这次采访中,张三并没有很好地审视进程通信。 虽然列出了进程间通信的方法,但这只是表面的。 应该有必要进一步了解每种通信方式的优缺点。 应用场景。

说真的,这次我们就帮张三复习一下,加深一下他对进程间通信的理解,这样下次他就能吹更长的时间空调了。

进程间的通信机制_进程通信机制有几种_哪些是linux进程间通信机制

文本

每个进程的用户地址空间是独立的,一般不能互相访问。 然而内核空间是各个进程共享的,因此进程之间的通信必须经过内核。

哪些是linux进程间通信机制_进程通信机制有几种_进程间的通信机制

Linux内核提供了许多进程间通信的机制。 我们来看看它们是什么?

管道

如果你学过Linux命令,你一定对竖线“|”很熟悉。

$ ps auxf | grep mysql

“|” 上面命令行中的竖线是一个管道。 它的功能是使用上一个命令(ps auxf)的输出作为下一个命令(grep mysql)的输入。 从这个功能描述我们可以看出,管道传输数据是单向的,如果我们想要互相通信,我们需要创建两个管道。

同时我们了解到上述类型的管道是没有名字的,所以用“|”表示的管道称为匿名管道,使用后被销毁。

另一种类型的管道是命名管道,也称为 FIFO,因为数据以先进先出的方式传输。

在使用命名管道之前,需要通过 mkfifo 命令创建它并指定管道名称:

$ mkfifo myPipe

myPipe 是该管道​​的名称。 基于Linux一切皆文件的理念,管道也以文件的形式存在。 我们可以使用ls看到这个文件的类型是p,即管道的意思:

$ ls -l
prw-r--r--. 1 root    root         0 Jul 17 02:45 myPipe

接下来,我们将数据写入 myPipe 管道:

echo "hello" > myPipe  // 将数据写进管道
                         // 停住了 ...

你操作之后会发现命令执行完就停在这里了。 这是因为管道中的内容还没有被读取。 只有读取管道中的数据后,命令才能正常退出。

因此,我们执行另一个命令来读取该管道中的数据:

$ cat < myPipe  // 读取管道里的数据
hello

正如您所看到的,管道的内容被读出并打印在终端上。 另一方面,echo命令也正常退出。

我们可以看到管道的通信方式效率低下,不适合进程间频繁交换数据。 当然它的优点就是简单,我们也很容易知道管道中的数据已经被另一个进程读取了。

如何创建管道以及其背后的原理是什么?

匿名管道的创建需要以下系统调用:

int pipe(int fd[2])

这意味着创建一个匿名管道并返回两个描述符,一个是管道的读端描述符fd[0],另一个是管道的写端描述符fd[1]。 注意,这个匿名管道是一个特殊的文件,只存在于内存中,不存在于文件系统中。

进程间的通信机制_进程通信机制有几种_哪些是linux进程间通信机制

其实所谓的管道就是内核中的一系列缓存。 从管道的一段写入的数据实际上缓存在内核中,从另一端读取意味着从内核中读取此数据。 另外,管道传输的数据是未格式化的流,并且大小有限。

看到这里,你可能会有疑问。 这两个描述符在一个进程中,不起到进程间通信的作用。 我们怎样才能让管道跨越两个进程呢?

我们可以使用fork来创建子进程。 创建的子进程会复制父进程的文件描述符。 这样,两个进程各有两个“fd[0]和fd[1]”,两个进程可以通过各自的fd写入和读取同一个管道文件,从而实现跨进程通信。

进程间的通信机制_进程通信机制有几种_哪些是linux进程间通信机制

管道只能向一端写入,从另一端读取,因此上述模式很容易造成混乱,因为父进程和子进程都可以同时写入和读取。 那么,为了避免这种情况,通常的做法是:

哪些是linux进程间通信机制_进程间的通信机制_进程通信机制有几种

因此,如果需要双向通信,则应创建两个管道。

到目前为止,我们只分析了父进程和子进程之间使用管道进行通信,但在我们的 shell 中并不是这样的。

当 A | B命令是在shell中执行的,A进程和B进程都是shell创建的子进程。 A和B之间没有父子关系,它们的父进程都是shell。

进程通信机制有几种_哪些是linux进程间通信机制_进程间的通信机制

因此,在shell中,通过“|”将多个命令连接在一起。 匿名管道实际上创建了多个子进程。 当我们编写 shell 脚本时,如果我们可以使用一根管道来做事情,那么我们就不应该使用多个管道。 管道,减少创建子进程的开销。

我们可以知道,对于匿名管道来说,其通信范围是具有父子关系的进程。 因为管道没有实体,即没有管道文​​件,所以只能通过fork复制父进程的fd文件描述符来达到通信目的。

另外,对于命名管道来说,它可以在不相关的进程之间进行相互通信。 因为命令管道的原因,提前创建了一个pipe类型的设备文件。 只要进程中使用了这个设备文件,它们就可以相互通信。

无论是匿名管道还是命名管道,进程写入的数据都会缓存在内核中。 当另一个进程读取数据时,自然会从内核获取。 同时通信数据遵循先进先出原则哪些是linux进程间通信机制,不支持lseek等。 文件位置操作。

消息队列

前面提到,管道的通信方式效率低下,因此管道不适合进程间频繁交换数据。

对于这个问题,消息队列的通信方式可以解决。 例如,进程A要向进程B发送消息,进程A将数据放入相应的消息队列后即可正常返回。 然后进程 B 可以在需要时读取数据。 同理,进程B想要发送消息给进程A。同样如此。

接下来,消息队列是内核中存储的消息链表。 发送数据时,会被分成独立的数据单元,即消息体(数据块)。 消息正文是用户定义的数据类型。 消息的发送方必须与接收方约定消息体的数据类型,因此每个消息体都是一个固定大小的存储块,不像管道是无格式的字节流数据。 如果进程从消息队列中读取了消息体,内核就会删除该消息体。

消息队列的生命周期遵循内核。 如果消息队列不释放或者操作系统不关闭,消息队列将一直存在。 上面提到的匿名管道的生命周期是随着进程的创建而建立,随着进程的结束而销毁。

通过这种消息模型,两个进程之间的通信就像平时发送电子邮件一样。 你发一份,我回复。 可以进行频繁的沟通。

然而,电子邮件通信方法有两个缺点。 一是沟通不及时,二是附件有大小限制。 这也是消息队列通信的一个缺点。

消息队列不适合大数据的传输,因为内核中的每个消息体都有最大长度限制,并且所有队列中包含的所有消息体的总长度也有上限。 在Linux内核中,有两个宏定义MSGMAX和MSGMNB,分别定义了消息的最大长度和队列的最大长度(以字节为单位)。

消息队列通信过程中,用户态和内核态之间存在数据复制开销。 因为当进程向内核中的消息队列写入数据时,就会发生数据从用户态复制到内核态的过程。 另一个进程也是如此。 在内核中读取消息数据时,会发生将数据从内核态复制到用户态的过程。

共享内存

消息队列的读写过程会涉及到用户态和内核态之间的消息复制过程。 共享内存的方法很好的解决了这个问题。

现代操作系统采用虚拟内存技术进行内存管理,即每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存。 因此,即使进程A和进程B的虚拟地址相同,但实际上访问的物理内存地址不同,数据的增删查改互不影响。

共享内存的机制就是取出一块虚拟地址空间,映射到同一块物理内存上。 这样,这个进程写的东西可以立即被另一个进程看到,而不需要复制和传递,这大大提高了进程间通信的速度。

进程间的通信机制_哪些是linux进程间通信机制_进程通信机制有几种

信号

使用共享内存通信带来了新的问题,即如果多个进程同时修改同一共享内存,很可能会发生冲突。 比如两个进程同时写入一个地址,先写入的进程会发现内容已经被其他进程覆盖了。

为了防止多个进程竞争共享资源而造成数据混乱,需要一种保护机制,使得共享资源在任何时候只能被一个进程访问。 幸运的是,信号量实现了这种保护机制。

信号量实际上是一个整数计数器,主要用于实现进程间的互斥和同步,而不是缓存用于进程间通信的数据。

信号量代表资源的数量。 有两个原子操作来控制信号量:

P操作在进入共享资源之前使用,V操作在离开共享资源之后使用。 这两个操作必须成对出现。

接下来,例如,如果我们想让两个进程互斥地访问共享内存,我们可以将信号量初始化为1。

进程间的通信机制_哪些是linux进程间通信机制_进程通信机制有几种

具体流程如下:

可以发现信号量被初始化为1,表示它是一个互斥信号量。 它可以保证任何时候只有一个进程在访问共享内存,很好地保护了共享内存。

另外,在多进程中,每个进程不一定是顺序执行的。 它们基本上以独立且不可预测的速度前进,但有时我们希望多个进程能够紧密合作来实现共同的任务。

例如,进程A负责产生数据,进程B负责读取数据。 这两个过程相互配合、相互依赖。 进程A必须先产生数据,进程B才能读取数据,因此执行是必要的。 依次。

那么这个时候,我们就可以利用信号量来实现多进程的同步。 我们可以将信号量初始化为0。

哪些是linux进程间通信机制_进程间的通信机制_进程通信机制有几种

具体流程:

可以发现该信号被初始化为0,这意味着它是一个同步信号量,可以保证进程A应该先于进程B执行。

信号

上面提到的进程间通信是一种正常的工作模式。 对于异常情况下的工作模式,需要使用“信号”来通知进程。

虽然信号和信号量的名称有 66.66% 的相似度,但它们的用途却完全不同,就像 Java 和 JavaScript 的区别一样。

在Linux操作系统中,为了响应各种事件,提供了几十种信号,每种信号代表不同的含义。 我们可以通过kill -l命令查看所有信号:

$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

对于运行在shell终端中的进程,当我们通过键盘输入某些组合键时,我们可以向进程发送信号。例如

如果进程在后台运行,可以通过kill命令向进程发送信号,但需要知道正在运行的进程的PID号,例如:

因此,信号事件的来源主要包括硬件来源(如键盘Cltr+C)和软件来源(如kill命令)。

信号是进程间通信机制中唯一的异步通信机制,因为信号可以随时发送到进程。 一旦信号产生,我们有以下方法供用户进程处理信号。

1. 执行默认操作。 Linux 为每个信号提供了默认操作。 例如,上面列表中的SIGTERM信号意味着终止进程。 Core的意思是Core Dump,即终止进程后,通过Core Dump将当前进程的运行状态保存在一个文件中,以便程序员事后分析问题。

2. 捕获信号。 我们可以为信号定义一个信号处理函数。 当信号发生时,我们执行相应的信号处理函数。

3. 忽略信号。 当我们不想处理某些信号时,可以忽略该信号,不做任何处理。 有两个信号是应用程序进程无法捕获和忽略的,即SIGKILL和SEGSTOP,它们用于随时中断或结束进程。

插座

前面提到的管道、消息队列、共享内存、信号量和信号都是用于同一主机上的进程间通信。 如果要通过网络与不同主机上的进程进行通信,就需要Socket通信。

事实上,Socket通信不仅可以跨网络、不同主机的进程之间进行通信,还可以在同一主机上的进程之间进行通信。

我们看一下创建套接字的系统调用:

int socket(int domain, int type, int protocal)

三个参数分别代表:

根据创建的套接字类型不同,通信方式也不同:

接下来简单说一下这三种通信编程模式。

TCP协议通信的Socket编程模型

哪些是linux进程间通信机制_进程通信机制有几种_进程间的通信机制

这里需要注意的是,当服务器调用accept时,如果连接成功哪些是linux进程间通信机制,会返回一个连接完成的socket,用于后面传输数据。

因此,监听套接字和实际用于传输数据的套接字是“两个”套接字,一个称为监听套接字,另一个称为已完成连接套接字。

连接建立成功后,双方开始通过读写函数读写数据,就像写入文件流一样。

UDP协议通信的Socket编程模型

进程间的通信机制_进程通信机制有几种_哪些是linux进程间通信机制

UDP没有连接,因此不需要三次握手,也不需要像TCP那样调用listen和connect。 但UDP交互仍然需要IP地址和端口号,所以也需要bind。

对于UDP来说,不需要维护连接,因此不存在所谓的发送方和接收方,甚至不存在客户端和服务器的概念。 只要有一个socket,多台机器就可以随意通信,所以每个UDP的socket都需要bind。

另外,每次通信、调用sendto和recvfrom时,都必须传入目标主机的IP地址和端口。

用于本地进程间通信的套接字编程模型

本地套接字用于同一主机上的进程间通信:

对于本地字节流套接字,套接字类型为 AF_LOCAL 和 SOCK_STREAM。

对于本地数据报套接字,套接字类型为 AF_LOCAL 和 SOCK_DGRAM。

本地字节流套接字和本地数据报套接字绑定时,与绑定IP地址和端口的TCP和UDP不同,它们绑定的是本地文件。 这是他们之间最大的区别。

总结

由于各个进程的用户空间是独立的,不能互相访问,所以需要利用内核空间来实现进程间的通信。 原因很简单。 每个进程共享一个内核空间。

Linux内核提供了多种进程间通信的方法,其中最简单的是管道。 管道分为“匿名管道”和“命名管道”。

顾名思义,匿名管道没有名称标识符。 匿名管道是只存在于内存中而不存在于文件系统中的特殊文件。 “|” shell命令中的竖线是匿名管道。 通信数据是无格式的流,大小有限。 有限,通信方式是单向的,数据只能朝一个方向流动。 如果要双向通信,则需要创建两个管道。 此外,匿名管道只能用于具有父子关系的进程间通信。 匿名管道的生命周期在进程创建时建立,在进程终止时消失。

命名管道突破了匿名管道只能在相关进程之间通信的限制。 因为使用命名管道的前提是在文件系统中创建一个p类型的设备文件,那么无关的进程就可以使用这个设备文件。 沟通。 另外,无论是匿名管道还是命名管道,进程写入的数据都会缓存在内核中。 当另一个进程读取数据时,自然会从内核获取。 同时通信数据遵循先进先出原则,不支持lseek。 类文件位置操作。

消息队列克服了管道通信中数据是无格式字节流的问题。 消息队列实际上是内核中存储的一个“消息链表”。 消息队列的消息体是用户定义的数据类型。 发送数据时,会被分成独立的消息体。 当然,在接收数据时,也必须与发送方发送的消息体的数据类型一致,这样才能保证读取到的数据正确。 消息队列通信的速度并不是最及时的。 毕竟,每次数据的写入和读取都需要在用户态和内核态之间进行一次复制过程。

共享内存可以解决消息队列通信中用户态和内核态之间的数据复制过程带来的开销。 它直接分配一个共享空间,每个进程都可以直接访问它。 就像访问进程自己的空间一样快捷方便。 它需要陷入内核态或系统调用,大大提高了通信速度,享有最快的进程间通信方式的美誉。 然而,便捷高效的共享内存通信带来了新的问题。 多个进程竞争同一共享资源会导致数据混乱。

那么就需要信号量来保护共享资源,保证任何时候只有一个进程可以访问共享资源。 该方法是互斥访问。 信号量不仅可以实现访问的互斥,还可以实现进程间的同步。 信号量实际上是一个计数器,它代表资源的数量。 它的值可以通过两个原子操作来控制,即P操作和V操作。

与信号量名称非常相似的信号量称为信号量。 虽然它们的名字相似,但功能却完全不同。 信号是进程间通信机制中唯一的异步通信机制。 信号可以在应用程序进程和内核之间直接交互。 内核还可以使用信号来通知用户空间进程发生了哪些系统事件。 信号事件的主要来源是硬件源。 (如键盘Cltr+C)和软件源(如kill命令),一旦有信号发生,进程有三种方式响应信号:1.执行默认操作,2.捕获信号,3.忽略信号。 有两个信号不能被应用程序进程捕获和忽略,即SIGKILL 和SEGSTOP。 这是为了方便我们随时结束或停止一个进程。

前面提到的通信机制都工作在同一主机上。 如果要与不同主机上的进程通信,就需要Socket通信。 Socket实际上不仅用于不同主机进程之间的通信,还可以用于本地主机进程之间的通信。 根据创建的Socket类型可以分为三种常见的通信方式。 一种是基于TCP协议的,一种是基于TCP协议的。 一种是基于UDP协议的通信方式,另一种是本地进程间通信方式。

以上就是进程间通信的主要机制。 你可能会问,线程间通信的方法呢?

同一进程中的线程都共享进程资源。 只要使用共享变量就可以实现线程间通信,比如全局变量。 因此,重点不在于线程之间的通信方式,而在于多线程对共享资源的竞争。 问题,信号量还可以实现线程间的互斥和同步:

好了,今天张三的点评就到这里了。 希望张三早日收到自己喜欢的offer,给这个夏天带来一个汗流浃背的结局。

进程通信机制有几种_哪些是linux进程间通信机制_进程间的通信机制

© 版权声明
THE END
喜欢就支持一下吧
点赞203赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容