进程间通信:system V共享内存

62 阅读7分钟

1.什么是共享内存?

共享内存是Linux系统中最高效的进程间通信(IPC)方式之一,它允许多个进程访问同一块物理内存区域,从而实现数据共享。

system V是进程间通信的一种标准

共享内存的优势:

高性能:数据不需要在进程间复制

低延迟:直接内存访问

大容量:可以共享大量数据

1.2共享内存示意图

进程间通信的前提是让不同的进程看到同一份资源,共享内存技术也是基于此前提设计的

要创建共享内存有两个步骤:

1 在物理内存处开辟一个内存块作为共享内存

2 将共享内存映射到需要通信的进程的地址空间中

这两个步骤都是有OS完成的,需要使用系统调用

1.3共享内存数据结构

OS中一定有很多进程需要通信,也就有很多共享内存,那么OS要不要对这些共享内存进行管理呢?当然需要

该怎么管理呢?先描述(struct结构体),再组织(数据结构)

struct shmid_ds 结构

struct shmid_ds {
    struct ipc_perm shm_perm;   // 所有权和权限
    size_t          shm_segsz;  // 段大小(字节)
    time_t          shm_atime;  // 最后附加时间
    time_t          shm_dtime;  // 最后分离时间
    time_t          shm_ctime;  // 最后修改时间
    pid_t           shm_cpid;   // 创建者PID
    pid_t           shm_lpid;   // 最后操作者PID
    shmatt_t        shm_nattch; // 当前附加数量
    // ... 其他系统特定字段
};

struct ipc_perm {
    key_t          __key;    // 提供的key
    uid_t          uid;      // 所有者有效UID
    gid_t          gid;      // 所有者有效GID
    uid_t          cuid;     // 创建者有效UID
    gid_t          cgid;     // 创建者有效GID
    unsigned short mode;     // 权限模式
    unsigned short __seq;    // 序列号
};

2.共享内存函数

1. shmget - 创建/获取共享内存段

使用共享内存的时候,一定是一个进程创建,一个进程获取,因为两个进程只需要也只能有一份共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:键值,内核中用来该标识共享内存的唯一性,由用户传入
size:共享内存大小,建议是4096字节的整数倍
shmflg:标志位,包含权限和创建选项
标志位:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
创建选项:
取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。
取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出错返回。
返回值:成功返回一个非负整数,即该共享内存段的标识码:shmid;失败返回-1

2.ftok函数

ftok函数是System V IPC中用于生成键值(key)的重要函数,用于创建唯一的标识符来访问共享内存、消息队列和信号量。

#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);
参数说明
pathname: 一个已存在的文件路径名
proj_id: 项目标识符(通常使用一个字符,0-255)

返回值
成功:返回生成的key_t类型键值
失败:返回-1,并设置errno

3.shmat函数

#include <sys/shm.h>

功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数说明
shmid: 共享内存标识符,由shmget返回
shmaddr: 指定链接地址,通常为以下值之一:
    NULL: 让系统自动选择合适地址
    具体地址:尝试在指定地址附加
shmflg: 附加标志,可以是以下值的按位或:
    0: 默认读写访问
    SHM_RDONLY: 只读方式附加
    SHM_RND: 与shmaddr配合使用,对地址进行舍入
    SHM_REMAP: 重新映射(Linux特定)
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
说明:
shmaddr为NULL,OS自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。
公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

4.shmdt函数

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

5.shmctl函数

shmctl函数是System V共享内存的控制函数,用于执行各种控制操作,包括获取状态信息、设置参数、删除共享内存段等。

功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
struct ipc_perm {
    key_t          __key;    // 提供的key
    uid_t          uid;      // 所有者有效UID
    gid_t          gid;      // 所有者有效GID
    uid_t          cuid;     // 创建者有效UID
    gid_t          cgid;     // 创建者有效GID
    unsigned short mode;     // 权限模式
    unsigned short __seq;    // 序列号
};

3.创建共享内存

ipcs -m 命令用于显示 Linux 系统中 System V 共享内存段的信息。

执行 ipcs -m 的典型输出如下:

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 0          user       600        524288     2          dest         
0x00000000 32769      user       600        1048576    1          dest         
0x11001234 65538      user       666        1024       0

各列含义:

key: 共享内存的键值,用于标识共享内存段

shmid: 共享内存段的 ID

owner: 共享内存段的所有者

perms: 权限位(八进制表示)

bytes: 共享内存段的大小(字节)

nattch: 当前附加到该共享内存段的进程数

status: 状态信息,如 dest 表示该段已被标记为待删除

代码实践:


#include<iostream>
#include<cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<unistd.h>
using namespace std;
int main()
{
    key_t key =ftok(".",0);
    if(key<0)
    {
        perror("ftok:");
        return false;
    }
    else
    printf("形成键值成功,键值:0x%x\n",key);
    //client 写,创建共享内存
    //IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出错返回
    int shmid=shmget(key,4096,IPC_CREAT | IPC_EXCL|0666);
    if(shmid<0)
    {
        perror("shmget:");
        return false;
    }
    else
    printf("创建共享内存成功,shmid:%d\n",shmid);
    //64位系统下,(void*)为8个字节
    char *addr=(char*)shmat(shmid,NULL,0);
    if((long long int)addr<0)
    {
        perror("shmat:");
        return false;
    }
    else
    printf("链接成功\n");
    sleep(3);
    char c='A';
    int i=0;
    while(c<'z')
    {
        addr[i++]=c++;
    }
    return 0;
}

#include<iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<unistd.h>
using namespace std;
int main()
{
    key_t key =ftok(".",0);
    if(key<0)
    {
        perror("ftok:");
        return false;
    }
    //server 写,获取共享内存
    //取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。
    int shmid=shmget(key,4096,IPC_CREAT );
    if(shmid<0)
    {
        perror("shmget:");
        return false;
    }
    else
    printf("创建共享内存成功,shimd:%d\n",shmid);
    char *addr=(char*)shmat(shmid,NULL,0);
    if((long long int)addr<0)
    {
        perror("shmat:");
        return false;
    }
    else
    printf("链接成功\n");
    sleep(3);
    int i=0;
    while(i<26)
    {
        cout<<addr[i++];
    }
    cout<<endl;
    return 0;
}

.PHONY:all
all:server client

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

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

.PHONY:clean
clean:
	rm -f client
	rm -f server

#删除shm ipc资源,注意,不是必须通过手动来删除,这里只为演示相关指令,删除IPC资源是进程该做的事情

shmctl(_shmid, IPC_RMID, nullptr);

到此,进程间通信:管道就讲完了,怎么样,是不是感觉大脑里面多了很多新知识。

如果觉得博主讲的还可以的话,就请大家多多支持博主,收藏加关注,追更不迷路

如果觉得博主哪里讲的不到位或是有疏漏,还请大家多多指出,博主一定会加以改正

博语小屋将持续为您推出文章