【Linux】进程间通信及管道详细介绍(上)

news2024/9/24 14:33:05

前言
本节我们开始学习进程间通信相关的知识,并详细探讨一下管道,学习匿名管道和命名管道的原理和代码实现等相关操作…

目录

    • 1. 进程间通信背景
      • 1.1 进程通信的目的:
    • 2 管道的引入:
      • 2.1 匿名管道:
        • 2.1.1 匿名管道的原理:
        • 2.1.2 匿名管道的创建:
        • 2.1.3 总结管道的特点:
        • 2.1.4 管道读写规则:
        • 2.1.6 控制多个子进程(进程池):
    • 3 命名管道
      • 3.1 命名管道的创建
      • 3.2 匿名管道与命名管道的区别
      • 3.3 两个进程之间的通信
      • 3.4 命名管道的特点:

1. 进程间通信背景

我们知道进程是具独立性的。但是,相互之间还是需要进行一些信息交互,简称为 IPC (Inter - Process Communication)

1.1 进程通信的目的:

  1. 数据传输: 一个进程需要将它的数据发送给另一个进程。
  2. 资源共享: 多个进程之间共享同样的资源。 比如:共享内存库
  3. 通知事件: 一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。 比如:进程等待
  4. 进程控制: 有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

需要多进程进行协同处理一件事情(并发处理)。单纯的数据传输,一个进程想把数据发给另一个进程。多进程之间共享同样的资源。一个进程想让另一个进程做其他的事情,进程控制。

进程间通信的发展历史:

  • 管道 :最古老的进程间通信的形式
  • System V进程间通信 (用的非常少了,设计的非常重,更多的用来本地通信)
  • POSIX进程间通信 (设计的很轻,可以本地,可以做成网络,因为里面有套接字)

注意:我们学习的是 POSIX进程间通信

在这里插入图片描述

2 管道的引入:

刚学Linux时,就接触过
竖划线| 的 操作.

  • 管道是Unix中最古老的进程间通信的形式.
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道.

在这里插入图片描述

  • 1.进程间通信的前提条件:

  • 两个进程看到同一份资源才具备通信的条件
    1.通信之前,让不同的进程看到同一份资源(文件、内存块…)
    2.进程间通信,就是让两个进程如何看到同一份资源
    3.资源的不同,决定了,不同种类的通信方式!

  • 2.如何才能让两个进程看到同一份资源?:

  • 让两个进程同时看到磁盘上的同一份文件:

这种方法,要考虑CPU 和外设之间得到读写速度,效率太低了 因为通信是一个相对常规的操作,将数据刷到外设,再从外设上读取,效率太低了,

  • 让两个进程打开同一个文件:

重点注意:
管道只能单向通信。

管道是基于文件实现的,管道就是文件,两个进程之间通过文件来实现的。
进程间的通信,大部分都是基于内存级别的,不会刷新到磁盘里面,都是临时数据
进程退出,文件描述符会被关掉,但文件不一定会被关闭。

进程通信的核心思想:让两个进程获取到同一份资源

2.1 匿名管道:

2.1.1 匿名管道的原理:

在这里插入图片描述

  • 如何做到让不同的进程,看到同一份资源的呢?

fork让子进程继承—能够让具有血缘关系的进程进行进程间通信(同一文件Struct file)—常用于父子进程。。

  • struct file中有个引用计数,是指对该文件的引用数量,用于跟踪文件被多少个进程或内核对象所引用。
  • 父进程指向什么文件,子进程也指向什么文件。
  • 这也就是为什么,创建fork子进程之后,我们让父子printf打印的时候,父子进程都会向同一个显示器打印,因为它们俩都指向了同一个文件
  • Linux中可以通过特定的系统调用来判断文件是普通文件还是管道文件。
  • 自己读写数据时,就在这个文件对应的内存缓冲区里面来完成数据交互,我们把这个文件我们称之为管道

Linux系统设计的时候就设计成,如果是普通文件就往磁盘上写,如果是管道文件也往缓冲区里写,但是就
不再往磁盘上刷新了。如果是管道,就把它和对应的磁盘去掉关联。

  • Linux下一切皆文件,管道也是文件~
2.1.2 匿名管道的创建:

匿名管道主要用于父子进程之间的通信,用pipe接口来创建管道:

