Linux-system V共享内存

本文介绍了Linux中System V共享内存通信体系。先阐述其概念,即独立于管道通信的系统函数调用接口。接着详细讲解使用方法,包括申请空间的shmget、ftok、shmctl函数,建立映射的shmat、shmdt函数。还分析了其优势是速度快、拷贝次数少,弊端是缺乏访问控制、操作复杂。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1. 概念

2. 使用方法

2.1 申请空间

shmget

ftok

shmctl

2.2 建立映射

shmat

shmdt

3. 优势

4. 弊端


1. 概念

        system V 是Linux自己支持的一套通信体系,不同于管道通信基于文件的形式,而是独立的一套系统函数调用接口。

        通信的本质是两不同的进程能够看到同一份资源,这个资源可以是文件,也可以单单就是一块内存。如果两个进程可以像malloc申请一块空间一样,对申请同一块空间,并对空间进行读写,是不是也可以达到通信的目的呢?

        第一步,向操作系统申请共享内存空间,第二步,建立映射 

        共享内存块,在操作系统中也是一种资源,如果被大量的申请,是不是也需要把这些共享内存块管理起来呢?肯定是要的,所以要对这些块,进行描述和组织,形成共享内存对应的数据结构

        共享内存 = 共享内存块 + 对应共享内存的数据结构

        在这些数据结构中就会保存共享内存的一些属性,例如,唯一标识符,权限,大小等等

查看系统中的共享内存资源

ipcs -m

2. 使用方法

2.1 申请空间

shmget

函数作用:

        向系统申请共享内存,拿到这个共享内存的标识符

参数:

  • key: key_t是一个整形,他的数据内容不重要,重要的是他要具有唯一性,就像一把钥匙,只有有这把🔑的进程,才能打开这个共享内存。这个可以通过系统提供的算法函数ftok来生成钥匙。
  • size:申请共享内存的大小,最好是页(4096byte)的整数倍,因为操作系统会按照页的整数倍申请空间。
  • shmflg:创建规则,和创建文件的属性     
    IPC_CREAT(0)如果不存在,创建并返回,如果存在,直接返回存在空间的shmid 
    IPC_CREAT | IPC_EXCL如果不存在,创建并返回,如果存在,返回错误
    | 空间属性(例如0664)如果没有,权限全0,和文件类似的权限

返回值:

        成功返回 shmid(用户层的共享内存标识符,类似于文件标识符fd)

        失败返回 -1

ftok

函数作用:

        通过算法生成重复率极低的key

参数:

  • pathname:文件或者目录的路径,必须真实存在,有inode,内容是什么不重要
  • proj_id:int类型的整数,必须非0,一样内容是什么不重要

原理:

        重要的是,这个函数会根据文件的inode和proj_id的低8位,通过它的算法生成一个key,只要传进去的两个参数相同,返回的key值也一定是确定的,根本目的是为了保障生成一个重复率极低的key

返回值:
        成功:返回key_t值

        失败:返回-1

shmctl

函数作用:

        控制shm共享内存,主要作用是删除,也可以获取shm数据结构的信息,或者对其设置

参数:

  • shmid:共享内存的描述符shmget的返回值
  • cmd:设置IPC_RMID代表删除
  • buf:输出型参数,用于获取shm数据结构中的属性(删除设置为nullptr就行)

返回值:
        成功:返回0

        失败:返回-1

注意:

        1. 如果不删除共享内存,他就会一直存在在你的操作系统中,shm的生命周期随内核,只要你不主动关闭,只有重启才能解决它了

        除了这种删除方式,还有命令行方式

ipcrm -m shmid

        2. 即便还有进程在挂接当前shm,删除是强制执行的,不管有没有挂接

 

2.2 建立映射

shmat

函数作用:

        shm attach挂接,通过建立页表映射,建立虚拟地址和物理地址的链接,拿到物理地址的起始地址

参数:

  • shmid:共享内存的描述符shmget的返回值
  • shmaddr:你可以给他一个起始地址,然后他会向后找可用的空间,建立映射并返回,一般建议,设为0,让他自己在共享区建立映射
  • 读写权限设置,0表示读写,SHM_RDONLY表示只读

返回值:
        成功:返回起始地址void*

        失败:返回(void*)-1

shmdt

 

函数作用:

        shm detach拆除,拆除挂接

参数:

  • shmaddr:虚拟内存中shm的起始地址,shmat的返回值

返回值:
        成功:返回0

        失败:返回-1

3. 优势

        是速度对快的IPC通信方式

        重要原因是拷贝次数少,为什么拷贝次数少呢?

        相比于管道通信基于文件,共享内存是基于用户空间的内存的,文件属于内核级空间内,如果要读写,都需要利用read和write文件系统调用接口,而使用这些接口就必须,先把内容存在一个缓冲区buffer,这就必须要多拷贝一次,读写各一次

        而shm共享内存,是用户空间上的内容,不需要再调用系统接口写入,直接写就可以,减少了拷贝

4. 弊端

  1. 共享内存缺乏访问控制,会产生并发问题(并发问题,信号量,后面会写)
  2. 操作复杂,而且相当于另起炉灶,和Linux下一切皆文件的思想冲突

小实验:

log.hpp

#pragma once

#include <ctime>
#include <iostream>
#include <string>

enum status
{
    Debug,
    Notice,
    Warning,
    Error
};

