system V 共享内存
system V是一种标准,linux内核支持这种标准,专门设计了一个ipc模板(通信的接口设计,原理,接口,相似性)
使用各自的虚拟地址访问物理内存
共享内存=描述共享内存的内核数据结构+它所对应的物理空间
进程间通信的本质:让不同的进程先看到同一份资源
进程都有自己的地址空间,都要把地址空间映射到物理内存处
先在物理内存上申请一部分内存空间,通过页表映射到进程的共享区(地址空间)
通过映射让两个不同的进程看到同一个内存
释放共享内存
存在有多组进程,都在使用不同的共享内存来进行通信。OS内,多个共享内存同时存在,就要管理共享内存,先描述再组织
描述共享内存的结构体
共享内存是否被使用?描述共享内存的结构体有引用计数可以来判断
共享内存接口的使用
先创建共享内存
传入key区分唯一性
让用户层约定一个key,一个创建设置到内存当中一个拿着key去查找就能拿到
ftok构建一个key值,把这个key值传递给shmget,创建共享内存的那一方就能构建出带key的共享内存,另一方通过相同的ftok构建相同的key就能使用同一个共享内存
用户指定key
返回值
成功构建共享内存的返回值
标识我们创建好的共享内存
shmget函数
不同的进程shm来解析通信,标识共享内存的唯一性,key来区分,不是内核直接形成的,而是在用户层,构建并传入给OS的
我们怎么评估共享内存是否存在,怎么保证两个不同的进程拿到的是同一个共享内存-->key
shmflg:由九个权限标志组成,他们的用法和创建文件时使用的莫得模式标志是一样的
IPC_CREAT:创建共享内存,如果目标共享内存不存在就创建;否则就打开这个已经存在的共享内存并返回
IPC_CREAT | IPC_EXCL: IPC_EXCL单独存在无意义,如果要创建的shm不存在就创建它,如果已经存在,shmget就会出错返回,只要shmget成功返回,一定是一个全新的共享内存
ftok
代码
进程结束,如果没有进行共享内存的删除,共享内存资源会一直存在 ,即共享内存的资源,生命周期随内核。如果没有显式地删除,即便进程退出了,IPC资源依旧被占用
删除,控制共享内存,在用户层我们不能使用key,key未来只给内核来进行唯一性的区分,需要使用shmid进行共享内存的管理
shmctl函数
我们也可以写入删除IPC对象的代码
用于控制内存
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作
buf:指向一个保存共享内存的模式状态和访问权限的数据结构
成功返回0,失败返回-1
加上这一段
并修改server.cc
shmat函数
将共享内存段链接到进程地址空间中,shmat:at:attach 关联
shmid:共享内存标识符
shmadder:指定连接的地址(虚拟地址,固定地址进行挂接)
shmflg:它的两个取值是SHM_RND和SHM_RDONLY
成功返回一个指针,指向共享内存的第一个节
失败返回-1
挂接
Attach 函数
功能:该函数用于将创建好的共享内存段附着(映射)到当前进程的地址空间,这样进程就可以通过返回的虚拟地址来访问共享内存中的数据
VirturalAddr 函数
功能:用于获取并打印共享内存的虚拟地址,同时返回该虚拟地址指针,方便其他地方使用该指针来访问共享内存
comm.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
const int gdefaultid = -1;
//gdefaultid:初始化为 -1,用作共享内存标识符(shmid)的默认值。在后续代码里,
//若 shmid 为 -1,就表明共享内存尚未成功创建或获取
const int gsize = 4096;
//设定共享内存段的大小为 4096 字节
const std::string pathname = ".";
//指定为当前目录 ".",这是 ftok 函数所需的路径名,ftok 会依据此路径名和项目 ID 生成一个键值(key)
const int projid = 0x65;
//ftok 函数所需的项目 ID
const int gmode = 0666;
#define ERR_EXIT(m) \
do \
{ \
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
//ERR_EXIT 是一个宏,其用途是处理错误情况。
//do {... } while(0) 这种结构保证了宏在被使用时,无论是否处于 if、for 等语句块里,都能正常运行。
//perror(m) 会把错误信息输出到标准错误流,同时显示 m 这个自定义的错误提示。
//exit(EXIT_FAILURE) 会让程序终止并返回一个表示失败的状态码
class Shm
{
private:
//创建的一定要是个全新的共享内存
void CreateHelper(int flg)
{
key_t k = ftok(pathname.c_str(), projid);
// pathname 和 projid 生成一个键值 k
if(k < 0)
{
ERR_EXIT("ftok");
// ftok 函数调用失败,会返回一个小于 0 的值,此时调用 ERR_EXIT("ftok")
//输出错误信息并终止程序
}
printf("key: 0x%x ", k);//ftok 调用成功,就打印生成的键值 k
//_shmid = shmget(k, _size, IPC_CREAT | IPC_EXCL | 0666);//也要有权限设置
_shmid = shmget(k, _size, flg);
//调用 shmget 函数来创建共享内存段。IPC_CREAT 表示若共享内存段不存在就创建它,
//IPC_EXCL 表示若共享内存段已经存在则返回错误
if(_shmid < 0)
{
ERR_EXIT("shmget");
// shmget 函数调用失败,会返回一个小于 0 的值,此时调用 ERR_EXIT("shmget")
//输出错误信息并终止程序
}
printf("shmid:%d\n ", _shmid);
//调用成功,就打印创建的共享内存段的标识符 _shmid
}
public:
Shm():_shmid(gdefaultid), _size(gsize), _start_mem(nullptr)
//_shmid 用于存储共享内存标识符,_size 用于存储共享内存的大小
{
}
void Creat()
{
CreateHelper( IPC_CREAT | IPC_EXCL | gmode);
}
//获取
void Get()
{
CreateHelper( IPC_CREAT );
}
void Destroy()
{
if(_shmid == gdefaultid) return;
// 检查共享内存标识符是否为默认值(即未创建共享内存)
int n = shmctl(_shmid, IPC_RMID, nullptr);
// 调用 shmctl 函数,尝试删除共享内存段
if(n > 0)
{
printf("shmctl delete shm: %d sucess!\n", _shmid);
//表示删除成功,输出成功信息
}
else
{
ERR_EXIT("shmctl");
//删除失败,调用 ERR_EXIT 宏处理错误
}
}
void Attach()
{
_start_mem = shmat(_shmid,nullptr,0);
if((long long)_start_mem < 0)
//在进行类型转换为 long long 后就是小于 0 的值
{
ERR_EXIT("shmat");
}
printf("attach succcess\n");
}
void *VirturalAddr()
{
printf("*VirturalAddr: %p\n", _start_mem);
//使用 printf 函数,以指针格式(%p)打印出共享内存的虚拟地址
return _start_mem;
//返回指向共享内存起始地址的指针 _start_mem,
//供其他函数使用该指针来操作共享内存中的数据
}
private:
int _shmid;
int _size;
void *_start_mem;
};
IPC本质:让不同的进程先看到同一份资源
读写共享内存并没有出现系统调用,共享内存属于用户空间,可以让用户直接使用
共享内存是进程间通信中速度最快的方式:
1.映射之后,读写直接被对方看到
2.不需要进行系统调用获取或者写入内容
但是也会有缺点:通信双方没有所谓的“同步机制”,导致数据不一致
共享内存没有保护机制,即对共享内存中数据的保护
我们shmget(key)就会被设置到共享内存的描述结构体中
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
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; /* Creation time/time of last
modification via shmctl() */
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 */
...
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
在内核中,共享内存在创建的时候,它的大小必须是4KB(4096)的整数倍
如果为4090 --> 4096*2 --> 向上4KB取整
但是能用的也只是4090的内存大小