#include <unistd.h>
功能:创建匿名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端,是输出性参数
返回值:成功返回0,失败返回-1 错误代码
2.1.3 总结管道的特点:

在使用系统调用pipe()创建管道时,pipefd[0] ---------》管道读取端的文件描述符,pipefd[1] ---------》管道写入端的文件描述符。

  • 管道是用来进行具有血缘关系的进程做进程间通信的–常用于父子通信
  • 管道具有通过让进程间协调,提供了访问限制。(读不到了会阻塞)
  • 管道提供的是面向流式的通信服务,—面向字节流,协议
  • 管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的。
  • 管道的pipefd[0]和pipefd[1]的用途是固定的,读取端只能从pipefd[0]读取数据,而写入端只能向pipefd[1]写入数据
    在这里插入图片描述
    在这里插入图片描述
    代码演示:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(int argc, char *argv[]) 
{ 
    int pipefd[2]; 
    if (pipe(pipefd) == -1) 
      perror("make pipe"),exit(1);
  if (pid == 0) {
     close(pipefd[0]);
     write(pipefd[1], "hello", 5);
     close(pipefd[1]);
     exit(0);
}
     close(pipefd[1]);
    char buf[10] = {0};
    read(pipefd[0], buf, 10);
    printf("buf=%s\n", buf);
   return 0;
}

2.1.4 管道读写规则:

当父进程没有写入数据的时候,子进程在等!所以,父进程写入之后,子进程才能read到数据,子进程打印读取数据要以父进程的节奏为主!

  • 管道内部,当没有数据可读时, read 读端就必须阻塞等待

    — 等待管中有数据,否则无法执行后面的代码。
    ----read调用返回-1

  • 管道内部,如果数据被写满了,写端(write)就必须阻塞等待

    -----等待管中有空间,否则此时写入会覆盖之前的数据。
    ----- write调用返回-1

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性
.

管道特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

  • 管道提供流式服务 一般而言,进程退出,管道释放,所以管道的生命周期随进程

  • 一般而言,内核会对管道操作进行同步与互斥

  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
    在这里插入图片描述

父进程给子进程派发任务:
结合上述所学知识,就可以简单写一个通过通信管道父进程给子进程派发任务执行的代码了。

 #include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <cassert>

using namespace std;

// 父进程控制子进程

typedef void (*functor)();

vector<functor> functors; // 方法集合

//for debug
unordered_map<uint32_t, string> info;

void f1()
{
    cout << "这是一个处理日志的任务, 执行的进程 ID [" << getpid() << "]"
         << "执行时间是[" << time(nullptr) << "]\n"
         << endl;
}

void f2()
{
    cout << "这是一个备份数据任务, 执行的进程 ID [" << getpid() << "]"
         << "执行时间是[" << time(nullptr) << "]\n"
         << endl;
}
void f3()
{
    cout << "这是一个处理网络连接的任务, 执行的进程 ID [" << getpid() << "]"
         << "执行时间是[" << time(nullptr) << "]\n"
         << endl;
}

void loadFunctor()
{
    info.insert({functors.size(), "处理日志的任务"});
    functors.push_back(f1);

    info.insert({functors.size(), "备份数据任务"});
    functors.push_back(f2);

    info.insert({functors.size(), "网络连接的任务"});
    functors.push_back(f3);
}

