【Linux】进程间通信_4

文章目录


七、进程间通信

1. 进程间通信分类

systeam V共享内存

进程间通信的本质就是让不同进程看到同一份资源。而systeam V是通过让不同的进程经过页表映射到同一块内存空间(操作系统完成的)。

我们申请的共享内存,如果进程结束了,但共享内存并不会释放,需要我们手动释放。管道文件的生命周期是随进程的,但是共享内存的生命周期是随内核的。

使用 ipcs -m 命令可以查看系统中我们创建的共享内存数量。使用 ipcrm -m 和要删除的共享内存的 shmid 即可删除指定共享内存。

这里我们来实现一下共享内存模式的进程间通信:
Makefile:

cpp 复制代码
.PHONY:all
all:shm_server shm_client

shm_server:ShmServer.cc
	g++ -o $@ $^ -std=c++11
shm_client:ShmClient.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f shm_server shm_client

Comm.hpp:

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <string>
#include <unistd.h>
using namespace std;

const char* pathname = "/home/student";
const int proj_id = 0x66;
const int defaultsize = 4096;

// 将key转换为16进制
string ToHex(key_t k)
{
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "0x%x", k);
    return buffer;
}

// 生成共享内存的key
key_t GetShmKeyOrDie()
{
    key_t k = ftok(pathname, proj_id);
    if (k < 0)
    {
        cerr << "ftok error, errno : " << errno << ", errno string : " << strerror(errno) << endl;
        exit(1);
    }
    return k;
}

// 根据key创建共享内存
int CreateShmOrDie(key_t key, int size, int flag)
{
    int shmid = shmget(key, size, flag);
    if (shmid < 0)
    {
        cerr << "shmget error, errno : " << errno << ", errno string : " << strerror(errno) << endl;
        exit(2);
    }
    return shmid;
}

int CreateShm(key_t key, int size)
{
    return CreateShmOrDie(key, size, IPC_CREAT | IPC_EXCL | 0666);
}

int GetShm(key_t key, int size)
{
    return CreateShmOrDie(key, size, IPC_CREAT);
}

// 删除共享内存
void DeleteShm(int shmid)
{
    int n = shmctl(shmid, IPC_RMID, nullptr);
    if (n < 0)
    {
        cerr << "shmctl error, errno : " << errno << ", errno string : " << strerror(errno) << endl;
    }
    else
    {
        cout << "shmctl delete shm success, shmid : " << shmid << endl;
    }
}

// 将共享内存附加到进程的地址空间,实现映射关系
void* ShmAttach(int shmid)
{
    void* addr = shmat(shmid, nullptr, 0);
    if (addr == (void*)-1)
    {
        cerr << "shmat error, errno : " << errno << ", errno string : " << strerror(errno) << endl;
        return nullptr;
    }
    return addr;
}

// 解除共享内存的映射关系
void ShmDetach(void* addr)
{
    int n = shmdt(addr);
    if (n < 0)
    {
        cerr << "shmdt error, errno : " << errno << ", errno string : " << strerror(errno) << endl;
    }
}

Fifo.hpp:

cpp 复制代码
#ifndef __COMM_HPP__
#define __COMM_HPP__

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

#define Mode 0666
#define Path "./fifo"

// 命名管道类
class Fifo
{
public:
    Fifo(const string& path = Path)
    : _path(path)
    {
        umask(0);
        // 创建命名管道
        int n = mkfifo(_path.c_str(), Mode);
        if (n == 0)
        {
            cout << "mkfifo success" << endl;
        }
        else
        {
            cout << "mkfifo failed, error : " << errno << "errstring : " << strerror(errno) << endl;
        }
    }

    ~Fifo()
    {
        // 删除命名管道
        int n = unlink(_path.c_str());
        if (n == 0)
        {
            cout << "remove " << _path << " success" << endl;
        }
        else
        {
            cout << "remove failed, error : " << errno << "errstring : " << strerror(errno) << endl;
        }
    }
private:
    // 文件路径 + 文件名
    string _path;
};

class Sync
{
public:
    Sync()
    :_rfd(-1)
    ,_wfd(-1)
    {}

