Linux进程通信 ---匿名/命名管道 --- 共享内存(一)

简介: Linux进程通信 ---匿名/命名管道 --- 共享内存(一)

通信的概念

进程之间的数据传输,资源共享,发送通知,进程控制就属于进程间的通信

数据传输:

一个进程将其数据发送给另一个进程

资源共享:

多个进程之间共享同样的资源

通知事件:

一个进程向另一个进程发送消息也可以是向一组进程发送消息

进程控制:

一个进程控制另一个进程的执行

目前通信的主要标准分类为:

POSIX — 让通信可以跨主机

System V — 聚焦在本地通信

基于文件的通信方式为:管道

通信的本质:

因为进程具有独立性,所以通信并不是进程之间的直接交互,而是由操作系统直接或间接的给通信双方的进程提供内存空间,内存空间里的数据就是公共资源,而通信的前提就是需要通信的进程都必须看到同一份公共资源。不同的通信种类就是操作系统中不同的模板提供的。由上述即可得知,通信并不是低成本的工作。

管道

管道是基于文件系统的通信方式:从一个进程连接到另一个进程的数据流称为一个管道

每个文件都会有一个属于自己的内核缓冲区,这个区域就属于是进程之间的公共资源。因此进程间的通信就可通过这个内核缓冲区去实现。而这种基于文件系统的通信就是管道通信,这个文件就称为管道文件。管道文件时内存级的文件,不需要进行IO。一般而言管道只能用来进行单向数据通信

管道的特征:

  1. 管道的生命周期随进程,进程退出则管道释放
  2. 只要是具有血缘关系的进程就可以进行管道通信
  3. 管道是面向字节流的(网络)
  4. 单向通信
  5. 管道具有互斥与同步的机制

管道的系统调用

int pipe(int pipefd[2]); — <unistd.h>

创建匿名管道

创建失败返回-1, 创建成功返回0.

参数为输出型参数,需要将两个文件描述符(分别对应进程读和写)传入这个参数

创建成功后,数组保存两个文件描述符:pipfd[0] — 读;pipfd[1] — 写

b7b4a090b7a795538a476a380ae07c20.png


int mkfifo(const char *filename,mode_t mode); — <sys/types.h><sys/stat.h>

创建以权限p开头的命名管道文件

参数1为文件名(C类型的字符串),参数2为文件的权限

创建失败返回-1, 创建成功返回0.

4fc64b2ac5cc27a72cfc64e1c6ce1263.png

int unlink(const char* path); — <unistd.h>

删除管道文件

删除成功返回0.

be17e38cf4813cdb1ad04e062e740d23.png

匿名管道

通过父进程创建子进程去继承文件地址的方式,使得两进程看到同一个内存级文件,此时的内存级文件没有名称就称为匿名管道

创建匿名管道的步骤:

父进程创建出管道,分别以读和写的方式打开文件(为了让子进程继承,方便后期选择对于文件的单向数据通信)


05ef8e31b159caccf587c0c3578fde0c.png

父进程创建出子进程

e0e83e3e2a4cdc5d0f7854d425faa0f0.png

命名管道

父进程关闭读,子进程关闭写(根据需求可自行关闭读/写),必须一个负责读一个负责写


30ac1d2a2bca81861c12382a83202150.png

匿名管道的读写特征:

  1. 当写的速度比读的速度慢时,会导致管道中没有数据,默认会将读的进程阻塞,等待管道有数据
  2. 当读的速度比写的速度慢时,会导致管道空间写满,默认会将写的进程阻塞等待读端读取
  3. 当写关闭后,读端会将数据读完,可由用户自行设置读完后的操作
  4. 当读关闭后,写就失去了意义,操作系统会发信号终止写的进程
int main(){
    //1、创建管道文件,打开读写端
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);
    //2、创建子进程
    pid_t id = fork();
    assert(id >= 0);
    //3、根据需求删除进程的读或写,确保单向传输
    //父进程读,子进程写
    if(id == 0){
        //子进程
        //子进程关闭读
        close(fds[0]);
        int count = 0;
        while(1){
            char buff[1024];
            //写入字符串
            snprintf(buff, sizeof(buff), "I am child -> parent: %d\n", ++count);
            write(fds[1], buff, strlen(buff));
            //每隔一秒写一次
            sleep(1);
        }
        exit(0);
    }
    //父进程
    //父进程关闭写
    close(fds[1]);
    //父进程读
    while(1){
        char buff[1024];
        //在管道文件中读取到父进程的buff
        ssize_t i = read(fds[0], buff, sizeof(buff) - 1);
        //将独到的字符串最后加上\0
        if(i > 0)
            buff[i] = 0;
        cout << "parent: " << buff << endl;
    }
    //等待
    n = waitpid(id, nullptr, 0);
    assert(id == n);
    return 0;
}

命名管道

创建一个以权限p开头的文件,进程可以往文件里写入也可以从文件里读取,实现通信

命名管道文件根据名称具有唯一性,进程可以通过路径 + 文件名 找到命名管道文件,从而实现通信

管道的读端在打开文件后不会继续往后运行,直至管道的写端也打开文件后才会继续运行

命名管道的通信需要两个不相干的进程来实现,如下代码示例

name_pipe.hpp

