目录
- 1.进程间通信目的
- 2. 什么是进程间通信
- 3. 进程间通信发展
- 4.如何进行进程间通信
- 4.1 一般规律
- 4.2 具体做法
- 5.进程间通信分类
- 6. 管道
- 匿名管道
- 用fork来共享管道原理
- 站在文件描述符角度-深度理解管道
- 验证管道通信代码
1.进程间通信目的
两个进程之间,可以进行“数据”的直接传递吗?不能!进程具有独立性
那么为什么需要进程间通信呢?
- 数据传输:一个进程需要将它的数据发送给另一个进程
在很多场景之间,两个进程需要进行数据传输,比如:A进程负责获取网络中的数据,但是它并不会去处理这份数据,而B进程为只负责处理数据,这样就可以实现多进程的并发
- 资源共享:多个进程之间共享同样的资源。
有一部分进程需要进行共享一份资源,以便于两进程共同访问共同完成一件事情,比如说:我们玩游戏,多玩家在同一局游戏中,我们看到的数据都是一模一样的,我们的每个用户也就是一个进程,我们对于地图资源进行的是共享
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
2. 什么是进程间通信
-
进程间通信(Inter-process Communication,IPC)是指在多道程序环境下,进程间进行数据交换和信息传递的一种机制或方法。在现代操作系统中,进程是系统资源分配的基本单位,不同进程之间需要相互合作和通信,才能完成各种任务。进程间通信是实现进程间协作的重要手段。
-
常用的进程间通信方式有管道、消息队列、共享内存、信号量和套接字等。管道是一种半双工的通信方式,消息队列是一种异步通信方式,共享内存是一种高效的通信方式,信号量是一种用于同步进程的通信方式,套接字是一种网络通信方式。
3. 进程间通信发展
进程间通信是指不同进程之间的数据交换和共享,是操作系统中非常重要的一个概念。随着计算机技术的发展,进程间通信也经历了多个发展阶段。
-
第一阶段是管道,它是Unix系统中最早的进程间通信方式,可以在两个相关进程之间传递数据。
-
第二阶段是信号,它是Unix系统中另一种进程间通信方式,用于通知接收进程某个事件已经发生。
-
第三阶段是共享内存,它可以允许多个进程访问同一块物理内存,从而实现数据共享。
-
第四阶段是消息队列,它可以在不同进程之间传递消息,实现进程间通信。
-
第五阶段是套接字,它可以在不同主机之间传递数据,并且支持多种通信协议。
-
第六阶段是远程过程调用(RPC),它允许程序员在不同计算机上运行的程序之间进行交互。
4.如何进行进程间通信
4.1 一般规律
- 进程间通信的本质:让不同的进程,看到同一份资源(一般都是由OS提供)
进程间通信需要一个另外的交换数据的空间(内存)
此交换空间不能由通信双方任何一方进行提供,如果提供了空间,让对方进程来进行访问,这就有违背进程独立性,所以我们需要另一个额外空间进行数据的传输,这种事情会有OS来进行操作
4.2 具体做法
OS提供的“空间”有不同的样式,这决定了不同的通信方式
常见通信方式:
- 管道(匿名和命名)
- 共享内存
- 消息队列
- 信号量
5.进程间通信分类
🍁管道
- 匿名管道pipe
- 命名管道
🍁System V IPC
System V IPC是指System V操作系统提供的进程间通信(IPC)机制,它允许进程在不同的计算机系统之间进行通信和同步。System V IPC主要包括三种类型的通信机制:消息队列、共享内存和信号量。
- System V 消息队列
- System V 共享内存
- System V 信号量
🍁POSIX IPC
POSIX IPC 是指可移植操作系统接口(Portable Operating System Interface,缩写为POSIX)中的进程间通信(Inter-Process Communication,缩写为IPC)机制。它提供了一组标准的函数和数据结构,用于在不同的进程间进行数据交换和同步操作。常用的 POSIX IPC 包括消息队列、共享内存、信号量等。使用 POSIX IPC 可以实现不同进程之间的数据共享和通信,进而实现更加复杂的多进程协作模式。
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
6. 管道
- 管道是Unix中最古老的进程间通信的形式。
- 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
管道是一种进程间通信机制,它可以用来在不同进程间传递数据。在底层实现上,管道其实是一种内核缓冲区,它由两个文件描述符(文件句柄)表示,一个用于读取,一个用于写入。当一个进程向管道中写入数据时,数据会被存储到内核缓冲区中,另一个进程则可以从同一个管道中读取这些数据。
管道底层实现的关键是通过操作系统提供的内核缓冲区实现进程间的数据传输。管道有两个缓冲区,分别用于存储读取和写入的数据。当写入进程向管道中写入数据时,数据会被存储到写入缓冲区中。如果写入缓冲区已满,则写入进程会被阻塞,直到有足够的空间来存储数据。当读取进程从管道中读取数据时,数据会从读取缓冲区中读取。如果读取缓冲区为空,则读取进程也会被阻塞,直到有新的数据可供读取。
需要注意的是,管道具有单向性,即数据只能从一个方向流动。如果需要双向通信,则需要创建两个管道。同时,在管道中传递的数据是没有结构的字节流,因此需要约定好传递的数据格式以及数据长度。
匿名管道
#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码
这个函数让我们不需要向磁盘中刷新并且磁盘中并不存在文件,其本质就是会创建一个内存级的文件,这个文件没有名字,为匿名文件(管道)
匿名管道如何做到让不同的进程看到同一份资源?
就是利用了创建子进程,子进程会创建父进程的相关属性信息,匿名管道可以(只能)进行具有血缘关系的进程,进行进程间通信,常用于父子
实例代码
例子:从键盘读取数据,写入管道,读取管道,写到屏幕
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int fds[2];
char buf[100];
int len;
if (pipe(fds) == -1)
perror("make pipe"), exit(1);
// read from stdin
while (fgets(buf, 100, stdin)) {
len = strlen(buf);
// write into pipe
if (write(fds[1], buf, len) != len) {
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
// read from pipe
if ((len = read(fds[0], buf, 100)) == -1) {
perror("read from pipe");
break;
}
// write to stdout
if (write(1, buf, len) != len) {
perror("write to stdout");
break;
}
}
}
用fork来共享管道原理
站在文件描述符角度-深度理解管道
为什么父进程最开始就要按照r和w打开同一个文件呢?
这是因为管道只允许一个人读,一个人写。
验证管道通信代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
void writer(int wfd)
{
const char *str ="hello father, I am child";
char buffer[128];
int cnt = 0;
pid_t pid = getpid();
while(1)
{
snprintf(buffer, sizeof(buffer),"message: %s,pid: %d,count: %d \n", str, pid, cnt);
write(wfd,buffer,strlen(buffer)+1);
cnt++;
sleep(1);
}
}
void reader(int rfd)
{
char buffer[1024];
while(1)
{
ssize_t n = read(rfd, buffer, sizeof(buffer)-1);
(void)n; //读取但是没有用
printf("father get a message: %s", buffer);
}
}
int main()
{
int pipefd[2];
int n = pipe(pipefd);
if(n<0) return 1;
printf("pipefd[0]: %d, pipefd[1]: %d\n", pipefd[0]/*read*/, pipefd[1])/*write*/; //创建管道
//父子进程
pid_t id=fork();
if(id == 0)
{
//child 实现W 关闭读取保留写入
close(pipefd[0]);
writer(pipefd[1]);
exit(0);
}
//father实现r
close(pipefd[1]);
reader(pipefd[0]);
wait(NULL);
return 0;
}
这里可以在左边看到有两个进程同时运行,初步实现管道通信,子进程可以写给父程序