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);
到此,进程间通信:管道就讲完了,怎么样,是不是感觉大脑里面多了很多新知识。
如果觉得博主讲的还可以的话,就请大家多多支持博主,收藏加关注,追更不迷路
如果觉得博主哪里讲的不到位或是有疏漏,还请大家多多指出,博主一定会加以改正
博语小屋将持续为您推出文章