【Linux】进程间通信——命名管道和共享内存

目录

命名管道(named pipe)

命令行中使用

代码中使用

共享内存(shared memory)

shmget

ipcs命令

shmctl

shmat/shmdt

简单通信


命名管道(named pipe)

之前我们说了匿名管道,但是匿名管道有一个特点就是只能具有血缘关系的进程间可以进行通信,那如果我想让两个毫无关系的进程进行通信,该怎么办呢?

还是不要忘记进程间通信的本质,就是让两个进程看到同一份资源,如果两个进程毫无关系,那么我们可以让它们看到某个路径下的一个文件,这样它们就看到同一份资源了。并且,因为这类文件是唯一路径下的唯一文件,它是可以确定的,所以叫命名文件,创建的管道也就叫命名管道

不同的进程以不同的方式打开同一个文件,那么这两个进程的文件描述符表中的指针指向不同的文件结构体对象,但是不同的文件结构体对象指向的文件的缓冲区是一样的,所以,两个进程可以看到相同的内容。

命令行中使用

我们可以首先在命令行中用一下管道文件,先查一下手册

man mkfifo

这个命令的基本使用就是mkfifo + 文件名

我们也可以看到这个文件的类型是p,表示pipe,管道文件

创建完管道文件后就可以用两个进程 (命令就是进程),一个往管道文件中写,一个往管道文件中读(可以echo向文件中写,cat从文件中读)

现象就是一个往管道文件中写,如果没人读,那么进程就会阻塞等待;同理,一个往管道文件中读,如果没人写,那么进程也会阻塞等待

代码中使用

上面是在命令行上进行的操作,如果我们要用代码来操作就需要用相关的接口

man 3 mkfifo

第一个参数就是路径加文件名,第二个参数就是要创建的文件的权限

其实我们的任务就是创建一个管道文件,然后用文件操作(open、close、read、write)的方式向管道文件中写和读

我们可以创建一个服务器端和一个客户端,把客户输入的内容通过命名管道传给服务器,创建下面几个文件:

Comm.hpp中放共同的用得到的代码,PipeClient.cc放客户端的代码,PipeServer.cc放服务器端的代码,我们就来非常简单的模拟实现一下

基本代码如下,代码的效果就是先开服务器端,再开客户端,客户端写什么,服务器端都可以收到

//Comm.hpp
#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include<cerrno>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
using namespace std;

#define PATH "./fifo"
#define Mode 0666
class Fifo
{
    public:
    Fifo(const char*path)
    :_path(path)
    {
        umask(0);
        int n=mkfifo(_path.c_str(),Mode);
        if(n==0)
        {
            cout<<"touch fifo success"<<endl;
        }
        else
        {
            cout<<"fail to fifo errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;
        }
    }
    ~Fifo()
    {
        int n=unlink(PATH);
        if(n==0)
        {
            cout<<"fifo remove"<<endl;
        }
        else
        {
            cout<<"fail to remove fifo errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;
        }
    }
    private:
    string _path;
};
//PipeServe.cc
#include"Comm.hpp"

int main()
{
    Fifo fifo(PATH);
    int rfd=open(PATH,O_RDONLY);//如果要是读先open,没人写,就要阻塞等待在这
    if(rfd<0)
    {
            cout<<"fail to read open fifo errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;
            return 1;
    }
    cout<<"read open success"<<endl;
    char buffer[1024]={0};
    while(1)
    {
        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 
        {
            cout<<"fail to read errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;
            break;
        }
        
    }
close(rfd);
    return 0;
}
//PipeClient.cc
#include"Comm.hpp"

int main()
{
    int wfd=open(PATH,O_WRONLY);
    if(wfd<0)
    {
            cout<<"fail to write open fifo errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;
            return 1;
    }
    string inbuffer;
    while(1)
    {
        cout<<"please input your message"<<endl;
        getline(cin,inbuffer);
        if(inbuffer=="quit")break;
        int n=write(wfd,inbuffer.c_str(),inbuffer.size());
        if(n<0)
        {
            cout<<"fail to write errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;
            break;
        }
    }
     close(wfd);
    return 0;
}

