『Linux从入门到精通』第 ㉓ 期 - 管道

在这里插入图片描述

文章目录

  • 💐专栏导读
  • 💐文章导读
  • 🐧进程间通信的目的
  • 🐧如何进行进程间通信
  • 🐧进程间通信的分类
  • 🐧管道
    • 🐦什么是管道
    • 🐦管道原理
  • 🐧实例代码
  • 🐧管道的特点
  • 🐧代码拓展 - 通过管道实现进程控制

💐专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 Linux从入门到精通,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法

💐文章导读

本章我们将深入学习如何通过管道进行进程间通信。

在这里插入图片描述

🐧进程间通信的目的

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

🐧如何进行进程间通信

首先进程间进行通信有最大的一个难题:

  • 进程是具有独立性的

也就是说,我们无法做到让两个进程之间访问彼此的资源。因此进程间通信的首要任务是

  • 让两个进程看到同一份资源
  • 然后让一方写入,另一方进行读取

这也是主流的我们将要的介绍的几种进程间通信的方法的核心思想。

🐧进程间通信的分类

进程间通信经过漫长的发展,逐渐衍生出以下几种方式:

管道

  • 匿名管道(pipe);
  • 命名管道;

System V IPC

  • System V 消息队列;
  • System V 共享内存;
  • System V 信号量;

POSIX IPC

  • 消息队列;
  • 共享内存;
  • 信号量;
  • 互斥量;
  • 条件变量;
  • 读写锁;

本章节我们将介绍管道的进程间通信方式。

🐧管道

🐦什么是管道

在不久之前我们应该都见过Linux中的管道,例如:

在 myfile.txt 中写入五行文字:

$ echo "hello world" >> myfile.txt
$ echo "hello world" >> myfile.txt
$ echo "hello world" >> myfile.txt
$ echo "hello world" >> myfile.txt
$ echo "hello world" >> myfile.txt
$ cat myfile.txt 
hello world
hello world
hello world
hello world
hello world

当我们想统计出 myfile.txt 中有几行文字时:

$ cat myfile.txt | wc -l
5
  • wc -l 用来统计文本中的行数;

在Linux中,"管道"是一种机制,允许将一个命令的输出直接传递给另一个命令作为输入。这个机制通过竖线符号|来实现。通过使用管道,你可以将一个命令的输出作为另一个命令的输入,从而实现多个命令的协同工作,形成一个命令链。

例如,假设你有两个命令:command1 和 command2。你可以使用管道将它们连接在一起,使 command2 处理 command1 的输出。命令的形式如下:

$ command1 | command2

这会将 command1 的输出传递给 command2,而不是将其打印到终端。这种机制使得在Linux系统上可以轻松地组合和重用命令,从而实现更复杂的操作。

在计算机领域中,管道是一个比较大的概念,Linux指令中的 | 是管道的一种形式。

🐦管道原理

我们首先要明确,管道是一个文件。要做到让两个进程看到同一份资源,说明这一份资源不独属于任何一个进程。

当我们创建一个进程,该进程会拥有自己的tast_struct结构体,在这个结构体中管理着 struct files_struct字段,files_struct中又管理着该进程的文件描述符表 (struct file* fd_array[]),如下图所示:

在这里插入图片描述

在该进程中,分别用读方式与写方式打开一个文件;然后 fork 创建子进程,此时子进程会继承来自父亲的与父亲相同的文件描述符表;

在这里插入图片描述

此时父进程与子进程指向了同一个文件,接下来结合实际场景,例如,我们想让父进程进行写入,让子进程负责读取,这时父子进程需要关闭不需要的文件描述符,父进程将 4 关闭,子进程将 3 关闭,这时父子进程就可以根据需要来收发数据了。
在这里插入图片描述

🐧实例代码

现在有一个需求:子进程向管道中发送数据,父进程每隔一秒读取一次管道中的内容。

在代码的编写过程中,我们需要用到一个接口——pipe为我们生成管道文件。它的基本原型如下:

#include <unistd.h>

int pipe(int pipefd[2]);

这里,pipefd 是一个整型数组,有两个元素,分别表示管道的两个端口。pipefd[0] 代表读取端口pipefd[1] 代表写入端口。成功调用 pipe 函数后,这两个文件描述符将被用于在两个相关进程之间传递数据。