#pragma once
#include<iostream>
#include<string>
#include<cerrno>
#include<cassert>
#include<cstring>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
using namespace std;
#define NAME_PIPE "./MyPipe"//宏定义管道文件名字与路径
//创建管道文件
bool CreatFilePipe(const string& path){
    umask(0);
    int n = mkfifo(path.c_str(), 0666);
    if(n == 0)
        return true;
    else{
        std::cout << errno << " " << strerror(errno) << endl;
        return false;
    }
}
//删除管道文件
void RemoveFifo(const string& path){
    assert(unlink(path.c_str()) == 0);
}

Read.cc

#include"name_pipe.hpp"
using namespace std;
//读取
int main(){
    //创建文件
    //读端负责创建管道文件
    bool flags = CreatFilePipe(NAME_PIPE);
    assert(flags);
  //创建成功后,打开管道文件
    int fd = open(NAME_PIPE, O_RDONLY);
    if(fd < 0) exit(1);
    char buff[1024];
    while(1){
      //读取管道文件中的数据
        ssize_t s = read(fd, buff, sizeof(buff) - 1);
        //读取成功
        if(s > 0){
            buff[s] = 0;
            std::cout << "Read: " << buff << endl;
        }
        //没有读到数据
        else if(s == 0){
            std::cout << "stop" << endl;
            break;
        }
        //读取出错
        else
            break;
    }
    close(fd);
  //删除管道文件
    RemoveFifo(NAME_PIPE);
    return 0;
}

Write.cc

#include"name_pipe.hpp"
using namespace std;
//写入
int main(){
  //因为读端已经有创建除了管道文件,写端只需要打开即可
    int fd = open(NAME_PIPE, O_WRONLY);
    if(fd < 0) exit(1);
    string str;
    while(1){
      //往管道文件里写入数据
        getline(cin, str);
        assert(write(fd, str.c_str(), strlen(str.c_str())) == strlen(str.c_str()));
    }
    close(fd);
    return 0;
}


cca0b8c12baa7f660ca22f51ab34d4ce.png

当写端写入数据后,读端就会立即读取。命名管道与匿名管道最大区别就在于, 匿名管道是用于有血缘关系的进程之间,而命名管道适用于毫不相干的进程之间


目录
相关文章
|
1天前
|
人工智能 监控 Linux
【Linux】进程控制深度了解(下)
【Linux】进程控制深度了解(下)
18 6
|
1天前
|
存储 缓存 安全
【Linux】冯诺依曼体系结构与操作系统及其进程
【Linux】冯诺依曼体系结构与操作系统及其进程
9 1
|
1天前
|
缓存 监控 关系型数据库
深入理解Linux操作系统的内存管理机制
【7月更文挑战第11天】在数字时代的浪潮中,Linux操作系统凭借其强大的功能和灵活性,成为了服务器、云计算以及嵌入式系统等领域的首选平台。内存管理作为操作系统的核心组成部分,对于系统的性能和稳定性有着至关重要的影响。本文将深入探讨Linux内存管理的基本原理、关键技术以及性能优化策略,旨在为读者提供一个全面而深入的理解视角,帮助开发者和系统管理员更好地优化和管理Linux系统。
|
2天前
|
算法 安全 调度
深入理解操作系统:进程调度与内存管理
【7月更文挑战第10天】本文将深入探讨操作系统的核心机制,即进程调度和内存管理。我们将从理论和实践的角度出发,解释这些机制如何影响系统性能和用户体验。通过分析不同的调度算法和内存分配策略,我们旨在揭示操作系统设计背后的复杂性和精妙之处。
|
7天前
|
小程序 Linux
【编程小实验】利用Linux fork()与文件I/O:父进程与子进程协同实现高效cp命令(前半文件与后半文件并行复制)
这个小程序是在文件IO的基础上去结合父子进程的一个使用,利用父子进程相互独立的特点实现对数据不同的操作
|
7天前
|
SQL 自然语言处理 网络协议
【Linux开发实战指南】基于TCP、进程数据结构与SQL数据库:构建在线云词典系统(含注册、登录、查询、历史记录管理功能及源码分享)
TCP(Transmission Control Protocol)连接是互联网上最常用的一种面向连接、可靠的、基于字节流的传输层通信协议。建立TCP连接需要经过著名的“三次握手”过程: 1. SYN(同步序列编号):客户端发送一个SYN包给服务器,并进入SYN_SEND状态,等待服务器确认。 2. SYN-ACK:服务器收到SYN包后,回应一个SYN-ACK(SYN+ACKnowledgment)包,告诉客户端其接收到了请求,并同意建立连接,此时服务器进入SYN_RECV状态。 3. ACK(确认字符):客户端收到服务器的SYN-ACK包后,发送一个ACK包给服务器,确认收到了服务器的确
|
8天前
|
消息中间件 分布式计算 网络协议
从管道路由到共享内存:进程间通信的演变(内附通信方式经典面试题及详解)
进程间通信(Inter-Process Communication, IPC)是计算机科学中的一个重要概念,指的是运行在同一系统或不同系统上的多个进程之间互相发送和接收信息的能力。IPC机制允许进程间共享数据、协调执行流程,是实现分布式系统、多任务操作系统和并发编程的基础。
从管道路由到共享内存:进程间通信的演变(内附通信方式经典面试题及详解)
|
21小时前
|
缓存 Linux 编译器
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
5 0
|
21小时前
|
存储 Linux 调度
【Linux】多线程——线程概念|进程VS线程|线程控制(上)
【Linux】多线程——线程概念|进程VS线程|线程控制(上)
9 0
|
21小时前
|
存储 NoSQL Unix
【Linux】进程信号(下)
【Linux】进程信号(下)
14 0