-
2020-12-19 20:07:10
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
#include
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
#include
#include
1.创建共享内存:
int shmget(key_t key,int size,int shmflg);
参数说明:
key:用来表示新建或者已经存在的共享内存去的关键字。
size:创建共享内存的大小。
shmflg:可以指定的特殊标志。IPC_CREATE,IPC_EXCL以及低九位的权限。
eg:
int shmid;
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:制定特殊的标志位。
eg:
int shmid;
char *shmp;
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);
*****************示例**********************
int *__accept_socketfd;
int shmid = shmget(SHAREMEMID,sizeof(int),IPC_CREAT|0666);
if (( __accept_socketfd = (int *)shmat(shmid,NULL,0 )) == (int *)-1 )
{
printf("Error:shmat\n");
return;
}
*__accept_socketfd = 0;
更多相关内容 -
shmget物理内存_Linux共享内存使用常见陷阱与分析
2021-11-20 09:59:15所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享...问题源头:
我有这样的代码片段:shmget的大小限制的问题
if ((shmid = shmget(key, 512, IPC_CREAT | 0666)) < 0) { perror("shmget"); exit(1); }
或
shmid = shmget(ftok(pathname, 'b'), Size, 0666|IPC_CREAT);//ftok:file to key 生成Key_t if (shmid == -1) { perror("shmget failed"); char* msg = strerror(errno); std::cout<<"errno="<<errno<<",msg="<<msg<<std::endl; }
每当我设置任何高于2048的数字,我收到一个错误,指出: shmget的:无效的参数
errno 是 22(EINVAL):参数小于SHMMNI 或大于SHMMAX
然而,当我运行cat/proc/sys/kernel/shmall 或cat /proc/sys/kernel/shmmax,我得到了4294967296(18446744073692774399)
解决办法
$ ipcs -m
,你可以删除你的细分:用(注意只删除您的一个):
$ ipcrm -M <key>
其实是我没有释放这个内key对应的共享内存,当我重新更改大小后,创建(存在即打开)时,
打开的大小和已经存在的大小不一致,则出现了问题。
正文:
所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的地址空间里去。所有进程都能访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会即时被有访问同一段共享内存的其他进程看到。共享内存的使用大大降低了在大规模数据处理过程中内存的消耗,但是共享内存的使用中有很多的陷阱,一不注意就很容易导致程序崩溃。
1.超过共享内存的大小限制?
在一个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 "2147483648" > /proc/sys/kernel/shmmax
您还可以使用 sysctl 命令来更改 SHMMAX 的值:sysctl -w kernel.shmmax=2147483648
最后,通过将该内核参数插入到 /etc/sysctl.conf 启动文件中,您可以使这种更改永久有效:echo "kernel.shmmax=2147483648" >> /etc/sysctl.conf
2、设置 SHMMNI
我们现在来看 SHMMNI 参数。这个内核参数用于设置系统范围内共享内存段的最大数量。该参数的默认值是 4096 。这一数值已经足够,通常不需要更改。您可以通过执行以下命令来确定 SHMMNI 的值:cat /proc/sys/kernel/shmmni
3、设置 SHMALL
最后,我们来看 SHMALL 共享内存内核参数。该参数控制着系统一次可以使用的共享内存总量(以页为单位)。简言之,该参数的值始终应该至少为:ceil(SHMMAX/PAGE_SIZE)
SHMALL 的默认大小为 2097152 ,可以使用以下命令进行查询:cat /proc/sys/kernel/shmall
SHMALL 的默认设置对于我们来说应该足够使用。注意: 在 i386 平台上 Red Hat Linux 的 页面大小 为 4096 字节。但是,您可以使用 bigpages ,它支持配置更大的内存页面尺寸。
需要C/C++ Linux服务器架构师学习资料加qun获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
ca21762244fda33ff052ffaa26c19820.png
2.多次进行shmat会出现什么问题?
当首次创建共享内存段时,它并不能被任何进程所访问。为了使共享内存区可以被访问,则必须通过 shmat 函数将其附加( attach )到自己的进程空间中,这样进程就与共享内存建立了连接。该函数声明在 linux/shm.h中: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 ,一个常址)。
Ø通常将参数 shmaddr 设置为 NULL 。
shmat() 调用成功后返回一个指向共享内存区的指针,使用该指针就可以访问共享内存区了,如果失败则返回 -1。其映射关系如下图所示:
其中,shmaddr表示的是物理内存空间映射到进程的虚拟内存空间时候,虚拟内存空间中该块内存的起始地址,在使用中,因为我们一般不清楚进程中哪些地址没有被占用,所以不好指定物理空间的内存要映射到本进程的虚拟内存地址,一般会让内核自己指定:ptr = shmat(shmid, NULL,0);
这样挂载一个共享内存如果是一次调用是没有问题的,但是一个进程是可以对同一个共享内存多次 shmat进行挂载的,物理内存是指向同一块,如果shmaddr为NULL,则每次返回的线性地址空间都不同。而且指向这块共享内存的引用计数会增加。也就是进程多块线性空间会指向同一块物理地址。这样,如果之前挂载过这块共享内存的进程的线性地址没有被shmdt掉,即申请的线性地址都没有释放,就会一直消耗进程的虚拟内存空间,很有可能会最后导致进程线性空间被使用完而导致下次shmat或者其他操作失败。
>解决方法:
可以通过判断需要申请的共享内存指针是否为空来标识是否是第一次挂载共享内存,若是则使用进行挂载,若不是则退出。
void* ptr = NULL;
if (NULL != ptr) return;
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移动到低边界地址的整数倍上。3.shmget创建共享内存,当key相同时,什么情况下会出错?
shmget() 用来创建一个共享内存区,或者访问一个已存在的共享内存区。该函数定义在头文件 linux/shm.h中,原型如下: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来获取到这个已经创建了的共享内存:shmid = shmget(key, size, IPC_CREATE|0666);
如果创建进程和挂接进程key相同,而对应的size大小不同,是否会shmget失败?
Ø 已经创建的共享内存的大小是可以调整的,但是已经创建的共享内存的大小只能调小,不能调大
当多个进程都能创建共享内存的时候,如果key出现相同的情况,并且一个进程需要创建的共享内存的大小要比另外一个进程要创建的共享内存小,共享内存大的进程先创建共享内存,共享内存小的进程后创建共享内存,小共享内存的进程就会获取到大的共享内存进程的共享内存,并修改其共享内存的大小和内容(留意下面的评论补充),从而可能导致大的共享内存进程崩溃。
>解决方法:
方法一:在所有的共享内存创建的时候,使用排他性创建,即使用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来创建共享内存,由操作系统来保证唯一性。
4.ftok是否一定会产生唯一的key值?
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×26010002。
查询文件索引节点号的方法是: 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,而使用文件或者进程间通信的方式告知。
5.共享内存删除的陷阱?
当进程结束使用共享内存区时,要通过函数 shmdt 断开与共享内存区的连接。该函数声明在 sys/shm.h 中,其原型如下: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)则是删除共享内存,彻底不可用,释放空间。
-
mmap内存映射和shmget共享内存
2019-06-14 12:41:17linux中的两种共享内存。一种是我们的IPC通信System V版本的共享内存,另外的一种就是我们今天提到的存储映射I/O(mmap函数) 在说mmap之前我们先说一下普通的读写文件的原理,进程调用read或是write后会陷入内核...转载原文地址:
linux中的两种共享内存。一种是我们的IPC通信System V版本的共享内存,另外的一种就是我们今天提到的存储映射I/O(mmap函数)
在说mmap之前我们先说一下普通的读写文件的原理,进程调用read或是write后会陷入内核,因为这两个函数都是系统调用,进入系统调用后,内核开始读写文件,假设内核在读取文件,内核首先把文件读入自己的内核空间,读完之后进程在内核回归用户态,内核把读入内核内存的数据再copy进入进程的用户态内存空间。实际上我们同一份文件内容相当于读了两次,先读入内核空间,再从内核空间读入用户空间。
Linux提供了内存映射函数mmap, 它把文件内容映射到一段内存上(准确说是虚拟内存上), 通过对这段内存的读取和修改, 实现对文件的读取和修改,mmap()系统调用使得进程之间可以通过映射一个普通的文件实现共享内存。普通文件映射到进程地址空间后,进程可以向访问内存的方式对文件进行访问,不需要其他系统调用(read,write)去操作。mmap图示例:
mmap系统调用介绍void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);1
21
2这就是mmap系统调用的接口,mmap函数成功返回指向内存区域的指针,图上的进程的地址空间的开始地址就是mmap函数的返回值,失败返回MAP_FAILED。
addr,某个特定的地址作为起始地址,当被设置为NULL,系统会在地址空间选择一块合适的内存区域。
length说的是内存段的长度。
prot是用来设定内存段的访问权限。
prot参数 说明
PROT_READ 内存段可读
PROT_WRITE 内存段可写
PROT_EXEC 内存段可执行
PROT_NONE 内存段不能被访问flags参数控制内存段内容被修改以后程序的行为。
flags参数 说明
MAP_SHARED 进程间共享内存,对该内存段修改反映到映射文件中。提供了POSIX共享内存
MAP_PRIVATE 内存段为调用进程所私有。对该内存段的修改不会反映到映射文件
MAP_ANNOYMOUS 这段内存不是从文件映射而来的。内容被初始化为全0
MAP_FIXED 内存段必须位于start参数指定的地址处,start必须是页大小的整数倍(4K整数倍)
MAP_HUGETLB 按照大内存页面来分配内存空间fd参数是用来被映射文件对应的文件描述符。通过open系统调用得到。offset设定从何处进行映射。
mmap使用注意事项:
利用mmap进行非血缘进程间通信代码:#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<string.h>
struct STU
{
int age;
char name[20];
char sex;
};
int main(int argc,char *argv[]) //这个进程用于创建映射区进行写。
{
if(argc != 2)
{
printf("./a,out file");
exit(1);
}
struct STU student = {10,"xiaoming",'m'};
int fd = open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd < 0)
{
perror("open");
exit(2);
}
ftruncate(fd,sizeof(struct STU)); //文件拓展大小。
struct STU *p = (struct STU*)mmap(NULL,sizeof(struct STU),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//创建一个结构体大小的共享映射区。共享映射区我们可以当做数组区看待。
if(p == MAP_FAILED)
{
perror("mmap");
exit(3);
}
close(fd); //关闭不用的文件描述符。
while(1)
{
memcpy(p,&student,sizeof(student));
student.age++;
sleep(1);
}
int ret = munmap(p,sizeof(student));
if(ret < 0)
{
perror("mmumap");
exit(4);
}
return 0;
}#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
struct STU
{
int age;
char name[20];
char sex;
};
int main(int argc,char *argv[]) //这个进程读
{
if(argc != 2)
{
printf("./a,out file");
exit(1);
}
int fd = open(argv[1],O_RDONLY,0644);
if(fd < 0)
{
perror("open");
exit(2);
}
struct STU student;
struct STU *p = (struct STU*)mmap(NULL,sizeof(struct STU),PROT_READ,MAP_SHARED,fd,0);
if(p == MAP_FAILED)
{
perror("mmap");
exit(3);
}
close(fd);
int i = 0;
while(1)
{
printf("id = %d\tname = %s\t%c\n",p->age,p->name,p->sex);
sleep(2);
}
int ret = munmap(p,sizeof(student));
if(ret < 0)
{
perror("mmumap");
exit(4);
}
return 0;
}代码截图:
分析:因为只创建一个结构体大小的共享内存,后面写入的数据把前面写入的数据覆盖了。
shm调用介绍:参见上一篇博客
http://blog.csdn.net/hj605635529/article/details/67636526shm图示例:
(1)通过int shmget(key_t key, size_t size, int shmflg);在物理内存创建一个共享内存,返回共享内存的编号。
(2)通过void *shmat(int shmid, constvoid shmaddr,int shmflg);连接成功后把共享内存区对象映射到调用进程的地址空间
(3)通过void *shmdt(constvoid* shmaddr);断开用户级页表到共享内存的那根箭头。
(4)通过int shmctl(int shmid, int cmd, struct shmid_ds* buf);释放物理内存中的那块共享内存。总结mmap和shm:
1、mmap是在磁盘上建立一个文件,每个进程地址空间中开辟出一块空间进行映射。
而对于shm而言,shm每个进程最终会映射到同一块物理内存。shm保存在物理内存,这样读写的速度要比磁盘要快,但是存储量不是特别大。
2、相对于shm来说,mmap更加简单,调用更加方便,所以这也是大家都喜欢用的原因。
3、另外mmap有一个好处是当机器重启,因为mmap把文件保存在磁盘上,这个文件还保存了操作系统同步的映像,所以mmap不会丢失,但是shmget就会丢失。---------------------
作者:hj605635529
来源:CSDN
原文:https://blog.csdn.net/hj605635529/article/details/73163513
版权声明:本文为博主原创文章,转载请附上博文链接! -
共享内存_shmget
2021-10-07 20:00:06共享内存是被多个进程共享的一部分物理内存。一个进程向共享内存写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。 如下图所示,共享内存的地址在进程A中的地址可能是0x5000,在进程B中的...共享内存
共享内存是被多个进程共享的一部分物理内存。一个进程向共享内存写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
如下图所示,共享内存的地址在进程A
中的地址可能是0x5000
,在进程B
中的地址可能是0x7000
,这是因为共享内存映射到不同进程当中的不同位置。
共享内存的实现分为
2
个步骤:- 使用
shmget
函数创建共享内存。 - 使用
shmat
函数将创建的共享内存映射到具体的进程空间。
创建共享内存
创建共享内存使用函数
shmget
:int shmget ( key_t key, int size, int shmflg );
key
是标识共享内存的键值,可以取非负整数
或IPC_PRIVATE
。
- 当
key
是IPC_PRIVATE
时,shmflg
不需要IPC_CREAT
。 - 当
key
是非负整数
时,shmflg
需要IPC_CREAT
。
size
是共享内存的大小,以字节为单位。shmflg
是权限标志,与文件的读写权限一样。
如果函数执行成功,则返回共享内存标识符,否则返回
-1
。映射共享内存
映射共享内存使用函数
shmat
:int shmat ( int shmid, char *shmaddr, int flag );
shmid
:shmget
函数返回的共享内存标识符。shmaddr
:通常为0
。flag
:通常为0
。
如果函数执行成功,则返回共享内存映射到进程中的地址,否则返回
-1
。删除共享内存
当一个进程不再需要共享内存时,需要把它从进程地址空间中删除:
int shmdt ( char *shmaddr );
参数
shmaddr
是从shmat
中获得的。父子进程共享内存
代码实例:
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/shm.h> #include <sys/wait.h> #define PERM (S_IRUSR | S_IWUSR) int main ( int argc, char **argv ) { int shmid; char *p_addr, *c_addr; if ( argc != 2 ) { fprintf ( stderr, "Usage: %s\n", argv[0] ); exit ( 1 ); } if ( ( shmid = shmget ( IPC_PRIVATE, 1024, PERM ) ) == -1 ) { /* 创建共享内存 */ fprintf ( stderr, "Create Share Memory Error: %s\n", strerror ( errno ) ); exit ( 1 ); } if ( fork() ) { /* 父进程写 */ p_addr = ( char * ) shmat ( shmid, NULL, 0 ); memset ( p_addr, '\0', 1024 ); strncpy ( p_addr, argv[1], 1024 ); wait ( NULL ); exit ( 0 ); } else { /* 子进程读 */ sleep ( 1 ); /* 暂停1秒 */ c_addr = ( char * ) shmat ( shmid, NULL, 0 ); printf ( "Client get %s\n", c_addr ); exit ( 0 ); } }
执行结果:
$ ./my_test 12345678 Client get 12345678
共享内存通信
shm_com.h
如下:#ifndef __SHM_COM__ #define __SHM_COM__ #define TEXT_SZ 2048 struct shared_use_st { int written_by_you; char some_text[TEXT_SZ]; }; #endif
shmread.c
如下:#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include "shm_com.h" int main ( void ) { int running = 1; void *shared_memory = NULL; struct shared_use_st *shared_stuff; int shmid = shmget ( ( key_t ) 1234, sizeof ( struct shared_use_st ), 0666 | IPC_CREAT ); if ( shmid == -1 ) { fprintf ( stderr, "shmget failed\n" ); exit ( EXIT_FAILURE ); } shared_memory = shmat ( shmid, NULL, 0 ); /* 映射共享内存 */ if ( shared_memory == ( void * ) -1 ) { fprintf ( stderr, "shmat failed\n" ); exit ( EXIT_FAILURE ); } printf ( "Memory attached at %p\n", shared_memory ); /* 让结构体指针指向这块共享内存 */ shared_stuff = ( struct shared_use_st * ) shared_memory; shared_stuff->written_by_you = 0; /* 控制读写顺序 */ while ( running ) { /* 循环的从共享内存中读数据,直到读到“end” */ if ( shared_stuff->written_by_you ) { printf ( "You wrote: %s", shared_stuff->some_text ); sleep ( 1 ); /* 读进程睡一秒,同时会导致写进程睡一秒,这样做到读了之后再写 */ shared_stuff->written_by_you = 0; if ( strncmp ( shared_stuff->some_text, "end", 3 ) == 0 ) { running = 0; /* 结束循环 */ } } } if ( shmdt ( shared_memory ) == -1 ) { /* 删除共享内存 */ fprintf ( stderr, "shmdt failed\n" ); exit ( EXIT_FAILURE ); } exit ( EXIT_SUCCESS ); }
shmwrite.c
如下:#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include "shm_com.h" int main ( void ) { int running = 1; void *shared_memory = NULL; struct shared_use_st *shared_stuff; char buffer[BUFSIZ]; int shmid = shmget ( ( key_t ) 1234, sizeof ( struct shared_use_st ), 0666 | IPC_CREAT ); if ( shmid == -1 ) { fprintf ( stderr, "shmget failed\n" ); exit ( EXIT_FAILURE ); } shared_memory = shmat ( shmid, NULL, 0 ); /* 映射共享内存 */ if ( shared_memory == ( void * ) -1 ) { fprintf ( stderr, "shmat failed\n" ); exit ( EXIT_FAILURE ); } printf ( "Memory attached at %p\n", shared_memory ); /* 让结构体指针指向这块共享内存 */ shared_stuff = ( struct shared_use_st * ) shared_memory; while ( running ) { /* 循环地向共享内存中写数据 */ while ( shared_stuff->written_by_you == 1 ) { sleep ( 1 ); /* 等到读进程读完之后再写 */ printf ( "waiting for client...\n" ); } printf ( "Enter some text: " ); fgets ( buffer, BUFSIZ, stdin ); strncpy ( shared_stuff->some_text, buffer, TEXT_SZ ); shared_stuff->written_by_you = 1; if ( strncmp ( buffer, "end", 3 ) == 0 ) { running = 0; /* 结束循环 */ } } if ( shmdt ( shared_memory ) == -1 ) { /* 删除共享内存 */ fprintf ( stderr, "shmdt failed\n" ); exit ( EXIT_FAILURE ); } exit ( EXIT_SUCCESS ); }
- 使用
-
共享内存 mmap shmget 区别
2020-12-19 20:06:59请参考apache对于共享内存的scoreboard的初始化,ap_init_scoreboard()得有一个内存变量ap_scoreboard_image来索引对应的共享内存By firework2@foxmail.com类Unix系统的共享内存有好几种机制,网上一搜就一大堆了,... -
C语言之共享内存之shmget进程间通信(二十三)
2021-09-21 14:03:421.向共享内存写数据 # emacs write_shm.c #include <stdio.h> #include <sys/shm.h> #include <unistd.h> #include <string.h> int main(int argc, char **argv) { int shmid; int i = 0... -
共享内存的常用函数详解shmget shmat
2020-12-19 20:07:08共享内存区域是被多个进程共享的一部分物理内存。如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信。共享内存是进程间共享数据的一种最... -
【转】进程的虚拟内存,物理内存,共享内存
2020-12-19 20:07:09比如我们可以通过top命令获得一个进程使用了多少虚拟内存(VIRT)、物理内存(RES)、共享内存(SHR)。最近遇到一个内存问题,某软件做性能分析需要获取进程占用物理内存的实际大小(不包括和其他进程共享的部分),看似很... -
共享内存的几点总结
2020-12-19 20:07:07在实验过程中,有些同学总对共享内存的一些概念弄不清,特别总结了下:所谓共享内存就是多个进程间共同使用同一段物理内存空间,它是通过将同一段物理内存映射到不同进程的虚空间中来实现的。由于映射到不同进程的虚... -
浅析Linux的共享内存与tmpfs文件系统
2021-01-12 06:18:50前言共享内存主要用于进程间通信,Linux有两种共享内存(Shared Memory)机制:(1) ** System V shared memory(shmget/shmat/shmdt) **Original shared memory mechanism, still widely used Sharing between ... -
进程的虚拟内存,物理内存,共享内存
2018-05-14 16:11:50转自:https://blog.csdn.net/rebirthme/article/details/50402082想必在linux上写过程序的同学都有分析进程占用多少内存的经历,或者被问到这样的问题——你的程序在运行时占用了多少内存(物理内存)?通常我们可以... -
一文解析Linux内存系统
2021-01-12 06:18:50本文主要介绍Linux内存组织结构和页面布局,内存碎片产生原因和优化算法,Linux内核几种内存管理的方法,内存使用场景以及内存使用的那些坑。从内存的原理和结构,到内存的算法优化,再到使用场景,去探寻内存管理的... -
共享内存和文件内存映射的区别
2020-12-19 20:07:071. 共享内存和文件内存映射有什么区别?我们的系统中,本来使用的是共享内存来进行进程间通信,共享内存文件的位置在/dev/shm/.但是后面要迁移至某平台,而据说平台的容器中并没有这个分区,所以无法使用共享内存。... -
为什么Oracle在有FREE空闲物理内存情况下仍使用SWAP交换空间换页?
2021-05-02 01:17:02其中包括内存资源,在许多unix/linux上使用shmget()函数调用申请内存, 对于分配到的内存 如果没有特殊配置的话,Oracle是不计较Unix/linux给它的到底是什么内存的,可能是可用的物理内存也可能是SWAP交换空间。... -
linux进程通信方式之shmget
2020-12-19 20:07:04共享内存区域是被多个进程共享的一部分物理内存;如果多个进程都把该内存区域映射到自身的虚拟地址空间,则这些进程都可以直接访问该共享内存区域,从而可以通过该区域进行通信。共享内存是进程间共享数据最快的一种... -
linux下共享内存mmap()方法和shmget()方法的疑问?
2020-12-19 20:07:09首先说结论:你在问题中说:“第一次调用mmap后,要访问时,进程空间已经和物理地址进行映射,但磁盘文件还没有加载到物理内存,所以会返回一个缺页异常,然后缺页处理程序会去把磁盘文件加载物理内存中。... -
Linux进程间通信(六):共享内存 shmget()、shmat()、shmdt()、shmctl()
2020-12-19 20:07:01不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc()分配的内存一样。而如果某... -
shmget - 共享内存
2012-07-04 16:37:15进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面。系统调用mmap()通 过映射一个普通文件实现共享内存。系统V则是通过映射shm文件系统中的文件实现进程间的共享内存通信。 ... -
shmget的具体使用原理以及其他关联函数(shmat ( ),shmdt ( ),shmctl ( ))、以及C++应用案例?...
2020-08-01 18:53:06共享内存(shared memory)共享内存1、背景2、定义3、两种方式(mmap上一篇博客说明)3.2、shmget3.2.1、使用流程和基本原理3.2.2、函数原型(创建共享内存)3.2.3、其他相关AP函数3.2.3.1、shmat ( ):挂接共享内存... -
进程间通信---共享内存&shmget
2018-08-17 12:43:30概念:共享内存是在物理内存上开辟一块区域,这段被多个进程映射到自己进程的虚拟地址空间上,这些进程就可以直接访问该共享内存区域,从而通过该区域实现各进程间的通信。共享内存是进程间最快的一种通信方式,一个... -
详解Linux进程间通信——使用共享内存
2021-01-10 23:06:59不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向... -
linux进程间通信——Sytem V shmget 共享内存
2013-05-21 22:35:06http://blog.csdn.net/qustdjx/article/details/7708311... 系统调用mmap()通过映射一个普通文件实现共享内存。系统V则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。也就是说,每个共享内存区域对应特 -
进程间通信:共享内存(Shared memory)篇 | 术与道的分享
2020-12-19 20:07:19共享内存是SystemV版本的最后一个进程间通信,...共享内存通信原理在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射... -
linux--共享内存函数(shmget、shmat、shmdt、shmctl)及其范例
2018-03-05 13:47:10共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。 二、共享内存的... -
【Linux】进程间通信(1):共享内存shmget() shmat() shmdt() shmctl()
2020-11-04 10:13:58进程的是独立的,每个进程有自己的地址空间,通过页表映射到物理内存上。共享内存就是在物理内存上开辟一段内存空间,然后分别与多个不相干的进程,通过页表关联起来。关联的过程就是每个进程都开辟一段自己的虚拟... -
Linux IPC 共享内存 ,System V 共享内存机制: shmget,shmat,shmdt,shmctl
2020-03-14 19:49:06System V 共享内存机制 原理及实现: system V IPC 机制下的共享内存本质是一段特殊的内存区域,进程间需要共享的数据被放在该共享内存区域中, 所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址... -
共享内存 shmget()、shmat()、shmdt()、shmctl()
2017-10-23 17:10:39下面将讲解进程间通信的...不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc()分配的内