【Linux系统】命名管道 && 共享内存

命名管道

原理

匿名管道是通过父子进程的继承关系来实现通信的,那如果是两个毫不相干的两个进程呢???

而命名管道就可以实现,命名管道是通过创建一个管道文件,而这个管道文件由两个进程共同使用,这其实就是通过文件路径让两个进程看到了同一份资源!!!

常用接口

mkfifo --- 创建管道文件

 pathname:路径字符串

mode:权限,一般0666

 unlink --- 删除管道文件

pathname:路径字符串

指令

mkfifo:创建一个指定管道文件

unlink 文件:删除一个指定管道文件

使用

Makefile

.PHONY:all
all:client server

client:client.cc
	@g++ -o $@ $^ -std=c++11
server:server.cc
	@g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -rf client server

NamedPipe.hpp

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

const std::string comm_path = "./NamedPipe";
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096

class NamedPipe
{
private:
    bool OpenNamedPipe(int mode)
    {
        _fd = open(_fifo_path.c_str(), mode);
        if (_fd < 0)
            return false;
        return true;
    }

public:
    NamedPipe(const std::string &fifo_path, int who)
        : _fifo_path(fifo_path), _id(who), _fd(DefaultFd)
    {
        if (_id == Creater)
        {
            int res = mkfifo(_fifo_path.c_str(), 0666);
            if (res != 0)
            {
                perror("mkfifo");
            }
            std::cout << "creater create named pipe" << std::endl;
        }
    }
    bool OpenForRead()
    {
        return OpenNamedPipe(Read);
    }
    bool OpenForWrite()
    {
        return OpenNamedPipe(Write);
    }
    int ReadNamedPipe(std::string *out)
    {
        char buffer[BaseSize];
        int n = read(_fd, buffer, sizeof(buffer));
        if (n > 0)
        {
            buffer[n] = 0;
            *out = buffer;
        }
        return n;
    }
    int WriteNamedPipe(std::string &in)
    {
        return write(_fd, in.c_str(), in.size());
    }
    ~NamedPipe()
    {
        if (_id == Creater)
        {
            int res = unlink(_fifo_path.c_str());
            if (res != 0)
            {
                perror("unlink");
            }
            std::cout << "creater free named pipe" << std::endl;
        }
        if (_fd != DefaultFd)
            close(_fd);
    }

private:
    const std::string _fifo_path;
    int _id;
    int _fd;
};

client.cc

#include"NamedPipe.hpp"

int main()
{
    NamedPipe fifo(comm_path, User);
    if(fifo.OpenForWrite())
    {
        std::cout << "client open named pipe done" << std::endl;
        while(true)
        {
            std::cout << "Please Enter>";
            std::string message;
            std::getline(std::cin, message);
            fifo.WriteNamedPipe(message);
        }
    }
    return 0;
}

server.cc

#include"NamedPipe.hpp"

//server read --- 管理命名管道的整个生命周期
int main()
{
    // 对于读端而言,如果我们打开文件,但是写还没来,我会阻塞在open调用中,直到对方打开
    // 进程同步
    NamedPipe fifo(comm_path, Creater);
    if(fifo.OpenForRead())
    {
        std::cout << "server open named pipe done" << std::endl;
        while(true)
        {
            std::string message;
            int n = fifo.ReadNamedPipe(&message);
            if(n > 0)
            {
                std::cout << "Client Say>" << message << std::endl;
            }
            else if(n == 0)
            {
                std::cout << "Client quit, Server Too!" << std::endl;
                break;
            }
            else
            {
                std::cout << "fifo.ReadNamedPipe Error" << std::endl;
                break;
            }
        }
    }
    return 0;
}

总结

匿名管道是通过父子进程继承关系来看到同一份资源。

命名管道是通过文件路径来看到同一份资源。

共享内存

其实什么匿名管道命名管道,以及接下来的共享内存都是本地通信。

本地通信方案的代码:System V IPC 包括

共享内存

消息队列

信号量

原理

共享内存也就是共享同一份内存!!!如何理解???

原理图

 理解:

  1. 所有的刚才的操作都是OS做的。
  2. OS提供上面1、2步骤的系统调用,供用户进程A、B进行调用 --- 系统调用
  3. AB进程可以共享一份内存,还有很多CD、EF...共享内存在系统中可以存在很多份,供不同个数,不同进程同时进行通信!
  4. OS注定了要对共享内存进行管理!--- 先描述,在组织 -- 共享内存,不是简单的一段内存空间,也要有描述并管理共享内存的数据结构和匹配的算法!
  5. 共享内存 = 内存空间(数据) + 共享内存的属性!

常用接口

shmget --- 获取共享内存

功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字(用户形成)
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmflag的取值

IPC_CREAT:如果你要创建的共享内存不存在,就创建,若存在,获取该共享内存并返回(总能获得一个标识)

IPC_EXCL:单独使用没有意义,与IPC_CREAT组合才有意义

IPC_CREAT | IPC_EXCL:如果你要创建的共享内存不存在,就创建,若存在,出错返回(如果成功返回了就说明shm是全新的)

shmctl函数

功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

cmd的三个可取值

IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值。

IPC_SET:在进程有足够权利的情况下,把共享内存当前的关联值设置为shmid_ds数据结构中给出的值。

IPC_RMID:删除共享内存段。

ftok --- 可以为我们形成一个随机的key来标识我们的共享内存。

 我(进程)怎么知道,OS的共享内存是否存在呢?--- 唯一性标识符

