进程间通信分类

发布于:2025-05-14 ⋅ 阅读:(8) ⋅ 点赞:(0)

前提:

进程具有独立性,要通信就是要让它们:看到同一份资源(某种形式的内存空间,操作系统提供)

本地通信:同一台主机,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_RDONLYO_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)​​ 就像是一个 ​​“工人团队”​​,它的作用是:

  1. ​​提前创建好多个进程(工人)​​,放在池子里待命。
  2. ​​有任务来了,直接派给空闲的进程处理​​,不用临时创建。
  3. ​​任务完成后,进程回到池子里​​,等待下一个任务。

​​为什么用进程池?​​

  • ✅ ​​省时间​​:不用反复创建/销毁进程(创建进程很耗资源)。
  • ✅ ​​控数量​​:避免同时运行太多进程把电脑卡死(比如只允许5个进程同时干活)。
  • ✅ ​​易管理​​:任务排队,谁空闲谁干活,高效利用资源。

实践:

linux: 关于课程的代码实践

命名管道

一.为什么要有命名管道?

匿名管道标识同一块区域的方法是父子进程的继承,要想让非父子进程通信可以用文件名标识同一块区域。

二.命名管道比普通文件好在哪

普通文件是在磁盘上的,对它进行读写,其需要刷新到磁盘上,IO消耗大量资源,命名管道只需要在磁盘上存储个空文件,能通过路径找到它,真正区域在内存中。


网站公告

今日签到

点亮在社区的每一天
去签到