0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux系统的共享内存的使用

jf_BxU6dNQb 来源:混说Linux 作者:混说Linux 2022-11-14 11:55 次阅读

Linux系统中,每个进程都有独立的虚拟内存空间,也就是说不同的进程访问同一段虚拟内存地址所得到的数据是不一样的,这是因为不同进程相同的虚拟内存地址会映射到不同的物理内存地址上。

但有时候为了让不同进程之间进行通信,需要让不同进程共享相同的物理内存,Linux通过共享内存来实现这个功能。下面先来介绍一下Linux系统的共享内存的使用。

共享内存使用

1. 获取共享内存

要使用共享内存,首先需要使用shmget()函数获取共享内存,shmget()函数的原型如下:

intshmget(key_tkey,size_tsize,intshmflg);
  • 参数key一般由ftok()函数生成,用于标识系统的唯一IPC资源。
  • 参数size指定创建的共享内存大小。
  • 参数shmflg指定shmget()函数的动作,比如传入IPC_CREAT表示要创建新的共享内存。

函数调用成功时返回一个新建或已经存在的的共享内存标识符,取决于shmflg的参数。失败返回-1,并设置错误码。

2. 关联共享内存

shmget()函数返回的是一个标识符,而不是可用的内存地址,所以还需要调用shmat()函数把共享内存关联到某个虚拟内存地址上。shmat()函数的原型如下:

void*shmat(intshmid,constvoid*shmaddr,intshmflg);
  • 参数shmidshmget()函数返回的标识符。
  • 参数shmaddr是要关联的虚拟内存地址,如果传入0,表示由系统自动选择合适的虚拟内存地址。
  • 参数shmflg若指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段。

函数调用成功返回一个可用的指针(虚拟内存地址),出错返回-1。

3. 取消关联共享内存

当一个进程不需要共享内存的时候,就需要取消共享内存与虚拟内存地址的关联。取消关联共享内存通过shmdt()函数实现,原型如下:

intshmdt(constvoid*shmaddr);
  • 参数shmaddr是要取消关联的虚拟内存地址,也就是shmat()函数返回的值。

函数调用成功返回0,出错返回-1。

共享内存使用例子

下面通过一个例子来介绍一下共享内存的使用方法。在这个例子中,有两个进程,分别为进程A进程B进程A创建一块共享内存,然后写入数据,进程B获取这块共享内存并且读取其内容。

进程A

#include
#include
#include
#include
#include

#defineSHM_PATH"/tmp/shm"
#defineSHM_SIZE128

