前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)

【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)

作者头像
秦jh
发布2024-10-29 08:33:07
发布2024-10-29 08:33:07
24100
代码可运行
举报
文章被收录于专栏:c语言,c++c语言,c++
运行总次数:0
代码可运行

前言

💬 hello! 各位铁子们大家好哇。 今日更新了Linux进程间通信的内容 🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

命名管道

  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  • 命名管道是一种特殊类型的文件

从命令行上创建

如上图,当我们在终端1创建了一个命名管道后,往里面写东西,管道不会关闭,在终端2上发现,它的内存大小还是0。当我们在终端2打印出内容后,管道就自动关闭了。如下图:

如上图,当我们在终端1上循环写到管道时,终端2可以一直打印出管道里的内容。

程序里创建

返回值为0是成功,不为0就是失败。

如上图,用mkfifo函数创建管道。

unlink函数可以删除指定路径下的文件。 成功就返回0。如果要关闭管道,就可以用unlink。

下面是用命名管道进行进程间通信的例子:

namedPipe.hpp

代码语言:javascript
代码运行次数:0
运行
复制
#pragma once

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

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

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

public:
    NamePiped(const std::string &path, int who)
        : _fifo_path(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(const std::string &in)
    {
        return write(_fd, in.c_str(), in.size());
    }
    ~NamePiped()
    {
        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

代码语言:javascript
代码运行次数:0
运行
复制
#include "namedPipe.hpp"

// write
int main()
{
    NamePiped fifo(comm_path, User);
    if (fifo.OpenForWrite())
    {
        std::cout << "client open namd 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

代码语言:javascript
代码运行次数:0
运行
复制
#include "namedPipe.hpp"

// server read: 管理命名管道的整个生命周期
int main()
{
    NamePiped fifo(comm_path, Creater);
    // 对于读端而言,如果我们打开文件,但是写还没来,我会阻塞在open调用中,直到对方打开
    if (fifo.OpenForRead())
    {
        std::cout << "server open named pipe done" << std::endl;

        sleep(3);
        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;
}

匿名管道与命名管道的区别:

  • 匿名管道由pipe函数创建并打开
  • 命名管道由mkfifo函数创建,打开用open

命名管道是通过文件路径让不同进程看到同一份资源。 命名管道可以让两个毫不相干的进程进行进程间通信。

system V共享内存

system V IPC是一种本地通信方案。共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

共享内存在系统中可以同时存在多份,供不同对进程进行通信。 共享内存不是简单的一段内存空间,它也要有描述并管理共享内存的数据结构和匹配算法。

共享内存函数

shmget 函数

该函数是系统调用,操作系统提供系统调用,让我们创建共享内存。

功能:用来创建共享内存 参数: key:这个共享内存段名字(由用户形成) size:共享内存大小 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的 常见标志位组合和使用: IPC_CREAT 、IPC_EXCL

  • IPC_CREAT:如果你要创建的共享内存不存在,就创建。如果存在,就获取该共享内存并返回。
  • IPC_EXCL:单独使用没意义,只有和IPC_CREAT组合才有意义
  • IPC_CREAT | IPC_EXCL:如果你要创建的共享内存不存在,就创建。如果存在,就出错返回。(如果成功返回,意味着这shm是全新的)

key,用来标志共享内存,可以让进程a和b找到共享内存。 系统提供了随机生成key值的方法ftok 返回值:成功则返回共享内存段的标识码;失败返回-1

ftok不是系统调用,通过我们提供路径和id(这两个值可以随便写)帮我们生成一个key值。我们给a、b两个进程提供同样的路径和id,调用ftok,就能形成同样的key,就能看到同一个共享内存了。 返回值:成功了就返回key值,失败就返回-1。

共享内存的释放

共享内存不随着进程的结束而自动释放,需要我们手动释放(指令或者其他系统调用),否则会一直存在,直到系统重启。 共享内存的生命周期随内核,文件的生命周期随进程。

如何释放?如下图:

通过指令 ipcs -m 可以查共享内存。

删除共享内存可通过指令 ipcrm -m 数字(id) 来删除。注意数字是shmid。

shmctl 函数

如果不想用指令释放,可以用shmctl 函数进行,如下:

功能:用于控制共享内存 参数 shmid:由shmget返回的共享内存标识码 cmd:将要采取的动作(有三个可取值,如下图)

buf:指向一个保存着共享内存的模式状态和访问权限的数据结构 返回值:成功返回0;失败返回-1 传IPC_STAT可以获取共享内存的属性,传IPC_RMID可以删除共享内存。

key和shmid的比较

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

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

shmat 函数

功能:将共享内存段连接到进程地址空间。 如果未来不想使用该共享内存,可以用shmdt去关联 参数 shmid: 共享内存标识 shmaddr:指定共享内存想挂接到哪个地址上 shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY 返回值:成功返回共享内存的起始地址;失败返回-1

shmdt 函数

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

接口使用例子

Shm.hpp

代码语言:javascript
代码运行次数:0
运行
复制
#ifndef __SHM_HPP__
#define __SHM_HPP__

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

const int gCreater = 1;
const int gUser = 2;
const std::string gpathname = "/home/whb/code/111/code/lesson22/4.shm";
const int gproj_id = 0x66;
const int gShmSize = 4097; // 4096*n

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;
    }
    std::string RoleToString(int who)
    {
        if (who == gCreater)
            return "Creater";
        else if (who == gUser)
            return "gUser";
        else
            return "None";
    }
    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;
    }
    void DetachShm(void *shmaddr)
    {
        if (shmaddr == nullptr)
            return;
        shmdt(shmaddr);
        std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;
    }

public:
    Shm(const std::string &pathname, int proj_id, int who)
        : _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr)
    {
        _key = GetCommKey();
        if (_who == gCreater)
            GetShmUseCreate();
        else if (_who == gUser)
            GetShmForUse();
        _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 GetShmUseCreate()
    {
        if (_who == gCreater)
        {
            _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
            if (_shmid >= 0)
                return true;
            std::cout << "shm create done..." << std::endl;
        }
        return false;
    }
    bool GetShmForUse()
    {
        if (_who == gUser)
        {
            _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);
            if (_shmid >= 0)
                return true;
            std::cout << "shm get done..." << std::endl;
        }
        return false;
    }
    void Zero()
    {
        if(_addrshm)
        {
            memset(_addrshm, 0, gShmSize);
        }
    }

    void *Addr()
    {
        return _addrshm;
    }

    void DebugShm()
    {
        struct shmid_ds ds;
        int n = shmctl(_shmid, IPC_STAT, &ds);
        if(n < 0) return;
        std::cout << "ds.shm_perm.__key : " << ToHex(ds.shm_perm.__key)  << std::endl;
        std::cout << "ds.shm_nattch: " << ds.shm_nattch << std::endl;
    }

private:
    key_t _key;
    int _shmid;

    std::string _pathname;
    int _proj_id;

    int _who;
    void *_addrshm;
};

#endif

client.cc

代码语言:javascript
代码运行次数:0
运行
复制
#include "Shm.hpp"
#include "namedPipe.hpp"

int main()
{
    // 1. 创建共享内存

    Shm shm(gpathname, gproj_id, gUser);
    shm.Zero();
    char *shmaddr = (char *)shm.Addr();
    sleep(3);

    // 2. 打开管道
    NamePiped 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++;
    }
    return 0;
}

