【Linux进程通信】Linux进程间的无声对话:匿名管道与命名管道技术

W...Y的主页 😊

代码仓库分享 💕 

 前言:我们已经知道了进程和文件的基本理论,知道了进程和文件的重要性。进程具有独立性,所以两个进程不能直接通信,那么进程间应该怎样通信呢?我们今天来解开其中的面纱。

目录

进程间通信

进程间通信目的

进程间通信发展

进程间通信分类

 管道

什么是管道

匿名管道

管道读写规则

管道特点

命名管道

创建一个命名管道

实现命名管道代码

命名管道的打开规则

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


进程间通信

进程间通信目的

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

我们往往需要进程间的互相协同去完成一件事情,所以才会需要进程通信。而进程间为了维护自己的独立性双方都不能直接提供一个共享内存,如果有一方进行提供内存,另一个进程就可以进行读写数据反而破坏了进程独立性,这时我们就需要一个话事人提供共享内存——操作系统。

操作系统有不同提供内存的方式,所以就决定了有不同的通信方法!!!

进程间通信发展

管道
System V进程间通信
POSIX进程间通信

进程间通信分类

管道(最古老,但是是最经典的通信方式)
        匿名管道pipe
        命名管道
System V IPC(只支持本主机内的通信)
        System V 消息队列
        System V 共享内存
        System V 信号量

POSIX IPC(主流通信方式,支持远端网络通信)
        消息队列
        共享内存
        信号量
        互斥量
        条件变量
        读写锁

 管道

什么是管道

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

我们之前学习过在操作系统创建进程时都会创建对应的结构体task_struct,而结构体中有一个结构体指针指向文件结构体,文件结构体struct files_struct中有一个文件描述符表。当我们以读方式打开,再以写方式打开,因为0,1,2是操作系统默认打开的标准输入输出,所以会在文件描述符队列中创建3和4,也会创建两个文件结构体,但是只有一个缓冲区。 

接下来我们创建一个子进程,子进程会继承父进程的task_struct,文件描述符表 ,但是不需要拷贝打开的文件。因为是单纯的浅拷贝,所以文件描述符表中的指针与父进程的相同,所以子进程指向的被打开的文件和父进程是相同的。

进程通信的本质就是让不同的进程看到相同的资源,所以我们的父子进程看到相同的文件资源。3号描述符对应的读端,4号描述符对应的写端。所以我们可以让父进程以4号文件描述符进行写入缓冲区。子进程以3号文件描述符读取缓冲区内容。这就完成了基于文件的进程间通信,这就叫做管道。

为了让管道通信更简单,管道只支持单项通信,所以当父子进程都打开读写端后,父进程关闭写端,子进程关闭读段,就可以进行父进程写子进程读的通信了。

struct file使用的是引用计数,所以文件允许多个指针进行指向文件,当计数值为0时才会销毁文件以及缓冲区。struct file允许多个进程通过指针指向。 

为了支持我们进行管道通信,系统提供pipe接口。

匿名管道

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

其中参数是一个输出型参数,当系统调用成功时我们希望得到读写的文件描述符,就会放到pipfd的数组中。pipe会提供一个类似文件缓冲区的空间,但是不需要向磁盘中刷新,磁盘中不存在的文件。这种文件属于内存级文件没有名字,所以我们将他称为匿名文件!!! 

 匿名管道只能进行具有血缘关系的进程进行通信,常用于父子进程进行通信。其原理就是通过继承的方式进行通信。

pipe管道中的读端fd[0],写端为fd[1],现在我们进入代码测试:

#include<stdio.h>
#include<unistd.h>

int main()
{
    int pipefd[2];
    int n = pipe(pipefd);
    if(n < 0) return 1;
    printf("pipefd[0]: %d, pipefd[1]: %d\n", pipefd[0], pipefd[1]);
    return 0;
}

 

