1 深入剖析Linux共享内存原理-德赢Vwin官网 网
0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

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

3天内不再提示

深入剖析Linux共享内存原理

Linux爱好者 来源:Linux云计算网络 作者:Linux云计算网络 2021-10-30 09:52 次阅读

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”,说明共享内存已经创建成功并且读取。

共享内存实现原理

我们先通过一幅图来了解一下共享内存的大概原理,如下图:

cc4e4bca-38ad-11ec-82a8-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() 函数的主要功能是当发生内存缺页时,申请新的物理内存页,并映射到共享内存中。由于使用共享内存时会映射到相同的物理内存页上,从而不同进程可以共用此块内存。

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

    关注

    87

    文章

    11292

    浏览量

    209311
  • 函数
    +关注

    关注

    3

    文章

    4327

    浏览量

    62567
  • 代码
    +关注

    关注

    30

    文章

    4779

    浏览量

    68516

原文标题:一文搞定:Linux共享内存原理

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

收藏 人收藏

    评论

    相关推荐

    Linux下如何管理虚拟内存 使用虚拟内存时的常见问题

    Linux系统中,虚拟内存管理是操作系统内核的一个重要功能,负责管理物理内存和磁盘上的交换空间。以下是对Linux下如何管理虚拟内存以及使
    的头像 发表于 12-04 09:19 348次阅读

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

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

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

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

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

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

    反射内存卡原理说明

    一、引言反射内存卡是一种用于实现高速数据共享和实时通信的先进技术。它在多个领域,特别是对数据传输速度和实时性要求极高的应用中,发挥着关键作用。二、基本原理共享内存模型反射
    的头像 发表于 09-04 10:19 296次阅读
    反射<b class='flag-5'>内存</b>卡原理说明

    深入剖析石英 CMOS 振荡器 PC3225 系列(1 to 200 MHz)的卓越性能

    深入剖析石英 CMOS 振荡器 PC3225 系列(1 to 200 MHz)的卓越性能
    的头像 发表于 08-08 10:14 344次阅读
    <b class='flag-5'>深入</b><b class='flag-5'>剖析</b>石英 CMOS 振荡器 PC3225 系列(1 to 200 MHz)的卓越性能

    buffers内存与cached内存的区别

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

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

    内存交换机应运而生,成为了构建高性能数据共享网络的重要组成部分。  16 口多模反射内存交换机,从字面上理解,它是一种具备 16 个端口,且采用多模传输方式的反射内存交换设备。但要
    的头像 发表于 07-15 10:01 295次阅读
    16 口多模反射<b class='flag-5'>内存</b>交换机:高速数据<b class='flag-5'>共享</b>的核心枢纽

    Linux内核内存管理之内核非连续物理内存分配

    的主要优点是避免了外部碎片,而缺点是需要修改内核页表。显然,非连续内存区域的大小必须是4096的倍数。Linux使用非连续物理内存区的场景有几种:(1)为swap区分配数据结构;(2)为模块分配空间
    的头像 发表于 02-23 09:44 944次阅读
    <b class='flag-5'>Linux</b>内核<b class='flag-5'>内存</b>管理之内核非连续物理<b class='flag-5'>内存</b>分配

    内存共享原理解析

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

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

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

    深入剖析人工智能应用价值与场景分析

    人工智能进入生成式阶段,本报告重点对人工智能应用价值与场景分析,并对企业AI应用突破方向与规划建议,针对营销/服务、办公协同、研发、企业安全等各个环节进行深入剖析,展开企业AI智能应用全景与规划建议。
    发表于 01-05 11:15 316次阅读
    <b class='flag-5'>深入</b><b class='flag-5'>剖析</b>人工智能应用价值与场景分析

    Linux内核内存管理架构解析

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

    深入理解Linux RCU:从硬件说起之内存屏障

    上一篇文章我们谈到了内存Cache,并且描述了典型的Cache一致性协议MESI。Cache的根本目的,是解决内存与CPU速度多达两个数量级的性能差异。
    的头像 发表于 12-25 13:42 807次阅读
    <b class='flag-5'>深入</b>理解<b class='flag-5'>Linux</b> RCU:从硬件说起之<b class='flag-5'>内存</b>屏障

    深入了解Linux中vi命令的使用

    深入了解Linux中vi命令的使用 VI是一款在Linux系统中使用的文本编辑器,它是一款功能强大、灵活性高的编辑器。VI编辑器具有非常高效的命令行操作方式,并且在各个版本的Linux
    的头像 发表于 12-25 11:15 454次阅读