前提:
进程具有独立性,要通信就是要让它们:看到同一份资源(某种形式的内存空间,操作系统提供)
本地通信:同一台主机,OS,用系统调用通信。标准:systemV。
System V IPC(进程通信)
首创三种经典IPC机制:
System V 消息队列
System V 共享内存
System V 信号量
最常用的通信方式:管道
分为:
匿名管道pipe
命名管道
二。管道
将两个进程的数据流连起来。
命令行种创建的管道,几个进程是兄弟关系
命令后加&是进入后端执行。图中两个进程同一父进程,pid不同,ppid相同
1.匿名管道
子进程创建时
files_struct也会拷贝-》是进程文件关系的结构,偏进程。
1.struct file也是拷贝的,独立-->进程对文件修改的位置指针(位置)是存在struct file里的,不同进程可以访问同一文件的不同位置,所以要独立。
2.同一文件的inode和文件内核缓冲区不拷贝-->属于文件,无需独立修改。
3.IPC本质是让不同进程看到同一份资源(文件内核级缓冲区是操作系统创建的,不是进程new出来的)
什么时候释放内核缓冲区?
文件内核缓冲区也存在引用计数。
进程间通信的文件不需要将文件刷新到磁盘(多个进程同时对内核缓冲区写也会乱)-->创建一个只会存在内存的文件,其他一样--->管道
1.基于内存级文件的通信方式:只能单向通信,一个读一个写
需要先创建管道,后创建进程,父子进程各关闭一个通道(不关闭操作上没问题,但是fd泄露资源浪费,容易误操作),因为创建管道的时候没有指定文件,需要继承看到同一文件。
需要同时创建读写的文件描述符,管道struct_file内flag读/写子进程继承资源也只能读/写。
//实现
#include <stdio.h>
#include <iostream>
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <cstdlib>
int main()
{
int fds[2] = {0};//一般fd[0]读,fd[1]写
int n = pipe(fds);
if (n)
{
std::cerr << "pipe" << std::endl;
return 1;
}
pid_t pid = fork();
if (pid < 0)
{
std::cerr << "fork erro" << std::endl;
}
else if (pid == 0)
{
// 子
//关闭读管道
::close(fds[0]);//系统调用,非库函数,加个标明好看
int cot=0;
std::string message="hello ";
message+=std::to_string(getpid());//整数转字符
while(true)
{
::write(fds[1],message.c_str(),message.size());
sleep(1);
}
exit(0);
}
else
{
// 关闭写管道
::close(fds[1]);
char buf[1024]={0};
while(true)
{
size_t n=::read(fds[0],buf,1024);
std::cout<<buf<<std::endl;
sleep(1);
}
pid_t rid = waitpid(pid, nullptr, 0);
std::cout<<"child return"<<std::endl;
}
return 0;
}
问:普通文件的struct_file内也有flag吗?
在 Linux 内核中,普通文件(regular file)的 struct file
结构体确实包含 f_flags
字段,但它与文件的打开方式(如 O_RDONLY
、O_NONBLOCK
等)相关,而不是文件本身的属性。以下是关键细节分析:struct file
中的 f_flags
- 定义位置:
include/linux/fs.h
(内核源码)struct file { // ... unsigned int f_flags; // 文件打开时的标志(如 O_RDWR、O_NONBLOCK) fmode_t f_mode; // 文件模式(FMODE_READ、FMODE_WRITE) // ... };
- 作用:
- 记录进程打开该文件时指定的标志(通过
open()
系统调用传递的flags
参数)。 - 例如:
O_RDONLY
(只读)、O_NONBLOCK
(非阻塞)、O_SYNC
(同步写入)等。
- 记录进程打开该文件时指定的标志(通过
- 与文件本身无关:
- 这些标志是进程级别的,同一个文件被不同进程打开时,各自的
struct file
可能有不同的f_flags
。
- 这些标志是进程级别的,同一个文件被不同进程打开时,各自的
1.2验证接口。
问1:为什么不能错误cerr打印的程序输出>重定向到文件里
在 Linux/C++ 中,cerr
(标准错误流,对应文件描述符 stderr
,fd=2)和 cout
(标准输出流,对应 stdout
,fd=1)默认都会输出到终端,但它们是独立的流。如果你直接用 >
重定向,它默认只会重定向 stdout
(fd=1),而 cerr
的内容仍然会打印到终端。
将标准错误重定向的方法:
这里重定向的>一定要紧挨着两边文字
错误和输出一起重定向的方法:
原理是将1重定向到errlog,再把2重定向到1里,就是2->1->errlog
底层是打开errlog,将errlog(3)写到1里,将1写到2里。
作用:以后可以将错误写到日志里。
前置知识:
管道的:
- 读操作的行为:
- 成功读取:数据从缓冲区移除,释放空间供后续写入(管道有上限)。
- 读完清空:如果所有数据被读取,缓冲区变为空,后续读操作会阻塞(默认)或返回
EOF
(写端已关闭)。
问题:两个进程对同一文件改,一个改了一半一个直接读,看到的资源和已经有的资源就不一致。
怎么保护资源:
读完进程阻塞可以让保护资源,让读取的不为空,让写的不覆盖。
验证4:
管道特性:
1.操作: 1.单向通信
2.自带同步互斥等资源保护机制(读完写,写完再读)
2.生命周期:随进程
3.范围:不仅父子,只要能看到同一份资源,爷孙,兄弟也行。有血缘关系的IPC通信
6.原子性,将少于一定大小的写入管道必须是原子的,不用担心写一半被读走
管道的使用场景
1.进程池:父进程写,子进程读。只有父进程写数据(命令),子进程才不会read阻塞。
(类似条件变量)
(理解ps ajx|grep h; |就是匿名管道,依次创建兄弟进程,左边的输出重定向到匿名管道的输出,右边读取)
进程池(Process Pool) 就像是一个 “工人团队”,它的作用是:
- 提前创建好多个进程(工人),放在池子里待命。
- 有任务来了,直接派给空闲的进程处理,不用临时创建。
- 任务完成后,进程回到池子里,等待下一个任务。
为什么用进程池?
- ✅ 省时间:不用反复创建/销毁进程(创建进程很耗资源)。
- ✅ 控数量:避免同时运行太多进程把电脑卡死(比如只允许5个进程同时干活)。
- ✅ 易管理:任务排队,谁空闲谁干活,高效利用资源。
实践:
命名管道
一.为什么要有命名管道?
匿名管道标识同一块区域的方法是父子进程的继承,要想让非父子进程通信可以用文件名标识同一块区域。
二.命名管道比普通文件好在哪
普通文件是在磁盘上的,对它进行读写,其需要刷新到磁盘上,IO消耗大量资源,命名管道只需要在磁盘上存储个空文件,能通过路径找到它,真正区域在内存中。