我们现在程序中默认子进程为写段,父进程为读端。所以我们要将子进程的读端进行关闭父进程的写端关闭,然后我们就可以进行通信了:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.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(buffer, sizeof(buffer), "message: %s, pid: %d, count: %d\n", str, pid, cnt);
        write(wfd, buffer, strlen(buffer));
        cnt++;
        sleep(1);
    }
}
void reader(int rfd)
{
    char buffer[1024];
    while(1)
    {
        ssize_t n = read(rfd, buffer, sizeof(buffer)-1);
        (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;
}

管道读写规则

 当我们在读写管道时一定会有一些特殊情况:

1.管道内部没有数据时并且子进程不关闭自己写端的fd读端父进程就要阻塞等待,直到pipe有数据。
2.管道内部被写满并且读端不关闭自己的fd,写端写满之后就要阻塞等待。
3.对于写端而言:不写了或关闭了pipe,读端会将pipe中的数据读完最后读到返回值为0表示读结束,如同读到了文件结尾。
4.读端不读了或者关闭但是写端在写OS直接终止写入的进程,通过信号13SIGPIPE信号杀掉进程。

现在我们写一段代码,让子进程持续写入,父进程读取10s后直接退出,查看一下退出信号:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// child
void writer(int wfd)
{
    const char *str = "hello father, I am child";
    char buffer[128];
    int cnt = 0;
    pid_t pid = getpid();
    while(1)
    {
        sleep(1);
        // snprintf(buffer, sizeof(buffer), "message: %s, pid: %d, count: %d\n", str, pid, cnt);
        // write(wfd, buffer, strlen(buffer));
        char c = 'A';
        write(wfd, &c, 1);
        cnt++;

        printf("cnt: %d\n", cnt);
        // if(cnt == 10) break;
    }
    close(wfd);
}

// father
void reader(int rfd)
{
    char buffer[1024];
    int cnt = 10;
    while(1)
    {
        // sleep(5);
        ssize_t n = read(rfd, buffer, sizeof(buffer)-1);
        if(n > 0)
            printf("father get a message: %s, n : %ld\n", buffer, n);
        else if(n == 0)
        {
            printf("read pipe done, read file done!\n");
            break;
        }
        else
            break;

        cnt--;
        if(cnt == 0) break;
    }
    close(rfd);
    printf("read endpoint close!\n");
}

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]);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    // wait(NULL);
    if(rid == id)
    {
        printf("exit code: %d, exit signal: %d\n", WEXITSTATUS(status), status&0x7F);
    }

    return 0;
}

当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
当管道满的时候
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程
退出
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

管道特点

1.只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
2.管道提供流式服务
3.一般而言,进程退出,管道释放,所以管道的生命周期随进程
4.一般而言,内核会对管道操作进行同步与互斥
5.管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道 

命名管道

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

当我们在对不同的进程进行进程通信时,我们需要先创建两个进程,这两个进程都有对应的task_struct,而每个进程里面有对应的文件描述符表。我们创建一个命名管道就可实现进程通信。

这种命名管道文件我们需要通过文件的路径+文件名进行查找。

创建一个命名管道

命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

$ mkfifo filename 

 

我们可以使用重定向,向管道中写入内容然后再使用cat读取管道内容: 删除管道使用rm即可。

命名管道也可以从程序里创建,相关函数有:

int mkfifo(const char *filename,mode_t mode); 

filename是管道名称,需要可以带指定路径,默认是在当前路径下创建。mode是管道文件的权限。

返回值为0表示创建管道成功,创建失败返回-1并且设置错误错误码。

 创建简单命名管道:

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

如果我们已经在此路径下有自己的命名管道了,再次运行此程序时会直接报错退出,除非我们将管道删除重新运行才可以正常进行。说明每个路径下只能有一个相同的命名管道。 

实现命名管道代码

 头文件内容:

//头文件

#ifndef __COMM_HPP__
#define __COMM_HPP__

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

using namespace std;

#define Mode 0666
#define Path "./fifo"

class Fifo
{
public:
    Fifo(const string &path) : _path(path)
    {
        umask(0);
        int n = mkfifo(_path.c_str(), Mode);
        if (n == 0)
        {
            cout << "mkfifo success" << endl;
        }
        else
        {
            cerr << "mkfifo failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
        }
    }
    ~Fifo()
    {
        int n = unlink(_path.c_str());
        if (n == 0)
        {
            cout << "remove fifo file " << _path << " success" << endl;
        }
        else
        {
            cerr << "remove failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
        }
    }

private:
    string _path; // 文件路径+文件名
};

#endif

进程写端:

#include "Comm.hpp"