key vs shmid

key:属于用户形成,内核使用的一个字段,用户不能使用key来进行shm的管理,内核进行区分shm的唯一性的。(struct file*)

 shmid:内核给用户返回的一个标识符,用来进行用户级对共享内存进行管理的id值(fd)

 shmat

功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

返回共享内存的起始地址,我们可以让其与malloc返回值类比,都是void*的返回。

shmdt

功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
 

指令

ipcs -m 查看当前所有的共享内存具体信息

 共享内存是OS级的,进程结束不会随进程释放!!!--- 一直存在,直到系统重启

所以只能手动释放(指令或者其他系统调用)  --- 生命周期随内核 ,文件生命周期随进程

我们可以使用命令ipcs -m来查看共享内存

ipcrm -m --- 删除指定共享内存

使用

shm.hpp

#ifndef __SHM__HPP__
#define __SHM_HPP__

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

const int gCreater = 1;
const int gUser = 2;
const int gShmSize = 4096;
const std::string gpathname = "/root/2024_code/shm";
const int gproj_id = 0x66;

class Shm
{
private:
    key_t GetCommKey()
    {
        key_t k = ftok(_pathname.c_str(), _proj_id);
        if (k < 0)
        {
            perror("ftok");
        }
        return k;
    }
    int GetShmHelper(key_t key, int size, int flag)
    {
        int shmid = shmget(key, size, flag);
        if (shmid < 0)
        {
            perror("shmget");
        }
        return shmid;
    }
    void *AttachShm()
    {
        if (_addrshm != nullptr)
            DetachShm(_addrshm);
        void *shmaddr = shmat(_shmid, nullptr, 0);
        if (shmaddr == nullptr)
        {
            perror("shmat");
        }
        std::cout << "who:" << RoleToString(_who) << " attach shm..." << std::endl;
        return shmaddr;
    }
    std::string RoleToString(int who)
    {
        if (who == gCreater)
            return "gCreater";
        else if (who == gUser)
            return "gUser";
        else
            return "None";
    }
    void DetachShm(void *shmaddr)
    {
        if (shmaddr == nullptr)
            return;
        shmdt(shmaddr);
        std::cout << "who:" << RoleToString(_who) << " detach shm..." << std::endl;
    }

public:
    Shm(const std::string &path, int proj_id, int who)
        : _pathname(path), _proj_id(proj_id), _who(who), _addrshm(nullptr)
    {
        _key = GetCommKey();
        if (_who == gCreater)
            GetShmForCreate();
        else if (_who == gUser)
            GetShmForUser();
        _addrshm = AttachShm();
        std::cout << "shmid:" << _shmid << std::endl;
        std::cout << "key:" << ToHex(_key) << std::endl;
    }
    ~Shm()
    {
        if (_who == gCreater)
        {
            int res = shmctl(_shmid, IPC_RMID, nullptr);
        }
        std::cout << "shm remove done..." << std::endl;
    }
    std::string ToHex(key_t key)
    {
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "0x%x", key);
        return buffer;
    }
    bool GetShmForCreate()
    {
        if (_who == gCreater)
        {
            _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
            std::cout << "shm create done..." << std::endl;

            if (_shmid >= 0)
                return true;
        }
        return false;
    }
    bool GetShmForUser()
    {
        if (_who == gUser)
        {
            _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);
            std::cout << "shm get done..." << std::endl;

            if (_shmid >= 0)
                return true;
        }
        return false;
    }
    void Zero()
    {
        if (_addrshm)
        {
            memset(_addrshm, 0, gShmSize);
        }
    }
    void *Addr()
    {
        return _addrshm;
    }

private:
    key_t _key;
    int _shmid;

    std::string _pathname;
    int _proj_id;

    int _who;
    void *_addrshm;
};

#endif

server.cc

#include"shm.hpp"
#include"NamedPipe.hpp"

int main()
{
    //1.创建共享内存
    Shm shm(gpathname, gproj_id, gCreater);
    char* shmaddr = (char*)shm.Addr();

    //2.创建管道
    NamedPipe fifo(comm_path, Creater);
    fifo.OpenForRead();

    while(true)
    {
        std::string temp;
        fifo.ReadNamedPipe(&temp);
        std::cout << "shm memory content:" << shmaddr << std::endl;
    }
    sleep(5);
    return 0;
}

client.cc

#include"shm.hpp"
#include"NamedPipe.hpp"

int main()
{
    //创建共享内存
    Shm shm(gpathname, gproj_id, gUser);
    shm.Zero();
    char* shmaddr = (char*)shm.Addr();

    //2.打开管道
    NamedPipe fifo(comm_path, User);
    fifo.OpenForWrite();

    //当成string
    char ch = 'A';
    while(ch <= 'Z')
    {
        shmaddr[ch - 'A'] = ch;
        
        std::string temp = "wakeup";
        std::cout << "add " << ch << " into Shm, " << "wakeup reader" << std::endl;
        fifo.WriteNamedPipe(temp);

        sleep(2);
        ch++;
    }
    sleep(5);
    return 0;
}

总结

缺点:共享内存不提供对共享内存的任何保护机制 --- 数据不一致问题

优点:我们在访问共享内存的时候,没有使用任何系统调用,共享内存是所有进程间通信IPC,速度最快的,因为,共享内存大大减少了数据的拷贝次数!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花影随风_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值