Linux 中System V IPC的共享内存

1. 概念介绍

System V IPC(Inter-Process Communication)是一组在UNIX系统中用于进程间通信的机制,包括共享内存、消息队列和信号量。这些机制由System V内核提供,并且它们的存在不依赖于创建它们的进程,而是由内核管理,直到显式删除。由于消息队列和信号量并不常用,故而以下主要介绍共享内存。

2. 共享内存 shm

共享内存允许不同进程共享一段由操作系统亲自开辟的物理内存,进程可以通过读写这段内存来交换数据。共享内存是最快的IPC方式,因为它减少了复制数据的需要,如通过管道交换数据时需要经过从内存复制到缓冲区中。

基本原理如上图,共享内存会被进程的页表直接映射到自己的进程地址空间的共享区,从而通过进程地址空间直接对内存进行操作,实现资源的共享与交换。

共享内存函数

shmget函数

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

参数:

  • key: 一个整数键值,用于唯一标识共享内存段,以创建或获取

我们可以看到 key 的类型为 key_t,实际上也是一个整数,不过要保证其数值的唯一性,要获取该参数 key 我们需要使用 ftok 函数,它会根据一个文件路径和一个项目标识符(proj_id)通过一系列算法生成一个几乎唯一的键值。

  • pathname 是一个指向存在的文件路径的指针,实际上可以随便写。
  • proj_id 是一个整数,通常是一个字符常量,其低8位被用于生成键值,实际上可以随便写。

key 是System V的唯一标识符,注意不是shm的,shmid 是 shm 的唯一标识符。

  • size: 共享内存大小,以字节为单位

注意:共享内存以4 kb为基本单位开辟内存,也就是4096 byte,哪怕只申请了1 byte的内存,实际上还是会开辟4096 byte大小的空间。因此开辟shm的时候,这个参数最好设置为4096的倍数。

  • shmflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。

其中最为常用的shmflg如下:

  • IPC_CREAT:如果共享内存段不存在,则创建一个新的共享内存段,存在则返回 shmid。
  • IPC_EXCL:与IPC_CREAT一起使用时,确保创建新的共享内存段,如果共享内存段已存在则失败。
  • 权限位:shmflg的低9位用于设置共享内存段的权限,与文件系统的权限位相同。
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
int main()
{
    key_t key=ftok("/home/lbk/lesson17/test.cpp",8888);
    int shmid=shmget(key,4096,IPC_CREAT|IPC_EXCL|0666);
    return 0;
}

第一个参数 key:我们通过ftok获得该唯一的共享内存的system V标识符key;
第二个参数为4096:即开辟的共享内存大小为4096 byte;
第三个参数为IPC_CREAT | IPC_EXCL | 0666:如果当前的key不存在,则创建对应的共享内存,该共享内存的初始权限为0666,如果存在,则创建失败。

通过 ipcs指令可以看当前所有的system V的总体情况:

也可以通过 ipcs -m命令只看共享内存的情况:

前面我们已经提到共享内存的存在不依赖于创建它们的进程,而是由内核管理,直到显式删除。即进程已经结束了,但是进程创建的共享内存还存在,如果想要删除一个共享内存,我们可以通过ipcrm -m shmid 命令。

shmat函数

共享内存是被直接映射到进程地址空间的共享区的,进程可以通过访问进程地址空间来访问共享内存,而从内存映射到进程地址空间我们就需要shmat函数。

功能:将共享内存段连接到进程地址空间
返回值:成功返回一个指针,指向共享内存第一个节;失败返回 -1
参数
  • shmid: 由 shmget 函数返回的共享内存段的标识符。
  • shmaddr: 一个指向进程地址空间中的位置的指针,用于指定共享内存段附加的起始地址。通常,这个参数设置为 NULL,让系统自动选择一个合适的地址。
  • shmflg: 一个标志位,可以包含 SHM_RDONLY 来指定以只读方式附加共享内存,或者 0 以允许读写。

shmdt函数

当一个进程不再需要访问共享内存时,可以调用 shmdt 来解除共享内存与该进程地址空间的关联。这个函数不会删除共享内存,只是使其对当前进程不可用。

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

shmctl函数