int main()
{
    // 0. 加载任务列表
    loadFunctor();

    // 1. 创建管道
    int pipefd[2] = {0};
    if (pipe(pipefd) != 0)
    {
        cerr << "pipe error" << endl;
        return 1;
    }

    // 2. 创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        // 创建失败
        cerr << "fork error" << endl;
        return 2;
    }
    else if (id == 0)
    {
        // 子进程,read - 读取
        // 3. 关闭不需要的文件fd
        close(pipefd[1]);

        // 子进程不断根据收到的信息,执行对应的方法
        // 如果没有人往管道中写,此时子进程就卡在了read这里等待别人分配任务
        while (true)
        {
            uint32_t operatorType = 0;

            // 从fd为pipefd[0]的文件里读sizeof(uint32_t)个字节的内容,写到operatorType中去

            // 如果有数据就读取,如果没有数据就阻塞等待,等待任务的到来。
            ssize_t s = read(pipefd[0], &operatorType, sizeof(uint32_t));
            if (s == 0)
            {
                cout << "我要退出了..." << endl;
                break;
            }
            assert(s == sizeof(uint32_t));
            (void)s;
            
            // 走到这里一定是一个成功的读取
            if (operatorType < functors.size())
            {
                functors[operatorType]();
            }
            else
            {
                cerr << "bug? operatorType = " << operatorType << endl;
            }
        }
        close(pipefd[0]);
        exit(0);
    }
    else if (id > 0)
    {
        srand((long long)time(nullptr));
        // 父进程,write - 操作
        // 3. 关闭不需要的文件fd
        close(pipefd[0]);
        // 4. 指派任务
        int num = functors.size();
        int cnt = 10;
        while (cnt--)
        {
            // 5. 形成任务码
            uint32_t commandCode = rand() % num;
            cout << "父进程指派任务完成,任务是:" << info[commandCode] << "任务的编号是: " << cnt << endl;
            
            // 向指定的进程下达执行任务的操作
            write(pipefd[1], &commandCode, sizeof(uint32_t));
            sleep(1);
        }
        close(pipefd[1]);
        
        pid_t res = waitpid(id, nullptr, 0);
        if (res) cout << "wait success" << endl;
    }

    return 0;
}
2.1.6 控制多个子进程(进程池):

在这里插入图片描述

命令行中输入的|命令,其实就是一个匿名管道:

3 命名管道

  • 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  • 命名管道是一种特殊类型的文件

3.1 命名管道的创建

在这里插入图片描述

int mkfifo(const char *filename,mode_t mode)
返回值:成功返回0,失败返回-1 错误代码