#include <iostream>
#include <iostream>
#include <string>
#include <cerrno>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

using namespace std;

int main()
{
    int pipefd[2] = {0};

    // 1. 创建管道
    int n = pipe(pipefd);
    if (n < 0)
    {
       cout << "pipe error" << errno << ": " << strerror(errno) << endl;
       return 1;
    }
    cout << "pipfd[0]" << pipefd[0] << endl;
    cout << "pipfd[1]" << pipefd[1] << endl;

    // 2.创建子进程
    pid_t id = fork();
    assert(id >= 0);

    if(id == 0) // 子进程
    {
        // 3.关闭不需要的fd
        close(pipefd[0]);

        // 4.开始通信
        const string namestr = "我是子进程";
        int cnt = 1;
        char buffer[1024];
        while(true)
        {
          snprintf(buffer, sizeof buffer, "%s : %d, pid : %d\n", namestr.c_str(), cnt++, getpid());
          write(pipefd[1], buffer, strlen(buffer));
        }
        close(pipefd[1]);
        exit(0);
    }

    // 父进程
    close(pipefd[1]);

    // 4.开始通信
    char buffer[1024];
    int cnt = 0;
    while(true)
    {
        int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            cout << "我是父进程,我读到了信息:\n" << buffer << endl;
        }
        else if(n == 0)
        {
            cout << "我是父进程,我读到了文件末尾" << buffer << endl;
            break;
        }
        else
        {
            cout << "我是父进程,读取异常" << buffer << endl;
            break;
        } 
        sleep(1);
    }
    return 0;
}

🐧管道的特点

在管道的使用中,我们经常能遇到以下四种场景:

  1. 当我们read读取完毕所有的管道数据,如果写端不再继续写入数据,那读端只能等待;
  2. 当写端将管道全部写满了之后(管道文件的容量是有上限的)就不能在继续写入数据了;
  3. 当写端将数据全部写入并退出后,读端读取完毕管道数据后,read就会返回0,代表读到了文件末尾;
  4. 当写端一直写入数据,而读端关闭,这时写端所作的事情是无意义的,OS此刻会杀死一直写入数据的进程,OS会通过发送信号来终止该进程;

由四种场景,我们可以总结出管道具有的特点:

  1. 管道是单向通信的(半双工)。管道并不能读取和写入同时进行;
  2. 管道的生命周期随进程,当进程退出时,管道释放;
  3. 管道通信经常是由具有“血缘关系”的进程间进行的,例如父子进程;
  4. 在管道通信中,读与写的次数并不是强相关的,因为管道提供的是流式服务;
  5. 管道具有一定的系统能力,让读端与写端能够按照一定的步骤进行通信;

🐧代码拓展 - 通过管道实现进程控制

接下来,我们就基于管道进行一个简单的设计——父进程向不同的子进程写入特定的消息,唤醒子进程,并让子进程去执行特定的命令。

/* ctrlProcess.cpp */

#include "Task.hpp"

using namespace std;

Task t;
const int gnum = 3;

class EndPonit
{
public:
    EndPonit(int id, int fd)
        : _child_id(id),
          _write_fd(fd)
    {}

    ~EndPonit()
    {}

public:
    pid_t _child_id; // 子进程id
    int _write_fd;   // 写端fd
};

// 子进程要执行的方法
//void WaitCommand(int _write_fd)
void WaitCommand()
{
    while (true)
    {
        int command = 0;
        int n = read(_write_fd, &command, sizeof(int));
        if (n == sizeof(int))
        {
            t.Execute(command);
        }
        else if (n == 0)
        {
            break;
        }
        else
        {
            break;
        }
    }
}

void creatProcess(vector<EndPonit> *end_points)
{
    for (int i = 0; i < gnum; i++)
    {
        // 1.创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n;

        // 2.创建进程
        pid_t id = fork();
        assert(id != -1);

        // 子进程
        if (id == 0)
        {
            // 3.关闭不要的fd
            close(pipefd[1]);
            // 输入重定向
            dup2(pipefd[0], 0);
            // 子进程等待获取命令
            WaitCommand();
            //WaitCommand(pipefd[0]);
            close(pipefd[0]);
            exit(0);
        }

        // 父进程
        // 关闭不要的fd
        close(pipefd[0]);

        // 4.将新的子进程和它的管道写端,构建对象
        end_points->push_back(EndPonit(id, pipefd[1]));
    }
}
int main()
{
    vector<EndPonit> end_points;
    creatProcess(&end_points);

    int num = 0;
    while (true)
    {
        // 1.选择任务
        int command = COMMAND_FUNC1;
        // 2.选择进程
        int index = rand() % end_points.size();

        // 3.下发任务
        write(end_points[index]._write_fd, &command, sizeof(int));
        sleep(1);
    }
    return 0;
}
/* Task.hpp */
#pragma once