共享内存(shared memory)

之前说的无论是匿名管道还是命名管道都是管道,它们都是基于文件实现的,下面我们要说的是system V版本下的进程间通信的方式共享内存,消息队列,信号量

下面我们先说什么是共享内存

在谈论进程间通信时永远不要忘掉进程间通信的本质:让不同的进程看到同一份资源。

所以,共享内存就是在物理内存中开辟一段空间,然后将这块空间通过页表映射到各个进程的地址空间(虚拟内存)的共享区中,这样,不同的进程就可以看到同样的一份资源了。

有了上面的理论基础我们就知道了我们需要学习什么样的系统调用,有申请共享内存的,有把共享内存进行挂接到地址空间的,有去挂接的,也有删除共享内存的。当然这只是我们粗略的去描述,具体有啥我们下面来看:

shmget

首先要介绍的就是申请共享内存的系统调用

man shmget

它如果成功了就返回共享内存的一个标识符,这个跟下面的key不同,这个标识符是给人看的,是我们通过这个标识符唯一确定一个共享内存

它有三个参数,第一个是在内核中唯一标识一个共享内存的key值,就是内核通过这个值唯一确定一个共享内存,因为内核中存在多个共享内存这是正常的。具体说这个值是怎么创建的呢,当然我们可以自己给一个值,但是我们给的值容易过于简单,容易重复,所以就提供了一个接口

man ftok

这个函数有两个参数,其实我们可以随便给一个路径,一个proj_id,只要它们是唯一的即可,这样就可以生成唯一的key值,不同的进程间只要传入相同的路径和proj_id,它们就能获得相同的key值,找到同一块共享内存。

第二个参数就是你要创建的共享内存的大小,单位是字节

第三个参数是一些选项,你可以设置,基本的选项有:

IPC_CREAT:共享内存不存在就创建,如果存在就直接获取

IPC_EXCL:不能单独使用,没意义

IPC_CREAT | IPC_EXCL:共享内存不存在就创建,存在就出错返回

但是在这些选项后面还有按位或上权限,一般给0666

我们简单的写一个使用它们的代码

#include<iostream>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<cstring>
#include<cerrno>
using namespace std;
#define PATH "/home/user100"
#define proj_id 111
#define SIZE 4096
int main()
{
    key_t key=ftok(PATH,proj_id);
    if(key<0)
    {
        cout<<"ftok fail errno is "<<errno<<" error string is "<<strerror(errno)<<endl;
        return 1;
    }
    int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
    if(shmid<0)
    {
        cout<<"shmid fail errno is "<<errno<<" error string is "<<strerror(errno)<<endl;
        return 2;
    }
    cout<<"make shm success"<<endl;
    return 0;
}

然后就运行,发现,第一次运行成功,第二次运行失败

因为共享内存在程序结束后并不会删除,也就是说,共享内存的生命周期随内核,我们关掉xShell再开启也不会消失,因为云服务器是一直运行着的,除非我们重启系统共享内存才会消失

ipcs命令

我们可以通过命令和代码删除共享内存

图中的perm其实就是我们代码中设置的权限(permission)

nattch是这个共享内存挂接(attach)到了几个进程中

并且我们要说明的是内核中共享内存的大小是以4kb为基本单位的,也就是说如果我们给4097个字节,它也是会申请8kb的大小的,只不过会显示4097个字节

ipcs可以查看目前都有哪些消息队列,共享内存和信号量,shmid为1的共享内存就是我们刚才创建的,添加不同的选项可以分别查看

-q:显示消息队列的信息。(massage queue)

-m:显示共享内存的信息。(shared memory)

-s:显示信号量的信息。(semaphore array)

