💥进程具有独立性,所以不同进程间通信该如何通信呢? 进程间进行通信,首先需要让不同的进程看到同一份资源(某种形式的内存空间),并且因为进程具有独立性的特性,一个进程不能在另一个进程中操作某种资源,所以该资源的提供者不能是进程,只能是操作系统,这也是进程间通信的核心。
进程间通信有两类通信方案——本地通信和网络通信;本地通信就是指在同一台主机同一个操作系统,不同进程间进行通信;后文讲的都是本地通信,网络通信等后续再学习。
Linux进程间通信可以分为以下几种分类:
需要根据具体的业务需求选择适合的进程间通信方式,每种通信方式都有其特点和适用场景。
管道是一种最简单的进程间通信方式,可以通过创建一个管道文件来实现两个进程之间的通信。读进程从管道中读取数据,写进程向管道中写入数据。管道只能在具有父子关系的进程之间使用。
父进程创建子进程后,子进程会拷贝父进程的task_struct
,同样父进程在创建子进程之前打开了一个文件,子进程也会拷贝该文件的struct file
,此时两个进程就指向同一个操作系统提供给该文件的内核缓冲区,所以就可以通过的该文件的内核缓冲区进行通信了:
管道分为匿名管道pipe和命名管道。
在Linux中,匿名管道(anonymous pipe)是一种常用的进程间通信机制。它的原理与上图类似,是通过pipe系统调用来创建一个管道文件,该文件与上图文件不同,没有inode,也不需要写入磁盘,仅仅用来给父子进程间进行通信,可以说是一次性使用的,没有名字,所以称为匿名管道。
创建匿名管道的思路如下:
父进程使用读和写两种方式打开管道文件,虽然是一个管道文件,但是文件描述符有两个并且
struct file
也有两个,一个指向读,一个指向写,但是都指向同一个文件内核缓冲区
子进程拷贝父进程的
task_struct
,这也就是为什么父进程要先将管道创建出来,再创建子进程并且要将读写文件同时打开,是为了给子进程继承
如果不关文件描述符,会造成文件描述符泄露并且可能会误操作,所以建议关闭
管道的通信是单向的,即父进程或子进程不能对一个管道同时读和写,只能选择一个,父进程读,子进程就写;子进程读,父进程就写。
在Linux系统中,可以使用以下系统调用接口创建匿名管道:
#include <unistd.h>
int pipe(int pipefd[2]);
该函数会创建一个管道,并将相应的读取和写入文件描述符存储在pipefd
数组中。pipefd[0]
表示读取端文件描述符,pipefd[1]
表示写入端文件描述符。
使用示例:
#include <unistd.h>
#include <stdio.h>
int main() {
int pipefd[2];
int ret = pipe(pipefd);
if (ret == -1) {
perror("pipe");
return 1;
}
printf("读取端文件描述符:%d\n", pipefd[0]);
printf("写入端文件描述符:%d\n", pipefd[1]);
return 0;
}
运行以上程序,会输出管道的读取和写入端文件描述符。
需要注意的是,创建管道时需要保证父子进程之间的通信,因为管道只能在具有亲缘关系的进程之间使用。通常情况下,我们会调用
pipe
之后创建子进程,将管道传递给子进程。
以下是一个通过匿名管道简单实现进程间通信的代码:
#include<iostream>
#include<string>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
//子进程写,父进程读
int main()
{
//1.创建管道
int pipefd[2];
int n = pipe(pipefd);
if(n == -1)
{
std::cerr<<"pipe error"<<std::endl;
return 1;
}
//2.创建子进程
pid_t id = fork();
//3.关闭不需要的文件描述符fd
if(id < 0)
{
std::cerr<<"fork error"<<std::endl;
return 1;
}
else if(id == 0)//子进程
{
::close(pipefd[0]);//关闭读端
//向管道写入数据
std::string message = "hello, I am child process.";
::write(pipefd[1],message.c_str(),message.size());
::exit(0);
}
else//父进程
{
::close(pipefd[1]);//关闭写端
char buffer[1024];
ssize_t count = ::read(pipefd[0],buffer,1024);
if(count > 0)
{
buffer[count] = 0;
std::cout<<"child->father,message:"<<buffer<<std::endl;
}
pid_t rid = waitpid(id,nullptr,0);
std::cout<<"father wait child success:"<<rid<<std::endl;
}
return 0;
}
将该代码编译为可执行文件后运行结果如下:
在进程间通信时,可能会出现以下场景:
管道是有上限,在某些系统下它只能写入64kb的内容,所以当管道写满了,write就会阻塞
匿名管道特性:
当父子进程都关闭了,管道就会被操作系统回收,和文件类似
只能有一方写入一方读取,不能同时读取和写入