intmain(intargc,char*argv[])
{
intshmid;
char*addr;
key_tkey=ftok(SHM_PATH,0x6666);

shmid=shmget(key,SHM_SIZE,IPC_CREAT|IPC_EXCL|0666);
if(shmid< 0){
printf("failedtocreatesharememory
");
return-1;
}

addr=shmat(shmid,NULL,0);
if(addr<= 0){
printf("failedtomapsharememory
");
return-1;
}

sprintf(addr,"%s","HelloWorld
");

return0;
}

进程B

#include
#include
#include
#include
#include
#include

#defineSHM_PATH"/tmp/shm"
#defineSHM_SIZE128

intmain(intargc,char*argv[])
{
intshmid;
char*addr;
key_tkey=ftok(SHM_PATH,0x6666);

charbuf[128];

shmid=shmget(key,SHM_SIZE,IPC_CREAT);
if(shmid< 0){
printf("failedtogetsharememory
");
return-1;
}

addr=shmat(shmid,NULL,0);
if(addr<= 0){
printf("failedtomapsharememory
");
return-1;
}

strcpy(buf,addr,128);
printf("%s",buf);

return0;
}

测试时先运行进程A,然后再运行进程B,可以看到进程B会打印出 “Hello World”,说明共享内存已经创建成功并且读取。

共享内存实现原理

我们先通过一幅图来了解一下共享内存的大概原理,如下图:7cff08c2-63cf-11ed-8abf-dac502259ad0.png

通过上图可知,共享内存是通过将不同进程的虚拟内存地址映射到相同的物理内存地址来实现的,下面将会介绍Linux的实现方式。

在Linux内核中,每个共享内存都由一个名为struct shmid_kernel的结构体来管理,而且Linux限制了系统最大能创建的共享内存为128个。通过类型为struct shmid_kernel结构的数组来管理,如下:

structshmid_ds{
structipc_permshm_perm;/*operationperms*/
intshm_segsz;/*sizeofsegment(bytes)*/
__kernel_time_tshm_atime;/*lastattachtime*/
__kernel_time_tshm_dtime;/*lastdetachtime*/
__kernel_time_tshm_ctime;/*lastchangetime*/
__kernel_ipc_pid_tshm_cpid;/*pidofcreator*/
__kernel_ipc_pid_tshm_lpid;/*pidoflastoperator*/
unsignedshortshm_nattch;/*no.ofcurrentattaches*/
unsignedshortshm_unused;/*compatibility*/
void*shm_unused2;/*ditto-usedbyDIPC*/
void*shm_unused3;/*unused*/
};

structshmid_kernel
{
structshmid_dsu;
/*thefollowingareprivate*/
unsignedlongshm_npages;/*sizeofsegment(pages)*/
pte_t*shm_pages;/*arrayofptrstoframes->SHMMAX*/
structvm_area_struct*attaches;/*descriptorsforattaches*/
};

staticstructshmid_kernel*shm_segs[SHMMNI];//SHMMNI等于128

从注释可以知道struct shmid_kernel结构体各个字段的作用,比如shm_npages字段表示共享内存使用了多少个内存页。而shm_pages字段指向了共享内存映射的虚拟内存页表项数组等。

另外struct shmid_ds结构体用于管理共享内存的信息,而shm_segs数组用于管理系统中所有的共享内存。

shmget() 函数实现

通过前面的例子可知,要使用共享内存,首先需要调用shmget()函数来创建或者获取一块共享内存。shmget()函数的实现如下:

asmlinkagelongsys_shmget(key_tkey,intsize,intshmflg)
{
structshmid_kernel*shp;
interr,id=0;

down(¤t->mm->mmap_sem);
spin_lock(&shm_lock);
if(size< 0||size>shmmax){
err=-EINVAL;
}elseif(key==IPC_PRIVATE){
err=newseg(key,shmflg,size);
}elseif((id=findkey(key))==-1){
if(!(shmflg&IPC_CREAT))
err=-ENOENT;
else
err=newseg(key,shmflg,size);
}elseif((shmflg&IPC_CREAT)&&(shmflg&IPC_EXCL)){
err=-EEXIST;
}else{
shp=shm_segs[id];
if(shp->u.shm_perm.mode&SHM_DEST)
err=-EIDRM;
elseif(size>shp->u.shm_segsz)
err=-EINVAL;
elseif(ipcperms(&shp->u.shm_perm,shmflg))
err=-EACCES;
else
err=(int)shp->u.shm_perm.seq*SHMMNI+id;
}
spin_unlock(&shm_lock);
up(¤t->mm->mmap_sem);
returnerr;
}

shmget()函数的实现比较简单,首先调用findkey()函数查找值为key的共享内存是否已经被创建,findkey()函数返回共享内存在shm_segs数组索引。如果找到,那么直接返回共享内存的标识符即可。否则就调用newseg()函数创建新的共享内存。newseg()函数的实现也比较简单,就是创建一个新的struct shmid_kernel结构体,然后设置其各个字段的值,并且保存到shm_segs数组中。

shmat() 函数实现

shmat()函数用于将共享内存映射到本地虚拟内存地址,由于shmat()函数的实现比较复杂,所以我们分段来分析这个函数:

asmlinkagelongsys_shmat(intshmid,char*shmaddr,intshmflg,ulong*raddr)
{
structshmid_kernel*shp;
structvm_area_struct*shmd;
interr=-EINVAL;
unsignedintid;
unsignedlongaddr;
unsignedlonglen;

down(¤t->mm->mmap_sem);
spin_lock(&shm_lock);
if(shmid< 0)
gotoout;

shp=shm_segs[id=(unsignedint)shmid%SHMMNI];
if(shp==IPC_UNUSED||shp==IPC_NOID)
gotoout;

上面这段代码主要通过shmid标识符来找到共享内存描述符,上面说过系统中所有的共享内存到保存在shm_segs数组中。

if(!(addr=(ulong)shmaddr)){
if(shmflg&SHM_REMAP)
gotoout;
err=-ENOMEM;
addr=0;
again:
if(!(addr=get_unmapped_area(addr,shp->u.shm_segsz)))//获取一个空闲的虚拟内存空间
gotoout;
if(addr&(SHMLBA-1)){
addr=(addr+(SHMLBA-1))&~(SHMLBA-1);
gotoagain;
}
}elseif(addr&(SHMLBA-1)){
if(shmflg&SHM_RND)
addr&=~(SHMLBA-1);/*rounddown*/
else
gotoout;
}

上面的代码主要找到一个可用的虚拟内存地址,如果在调用shmat()函数时没有指定了虚拟内存地址,那么就通过get_unmapped_area()函数来获取一个可用的虚拟内存地址。

spin_unlock(&shm_lock);
err=-ENOMEM;
shmd=kmem_cache_alloc(vm_area_cachep,SLAB_KERNEL);
spin_lock(&shm_lock);
if(!shmd)
gotoout;
if((shp!=shm_segs[id])||(shp->u.shm_perm.seq!=(unsignedint)shmid/SHMMNI)){
kmem_cache_free(vm_area_cachep,shmd);
err=-EIDRM;
gotoout;
}

上面的代码主要通过调用kmem_cache_alloc()函数创建一个vm_area_struct结构,在内存管理一章知道,vm_area_struct结构用于管理进程的虚拟内存空间。

shmd->vm_private_data=shm_segs+id;
shmd->vm_start=addr;
shmd->vm_end=addr+shp->shm_npages*PAGE_SIZE;
shmd->vm_mm=current->mm;
shmd->vm_page_prot=(shmflg&SHM_RDONLY)?PAGE_READONLY:PAGE_SHARED;
shmd->vm_flags=VM_SHM|VM_MAYSHARE|VM_SHARED
|VM_MAYREAD|VM_MAYEXEC|VM_READ|VM_EXEC
|((shmflg&SHM_RDONLY)?0:VM_MAYWRITE|VM_WRITE);
shmd->vm_file=NULL;
shmd->vm_offset=0;
shmd->vm_ops=&shm_vm_ops;

shp->u.shm_nattch++;/*preventdestruction*/
spin_unlock(&shm_lock);
err=shm_map(shmd);
spin_lock(&shm_lock);
if(err)
gotofailed_shm_map;

insert_attach(shp,shmd);/*insertshmdintoshp->attaches*/

shp->u.shm_lpid=current->pid;
shp->u.shm_atime=CURRENT_TIME;

*raddr=addr;
err=0;
out:
spin_unlock(&shm_lock);
up(¤t->mm->mmap_sem);
returnerr;
...
}

上面的代码主要是设置刚创建的vm_area_struct结构的各个字段,比较重要的是设置其vm_ops字段为shm_vm_opsshm_vm_ops定义如下:

staticstructvm_operations_structshm_vm_ops={
shm_open,/*open-callbackforanewvm-areaopen*/
shm_close,/*close-callbackforwhenthevm-areaisreleased*/
NULL,/*noneedtosyncpagesatunmap*/
NULL,/*protect*/
NULL,/*sync*/
NULL,/*advise*/
shm_nopage,/*nopage*/
NULL,/*wppage*/
shm_swapout/*swapout*/
};

shm_vm_opsnopage回调为shm_nopage()函数,也就是说,当发生页缺失异常时将会调用此函数来恢复内存的映射。

从上面的代码可看出,shmat()函数只是申请了进程的虚拟内存空间,而共享内存的物理空间并没有申请,那么在什么时候申请物理内存呢?答案就是当进程发生缺页异常的时候会调用shm_nopage()函数来恢复进程的虚拟内存地址到物理内存地址的映射。

shm_nopage() 函数实现

shm_nopage() 函数是当发生内存缺页异常时被调用的,代码如下:

staticstructpage*shm_nopage(structvm_area_struct*shmd,unsignedlongaddress,intno_share)
{
pte_tpte;
structshmid_kernel*shp;
unsignedintidx;
structpage*page;

shp=*(structshmid_kernel**)shmd->vm_private_data;
idx=(address-shmd->vm_start+shmd->vm_offset)>>PAGE_SHIFT;

spin_lock(&shm_lock);
again:
pte=shp->shm_pages[idx];//共享内存的页表项
if(!pte_present(pte)){//如果内存页不存在
if(pte_none(pte)){
spin_unlock(&shm_lock);
page=get_free_highpage(GFP_HIGHUSER);//申请一个新的物理内存页
if(!page)
gotooom;
clear_highpage(page);
spin_lock(&shm_lock);
if(pte_val(pte)!=pte_val(shp->shm_pages[idx]))
gotochanged;
}else{
...
}
shm_rss++;
pte=pte_mkdirty(mk_pte(page,PAGE_SHARED));//创建页表项
shp->shm_pages[idx]=pte;//保存共享内存的页表项
}else
--current->maj_flt;/*wasincrementedindo_no_page*/

done:
get_page(pte_page(pte));
spin_unlock(&shm_lock);
current->min_flt++;
returnpte_page(pte);
...
}

shm_nopage() 函数的主要功能是当发生内存缺页时,申请新的物理内存页,并映射到共享内存中。由于使用共享内存时会映射到相同的物理内存页上,从而不同进程可以共用此块内存。


审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11219

    浏览量

    208872
  • 内存
    +关注

    关注

    8

    文章

    2996

    浏览量

    73868
  • 函数
    +关注

    关注

    3

    文章

    4303

    浏览量

    62411

原文标题:一文读懂 | Linux共享内存原理

文章出处:【微信号:混说Linux,微信公众号:混说Linux】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux内存泄露案例分析和内存管理分享

    作者:京东科技 李遵举 一、问题 近期我们运维同事接到线上LB(负载均衡)服务内存报警,运维同事反馈说LB集群有部分机器的内存使用率超过80%,有的甚至超过90%,而且内存使用率还再不停的增长。接到
    的头像 发表于 10-24 16:14 688次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内存</b>泄露案例分析和<b class='flag-5'>内存</b>管理分享

    16 口多模反射内存交换机:高速数据共享的核心枢纽

    在当今数字化和信息化高速发展的时代,数据的快速传输、实时共享以及高效处理成为了众多行业和领域追求的关键目标。在这样的背景下,16口多模反射内存交换机应运而生,成为了构建高性能数据共享网络的重要
    的头像 发表于 09-04 14:38 219次阅读
    16 口多模反射<b class='flag-5'>内存</b>交换机:高速数据<b class='flag-5'>共享</b>的核心枢纽

    多模反射内存交换机:实现高速实时数据共享的关键设备

    在当今数字化、信息化的时代,数据的快速传输和实时共享对于许多领域的系统运行至关重要。多模反射内存交换机作为一种先进的网络设备,为满足这些需求提供了高效、可靠的解决方案。多模反射内存交换
    的头像 发表于 09-04 10:55 256次阅读
    多模反射<b class='flag-5'>内存</b>交换机:实现高速实时数据<b class='flag-5'>共享</b>的关键设备

    buffers内存与cached内存的区别

    free 命令是Linux系统上查看内存使用状况最常用的工具,然而很少有人能说清楚 “buffers” 与 “cached” 之间的区别。
    的头像 发表于 07-29 14:17 444次阅读
    buffers<b class='flag-5'>内存</b>与cached<b class='flag-5'>内存</b>的区别

    16 口多模反射内存交换机:高速数据共享的核心枢纽

     16 口多模反射内存交换机:高速数据共享的核心枢纽  在当今数字化和信息化高速发展的时代,数据的快速传输、实时共享以及高效处理成为了众多行业和领域追求的关键目标。在这样的背景下,16 口多模反射
    的头像 发表于 07-15 10:01 260次阅读
    16 口多模反射<b class='flag-5'>内存</b>交换机:高速数据<b class='flag-5'>共享</b>的核心枢纽

    Linux系统共享文件找不到怎么办

      1.首先输下面命令查看是否真的设置成功共享文件夹 vmware-hgfsclient 如果确实已经设置过共享文件夹将输出window下共享文件夹名字 2.确认自己已设置共享文件夹后
    的头像 发表于 05-11 08:49 708次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>系统</b>中<b class='flag-5'>共享</b>文件找不到怎么办

    Linux系统共享文件缺失的解决策略

    如果确实已经设置过共享文件夹将输出window下共享文件夹名字
    的头像 发表于 04-15 10:25 901次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>系统共享</b>文件缺失的解决策略

    内存共享原理解析

    内存共享是一种在多个进程之间共享数据的机制,它允许不同的进程直接访问同一块内存区域,从而实现数据的快速传递和通信。
    的头像 发表于 02-19 15:11 1196次阅读
    <b class='flag-5'>内存</b><b class='flag-5'>共享</b>原理解析

    linux和windows的区别 linux系统一般用来干嘛

    Linux的设计理念 Linux是一个开源操作系统,其设计理念是自由、共享和开放。Linux系统
    的头像 发表于 02-05 14:06 871次阅读

    JH-7110现已支持AMP双系统Linux + RT-Thread)

    可以实现复杂功能,大大降低了系统硬件成本。 提高系统实时性与稳定性: 在RTOS的CPU运行实时的进程中,把部分实时驱动运行在RTOS中进行数据采集,将数据通过共享内存方式发回到
    发表于 01-27 15:11

    linux内核主要由哪几个部分组成,作用是什么

    内存。它将内存划分为不同的区域,并通过内存管理算法来分配和回收内存。它还提供了虚拟内存功能,允许多个进程
    的头像 发表于 01-22 14:34 2598次阅读

    系统内存和运行内存的区别

    系统内存和运行内存都是计算机中重要的概念,它们在计算机的存储和运行方面起着不可或缺的作用。虽然它们与计算机存储和运行息息相关,但是它们具有不同的功能和实现方式。接下来我将详细介绍系统
    的头像 发表于 01-15 16:32 3073次阅读

    Linux内核内存管理架构解析

    内存管理子系统可能是linux内核中最为复杂的一个子系统,其支持的功能需求众多,如页面映射、页面分配、页面回收、页面交换、冷热页面、紧急页面、页面碎片管理、页面缓存、页面统计等,而且对
    的头像 发表于 01-04 09:24 628次阅读
    <b class='flag-5'>Linux</b>内核<b class='flag-5'>内存</b>管理架构解析

    jvm内存区域中,哪一块是属于线程共享

    JVM(Java虚拟机)是一种计算机软件,用于执行Java字节码。在JVM中,存在多个内存区域,包括线程共享内存区域。本文将详细介绍JVM内存区域中属于线程
    的头像 发表于 12-05 14:14 1304次阅读

    VMIPCI5565反射内存卡通信协议

    支持PCI、CPCI和VME等不同总线结构的多计算机系统,可以适用不同的操作系统共享高速的、稳定速率的实时数据。 反射内存的优点: 高速的、基于2.12G波特率的光纤网络,大传输速率
    的头像 发表于 11-29 14:48 468次阅读