目录
[ftok 函数 -- 获取 key 值](#ftok 函数 -- 获取 key 值)
[什么是 key?](#什么是 key?)
[如何生成 key ?](#如何生成 key ?)
[shmget 函数 -- 获取 shmid 值](#shmget 函数 -- 获取 shmid 值)
[什么是 shmid?](#什么是 shmid?)
[shmid 和 key 的区别?](#shmid 和 key 的区别?)
[如何获取 shmid ?](#如何获取 shmid ?)
[shmctl 函数 -- 控制共享内存](#shmctl 函数 -- 控制共享内存)
[shmat 函数 -- 将共享内存挂接到进程地址空间](#shmat 函数 -- 将共享内存挂接到进程地址空间)
[shmdt 函数 -- 取消挂接](#shmdt 函数 -- 取消挂接)
共享内存
共享内存是一种在计算机系统中允许多个进程访问同一块内存区域的机制。它是进程间通信(IPC, Inter-Process Communication)的一种方式,可以让不同的进程通过读写同一个内存段来交换数据。这种方式非常高效,因为它避免了数据复制的过程,直接在内存中进行数据的传递。
**在操作系统层面,当一个进程创建了一个共享内存区后,其他被授权的进程可以映射这个共享内存到自己的地址空间,并且可以像访问普通内存一样访问这块共享内存。**这种机制对于需要频繁交换大量数据的应用程序特别有用,比如数据库管理系统、多媒体应用程序等。
共享内存相关函数
ftok 函数 -- 获取 key 值
什么是 key?
因为两个进程访问同一块共享内存,而且操作系统不止一块共享内存,怎么保证两个或者不同的进程看到的是同一块共享内存呢?操作系统如何管理所有的共享内存?
我们给共享内存提供内核中的唯一性的标识,称为 key , 主要用于在创建或访问共享内存段时标识这个内存段。
如何生成 key ?
cpp
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数:
pathname
:指向一个字符串的指针,该字符串包含了一个路径名。这个路径名可以是任何文件或目录的路径,甚至可以是一个不存在的文件路径。其主要目的是为了生成一个唯一的键值。
proj_id
:一个整数,用于进一步区分不同的共享内存段或信号量集。proj_id
通常是一个较小的整数,因为ftok
函数返回的 key 值是由路径名和proj_id
组合计算得出的。过大的proj_id
可能会影响到 key 值的分布。这个值通常由程序员根据应用程序的需要自行选择。通常情况下,开发者会根据项目的实际需求来选择一个合适的
proj_id
。例如,可以使用一个固定的数字,或者是某种有意义的编码,只要保证它是唯一且一致的即可。
返回值:
- 如果成功,
ftok
函数返回一个非负的键值(key_t
类型)。- 如果失败,返回 -1,并且会**设置
errno
**来表明错误的原因。
封装:
cpp
key_t GetShmKeyOrDie(const char* pathname,const int pro_jid)
{
key_t k=ftok(pathname,pro_jid);//得到key值
if(k<0)//获取失败
{
cerr<<" ftok failed,errno:"<<errno<<", errstring:"<<strerror(errno)<<endl;
exit(1);//直接终止程序
}
return k;//获取成功
}
shmget 函数 -- 获取 shmid 值
什么是 shmid?
shmid
是一个整数,用于标识一个已创建的共享内存段。一旦获取了shmid
,进程就可以通过它来进一步操作共享内存段(如附着、分离等)。类似于文件描述符。
shmid 和 key 的区别?
- key用于标识共享内存段的逻辑键值,而
shmid
则是操作系统分配给这个共享内存段的一个内部标识符,用来管理具体共享内存段。两者共同作用于共享内存机制,确保多个进程能够正确地共享同一块内存。key
是一个逻辑上的标识符,用于创建或查找共享内存段;而shmid
是一个物理上的标识符,用于具体的操作共享内存段。- **
key
是静态的,一旦生成就不会改变 ;而shmid
**是动态的,每次创建共享内存段时,操作系统都会返回一个新的shmid
。key
用于解决"找到"共享内存的问题,而shmid
用于解决"操作"共享内存的问题。
如何获取 shmid ?
cpp
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
key
:用于标识共享内存段的键值。这个键值通常通过ftok
函数生成。
size
:共享内存段的大小(以字节为单位)。如果key
对应的一个共享内存段已经存在,那么这个参数将被忽略。在内核中,共享内存的大小以 4KB为基本单位,且你只能用你申请的大小,为了避免空间浪费,建议 size 的大小是 n*4KB。
shmflg
:一组标志位,用于控制创建和访问共享内存段的行为。
常见的标志位包括:
IPC_CREAT
:如果指定的key
对应的共享内存段不存在,则创建一个新的共享内存段;如果存在,则直接获取该共享内存。传该参数得到的共享内存不一定是新的,可能是之前创建的。IPC_EXCL
:独自使用时没有意义。IPC_EXCL|``IPC_CREAT
:key
对应的共享内存段如果不存在,就会创建一个新的共享内存;如果已经存在,则shmget
会失败并返回EEXIST
错误。传该参数得到的共享内存一定是新的!- 权限位:如
0666
,用于设置共享内存段的访问权限。
返回值:
- 如果成功,
shmget
返回一个非负整数,即shmid
。- 如果失败,返回 -1,并且**设置
errno
**以指示失败的原因。
封装:
cpp
int CreateShmOrDie(key_t key, int size, int flag)
{
//得到共享内存的shmid
int shmid=shmget(key,size,flag);
if(shmid<0)//创建失败
{
cerr<<" shmget failed, errno:"<<errno<<", errstring:"<<strerror(errno)<<endl;
exit(2);
}
return shmid;//创建成功
}
int CreateShm(key_t key,int size)
{
//创建一个新的共享内存,并设置权限
return CreateShmOrDie(key,size,IPC_CREAT|IPC_EXCL|0666);
}
int GetShm(key_t key,int size)
{
return CreateShmOrDie(key,size,IPC_CREAT);
}
shmctl 函数 -- 控制共享内存
cpp
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid
:共享内存段的标识符,通常通过shmget
函数获得。
cmd
:命令代码,用于指定要执行的操作。
cmd
参数的常用值:
IPC_STAT
:获取共享内存段的状态信息,并存储在buf
指向的结构体中。IPC_RMID
:删除共享内存段。
buf
:指向struct shmid_ds
类型的指针,struct shmid_ds
包含有关共享内存段的各种信息,包括权限、所有者等。若不需要该参数,可以传 NULL 。
返回值:
- 如果shmctl函数成功执行,它返回0。
- 如果shmctl函数执行失败,它返回**-1**,并且会设置 errno以指示失败的原因。
封装:
删除共享内存:
cpp
void DeleteShm(int shmid)
{
int n=shmctl(shmid,IPC_RMID,nullptr);
if(n<0)
cerr<<" Delete failed,errno:"<<errno<<", errstring:"<<strerror(errno)<<endl;
else
cout<<" Delete success, shmid:"<<shmid<<endl;
}
获得共享内存的状态:
cpp
string ToHex(key_t k)//将 key 值转为十六进制
{
char buffer[1024];//用C语言方便用 %x 直接转为十六进制
snprintf(buffer,sizeof(buffer),"0x%x",k);
return buffer;
}
void DebugShm(int shmid)
{
struct shmid_ds shmds;
int n = shmctl(shmid, IPC_STAT, &shmds);
if (n < 0)
cerr << " shmctl failed " << endl;
else
{
std::cout << "shmds.shm_segsz: " << shmds.shm_segsz << std::endl;//共享内存的大小
std::cout << "shmds.shm_nattch:" << shmds.shm_nattch << std::endl;//有多少个进程挂接
std::cout << "shmds.shm_ctime:" << shmds.shm_ctime << std::endl;//上一次挂接或取消挂接的时间
std::cout << "shmds.shm_perm.__key:" << ToHex(shmds.shm_perm.__key) << std::endl;//对应的key
}
}
shmat 函数 -- 将共享内存挂接到进程地址空间
上面的 shmget 函数只是创建了一个共享内存,但是这个共享内存并没有映射到进程的地址空间,还不属于进程,所以需要调用函数,把该共享内存挂接到进程地址空间中。
cpp
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid
:共享内存段的标识符,通常通过shmget
函数获得。
shmaddr
:指定共享内存段在进程地址空间中的起始地址。如果设置为**NULL
,则由系统选择一个合适的地址。
shmflg
:一组标志位,用于控制共享内存段的映射行为**。
常用的标志位包括:
SHM_RDONLY
:只读方式映射共享内存段。SHM_RND
:映射地址将是 4K 对齐的随机地址。SHM_REMAP
:如果共享内存段已经存在于进程地址空间中,则重新映射共享内存段。
当
shmflg
设为 0 时,意味着不使用任何特定的标志位,默认按照系统默认的方式进行映射。具体来说:
- 默认映射方式:系统会选择一个适合的地址来映射共享内存段。
- 读写模式 :默认情况下,映射是以读写模式进行的,而不是只读模式。
- 无特殊要求:不强制使用特定的映射地址,也不要求重新映射已经存在的共享内存段。
返回值:
如果成功,
shmat
返回一个指向共享内存段的指针。如果失败,返回**
(void *)-1
,并且会设置errno
**以指示失败的原因。
void*(空指针类型)是C语言中的一种指针类型,表示一个未指定类型的指针。void* 可以存储任何类型的指针值,并且在使用时需要显式转换为目标类型的指针。
封装:
cpp
void *ShmAttach(int shmid)
{
void* addr=shmat(shmid,nullptr,0);
if((long long int)addr==-1)
{
//挂接失败
cerr<<" attach failed,errno:"<<errno<<", errstring:"<<strerror(errno)<<endl;
return nullptr;
}
else
{
//挂接成功
return addr;
}
}
shmdt 函数 -- 分离
shmdt
(shared memory detach)函数用于将先前通过shmat
函数映射到进程地址空间的共享内存段分离(detach)出来。分离后,共享内存段仍然存在于系统中,只是不再映射到当前进程的地址空间中。这意味着进程不能再直接访问这块共享内存中的数据。
cpp
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数:
shmaddr
:指向先前通过shmat
函数映射到进程地址空间的共享内存段的指针。
返回值:
- 如果
shmdt
函数成功执行,它返回 0。- 如果
shmdt
函数执行失败,它返回 -1,并**设置errno
**以指示失败的原因。
封装:
cpp
void ShmDetach(void *addr)
{
int n=shmdt(addr);
if(n<0)
{
cerr<<" Detach failed,errno:"<<errno<<", errstring:"<<strerror(errno)<<endl;
}
}