#include <iostream>
#include <vector>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <assert.h>
using namespace std;

typedef void (*func_t)();

void Func1()
{
    cout << "pid:" << getpid() << "Func1...." << endl;
}

void Func2()
{
    cout << "pid:" << getpid() << "Func2...." << endl;
}

void Func3()
{
    cout << "pid:" << getpid() << "Func3...." << endl;
}

#define COMMAND_FUNC1 0
#define COMMAND_FUNC2 1
#define COMMAND_FUNC3 2

class Task
{
public:
    Task()
    {
        _tasks.push_back(Func1);
        _tasks.push_back(Func2);
        _tasks.push_back(Func3);
    }
    void Execute(int command)
    {
        if (command >= 0 && command < _tasks.size())
            _tasks[command]();
    }
    ~Task()
    {
    }

private:
    vector<func_t> _tasks;
};

运行效果展示

在这里插入图片描述

本章的内容到这里就结束了!如果觉得对你有所帮助的话,欢迎三连~

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何扫码查看图片信息?图片放到二维码展示的在线教学

现在通过扫码来查看物品图片是很常用的一种方式&#xff0c;将物品不同角度的图片存入一张二维码后&#xff0c;用户只需要扫描这张二维码图片&#xff0c;就可以了解物品预览图及其他信息。常用的图片格式比如jpg、png、gif都可以放到二维码中显示&#xff0c;那么具体该怎么做…

FreeCAD|建模常用命令

import FreeCAD as App import Part 1、创建点 V1 App.Vector(0, 10, 0) 2、创建线段 L1 Part.LineSegment(V1, V2) 3、创建圆弧 C1 Part.Arc(V1, VC1, V4) 4、创建Shape S1 Part.Shape([C1, L1, C2, L2]) 5、创建基本形状 makeBox(l, w, h, [p, d]) makeCircl…

C语言:qsort的使用方法

目录 1. qsort是什么&#xff1f; 2. 为什么要使用qsort 3. qsort的使用 3.1 qsort的返回值和参数 3.2 qsort的compare函数参数 3.3 int类型数组的qsort完整代码 4. qsort完整代码 1. qsort是什么&#xff1f; qsort中的q在英语中是quick&#xff0c;快速的意思了&#…

LeetCode_Java_动态规划系列(3)(题目+思路+代码)

338.比特位计数 给你一个整数 n &#xff0c;对于 0 < i < n 中的每个 i &#xff0c;计算其二进制表示中 1 的个数 &#xff0c;返回一个长度为 n 1 的数组 ans 作为答案。 class Solution {public int[] countBits(int n) {/** 思路&#xff1a;* 1.创建一个长度为 n…

智慧市容环境卫生管理信息系统建设项目初步设计参考指南

第四章项目建设方案 梳理和编制数据标准规范&#xff0c;为数据体系建设提供建设指导。数据标准规范体系是根据统一市容环卫基础数据资源建立的&#xff0c;从要素分类、编码、符号、制图、更新机制等层 面解决各类规划标准不衔接、各自为政问题。标准规范体系包括&#xff1…

回溯难题(算法村第十八关黄金挑战)

复原 IP 地址 93. 复原 IP 地址 - 力扣&#xff08;LeetCode&#xff09; 有效 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 . 分隔。 例如&#xff1a;"0.1.2.201" 和 &q…

【计算机毕业设计】044学生管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

数据结构——算法与算法分析3,4

目录 1.分析算法时间复杂度的方法 举例&#xff1a; 1.数据集队时间复杂度的影响 2.空间复杂度 3.设计好算法的过程 1.分析算法时间复杂度的方法 举例&#xff1a; 1.数据集队时间复杂度的影响 一般只考虑最坏时间复杂度和平均时间复杂度 2.空间复杂度 3.设计好算法的过程…

【Android 内存优化】怎么理解Android PLT hook?

