首页 文章 精选 留言 我的

精选列表

搜索[shared],共2263篇文章
优秀的个人博客,低调大师

Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析(下)

四.匿名共享内存的锁定和解锁操作 前面提到,Android系统的运行时库提到了执行匿名共享内存的锁定和解锁操作的两个函数ashmem_pin_region和ashmem_unpin_region,它们实现在system/core/libcutils/ashmem-dev.c文件中: intashmem_pin_region(intfd,size_toffset,size_tlen) { structashmem_pinpin={offset,len}; returnioctl(fd,ASHMEM_PIN,&pin); } intashmem_unpin_region(intfd,size_toffset,size_tlen) { structashmem_pinpin={offset,len}; returnioctl(fd,ASHMEM_UNPIN,&pin); } 它们的实现很简单,通过ASHMEM_PIN和ASHMEM_UNPIN两个ioctl操作来实现匿名共享内存的锁定和解锁操作。 我们先看来一下ASHMEM_PIN和ASHMEM_UNPIN这两个命令号的定义,它们的定义可以在kernel/common/include/linux/ashmem.h文件中找到: #define__ASHMEMIOC0x77 #defineASHMEM_PIN_IOW(__ASHMEMIOC,7,structashmem_pin) #defineASHMEM_UNPIN_IOW(__ASHMEMIOC,8,structashmem_pin) 它们的参数类型为struct ashmem_pin,它也是定义在kernel/common/include/linux/ashmem.h文件中: structashmem_pin{ __u32offset;/*offsetintoregion,inbytes,page-aligned*/ __u32len;/*lengthforwardfromoffset,inbytes,page-aligned*/ }; 这个结构体只有两个域,分别表示要锁定或者要解锁的内块块的起始大小以及大小。 在分析这两个操作之前,我们先来看一下Ashmem驱动程序中的一个数据结构struct ashmem_range,这个数据结构就是用来表示某一块被解锁(unpinnd)的内存: /* *ashmem_range-representsanintervalofunpinned(evictable)pages *Lifecycle:Fromunpintopin *Locking:Protectedby`ashmem_mutex' */ structashmem_range{ structlist_headlru;/*entryinLRUlist*/ structlist_headunpinned;/*entryinitsarea'sunpinnedlist*/ structashmem_area*asma;/*associatedarea*/ size_tpgstart;/*startingpage,inclusive*/ size_tpgend;/*endingpage,inclusive*/ unsignedintpurged;/*ASHMEM_NOTorASHMEM_WAS_PURGED*/ }; 域asma表示这块被解锁的内存所属于的匿名共享内存,它通过域unpinned连接在asma->unpinned_list表示的列表中;域pgstart和paend表示这个内存块的开始和结束页面号,它们表示一个前后闭合的区间;域purged表示这个内存块占用的物理内存是否已经被回收;这块被解锁的内存块除了保存在它所属的匿名共享内存asma的解锁列表unpinned_list之外,还通过域lru保存在一个全局的最近最少使用列表ashmem_lru_list列表中,它的定义如下: /*LRUlistofunpinnedpages,protectedbyashmem_mutex*/ staticLIST_HEAD(ashmem_lru_list); 了解了这个数据结构之后,我们就可以来看ashmem_ioctl函数中关于ASHMEM_PIN和ASHMEM_UNPIN的操作了: staticlongashmem_ioctl(structfile*file,unsignedintcmd,unsignedlongarg) { structashmem_area*asma=file->private_data; longret=-ENOTTY; switch(cmd){ ...... caseASHMEM_PIN: caseASHMEM_UNPIN: ret=ashmem_pin_unpin(asma,cmd,(void__user*)arg); break; ...... } returnret; } 它们都是通过ashmem_pin_unpin来进一步处理: staticintashmem_pin_unpin(structashmem_area*asma,unsignedlongcmd, void__user*p) { structashmem_pinpin; size_tpgstart,pgend; intret=-EINVAL; if(unlikely(!asma->file)) return-EINVAL; if(unlikely(copy_from_user(&pin,p,sizeof(pin)))) return-EFAULT; /*percustom,youcanpasszeroforlentomean"everythingonward"*/ if(!pin.len) pin.len=PAGE_ALIGN(asma->size)-pin.offset; if(unlikely((pin.offset|pin.len)&~PAGE_MASK)) return-EINVAL; if(unlikely(((__u32)-1)-pin.offset<pin.len)) return-EINVAL; if(unlikely(PAGE_ALIGN(asma->size)<pin.offset+pin.len)) return-EINVAL; pgstart=pin.offset/PAGE_SIZE; pgend=pgstart+(pin.len/PAGE_SIZE)-1; mutex_lock(&ashmem_mutex); switch(cmd){ caseASHMEM_PIN: ret=ashmem_pin(asma,pgstart,pgend); break; caseASHMEM_UNPIN: ret=ashmem_unpin(asma,pgstart,pgend); break; ...... } mutex_unlock(&ashmem_mutex); returnret; } 首先是获得用户空间传进来的参数,并保存在本地变量pin中,这是一个struct ashmem_pin类型的变量,这个结构体我们在前面已经见过了,它包括了要pin/unpin的内存块的起始地址和大小,这里的起始地址和大小都是以字节为单位的,因此,通过转换把它们换成以页面为单位的,并且保存在本地变量pgstart和pgend中。这里除了要对参数作一个安全性检查外,还要一个处理逻辑是,如果从用户空间传进来的内块块的大小值为0 ,则认为是要pin/unpin整个匿名共享内存。 函数最后根据当前要执行的是ASHMEM_PIN操作还是ASHMEM_UNPIN操作来分别执行ashmem_pin和ashmem_unpin来进一步处理。创建匿名共享内存时,默认所有的内存都是pinned状态的,只有用户告诉Ashmem驱动程序要unpin某一块内存时,Ashmem驱动程序才会把这块内存unpin,之后,用户可以再告诉Ashmem驱动程序要重新pin某一块之前被unpin过的内块,从而把这块内存从unpinned状态改为pinned状态,也就是说,执行ASHMEM_PIN操作时,目标对象必须是一块当前处于unpinned状态的内存块。 我们先来看一下ASHMEM_UNPIN操作,进入到ashmem_unpin函数: /* *ashmem_unpin-unpinthegivenrangeofpages.Returnszeroonsuccess. * *Callermustholdashmem_mutex. */ staticintashmem_unpin(structashmem_area*asma,size_tpgstart,size_tpgend) { structashmem_range*range,*next; unsignedintpurged=ASHMEM_NOT_PURGED; restart: list_for_each_entry_safe(range,next,&asma->unpinned_list,unpinned){ /*shortcircuit:thisisourinsertionpoint*/ if(range_before_page(range,pgstart)) break; /* *Theusercanaskustounpinpagesthatarealreadyentirely *orpartiallypinned.Wehandlethosetwocaseshere. */ if(page_range_subsumed_by_range(range,pgstart,pgend)) return0; if(page_range_in_range(range,pgstart,pgend)){ pgstart=min_t(size_t,range->pgstart,pgstart), pgend=max_t(size_t,range->pgend,pgend); purged|=range->purged; range_del(range); gotorestart; } } returnrange_alloc(asma,range,purged,pgstart,pgend); } 这个函数的主体就是在遍历asma->unpinned_list列表,从中查找当前处于unpinned状态的内存块是否与将要unpin的内存块[pgstart, pgend]是否相交,如果相交,则要执行合并操作,即调整pgstart和pgend的大小,然后通过调用range_del函数删掉原来的已经被unpinned过的内存块,最后再通过range_alloc函数来重新unpinned这块调整过后的内存块[pgstart, pgend],这里新的内存块[pgstart, pgend]已经包含了刚才所有被删掉的unpinned状态的内存。注意,这里如果找到一块相并的内存块,并且调整了pgstart和pgend的大小之后,要重新再扫描一遍asma->unpinned_list列表,因为新的内存块[pgstart, pgend]可能还会与前后的处于unpinned状态的内存块发生相交。 我们来看一下range_before_page的操作,这是一个宏定义: #definerange_before_page(range,page)\ ((range)->pgend<(page)) 表示range描述的内存块是否在page页面之前,如果是,则整个描述就结束了。从这里我们可以看出asma->unpinned_list列表是按照页面号从大到小进行排列的,并且每一块被unpin的内存都是不相交的。 再来看一下page_range_subsumed_by_range的操作,这也是一个宏定义: #definepage_range_subsumed_by_range(range,start,end)\ (((range)->pgstart<=(start))&&((range)->pgend>=(end))) 表示range描述的内存块是不是包含了[start, end]这个内存块,如果包含了,则说明当前要unpin的内存块已经处于unpinned状态,什么也不用操作,直接返回即可。 再看page_range_in_range的操作,它也是一个宏定义: #definepage_range_in_range(range,start,end)\ (page_in_range(range,start)||page_in_range(range,end)||\ page_range_subsumes_range(range,start,end)) 它用到的其它两个宏分别定义为: #definepage_range_subsumed_by_range(range,start,end)\ (((range)->pgstart<=(start))&&((range)->pgend>=(end))) #definepage_in_range(range,page)\ (((range)->pgstart<=(page))&&((range)->pgend>=(page))) 它们都是用来判断两个内存区间是否相交的。 两个内存块相交分为四种情况: |-------range-----| |-------range------| |--------range---------| |----range---| |-start----end-| |-start-----end-| |-start-------end-| |-start-----------end-| (1) (2) (3) (4) 第一种情况,前面已经讨论过了,对于第二到第四种情况,都是需要执行合并操作的。 再来看从asma->unpinned_list中删掉内存块的range_del函数: staticvoidrange_del(structashmem_range*range) { list_del(&range->unpinned); if(range_on_lru(range)) lru_del(range); kmem_cache_free(ashmem_range_cachep,range); } 这个函数首先把range从相应的unpinned_list列表中删除,然后判断它是否在lru列表中: #definerange_on_lru(range)\ ((range)->purged==ASHMEM_NOT_PURGED) 如果它的状态purged等于ASHMEM_NOT_PURGED,即对应的物理页面尚未被回收,它就位于lru列表中,通过调用lru_del函数进行删除: staticinlinevoidlru_del(structashmem_range*range) { list_del(&range->lru); lru_count-=range_size(range); } 最后调用kmem_cache_free将它从slab缓冲区ashmem_range_cachep中释放。 这里的slab缓冲区ashmem_range_cachep定义如下: staticstructkmem_cache*ashmem_range_cachep__read_mostly; 它和前面介绍的slab缓冲区ashmem_area_cachep一样,是在Ashmem驱动程序模块初始化函数ashmem_init进行初始化的: staticint__initashmem_init(void) { intret; ...... ashmem_range_cachep=kmem_cache_create("ashmem_range_cache", sizeof(structashmem_range), 0,0,NULL); if(unlikely(!ashmem_range_cachep)){ printk(KERN_ERR"ashmem:failedtocreateslabcache\n"); return-ENOMEM; } ...... printk(KERN_INFO"ashmem:initialized\n"); return0; } 回到ashmem_unpin函数中,我们再来看看range_alloc函数的实现: /* *range_alloc-allocateandinitializeanewashmem_rangestructure * *'asma'-associatedashmem_area *'prev_range'-thepreviousashmem_rangeinthesortedasma->unpinnedlist *'purged'-initialpurgevalue(ASMEM_NOT_PURGEDorASHMEM_WAS_PURGED) *'start'-startingpage,inclusive *'end'-endingpage,inclusive * *Callermustholdashmem_mutex. */ staticintrange_alloc(structashmem_area*asma, structashmem_range*prev_range,unsignedintpurged, size_tstart,size_tend) { structashmem_range*range; range=kmem_cache_zalloc(ashmem_range_cachep,GFP_KERNEL); if(unlikely(!range)) return-ENOMEM; range->asma=asma; range->pgstart=start; range->pgend=end; range->purged=purged; list_add_tail(&range->unpinned,&prev_range->unpinned); if(range_on_lru(range)) lru_add(range); return0; } 这个函数的作用是从slab 缓冲区中ashmem_range_cachep分配一个ashmem_range,然后对它作相应的初始化,放在相应的ashmem_area->unpinned_list列表中,并且还要判断这个range的purged是否是ASHMEM_NOT_PURGED状态,如果是,还要把它放在lru列表中: staticinlinevoidlru_add(structashmem_range*range) { list_add_tail(&range->lru,&ashmem_lru_list); lru_count+=range_size(range); } 这样,ashmem_unpin的源代码我们就分析完了。 接着,我们再来看一下ASHMEM_PIN操作,进入到ashmem_pin函数: /* *ashmem_pin-pinthegivenashmemregion,returningwhetheritwas *previouslypurged(ASHMEM_WAS_PURGED)ornot(ASHMEM_NOT_PURGED). * *Callermustholdashmem_mutex. */ staticintashmem_pin(structashmem_area*asma,size_tpgstart,size_tpgend) { structashmem_range*range,*next; intret=ASHMEM_NOT_PURGED; list_for_each_entry_safe(range,next,&asma->unpinned_list,unpinned){ /*movedpastlastapplicablepage;wecanshortcircuit*/ if(range_before_page(range,pgstart)) break; /* *Theusercanaskustopinpagesthatspanmultipleranges, *ortopinpagesthataren'tevenunpinned,sothisismessy. * *Fourcases: *1.Therequestedrangesubsumesanexistingrange,sowe *justremovetheentirematchingrange. *2.Therequestedrangeoverlapsthestartofanexisting *range,sowejustupdatethatrange. *3.Therequestedrangeoverlapstheendofanexisting *range,sowejustupdatethatrange. *4.Therequestedrangepunchesaholeinanexistingrange, *sowehavetoupdateonesideoftherangeandthen *createanewrangefortheotherside. */ if(page_range_in_range(range,pgstart,pgend)){ ret|=range->purged; /*Case#1:Easy.Justnukethewholething.*/ if(page_range_subsumes_range(range,pgstart,pgend)){ range_del(range); continue; } /*Case#2:Weoverlapfromthestart,soadjustit*/ if(range->pgstart>=pgstart){ range_shrink(range,pgend+1,range->pgend); continue; } /*Case#3:Weoverlapfromtherear,soadjustit*/ if(range->pgend<=pgend){ range_shrink(range,range->pgstart,pgstart-1); continue; } /* *Case#4:Weeatachunkoutofthemiddle.Abit *morecomplicated,weallocateanewrangeforthe *secondhalfandadjustthefirstchunk'sendpoint. */ range_alloc(asma,range,range->purged, pgend+1,range->pgend); range_shrink(range,range->pgstart,pgstart-1); break; } } returnret; } 前面我们说过,被pin的内存块,必须是在unpinned_list列表中的,如果不在,就什么都不用做。要判断要pin的内存块是否在unpinned_list列表中,又要通过遍历相应的asma->unpinned_list列表来找出与之相交的内存块了。这个函数的处理方法大体与前面的ashmem_unpin函数是一致的,也是要考虑四种不同的相交情况,这里就不详述了,读者可以自己分析一下。 这里我们只看一下range_shrink函数的实现: /* *range_shrink-shrinksarange * *Callermustholdashmem_mutex. */ staticinlinevoidrange_shrink(structashmem_range*range, size_tstart,size_tend) { size_tpre=range_size(range); range->pgstart=start; range->pgend=end; if(range_on_lru(range)) lru_count-=pre-range_size(range); } 这个函数的实现很简单,只是调整一下range描述的内存块的起始页面号,如果它是位于lru列表中,还要调整一下在lru列表中的总页面数大小。 这样,匿名共享内存的ASHMEM_PIN和ASHMEM_UNPIN操作就介绍完了,但是,我们还看不出来Ashmem驱动程序是怎么样辅助内存管理系统来有效管理内存的。有了前面这些unpinned的内存块列表之后,下面我们就看一下Ashmem驱动程序是怎么样辅助内存管理系统来有效管理内存的。 首先看一下Ashmem驱动程序模块初始化函数ashmem_init: staticstructshrinkerashmem_shrinker={ .shrink=ashmem_shrink, .seeks=DEFAULT_SEEKS*4, }; staticint__initashmem_init(void) { intret; ...... register_shrinker(&ashmem_shrinker); printk(KERN_INFO"ashmem:initialized\n"); return0; } 这里通过调用register_shrinker函数向内存管理系统注册一个内存回收算法函数。在Linux内核中,当系统内存紧张时,内存管理系统就会进行内存回收算法,将一些最近没有用过的内存换出物理内存去,这样可以增加物理内存的供应。因此,当内存管理系统进行内存回收时,就会调用到这里的ashmem_shrink函数,让Ashmem驱动程序执行内存回收操作: /* *ashmem_shrink-ourcacheshrinker,calledfrommm/vmscan.c::shrink_slab * *'nr_to_scan'isthenumberofobjects(pages)toprune,or0toqueryhow *manyobjects(pages)wehaveintotal. * *'gfp_mask'isthemaskoftheallocationthatgotusintothismess. * *Returnvalueisthenumberofobjects(pages)remaining,or-1ifwecannot *proceedwithoutriskofdeadlock(duetogfp_mask). * *WeapproximateLRUvialeast-recently-unpinned,jettisoningunpinnedpartial *chunksofashmemregionsLRU-wiseone-at-a-timeuntilwehit'nr_to_scan' *pagesfreed. */ staticintashmem_shrink(intnr_to_scan,gfp_tgfp_mask) { structashmem_range*range,*next; /*Wemightrecurseintofilesystemcode,sobailoutifnecessary*/ if(nr_to_scan&&!(gfp_mask&__GFP_FS)) return-1; if(!nr_to_scan) returnlru_count; mutex_lock(&ashmem_mutex); list_for_each_entry_safe(range,next,&ashmem_lru_list,lru){ structinode*inode=range->asma->file->f_dentry->d_inode; loff_tstart=range->pgstart*PAGE_SIZE; loff_tend=(range->pgend+1)*PAGE_SIZE-1; vmtruncate_range(inode,start,end); range->purged=ASHMEM_WAS_PURGED; lru_del(range); nr_to_scan-=range_size(range); if(nr_to_scan<=0) break; } mutex_unlock(&ashmem_mutex); returnlru_count; } 这里的参数nr_to_scan表示要扫描的页数,如果是0,则表示要查询一下,当前Ashmem驱动程序有多少页面可以回收,这里就等于挂在lru列表的内块页面的总数了,即lru_count;否则,就要开始扫描lru列表,从中回收内存了,直到回收的内存页数等于nr_to_scan,或者已经没有内存可回收为止。回收内存页面是通过vm_truncate_range函数进行的,这个函数定义在kernel/common/mm/memory.c文件中,它是Linux内核内存管理系统实现的,有兴趣的读者可以研究一下。 这样,Android系统匿名共享内存Ashmem驱动程序源代码就分析完了,在下一篇文章中,我们将继续分析Android系统的匿名共享内存机制,研究它是如何通过Binder进程间通信机制实现在不同进程程进行内存共享的,敬请关注。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/965476,如需转载请自行联系原作者

优秀的个人博客,低调大师

HTAP数据库(OLTP+OLAP) - 数据库典型架构 优缺点剖析(shard VS shared)

标签 PostgreSQL , 共享分布式存储 , 存储计算能力。 背景 随着互联网的发展,数据爆炸性的增长,数据库逐渐成为了很多业务的绊脚石,很多业务也哭着喊着要上分布式数据库(个人认为大部分是高估了自己的业务)。 分布式数据库又分很多流派,比如重点要说的sharding和共享分布式存储的架构,它们有着什么样的优缺点呢? sharding vs 共享分布式存储 数据库架构 如果要在单机并行能力的前提下,再实现多机器并行,可以有两种玩法: 第一种玩法,可以带其他产品一起玩,用PostgreSQL 10+的fdw+append parallel+继承+pushdown(join,agg,where,sort,...)+merge sort,可以实现对任意产品的多机并行(比如后端可以是MySQL)。 第二种玩法,更加的先进,节点间不仅共享数据,而且能

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册