ipcrm -m shmid 就是删除共享内存,要是删消息队列和信号量也是以此类推

shmctl

下面就是用代码删除了,我们需要用到下面的接口

man shmctl

这个其实是对共享内存进行控制,当然了,控制也包括删除

第一个参数就是shmid,就是shmget的返回值

第二个参数是一些选项,不过我们删除要用到的选项是IPC_RMID

第三个参数是你传一个这个类型的结构体指针过去,然后它把共享内存的信息给你传回来,我们一般不需要信息的话给个nullptr就行了

我们也可以看一下内核都暴露给我们什么样的信息:

struct shmid_ds中第一个内容就是第二张图片的struct ipc_perm。

我们简单的用一下就行了

 int ret=shmctl(shmid,IPC_RMID,nullptr);
    if(ret<0)
    {
        cout<<"remove shm fail errno is "<<errno<<" error string is "<<strerror(errno)<<endl;
        return 3;
    }
    cout<<"remove shm success,shmid is "<<shmid<<endl;

shmat/shmdt

上面我们讲了共享内存的创建和删除,下面我们就要说它该如何挂接和去挂接到进程的地址空间中呢?

man shmat(attach)挂接

man shmdt(detach)去关联

shmat第二个参数是一个地址,就是你想挂接到地址空间的哪里,但是我们一般给nullptr,就让OS随便找合适的空间即可

第三个仍然是一些选项,我们一般给0即可

返回值就是,挂接到的地址空间的首地址

shmdt参数就是shmat的返回值

我们简单来用一下

void *addr = shmat(shmid, nullptr, 0);
    if ((long long)addr == -1)
    {
        cout << "shmat fail errno is " << errno << " error string is " << strerror(errno) << endl;
    }
    cout << "shmat success" << endl;
    sleep(5);
    int ret1 = shmdt(addr);
    if (ret1 < 0)
    {
        cout << "shmdt fail errno is " << errno << " error string is " << strerror(errno) << endl;
    }
    cout << "shmdt success" << endl;
    sleep(2);

简单通信

上面是一些基础的使用,下面我们就像命名管道写代码完成两个进程间简单的通信,我们还是创建那么几个文件

我们不得不说,共享内存是最快的进程间通信的方式,因为一个进程只需要把数据写入到它的地址空间中(其实就是写到了物理内存中),另一个进程就可以看到,这是它的优点,但是共享内存是不提供进程间协同的机制的,就是你写你的我读我的,这就会导致你可能还没写完我就读了,这就导致信息丢失。但是我们知道管道是提供的,于是呢,我们可以利用管道的特点(写端不写,读端阻塞等待)实现共享内存的同步机制,基本代码如下:

//server.cc
#include "Comm.hpp"
#include "pipe.hpp"
int main()
{
    key_t key = Getkey();//获取key
    int shmid = ServerGetshm(key);//创建共享内存
    void *addr = Attach(shmid);//挂接
    int *address = (int *)addr;
    Fifo fifo;//创建管道
    int rfd = Serveropenfifo();//以读方式打开文件
    for (int i = 0; i < 10; i++)
    {
        wait(rfd);//写端不写,就阻塞在这里
        int *tmp = address;
        cout << "Server get ";
        while (*(tmp) != 0)
        {
            cout << *(tmp);
            tmp++;
        }
        cout << endl;
    }
    Detach(addr);//去挂接
    removeshm(shmid);//删除共享内存
    return 0;
}
//client.cc
#include "Comm.hpp"
#include"pipe.hpp"
int main()
{
    key_t key = Getkey();//获取key
    int shmid = ClientGetshm(key);//获取共享内存
    void *addr = Attach(shmid);//挂接
    int *address = (int *)addr;
   int wfd= clientopenfifo();//以写方式打开文件
    for (int i = 0; i < 10; i++)
    {
        *(address + i) = i + 1;
        wakeup(wfd);//写完了一部分完整的数据就通知唤醒读端
        sleep(1);
    }
    Detach(addr);//去挂接
    return 0;
}
//comm.hpp
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <cstring>
#include <unistd.h>
#include <cerrno>
using namespace std;
#define PATH "/home/user100"
#define proj_id 111
#define SIZE 4096

