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

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


目录
相关文章
|
10月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
229 0
|
10月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
298 0
|
10月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
178 0
|
9月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
2875 0
|
9月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
951 1
|
9月前
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
1130 0
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
1050 0
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
1158 1

热门文章

最新文章