怎么打印一块共享内存磁盘首尾没有空间guid空间地址

Linux 共享内存 详解
一、什么是共享内存区
共享内存区是最快的可用IPC形式。它允许多个不相关的进程去访问同一部分逻辑内存。如果需要在两个运行中的进程之间传输数据,共享内存将是一种效率极高的解决方案。一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传输就不再涉及内核。
一、什么是共享内存区
共享内存区是最快的可用IPC形式。它允许多个不相关的进程去访问同一部分逻辑内存。如果需要在两个运行中的进程之间传输数据,共享内存将是一种效率极高的解决方案。一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传输就不再涉及内核。这样就可以减少系统调用时间,提高程序效率。
共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址空间中。其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去。所有进程都可以访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会立刻被有访问同一段共享内存的其他进程看到。
要注意的是共享内存本身没有提供任何同步功能。也就是说,在第一个进程结束对共享内存的写操作之前,并没有什么自动功能能够预防第二个进程开始对它进行读操作。共享内存的访问同步问题必须由程序员负责。可选的同步方式有互斥锁、条件变量、读写锁、纪录锁、信号灯。
在将共享内存前我们要先来介绍下面几个函数。
mmap函数把一个文件或一个Posix共享内存区对象映射到调用进程的地址空间。使用该函数有三个目的:
1.使用普通文件以提供内存映射I/O
2.使用特殊文件以提供匿名内存映射。
3.使用shm_open以提供无亲缘关系进程间的Posix共享内存区。
把I/O文件映射到一个存储区域中
#include &sys/mman.h&
函数原形:
void *mmap(void *addr,size_t len,int prot,int flag,int filedes,off_t off);
指向映射存储区的起始地址
映射的字节
对映射存储区的保护要求
flag标志位
要被映射文件的描述符
要映射字节在文件中的起始偏移量
若成功则返回映射区的起始地址,若出错则返回MAP_FAILED
addr参数用于指定映射存储区的起始地址。通常将其设置为NULL,这表示由系统选择该映射区的起始地址。
filedes指要被映射文件的描述符。在映射该文件到一个地址空间之前,先要打开该文件。len是映射的字节数。
off是要映射字节在文件中的起始偏移量。通常将其设置为0。
prot参数说明对映射存储区的保护要求。可将prot参数指定为PROT_NONE,或者是PROT_READ(映射区可读),PROT_WRITE(映射区可写),PROT_EXEC(映射区可执行)任意组合的按位或,也可以是PROT_NONE(映射区不可访问)。对指定映射存储区的保护要求不能超过文件open模式访问权限。
flag参数影响映射区的多种属性:
MAP_FIXED返回值必须等于addr.因为这不利于可移植性,所以不鼓励使用此标志。
MAP_SHARED这一标志说明了本进程对映射区所进行的存储操作的配置。此标志指定存储操作修改映射文件。
MAP_PRIVATE本标志导致对映射区建立一个该映射文件的一个私有副本。所有后来对该映射区的引用都是引用该副本,而不是原始文件。
要注意的是必须指定MAP_FIXED或MAP_PRIVATE标志其中的一个,指定前者是对存储映射文件本身的一个操作,而后者是对其副本进行操作。
mmap成功返回后,fd参数可以关闭。该操作对于由mmap建立的映射关系没有影响。为从某个进程的地址空间删除一个映射关系,我们调用munmap.
解除存储映射
#include &sys/mman.h&
函数原形:
int munmap(caddr_t addr,size_t len);
指向映射存储区的起始地址
映射的字节
若成功则返回0,若出错则返回-1
其中addr参数是由mmap返回的地址,len是映射区的大小。再次访问这些地址导致向调用进程产生一个SIGSEGV信号。
如果被映射区是使用MAP_PRIVATE标志映射的,那么调用进程对它所作的变动都被丢弃掉。
内核的虚存算法保持内存映射文件(一般在硬盘上)与内存映射区(在内存中)的同步(前提它是MAP_SHARED内存区)。这就是说,如果我们修改了内存映射到某个文件的内存区中某个位置的内容,那么内核将在稍后某个时刻相应地更新文件。然而有时候我们希望确信硬盘上的文件内容与内存映射区中的文件内容一致,于是调用msync来执行这种同步。
同步文件到存储器
#include &sys/mman.h&
函数原形:
int msync(void *addr,size_t len,int flags);
指向映射存储区的起始地址
映射的字节
若成功则返回0,若出错则返回-1
其中addr和len参数通常指代内存中的整个内存映射区,不过也可以指定该内存区的一个子集。flags参数为MS_ASYNC(执行异步写),MS_SYNC(执行同步写),MS_INVALIDATE(使高速缓存的数据实效)。其中MS_ASYNC和MS_SYNC这两个常值中必须指定一个,但不能都指定。它们的差别是,一旦写操作已由内核排入队列,MS_ASYNC即返回,而MS_SYNC则要等到写操作完成后才返回。如果还指定了MS_INVALIDATE,那么与其最终拷贝不一致的文件数据的所有内存中拷贝都失效。后续的引用将从文件取得数据。
复制映射存储区
#include &string.h&
函数原形:
void *memcpy(void *dest,const void *src,size_t n);
待复制的映射存储区
复制后的映射存储区
待复制的映射存储区的大小
返回dest的首地址
memcpy拷贝n个字节从dest到src。
下面就是利用mmap函数影射I/O实现的cp命令。
#include &unistd.h&
#include &fcntl.h&
#include &sys/mman.h&
#include &sys/stat.h&
#include &sys/types.h&
int main(int argc,char *argv[])
void *arc,
if(argc!=3)
printf(“please input two file!\n”);
if((fdin=open(argv[1],O_RDONLY))&0) /*打开原文件*/
perror(argv[1]);
if((fdout=open(argv[2],O_RDWR|O_CREAT|O_TRUNC))&0)/*创建并打开目标文件*/
perror(argv[2]);
if(fstat(fdin,&statbuf)&0) /*获得文件大小信息*/
printf(“fstat error”);
if(lseek(fdout,statbuf.st_size-1,SEEK_SET)==-1)/*初始化输出映射存储区*/
printf(“lseek error”);
if(write(fdout,”1”)!=1)
printf(“write error”);
if((src=mmap(0,statbuf.st_size,PROT_READ,MAP_SHARED,fdin,0))==MAP_FAILED)
/*映射原文件到输入的映射存储区*/
printf(“mmap error);
if((dst=mmap(0,statbuf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fdout,0)) ==MAP_FAILED) /*映射目标文件到输出的映射存储区*/
printf(“mmap error);
memcpy(dst,src,statbuf.st_size);/*复制映射存储区*/
munmap(src,statbuf.st_size); /*解除输入映射*/
munmap(dst,statbuf.st_size); /*解除输出映射*/
close(fdin);
close(fdout);
下面是运行结果:
#cc –o mycp mycp.c
#./mycp test1 test2
三、posix共享内存函数
posix共享内存区涉及两个步骤:
1、指定一个名字参数调用shm_open,以创建一个新的共享内存区对象或打开一个以存在的共享内存区对象。
2、调用mmap把这个共享内存区映射到调用进程的地址空间。传递给shm_open的名字参数随后由希望共享该内存区的任何其他进程使用。
打开或创建一个共享内存区
#include &sys/mman.h&
函数原形:
int shm_open(const char *name,int oflag,mode_t mode);
共享内存区的名字
成功返回0,出错返回-1
oflag参数必须含有O_RDONLY和O_RDWR标志,还可以指定如下标志:O_CREAT,O_EXCL或O_TRUNC.
mode参数指定权限位,它指定O_CREAT标志的前提下使用。
shm_open的返回值是一个整数描述字,它随后用作mmap的第五个参数。
shm_unlink
删除一个共享内存区
#include &sys/mman.h&
函数原形:
int shm_unlink(const char *name);
共享内存区的名字
成功返回0,出错返回-1
shm_unlink函数删除一个共享内存区对象的名字,删除一个名字仅仅防止后续的open,mq_open或sem_open调用取得成功。
下面是创建一个共享内存区的例子:
#include &sys/mman.h&
#include &stdio.h&
#include &fcntl.h&
int main(int argc,char **argv)
if(argc!=2)
printf(“usage:shm_open &pathname&\n”);
shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);
printf(“shmid:%d\n”,shm_id);
shm_unlink(argv[1]);
下面是运行结果,注意编译程序我们要加上“-lrt”参数。
#cc –lrt –o shm_open shm_open.c
#./shm_open test
四、ftruncate和fstat函数
普通文件或共享内存区对象的大小都可以通过调用ftruncate修改。
调整文件或共享内存区大小
#include &unistd.h&
函数原形:
int ftruncate(int fd,off_t length);
成功返回0,出错返回-1
当打开一个已存在的共享内存区对象时,我们可调用fstat来获取有关该对象的信息。
获得文件或共享内存区的信息
#include &unistd.h&
#include &sys/types.h&
#include &sys/stat.h&
函数原形:
int stat(const char *file_name,struct stat *buf);
成功返回0,出错返回-1
对于普通文件stat结构可以获得12个以上的成员信息,然而当fd指代一个共享内存区对象时,只有四个成员含有信息。
struct stat{
mode_t st_
#include &unistd.h&
#include &sys/type.h&
#include &sys/stat.h&
#include &sys/types.h&
#include &fcntl.h&
#include &sys/mman.h&
int main(int argc,char **argv)
if(argc!=2)
printf(“usage:shm_open &pathname&\n”);
shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);/*创建共享内存*/
ftruncate(shm_id,100);/*修改共享内存的打开*/
fstat(shm_id,&buf); /*把共享内存的信息记录到buf中*/
printf(“uid_t:%d\n”,buf.st_uid); /*共享内存区所有者ID*/
printf(“git_t:%d\n”,buf.st_gid); /*共享内存区所有者组ID*/
printf(“size :%d\n”,buf.st_size); /*共享内存区大小*/
下面是运行结果:
#cc –lrt –o shm_show shm_show.c
#./shm_show test
五、共享内存区的写入和读出
上面我们介绍了mmap函数,下面我们就可以通过这些函数,把进程映射到共享内存区。
然后我们就可以通过共享内存区进行进程间通信了。
下面是共享内存区写入的例子:
#include &sys/stat.h&
#include &fcntl.h&
#include &sys/mman.h&
#include &unistd.h&
int main(int argc,char **argv)
if(argc!=2)
printf(“usage:shm_open &pathname&\n”);
shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);/*创建共享内存区*/
ftruncate(shm_id,100);/*修改共享区大小*/
fstat(shm_id,&buf);
ptr=mmap(NULL,buf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);/*连接共享内存区*/
strcpy(ptr,”hello linux”);/*写入共享内存区*/
printf(“%s\n”,ptr);/*读出共享内存区*/
shm_unlink(argv[1]);/*删除共享内存区*/
下面是运行结果:
#cc –lrt –o shm_write shm_write.c
#./shm_write test
hello linux
六、程序例子
下面是利用pisix共享内存区实现进程间通信的例子:服务器进程读出共享内存区内容,然后清空。客户进程向共享内存区写入数据。直到用户输入“q”程序结束。程序用posix信号量实现互斥。
/*server.c服务器程序*/
#include &sys/stat.h&
#include &fcntl.h&
#include &sys/mman.h&
#include &unistd.h&
#include &semaphore.h&
int main(int argc,char **argv)
if(argc!=2)
printf(“usage:shm_open &pathname&\n”);
shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);/*创建共享内存区*/
ftruncate(shm_id,100);/*调整共享内存区大小*/
sem=sem_open(argv[1],O_CREAD,0644,1);/*创建信号量*/
ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);/*连接共享内存区*/
strcpy(ptr,”\0”);
if((strcmp(ptr,”\0”))==0)/*如果为空,则等待*/
if((strcmp(ptr,”q\n”))==0)/*如果内存为q\n退出循环*/
sem_wait(sem);/*申请信号量*/
printf(“server:%s”,ptr);/*输入共享内存区内容*/
strcpy(ptr,”\0”);/*清空共享内存区*/
sem_pose(sem);/*释放信号量*/
sem_unlink(argv[1]);/*删除信号量*/
shm_unlink(argv[1]);/*删除共享内存区*/
客户端程序:
/*user.c 客户端程序*/
#include &sys/stat.h&
#include &fcntl.h&
#include &sys/mman.h&
#include &unistd.h&
#include &semaphore.h&
#include &stdio.h&
int main(int argc,char **argv)
if(argc!=2)
printf(“usage:shm_open &pathname&\n”);
shm_id=shm_open(argv[1],0);/*打开共享内存区
sem=sem_open(argv[1],0);/*打开信号量*/
ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);/*连接共享内存区*/
sem_wait(sem);/*申请信号量*/
fgets(ptr,10,stdin);/*从键盘读入数据到共享内存区*/
printf(“user:%s”,ptr);
if((strcmp(ptr,”q\n”))==0)
sem_pose(sem);/*释放信号量*/
#cc –lrt –o server server.c
#cc –lrt –o user user.c
#./server test&
#./user test
server:abc
server:123
版权声明:本文内容由互联网用户自发贡献,本社区不拥有所有权,也不承担相关法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至: 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
用云栖社区APP,舒服~
【云栖快讯】红轴机械键盘、无线鼠标等753个大奖,先到先得,云栖社区首届博主招募大赛9月21日-11月20日限时开启,为你再添一个高端技术交流场所&&
共享带宽提供地域级带宽共享和复用功能,支持同地域下所有弹性公网IP共享带宽,进而让绑定弹性公网IP的云服务器EC...
一种高性能、高可靠、可平滑扩容的分布式内存数据库服务。
NAT网关(NAT Gateway)是一款企业级的高性能VPC公网网关,提供SNAT、DNAT等NAT代理转发方...
为您提供简单高效、处理能力可弹性伸缩的计算服务,帮助您快速构建更稳定、安全的应用,提升运维效率,降低 IT 成本...
MaxCompute75折抢购
Loading...如何用ftok来产生相关的key,然后共享内存
我的图书馆
如何用ftok来产生相关的key,然后共享内存
在IPC中,我们经常用用key_t的值来创建或者打开信号量,共享内存和消息队列。这个在IPC的环境中十分的重要,比如说,服务器创建了一个消息队列,等待客户机发送请求。那么如何创建或者打开已有的消息队列呢?一般而言,我们对于服务器使用的路径和项目id(proj_id)是已知的,所以客户机可以获取相同的key来打开 消息队列并进行操作。下面就是ftok的使用原型:# include &sys/types.h&# include &sys/ipc.h&key_t ftok(const char *pathname, int proj_id);注意:1)pathname一定要在系统中存在2)pathname一定是使用进程能够访问的3)proj_id是一个1-255之间的一个整数值,典型的值是一个ASCII值。当成功执行的时候,一个key_t值将会被返回,否则-1被返回。我们可以使用errno来确定具体的错误信息如果我们很懒惰的话,不妨就使用perror函数来答应对应的出错字符信息。下面的程序简单的演示和打印如何使用ftok及其对应值
共享内存实现分为两个步骤:一、创建共享内存,使用shmget函数。二、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数
由于这种方式是直接对用户空间的内存进行映射,故效率较高,使用起来也方便;
但需要注意的是,多个进程并发访问共享内存时的同步控制问题,可用信号量操作解决。
系统调用:shmget( ) ;原型:int shmget ( key_t key, int size, int shmflg );&&& 返回值:如果成功,返回共享内存段标识符。&&& 如果失败,则返回- 1:&&& errno = EINVAL (无效的内存段大小)&&& &&& && EEXIST (内存段已经存在,无法创建)&&& &&& && EIDRM (内存段已经被删除)&&& &&& && ENOENT (内存段不存在)&&& &&& && EACCES (权限不够)&&& &&& && ENOMEM (没有足够的内存来创建内存段)
函数中的key_t如何获得已经在上一篇文章中说明了
系统调用:shmat();原型:int shmat ( int shmid, char *shmaddr, int shmflg);&&& 返回值:如果成功,则返回共享内存段连接到进程中的地址。&&& 如果失败,则返回- 1:&&& errno = EINVAL (无效的IPC ID 值或者无效的地址)&&& &&& && ENOMEM (没有足够的内存)&&& &&& && EACCES (存取权限不够)
当一个进程不在需要共享的内存段时,它将会把内存段从其地址空间中脱离。系统调用:shmdt();调用原型:int shmdt ( char *shmaddr );&&& 返回值:如果失败,则返回- 1:&&& &&& errno = EINVAL (无效的连接地址)
举例:shm1.c
#include &stdio.h&
#include &stdlib.h& #include &fcntl.h& #include &unistd.h& #include &sys/stat.h& #include &sys/types.h& #include &string.h& #include &sys/ipc.h& #include &sys/shm.h& #include &sys/sem.h& int main() { && key_ && && && char *addr_c; && char srcbuf[8]="abcdefg"; && ipckey=ftok("/home/yds/tmp/shmipc",0); && shmid=shmget(ipckey,1024,IPC_CREAT|0666); && if(shmid==-1) &&& { &&&&&& printf("creat shm error!\n"); &&&&&& return -1; &&&& } && addr_c=(char *)shmat(shmid,0,0); && if((int)addr_c==-1) &&& { &&&&&& printf("attach shm error!\n"); &&&&&& return -1; &&&& } && while(1) && {&&&&& &&&&&& for(i=0;i&strlen(srcbuf);i++) &&&&&& { &&&&&&&& srcbuf[i]=srcbuf[i]+1; &&&&&& } &&&&&& strcpy(addr_c,srcbuf); &&&&&& sleep(1);&& && } && shmdt(addr_c); && return 0; }
#include &stdio.h& #include &stdlib.h& #include &fcntl.h& #include &unistd.h& #include &sys/stat.h& #include &sys/types.h& #include &string.h& #include &sys/ipc.h& #include &sys/shm.h& #include &sys/sem.h& int main() { key_
char *addr_c; ipckey=ftok("/home/yds/tmp/shmipc",0); shmid=shmget(ipckey,1024,IPC_CREAT|0666); if(shmid==-1){ printf("creat shm error!\n"); return -1; } addr_c=(char *)shmat(shmid,0,0); if((int)addr_c==-1) { printf("attach shm error!\n"); return -1; }
while(1) { sleep(2); printf("string(shm) is:%s\n",addr_c); } shmdt(addr_c); return 0; }
以上两个程序shm1.c先运行,shm2.c后运行 由于没有采用信号量机制对进程进行同步,所以只能先人为规定一下执行顺序了,只在说明问题,不在实用。
进程shm1负责每秒钟向共享内存中写入数据, shm2负责每两秒从共享内存中读数据,这个例子中key_t ipckey 保证操作的共享内存是同一个。上一篇文章有关于key_t的解释
TA的最新馆藏[转]&[转]&[转]&[转]&
喜欢该文的人也喜欢您所在的位置: &
Linux共享内存使用常见陷阱与分析
Linux共享内存使用常见陷阱与分析
所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的地址空间里去。本文详细分析了常见的Linux共享内存陷阱,希望对大家有所帮助。
所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享内存段&连接到&他们自己的地址空间里去。所有进程都能访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会即时被有访问同一段共享内存的其他进程看到。共享内存的使用大大降低了在大规模数据处理过程中内存的消耗,但是共享内存的使用中有很多的陷阱,一不注意就很容易导致程序崩溃。
超过共享内存的大小限制?
在一个linux服务器上,共享内存的总体大小是有限制的,这个大小通过SHMMAX参数来定义(以字节为单位),您可以通过执行以下命令来确定 SHMMAX 的值:
#&cat&/proc/sys/kernel/shmmax&
如果机器上创建的共享内存的总共大小超出了这个限制,在程序中使用标准错误perror可能会出现以下的信息:
unable&to&attach&to&shared&memory&
解决方法:
1、设置 SHMMAX
SHMMAX 的默认值是 32MB 。一般使用下列方法之一种将 SHMMAX 参数设为 2GB :
通过直接更改 /proc 文件系统,你不需重新启动机器就可以改变 SHMMAX 的默认设置。我使用的方法是将以下命令放入 /&etc/rc.local 启动文件中:
#&echo&&&&&/proc/sys/kernel/shmmax&
您还可以使用 sysctl 命令来更改 SHMMAX 的值:
#&sysctl&-w&kernel.shmmax=&
最后,通过将该内核参数插入到 /etc/sysctl.conf 启动文件中,您可以使这种更改永久有效:
#&echo&&kernel.shmmax=&&&/etc/sysctl.conf&
2、设置 SHMMNI
我们现在来看 SHMMNI 参数。这个内核参数用于设置系统范围内共享内存段的最大数量。该参数的默认值是 4096 。这一数值已经足够,通常不需要更改。
您可以通过执行以下命令来确定 SHMMNI 的值:
#&cat&/proc/sys/kernel/shmmni&4096&
3、设置 SHMALL
最后,我们来看 SHMALL 共享内存内核参数。该参数控制着系统一次可以使用的共享内存总量(以页为单位)。简言之,该参数的值始终应该至少为:
ceil(SHMMAX/PAGE_SIZE)&
SHMALL 的默认大小为 2097152 ,可以使用以下命令进行查询:
#&cat&/proc/sys/kernel/shmall&2097152&
SHMALL 的默认设置对于我们来说应该足够使用。
注意: 在 i386 平台上 Red Hat Linux 的 页面大小 为 4096 字节。但是,您可以使用 bigpages ,它支持配置更大的内存页面尺寸。
多次进行shmat会出现什么问题?
当首次创建共享内存段时,它并不能被任何进程所访问。为了使共享内存区可以被访问,则必须通过 shmat 函数将其附加( attach )到自己的进程空间中,这样进程就与共享内存建立了连接。该函数声明在 linux/shm.h中:
#include&#include&void&*shmat(int&shmid,&const&void&*shmaddr,&int&shmflg);&
参数 shmid 是 shmget() 的返回值,是个标识符;
参数 shmflg 是存取权限标志;如果为 0 ,则不设置任何限制权限。在 中定义了几个权限:
#define&SHM_RDONLY&010000&/*&attach&read-only&else&read-write&*/&#define&SHM_RND&020000&/*&round&attach&address&to&SHMLBA&*/&#define&SHM_REMAP&040000&/*&take-over&region&on&attach&*/&
如果指定 SHM_RDONLY ,那么共享内存区只有读取权限。
参数 shmaddr 是共享内存的附加点,不同的取值有不同的含义:
?如果为空,则由内核选择一个空闲的内存区;如果非空,返回地址取决于调用者是否给 shmflg 参数指定 SHM_RND 值,如果没有指定,则共享内存区附加到由 shmaddr 指定的地址;否则附加地址为 shmaddr 向下舍入一个共享内存低端边界地址后的地址 (SHMLBA ,一个常址)。
&O通常将参数 shmaddr 设置为 NULL 。
shmat() 调用成功后返回一个指向共享内存区的指针,使用该指针就可以访问共享内存区了,如果失败则返回 -1。
其映射关系如下图所示:
图1.1 共享内存映射图
其中,shmaddr表示的是物理内存空间映射到进程的虚拟内存空间时候,虚拟内存空间中该块内存的起始地址,在使用中,因为我们一般不清楚进程中哪些地址没有被占用,所以不好指定物理空间的内存要映射到本进程的虚拟内存地址,一般会让内核自己指定:
void&ptr&=&shmat(shmid,&NULL,0);&
这样挂载一个共享内存如果是一次调用是没有问题的,但是一个进程是可以对同一个共享内存多次 shmat进行挂载的,物理内存是指向同一块,如果shmaddr为NULL,则每次返回的线性地址空间都不同。而且指向这块共享内存的引用计数会增加。也就是进程多块线性空间会指向同一块物理地址。这样,如果之前挂载过这块共享内存的进程的线性地址没有被shmdt掉,即申请的线性地址都没有释放,就会一直消耗进程的虚拟内存空间,很有可能会最后导致进程线性空间被使用完而导致下次shmat或者其他操作失败。
解决方法:
可以通过判断需要申请的共享内存指针是否为空来标识是否是第一次挂载共享内存,若是则使用进行挂载,若不是则退出。
void*&ptr&=&NULL;&...&if&(NULL&!=&ptr)&&ptr&=&shmat(shmid,ptr,0666);&
函数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中,原型如下:
#include&#include&int&shmget(key_t&key,&size_t&size,&int&shmflg);&
参数 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来创建共享内存,
Int&shmid&=&shmget(key,&size,&IPC_CREATE|0666);&
而在另外的进程中,使用shmget和同样的key来获取到这个已经创建了的共享内存,
Int&shmid&=&shmget(key,&size,&IPC_CREATE|0666);&
如果创建进程和挂接进程key相同,而对应的size大小不同,是否会shmget失败?
&O&已经创建的共享内存的大小是可以调整的,但是已经创建的共享内存的大小只能调小,不能调大
shm_id&=&shmget(key,4194304,IPC_CREAT);&
创建了一个4M大小的共享内存,如果这个共享内存没有删掉,我们再使用
shm_id&=&shmget(key,,IPC_CREAT);&
来创建一个10M大小的共享内存的时候,使用标准错误输出会有如下错误信息:
shmget&error:&Invalid&argument&
但是,如果我们使用:
shm_id&=&shmget(key,3145728,IPC_CREAT);&
来创建一个3M大小的共享内存的时候,并不会输出错误信息,只是共享内存大小会被修改为3145728,这也说明,使用共享内存的时候,是用key来作为共享内存的唯一标识的,共享内存的大小不能区分共享内存。
这样会导致什么问题?
当多个进程都能创建共享内存的时候,如果key出现相同的情况,并且一个进程需要创建的共享内存的大小要比另外一个进程要创建的共享内存小,共享内存大的进程先创建共享内存,共享内存小的进程后创建共享内存,小共享内存的进程就会获取到大的共享内存进程的共享内存, 并修改其共享内存的大小和内容(留意下面的评论补充),从而可能导致大的共享内存进程崩溃。
解决方法:
在所有的共享内存创建的时候,使用排他性创建,即使用IPC_EXCL标记:
Shmget(key,&size,IPC_CREATE|IPC_EXCL);&
在共享内存挂接的时候,先使用排他性创建判断共享内存是否已经创建,如果还没创建则进行出错处理,若已经创建,则挂接:
Shmid&=&Shmget(key,&size,IPC_CREATE|IPC_EXCL);&If&(-1&!=&shmid)&{&Printf(&error&);&}&Shmid&=&Shmget(key,&size,IPC_CREATE);&
虽然都希望自己的程序能和其他的程序预先约定一个唯一的键值,但实际上并不是总可能的成行的,因为自己的程序无法为一块共享内存选择一个键值。因此,在此把key设为IPC_PRIVATE,这样,操作系统将忽略键,建立一个新的共享内存,指定一个键值,然后返回这块共享内存IPC标识符ID。而将这个新的共享内存的标识符ID告诉其他进程可以在建立共享内存后通过派生子进程,或写入文件或管道来实现,即这种方法不使用key来创建共享内存,由操作系统来保证唯一性。
ftok是否一定会产生唯一的key值?
系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
ftok原型如下:
key_t&ftok(&char&*&pathname,&int&proj_id)&
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 中,其原型如下:
#include&#include&int&shmdt(const&void&*shmaddr);&
参数 shmaddr 是 shmat 函数的返回值。
进程脱离共享内存区后,数据结构 shmid_ds 中的 shm_nattch 就会减 1 。但是共享段内存依然存在,只有 shm_attch 为 0 后,即没有任何进程再使用该共享内存区,共享内存区才在内核中被删除。一般来说,当一个进程终止时,它所附加的共享内存区都会自动脱离。
我们通过:
int&shmctl(&int&shmid&,&int&cmd&,&struct&shmid_ds&*buf&);&
来删除已经存在的共享内存。
第一个参数,shmid,是由shmget所返回的标记符。
第二个参数,cmd,是要执行的动作。他可以有三个值:
IPC_STAT 设置shmid_ds结构中的数据反射与共享内存相关联的值。
IPC_SET 如果进程有相应的权限,将与共享内存相关联的值设置为shmid_ds数据结构中所提供的值。
IPC_RMID 删除共享内存段。
第三个参数,buf,是一个指向包含共享内存模式与权限的结构的指针,删除的时候可以默认为0。
如果共享内存已经与所有访问它的进程断开了连接,则调用IPC_RMID子命令后,系统将立即删除共享内存的标识符,并删除该共享内存区,以及所有相关的数据结构;
如果仍有别的进程与该共享内存保持连接,则调用IPC_RMID子命令后,该共享内存并不会被立即从系统中删除,而是被设置为IPC_PRIVATE状态,并被标记为&已被删除&(使用ipcs命令可以看到dest字段);直到已有连接全部断开,该共享内存才会最终从系统中消失。
需要说明的是:一旦通过shmctl对共享内存进行了删除操作,则该共享内存将不能再接受任何新的连接,即使它依然存在于系统中!所以,可以确知, 在对共享内存删除之后不可能再有新的连接,则执行删除操作是安全的;否则,在删除操作之后如仍有新的连接发生,则这些连接都将可能失败!
Shmdt和shmctl的区别:
Shmdt 是将共享内存从进程空间detach出来,使进程中的shmid无效化,不可以使用。但是保留空间。
而shmctl(sid,IPC_RMID,0)则是删除共享内存,彻底不可用,释放空间。
原文链接:
【编辑推荐】
【责任编辑: TEL:(010)】
关于&&的更多文章
内核是操作系统的核心,它控制着硬件和应用。应用并不直接和硬件
随着云计算、物联网、大数据、移动互联网的大发展,你应该知道这些。
八月的天气依然那样炎热,仿佛一点星火就会引起爆炸。
那些由“补丁周二”引发的大麻烦已经成为传说,但却依
日前,微软宣布 Windows 8.1 企业预览版开放下载,很
本书是Inside Microsoft SQL Server 2005系列四本著作中的一本。它详细介绍了T-SQL的内部构造,包含了非常全面的编程参考。它提
51CTO旗下网站

我要回帖

更多关于 共享内存 的文章

 

随机推荐