一、共享内存的概念
为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射
到自己的私有地址空间。进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高
的效率。
共享内存就是在两个或多个正在运行的进程之间共享内存区域的一种进程间的通信方式(就
是允许两个或多个不相关的进程访问同一个逻辑内存)。是 IPC中最快捷的通信方式,因为共享内
存方式的通信没有中间过程,而管道、消息队列等方式则是需要将数据通过中间机制进行转换。
共享内存方式是直接将某段“物理内存”段进行映射,多个进程间共享的内存是同一块的物理
空间,各进程只是映射这段物理内存地址到自己的地址空间而已,因此不需要进行复制,可以直接
使用此段空间。就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向共享内存
写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
注意:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之
前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步
对共享内存的访问,例如前面说到的信号量。
二、共享内存的使用
与信号量的函数接口类似,在Linux中也提供了一组函数接口用于使用共享内存。共享共存的
接口函数与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h
中,共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成。
1、shmget函数
shmget: 得到一个共享内存标识符或创建一个共享内存 | ||
所需头文件 | #include <sys/ipc.h> #include <sys/shm.h> | |
函数说明 | 得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符 | |
函数原型 | int shmget(key_t key, size_t size, int shmflg) | |
函数传入值 | key | 0 (IPC_PRIVATE):会建立新共享内存对象 |
大于0的32位整数:视参数shmflg来确定操作。通常要求此值来源于ftok返回的IPC键值 | ||
size | 大于0的整数:新建的共享内存大小,以字节为单位 | |
0:只获取共享内存时指定为0 | ||
shmflg | 0:取共享内存标识符,若不存在则函数会报错 | |
IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符 | ||
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错 | ||
函数返回值 | 成功:返回共享内存的标识符 | |
出错:-1,错误原因存于error中 | ||
附加说明 | 上述shmflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限。 shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。 而如果需要指定一个键值,然后返回这块共享内存IPC标识符ID,从而将这个新的标识符ID告诉其他进程使用时。就需要通过ftok函数来实现。 | |
错误代码 | EINVAL:参数size小于SHMMIN或大于SHMMAX EEXIST:预建立key所指的共享内存,但已经存在 EIDRM:参数key所指的共享内存已经删除 ENOSPC:超过了系统允许建立的共享内存的最大值(SHMALL) ENOENT:参数key所指的共享内存不存在,而参数shmflg未设IPC_CREAT位 EACCES:没有权限 ENOMEM:核心内存不足 |
2、shmat函数
shmat: 把共享内存区的五物理地址映射到调用进程的地址空间(一般得到的是虚拟地址) | ||
所需头文件 | #include <sys/types.h> #include <sys/shm.h> | |
函数说明 | 连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。(核心理解是:连接共享内存区域) | |
函数原型 | void *shmat(int shmid, const void *shmaddr, int shmflg) | |
函数传入值 | msqid | 共享内存标识符 |
shmaddr | 指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置 | |
shmflg | SHM_RDONLY:为只读模式,其他为读写模式 | |
函数返回值 | 成功:附加好的共享内存地址 | |
出错:-1,错误原因存于error中 | ||
附加说明 | fork后子进程继承已连接的共享内存地址。exec后该子进程与已连接的共享内存地址自动脱离(detach)。进程结束后,已连接的共享内存地址会自动脱离(detach) | |
错误代码 | EACCES:无权限以指定方式连接共享内存 EINVAL:无效的参数shmid或shmaddr ENOMEM:核心内存不足 |
3、shmdt函数
shmdt: 断开共享内存连接(该函数仅仅是断开,并不是删除,核心理解是:断开连接) | |
所需头文件 | #include <sys/types.h> #include <sys/shm.h> |
函数说明 | 与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存 |
函数原型 | int shmdt(const void *shmaddr) |
函数传入值 | shmaddr:连接的共享内存的起始地址 |
函数返回值 | 成功:0 |
出错:-1,错误原因存于error中 | |
附加说明 | 本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程 |
错误代码 | EINVAL:无效的参数shmaddr |
4、shmctl函数
shmctl(共享内存管理) | ||
所需头文件 | #include <sys/types.h> #include <sys/shm.h> | |
函数说明 | 完成对共享内存的控制(核心理解是:控制),可以在该函数中删除共享内存区域 | |
函数原型 | int shmctl(int shmid, int cmd, struct shmid_ds *buf) | |
函数传入值 | shmid | 共享内存标识符 |
cmd | IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中 | |
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内 | ||
IPC_RMID:删除这片共享内存 | ||
buf | 共享内存管理结构体。具体说明参见共享内存内核结构定义部分 | |
函数返回值 | 成功:0 | |
出错:-1,错误原因存于error中 | ||
错误代码 | EACCESS:参数cmd为IPC_STAT,确无权限读取该共享内存 EFAULT:参数buf指向无效的内存地址 EIDRM:标识符为shmid的共享内存已被删除 EINVAL:无效的参数cmd或shmid EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行 |
=========================================================================
转载:ftok()函数深度解析_u013485792的专栏-优快云博客_ftok函数作用
关于ftok函数,先不去了解它的作用,先来说说为什么要用它。共享内存,消息队列,信号量
它们三个都是找一个中间介质,来进行通信的,这种介质多的是。就是怎么区分出来,就像唯一的
身份证来区分人一样。你随便来一个,只要唯一就行。因此就想起来文件的设备编号和节点,它是
唯一的,但是直接用它来作识别好像不太好,不过可以用它来产生一个号,ftok()就出场了。
ftok函数具体形式如下:
key_t ftok(const char *pathname, int proj_id);
其中参数pathname是指定的文件名,这个文件必须是存在的而且可以访问的。proj_id是子
序号。它是一个8bit的整数。即范围是0~255。当函数执行成功,则会返回key_t键值,否则返
回-1。在一般的UNIX中,通常是将文件的索引节点取出,然后在前面加上子序号就得到key_t的
值。
- 有关该函数的三个常见问题:
1. pathname是目录还是文件的具体路径,是否可以随便设置?
2. pathname指定的目录或文件的权限是否有要求?
3. proj_id是否可以随便设定,有什么限制条件?
解答:
1、ftok根据路径名,提取文件信息,再根据这些文件信息及project ID合成key,该路径可以
随便设置。
2、该路径是必须存在的,ftok只是根据文件inode在系统内的唯一性来取一个数值,和文件的
权限无关。
3、proj_id是可以根据自己的约定,随意设置。这个数字,有的称之为project ID; 在UNIX系统
上,它的取值是1到255。
- 关于ftok()函数的一个陷阱
在使用ftok()函数时,里面有两个参数,即pathname和proj_id,pathname为指定的文件名,
而proj_id为子序列号,这个函数的返回值就是key,它与指定的文件的索引节点号和子序列号id有
关,这样就会给我们一个误解,即只要文件的路径,名称和子序列号不变,那么得到的key值永远
就不会变。
事实上,这种认识是错误的,想想一下,假如存在这样一种情况:在访问同一共享内存的多个
进程先后调用ftok()时间段中,如果fname指向的文件或者目录被删除而且又重新创建,那么文件系
统会赋予这个同名文件新的i节点信息,于是这些进程调用的ftok()都能正常返回,但键值key却不一
定相同了。由此可能造成的后果是,原本这些进程意图访问一个相同的共享内存对象,然而由于它
们各自得到的键值不同,实际上进程指向的共享内存不再一致;如果这些共享内存都得到创建,则
在整个应用运行的过程中表面上不会报出任何错误,然而通过一个共享内存对象进行数据传输的目
的将无法实现。
这是一个很重要的问题,希望能谨记!!!
所以要确保key值不变,要么确保ftok()的文件不被删除,要么不用ftok(),指定一个固定的key值。
=========================================================================
然后来看两个共享内存函数的具体应用,以下例程都是在项目中的接口封装:
- 参数key为IPC_PRIVATE时
/*获取共享内存shmget()用来获得共享内存区域的ID,
如果不存在指定的共享区域就创建相应的区域*/
INT32 CreateShmMem(UINT32 size)
{
int shmid=-1;
// 使用IPC_PRIVATE参数来获取一篇新的共享内存区。
if((shmid=shmget(IPC_PRIVATE,size,IPC_CREAT))<0)
{
printf("[%s][%d] create shmmem fail\n",__FUNCTION__,__LINE__);
return FAILURE;
}
return shmid;
}
/*
作用:共享内存区对象映射到调用进程的地址空间
核心处理函数: void *shmat( int shmid , char *shmaddr , int shmflag );
shmat()是用来允许本进程访问一块共享内存的函数。
int shmid是那块共享内存的ID。
char *shmaddr是共享内存的起始地址
int shmflag是本进程对该内存的操作模式。如果是SHM_RDONLY的话,就是只读模式。其它的是读写模式
成功时,这个函数返回共享内存的起始地址。失败时返回-
*/
void* MMapShmMem( INT32 shmid, INT8 *shmaddr, INT32 shmflag)
{
if(shmid<0)
{
return NULL;
}
return shmat(shmid,shmaddr,shmflag);
}
/*int shmdt( char *shmaddr );
使进程中的映射内存无效化,不可以使用。但是保留空间*/
INT32 DisableShmMem(INT8*shmaddr)
{
if(!shmaddr)
{
return FAILURE;
}
return shmdt(shmaddr);
}
/*控制:shmctl( shmid , IPC_STAT , &buf );
取得共享内存的状态 struct shmid_ds buf;
shmctl( shmid , IPC_RMID , &buf );
删除共享内存–删除共享内存,彻底不可用,释放空间*/
INT32 RemoveShmMem(INT32 shmid)
{
struct shmid_ds buf;
if(shmid<0)
{
return FAILURE;
}
/*取得共享内存的状态*/
if(shmctl( shmid , IPC_STAT , &buf )<0)
{
shmctl(shmid, IPC_RMID, NULL);/*删除共享内存*/
return FAILURE;
}
return shmctl(shmid, IPC_RMID, &buf);/*删除共享内存*/
}
- 参数key不为IPC_PRIVATE时,多进程使用同一个文件产生key,来对同一片片共享内存区进行操作。
// 多进程之间使用同一个文件产生key值,来对同一片片共享内存区进行操作
// 多进程之间遵循同一个数据结构的大小来操作该片共享内存区
// 多个不同进程同时使用该文件产生key的话,就会获得同一片共享内存区的地址
void CreateShareMemory(char *filename, int size)
{
int fd;
// 使用文件作为产生key的契子
if ((fd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO)) > 0){
close(fd);
}else{
PRINTF("err:open %s failed\n", filename);
}
// 通过文件节点产生key
key_t key = ftok(filename, 'k');
// 申请一片size大小的共享内存
int m_Shmid = shmget( key, size, IPC_CREAT | S_IRWXU | S_IRWXG | S_IRWXO );
if( m_Shmid == -1 ){
printf("key=%d size=%d name=%s\n", key, size, filename);
perror("playback alloc share mem error:");
}
assert( m_Shmid != -1 ); //TBD: exit
// 把物理共享内存区的地址映射到调用进程的地址空间
unsigned char * m_pShareMemory = (unsigned char *) shmat(m_Shmid, NULL, 0);
if((int)m_pShareMemory == (int)-1){
PRINTF("CreateShareMemory err\n");
perror("sharem mememe:");
}
// 获取共享内存的状态,把共享内存的shmid_ds结构复制到buf中
struct shmid_ds buf;
shmctl(m_Shmid, IPC_STAT, &buf);
if (buf.shm_nattch == 1){
// 清空之后给共享内存区的元素初始化
memset(m_pShareMemory, 0, size);
// todo
}
// 返回当前进程的地址空间
return m_pShareMemory;
}