如何windbg 查看内存分配shmget分配的内存地址

posts - 0,&
comments - 3,&
trackbacks - 0
&&&&&&&&共享内存区域是被多个进程共享的一部分物理内存。如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。这块共享虚拟内存的页面,出现在每一个共享该页面的进程的页表中。但是它不需要在所有进程的虚拟内存中都有相同的虚拟地址。
图 共享内存映射图&&
&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&象所有的 System V IPC对象一样,对于共享内存对象的获取是由key控制。内存共享之后,对进程如何使用这块内存就不再做检查。它们必须依赖于其它机制,比如System V的信号灯来同步对于共享内存区域的访问(信号灯如何控制对临界代码的访问另起一篇说话)。
&&&&&&&&每一个新创建的共享内存对象都用一个shmid_kernel数据结构来表达。系统中所有的shmid_kernel数据结构都保存在shm_segs向量表中,该向量表的每一个元素都是一个指向shmid_kernel数据结构的指针。
shm_segs向量表的定义如下:
struct shmid_kernel *shm_segs[SHMMNI];
&&& SHMMNI为128,表示系统中最多可以有128个共享内存对象。
&&& 数据结构shmid_kernel的定义如下:
&&&&struct shmid_kernel
&&&&&&&&struct shmid_&&&&&&&& /* the following are private */
&&&&&&&&unsigned long shm_& /* size of segment (pages) */
&&&&&&&&unsigned long *shm_& /* array of ptrs to frames -& SHMMAX */&
&&&&&&&&struct vm_area_struct *& /* descriptors for attaches */
&&& 其中:
&&&&shm_pages代表该共享内存对象的所占据的内存页面数组,数组里面的每个元素当然是每个内存页面的起始地址.
&&& shm_npages则是该共享内存对象占用内存页面的个数,以页为单位。这个数量当然涵盖了申请空间的最小整数倍.
&&&&(A new shared memory segment,& with size& equal to the value of size rounded up to a multiple of PAGE_SIZE)
&&& shmid_ds是一个数据结构,它描述了这个共享内存区的认证信息,字节大小,最后一次粘附时间、分离时间、改变时间,创建该共享区域的进程,最后一次对它操作的进程,当前有多少个进程在使用它等信息。
&&&&其定义如下:
&&&&struct shmid_ds {
&&&&&&&&struct ipc_perm shm_&& /* operation perms */
&&&&&&&&int shm_&&&&&&&&&&&&& /* size of segment (bytes) */
&&&&&&&&__kernel_time_t shm_& /* last attach time */
&&&&&&&&__kernel_time_t shm_& /* last detach time */
&&&&&&&&__kernel_time_t shm_& /* last change time */
&&&&&&&&__kernel_ipc_pid_t shm_ /* pid of creator */
&&&&&&&&__kernel_ipc_pid_t shm_ /* pid of last operator */
&&&&&&&&unsigned short shm_&& /* no. of current attaches */
&&&&&&&&unsigned short shm_&& /* compatibility */
&&&&&&&&void *shm_unused2;&&&&&&&&&& /* ditto - used by DIPC */
&&&&&&&&void *shm_unused3;&&&&&&&&&& /* unused */
&&&&&&& attaches描述被共享的物理内存对象所映射的各进程的虚拟内存区域。每一个希望共享这块内存的进程都必须通过系统调用将其关联(attach)到它的虚拟内存中。这一过程将为该进程创建了一个新的描述这块共享内存的vm_area_struct数据结构。创建时可以指定共享内存在它的虚拟地址空间的位置,也可以让Linux自己为它选择一块足够的空闲区域。
&&&&&&&&这个新的vm_area_struct结构是维系共享内存和使用它的进程之间的关系的,所以除了要关联进程信息外,还要指明这个共享内存数据结构shmid_kernel所在位置; 另外,便于管理这些经常变化的vm_area_struct,所以采取了链表形式组织这些数据结构,链表由attaches指向,同时 vm_area_struct数据结构中专门提供了两个指针:vm_next_shared和 vm_prev_shared,用于连接该共享区域在使用它的各进程中所对应的vm_area_struct数据结构。&
图 System V IPC 机制 - 共享内存&
&&&&&&&&&&&& &&
&&&&&&&&Linux为共享内存提供了四种操作。
&&&&&&&&1. 共享内存对象的创建或获得。与其它两种IPC机制一样,进程在使用共享内存区域以前,必须通过系统调用sys_ipc (call值为SHMGET)创建一个键值为key的共享内存对象,或获得已经存在的键值为key的某共享内存对象的引用标识符。以后对共享内存对象的访问都通过该引用标识符进行。对共享内存对象的创建或获得由函数sys_shmget完成,其定义如下:
int sys_shmget (key_t key, int size, int shmflg)
&&& 这里key是表示该共享内存对象的键值,size是该共享内存区域的大小(以字节为单位),shmflg是标志(对该共享内存对象的特殊要求)。
&&& 它所做的工作如下:
&&& 1) 如果key == IPC_PRIVATE,则总是会创建一个新的共享内存对象。
&但是& (The name choice IPC_PRIVATE was perhaps unfortunate, IPC_NEW would more&clearly show its function)
&&& * 算出size要占用的页数,检查其合法性。
&&& * 申请一块内存用于建立shmid_kernel数据结构,注意这里申请的内存区域大小不包括真正的共享内存区,实际上,要等到第一个进程试图访问它的时候才真正创建共享内存区。
&&& * 根据该共享内存区所占用的页数,为其申请一块空间用于建立页表(每页4个字节),将页表清0。
&&& * 搜索向量表shm_segs,为新创建的共享内存对象找一个空位置。
&&& * 填写shmid_kernel数据结构,将其加入到向量表shm_segs中为其找到的空位置。
&&& * 返回该共享内存对象的引用标识符。
&&& 2) 在向量表shm_segs中查找键值为key的共享内存对象,结果有三:
&&& * 如果没有找到,而且在操作标志shmflg中没有指明要创建新共享内存,则错误返回,否则创建一个新的共享内存对象。
&&& * 如果找到了,但该次操作要求必须创建一个键值为key的新对象,那么错误返回。
&&& * 否则,合法性、认证检查,如有错,则错误返回;否则,返回该内存对象的引用标识符。
&&& 共享内存对象的创建者可以控制对于这块内存的访问权限和它的key是公开还是私有。如果有足够的权限,它也可以把共享内存锁定在物理内存中。
&&& 参见include/linux/shm.h
&&& 2. 关联。在创建或获得某个共享内存区域的引用标识符后,还必须将共享内存区域映射(粘附)到进程的虚拟地址空间,然后才能使用该共享内存区域。系统调用 sys_ipc(call值为SHMAT)用于共享内存区到进程虚拟地址空间的映射,而真正完成粘附动作的是函数sys_shmat,
其定义如下:&&&
&&&&&&&#include &sys/types.h&
&&&&&& #include &sys/shm.h&
&&&&&& void *shmat(int shmid, const void *shmaddr, int shmflg);
&&& 其中:
&&&& shmid是shmget返回的共享内存对象的引用标识符;
&&& shmaddr用来指定该共享内存区域在进程的虚拟地址空间对应的虚拟地址;
&&& shmflg是映射标志;
&&&&返回的是 在进程中的虚拟地址
&&& 该函数所做的工作如下:
&&& 1) 根据shmid找到共享内存对象。
&&& 2) 如果shmaddr为0,即用户没有指定该共享内存区域在它的虚拟空间中的位置,则由系统在进程的虚拟地址空间中为其找一块区域(从1G开始);否则,就用shmaddr作为映射的虚拟地址。
&& (If& shmaddr& is NULL, the system chooses a suitable (unused) address a他 which to attach the segment)
&&& 3) 检查虚拟地址的合法性(不能超过进程的最大虚拟空间大小—3G,不能太接近堆栈栈顶)。
&&& 4) 认证检查。
&&& 5) 申请一块内存用于建立数据结构vm_area_struct,填写该结构。
&&& 6) 检查该内存区域,将其加入到进程的mm结构和该共享内存对象的vm_area_struct队列中。
&&& 共享内存的粘附只是创建一个vm_area_struct数据结构,并将其加入到相应的队列中,此时并没有创建真正的共享内存页。
&&& 当进程第一次访问共享虚拟内存的某页时,因为所有的共享内存页还都没有分配,所以会发生一个page fault异常。当Linux处理这个page fault的时候,它找到发生异常的虚拟地址所在的vm_area_struct数据结构。在该数据结构中包含有这类共享虚拟内存的一组处理程序,其中的 nopage操作用来处理虚拟页对应的物理页不存在的情况。对共享内存,该操作是shm_nopage(定义在ipc/shm.c中)。该操作在描述这个共享内存的shmid_kernel数据结构的页表shm_pages中查找发生page fault异常的虚拟地址所对应的页表条目,看共享页是否存在(页表条目为0,表示共享页是第一次使用)。如果不存在,它就分配一个物理页,并为它创建一个页表条目。这个条目不但进入当前进程的页表,同时也存到shmid_kernel数据结构的页表shm_pages中。
&&& 当下一个进程试图访问这块内存并得到一个page fault的时候,经过同样的路径,也会走到函数shm_nopage。此时,该函数查看shmid_kernel数据结构的页表shm_pages时,发现共享页已经存在,它只需把这里的页表项填到进程页表的相应位置即可,而不需要重新创建物理页。所以,是第一个访问共享内存页的进程使得这一页被创建,而随后访问它的其它进程仅把此页加到它们的虚拟地址空间。
&&& 3. 分离。当进程不再需要共享虚拟内存的时候,它们与之分离(detach)。只要仍旧有其它进程在使用这块内存,这种分离就只会影响当前的进程,而不会影响其它进程。当前进程的vm_area_struct数据结构被从shmid_ds中删除,并被释放。当前进程的页表也被更新,共享内存对应的虚拟内存页被标记为无效。当共享这块内存的最后一个进程与之分离时,共享内存页被释放,同时,这块共享内存的shmid_kernel数据结构也被释放。
& 系统调用sys_ipc (call值为SHMDT) 用于共享内存区与进程虚拟地址空间的分离,而真正完成分离动作的是函数&&&&
&&& sys_shmdt,其定义如下:
&&& int sys_shmdt (char *shmaddr)
&&& 其中shmaddr是进程要分离的共享页的开始虚拟地址。
&&& 该函数搜索进程的内存结构中的所有vm_area_struct数据结构,找到地址shmaddr对应的一个,调用函数do_munmap将其释放。
&&& 在函数do_munmap中,将要释放的vm_area_struct数据结构从进程的虚拟内存中摘下,清除它在进程页表中对应的页表项(可能占多个页表项).&
&&& 如果共享的虚拟内存没有被锁定在物理内存中,分离会更加复杂。因为在这种情况下,共享内存的页可能在系统大量使用内存的时候被交换到系统的交换磁盘。为了避免这种情况,可以通过下面的控制操作,将某共享内存页锁定在物理内存不允许向外交换。共享内存的换出和换入,已在第3章中讨论。
&&& 4. 控制。Linux在共享内存上实现的第四种操作是共享内存的控制(call值为SHMCTL的sys_ipc调用),它由函数sys_shmctl实现。控制操作包括获得共享内存对象的状态,设置共享内存对象的参数(如uid、gid、mode、ctime等),将共享内存对象在内存中锁定和释放(在对象的mode上增加或去除SHM_LOCKED标志),释放共享内存对象资源等。
&&& 共享内存提供了一种快速灵活的机制,它允许进程之间直接共享大量的数据,而无须使用拷贝或系统调用。共享内存的主要局限性是它不能提供同步,如果两个进程企图修改相同的共享内存区域,由于内核不能串行化这些动作,因此写的数据可能任意地互相混合。所以使用共享内存的进程必须设计它们自己的同步协议,如用信号灯等。
以下是使用共享内存机制进行进程间通信的基本操作:
需要包含的头文件:
#include &sys/types.h&
#include &sys/ipc.h&
#include &sys/shm.h&
1.创建共享内存:
&int shmget(key_t key,int size,int shmflg);
参数说明:
key:用来表示新建或者已经存在的共享内存去的关键字。
size:创建共享内存的大小。
shmflg:可以指定的特殊标志。IPC_CREATE,IPC_EXCL以及低九位的权限。
shmid=shmget(IPC_PRIVATE,4096,IPC_CREATE|IPC_EXCL|0660);
if(shmid==-1)
perror("shmget()");
2.连接共享内存
char *shmat(int shmid,char *shmaddr,int shmflg);
shmid:共享内存的关键字
shmaddr:指定共享内存出现在进程内存地址的什么位置,通常我们让内核自己决定一个合适的地址位置,用的时候设为0。
shmflg:制定特殊的标志位。
shmp=shmat(shmid,0,0);
if(shmp==(char *)(-1))
perror("shmat()\n");
3.使用共享内存
在使用共享内存是需要注意的是,为防止内存访问冲突,我们一般与信号量结合使用。
4.分离共享内存:当程序不再需要共享内后,我们需要将共享内存分离以便对其进行释放,分离共享内存的函数原形如下:
int shmdt(char *shmaddr);
5. 释放共享内存
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
阅读(8952)
&re: Linux 进程间通信 - 共享内存shmget方式(转)
很好&&&&&&
&re: Linux 进程间通信 - 共享内存shmget方式(转)
分析透彻,强&&&&&&
24252627282930123456789101112131415161718192021222324252627282930311234Linux进程间如何共享内存
共享内存是在内存中单独开辟的一段内存空间,这段内存空间有自己特有的数据结构,包括访问权限、大小和最近访问的时间等。
共享内存 IPC 原理
共享内存进程间通信机制主要用于实现进程间大量的数据传输,下图所示为进程间使用共享内存实现大量数据传输的示意图:
请点击此处输入图片描述
共享内存是在内存中单独开辟的一段内存空间,这段内存空间有自己特有的数据结构,包括访问权限、大小和最近访问的时间等。该数据结构定义如下:
from /usr/include/linux/shm.hstruct shmid_ds {struct ipc_perm shm_ /* operation perms 操作权限 */int shm_ /* size of segment (bytes) 段长度大小 */__kernel_time_t shm_ /* last attach time 最近attach时间 */__kernel_time_t shm_ /* last detach time 最近detach时间 */__kernel_time_t shm_ /* last change time 最近change时间 */__kernel_ipc_pid_t shm_ /* pid of creator 创建者pid */__kernel_ipc_pid_t shm_ /* pid of last operator 最近操作pid */unsigned short shm_ /* no. of current attaches */unsigned short shm_ /* compatibility */void *shm_unused2; /* ditto - used by DIPC */void *shm_unused3; /* unused */|};
两个进程在使用此共享内存空间之前,需要在进程地址空间与共享内存空间之间建立联系,即将共享内存空间挂载到进程中。
系统对共享内存做了以下限制:
#define SHMMAX 0x2000000 /* max shared seg size (bytes) 最大共享段大小 */#define SHMMIN 1 /* min shared seg size (bytes) 最小共享段大小 */#define SHMMNI 4096 /* max num of segs system wide */#define SHMALL (SHMMAX/getpagesize()*(SHMMNI/16))|define SHMSEG SHMMNI /* max shared segs per process */
Linux 共享内存管理
1.创建共享内存
#include &sys/ipc.h& #include &sys/shm.h&
* 第一个参数为 key 值,一般由 ftok() 函数产生
* 第二个参数为欲创建的共享内存段大小(单位为字节)
* 第三个参数用来标识共享内存段的创建标识
int shmget(key_t key, size_t size, int shmflg);
2.共享内存控制
#include &sys/ipc.h& #include &sys/shm.h&
* 第一个参数为要操作的共享内存标识符
* 第二个参数为要执行的操作
* 第三个参数为 shmid_ds 结构的临时共享内存变量信息
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
3.映射共享内存对象
系统调用 shmat() 函数实现将一个共享内存段映射到调用进程的数据段中,并返回内存空间首地址,其函数声明如下:
#include &sys/types.h&#include &sys/shm.h&
* 第一个参数为要操作的共享内存标识符
* 第二个参数用来指定共享内存的映射地址,非0则为此参数,为0的话由系统分配
* 第三个参数用来指定共享内存段的访问权限和映射条件
void *shmat(int shmid, const void *shmaddr, int shmflg);
4.分离共享内存对象
在使用完毕共享内存空间后,需要使用 shmdt() 函数调用将其与当前进程分离。函数声明如下:
#include &sys/types.h&#include &sys/shm.h&
* 参数为分配的共享内存首地址
int shmdt(const void *shmaddr);
共享内存在父子进程间遵循的约定
1.使用 fork() 函数创建一个子进程后,该进程继承父亲进程挂载的共享内存。
2.如果调用 exec() 执行一个新的程序,则所有挂载的共享内存将被自动卸载。
3.如果在某个进程中调用了 exit() 函数,所有挂载的共享内存将与当前进程脱离关系。
申请一段共享内存,父进程在首地址处存入一整数,子进程读出。
#include#include &sys/ipc.h&#include &sys/shm.h&#include &sys/types.h&#include#include#define SHM_SIZE 1024int main(){int shm_id,int *ptr = NULL;
/* 申请共享内存 */
shm_id = shmget((key_t)1004, SHM_SIZE, IPC_CREAT | 0600);
/* 映射共享内存到进程地址空间 */
ptr = (int*)shmat(shm_id, 0, 0);printf("Attach addr is %p \n", ptr);*ptr = 1004;printf("The Value of Parent is : %d \n", *ptr);if((pid=fork()) == -1){perror("fork Err");exit(0);}else if(!pid){printf("The Value of Child is : %d \n", *ptr);exit(0);}else{sleep(1);
/* 解除映射 */
shmdt(ptr);
/* 删除共享内存 */
shmctl(shm_id, IPC_RMID, 0);}return 0;}
输出结果:
请点击此处输入图片描述
原文来自:http://www.linuxidc.com/Linux/831.htm
本文地址:https://www.linuxprobe.com/linux-system-v.html 编辑:周晓雪,审核员:逄增宝
责任编辑:
声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
今日搜狐热点新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
白手起家, 积分 58, 距离下一级还需 142 积分
论坛徽章:0
CentOS 6.5
& & size_t size = 65536;
& & //创建共享内存&&
& & printf(&size=%ld\n&, size);
& & shmid = shmget((key_t)1234, size, 0666|IPC_CREAT);
size为65535及以下就没问题,但只要超过65535就失败
查了下系统的shmmax
[root@localhost sm]# cat /proc/sys/kernel/shmmax
到底怎么回事呢?
哪位大侠给个建议?
丰衣足食, 积分 616, 距离下一级还需 384 积分
论坛徽章:3
怎么失败, errno是什么都没说, 怎么知道怎么失败?
白手起家, 积分 58, 距离下一级还需 142 积分
论坛徽章:0
忘了写了,error=22
富甲一方, 积分 45648, 距离下一级还需 4352 积分
论坛徽章:208
fishrei 发表于
CentOS 6.5
& & size_t size = 65536;
是不是要page align?
富甲一方, 积分 45648, 距离下一级还需 4352 积分
论坛徽章:208
流氓无产者 发表于
是不是要page align?
腰缠万贯, 积分 8036, 距离下一级还需 1964 积分
论坛徽章:254
流氓无产者
对齐个啥?
我看应该是这个key的已经存在且size=65535。
试试换个key,或者用IPC_PRIVATE
巨富豪门, 积分 38223, 距离下一级还需 1777 积分
论坛徽章:4
你不删掉这个key,怎么可能重新shmget一个不同size的共享内存呢。
论坛徽章:42
所以我一直建议大家不要用SysV IPC,改用POSIX才好。
你看mmap就没有key leak的问题。
白手起家, 积分 58, 距离下一级还需 142 积分
论坛徽章:0
@hellioncu @linux_c_py_php
果然是key已经存在了
北京盛拓优讯信息技术有限公司. 版权所有 京ICP备号 北京市公安局海淀分局网监中心备案编号:22
广播电视节目制作经营许可证(京) 字第1234号
中国互联网协会会员&&联系我们:
感谢所有关心和支持过ChinaUnix的朋友们
转载本站内容请注明原作者名及出处常思己过,勿论人非。
Linux 内存共享陷阱及分析(shmget,shmat,shmdt,shmctl)
所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的地址空间里去。所有进程都能访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会即时被有访问同一段共享内存的其他进程看到。共享内存的使用大大降低了在大规模数据处理过程中内存的消耗,但是共享内存的使用中有很多的陷阱,一不注意就很容易导致程序崩溃。
超过共享内存的大小限制?
在一个linux服务器上,共享内存的总体大小是有限制的,这个大小通过SHMMAX参数来定义(以字节为单位),您可以通过执行以下命令来确定 SHMMAX 的值:
如果机器上创建的共享内存的总共大小超出了这个限制,在程序中使用标准错误perror可能会出现以下的信息:
解决方法:
1、设置 SHMMAX
SHMMAX 的默认值是 32MB 。一般使用下列方法之一种将 SHMMAX 参数设为 2GB :
通过直接更改 /proc 文件系统,你不需重新启动机器就可以改变 SHMMAX 的默认设置。我使用的方法是将以下命令放入 /&etc/rc.local 启动文件中:
您还可以使用 sysctl 命令来更改 SHMMAX 的值:
最后,通过将该内核参数插入到 /etc/sysctl.conf 启动文件中,您可以使这种更改永久有效:
2、设置 SHMMNI
我们现在来看 SHMMNI 参数。这个内核参数用于设置系统范围内共享内存段的最大数量。该参数的默认值是 4096 。这一数值已经足够,通常不需要更改。
您可以通过执行以下命令来确定 SHMMNI 的值:
3、设置 SHMALL
最后,我们来看 SHMALL 共享内存内核参数。该参数控制着系统一次可以使用的共享内存总量(以页为单位)。简言之,该参数的值始终应该至少为:
SHMALL 的默认大小为 2097152 ,可以使用以下命令进行查询:
SHMALL 的默认设置对于我们来说应该足够使用。
注意: 在 i386 平台上 Red Hat Linux 的 页面大小 为 4096 字节。但是,您可以使用 bigpages ,它支持配置更大的内存页面尺寸。
多次进行shmat会出现什么问题?
当首次创建共享内存段时,它并不能被任何进程所访问。为了使共享内存区可以被访问,则必须通过 shmat 函数将其附加( attach )到自己的进程空间中,这样进程就与共享内存建立了连接。该函数声明在 linux/shm.h中:
参数 shmid 是 shmget() 的返回值,是个标识符;
参数 shmflg 是存取权限标志;如果为 0 ,则不设置任何限制权限。在 中定义了几个权限:
如果指定 SHM_RDONLY ,那么共享内存区只有读取权限。
参数 shmaddr 是共享内存的附加点,不同的取值有不同的含义:
?如果为空,则由内核选择一个空闲的内存区;如果非空,返回地址取决于调用者是否给 shmflg 参数指定 SHM_RND 值,如果没有指定,则共享内存区附加到由 shmaddr 指定的地址;否则附加地址为 shmaddr 向下舍入一个共享内存低端边界地址后的地址 (SHMLBA ,一个常址)。
?通常将参数 shmaddr 设置为 NULL 。
shmat() 调用成功后返回一个指向共享内存区的指针,使用该指针就可以访问共享内存区了,如果失败则返回 -1。
其映射关系如下图所示:
图1.1 共享内存映射图
其中,shmaddr表示的是物理内存空间映射到进程的虚拟内存空间时候,虚拟内存空间中该块内存的起始地址,在使用中,因为我们一般不清楚进程中哪些地址没有被占用,所以不好指定物理空间的内存要映射到本进程的虚拟内存地址,一般会让内核自己指定:
这样挂载一个共享内存如果是一次调用是没有问题的,但是一个进程是可以对同一个共享内存多次 shmat进行挂载的,物理内存是指向同一块,如果shmaddr为NULL,则每次返回的线性地址空间都不同。而且指向这块共享内存的引用计数会增加。也就是进程多块线性空间会指向同一块物理地址。这样,如果之前挂载过这块共享内存的进程的线性地址没有被shmdt掉,即申请的线性地址都没有释放,就会一直消耗进程的虚拟内存空间,很有可能会最后导致进程线性空间被使用完而导致下次shmat或者其他操作失败。
解决方法:
可以通过判断需要申请的共享内存指针是否为空来标识是否是第一次挂载共享内存,若是则使用进行挂载,若不是则退出。
函数shmat将标识号为shmid共享内存映射到调用进程的地址空间中,映射的地址由参数shmaddr和shmflg共同确定,其准则为:
(1) 如果参数shmaddr取值为NULL,系统将自动确定共享内存链接到进程空间的首地址。
(2) 如果参数shmaddr取值不为NULL且参数shmflg没有指定SHM_RND标志,系统将运用地址shmaddr链接共享内存。
(3) 如果参数shmaddr取值不为NULL且参数shmflg指定了SHM_RND标志位,系统将地址shmaddr对齐后链接共享内存。其中选项SHM_RND的意思是取整对齐,常数SHMLBA代表了低边界地址的倍数,公式“shmaddr – (shmaddr % SHMLBA)”的意思是将地址shmaddr移动到低边界地址的整数倍上。
Shmget创建共享内存,当key相同时,什么情况下会出错?
shmget() 用来创建一个共享内存区,或者访问一个已存在的共享内存区。该函数定义在头文件 linux/shm.h中,原型如下:
参数 key是由 ftok() 得到的键值;
参数 size 是以字节为单位指定内存的大小;
参数 shmflg 是操作标志位,它的一些宏定义如下:
IPC_CREATE : 调用 shmget 时,系统将此值与其他共享内存区的 key 进行比较,如果存在相同的 key ,说明共享内存区已存在,此时返回该共享内存区的标识符,否则新建一个共享内存区并返回其标识符。
IPC_EXCL : 该宏必须和 IPC_CREATE 一起使用,否则没意义。当 shmflg 取 IPC_CREATE | IPC_EXCL 时,表示如果发现内存区已经存在则返回 -1,错误代码为 EEXIST 。
注意,当创建一个新的共享内存区时,size 的值必须大于 0 ;如果是访问一个已经存在的内存共享区,则置 size 为 0 。
一般我们创建共享内存的时候会在一个进程中使用shmget来创建共享内存,
而在另外的进程中,使用shmget和同样的key来获取到这个已经创建了的共享内存,
如果创建进程和挂接进程key相同,而对应的size大小不同,是否会shmget失败?
? 已经创建的共享内存的大小是可以调整的,但是已经创建的共享内存的大小只能调小,不能调大
创建了一个4M大小的共享内存,如果这个共享内存没有删掉,我们再使用
来创建一个10M大小的共享内存的时候,使用标准错误输出会有如下错误信息:
但是,如果我们使用:
来创建一个3M大小的共享内存的时候,并不会输出错误信息,只是共享内存大小会被修改为3145728,这也说明,使用共享内存的时候,是用key来作为共享内存的唯一标识的,共享内存的大小不能区分共享内存。
这样会导致什么问题?
当多个进程都能创建共享内存的时候,如果key出现相同的情况,并且一个进程需要创建的共享内存的大小要比另外一个进程要创建的共享内存小,共享内存大的进程先创建共享内存,共享内存小的进程后创建共享内存,小共享内存的进程就会获取到大的共享内存进程的共享内存, 并修改其共享内存的大小和内容(留意下面的评论补充),从而可能导致大的共享内存进程崩溃。
解决方法:
在所有的共享内存创建的时候,使用排他性创建,即使用IPC_EXCL标记:
在共享内存挂接的时候,先使用排他性创建判断共享内存是否已经创建,如果还没创建则进行出错处理,若已经创建,则挂接:
虽然都希望自己的程序能和其他的程序预先约定一个唯一的键值,但实际上并不是总可能的成行的,因为自己的程序无法为一块共享内存选择一个键值。因此,在此把key设为IPC_PRIVATE,这样,操作系统将忽略键,建立一个新的共享内存,指定一个键值,然后返回这块共享内存IPC标识符ID。而将这个新的共享内存的标识符ID告诉其他进程可以在建立共享内存后通过派生子进程,或写入文件或管道来实现,即这种方法不使用key来创建共享内存,由操作系统来保证唯一性。
ftok是否一定会产生唯一的key值?
系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
ftok原型如下:
pathname就时你指定的文件名,proj_id是子序号。
在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。如指定文件的索引节点号为65538,换算成16进制为0×010002,而你指定的proj_id值为38,换算成16进制为0×26,则最后的key_t返回值为0×。
查询文件索引节点号的方法是: ls -i
但当删除重建文件后,索引节点号由操作系统根据当时文件系统的使用情况分配,因此与原来不同,所以得到的索引节点号也不同。
根据pathname指定的文件(或目录)名称,以及proj_id参数指定的数字,ftok函数为IPC对象生成一个唯一性的键值。在实际应用中,很容易产生的一个理解是,在proj_id相同的情况下,只要文件(或目录)名称不变,就可以确保ftok返回始终一致的键值。然而,这个理解并非完全正确,有可能给应用开发埋下很隐晦的陷阱。因为ftok的实现存在这样的风险,即在访问同一共享内存的多个进程先后调用ftok函数的时间段中,如果pathname指定的文件(或目录)被删除且重新创建,则文件系统会赋予这个同名文件(或目录)新的i节点信息,于是这些进程所调用的ftok虽然都能正常返回,但得到的键值却并不能保证相同。由此可能造成的后果是,原本这些进程意图访问一个相同的共享内存对象,然而由于它们各自得到的键值不同,实际上进程指向的共享内存不再一致;如果这些共享内存都得到创建,则在整个应用运行的过程中表面上不会报出任何错误,然而通过一个共享内存对象进行数据传输的目的将无法实现。
所以如果要确保key_t值不变, 要么确保ftok的文件不被删除,要么不用ftok,指定一个固定的key_t值。
如果存在生成key_t值的文件被删除过,则很有可能自己现在使用的共享内存key_t值会和另外一个进程的key_t值冲突,如下面这种情况:
进程1使用文件1来ftok生成了key10000,进程2使用文件2来ftok生成了key 11111,此时如果进程1和进程2都需要下载文件,并将文件的内容更新到共享内存,此时进程1和2都需要先下文件,再删掉之前的共享内存,再使用ftok生成新的key,再用这个key去申请新的共享内存来装载新的问题,但是可能文件2比较大,下载慢,而文件1比较小,下载比较慢,由于文件1和文件2都被修改,此时文件1所占用的文件节点号可能是文件2之前所占用的,此时如果下载的文件1的ftok生成的key为11111的话,就会和此时还没有是否11111这个key的进程2的共享内存冲突,导致出现问题。
解决方法:
在有下载文件操作的程序中,对下载的文件使用ftok获取key的时候,需要进行冲突避免的措施,如使用独占的方式获取共享内存,如果不成功,则对key进行加一操作,再进行获取共享内存,一直到不会产生冲突为止。
下载文件之前,将之前的文件进行mv一下,先“占”着这个文件节点号,防止其他共享内存申请key的时候获取到。
创建进程在通知其他进程挂接的时候,建议不使用ftok方式来获取Key,而使用文件或者进程间通信的方式告知。
共享内存删除的陷阱?
当进程结束使用共享内存区时,要通过函数 shmdt 断开与共享内存区的连接。该函数声明在 sys/shm.h 中,其原型如下:
参数 shmaddr 是 shmat 函数的返回值。
进程脱离共享内存区后,数据结构 shmid_ds 中的 shm_nattch 就会减 1 。但是共享段内存依然存在,只有 shm_attch 为 0 后,即没有任何进程再使用该共享内存区,共享内存区才在内核中被删除。一般来说,当一个进程终止时,它所附加的共享内存区都会自动脱离。
我们通过:
来删除已经存在的共享内存。
第一个参数,shmid,是由shmget所返回的标记符。
第二个参数,cmd,是要执行的动作。他可以有三个值:
第三个参数,buf,是一个指向包含共享内存模式与权限的结构的指针,删除的时候可以默认为0。
如果共享内存已经与所有访问它的进程断开了连接,则调用IPC_RMID子命令后,系统将立即删除共享内存的标识符,并删除该共享内存区,以及所有相关的数据结构;
如果仍有别的进程与该共享内存保持连接,则调用IPC_RMID子命令后,该共享内存并不会被立即从系统中删除,而是被设置为IPC_PRIVATE状态,并被标记为”已被删除”(使用ipcs命令可以看到dest字段);直到已有连接全部断开,该共享内存才会最终从系统中消失。
需要说明的是:一旦通过shmctl对共享内存进行了删除操作,则该共享内存将不能再接受任何新的连接,即使它依然存在于系统中!所以,可以确知, 在对共享内存删除之后不可能再有新的连接,则执行删除操作是安全的;否则,在删除操作之后如仍有新的连接发生,则这些连接都将可能失败!
Shmdt和shmctl的区别:
Shmdt 是将共享内存从进程空间detach出来,使进程中的shmid无效化,不可以使用。但是保留空间。
而shmctl(sid,IPC_RMID,0)则是删除共享内存,彻底不可用,释放空间。
没有更多推荐了,

我要回帖

更多关于 共享内存 c shmget 的文章

 

随机推荐