进程间通信
目的:多个进程协同,去完成一件事
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并知道他的状态改变了
定义
进程将自己的数据交给另一个进程
怎么做到的?
进程是有独立性的,你不可能直接读取另一个进程的数据,那是怎么做到的?
一般规律
所以内存中就有专门交换数据的空间,这块空间要由操作系统来提供(一般由OS提供
具体做法
因为OS提供的空间有多种方式,所以就有了不同的通信方式
- 管道(匿名,命名)
- 共享内存
- 消息队列
- 信号量
文件描述符
我们要通过文件描述符来学习管道,先来复习一下
- 文件描述符是整数,有默认的三个(0、1、2)分别代表标准输入(键盘)、标准输出(显示器)、标准错误(显示器)
- file_struct 是个结构体 用于存储各个文件,其下标就是文件描述符
- 进程和文件之间,通过进程内部的*files 来指向file_struct,来管理文件
- 创建子进程的时候,需要将父进程的file_struct也给子进程一份,但父进程管理的文件不用拷贝给子进程,但内部的指针指向不变(仍指向原本的文件,是浅拷贝)
通过同一文件描述符指向同一个文件来实现在 不同进程中修改同一个文件,得到同一份资源的方式 就是管道
(同一个文件写入和读取是一个缓冲区
并且管道是单向管道
管道
了解完基本概念,我们就通过父子进程来进一步学习
让父进程来读取,子进程来写入
关掉父进程的写端和子进程的读端 这就实现了单向通信
这里解释了为什么进程要用读方式和写方式同时打开一个文件
并且由于struct file是由引用计数来决定是否释放的(记数为0再释放)
所以不用担心关闭端口会释放掉这个指针
pipe():匿名管道
操作系统提供了这个函数来进行管道通信
#include<unistd.h>
int pipe(int pipefd[2])
//pipefd是个输出型参数
//pipe创建了一个不需要向磁盘中刷新并且在磁盘中不存在的文件
//只能通过文件描述符找到,是个匿名文件(即匿名管道)
他的原理就是创建子进程继承父进程的资源,来实现共享资源
同理 父进程和兄弟进程,爷孙进程也可以通信(即在有血缘关系的进程间通信
代码展示
看一下用c是怎么使用管道的
#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 函数格式化字符串,将消息、进程 ID 和计数写入缓冲区
snprintf(buffer, sizeof(buffer), "message: %s, pid: %d, count: %d\n", str, pid, cnt);
// 将格式化后的字符串写入文件描述符 wfd,写入的字节数为字符串的长度
write(wfd, buffer, strlen(buffer));
cnt++;
sleep(1);
}
}
void reader(int rfd)
{
char buffer[1024];
while(1)
{
// 从文件描述符 rfd 中读取数据,最多读取 sizeof(buffer)-1 字节到缓冲区 buffer
ssize_t n = read(rfd, buffer, sizeof(buffer)-1);
// 强制转换并忽略返回值 n,表示不关心读取的字节数
(void)n;
printf("father get a message: %s", buffer);
}
}
int main()
{
// 1.
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*/); // 3, 4
//默认第一个是读,第二个是写
// 2.
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;
}
小结
下一篇文章继续介绍管道