Linux——进程间通信——system V系列

 ✅<1>主页::我的代码爱吃辣
<2>知识讲解:Linux——进程间通信——system V系列
☂️<3>开发环境:Centos7
<4>前言:system V 版本进程间通信的机制。

目录

一.共享内存介绍

 二.共享内存函数

1.shmget函数

 2.shmat函数

 3.shmdt函数

 4.shmctl函数

 三.代码示例

1.Comm.hpp

2.client.cc

3.server.cc

4.测试结果

四. 消息队列

 五.信号量

1.互斥



system共有三个IPC方案:

  1. System V 共享内存
  2. System V 消息队列
  3. System V 信号量

 今天我们重点讲解 system共享内存。

一.共享内存介绍

共享内存是进程间通信中最简单的方式之一,也是最快的IPC形式,因为进程可以直接读写内存,而不需要任何数据的拷贝 。共享内存允许两个或更多进程访问同一块内存,就如同malloc()函数向不同进程返回了指向同一个物理内存区域的指针。

共享内存在进程空间的映射:使用共享内存通信的一个进程可以将共享内存映射到自己的地址空间中,这样就可以像操作普通变量一样操作这块内存了。另外一个进程也可以通过相同的方式将这块内存映射到自己的地址空间中,然后进行读写操作。

示意图:

Linux——进程间通信——system V系列_第1张图片

 查看内核中的共享内存使用命令:

ipcs

Linux——进程间通信——system V系列_第2张图片

 二.共享内存函数

1.shmget函数

功能:用来创建共享内存。
原型:int shmget(key_t key, size_t size, int shmflg)。
参数:

  1. key:这个共享内存段名字。
  2. size:共享内存大小。
  3. shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1。

 2.shmat函数

功能:将共享内存段连接到进程地址空间。
原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
参数

  1. shmid: 共享内存标识。
  2. shmaddr:指定连接的地址。
  3. shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY。

返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1 。

说明:

  • shmaddr为NULL,核心自动选择一个地址。
  • shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
  • shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA)。
  • shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。

 3.shmdt函数

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

 4.shmctl函数

功能:用于控制共享内存。
原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数

  1. shmid:由shmget返回的共享内存标识码。
  2. cmd:将要采取的动作(有三个可取值)。
  3. buf:指向一个保存着共享内存的模式状态和访问权限的数据结构。

返回值:成功返回0;失败返回-1。

Linux——进程间通信——system V系列_第3张图片

 三.代码示例

基于共享内存实现server,client通信:

1.Comm.hpp

#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

// 项目路径
const char *pathname = "/home/wq/23_10_1";
// 项目编号
const int pro_id = 0664;
// 共享内存大小
const size_t gsize = 4096;

#define CLIENT 1
#define SERVER 0

class Shm
{
public:
    Shm(int type)
        : _type(type)
    {
        getkey();
        if (_type == 0)
            createshmserver();
        else
            createshmclient();
        hookshm();
    }
    char *getstart()
    {
        return _start;
    }
    ~Shm()
    {
        Leaveshm();

        if (_type == 0)
        {
            sleep(3);
            delshm();
        }
    }

private:
    void To_Hex(key_t k)
    {
        printf("0x%x\n", k);
    }

    // 1.获取唯一共享内存段名字key
    void getkey()
    {
        _key = ftok(pathname, pro_id);
        To_Hex(_key);
        if (_key == -1)
        {
            cerr << "shm:" << errno << ":" << strerror(errno) << endl;
        }
    }

    // 2.创建共享内存
    int createshm(int flag)
    {
        int shmid = shmget(_key, gsize, flag);

        return shmid;
    }

    // 2.1创建共享内存客户端接口
    void createshmclient()
    {
        // 客户端仅仅找到服务端创建的shm
        _shmid = createshm(IPC_CREAT);
        if (_shmid == -1)
        {
            cerr << "client shm create:" << errno << ":" << strerror(errno) << endl;
            exit(2);
        }
    }

    // 2.2创建共享内存服务端接口
    void createshmserver()
    {
        // 创建shm,如果已经存在就报错,不存在创建一个新的共享内存
        _shmid = createshm(IPC_CREAT | IPC_EXCL | 0666);
        if (_shmid == -1)
        {
            cerr << "server shm create:" << errno << ":" << strerror(errno) << endl;
            exit(3);
        }
    }

    // 3.将共享内存段连接到进程地址空间
    void hookshm()
    {
        _start = (char *)shmat(_shmid, NULL, SHM_RND);
        if (_start == NULL)
        {
            cerr << "hookshm:" << errno << ":" << strerror(errno) << endl;
        }
    }

    // 4.将共享内存段与到进程地址空间断开
    void Leaveshm()
    {
        int n = shmdt(_start);
        if (n == -1)
        {
            cerr << "shmdt:" << errno << ":" << strerror(errno) << endl;
        }
    }

    // 5.删除共享内存
    void delshm()
    {
        int n = shmctl(_shmid, IPC_RMID, NULL);
        if (n == -1)
        {
            cerr << "shmdt:" << errno << ":" << strerror(errno) << endl;
        }
    }

private:
    key_t _key;   // 共享内存段名字
    int _shmid;   // 共享内存段的标识码
    char *_start; // 共享内存映射到地址空间的起始地址,将来当作一段malloc的空间使用
    int _type;    // 身份 —— SERVER,CLIENT
};

2.client.cc

#include "Comm.hpp"

int main()
{
    // 1.创建共享内存对象
    Shm shm(CLIENT);
    // 2.得到内存块
    char *start = shm.getstart();
    // 3.读取数据
    while (1)
    {
        sleep(2);
        printf("%s", start);
    }

    return 0;
}

3.server.cc

#include "Comm.hpp"

int main()
{
    // 1.创建共享内存对象
    Shm shm(SERVER);
    // 2.得到内存块
    char *start = shm.getstart();
    // 3.写入数据
    while (1)
    {
        char buff[1024] = {0};
        char *str = fgets(buff, sizeof(buff), stdin);
        buff[strlen(str)] = 0;
        for (int i = 0; i < strlen(str); i++)
        {
            start[i] = buff[i];
        }
        start[strlen(str)] = 0;
    }
    sleep(20);

    return 0;
}

 makefile:

.PHONY:all
all:server client

server:server.cc
	g++ -o $@ $^ -std=c++11

client:client.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf server client 

4.测试结果

Linux——进程间通信——system V系列_第4张图片

四. 消息队列

  1. 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。
  2. 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值特性方面。
  3. IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。

Linux——进程间通信——system V系列_第5张图片

 五.信号量

信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥。

1.互斥

  1. 任何一个时刻,都只允许一个执行流在进行共享资源的访问——加锁。
  2. 我们把任何一个时刻,都只允许一个执行流在进行访问的共享资源,叫做临界资源。
  3. 临界资源是要通过代码访问的,凡是访问临界资源的代码,叫做临界区。
  4. 要么不做,要么做完,只有两种确定状态的属性,原子性。
  5. IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。
     

信号量就是对资源的一种预定机制,就像电影院座位和电影票一样。其本身就是一种计数器,信号量有两种操作P操作(申请信号量)计数器++,V操作(释放信号量)计数器--。

进程想要访问临界资源,首先申请信号量(P操作),只要我们申请成功未来一定能得到子资源。

访问完临界资源释放信号量(V操作)。

P,V操作,计数器的++或者--,一定要是原子的。

 

你可能感兴趣的:(linux,linux,运维,服务器)