server.cc

代码语言:javascript
代码运行次数:0
运行
复制
#include "Shm.hpp"
#include "namedPipe.hpp"

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

    shm.DebugShm();

    // 2. 创建管道
    NamePiped 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;
}

共享内存有一个缺点:共享内存不提供对共享内存的任何保护机制。会导致数据不一致的问题。即双方不存在谁等谁的情况。比如我想传hello,结果写到he就被读走了,就会导致数据不一样。 我们在访问共享内存的时候,没有用任何系统调用。因此共享内存是所有进程IPC中,速度最快的。

system V消息队列

  • 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值

消息队列的接口的使用跟共享内存函数很像。

如果要发消息队列的数据,用

如果要接收数据,用

要查消息队列就用 ipcs -q ,它的指令跟共享内存就一字之差

system V信号量

信号量主要用于同步和互斥的。

进程互斥

  • 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
  • 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
  • 在进程中涉及到互斥资源的程序段叫临界区

多个执行流(进程),都能看到的一份资源:共享资源 被保护起来的资源:临界资源。 用互斥的方式进行保护。 互斥:任何时刻只能有一个进程在访问共享资源。

信号量的主要作用是用来保护共享资源的。经过保护,共享资源就变成临界资源。

假设上面的方格是电影院的座位。看电影时,只要有了票,位置就一定是你的,而不是谁先坐到就是谁的。 所以成功申请了信号量,即使不访问共享资源,也会留着一部分资源给你。 这里的信号量也叫多元信号量。

对共享资源的整体使用,即资源只有一个,也就是有人用了,别人就用不了了,即互斥。申请信号量时,这种信号量叫二元信号量。 信号量也是一个公共资源。 信号量本质是一个计数器,申请信号量时,计数器--,也叫P操作。释放信号量时,计数器++,也叫V操作。

信号量的操作

Linux中允许用户一次申请多个信号量,用信号量集保存,信号量集用数组来维护。 如果申请了多个信号量,上面的nsems就是申请的信号量的个数。

如果信号量不需要了,就用 semctl 。 semid就是要删除的信号量集,semnum就是要删除的信号量集的下标。

要对信号量进行PV操作,就用 semop 。struct sembuf结构体如下:

sem_op表示信号量的操作,如果为-1,相当于对信号量值作--。如果为1,表示作++。

查看信号量,用 ipcs -s 。删除操作跟前面类似。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-10-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 命名管道
    • 从命令行上创建
    • 程序里创建
  • system V共享内存
    • 共享内存函数
      • shmget 函数
      • 共享内存的释放
      • shmctl 函数
      • key和shmid的比较
      • shmat 函数
      • shmdt 函数
    • 接口使用例子
  • system V消息队列
  • system V信号量
    • 进程互斥
    • 信号量的操作
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档