进程间通信的目的: 数据传输、资源共享、通知事件发生(例如子进程退出时要向父进程发送SIGCHLD信号通知父进程)、进程控制等
几种进程间通信方式
由于进程之间的独立性 — 每个进程都有自己的虚拟地址空间, 操作的是自己的地址, 因此进程之间无法进行直接通信;那么操作系统是如何完成进程间通信的?
给多个进程之间提供一个大家都能访问到的通信介质 –内存,并且操作系统在提供进程间通信方式的时候也根据通信场景的不同提供不同的方式:管道(匿名管道和命名管道)、共享内存、消息队列、信号量
管道
我们把从一个进程连接到另一个进程的数据流称为“管道”,管道提供字节流传输服务(可靠的,有序的,基于连接的一种灵活性比较高的传输服务)管道本质是内核中的一块缓冲区,并非无限大,若多个进程可以访问到同一个缓冲区,就可以实现通信。管道分为匿名管道和命名管道
管道是一个半双工通信(可以选择方向的单向传输),要么读,要么写,如果不使用哪一端,则关闭
管道生命周期随进程,所有打开管道的进程退出,资源就会被释放
匿名管道
内核中的这块缓冲区没有具体的标识符,只能用于具有亲缘关系(父子进程)的进程间通信
#include <unistd.h>
int pipe(int pipefd[2]); //创建匿名管道
返回值: 成功返回0,失败返回-1
函数参数:
pipefd: 文件描述符数组,其中pipefd[0]表示读端,pipefd[1]表示写端
管道的读写特性:
1.若管道中没有数据,则调用read读取数据会阻塞
2.若管道中数据写满 ,则调用write写入数据会阻塞
阻塞: 为了完成一个功能,发起调用,若当前不具备完成条件,则一直等待
3.若管道的所有读端pipefd[0]被关闭,则继续调用write会产生异常导致进程退出(因为读端都被关闭了,再写入数据也没有意义,也不会读)
4.若管道的所有写端pipefd[1]被关闭,继续调用read,当read读完管道中的数据不再阻塞,而是退出
管道符的实现
ps -ef进程将输出结果重定向到管道写入端,grep ssh进程重定向不再从标准输入获得数据,而是从管道中读取;但是要注意,grep过滤了结果后并不会退出,因此需要在ps进程退出后关闭所有的写端,告知grep进程不会再写入数据了,grep读取完管道中的数据就会退出
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
int pipefd[2] = {-1};
if (pipe(pipefd) < 0)
{
perror("pipe error");
return -1;
}
pid_t ps_pid = fork();
if (ps_pid == 0)
{
//ps子进程
dup2(pipefd[1], 1);//将标准输出重定向到管道写入端,向1写入数据就相当于向管道写入数据
execlp("ps", "ps", "-ef", NULL);
exit(0);
}
pid_t grep_pid = fork();
if (grep_pid == 0)
{
//grep子进程
close(pipefd[1]);//关闭写端;ps进程一旦退出,所有的写端被关闭,grep读完数据后返回0不再阻塞
dup2(pipefd[0], 0);//将标准输入重定向到管道读取端,从0读取数据就相当于从管道读取数据
execlp("grep", "grep", "ssh", NULL);
exit(0);
}
close(pipefd[0]);
close(pipefd[1]); //父进程的读写端也要关闭
waitpid(ps_pid, NULL, 0);
waitpid(grep_pid, NULL, 0);
return 0;
}
运行结果:
命名管道
内核中的这块缓冲区具有标识符,这个标识符是一个可见于文件系统的管道文件,命名管道是一种特殊类型的文件,能够被其他进程找到,可用于同一主机上的任意进程间通信
多个进程通过命名管道通信是通过打开命名管道文件访问同一块内核中的缓冲区实现通信的
命令行下创建:
$ mkfifo filename
使用指定的文件名创建FIFO(也称为"命名管道")
函数创建:
int mkfifo(const char *pathname, mode_t mode);
返回值: 成功返回0;发生错误返回-1
pathname:创建管道文件名称
mode: 指定FIFO的权限
它是由进程的umask设定的,通常的方式是:mode& ~umask
打开命名管道文件要用open:
若文件以只读打开,则会阻塞,直到文件被以写的方式打开
若文件以只写打开,则会阻塞,直到文件被以读的方式打开
管道自带同步与互斥
同步:通过条件判断实现对临界资源操作的合理性 —管道中没有数据则会read阻塞/数据满了则会write阻塞
互斥:通过唯一访问实现对临界资源操作的安全性 —管道的读写操作在PIPE_BUF大小以内保证操作的原子性
临界资源: 大家都能访问到的资源
原子操作: 不能被打断的操作。指的是操作要么一次完成,要么就不做