const std::string str[] = {
    "Debug",
    "Notice",
    "Warning",
    "Error"
};

std::ostream& log(std::string message, status level)
{
    std::cout << " | " << str[level] << " | " << (unsigned int)time(nullptr) << " | " << message << "\t";
    return std::cout;
}

head.hpp

#pragma once

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cassert>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/stat.h>
#include<sys/shm.h>
#include<fcntl.h>
#include"log.hpp"

using namespace std;

#define PATH_NAME "."

#define SIZE 4096

#define ID 066

#define FIFO_NAME "./fifo"

#define READ O_RDONLY

#define WRITE O_WRONLY

class Init
{
public:
    Init()
    {
        int n = mkfifo(FIFO_NAME, 0664);
        if(n == -1)
        {
            perror("mkfifo");
            exit(1);
        }
    }

    ~Init()
    {
        int n = unlink(FIFO_NAME);
        assert(n != -1);
    }
};


int Open(string pathname, int flags)
{
    int fd = open(pathname.c_str(), flags);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    return fd;
}

void Wait(int fd)
{
    log("waiting....", Notice) << endl;
    uint32_t temp = 0;
    ssize_t sz = read(fd, &temp, sizeof(temp));
    assert(sz == sizeof(temp));
}

void Signal(int fd)
{
    uint32_t temp = 1;
    ssize_t sz = write(fd, &temp, sizeof(temp));
    assert(sz == sizeof(temp));
    log("signal....", Notice) << endl;
}

void Close(int fd)
{
    close(fd);
}

 shm_client.cc

#include "head.hpp"

int main()
{
    key_t key = ftok(PATH_NAME, ID);
    if (key == -1)
    {
        perror("ftok");
        exit(1);
    }
    log("ftok key success", Debug) << "key: " << key << endl;

    // 链接共享内存
    int shmid = shmget(key, SIZE, IPC_CREAT);
    if (shmid == -1)
    {
        perror("shmid");
        exit(2);
    }
    log("shmget success", Debug) << "shmid: " << shmid << endl;
    // sleep(10);

    // 挂接shm
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if (shmaddr == (char *)-1)
    {
        perror("shmat");
        exit(3);
    }
    log("shmat success", Debug) << "shmaddr: " << (void *)shmaddr << endl;
    // sleep(10);
    // 进行写操作
    int fifo_fd = Open(FIFO_NAME, WRITE);
    while(true)
    {
        ssize_t sz = read(0, shmaddr, SIZE - 1);
        assert(sz > 0);
        shmaddr[sz - 1] = '\0';
        Signal(fifo_fd);
        if(strcmp("quit", shmaddr) == 0)
            break;
    }


    // while (true)
    // {
    //     ssize_t sz = read(0, shmaddr, SIZE - 1);
    //     assert(sz > 0);
    //     shmaddr[sz - 1] = '\0';
    //     if(strcmp("quit", shmaddr) == 0)
    //         break;
    // }
    // char a = 'a';
    // for(; a <= 'z'; a++)
    // {
    //     shmaddr[a - 'a'] = a;
    //     //snprintf(shmaddr, SIZE, "我是client,我的pid:%d,信息:%c", getpid(), a);
    //     sleep(1);
    // }
    // strcpy(shmaddr, "quit");
    log("client quit", Notice) << endl;

    // 分离挂接
    int ret = shmdt(shmaddr);
    assert(ret != -1);
    log("shmdt success", Debug) << "shmaddr: " << (void *)shmaddr << endl;
    // 不需要删除
    return 0;
}

shm_server.cc

#include "head.hpp"

string TRANSTOHEX(int x) // 转16进制
{
    char str[35]; // 32+2+1
    snprintf(str, sizeof(str), "0x%x", x);
    return str;
}

Init init;

int main()
{
    key_t key = ftok(PATH_NAME, ID);
    if (key == -1)
    {
        perror("ftok");
        exit(1);
    }
    log("ftok key success", Debug) << "key: " << TRANSTOHEX(key) << endl;
    // sleep(10);

    // 创建共享内存
    int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0664);
    if (shmid == -1)
    {
        perror("shmid");
        exit(2);
    }
    log("shmget success", Debug) << "shmid: " << shmid << endl;
    // sleep(10);

    // 挂接shm
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if (shmaddr == (char *)-1)
    {
        perror("shmat");
        exit(3);
    }
    log("shmat success", Debug) << "shmaddr: " << (void *)shmaddr << endl;
    // sleep(10);

    // 进行读操作
    // 读,利用管道,构建访问控制
    int fifo_fd = Open(FIFO_NAME, READ);
    while(true)
    {
        Wait(fifo_fd);
        cout << shmaddr << endl;
        if (strcmp(shmaddr, "quit") == 0)
        {
            log("server quit", Notice) << endl;
            break;
        }
    }

    // while (true)
    // {
    //     cout << shmaddr << endl;
    //     if (strcmp(shmaddr, "quit") == 0)
    //     {
    //         log("server quit", Notice) << endl;
    //         break;
    //     }
    //     sleep(1);
    // }

    // 分离挂接
    int ret = shmdt(shmaddr);
    assert(ret != -1);
    log("shmdt success", Debug) << "shmaddr: " << (void *)shmaddr << endl;
    // sleep(10);

    // 删除共享内存
    int n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    log("shmrm success", Debug) << "shmid: " << shmid << endl;
    return 0;
}

完。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值