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

    文章

    10988

    浏览量

    206724
  • 内存
    +关注

    关注

    8

    文章

    2767

    浏览量

    72752
  • 函数
    +关注

    关注

    3

    文章

    3866

    浏览量

    61308

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

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

收藏 人收藏

    评论

    相关推荐

    内存共享原理解析

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

    EC SRAM映射到CPU Memory空间的共享内存设计

    ShareMemory,顾名思义就是共享内存。这个概念在很多计算机系统中都存在,本文特指 EC SRAM 映射到 CPU Memory 空间的共享
    的头像 发表于 11-18 15:11 681次阅读
    EC SRAM映射到CPU Memory空间的<b class='flag-5'>共享</b><b class='flag-5'>内存</b>设计

    查看Linux系统内存使用情况的几种方法

    Linux系统中,内存监控是优化系统性能的关键。本文为你介绍12种方法,帮助你全面掌握Linux系统
    的头像 发表于 11-13 09:30 1718次阅读
    查看<b class='flag-5'>Linux</b><b class='flag-5'>系统</b><b class='flag-5'>内存</b>使用情况的几种方法

    Linux 内存管理总结

    一、Linux内存管理概述 Linux内存管理是指对系统内存的分配、释放、映射、管理、交换、压缩
    的头像 发表于 11-10 14:58 240次阅读
    <b class='flag-5'>Linux</b> <b class='flag-5'>内存</b>管理总结

    什么是内存碎片Linux

    什么是内存碎片? 内存碎片在Linux很早的时候就已经出现了,了解早期内存碎片产生的历史,有利于我们对它的理解。 假设现在有一块32MB大小的内存
    的头像 发表于 10-08 10:12 367次阅读
    什么是<b class='flag-5'>内存</b>碎片<b class='flag-5'>Linux</b>

    使用Rust语言的WinAPI模块来实现共享内存

    进程间通信(IPC)是操作系统中非常重要的一部分,它使得不同的进程可以在不同的计算机上进行通信。在Windows操作系统中,共享内存是一种常见的IPC机制,它可以在不同的进程之间
    的头像 发表于 09-19 16:15 998次阅读

    Linux内存管理子系统开发必知的3个结构概念

    Linux内存管理子系统使用节点(node)、区域(zone)和页(page)三级结构描述物理内存
    的头像 发表于 08-28 09:34 572次阅读
    <b class='flag-5'>Linux</b>中<b class='flag-5'>内存</b>管理子<b class='flag-5'>系统</b>开发必知的3个结构概念

    如何查看linux程序共享库呢?

    linux系统中,程序通常需要依赖于外部库来正常工作。这些库可以直接编译到程序中,也可以从共享库池中加载。
    的头像 发表于 08-28 09:11 976次阅读
    如何查看<b class='flag-5'>linux</b>程序<b class='flag-5'>共享</b>库呢?

    Linux内核的物理内存组织结构详解

    Linux内存管理子系统使用 节点(node)、区域(zone)和页(page) 三级结构描述物理内存
    发表于 08-21 15:35 245次阅读
    <b class='flag-5'>Linux</b>内核的物理<b class='flag-5'>内存</b>组织结构详解

    Linux内存相关知识科普

    Linux 内存是后台开发人员,需要深入了解的计算机资源。合理的使用内存,有助于提升机器的性能和稳定性。本文主要介绍**Linu****x 内存组织结构和页面布局,
    发表于 07-25 14:43 539次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内存</b>相关知识科普

    Linux下进程间共享内存通信常用的同步机制

    今天我们来讲讲进程间使用共享内存通信时为了确保数据的正确,如何进行同步?
    发表于 06-20 09:41 596次阅读

    Linux进程间如何实现共享内存通信

    在上面的例程中,我们首先使用ftok()函数生成一个key值作为共享内存的标识符。然后使用shmget()函数创建共享内存区域,shmaddr指向
    发表于 06-19 09:55 428次阅读

    CUDA编程共享内存

    共享内存是使用__shared__内存空间说明符分配的。
    的头像 发表于 05-19 15:32 797次阅读
    CUDA编程<b class='flag-5'>共享</b><b class='flag-5'>内存</b>

    Linux进程间共享内存通信时如何同步?

    今天我们来讲讲进程间使用共享内存通信时为了确保数据的正确,如何进行同步?
    的头像 发表于 05-11 18:25 1227次阅读

    Linux下进程间如何实现共享内存通信

    这次我们来讲一下Linux进程通信中重要的通信方式:共享内存作为Linux软件开发攻城狮,进程间通信是必须熟练掌握的重要技能,而共享
    发表于 04-26 17:14 571次阅读