key_t Getkey()
{
    key_t key = ftok(PATH, proj_id);
    if (key < 0)
    {
        cout << "ftok fail errno is " << errno << " error string is " << strerror(errno) << endl;
        return -1;
    }
    return key;
}

int ServerGetshm(key_t key)
{
    int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        cout << "Server Get shm fail errno is " << errno << " error string is " << strerror(errno) << endl;
        return -1;
    }
    cout << "make shm success" << endl;
    return shmid;
}
int ClientGetshm(key_t key)
{
    int shmid = shmget(key, SIZE, IPC_CREAT | 0666);
    if (shmid > 0)
        cout << "Client Get shm success" << endl;
    return shmid;
}

void *Attach(int shmid)
{
    void *addr = shmat(shmid, nullptr, 0);
    if ((long long)addr == -1)
    {
        cout << "shmat fail errno is " << errno << " error string is " << strerror(errno) << endl;
        return nullptr;
    }
    cout << "shmat success" << endl;
    return addr;
}

int Detach(void *addr)
{
    int ret1 = shmdt(addr);
    if (ret1 < 0)
    {
        cout << "shmdt fail errno is " << errno << " error string is " << strerror(errno) << endl;
        return -1;
    }
    cout << "shmdt success" << endl;
    return 0;
}

int removeshm(int shmid)
{
    int ret = shmctl(shmid, IPC_RMID, nullptr);
    if (ret < 0)
    {
        cout << "remove shm fail errno is " << errno << " error string is " << strerror(errno) << endl;
        return -1;
    }
    cout << "remove shm success,shmid is " << shmid << endl;
    return 0;
}
//pipe.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
using namespace std;

#define Pipepath "./fifo"
#define Mode 0666
class Fifo
{
public:
    Fifo(const char *path = Pipepath)
        : _path(path)
    {
        umask(0);
        int n = mkfifo(_path.c_str(), Mode);
        if (n == 0)
        {
            cout << "touch fifo success" << endl;
        }
        else
        {
            cout << "fail to fifo errno is " << errno << " strerror is " << strerror(errno) << endl;
        }
    }
    ~Fifo()
    {
        int n = unlink(Pipepath);
        if (n == 0)
        {
            cout << "fifo remove" << endl;
        }
        else
        {
            cout << "fail to remove fifo errno is " << errno << " strerror is " << strerror(errno) << endl;
        }
    }

private:
    string _path;
};

int Serveropenfifo()
{
    int rfd = open(Pipepath, O_RDONLY);
    if (rfd < 0)
    {
        cout << "fail to ropen fifo errno is " << errno << " strerror is " << strerror(errno) << endl;
        return 1;
    }
    return rfd;
}
int clientopenfifo()
{
    int wfd = open(Pipepath, O_WRONLY);
    if (wfd < 0)
    {
        cout << "fail to wopen fifo errno is " << errno << " strerror is " << strerror(errno) << endl;
        return 1;
    }
    return wfd;
}

void wakeup(int wfd)
{
    char ch='A';
    write(wfd,&ch,sizeof(ch));
}
void wait(int rfd)
{
    char buffer[10]={0};
    read(rfd,buffer,sizeof(buffer));
}

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

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

相关文章

Spring-Spring、IoC、DI、注解开发

1、Spring是什么 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。 Spring整体架构 Spring优点&#xff1a; Spring属于低侵入设计。IOC将对象之间的依赖关系交给Spring,降低组件之间的耦合&#xff0c;实现各个层之间的解耦&#xff0c;让我们更专注于业务…

【python】基于随机森林和决策树的鸢尾花分类

