管道模型
先看一个命令:
ps -ef | grep 关键字 | awk '{print $2}' | xargs kill -9
这里面的竖线 " | " 就是一个管道。它会将前一个命令的输出,作为后一个命令的输入。从管道的这个名称可以看出来,管道是一种单向传输数据的机制,它其实是一段缓存,里面的数据只能从一端写入,从另一端读出。如果想互相通信,我们需要创建两个管道才行。
管道分为两种类型。 " | " 表示的管道称为匿名管道,意思就是这个类型的管道没有名字,用完了就销毁了。就像上面那个命令里面的一样,竖线代表的管道随着命令的执行自动创建、自动销毁。用户甚至不知道自己在用管道这种技术,就已经解决了问题。
另外一种类型是命名管道。这个类型的管道需要通过 mkfifo 命令显式地创建。
mkfifo hello
hello 就是这个管道的名称。管道以文件的形式存在,这也符合 Linux 里面一切皆文件的原则。如果我们用 ls 可以看到,这个文件的类型是 p ,就是 pipe 的意思。
# ls -l
prw-r--r-- 1 root root 0 May 21 23:29 hello
接下来,我们还可以往管道里面写入东西。例如,写入一个字符串:
# echo "hello world" > hello
这时,管道里面的内容没有被读出,这个命令就是停在这里的,这就意味着,一个人的输出产品,要有另外一人去接收,而不能置之不理。
那么,我们就需要从另外一个终端上读取管道里面的内容:
# cat < hello
hello world
这样,数据被成功读取出来了,打印到了终端上;另一方面,echo 那个命令正常退出了。
我们可以看出,管道的效率比较低下,管道的使用模式,也不适合进程间频繁的交换数据。
消息队列模型
这种模型类似进程间通信的消息队列模型。和管道将信息一股脑儿地从一个进程,倒给另一个进程不同,消息队列有点儿像邮件,发送数据时,会分成一个一个独立的数据单元,也就是消息体,每个消息体都是固定大小的存储块,在字节上不连续。
有了消息这种模型,两个进程之间的通信就像咱们平时发邮件一样,你来一封,我回一封,可以频繁沟通了。
共享内存模型
但是有时候,项目组之间的沟通需要特别紧密,而且要分享一些比较大的数据。如果使用邮件,就发现,一方面邮件的来去不及时;另外一方面,附件大小也有限制,所以,这个时候,我们经常采取的方式就是,把两个项目组在需要合作的期间,拉到一个会议室进行合作开发,这样大家可以直接交流文档、架构图等,直接在白板上画或者直接扔给对方,就可以直接看到。
可以看出来,共享会议室这种模型,类似于进程间通信的共享内存模型。我们知道,每个进程都有自己独立的虚拟内存空间,不同的进程的虚拟内存空间映射到不同的物理内存中去。这个进程访问 A 地址和另一个进程访问 A 地址,其实访问的是不同的物理内存地址,对于数据的增删改查互不影响。
但是,如果我们拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去。
信号量
这里有个问题,就是,大家在共享的内存都去写,就会导致冲突。所以,这里就需要一种保护机制,使得同一个共享的资源,同时只能被一个进程访问。因此,信号量和共享内存往往要配合使用。
信号量其实是一个计数器,主要用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。我们可以将信号量初始化为一个数值,来代表某种资源的总体数量。对于信号量来讲,会定义两种原子操作,一个是 P 操作,我们称为申请资源操作。这个操作会申请将信号量的数值减去 N,表示这些数量被他申请使用了,其他人不能用了。另一个是V 操作,我们称为归还资源操作,这个操作会申请将信号量加上 M,表示这些数量已经还给信号量了,其他人可以使用了。
假如你有100元钱,就可以将信号量设置为100。其中A向你借80元,就会调用P操作,申请减去80.如果同时B向你借50元,但是B的P操作比A晚,那就没有办法,只好等待A归还钱的时候,B的P操作才能成功。之后,A调用V操作,申请加上30元,也就是还给你30元,这个时候信号量有50元,这时候B的P操作才能成功,才能借走这50元。
所谓原子操作,就是任何一块钱,都只能通过P操作借给一个人,不能同时借给两个人。也就是说,当A的P操作(借80)和B的P操作(借50),几乎同时到达的时候,不能因为大家都看到账户里有100就都成功,必须分个先来后到。
信号
上面说的进程间通信的方式,都是常规状态下的工作模式,对应到咱们平时的工作交接,收发邮件、联合开发等,其实还有一种异常情况下的工作模式。
例如出现线上系统故障,这个时候,什么流程都来不及了,不可能发邮件,也来不及开会,所有的架构师、开发、运维都要被通知紧急出动。所以,7乘24小时不间断执行的系统都需要有告警系统,一旦出事情,就要通知到人,哪怕是半夜,也要电话叫起来,处理故障。
对应到操作系统中,就是信号。信号没有特别复杂的数据结构,就是用一个代号一样的数字。Linux 提供了几十种信号,分别代表不同的意义。信号之间依靠它们的值来区分。这就像咱们看警匪片,对于紧急的行动,都是说,“1 号作战任务”开始执行,警察就开始行动了。情况紧急,不能啰里啰嗦了。
信号可以在任何时候发送给某一个进程,进程需要为这个信号配置信号处理函数。当某个信号发生的时候,就默认执行这个函数就可以了。这就相当于咱们运维一个系统应急手册,当遇到什么情况,做什么事情,都事先准备好,出了事情照着做就可以了。