功能:用于控制System V共享内存段的系统调用。它可以执行多种操作,包括获取共享内存段的状态信息、修改共享内存段的属性、以及删除共享内存段。
返回值:成功返回 0 ;失败返回 -1
参数:
  • shmid: shmget返回的共享内存标识码
  • cmd: 命令参数,指定了要对共享内存段执行的操作(有三个可取值)

IPC_STAT:获取共享内存段的状态信息
IPC_SET:设置共享内存段的某些属性
IPC_RMID:删除共享内存段

  • buf: 一个指向 shmid_ds 结构的指针,用于存储或接收共享内存段的信息。如果 cmd 是IPC_STAT 或 IPC_SET, 则 buf 不为 NULL;如果 cmd 是 IPC_RMID,则 buf 可以为 NULL

shmid_ds 结构体是与System V共享内存段相关联的数据结构,它包含了共享内存段的各种状态信息和属性。这个结构体在 <sys/shm.h> 头文件中定义,并且在内核中用于维护每个共享内存段的信息。

struct shmid_ds {
    struct ipc_perm shm_perm;    /* Ownership and permissions */
    size_t          shm_segsz;   /* Size of segment (bytes) */
    time_t          shm_atime;   /* Last attach time */
    time_t          shm_dtime;   /* Last detach time */
    time_t          shm_ctime;   /* Last change time */
    pid_t           shm_cpid;    /* PID of creator */
    pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;  /* No. of current attaches */
    ...
};
使用示例:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main()
{
    key_t key = ftok("/home/lbk/lesson17/test.cpp", 8888);
    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        perror("shmget");
        exit(-1);
    }
    struct shmid_ds shm;
    shmctl(shmid, IPC_STAT, &shm);
    cout << "shm_nattch:" << shm.shm_nattch << endl;
    cout << "shm_segsz:" << shm.shm_segsz << endl;
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

进程间通信示例

comm.hpp

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

#define PATH_NAME "/home/lbk/lesson17/comm.hpp"
#define PROJECT_ID 0x8888
#define SHM_SIZE 4096
key_t Getkey()
{
    return ftok(PATH_NAME, PROJECT_ID);
}
int Creatshm()
{
    int shmid = shmget(Getkey(), SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        cout << "shmget error" << endl;
        return -1;
    }
    return shmid;
}
int Getshm()
{
    int shmid = shmget(Getkey(), SHM_SIZE, IPC_CREAT);
    if (shmid == -1)
    {
        cout << "shmget error" << endl;
        return -1;
    }
    return shmid;
}

server.cpp

#include "comm.hpp"
int main()
{
    int shmid = Creatshm();
    char *buf = (char *)shmat(shmid, NULL, 0);
    cout << "Client start" << endl;
    while (true)
    {
        cout << "Please Enter@";
        cin >> buf;
    }
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

client.cpp

#include "comm.hpp"
int main()
{
    int shmid = Getshm();
    char *buf = (char *)shmat(shmid, NULL, 0);
    while (true)
    {
        cout << "client receive:" << buf << endl;
        sleep(1);
    }
    return 0;
}

makefile

.PHONY:all
all:client.exe server.exe
client.exe:client.cpp
	g++ -o $@ $^
server.exe:server.cpp
	g++ -o $@ $^
.PHONY:clean
clean:
	rm -f client.exe server.exe

特点

  • 高效性:共享内存允许直接在内存中交换数据,避免了数据拷贝的开销,因此在进程间通信中速度非常快。

  • 同步问题:由于多个进程可以同时访问共享内存,因此需要同步机制来避免数据竞争和一致性问题。通常,进程会使用信号量或互斥锁来同步对共享内存的访问。

  • 系统资源:共享内存不需要操作系统维护额外的通信队列或管道,它直接使用进程的地址空间。

在使用共享内存时,需要注意的是,共享内存的内容在系统重启后不会持久化,因为它存储在物理内存中,而不是磁盘上。此外,共享内存的管理通常涉及一系列系统调用,如 shmget 创建共享内存段,shmat 将共享内存段附加到进程的地址空间,shmdt 分离共享内存段,以及 shmctl 控制共享内存段的属性和状态.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

我要满血复活

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

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

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

打赏作者

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

抵扣说明:

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

余额充值