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

news2025/1/19 8:18:25

目录

命名管道(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就行了

我们简单的用一下就行了

 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.coloradmin.cn/o/1920062.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

9717 取数对弈

首先&#xff0c;我们需要初始化两个数组&#xff0c;一个用于存储输入的数列a[]&#xff0c;另一个用于动态规划过程中存储中间结果的二维数组dp[][]。dp[i][j]表示从数列的第i个数到第j个数时&#xff0c;当前玩家&#xff08;甲方先手&#xff09;能够获得的最大得分。 接下…

2023 N1CTF-n1canary

文章目录 参考n1canary模板类和模板函数make_unique和unique_ptrstd::unique_ptr示例&#xff1a; std::make_unique示例&#xff1a; 结合使用示例 operator->getrandom逆向源码思路exp 参考 https://nese.team/posts/n1ctf2023/ n1canary 模板类和模板函数 template &…

从汇编层看64位程序运行——栈帧(Stack Frame)入门

在《从汇编层看64位程序运行——程序中的栈(Stack)结构及其产生的历史原因》一文中&#xff0c;我们讲解了X86体系架构下&#xff0c;程序的栈结构的特点。本文将介绍另外一个非常重要的结构——栈帧。 A stack frame, often just called a ‘frame,’ is a section of the sta…

大众汽车入职SHL在线测评、英语口语、招聘笔试如何通过、考点分析|备考建议

大众汽车入职在线测验真题考点分析&#xff0c;通过技巧&#xff1f; 大众汽车集团&#xff08;中国&#xff09;在招聘过程中&#xff0c;认知能力测试是评估候选人是否适合某个职位的重要环节。候选人会收到带有线上测评链接的邮件&#xff0c;测评包括胜任力潜力测试(Compe…

MySQL数字相关数据处理函数

目录 1. 随机数生成 rand ( ) 2. 四舍五入 round&#xff08;&#xff09; 3. 舍去 truncate ( ) 4. 向上/下取整 5. 空处理 ifnull&#xff08; x , y &#xff09; 1. 随机数生成 rand ( ) rand ( ) 生成 0 到 1 的随机数&#xff1b; rand ( x ) 生成 0 到 1 的随机数…

Unity之Text组件换行\n没有实现+动态中英互换

前因&#xff1a;文本中的换行 \n没有换行而是打印出来了&#xff0c;解决方式 因为unity会默认把\n替换成\\n 面板中使用富文本这个选项啊 没有用 m_text.text m_text.text.Replace("\\n", "\n"); ###动态中英文互译 using System.Collections; using…

Redis分布式锁-Redisson可重入锁原理的个人见解。

记录Redisson可重入锁的个人见解。 文章目录 前言一、什么叫做锁的重入&#xff1f;二、Redisson可重入锁原理 前言 ⁣⁣⁣⁣ ⁣⁣⁣⁣ 之前在写项目的时候&#xff0c;注意到Redisson可重入锁的一个问题&#xff0c;随即在网上搜索其对应的资料&#xff0c;下面就记录一下个…

nfs共享存储配置

目录 一.存储和NFS共享 1.存储的类型分为三种 2.三种存储架构的应用场景 二.NFS共享存储服务 1.NFS简介 2.NFS存储 3.NFS原理 4.软件介绍 三.搭建NFS服务器 1.搭建 2.使用权限&#xff1a; 读写权限 属主&#xff0c;属组权限 客户端创建文件指向同一属主和属组 …

昇思25天学习打卡营第23天|基于MindSpore通过GPT实现情感分类

1. 学习内容复盘 %%capture captured_output # 实验环境已经预装了mindspore2.2.14&#xff0c;如需更换mindspore版本&#xff0c;可更改下面mindspore的版本号 !pip uninstall mindspore -y !pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspore2.2.14 I…

Python骨架肌体运动学数学模型

&#x1f3af;要点 &#x1f3af;运动学矢量计算 | &#x1f3af;跳远的运动学计算 | &#x1f3af;关节肢体运动最小加加速度模型 | &#x1f3af;膝关节和踝关节角度二维运动学计算 | &#x1f3af;上下肢体关节连接运动链数学模型 | &#x1f3af;刚体连接点速度加速度计算…

Qt图形与图片(Qt位置相关函数、Qt基础图形的绘制、双缓冲机制、显示SVG格式图片)

此篇文章介绍几种主要位置函数及其之间的区别&#xff0c;以及各种与位置相关函数的使用场合&#xff1b;然后&#xff0c;通过一个简单绘图工具实例&#xff0c;介绍利用QPainter和QPainterPath两种方法绘制各种基础图形&#xff1b;最后&#xff0c;通过几个实例介绍如何利用…

【JVM】对象的生命周期一 | 对象的创建与存储

Java | 对象的生命周期1-对象的创建与存储 文章目录 前言对象的创建过程内存空间的分配方式方式1 | 指针碰撞方式2 | 空闲列表 线程安全问题 | 避免空间冲突的方式方式1 | 同步处理&#xff08;加锁)方式2 | 本地线程分配缓存 对象的内存布局Part1 | 对象头Mark Word类型指针 P…

昇思25天学习打卡营第1天|初步了解

1在昇思平台上申请过相关资源之后&#xff0c;将示例代码粘贴到输入框内。可以在下图中创建一个新的文档。 2不过初次运行的时候会遇到一个问题&#xff0c;点击运行的时候会出现新的输入框&#xff0c;而不是直接运行。遇到此问题等待就可以了&#xff0c;或者稍微等一下再运…

linux系统判断网络物理连接状态

最近发现 /sys/class/net/ 似乎可以获取网口物理连接状态 于是乎在T113i主板上进行了测试。当前是双网口交换机芯片。如图所示&#xff1a; 具体的是 eth0和eth2是单网口&#xff0c;eth1是交换机芯片接的四个网口。 对于carrier 下面对单网口和交换机芯片的网口进行测试。命…

鸿蒙语言基础类库:【@ohos.util.Vector (线性容器Vector)】

线性容器Vector 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 Vect…

Perl语言简介

1.简介 Perl 是 Practical Extraction and Report Language 的缩写&#xff0c;可翻译为"实用报表提取语言"。   Perl 是高级、通用、直译式、动态的程序语言。   Perl 最初的设计者为拉里沃尔&#xff08;Larry Wall&#xff09;&#xff0c;于1987年12月18日发…

python+pygame实现五子棋人机对战之三

上回讲过&#xff1a; pythonpygame实现五子棋人机对战之一 pythonpygame实现五子棋人机对战之二 界面已经有了&#xff0c;并且可以支持鼠标操作选择菜单和人机对战开始下棋了&#xff0c;那电脑是如何应手落子呢&#xff1f;以下内容是通用的类&#xff0c;全部放在utils.…

SQL优化之深分页

SQL优化之深分页 我们都知道&#xff0c;大型项目中的SQL语句&#xff0c;应该尽量避免深分页。 那么问题就来了&#xff1a; 深分页的性能差在哪&#xff1f;什么方案能避免深分页呢&#xff1f; 什么是深分页 深分页&#xff0c;即SQL查询过程中&#xff0c;使用的页数过…

无需构建工具,快速上手Vue2 + ElementUI

无需构建工具&#xff0c;快速上手Vue2 ElementUI 在前端开发的世界中&#xff0c;Vue.js以其轻量级和易用性赢得了开发者的青睐。而Element UI&#xff0c;作为一个基于Vue 2.0的桌面端组件库&#xff0c;提供了丰富的界面组件&#xff0c;使得构建美观且功能丰富的应用变得…

第6章 IT服务运营管理

第6章 IT服务运营管理 6.1 概述 大量企业的实践表明&#xff0c;IT服务运营方面的问题更多的不是来自产品或技术&#xff08;如硬件、软件、网络、电力故障等&#xff09;方面&#xff0c;而是来自管理方面。IT服务的提供者&#xff0c;无论是企业内部的IT部门&#xff0c;还…