int main()
{
    int wfd = open(Path, O_WRONLY);
    if (wfd < 0)
    {
        cerr << "open failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
        return 1;
    }

    string inbuffer;
    while (true)
    {
        cout << "Please Enter Your Message# ";
        std::getline(cin, inbuffer);
        if(inbuffer == "quit") break;
        ssize_t n = write(wfd, inbuffer.c_str(), inbuffer.size());
        if (n < 0)
        {
            cerr << "write failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
            break;
        }
    }

    close(wfd);
    return 0;
}

进程读端:

#include "Comm.hpp"
#include <unistd.h>

int main()
{
    Fifo fifo(Path);

    int rfd = open(Path, O_RDONLY);
    if (rfd < 0)
    {
        cerr << "open failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
        return 1;
    }
    // 如果我们的写端没打开,先读打开,open的时候就会阻塞,直到把写端打开,读open才会返回
    cout << "open success" << endl;

    char buffer[1024];
    while (true)
    {
        ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n] = 0;
            cout << "client say : " << buffer << endl;
        }
        else if (n == 0)
        {
            cout << "client quit, me too!!" << endl;
            break;
        }
        else
        {
            cerr << "read failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
            break;
        }
    }

    close(rfd);
    return 0;
}

 这样我们就可以通过命名管道实现对没有血缘关系的两个进程进行通信。

命名管道的打开规则

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

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

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


以上就是今天的内容,感谢大家的观看!!! 

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

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

相关文章

物联网技术-第4章物联网通信技术-4.1计算机网络

目录 1.1计算机网络拓扑与组成 &#xff08;1&#xff09;全连通式网络 &#xff08;2&#xff09;星型网 &#xff08;3&#xff09;环形网 &#xff08;4&#xff09;总线网 &#xff08;5&#xff09;不规则型网 1.2数据交换类型 &#xff08;1&#xff09;电路交换网 &…

硬件开发笔记(十八):核心板与底板之间的连接方式介绍说明:板对板连接器

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/139663096 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

DSP28335:独立按键控制LED灯

做任何事情不可操之过急&#xff0c;虽然我们可能在之前的单片机学过相关的原理&#xff0c;但是一个新的单片机依然有他的学习的地方&#xff0c;之前我觉得很简单&#xff0c;就跳过这个学习&#xff0c;结果到后面就很浮躁&#xff0c;导致后面的内容与这一章相连接的时候&a…

利用这个css属性,你也能轻松实现一个新手引导库

相信大家或多或少都在各种网站上使用过新手引导&#xff0c;当网站提供的功能有点复杂时&#xff0c;这是一个对新手非常友好的功能&#xff0c;可以跟随新手引导一步一步了解网站的各种功能&#xff0c;我们要做的只是点击下一步或者上一步&#xff0c;网站就能滚动到指定位置…

齐普夫定律在循环神经网络中的语言模型的应用

目录 齐普夫定律解释公式解释图与公式的关系代码与图的分析结论 使用对数表达方式的原因1. 线性化非线性关系2. 方便数据可视化和分析3. 降低数值范围4. 方便参数估计公式详细解释结论 来自&#xff1a;https://zh-v2.d2l.ai/chapter_recurrent-neural-networks/language-model…

智慧校园发展趋势:2024年及未来教育科技展望

展望2024年及未来的教育科技领域&#xff0c;智慧校园的发展正引领着一场教育模式的深刻变革&#xff0c;其核心在于更深层次地融合技术与教育实践。随着人工智能技术的不断成熟&#xff0c;个性化学习将不再停留于表面&#xff0c;而是深入到每个学生的个性化需求之中。通过精…

电感的本质是什么

什么是电感&#xff1f; 电感器件一般是指螺线圈&#xff0c;由导线圈一圈靠一圈地绕在绝缘管上&#xff0c;绝缘管可以是空心的&#xff0c;也可以包含铁芯或磁粉芯。 为什么把’线’绕成’圈’就是电感&#xff1f; 电感的工作原理非常抽象&#xff0c;为了解释什么是电感…

单片机 PWM输入捕获【学习记录】

前言 学习是永无止境的&#xff0c;就算之前学过的东西再次学习一遍也能狗学习到很多东西&#xff0c;输入捕获很早之前就用过了&#xff0c;但是仅仅是照搬例程没有去进行理解。温故而知新&#xff01; 定时器 定时器简介 定时器的分类 高级定时器 通用定时器 基本定时器…

Facebook与地方文化:数字平台的多元表达