创建命名管道时候,要指明路径,和umask值,为了防止默认umask的扰乱,我们一开始将`umask``置为0。

管道文件是以p开头的:
在这里插入图片描述

int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}

3.2 匿名管道与命名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open(要自己进行打开)
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完 成之后,它们具有相同的语义

命名管道的打开规则

  • 如果当前打开操作是为读而打开FIFO时,
    O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
    O_NONBLOCK enable:立刻返回成功
  • 如果当前打开操作是为写而打开FIFO时,
    O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
    O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

3.3 两个进程之间的通信

  • 匿名管道之间的通信是基于父子进程继承的关系来实现的。
    而让两个毫不相干的进程实现进程通信则是命名管道做的事情.

命名管道:通过一个fifo文件,有路径就具有唯一性,通过路径,就能找到同一资源。

只要打开的是同一个文件在内核里用的就是同一个struct file,那么指向的就是同一个inode,用的就是同一个缓冲区

命名管道是让两个进程之间是看到同一个文件,这个文件做了符号处理,相当于管道文件(通信时,数据不会刷新到磁盘上),操作系统一看到这个文件就知道了,这个文件的数据不用刷新到磁盘上,所以此时就在内存里,就有了管道。

代码演示:

#pragma

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <unistd.h>

#define IPC_PATH "./.fifo"

using namespace std;

服务端:

#include "comm.h"
// 读取
int main()
{
    umask(0);
    // server创建好了,client就不用创建了
    if (mkfifo(IPC_PATH, 0600) != 0)
    {
        cerr << "mkfifo error" << endl;
        return 1;
    }

    int pipeFd = open(IPC_PATH, O_RDONLY);
    if (pipeFd < 0)
    {
        cerr << "open fifo error" << endl;
        return 2;
    }

#define NUM 1024
    // 正常的通信过程
    char buffer[NUM];
    while (true)
    {
        ssize_t s = read(pipeFd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = '\0';
            cout << "客户端->服务器#" << buffer << endl;
        }
        else if (s == 0)
        {
            cout << "客户退出了,我也退出了" << endl;
            break;
        }
        else
        {
            // do nothing
            cout << "read: " << strerror(errno) << endl;
        }
    }
    close(pipeFd);
    cout << "服务端退出了" << endl;

    // 跑完之后删除管道
    unlink(IPC_PATH);

    return 0;
}

客户端:

#include "comm.h"

// 写入
int main()
{
    int pipeFd = open(IPC_PATH, O_WRONLY);
    if (pipeFd < 0)
    {
        cerr << "open: " << strerror(errno) << endl;
        return 1;
    }

#define NUM 1024
    char line[NUM];
    // 进行通信

    while (true)
    {
        printf("请输入你的消息# ");
        fflush(stdout);
        memset(line, 0, sizeof(line));
        // fgets -> C语言的函数 -> line结尾自动添加\0
        if (fgets(line, sizeof(line), stdin) != nullptr)
        {
            line[strlen(line) - 1] = '\0';
            write(pipeFd, line, strlen(line));
        }
        else
        {
            break;
        }

    }
    close(pipeFd);
    cout << "客户端退出了" << endl;

    return 0;
}

3.4 命名管道的特点:

  • 命名管道,会在磁盘上,但是里面没有内容,不会将内存数据进行刷新到磁盘上
  • 命名管道,手段不一样,是靠系统文件路径的唯一性,来看到同一份文件的

尾声
看到这里,相信大家对这个Linux 有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1937953.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LATEX模板支持中文、目录和段落

\documentclass{ctexart} \usepackage{amsmath,amssymb,amsfonts,hyperref} \usepackage{CJKutf8} \usepackage{enumitem} % 引入宏包 \usepackage [colorlinkstrue] {} \begin{document}\begin{CJK}{UTF8}{gkai}%正文放在此行下与\end{CJK}之间就行\tableofcontents\newpage\s…

【Python】从基础到进阶(四):深入了解Python中的控制流

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、条件语句1. if 语句2. if-else 语句3. if-elif-else 语句4. 嵌套条件语句5. 三元运算符 三、循环语句1. for 循环基本语法使用range() 函数遍历列表、字典和字符串 2. while 循环基本语法无限循环与终止条件 3.…

Python 实现股票指标计算——BBI

BBI (Bull And Bear lndex) - 多空指标 1 公式 3日均价 3日收盘价之和 / 36日均价 6日收盘价之和 / 612日均价 12日收盘价之和 / 1224日均价 24日收盘价之和 / 24BBI (3日均价 6日均价 12日均价 24日均价) / 4 2 数据准备 我们以科创50指数 000688 为例&#xff0c…

使用Vue实现点击页面触发特效

效果描述 在页面上的指定区域内进行点击&#xff0c;则会在页面上显示设置好的随机文本&#xff0c;此文本出现后会执行动画&#xff0c;动画效果为节点在1s之内向右上方移动并在移动的过程中完成180翻转&#xff0c;最后消失。 效果展示 完整代码 <template><div…

Windows 11 Visual Studio 2022 cmake 3.29 CUDA12.5 构建VTK

The Visualization Toolkit (VTK)是一个用于操作和展示科学数据的开源软件&#xff0c;包括了二三维渲染功能。 下载VTK 从官网Download | VTK下载VTK版本&#xff0c;我下载的是9.3.1源代码&#xff0c;在Windows 11上安装。 CMake构建VTK的VS2022工程 生成与安装 分别生成De…

AI智能名片小程序:跨界融合,重塑品牌与顾客的“三度情缘”

在这个信息爆炸、竞争白热化的时代&#xff0c;品牌如何突破重围&#xff0c;与顾客建立超越常规的情感链接&#xff1f;答案或许就藏在那个看似不起眼&#xff0c;实则暗藏玄机的AI智能名片小程序里&#xff01;它不仅是技术的结晶&#xff0c;更是品牌与顾客之间“三度情缘”…

Redis持久化(AOF和RDB)

目录 前言 一.RDB 1.1手动执行 1.2自动执行 二.AOF 2.1重写机制 三.混合持久化 Redis的学习专栏&#xff1a;http://t.csdnimg.cn/a8cvV 前言 持久化&#xff0c;在之前&#xff0c;我们接触这个词汇是在mysql数据库当中的事务四大特性里。 持久性&#xff1a;指一旦事…

探索免费隧道服务:为本地开发提供自定义子域名的解决方案

目录 引言 使用Ngrok进行本地开发 免费替代方案 Localtunnel Serveo Ngrok付费计划&#xff08;有限的免费试用&#xff09; 开源替代方案 SISH 总结 引言 在Web开发中&#xff0c;将本地服务器暴露给互联网进行测试或演示是常见需求。Ngrok等工具因其便捷性而广受欢迎…

【LeetCode】分隔链表

目录 一、题目二、解法完整代码 一、题目 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示例 1&#xff1a; 输入&a…

等保-Linux等保测评

等保-Linux等保测评 1.查看相应文件&#xff0c;账户xiaoming的密码设定多久过期 rootdengbap:~# chage -l xiaoming Last password change : password must be changed Password expires : pass…

day5 分布式节点

文章目录 1 流程回顾2 抽象 PeerPicker3 节点选择与 HTTP 客户端4 实现主流程5 main 函数测试。6 QA 本文代码地址&#xff1a; 本文是7天用Go从零实现分布式缓存GeeCache的第五篇。 注册节点(Register Peers)&#xff0c;借助一致性哈希算法选择节点。实现 HTTP 客户端&…

OpenGL-ES 学习(7) ---- VBO EBO 和 VAO

目录 VBO(Vertex Buffer Object)EBO(Element Buffer Object)VAO(Vertex Array Object) VBO(Vertex Buffer Object) EBO(Element Buffer Object) VBO(Vertex Buffer Object) 实际是指顶点缓冲器对象 在 opengl-es 2.0 的编程中&#xff0c;用于绘制图元的顶点数据是从 CPU 传…

LoadRunner-Vugen脚本使用教程

1 使用VuGen录制脚本 1.1新建脚本和解决方案 &#xff08;1&#xff09;打开VuGen&#xff0c;单击【File】→【New Script and Solution】创建项目&#xff0c;弹出Create a New Script对话框。 左侧栏是协议分类&#xff0c;每项含义如下所示&#xff1a; ● Single Pro…

数字通云平台 智慧政务OA PayslipUser SQL注入漏洞复现

0x01 产品简介 数字通云平台智慧政务OA产品是基于云计算、大数据、人工智能等先进技术,为政府部门量身定制的智能化办公系统。该系统旨在提高政府部门的办公效率、协同能力和信息资源共享水平,推动电子政务向更高层次发展。 0x02 漏洞概述 数字通云平台 智慧政务OA Paysli…

pytorch学习(六)transforms使用

1.Transforms可以对训练图像进行预处理&#xff0c;以提高模型的稳定性&#xff0c;提高泛化能力。其中包含&#xff1a; 中心裁剪、数据标准化、缩放、裁剪、旋转、仿射、反转、填充、噪声添加、灰度变换、线性变换、亮度饱和度以及对比度变换等。 所处理的图像用tensorboard…

【iOS】APP仿写——网易云音乐

网易云音乐 启动页发现定时器控制轮播图UIButtonConfiguration 发现换头像 我的总结 启动页 这里我的启动页是使用Xcode自带的启动功能&#xff0c;将图片放置在LaunchScreen中即可。这里也可以通过定时器控制&#xff0c;来实现启动的效果 效果图&#xff1a; 这里放一篇大…

基于视觉工具箱和背景差法的行人检测,行走轨迹跟踪,人员行走习惯统计matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 在三维图中&#xff0c;幅度越大&#xff0c;则表示人员更习惯的行走路线。 2.算法运行软件版本 matlab2022a 3.部分核…

【Nacos】Nacos服务注册与发现 心跳检测机制源码解析

在前两篇文章&#xff0c;介绍了springboot的自动配置原理&#xff0c;而nacos的服务注册就依赖自动配置原理。 Nacos Nacos核心功能点 服务注册 :Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务&#xff0c;提供自身的元数据&#xff0c;比如ip地址、端…

【C语言】深入解析希尔排序

文章目录 什么是希尔排序&#xff1f;希尔排序的基本实现代码解释希尔排序的优化希尔排序的性能分析希尔排序的实际应用结论 在C语言编程中&#xff0c;希尔排序是一种高效的排序算法&#xff0c;是插入排序的一种更高效的改进版本。它通过比较相距一定间隔的元素来进行排序&am…

JRT报告打印设计

检验报告单打印一直是个难点问题&#xff0c;JRT开发时候重点考虑了简化检验报告打印&#xff0c;首先采用脚本化方便快速修改报告。然后打印基础解决难点问题&#xff0c;基于JRT打印就可以简化到本文代码的水平&#xff0c;维护方便&#xff0c;结构清晰&#xff0c;上线修改…