进程间通信
简介
最简单的进程间通信可以通过文件共享来实现,在同一台机器上,一般的方式有:管道,消息队列,共享内存,信号量,在不同的机器上一般用套接字。
管道
父子进程之间不共享数据段和堆栈段,有血缘关系的进程之间用无名管道就可以通信,一般进程之间使用有名管道进行通信。管道是一种单向传输机制,
管道所传输的是无格式的字节流,且缓冲区大小有限制。
调用pipe(int fd[2])函数创建一个管道文件,这个王稳健存在于创建他的进程中,因此由这个进程再创建一个子进程与之通信。管道的数据只能从fd[1]写到fd[0]。
但是,linux进城创建是调用fork函数的,子进程与父进程共享代码段,但有独立的数据段和堆栈,子进程只执行fork之后的代码。对于有些情况,在fork之前的print会被打印两次,并不是代码执行了两次,而是print没有遇到\n,所以数据存在了缓冲区,fork的时候子进程复制了父进程的缓冲区数据段,最终打印的时候也会打出来一个。
过程
创建管道
子进程执行写操作
父进程执行读操作
解释
无名管道只是单向传递,无论是上述子写给父,还是父写给子,都必须关闭另一个方向的管道文件描述符。
这又是fork的一个点。还记得在守护进程中的有一步是关闭所有文件描述符,否则可能导致文件系统无法正常关闭。fork在调用pipe函数之后,父子进程拥有相同的文件描述符。就好像每一个文件描述符都执行了dup函数。父子进程的每个打开的文件描述符指向相同的文件表中的一项。
所以,如果父子进程在通信时不关闭多余的文件描述符,pipe【0】和pipe【1】的引用计数都是2。这里假设父进程读、子进程写,那么父进程就会后于子进程退出(合理的顺序)。子进程退出后,pipe【1】的引用计数仍为1(父进程自己的引用)。此时,父进程去读,pipe【1】文件描述符打开,但是没有进程会向pipe【1】中写入数据,父进程就会阻塞。如果pipe【1】文件描述符关闭,再次read就会返回0,而不是阻塞,就好比读到了文件的末尾。因此,需要关闭另一个方向的管道文件描述符,否则就会导致父进程死锁。
管道需要考虑同步问题,多个子进程连接同一个父进程。
有名管道
区别
介绍
有名管道提供一个文件路径,本身其实是一个FIFO文件,而不像无名管道是两个文件。
特点
1.用于不相关的进程之间通信。
2.对于文件系统来说是可见的。
3.严格的先入先出规则,从开始处读取数据,写入到管道文件的尾部。
函数原型
过程
指明FIFO文件位置
在读端创建新的FIFO文件
读
实现读写程序的异步,而非线性。
可以通过死循环,读返回0说明空,读到说明有。可以用select的思想,只有read返回>0的数,才响应这个读短的请求。
写
总结
阻塞可能是被其他读阻塞,也可能是被写阻塞。
消息队列
介绍
类似于无名管道,是一个内核中的队列,以消息链表的方式存储。
函数原型
双方可以共用一个已经存在的队列或者一个创建一个引用。
过程分析
对比
优点
1.消息队列可以独立于发送和接受进程存在,避免了同步命名管道打开关闭的困难。
2.多个客户端可以同时发消息,不需要线程本身实现同步。因为消息msg_st的msgtype可以制定不同的客户端。
3.接收端也可以选择性的接受。
缺点:快被淘汰了
共享内存
概念
两个进程访问俄一个逻辑内存,共享内存一般在同一个物理段中。进程可以吧共享内存连接到进程的地址空间即可。
需要其他的同步机制
函数原型
代码详解
整体思路
读端创建共享内存,然后把这段内存的地址链接到自己的进程地址空间。
写端取得共享内存地址,把他链接到自己的进程地址空间。
共享内存通过一个标志位实现同步,类似于但生产者–消费者模型。
读端:consumer
写端:producer
优点和缺点
这个程序只实现了读—写同步,没有实现读—读同步,需要设置wirtten标志位的值为写入的生产者进程数量。
而且对共享内存这个标志位的操作不是原子操作,无法保证有效读取。
这就引出了下一种方式:信号量。
信号量
简介
多线程信号量是POSIX信号量,多进程是SYSTEM V信号量。二者本质上都是用户态下进程可使用的信号量。
函数原型
semget
semop
semctl
应用
结合共享内存,解决不同步的内存读写问题。
代码详解
共享内存的部分省略
reader
writer
ipcs命令
用于查看系统的管道,消息队列,共享内存,信号量等状态。
-a:所有
-q:消息队列
-s:信号量
-m:共享内存
-u:当前使用状况
-l:系统限额
总结
这些都是长连接的例子,后面会介绍HTTP短连接。