在当今数字化时代&#xff0c;社交媒体不仅仅是人们交流的工具&#xff0c;更是促进地方文化传播和表达的重要平台。作为全球最大的社交网络之一&#xff0c;Facebook在连接世界各地用户的同时&#xff0c;也成为了地方文化多元表达的重要舞台。本文将深入探讨Facebook如何通过…

LabVIEW的热门应用

LabVIEW是一种图形化编程语言&#xff0c;因其易用性和强大的功能&#xff0c;在多个行业和领域中广泛应用。介绍LabVIEW在以下五个热门应用领域中的使用情况&#xff0c;&#xff1a;工业自动化、医疗设备与生物医学工程、科学研究与实验室自动化、能源管理与智能电网、航空航…

streamlit markdown里支持latex公式显示

参考&#xff1a; https://docs.streamlit.io/develop/api-reference/write-magic/st.write https://discuss.streamlit.io/t/streamlit-markdown-a-streaming-markdown-component-with-latex-mermaid-table-code-support/72187 也有独立支持的st.latex 接口单独显示公司&…

LeetCode347:前K个高频元素

题目描述 给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 解题思想 使用优先队列 priority_queue<Type, Container, Functional> Type 就是数据类型&#xff0c;Container 就是容器类型&#xff08;C…

Next14的appRouter模式中使用状态管理React-Redux

安装依赖 npm install reduxjs/toolkit react-redux创建store模块 创建 app/store/counterSlice.js文件 "use client"// redux需要作为客户端渲染的模块import { createSlice } from "reduxjs/toolkit"export const counterSlice createSlice({name: &…

解读自然语言处理:技术、应用与未来展望

引言 自然语言处理&#xff08;Natural Language Processing&#xff0c;简称NLP&#xff09;是计算机科学、人工智能和语言学的一个跨学科领域&#xff0c;致力于实现人与计算机之间通过自然语言进行有效沟通的能力。NLP 的核心任务是理解、解释和生成人类语言&#xff0c;使计…

AI早班2024.6.18

先一步知道AI未来&#xff01; 全球AI新闻速递 1.绿米 AI 智能存在传感器 FP1E开售。 2.摩尔线程 师者AI&#xff1a;完成70亿参数教育AI大模型训练测试。 3.Google 在 AI 功能推出新功能&#xff0c;需要明确说明可能出错的地方。 4.北大、快手攻克复杂视频生成难题&#…

生成式人工智能如何改变客户服务

生成式人工智能不仅重新定义了品牌与客户的互动方式&#xff0c;还重新定义了品牌如何优化内部资源&#xff0c;以提供更加个性化和高效的服务。 了解在就业和效率方面的挑战和机遇&#xff0c;使用生成式人工智能工具进行客户服务和支持任务。 生成式人工智能不仅重新定义了品…

一键复制备份轻松守护数据安全;安全删除目标文件夹里的原文件,释放存储空间

在这个信息爆炸的时代&#xff0c;我们的生活中充满了各种各样的数据和信息。从工作文档到家庭照片&#xff0c;从学习资料到个人收藏&#xff0c;这些资料都是我们的宝贵财富。然而&#xff0c;如何高效、安全地管理这些资料&#xff0c;却成为了许多人头疼的问题。今天&#…

关于在word中使用Axmath的报错的解决

介绍 Axmath是数学公式编辑器软件。官网如下。 AxMath/AxGlyph/AxCells (amyxun.com) 支持正版。 在word中使用Axmath 点击word中的“文件”→“选项”。 选择“加载项” 选择“word加载项” 在Axmath默认的安装目录如下&#xff1a; C:\Program Files (x86)\AxMathhao&am…

CSS-0_2 CSS和继承(inherit initial)

文章目录 CSS的层叠和继承inheritinitial很多你以为的样式初始值&#xff0c;其实是用户代理样式 碎碎念 CSS的层叠和继承 在上一篇 CSS和层叠、样式优先级 里已经讲过了层叠和优先级之间的关系&#xff0c;但是在CSS中的层叠除了体现在争抢露脸机会的优先级之外&#xff0c;还…

Pyqt QCustomPlot 简介、安装与实用代码示例(三)

目录 前言实用代码示例Line Style DemoDate Axis DemoParametric Curves DemoBar Chart DemoStatistical Box Demo 所有文章除特别声明外&#xff0c;均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 nixgnauhcuy’s blog&#xff01; 如需转载&#xff0c;请标明出处&#x…