管道
管道是内核里的一串缓存,从管道一端写入的数据,实际上被缓存在了内核中。另一端读取也是从内核中读取这段数据。此外,管道的数据是无格式数据,且大小受限。
Linux命令ps auxf | grep mysql
中的竖线“ | ”就是一个管道,其功能是将前一个命令的输出,作为后一个命令的输入。由此看出,管道传输的数据是单向的。若要互相通信,则需要创建两个管道。
竖线“ | ”创建的管道属于匿名管道,匿名管道用完了就会被销毁。
另一种管道模式是命名管道,也称为FIFO,因为这种管道的数据是先进先出的传输方式。
创建命名管道时,需要指定管道的名字:mkfifo myPipe
。
“hello">myPipe //将数据写进管道
//停住了
cat<myPipe //读取管道里的数据
hello
可以看出,将数据写进管道之后会停住,因为管道里的内容没有被读取,当管道里的数据被读完后,命令才可以正常退出。
而且,不管是匿名管道还是命名管道,进程写⼊的数据都是缓存在内核中,另⼀个进程读取数据时候⾃然也是从内核中获取,同时通信数据都遵循先进先出原则。
所以管道这种通信方式效率低,不适合进程间频繁地交换数据。
对于匿名管道,其通信范围是存在父子关系的进程。因为管道没有实体,也就没有管道文件,只能通过fork命令复制父进程的文件描述符fd,来达到通信的目的。
对于命名管道,它可以在不相关的进程间实现通信。因为命名管道提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。
消息队列
消息队列是保存在内核中的消息链表。在发送数据时,会分成一个个独立的数据单元,也就是 消息体。消息的发送方和接收方事件约定好消息体的数据类型,每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。从消息队列中读取了消息体后,内核就会把该消息体删除。
消息队列优于管道的地方在于,A进程给B进程发送消息,把数据放在对应的消息队列之后就可以返回了。B进程需要的时候再去读取数据就可以,是一种异步通信方式。
消息队列也有其局限性,一是通信不够及时,二是消息队列的大小也有限制,即每个消息体有一个最大长度的限制。
此外,消息队列通信过程中,还存在用户态与内核态之间的数据拷贝开销。进程写入数据到内核中消息队列时,会从用户态拷贝数据到内核态;同理另一进程读取内核中消息时,会从内核态拷贝数据到用户态。
共享内存
共享内存就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样一个进程写入的东西另一进程可以立马看到,不需要拷贝数据,改善了消息队列内核态与用户态之间消息拷贝过程的开销。
但共享内存同样有其缺点,那就是多个进程同时修改一处物理内存,很有可能造成冲突,先写完的内容很快会被后写完的覆盖。
信号量
为了改善共享内存中多个进程同时修改一处共享内存的问题,可以使用信号量。信号量是一个整形的计数器,用于实现进程间的互斥与同步,而并非直接用于进程间的通信。
P操作会把信号量减去 1,相减后如果信号量 < 0,则表明资源已被占⽤,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使⽤,进程可正常继续执⾏。
V 操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运⾏;相加后如果信号量 > 0,则表明当前没有阻塞中的进程。
P 操作是⽤在进⼊共享资源之前,V 操作是⽤在离开共享资源之后,这两个操作必须成对出现。
信号
上面的进程间通信都是常规状态下的工作模式,对于异常情况下的工作模式,需要用信号的方式来通知进程。
任何时候都可以发送信号来通知进程,一旦有信号产生,有以下几种用户对信号的处理方式:
1.执⾏默认操作。Linux 对每种信号都规定了默认操作,如SIGTERM 信号为终⽌进程。
2.捕捉信号。我们可以为信号定义⼀个信号处理函数。当信号发⽣时,我们就执⾏相应的信号处理函数。
3.忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。但有两个信号是应⽤进程⽆法捕捉和忽略的,即 SIGKILL 和 SEGSTOP ,它们⽤于在任何时候中断或结束某⼀进程。
Socket
Socket既可以实现同主机上进程间通信,也可以实现跨网络与不同主机上的进程间通信。
创建socket系统调用:
int socket(int domain, int type, int protocal)
domain 参数⽤来指定协议族,⽐如 AF_INET ⽤于 IPV4、AF_INET6 ⽤于 IPV6、AF_LOCAL/AF_UNIX ⽤于本机;
type 参数⽤来指定通信特性,⽐如 SOCK_STREAM 表示的是字节流,对应 TCP、SOCK_DGRAM 表示的是数据报,对应 UDP、SOCK_RAW 表示的是原始套接字;
protocal 参数原本是⽤来指定通信协议的,但现在基本废弃。因为协议已经通过前⾯两个参数指定完成,protocol ⽬前⼀般写成 0 即可。
根据创建 socket 类型的不同,通信的⽅式也就不同:
实现 TCP 字节流通信: socket 类型是 AF_INET 和 SOCK_STREAM;
实现 UDP 数据报通信:socket 类型是 AF_INET 和 SOCK_DGRAM;
实现本地进程间通信: 本地字节流 socket 类型是 AF_LOCAL 和 SOCK_STREAM,本地数据报 socket 类型是 AF_LOCAL 和 SOCK_DGRAM。另外,AF_UNIX 和 AF_LOCAL 是等价的,所以
AF_UNIX 也属于本地 socket。