    void OpenReadOrDie()
    {
        _rfd = open(Path, O_RDONLY);
        if (_rfd < 0) exit(1);
    }

    void OpenWriteOrDie()
    {
        _wfd = open(Path, O_WRONLY);
        if (_wfd < 0) exit(1);
    }

    bool Wait()
    {
        bool ret = true;
        uint32_t c = 0;
        ssize_t n = read(_rfd, &c, sizeof(uint32_t));
        if (n == sizeof(uint32_t))
        {
            cout << "wait for server" << endl;
        }
        else if (n == 0)
        {
            ret = false;
        }
        else
        {
            return false;
        }
        return ret;
    }

    void WakeUp()
    {
        uint32_t c = 0;
        ssize_t n = write(_wfd, &c, sizeof(uint32_t));
        assert(n == sizeof(uint32_t));
        cout << "wakeup server" << endl;
    }

    ~Sync() {}
private:
    int _rfd;
    int _wfd;
};

#endif

ShmServer:

cpp 复制代码
#include "Comm.hpp"
#include "Fifo.hpp"

int main()
{
    // 生成一个key
    key_t key = GetShmKeyOrDie();
    // 生成共享内存
    int shmid = CreateShm(key, defaultsize);
    // 将共享内存和进程进行关联(挂接)
    char *addr = (char*)ShmAttach(shmid);

    // 引入管道
    Fifo fifo;
    Sync syn;
    syn.OpenReadOrDie();

    // 循环读取共享内存
    while(1)
    {
        if (!syn.Wait()) break;
        cout << "Shm content: " << addr << endl;
    }

    // 去关联共享内存
    ShmDetach(addr);
    // 删除共享内存
    DeleteShm(shmid);
    return 0;
}

ShmClient:

cpp 复制代码
#include "Comm.hpp"
#include "Fifo.hpp"

int main()
{
    key_t key = GetShmKeyOrDie();
    int shmid = GetShm(key, defaultsize);
    char *addr = (char*)ShmAttach(shmid);

    Sync syn;
    syn.OpenWriteOrDie();

    memset(addr, 0, defaultsize);
    for (int c = 'A'; c <= 'Z'; c++)
    {
        addr[c - 'A'] = c;
        sleep(1);
        syn.WakeUp();
    }

    ShmDetach(addr);
    return 0;
}

结果:

消息队列

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。
消息队列的本质就是操作系统在内核维护了多个数据块队列,不同进程根据数据怪的标识来通信。



信号量

信号量本质是一个计数器,描述临界资源数量的计数器。进程访问临界资源本质就是申请信号量,申请成功才能访问临界资源,否者只能等待申请。所有的进程访问临界资源,都需要申请信号量,所有的进程都必须要看到同一个信号量,说明信号量本身就是一个共享资源。

信号量的申请操作称为 P操作 ,释放操作称为 V操作 。

在命令行使用 ipcs -s 即可查询信号量, ipcrm -s 加信号量标识符 即可删除指定信号量。



未完待续

相关推荐
M78佐菲21 分钟前
Linux学习笔记:进程
linux·笔记·学习·算法
zbyyd1 小时前
Linux 进程管理详解:从概念到实战
linux·运维·服务器
我命由我123452 小时前
人脸识别 - 勒克斯(光线照射到物体表面上的明亮程度)
android·java·学习·java-ee·人脸识别·学习方法·android runtime
薛定猫AI2 小时前
OpenAI Codex本地完整配置教程|(Windows/Mac/Linux全平台,修复鉴权报错)
linux·windows·macos
Logintern093 小时前
【每天学习一点点】MPI是什么?
学习·mpi·多机训练
fengci.3 小时前
提示词注入学习小结
学习
晓梦林4 小时前
Tools2靶场学习笔记
笔记·学习
躺不平的理查德4 小时前
OpenSSL 的异步 TLS 1.3 加密
linux·运维·服务器
tedcloud1234 小时前
Awesome DSH Plugin 怎么用?给 DeepSeek Harness 搭建自己的插件生态
大数据·linux·服务器·人工智能·音视频