目录 引言 决策树&#xff08;Decision Tree&#xff09; 随机森林&#xff08;Random Forest&#xff09; 数据集 结果 代码实现 引言 随机森林&#xff08;Random Forest&#xff09;和决策树&#xff08;Decision Tree&#xff09;是两种在机器学习中广泛使用的分类和…

红色文化3D虚拟数字展馆搭建意义深远

在房地产与土地市场的浪潮中&#xff0c;无论是新城规划、乡村振兴&#xff0c;还是商圈建设&#xff0c;借助VR全景制作、虚拟现实和web3d开发技术打造的全链条无缝VR看房新体验。不仅极大提升了带看与成交的转化率&#xff0c;更让购房者足不出户&#xff0c;即可享受身临其境…

【填坑指南】PHP8报:Unable to load dynamic library ‘zip.so’ 错误

1.原因分析 这种情况多数发生在PHP安装时因为各种原因失败后&#xff0c;残余的库与最后安装的PHP版本不兼容导致的。 2.我的路径 一开始我按照以前摸索出来的安装PHP7.3的成功经验来编译方法安装PHP8.3&#xff0c;发现以前的套路已经失效了。反复重装PHP8.3失败后&#xf…

Sentinel 学习笔记

Sentinel 学习笔记 作者&#xff1a;王珂 邮箱&#xff1a;49186456qq.com 文章目录 Sentinel 学习笔记[TOC] 前言一、基础概念二、Sentinel控制台2.1 安装控制台2.2 簇点链路2.3 请求限流2.4 线程隔离2.5 服务降级2.6 服务熔断 三、Sentinel客户端3.1 原始Jar包客户端3.2 Sp…

python条件

条件语句 if语句 if...else语句 if...elif...else语句 嵌套 is is 是一个身份运算符&#xff0c;用于比较两个对象的身份&#xff0c;即它们在内存中的地址是否相同。这与比较两个对象是否相等的 运算符不同。 运算符比较的是两个对象的值是否相等。 比较对象 比较基本数据…

2024-07-12 Unity AI状态机1 —— 框架介绍

文章目录 1 有限状态机2 状态机实现框架2.1 StateMachine2.2 BaseState2.3 ...State2.4 IAIObject 3 框架类图 本文章参考 B 站唐老狮 2023 年直播内容。点击前往唐老狮 B 站主页。 1 有限状态机 ​ 有限状态机&#xff08;Finite - State Machine&#xff0c;FSM&#xff09…

linux的学习(四):磁盘,进程,定时,软件包的相关命令

简介 关于磁盘管理&#xff0c;进程管理&#xff0c;定时任务&#xff0c;软件包管理的命令的使用 磁盘管理类命令 du du 目录名&#xff1a; 查看文件和目录占用的磁盘空间 参数&#xff1a; -h&#xff1a;可以看到大小的单位&#xff0c;g,mb-a&#xff1a;还可以看到文…

日前光伏功率曲线预测

《利用 2DG&#xff32;A&#xff0d;BiLSTM 模型的日前光伏功率曲线预测方法》 利用2DGRA实现最佳历史相似日数据的获取&#xff0c;根据日功率曲线的波动性将总数据分为3类&#xff08;晴空条件、轻度非晴空条件和重度非晴空条件&#xff09;&#xff0c;根据3种分类&#x…

SpringCloud架构师面试

一、微服务是什么 1、基本概念 微服务是一种架构风格&#xff08;区别于单体架构、垂直架构、分布式架构、SOA架构&#xff09;&#xff0c;应用程序被划分为更小的、流程驱动的服务。 2、微服务的特征 轻量化&#xff1a;将复杂的系统或者服务进行纵向拆分&#xff0c;每个…

【自然语言处理】面向新冠肺炎的社会计算应用

面向新冠肺炎的社会计算应用 1 任务目标 1.1 案例简介 新冠肺炎疫情牵动着我们每一个人的心&#xff0c;在这个案例中&#xff0c;我们将尝试用社会计算的方法对疫情相关的新闻和谣言进行分析&#xff0c;助力疫情信息研究。本次作业为开放性作业&#xff0c;我们提供了疫情…

