每个进程间用户空间是独立的,但内核空间是共享的,因此进程间通信要借助内核。
1、管道管道就是内核里面的一串缓存,管道传输的数据是无格式的字节流且大小受限。不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则。
管道通信方式效率低,不适合进程间频繁交换数据,这是因为一个进程写入数据后,必须等到管道中数据被读取了,命令才可以正常退出。优点是简单。
匿名管道ps auxf | grep mysql,其中|就是一个管道,将前一个命令的输出作为后一个命令的输入,管道传输数据时单向的。匿名管道的生命周期随进程创建而创建,随进程结束而销毁。
对于匿名管道,它的通信范围是存在父子关系的进程,因为没有管道文件,只能通过fork来复制父进程fd文件描述符。
在匿名管道的创建背后原理是,系统创建一个匿名管道,并返回两个描述符,一个是管道读取端描述符fd[0],一个是写入端描述符fd[1]。以上两个描述符都存在同一个进程中,如果两个进程想建立通信,就要通过创建子进程,子进程会复制父进程的文件描述符,从而实现通信。
命名管道也叫FIFO,数据是先进先出的传输方式,通过mkfifo命令创建,mkfifo mypipe。
对于命名管道,他可以在不相关的进程间进行通信,因为命名管道提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,皆可以相互通信。
2、消息队列消息队列是保存在内核中的消息链表,可以解决管道通信效率低的问题。发送的数据是自定义的消息体,消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列一直存在。
缺点:
1、消息队列不适合比较大数据的传输,因为内核中每个消息体有一个最大长度,并且队列也有最大长度限制
2、消息队列通信过程中,存在用户态和内核态之间的数据拷贝开销。
3、共享内存共享内存可以解决消息队列通信方式用户态和内核态数据拷贝开销。共享内存机制,就是拿出一块虚拟地址空间,映射到相同的物理内存中。这样这个进程写入的东西,另一个进程马上看到,不需要拷贝,大大提高进程间通信速度。
缺点:多个进程同时修改同一共享内存,会带来冲突。
4、信号量信号量保证共享的资源,在任意时刻只能被一个进程访问。
信号量是一个整型计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。
信号量表示资源的数量,控制信号量有两种原子操作:
- P操作,用在进入共享资源之前,这个操作会将信号量减一,如果相减后信号量<0,表明当前资源被占用,进程需阻塞等待;如果相减后信号量>=0,表示还有资源可用,进程可以正常执行。
- V操作,用在离开共享资源之后,这个操作会将信号量加一,相加后信号量<=0,表明当前有阻塞的进程,于是会将该进程唤醒;相加后信号量>0,表明当前没有阻塞的线程。
信号量初始化为1,代表互斥信号量,保证共享数据在任何时刻只能被一个进程访问。
信号量初始化为0,代表同步信号量,保证进程A在进程B之前执行。
5、信号对于异常情况下的工作模式,需要利用信号的方式通知进程,在Linux操作系统中,提供了几十种信号。信号的来源主要有硬件来源(键盘Ctrl + C)和软件来源(kill命令)。
信号是进程间通信机制中唯一的异步通信机制,一旦有信号产生,进程对信号的处理方式主要有三种:
- 执行默认操作
- 捕捉信号,为信号定义一个信号处理函数,信号发生时,执行信号处理函数
- 忽略信号
要想实现跨网络与不同主机上的进程之间通信,就需要Socket通信。另外socket也可以用于同主机上进程通信。
socket的创建:int socket(int domain, int type, int protocal)
domain用于指定协议族,type用于指定通信特性(如字节流,报文),protocal用于指定通信协议。创建的socket不同,通信方式就不同。
比如:针对TCP协议的socket编程模型
- 服务端和客户端初始化 socket,得到文件描述符;
- 服务端调用 bind,将绑定在 IP 地址和端口;
- 服务端调用 listen,进行监听;
- 服务端调用 accept,等待客户端连接;
- 客户端调用 connect,向服务器端的地址和端口发起连接请求;
- 服务端 accept 返回用于传输的 socket 的文件描述符;
- 客户端调用 write 写入数据;服务端调用 read 读取数据;
- 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。
这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。