文章目录 前言什么是hook?PLT hook作用基本原理PLT hook 总体步骤 代码案例分析方案预研面临的问题怎么做&#xff1f;ELFELF 文件头SHT&#xff08;section header table&#xff09; 链接视图&#xff08;Linking View&#xff09;和执行视图&#xff08;Execution View&…

Vue使用高德地图定位到当前位置,并显示天气信息

首先得去高德控制台申请两个 key&#xff0c;一个天气key和一个定位key 获取天气信息的函数&#xff1a; const getWeather function (city) {// 使用 fetch 发送请求获取天气信息fetch(https://restapi.amap.com/v3/weather/weatherInfo?city${city}&keyeefd36557b0250…

二,几何相交----2,区间相交检测IID--(1)算法

对于空间的线段是否相交&#xff0c;假设都是与x平行&#xff0c;则需要三步 1&#xff0c;对各线段左右端点设置为L,R标志 2&#xff0c;从小到大进行排序 3&#xff0c;线性扫描&#xff0c;从小到大&#xff0c;根据模式判断是否相交&#xff0c;假设不相交&#xff0c;则应…

【JavaScript】面试手撕浅拷贝

【JavaScript】面试手撕浅拷贝 引入 浅拷贝和深拷贝应该是面试时非常常见的问题了&#xff0c;为了能将这两者说清楚&#xff0c;于是打算用两篇文章分别解释下深浅拷贝。 PS: 我第一次听到拷贝这个词&#xff0c;有种莫名的熟悉感&#xff0c;感觉跟某个英文很相似&#xff…

sc-MAVE

Deep-joint-learning analysis model of single cell transcriptome and open chromatin accessibility data单细胞转录组和开放染色质可及性数据的深度联合学习分析模型 在同一个细胞中同时分析转录组和染色质可及性信息为了解细胞状态提供了前所未有的解决方案。然而&#x…

WPS/Office 好用的Word插件-查找替换

例如&#xff1a;一片文档&#xff1a;…………泰山…………泰&#xff08;少打了山字&#xff09;………… 要是把“泰”查找替换为“泰山”&#xff0c;就会把前面的“泰山”变成“泰山山”&#xff0c;这种问题除了再把“泰山山”查找替换为“泰山”&#xff0c;有没有更简单…

Flutter输入框换行后自适应高度

Flutter输入框换行后输入框高度随之增加 效果 设计思想 通过TextEditingController在build中监听输入框&#xff0c;输入内容后计算输入框高度然后自定义适合的值&#xff0c;并且改变外部容器高度达到自适应高度的目的 参考代码 //以下代码中的值只适用于案例&#xff0c;…

智能果园风吸杀虫灯的优势

TH-FD2随着农业科技的不断进步&#xff0c;果园管理也迎来了革命性的变革。智能果园风吸杀虫灯作为一种新型的果园管理工具&#xff0c;以其独特的优势&#xff0c;正逐渐受到广大果农的青睐。 一、智能果园风吸杀虫灯的工作原理 智能果园风吸杀虫灯利用害虫的趋光性&#xff0…

贪心 Leetcode 134 加油站

加油站 Leetcode 134 学习记录自代码随想录 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发&#xff0c;开始时油…

MonkeyRunner在自动化测试里的应用场景!

MonkeyRunner是Android提供的一个自动化测试工具&#xff0c;主要用于对Android设备或模拟器进行功能和压力测试。以下是一些MonkeyRunner在自动化测试中的应用场景及实例代码&#xff1a; 基本操作测试 点击屏幕上的特定位置或元素。 模拟滑动和手势操作。 发送按键事件。 …

指针习题二

使用函数指针实现转移表 #include <stdio.h> int add(int a, int b) {return a b; } int sub(int a, int b) {return a - b; } int mul(int a, int b) {return a * b; } int div(int a, int b) {return a / b; } int main() {int x, y;int input 1;int ret 0;int(*p[…

Linux学习:初识Linux

目录 1. 引子&#xff1a;1.1 简述&#xff1a;操作系统1.2 学习工具 2. Linux操作系统中的一些基础概念与指令2.1 简单指令2.2 ls指令与文件2.3 cd指令与目录2.4 文件目录的新建与删除指令2.5 补充指令1&#xff1a;2.6 文件编辑与拷贝剪切2.7 文件的查看2.8 时间相关指令2.9 …