计算机网络之广域网

广域网特点: 主要提供面向通信的服务&#xff0c;支持用户使用计算机进行远距离的信息交换。 覆盖范围广,通信的距离远&#xff0c;需要考虑的因素增多&#xff0c; 线路的冗余、媒体带宽的利用和差错处理问题。 由电信部门或公司负责组建、管理和维护&#xff0c;并向全社会…

Access denied for user ‘root‘@‘localhost‘ (using password: YES)解决办法

在Spring配置数据源时&#xff0c;当使用Spring容器加载druid.properties数据库连接池配置文件时&#xff0c;容易碰到create connection SQLException, url: jdbc:mysql://127.0.0.1:3306/mydbs, errorCode 1045, state 28000 java.sql.SQLException: Access denied for user …

在JavaScript中,什么是解构赋值(destructuring assignment)?

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介在JavaScript中&#xff0c;什么是解构赋值&#xff08;destructuring assignment&#xff09;&#xff1f;1. 引言2. 解构赋值的概念3. 数组解构赋值3.1 基本语法3.2 跳过元素3.3 默认值3.4 交换变量值 4. 对象解构赋值4.1 基本语…

springboot系列教程(一):简介与入门案例(含源码)

一、SpringBoot简介 SpringBoot继承了Spring优秀的基因&#xff0c;上手难度小简化配置&#xff0c;提供各种默认配置来简化项目配置内嵌式容器简化Web项目&#xff0c;简化编码 Spring Boot 则会帮助开发着快速启动一个 web 容器&#xff0c;在 Spring Boot 中&#xff0c;只…

React学习笔记03-----手动创建和运行

一、项目创建与运行【手动】 react-scripts集成了webpack、bable、提供测试服务器 1.目录结构 public是静态目录&#xff0c;提供可以供外部直接访问的文件&#xff0c;存放不需要webpack打包的文件&#xff0c;比如静态图片、CSS、JS src存放源码 &#xff08;1&#xff09…

QT多线程下,信号槽分别在什么线程中执行,如何控制?

可以通过connect的第五个参数进行控制信号槽执行时所在的线程 connect有几种连接方式&#xff0c;直接连接、队列连接和 自动连接 直接连接&#xff08;Qt::DirectConnection&#xff09;&#xff1a;信号槽在信号发出者所在的线程中执行 队列连接&#xff08;Qt::QueuedConn…

whereis命令是 Linux 和类 Unix 系统中的一个命令行工具,用于定位二进制程序、源代码和手册页(man pages)的位置

文章目录 1、whereis2、实例 1、whereis whereis 命令是 Linux 和类 Unix 系统中的一个命令行工具&#xff0c;用于定位二进制程序、源代码和手册页&#xff08;man pages&#xff09;的位置。当你想要快速找到某个程序或命令的安装位置时&#xff0c;whereis 命令会非常有用。…

关于无法定位程序输入点 SetDefaultDllDirectories于动态链接库KERNEL32.dll 上 解决方法

文章目录 1. ERNEL32.dll 下载2. 解决方法 &#x1f44d; 个人网站:【 洛秋小站】 1. ERNEL32.dll 下载 Windows 7 在安装postman时报错缺少动态链接库,提示缺少.NET Framework,这是因为本地缺少相应的dll文件导致的&#xff0c;这时就需要下载ERNEL32.dll文件&#xff0c;在解…

MySQl高级篇-查询优化篇

SQL性能分析 SQL性能下降原因&#xff1a; 查询语句写的烂索引失效&#xff08;数据变更&#xff09;关联查询太多join&#xff08;设计缺陷或不得已的需求&#xff09;服务器调优及各个参数设置&#xff08;缓冲、线程数等&#xff09; SQL调优过程&#xff1a; 观察&…