精华内容
下载资源
问答
  • Android 匿名共享内存C接口分析

    万次阅读 2013-09-17 09:21:09
    匿名共享内存驱动源码分析中详细分析了匿名共享内存在Linux内核空间的实现,虽然内核空间实现了匿名共享内存,但仍然需要在用户空间为用户使用匿名共享内存提供访问接口。Android系统在用户空间,C++应用程序框架层...

    Android 匿名共享内存驱动源码分析中详细分析了匿名共享内存在Linux内核空间的实现,虽然内核空间实现了匿名共享内存,但仍然需要在用户空间为用户使用匿名共享内存提供访问接口。Android系统在用户空间,C++应用程序框架层,Java层分别提供了访问接口


    本文首先介绍匿名共享内存在用户空间提供的C语言接口,在后续文章中在介绍Android匿名共享内存的C++及Java接口,从而全面理解并掌握Android匿名共享内存的使用。

    1)匿名共享内存的创建

    system\core\libcutils\ashmem-dev.c
    int ashmem_create_region(const char *name, size_t size)
    {
    	int fd, ret;
        //打开"/dev/ashmem"设备文件
    	fd = open(ASHMEM_DEVICE, O_RDWR);
    	if (fd < 0)
    		return fd;
        //根据Java空间传过来的名称修改设备文件名
    	if (name) {
    		char buf[ASHMEM_NAME_LEN];
    		strlcpy(buf, name, sizeof(buf));
    		//进入匿名共享内存驱动修改匿名共享内存名称
    		ret = ioctl(fd, ASHMEM_SET_NAME, buf);
    		if (ret < 0)
    			goto error;
    	}
        进入匿名共享内存驱动修改匿名共享内存大小
    	ret = ioctl(fd, ASHMEM_SET_SIZE, size);
    	if (ret < 0)
    		goto error;
    	return fd;
    error:
    	close(fd);
    	return ret;
    }
    ASHMEM_DEVICE的宏定义如下:
    #define ASHMEM_DEVICE	"/dev/ashmem"
    函数ashmem_create_region首先通过open函数进入匿名共享内存驱动打开/dev/ashmem设备文件,打开过程在Android 匿名共享内存驱动源码分析中已经详细分析了,就是在匿名共享内存初始化过程创建的slab缓冲区ashmem_area_cachep中创建并初始化一个ashmem_area结构体了,接着通过IO命令来修改该ashmem_area结构体的成员name和size,具体设置过程请查看Android 匿名共享内存驱动源码分析。匿名共享内存的创建过程可以归纳为以下三个步骤:
    1. 打开/dev/ashmem设备文件;
    2. 修改匿名共享内存名称
    3. 修改匿名共享内存大小

    2)设置匿名共享内存属性

    通过Ioctl命令控制系统调用进入内核空间的匿名共享内存驱动来设置匿名共享内存块的属性值,比如设置匿名共享内存块的锁定与解锁,设置匿名共享内存块的大小,名称,保护位等属性信息。Android对匿名共享内存的这些属性访问也提供了相应的C语言接口:

    1. 设置匿名共享内存的保护位

    int ashmem_set_prot_region(int fd, int prot)
    {
    	return ioctl(fd, ASHMEM_SET_PROT_MASK, prot);
    }

    2.锁定匿名共享内存块
    int ashmem_pin_region(int fd, size_t offset, size_t len)
    {
    	struct ashmem_pin pin = { offset, len };
    	return ioctl(fd, ASHMEM_PIN, &pin);
    }

    3.解锁指定匿名共享内存块
    int ashmem_unpin_region(int fd, size_t offset, size_t len)
    {
    	struct ashmem_pin pin = { offset, len };
    	return ioctl(fd, ASHMEM_UNPIN, &pin);
    }

    4.获取创建的匿名共享内存大小
    int ashmem_get_size_region(int fd)
    {
      return ioctl(fd, ASHMEM_GET_SIZE, NULL);
    }
    
    无论是匿名共享内存的属性设置还是获取,都是直接使用ioctl系统调用进入匿名共享内存驱动中实现的,关于匿名共享内存驱动是如何实现这些功能的,在Android 匿名共享内存驱动源码分析中有详细的介绍,这里就不重复介绍了。这里我们知道Android提供的匿名共享内存C语言接口比较简单。了解了匿名共享内存的C语言接口之后也为以后学习匿名共享内存的C++接口提供基础。
    展开全文
  • jvm堆外直接内存实现高性能入层

    千次阅读 2017-04-04 11:44:12
    入层接受app的请求,解包数据重新封装数据包,把app数据包做为包体,同时加上入层的包头数据,根据app数据包头的协议号转发到后端业务服务器。 所有app端的请求都先经过入层,因此入层的性能是至关重要。 ...

           接入层接受app的请求,解包数据重新封装数据包,把app数据包做为包体,同时再加上接入层的包头数据,根据app数据包头的协议号转发到后端业务服务器。
    所有app端的请求都先经过接入层,因此接入层的性能是至关重要。

           为了性能上的保证,使用到netty的PooledDirectByteBuf,顾名思义就是池化的堆外直接内存,堆外直接内存就是jvm堆以外申请的内存,这里为什么考虑用堆外内存?看下图就明白了:
     
           上图中是没有使用堆外内存的例子,运行在内核态的socket接收到的数据放在堆外直接内存中,java进程要得到这份数据,选 通过jvm把数据复制到jvm的堆内存,java进程发送数据时,jvm会把堆内存数据复制到堆外直接内存,再用socket发送堆外直接内存数据。
    使用了PooledDirectByteBuf后如下:
     

            堆外直接内存减少jvm的GC造成的停顿时间,但是创建和销毁的开销较大,PooledDirectByteBuf对堆外直接内存进行池化,先申请一大块的堆外内存,创建PooledDirectByteBuf时从池子里拿到一块空间写入数据,使用完后再把空间归还。
            堆外直接内存在java里用DirectByteBuffer,netty的PooledDirectByteBuf是基于DirectByteBuffer进行池化封装,DirectByteBuffer的父类是MappedByteBuffer,MappedByteBuffer源码里注释有一行描述A direct byte buffer whose content is a memory-mapped region of a file,内容是个内存映射文件,内存映射文件,是由一个文件到一块内存的映射。内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对文件进行映射。两个或多个进程映射同一文件映射对象的视图,即它们在共享同一物理存储页。这样,当一个进程向内存映射文件的一个视图写入数据时,其他的进程立即在自己的视图中看到变化

    展开全文
  • 高性能网络编程7--tcp连接的内存使用

    万次阅读 多人点赞 2014-01-23 17:47:28
    当服务器的并发TCP连接数以十万计时,我们就会对一个TCP连接在操作系统内核上消耗的内存多少感兴趣。操作系统里可配置的、貌似跟TCP内存相关的设置项却很让人疑惑,例如,tcp_rmem[2]和rmem_max似乎都跟接收缓存最大...
    当服务器的并发TCP连接数以十万计时,我们就会对一个TCP连接在操作系统内核上消耗的内存多少感兴趣。socket编程方法提供了SO_SNDBUF、SO_RCVBUF这样的接口来设置连接的读写缓存,linux上还提供了以下系统级的配置来整体设置服务器上的TCP内存使用,但这些配置看名字却有些互相冲突、概念模糊的感觉,如下(sysctl -a命令可以查看这些配置):
    net.ipv4.tcp_rmem = 8192 87380 16777216
    net.ipv4.tcp_wmem = 8192 65536 16777216
    net.ipv4.tcp_mem = 8388608 12582912 16777216
    net.core.rmem_default = 262144
    net.core.wmem_default = 262144
    net.core.rmem_max = 16777216
    net.core.wmem_max = 16777216

    还有一些较少被提及的、也跟TCP内存相关的配置:
    net.ipv4.tcp_moderate_rcvbuf = 1
    net.ipv4.tcp_adv_win_scale = 2

    (注:为方便下文讲述,介绍以上系统配置时前缀省略掉,配置值以空格分隔的多个数字以数组来称呼,例如tcp_rmem[2]表示上面第一行最后一列16777216。)

    网上可以找到很多这些系统配置项的说明,然而往往还是让人费解,例如,tcp_rmem[2]和rmem_max似乎都跟接收缓存最大值有关,但它们却可以不一致,究竟有什么区别?或者tcp_wmem[1]和wmem_default似乎都表示发送缓存的默认值,冲突了怎么办?在用抓包软件抓到的syn握手包里,为什么TCP接收窗口大小似乎与这些配置完全没关系?

    TCP连接在进程中使用的内存大小千变万化,通常程序较复杂时可能不是直接基于socket编程,这时平台级的组件可能就封装了TCP连接使用到的用户态内存。不同的平台、组件、中间件、网络库都大不相同。而内核态为TCP连接分配内存的算法则是基本不变的,这篇文章将试图说明TCP连接在内核态中会使用多少内存,操作系统使用怎样的策略来平衡宏观的吞吐量与微观的某个连接传输速度。这篇文章也将一如既往的面向应用程序开发者,而不是系统级的内核开发者,所以,不会详细的介绍为了一个TCP连接、一个TCP报文操作系统分配了多少字节的内存,内核级的数据结构也不是本文的关注点,这些也不是应用级程序员的关注点。这篇文章主要描述linux内核为了TCP连接上传输的数据是怎样管理读写缓存的。


    一、缓存上限是什么?

    (1)先从应用程序编程时可以设置的SO_SNDBUF、SO_RCVBUF说起。

    无论何种语言,都对TCP连接提供基于setsockopt方法实现的SO_SNDBUF、SO_RCVBUF,怎么理解这两个属性的意义呢?
    SO_SNDBUF、SO_RCVBUF都是个体化的设置,即,只会影响到设置过的连接,而不会对其他连接生效。SO_SNDBUF表示这个连接上的内核写缓存上限。实际上,进程设置的SO_SNDBUF也并不是真的上限,在内核中会把这个值翻一倍再作为写缓存上限使用,我们不需要纠结这种细节,只需要知道,当设置了SO_SNDBUF时,就相当于划定了所操作的TCP连接上的写缓存能够使用的最大内存。然而,这个值也不是可以由着进程随意设置的,它会受制于系统级的上下限,当它大于上面的系统配置wmem_max(net.core.wmem_max)时,将会被wmem_max替代(同样翻一倍);而当它特别小时,例如在2.6.18内核中设计的写缓存最小值为2K字节,此时也会被直接替代为2K。

    SO_RCVBUF表示连接上的读缓存上限,与SO_SNDBUF类似,它也受制于rmem_max配置项,实际在内核中也是2倍大小作为读缓存的使用上限。SO_RCVBUF设置时也有下限,同样在2.6.18内核中若这个值小于256字节就会被256所替代。


    (2)那么,可以设置的SO_SNDBUF、SO_RCVBUF缓存使用上限与实际内存到底有怎样的关系呢?

    TCP连接所用内存主要由读写缓存决定,而读写缓存的大小只与实际使用场景有关,在实际使用未达到上限时,SO_SNDBUF、SO_RCVBUF是不起任何作用的。对读缓存来说,接收到一个来自连接对端的TCP报文时,会导致读缓存增加,当然,如果加上报文大小后读缓存已经超过了读缓存上限,那么这个报文会被丢弃从而读缓存大小维持不变。什么时候读缓存使用的内存会减少呢?当进程调用read、recv这样的方法读取TCP流时,读缓存就会减少。因此,读缓存是一个动态变化的、实际用到多少才分配多少的缓冲内存,当这个连接非常空闲时,且用户进程已经把连接上接收到的数据都消费了,那么读缓存使用内存就是0。

    写缓存也是同样道理。当用户进程调用send或者write这样的方法发送TCP流时,就会造成写缓存增大。当然,如果写缓存已经到达上限,那么写缓存维持不变,向用户进程返回失败。而每当接收到TCP连接对端发来的ACK确认了报文的成功发送时,写缓存就会减少,这是因为TCP的可靠性决定的,发出去报文后由于担心报文丢失而不会销毁它,可能会由重发定时器来重发报文。因此,写缓存也是动态变化的,空闲的正常连接上,写缓存所用内存通常也为0。

    因此,只有当接收网络报文的速度大于应用程序读取报文的速度时,可能使读缓存达到了上限,这时这个缓存使用上限才会起作用。所起作用为:丢弃掉新收到的报文,防止这个TCP连接消耗太多的服务器资源。同样,当应用程序发送报文的速度大于接收对方确认ACK报文的速度时,写缓存可能达到上限,从而使send这样的方法失败,内核不为其分配内存。


    二、缓存的大小与TCP的滑动窗口到底有什么关系?

    (1)滑动窗口的大小与缓存大小肯定是有关的,但却不是一一对应的关系,更不会与缓存上限具有一一对应的关系。因此,网上很多资料介绍rmem_max等配置设置了滑动窗口的最大值,与我们tcpdump抓包时看到的win窗口值完全不一致,是讲得通的。下面我们来细探其分别在哪里。

    读缓存的作用有2个:1、将无序的、落在接收滑动窗口内的TCP报文缓存起来;2、当有序的、可以供应用程序读取的报文出现时,由于应用程序的读取是延时的,所以会把待应用程序读取的报文也保存在读缓存中。所以,读缓存一分为二,一部分缓存无序报文,一部分缓存待延时读取的有序报文。这两部分缓存大小之和由于受制于同一个上限值,所以它们是会互相影响的,当应用程序读取速率过慢时,这块过大的应用缓存将会影响到套接字缓存,使接收滑动窗口缩小,从而通知连接的对端降低发送速度,避免无谓的网络传输。当应用程序长时间不读取数据,造成应用缓存将套接字缓存挤压到没空间,那么连接对端会收到接收窗口为0的通知,告诉对方:我现在消化不了更多的报文了。

    反之,接收滑动窗口也是一直在变化的,我们用tcpdump抓三次握手的报文:
    14:49:52.421674 IP houyi-vm02.dev.sd.aliyun.com.6400 > r14a02001.dg.tbsite.net.54073: S 2736789705:2736789705(0) ack 1609024383 win 5792 <mss 1460,sackOK,timestamp 2925954240 2940689794,nop,wscale 9>

    可以看到初始的接收窗口是5792,当然也远小于最大接收缓存(稍后介绍的tcp_rmem[1])。
    这当然是有原因的,TCP协议需要考虑复杂的网络环境,所以使用了慢启动、拥塞窗口(参见高性能网络编程2----TCP消息的发送),建立连接时的初始窗口并不会按照接收缓存的最大值来初始化。这是因为,过大的初始窗口从宏观角度,对整个网络可能造成过载引发恶性循环,也就是考虑到链路上各环节的诸多路由器、交换机可能扛不住压力不断的丢包(特别是广域网),而微观的TCP连接的双方却只按照自己的读缓存上限作为接收窗口,这样双方的发送窗口(对方的接收窗口)越大就对网络产生越坏的影响。慢启动就是使初始窗口尽量的小,随着接收到对方的有效报文,确认了网络的有效传输能力后,才开始增大接收窗口。

    不同的linux内核有着不同的初始窗口,我们以广为使用的linux2.6.18内核为例,在以太网里,MSS大小为1460,此时初始窗口大小为4倍的MSS,简单列下代码(*rcv_wnd即初始接收窗口):
      int init_cwnd = 4;
      if (mss > 1460*3)
       init_cwnd = 2;
      else if (mss > 1460)
       init_cwnd = 3;
      if (*rcv_wnd > init_cwnd*mss)
       *rcv_wnd = init_cwnd*mss;

    大家可能要问,为何上面的抓包上显示窗口其实是5792,并不是1460*4为5840呢?这是因为1460想表达的意义是:将1500字节的MTU去除了20字节的IP头、20字节的TCP头以后,一个最大报文能够承载的有效数据长度。但有些网络中,会在TCP的可选头部里,使用12字节作为时间戳使用,这样,有效数据就是MSS再减去12,初始窗口就是(1460-12)*4=5792,这与窗口想表达的含义是一致的,即:我能够处理的有效数据长度。

    在linux3以后的版本中,初始窗口调整到了10个MSS大小,这主要来自于GOOGLE的建议。原因是这样的,接收窗口虽然常以指数方式来快速增加窗口大小(拥塞阀值以下是指数增长的,阀值以上进入拥塞避免阶段则为线性增长,而且,拥塞阀值自身在收到128以上数据报文时也有机会快速增加),若是传输视频这样的大数据,那么随着窗口增加到(接近)最大读缓存后,就会“开足马力”传输数据,但若是通常都是几十KB的网页,那么过小的初始窗口还没有增加到合适的窗口时,连接就结束了。这样相比较大的初始窗口,就使得用户需要更多的时间(RTT)才能传输完数据,体验不好。

    那么这时大家可能有疑问,当窗口从初始窗口一路扩张到最大接收窗口时,最大接收窗口就是最大读缓存吗?
    不是,因为必须分一部分缓存用于应用程序的延时报文读取。到底会分多少出来呢?这是可配的系统选项,如下:
    net.ipv4.tcp_adv_win_scale = 2

    这里的tcp_adv_win_scale意味着,将要拿出1/(2^tcp_adv_win_scale)缓存出来做应用缓存。即,默认tcp_adv_win_scale配置为2时,就是拿出至少1/4的内存用于应用读缓存,那么,最大的接收滑动窗口的大小只能到达读缓存的3/4。


    (2)最大读缓存到底应该设置到多少为合适呢?

    当应用缓存所占的份额通过tcp_adv_win_scale配置确定后,读缓存的上限应当由最大的TCP接收窗口决定。初始窗口可能只有4个或者10个MSS,但在无丢包情形下随着报文的交互窗口就会增大,当窗口过大时,“过大”是什么意思呢?即,对于通讯的两台机器的内存而言不算大,但是对于整个网络负载来说过大了,就会对网络设备引发恶性循环,不断的因为繁忙的网络设备造成丢包。而窗口过小时,就无法充分的利用网络资源。所以,一般会以BDP来设置最大接收窗口(可计算出最大读缓存)。BDP叫做带宽时延积,也就是带宽与网络时延的乘积,例如若我们的带宽为2Gbps,时延为10ms,那么带宽时延积BDP则为2G/8*0.01=2.5MB,所以这样的网络中可以设最大接收窗口为2.5MB,这样最大读缓存可以设为4/3*2.5MB=3.3MB。

    为什么呢?因为BDP就表示了网络承载能力,最大接收窗口就表示了网络承载能力内可以不经确认发出的报文。如下图所示:

    经常提及的所谓长肥网络,“长”就是是时延长,“肥”就是带宽大,这两者任何一个大时,BDP就大,都应导致最大窗口增大,进而导致读缓存上限增大。所以在长肥网络中的服务器,缓存上限都是比较大的。(当然,TCP原始的16位长度的数字表示窗口虽然有上限,但在RFC1323中定义的弹性滑动窗口使得滑动窗口可以扩展到足够大。)

    发送窗口实际上就是TCP连接对方的接收窗口,所以大家可以按接收窗口来推断,这里不再啰嗦。


    三、linux的TCP缓存上限自动调整策略
    那么,设置好最大缓存限制后就高枕无忧了吗?对于一个TCP连接来说,可能已经充分利用网络资源,使用大窗口、大缓存来保持高速传输了。比如在长肥网络中,缓存上限可能会被设置为几十兆字节,但系统的总内存却是有限的,当每一个连接都全速飞奔使用到最大窗口时,1万个连接就会占用内存到几百G了,这就限制了高并发场景的使用,公平性也得不到保证。我们希望的场景是,在并发连接比较少时,把缓存限制放大一些,让每一个TCP连接开足马力工作;当并发连接很多时,此时系统内存资源不足,那么就把缓存限制缩小一些,使每一个TCP连接的缓存尽量的小一些,以容纳更多的连接。

    linux为了实现这种场景,引入了自动调整内存分配的功能,由tcp_moderate_rcvbuf配置决定,如下:
    net.ipv4.tcp_moderate_rcvbuf = 1
    默认tcp_moderate_rcvbuf配置为1,表示打开了TCP内存自动调整功能。若配置为0,这个功能将不会生效(慎用)。

    另外请注意:当我们在编程中对连接设置了SO_SNDBUF、SO_RCVBUF,将会使linux内核不再对这样的连接执行自动调整功能!

    那么,这个功能到底是怎样起作用的呢?看以下配置:
    net.ipv4.tcp_rmem = 8192 87380 16777216
    net.ipv4.tcp_wmem = 8192 65536 16777216
    net.ipv4.tcp_mem = 8388608 12582912 16777216

    tcp_rmem[3]数组表示任何一个TCP连接上的读缓存上限,其中tcp_rmem[0]表示最小上限,tcp_rmem[1]表示初始上限(注意,它会覆盖适用于所有协议的rmem_default配置),tcp_rmem[2]表示最大上限。
    tcp_wmem[3]数组表示写缓存,与tcp_rmem[3]类似,不再赘述。

    tcp_mem[3]数组就用来设定TCP内存的整体使用状况,所以它的值很大(它的单位也不是字节,而是页--4K或者8K等这样的单位!)。这3个值定义了TCP整体内存的无压力值、压力模式开启阀值、最大使用值。以这3个值为标记点则内存共有4种情况:

    1、当TCP整体内存小于tcp_mem[0]时,表示系统内存总体无压力。若之前内存曾经超过了tcp_mem[1]使系统进入内存压力模式,那么此时也会把压力模式关闭。
    这种情况下,只要TCP连接使用的缓存没有达到上限(注意,虽然初始上限是tcp_rmem[1],但这个值是可变的,下文会详述),那么新内存的分配一定是成功的。

    2、当TCP内存在tcp_mem[0]与tcp_mem[1]之间时,系统可能处于内存压力模式,例如总内存刚从tcp_mem[1]之上下来;也可能是在非压力模式下,例如总内存刚从tcp_mem[0]以下上来。
    此时,无论是否在压力模式下,只要TCP连接所用缓存未超过tcp_rmem[0]或者tcp_wmem[0],那么都一定都能成功分配新内存。否则,基本上就会面临分配失败的状况。(注意:还有一些例外场景允许分配内存成功,由于对于我们理解这几个配置项意义不大,故略过。)

    3、当TCP内存在tcp_mem[1]与tcp_mem[2]之间时,系统一定处于系统压力模式下。其他行为与上同。

    4、当TCP内存在tcp_mem[2]之上时,毫无疑问,系统一定在压力模式下,而且此时所有的新TCP缓存分配都会失败。

    下图为需要新缓存时内核的简化逻辑:

    当系统在非压力模式下,上面我所说的每个连接的读写缓存上限,才有可能增加,当然最大也不会超过tcp_rmem[2]或者tcp_wmem[2]。相反,在压力模式下,读写缓存上限则有可能减少,虽然上限可能会小于tcp_rmem[0]或者tcp_wmem[0]

    所以,粗略的总结下,对这3个数组可以这么看:
    1、只要系统TCP的总体内存超了 tcp_mem[2] ,新内存分配都会失败。
    2、tcp_rmem[0]或者tcp_wmem[0]优先级也很高,只要条件1不超限,那么只要连接内存小于这两个值,就保证新内存分配一定成功。
    3、只要总体内存不超过tcp_mem[0],那么新内存在不超过连接缓存的上限时也能保证分配成功。
    4、tcp_mem[1]与tcp_mem[0]构成了开启、关闭内存压力模式的开关。在压力模式下,连接缓存上限可能会减少。在非压力模式下,连接缓存上限可能会增加,最多增加到tcp_rmem[2]或者tcp_wmem[2]。








    展开全文
  • 在Android系统中,针对移动设备内存空间有限的特点,提供了一种在进程间共享数据的机制:匿名共享内存,它能够辅助内存管理系统来有效地管理内存,它的实现原理我们在前面已经分析过了。为了方便使用匿名共享内存...

            在Android系统中,针对移动设备内存空间有限的特点,提供了一种在进程间共享数据的机制:匿名共享内存,它能够辅助内存管理系统来有效地管理内存,它的实现原理我们在前面已经分析过了。为了方便使用匿名共享内存机制,系统还提供了Java调用接口(MemoryFile)和C++调用接口(MemoryHeapBase、MemoryBase),Java接口在前面也已经分析过了,本文中将继续分析它的C++接口。

    《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

            在前面一篇文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析中,我们分析了匿名共享内存驱动程序Ashmem的实现,重点介绍了它是如何辅助内存管理系统来有效地管理内存的,简单来说,它就是给使用者提供锁机制来辅助管理内存,当我们申请了一大块匿名共享内存时,中间过程有一部分不需要使用时,我们就可以将这一部分内存块解锁,这样内存管理系统就可以把它回收回去了。接着又在前面一篇文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析中,我们分析了匿名共享内存是如何通过Binder进程间通信机制来实现在进程间共享的,简单来说,就是每一个匿名共享内存块都是一个文件,当我们需要在进程间共享时,就把这个文件的打开描述符通过Binder进程间通信机制传递给另一外进程,在传递的过程中,Binder驱动程序就通过这个复制这个打开文件描述符到目标进程中去,从而实现数据共享。在文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划中,我们介绍了如何在Android应用程序中使用匿名共享内存,主要是通过应用程序框架层提供的MemoryFile接口来使用的,而MemoryFile接口是通过JNI方法调用到系统运行时库层中的匿名共享内存C接口,最终通过这些C接口来使用内核空间中的匿名共享内存驱动模块。为了方便开发者灵活地使用匿名共享内存,Android系统在应用程序框架层中还提供了使用匿名共享内存的C++接口,例如,Android应用程序四大组件之一Content Provider,它在应用程序间共享数据时,就是通过匿名共享内存机制来实现,但是它并不是通过MemoryFile接口来使用,而是通过调用C++接口中的MemoryBase类和MemoryHeapBase类来使用。在接下来的内容中,我们就详细分析MemoryHeapBase类和MemoryBase类的实现,以及它们是如何实现在进程间共享数据的。

            如果我们想在进程间共享一个完整的匿名共享内存块,可以通过使用MemoryHeapBase接口来实现,如果我们只想在进程间共享一个匿名共享内存块中的其中一部分时,就可以通过MemoryBase接口来实现。MemoryBase接口是建立在MemoryHeapBase接口的基础上面的,它们都可以作为一个Binder对象来在进程间传输,因此,希望读者在继续阅读本文之前,对Android系统的Binder进程间通信机制有一定的了解,具体可以参考前面一篇文章Android进程间通信(IPC)机制Binder简要介绍和学习计划。下面我们就首先分析MemoryHeapBase接口的实现,然后再分析MemoryBase接口的实现,最后,通过一个实例来说明它们是如何使用的。

            1. MemoryHeapBase

             前面说到,MemoryHeapBase类的对象可以作为Binder对象在进程间传输,作为一个Binder对象,就有Server端对象和Client端引用的概念,其中,Server端对象必须要实现一个BnInterface接口,而Client端引用必须要实现一个BpInterface接口。下面我们就先看一下MemoryHeapBase在Server端实现的类图:


             这个类图中的类可以划分为两部分,一部分是和业务相关的,即跟匿名共享内存操作相关的类,包括MemoryHeapBase、IMemoryBase和RefBase三个类,另一部分是和Binder机制相关的,包括IInterface、BnInterface、BnMemoryHeap、IBinder、BBinder、ProcessState和IPCThreadState七个类。

            我们先来看跟匿名共享内存业务相关的这部分类的逻辑关系。IMemoryBase定义了匿名共享内操作的接口,而MemoryHeapBase是作为Binder机制中的Server角色的,因此,它需要实现IMemoryBase接口,此外,MemoryHeapBase还继承了RefBase类。从前面一篇文章Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析中,我们知道,继承了RefBase类的子类,它们的对象都可以结合Android系统的智能指针来使用,因此,我们在实例化MemoryHeapBase类时,可以通过智能指针来管理它们的生命周期。

            再来看和Binder机制相关的这部分类的逻辑关系。从Android系统进程间通信(IPC)机制Binder中的Server启动过程源代码分析这篇文章中,我们知道,所有的Binder对象都必须实现IInterface接口,无论是Server端实体对象,还是Client端引用对象,通过这个接口的asBinder成员函数我们可以获得Binder对象的IBinder接口,然后通过Binder驱动程序把它传输给另外一个进程。当一个类的对象作为Server端的实体对象时,它还必须实现一个模板类BnInterface,这里负责实例化模板类BnInterface的类便是BnMemoryHeap类了,它里面有一个重要的成员函数onTransact,当Client端引用请求Server端对象执行命令时,Binder系统就会调用BnMemoryHeap类的onTransact成员函数来执行具体的命令。当一个类的对象作为Server端的实体对象时,它还要继承于BBinder类,这是一个实现了IBinder接口的类,它里面有一个重要的成员函数transact,当我们从Server端线程中接收到Client端的请求时,就会调用注册在这个线程中的BBinder对象的transact函数来处理这个请求,而这个transact函数会将这些Client端请求转发给BnMemoryHeap类的onTransact成员函数来处理。最后,ProcessState和IPCThreadState两个类是负责和Binder驱动程序打交道的,其中,ProcessState负责打开Binder设备文件/dev/binder,打开了这个Binder设备文件后,就会得到一个打开设备文件描述符,而IPCThreadState就是通过这个设备文件描述符来和Binder驱动程序进行交互的,例如它通过一个for循环来不断地等待Binder驱动程序通知它有新的Client端请求到来了,一旦有新的Client端请求到来,它就会调用相应的BBinder对象的transact函数来处理。

            本文我们主要是要关注和匿名共享内存业务相关的这部分类,即IMemoryBase和MemoryHeapBase类的实现,和Binder机制相关的这部分类的实现,可以参考Android进程间通信(IPC)机制Binder简要介绍和学习计划一文。

            IMemoryBase类主要定义了几个重要的操作匿名共享内存的方法,它定义在frameworks/base/include/binder/IMemory.h文件中:

    class IMemoryHeap : public IInterface
    {
    public:
    	......
    
    	virtual int         getHeapID() const = 0;
    	virtual void*       getBase() const = 0;
    	virtual size_t      getSize() const = 0;
    
    	......
    };

            成员函数getHeapID是用来获得匿名共享内存块的打开文件描述符的;成员函数getBase是用来获得匿名共享内存块的基地址的,有了这个地址之后,我们就可以在程序里面直接访问这块共享内存了;成员函数getSize是用来获得匿名共享内存块的大小的。
            MemoryHeapBase类主要用来实现上面IMemoryBase类中列出来的几个成员函数的,这个类声明在frameworks/base/include/binder/MemoryHeapBase.h文件中:

    class MemoryHeapBase : public virtual BnMemoryHeap
    {
    public:
    	......
    
    	/*
    	* maps memory from ashmem, with the given name for debugging
    	*/
    	MemoryHeapBase(size_t size, uint32_t flags = 0, char const* name = NULL);
    
    	......
    
    	/* implement IMemoryHeap interface */
    	virtual int         getHeapID() const;
    	virtual void*       getBase() const;
    	virtual size_t      getSize() const;
    
    	......
    private:
    	int         mFD;
    	size_t      mSize;
    	void*       mBase;
    
    	......
    }
             MemoryHeapBase类的实现定义在frameworks/base/libs/binder/MemoryHeapBase.cpp文件中,我们先来看一下它的构造函数的实现:

    MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name)
    : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
    mDevice(0), mNeedUnmap(false)
    {
    	const size_t pagesize = getpagesize();
    	size = ((size + pagesize-1) & ~(pagesize-1));
    	int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size);
    	LOGE_IF(fd<0, "error creating ashmem region: %s", strerror(errno));
    	if (fd >= 0) {
    		if (mapfd(fd, size) == NO_ERROR) {
    			if (flags & READ_ONLY) {
    				ashmem_set_prot_region(fd, PROT_READ);
    			}
    		}
    	}
    }
            这个构造函数有三个参数,其中size表示要创建的匿名共享内存的大小,flags是用来设置这块匿名共享内存的属性的,例如是可读写的还是只读的,name是用来标识这个匿名共享内存的名字的,可以传空值进来,这个参数只是作为调试信息使用的。

            MemoryHeapBase类创建的匿名共享内存是以页为单位的,页的大小一般为4K,但是是可以设置的,这个函数首先通过getpagesize函数获得系统中一页内存的大小值,然后把size参数对齐到页大小去,即如果size不是页大小的整数倍时,就增加它的大小,使得它的值为页大小的整数倍:

    const size_t pagesize = getpagesize();
    size = ((size + pagesize-1) & ~(pagesize-1));
            调整好size的大小后,就调用系统运行时库层的C接口ashmem_create_region来创建一块共享内存了:

    int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size);
            这个函数我们在前面一篇文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析中可以介绍过了,这里不再详细,它只要就是通过Ashmem驱动程序来创建一个匿名共享内存文件,因此,它的返回值是一个文件描述符。

            得到了这个匿名共享内存的文件描述符后,还需要调用mapfd成函数把它映射到进程地址空间去:

    status_t MemoryHeapBase::mapfd(int fd, size_t size, uint32_t offset)
    {
    	......
    
    	if ((mFlags & DONT_MAP_LOCALLY) == 0) {
    		void* base = (uint8_t*)mmap(0, size,
    			PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset);
    		......
    		mBase = base;
    		......
    	} else  {
    		......
    	}
    
    	mFD = fd;
    	mSize = size;
    	return NO_ERROR;
    }
            一般我们创建MemoryHeapBase类的实例时,都是需要把匿名共享内存映射到本进程的地址空间去的,因此,这里的条件(mFlags & DONT_MAP_LOCALLY == 0)为true,于是执行系统调用mmap来执行内存映射的操作。
    void* base = (uint8_t*)mmap(0, size,
    	PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset);
            传进去的第一个参数0表示由内核来决定这个匿名共享内存文件在进程地址空间的起始位置,第二个参数size表示要映射的匿名共享内文件的大小,第三个参数PROT_READ|PROT_WRITE表示这个匿名共享内存是可读写的,第四个参数fd指定要映射的匿名共享内存的文件描述符,第五个参数offset表示要从这个文件的哪个偏移位置开始映射。调用了这个函数之后,最后会进入到内核空间的ashmem驱动程序模块中去执行ashmem_map函数,这个函数的实现具体可以参考Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析一文,这里就不同详细描述了。调用mmap函数返回之后,就得这块匿名共享内存在本进程地址空间中的起始访问地址了,将这个地址保存在成员变量mBase中,最后,还将这个匿名共享内存的文件描述符和以及大小分别保存在成员变量mFD和mSize中。

            回到前面MemoryHeapBase类的构造函数中,将匿名共享内存映射到本进程的地址空间去后,还看继续设置这块匿名共享内存的读写属性:

    if (fd >= 0) {
    	if (mapfd(fd, size) == NO_ERROR) {
    		if (flags & READ_ONLY) {
    			ashmem_set_prot_region(fd, PROT_READ);
    		}
    	}
    }
            上面调用mapfd函数来映射匿名共享内存时,指定这块内存是可读写的,但是如果传进来的参数flags设置了只读属性,那么还需要调用系统运行时库存层的ashmem_set_prot_region函数来设置这块匿名共享内存为只读,这个函数定义在system/core/libcutils/ashmem-dev.c文件,有兴趣的读者可以自己去研究一下。

            这样,通过这个构造函数,一块匿名共享内存就建立好了,其余的三个成员函数getHeapID、getBase和getSize就简单了:

    int MemoryHeapBase::getHeapID() const {
        return mFD;
    }
    
    void* MemoryHeapBase::getBase() const {
        return mBase;
    }
    
    size_t MemoryHeapBase::getSize() const {
        return mSize;
    }
           接下来我们再来看一下MemoryHeapBase在Client端实现的类图:


             这个类图中的类也是可以划分为两部分,一部分是和业务相关的,即跟匿名共享内存操作相关的类,包括BpMemoryHeap、IMemoryBase和RefBase三个类,另一部分是和Binder机制相关的,包括IInterface、BpInterface、BpRefBase、IBinder、BpBinder、ProcessState和IPCThreadState七个类。

            在和匿名共享内存操作相关的类中,BpMemoryHeap类是前面分析的MemoryHeapBase类在Client端进程的远接接口类,当Client端进程从Service Manager或者其它途径获得了一个MemoryHeapBase对象的引用之后,就会在本地创建一个BpMemoryHeap对象来代表这个引用。BpMemoryHeap类同样是要实现IMemoryHeap接口,同时,它是从RefBase类继承下来的,因此,它可以与智能指针来结合使用。

            在和Binder机制相关的类中,和Server端实现不一样的地方是,Client端不需要实现BnInterface和BBinder两个类,但是需要实现BpInterface、BpRefBase和BpBinder三个类。BpInterface类继承于BpRefBase类,而在BpRefBase类里面,有一个成员变量mRemote,它指向一个BpBinder对象,当BpMemoryHeap类需要向Server端对象发出请求时,它就会通过这个BpBinder对象的transact函数来发出这个请求。这里的BpBinder对象是如何知道要向哪个Server对象发出请深圳市的呢?它里面有一个成员变量mHandle,它表示的是一个Server端Binder对象的引用值,BpBinder对象就是要通过这个引用值来把请求发送到相应的Server端对象去的了,这个引用值与Server端Binder对象的对应关系是在Binder驱动程序内部维护的。这里的ProcessSate类和IPCThreadState类的作用和在Server端的作用是类似的,它们都是负责和底层的Binder驱动程序进行交互,例如,BpBinder对象的transact函数就通过线程中的IPCThreadState对象来将Client端请求发送出去的。这些实现具体可以参考Android系统进程间通信(IPC)机制Binder中的Client获得Server远程接口过程源代码分析一文。

            这里我们主要关注BpMemoryHeap类是如何实现IMemoryHeap接口的,这个类声明和定义在frameworks/base/libs/binder/IMemory.cpp文件中:

    class BpMemoryHeap : public BpInterface<IMemoryHeap>
    {
    public:
    	BpMemoryHeap(const sp<IBinder>& impl);
    	......
    
    	virtual int getHeapID() const;
    	virtual void* getBase() const;
    	virtual size_t getSize() const;
    
    	......
    private:
    	mutable volatile int32_t mHeapId;
    	mutable void*       mBase;
    	mutable size_t      mSize;
    
    	......
    }
            先来看构造函数BpMemoryHeap的实现:

    BpMemoryHeap::BpMemoryHeap(const sp<IBinder>& impl)
        : BpInterface<IMemoryHeap>(impl),
            mHeapId(-1), mBase(MAP_FAILED), mSize(0), mFlags(0), mRealHeap(false)
    {
    }
            它的实现很简单,只是初始化一下各个成员变量,例如,表示匿名共享内存文件描述符的mHeapId值初化为-1、表示匿名内共享内存基地址的mBase值初始化为MAP_FAILED以及表示匿名共享内存大小的mSize初始为为0,它们都表示在Client端进程中,这个匿名共享内存还未准备就绪,要等到第一次使用时才会去创建。这里还需要注意的一点,参数impl指向的是一个BpBinder对象,它里面包含了一个指向Server端Binder对象,即MemoryHeapBase对象的引用。

            其余三个成员函数getHeapID、getBase和getSize的实现是类似的:

    int BpMemoryHeap::getHeapID() const {
        assertMapped();
        return mHeapId;
    }
    
    void* BpMemoryHeap::getBase() const {
        assertMapped();
        return mBase;
    }
    
    size_t BpMemoryHeap::getSize() const {
        assertMapped();
        return mSize;
    }
            即它们在使用之前,都会首先调用assertMapped函数来保证在Client端的匿名共享内存是已经准备就绪了的:

    void BpMemoryHeap::assertMapped() const
    {
        if (mHeapId == -1) {
            sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder());
            sp<BpMemoryHeap> heap(static_cast<BpMemoryHeap*>(find_heap(binder).get()));
            heap->assertReallyMapped();
            if (heap->mBase != MAP_FAILED) {
                Mutex::Autolock _l(mLock);
                if (mHeapId == -1) {
                    mBase   = heap->mBase;
                    mSize   = heap->mSize;
                    android_atomic_write( dup( heap->mHeapId ), &mHeapId );
                }
            } else {
                // something went wrong
                free_heap(binder);
            }
        }
    }
           在解释这个函数之前,我们需要先了解一下BpMemoryHeap是如何知道自己内部维护的这块匿名共享内存有没有准备就绪的。
           在frameworks/base/libs/binder/IMemory.cpp文件中,定义了一个全局变量gHeapCache:

    static sp<HeapCache> gHeapCache = new HeapCache();

           它的类型为HeapCache,这也是一个定义在frameworks/base/libs/binder/IMemory.cpp文件的类,它里面维护了本进程中所有的MemoryHeapBase对象的引用。由于在Client端进程中,可能会有多个引用,即多个BpMemoryHeap对象,对应同一个MemoryHeapBase对象(这是由于可以用同一个BpBinder对象来创建多个BpMemoryHeap对象),因此,当第一个BpMemoryHeap对象在本进程中映射好这块匿名共享内存之后,后面的BpMemoryHeap对象就可以直接使用了,不需要再映射一次,当然重新再映射一次没有害处,但是会是多此一举,Google在设计这个类时,可以说是考虑得非常周到的。

           我们来看一下HeapCache的实现:

    class HeapCache : public IBinder::DeathRecipient
    {
    public:
        HeapCache();
        virtual ~HeapCache();
    
        ......
    
        sp<IMemoryHeap> find_heap(const sp<IBinder>& binder);
        void free_heap(const sp<IBinder>& binder);
        sp<IMemoryHeap> get_heap(const sp<IBinder>& binder);
        ......
    
    private:
        // For IMemory.cpp
        struct heap_info_t {
            sp<IMemoryHeap> heap;
            int32_t         count;
        };
    
        ......
    
        Mutex mHeapCacheLock;
        KeyedVector< wp<IBinder>, heap_info_t > mHeapCache;
    };
           它里面定义了一个成员变量mHeapCache,用来维护本进程中的所有BpMemoryHeap对象,同时还提供了find_heap和get_heap函数来查找内部所维护的BpMemoryHeap对象的功能。函数find_heap和get_heap的区别是,在find_heap函数中,如果在mHeapCache找不到相应的BpMemoryHeap对象,就会把这个BpMemoryHeap对象加入到mHeapCache中去,而在get_heap函数中,则不会自动把这个BpMemoryHeap对象加入到mHeapCache中去。

           这里,我们主要看一下find_heap函数的实现:

    sp<IMemoryHeap> HeapCache::find_heap(const sp<IBinder>& binder)
    {
        Mutex::Autolock _l(mHeapCacheLock);
        ssize_t i = mHeapCache.indexOfKey(binder);
        if (i>=0) {
            heap_info_t& info = mHeapCache.editValueAt(i);
            LOGD_IF(VERBOSE,
                    "found binder=%p, heap=%p, size=%d, fd=%d, count=%d",
                    binder.get(), info.heap.get(),
                    static_cast<BpMemoryHeap*>(info.heap.get())->mSize,
                    static_cast<BpMemoryHeap*>(info.heap.get())->mHeapId,
                    info.count);
            android_atomic_inc(&info.count);
            return info.heap;
        } else {
            heap_info_t info;
            info.heap = interface_cast<IMemoryHeap>(binder);
            info.count = 1;
            //LOGD("adding binder=%p, heap=%p, count=%d",
            //      binder.get(), info.heap.get(), info.count);
            mHeapCache.add(binder, info);
            return info.heap;
        }
    }
            这个函数很简单,首先它以传进来的参数binder为关键字,在mHeapCache中查找,看看是否有对应的heap_info对象info存在,如果有的话,就增加它的引用计数info.count值,表示这个BpBinder对象多了一个使用者;如果没有的话,那么就需要创建一个heap_info对象info,并且将它加放到mHeapCache中去了。

            回到前面BpMemoryHeap类中的assertMapped函数中,如果本BpMemoryHeap对象中的mHeapID等于-1,那么就说明这个BpMemoryHeap对象中的匿名共享内存还没准备就绪,因此,需要执行一次映射匿名共享内存的操作。

            在执行映射操作之作,先要看看在本进程中是否有其它映射到同一个MemoryHeapBase对象的BpMemoryHeap对象存在:

    sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder());
    sp<BpMemoryHeap> heap(static_cast<BpMemoryHeap*>(find_heap(binder).get()));
           这里的find_heap函数是BpMemoryHeap的成员函数,最终它调用了前面提到的全局变量gHeapCache来直正执行查找的操作:

    class BpMemoryHeap : public BpInterface<IMemoryHeap>
    {	
    ......
    
    private:
    	static inline sp<IMemoryHeap> find_heap(const sp<IBinder>& binder) {
    		return gHeapCache->find_heap(binder);
    	}
    
    	......
    }
            注意,这里通过find_heap函数得到BpMemoryHeap对象可能是和正在执行assertMapped函数中的BpMemoryHeap对象一样,也可能不一样,但是这没有关系,这两种情况的处理方式都是一样的,都是通过调用这个通过find_heap函数得到BpMemoryHeap对象的assertReallyMapped函数来进一步确认它内部的匿名共享内存是否已经映射到进程空间了:

    void BpMemoryHeap::assertReallyMapped() const
    {
    	if (mHeapId == -1) {
    
    		// remote call without mLock held, worse case scenario, we end up
    		// calling transact() from multiple threads, but that's not a problem,
    		// only mmap below must be in the critical section.
    
    		Parcel data, reply;
    		data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor());
    		status_t err = remote()->transact(HEAP_ID, data, &reply);
    		int parcel_fd = reply.readFileDescriptor();
    		ssize_t size = reply.readInt32();
    		uint32_t flags = reply.readInt32();
    
    		LOGE_IF(err, "binder=%p transaction failed fd=%d, size=%ld, err=%d (%s)",
    			asBinder().get(), parcel_fd, size, err, strerror(-err));
    
    		int fd = dup( parcel_fd );
    		LOGE_IF(fd==-1, "cannot dup fd=%d, size=%ld, err=%d (%s)",
    			parcel_fd, size, err, strerror(errno));
    
    		int access = PROT_READ;
    		if (!(flags & READ_ONLY)) {
    			access |= PROT_WRITE;
    		}
    
    		Mutex::Autolock _l(mLock);
    		if (mHeapId == -1) {
    			mRealHeap = true;
    			mBase = mmap(0, size, access, MAP_SHARED, fd, 0);
    			if (mBase == MAP_FAILED) {
    				LOGE("cannot map BpMemoryHeap (binder=%p), size=%ld, fd=%d (%s)",
    					asBinder().get(), size, fd, strerror(errno));
    				close(fd);
    			} else {
    				mSize = size;
    				mFlags = flags;
    				android_atomic_write(fd, &mHeapId);
    			}
    		}
    	}
    }
            如果成员变量mHeapId的值为-1,就说明还没有把在Server端的MemoryHeapBase对象中的匿名共享内存映射到本进程空间来,于是,就通过一个Binder进程间调用把Server端的MemoryHeapBase对象中的匿名共享内存对象信息取回来:

    Parcel data, reply;
    data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor());
    status_t err = remote()->transact(HEAP_ID, data, &reply);
    int parcel_fd = reply.readFileDescriptor();
    ssize_t size = reply.readInt32();
    uint32_t flags = reply.readInt32();
    
    ......
    
    int fd = dup( parcel_fd );
    		
    ......
            取回来的信息包括MemoryHeapBase对象中的匿名共享内存在本进程中的文件描述符fd、大小size以及访问属性flags。如何把MemoryHeapBase对象中的匿名共享内存作为本进程的一个打开文件描述符,请参考前面一篇文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析。有了这个文件描述符fd后,就可以对它进行内存映射操作了:

    Mutex::Autolock _l(mLock);
    if (mHeapId == -1) {
    	mRealHeap = true;
    	mBase = mmap(0, size, access, MAP_SHARED, fd, 0);
    	if (mBase == MAP_FAILED) {
    		LOGE("cannot map BpMemoryHeap (binder=%p), size=%ld, fd=%d (%s)",
    			asBinder().get(), size, fd, strerror(errno));
    		close(fd);
    	} else {
    		mSize = size;
    		mFlags = flags;
    		android_atomic_write(fd, &mHeapId);
    	}
    }
            前面已经判断过mHeapId是否为-1了,这里为什么又要重新判断一次呢?这里因为,在上面执行Binder进程间调用的过程中,很有可能也有其它的线程也对这个BpMemoryHeap对象执行匿名共享内存映射的操作,因此,这里还要重新判断一下mHeapId的值是否为-1,如果是的话,就要执行匿名共享内存映射的操作了,这是通过调用mmap函数来进行的,这个函数我们前面在分析MemoryHeapBase类的实现时已经见过了。

            从assertReallyMapped函数返回到assertMapped函数中:

    if (heap->mBase != MAP_FAILED) {
        Mutex::Autolock _l(mLock);
        if (mHeapId == -1) {
            mBase   = heap->mBase;
            mSize   = heap->mSize;
            android_atomic_write( dup( heap->mHeapId ), &mHeapId );
        }
    } else {
        // something went wrong
        free_heap(binder);
    }
            如果heap->mBase的值不为MAP_FAILED,就说明这个heap对象中的匿名共享内存已经映射好了。进入到里面的if语句,如果本BpMemoryHeap对象中的mHeap成员变量的值不等待-1,就说明前面通过find_heap函数得到的BpMemoryHeap对象和正在执行assertMapped函数的BpMemoryHeap对象是同一个对象了,因此,什么也不用做就可以返回了,否则的话,就要初始化一下本BpMemoryHeap对象的相关成员变量了:

    mBase   = heap->mBase;
    mSize   = heap->mSize;
    android_atomic_write( dup( heap->mHeapId ), &mHeapId );
            注意,由于这块匿名共享内存已经在本进程中映射好了,因此,这里不需要再执行一次mmap操作,只需要把heap对象的相应成员变量的值拷贝过来就行了,不过对于文件描述符,需要通过dup函数来复制一个。

            这样,BpMemoryHeap对象中的匿名共享内存就准备就绪了,可以通过使用的它mBase成员变量来直接访问这块匿名共享内存。

            至此,MemoryHeapBase类的实现就分析完了,下面我们继续分析MemoryBase类的实现。

            2. MemoryBase

            文章开始时说过,MemoryBase接口是建立在MemoryHeapBase接口的基础上的,它们都可以作为一个Binder对象来在进程间进行数据共享,它们的关系如下所示:


            MemoryBase类包含了一个成员变量mHeap,它的类型的IMemoryHeap,MemoryBase类所代表的匿名共享内存就是通过这个成员变量来实现的。

            与MemoryHeapBase的分析过程一样,我们先来看MemoryBase类在Server端的实现,然后再来看它在Client端的实现。

            MemoryBase在Server端实现的类图如下所示:


            MemoryBase类在Server端的实现与MemoryHeapBase类在Server端的实现是类似的,这里只要把IMemory类换成IMemoryHeap类、把BnMemory类换成BnMemoryHeap类以及MemoryBase类换成MemoryHeapBase类就变成是MemoryHeapBase类在Server端的实现了,因此,我们这里只简单分析IMemory类和MemoryBase类的实现。

            IMemory类定义了MemoryBase类所需要实现的接口,这个类定义在frameworks/base/include/binder/IMemory.h文件中:

    class IMemory : public IInterface
    {
    public:
    	......
    
    	virtual sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const = 0;
    
    	......
    	void* pointer() const;
    	size_t size() const;
    	ssize_t offset() const;
    };
            成员函数getMemory用来获取内部的MemoryHeapBase对象的IMemoryHeap接口;成员函数pointer()用来获取内部所维护的匿名共享内存的基地址;成员函数size()用来获取内部所维护的匿名共享内存的大小;成员函数offset()用来获取内部所维护的这部分匿名共享内存在整个匿名共享内存中的偏移量。

            IMemory类本身实现了pointer、size和offset三个成员函数,因此,它的子类,即MemoryBase类,只需要实现getMemory成员函数就可以了。IMemory类的实现定义在frameworks/base/libs/binder/IMemory.cpp文件中:

    void* IMemory::pointer() const {
        ssize_t offset;
        sp<IMemoryHeap> heap = getMemory(&offset);
        void* const base = heap!=0 ? heap->base() : MAP_FAILED;
        if (base == MAP_FAILED)
            return 0;
        return static_cast<char*>(base) + offset;
    }
    
    size_t IMemory::size() const {
        size_t size;
        getMemory(NULL, &size);
        return size;
    }
    
    ssize_t IMemory::offset() const {
        ssize_t offset;
        getMemory(&offset);
        return offset;
    }
            MemoryBase类声明在frameworks/base/include/binder/MemoryBase.h文件中:

    class MemoryBase : public BnMemory
    {
    public:
    	MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size);
    	......
    	virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size) const;
    
        ......
    private:
    	size_t          mSize;
    	ssize_t         mOffset;
    	sp<IMemoryHeap> mHeap;
    };
           MemoryBase类实现在frameworks/base/libs/binder/MemoryBase.cpp文件中:

    MemoryBase::MemoryBase(const sp<IMemoryHeap>& heap,
            ssize_t offset, size_t size)
        : mSize(size), mOffset(offset), mHeap(heap)
    {
    }
    
    sp<IMemoryHeap> MemoryBase::getMemory(ssize_t* offset, size_t* size) const
    {
        if (offset) *offset = mOffset;
        if (size)   *size = mSize;
        return mHeap;
    }
            在它的构造函数中,接受三个参数,参数heap指向的是一个MemoryHeapBase对象,真正的匿名共享内存就是由它来维护的,参数offset表示这个MemoryBase对象所要维护的这部分匿名共享内存在整个匿名共享内存块中的起始位置,参数size表示这个MemoryBase对象所要维护的这部分匿名共享内存的大小。

            成员函数getMemory的实现很简单,只是简单地返回内部的MemoryHeapBase对象的IMemoryHeap接口,如果传进来的参数offset和size不为NULL,还会把其内部维护的这部分匿名共享内存在整个匿名共享内存块中的偏移位置以及这部分匿名共享内存的大小返回给调用者。

            这里我们可以看出,MemoryBase在Server端的实现只是简单地封装了MemoryHeapBase的实现。

            下面我们再来看MemoryBase类在Client端的实现,同样,先看它们的类图关系:


            这个图中我们可以看出,MemoryBase类在Client端的实现与MemoryHeapBase类在Client端的实现是类似的,这里只要把IMemory类换成IMemoryHeap类以及把BpMemory类换成BpMemoryHeap类就变成是MemoryHeapBase类在Client端的实现了,因此,我们这里只简单分析BpMemory类的实现,前面已经分析过IMemory类的实现了。

            BpMemory类实现在frameworks/base/libs/binder/IMemory.cpp文件中,我们先看它的声明:

    class BpMemory : public BpInterface<IMemory>
    {
    public:
        BpMemory(const sp<IBinder>& impl);
        virtual ~BpMemory();
        virtual sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const;
    
    private:
        mutable sp<IMemoryHeap> mHeap;
        mutable ssize_t mOffset;
        mutable size_t mSize;
    };
           和MemoryBase类一样,它实现了IMemory类的getMemory成员函数,在它的成员变量中,mHeap的类型为IMemoryHeap,它指向的是一个BpMemoryHeap对象,mOffset表示这个BpMemory对象所要维护的这部分匿名共享内存在整个匿名共享内存块中的起始位置,mSize表示这个BpMemory对象所要维护的这部分匿名共享内存的大小。

           下面我们就看一下BpMemory类的成员函数getMemory的实现:

    sp<IMemoryHeap> BpMemory::getMemory(ssize_t* offset, size_t* size) const
    {
        if (mHeap == 0) {
            Parcel data, reply;
            data.writeInterfaceToken(IMemory::getInterfaceDescriptor());
            if (remote()->transact(GET_MEMORY, data, &reply) == NO_ERROR) {
                sp<IBinder> heap = reply.readStrongBinder();
                ssize_t o = reply.readInt32();
                size_t s = reply.readInt32();
                if (heap != 0) {
                    mHeap = interface_cast<IMemoryHeap>(heap);
                    if (mHeap != 0) {
                        mOffset = o;
                        mSize = s;
                    }
                }
            }
        }
        if (offset) *offset = mOffset;
        if (size) *size = mSize;
        return mHeap;
    }
            如果成员变量mHeap的值为NULL,就表示这个BpMemory对象尚未建立好匿名共享内存,于是,就会通过一个Binder进程间调用去Server端请求匿名共享内存信息,在这些信息中,最重要的就是这个Server端的MemoryHeapBase对象的引用heap了,通过这个引用可以在Client端进程中创建一个BpMemoryHeap远程接口,最后将这个BpMemoryHeap远程接口保存在成员变量mHeap中,同时,从Server端获得的信息还包括这块匿名共享内存在整个匿名共享内存中的偏移位置以及大小。这样,这个BpMemory对象中的匿名共享内存就准备就绪了。

            至此,MemoryBase类的实现就分析完了,下面我们将通过一个实例来说明如何使用MemoryBase类在进程间进行内存共享,因为MemoryBase内部使用了MemoryHeapBase类,所以,这个例子同时也可以说明MemoryHeapBase类的使用方法。

            3. MemoryHeapBas类e和MemoryBase类的使用示例

            在这个例子中,我们将在Android源代码工程的external目录中创建一个ashmem源代码工程,它里面包括两个应用程序,一个是Server端应用程序SharedBufferServer,它提供一段共享内存来给Client端程序使用,一个是Client端应用程序SharedBufferClient,它简单地对Server端提供的共享内存进行读和写的操作。Server端应用程序SharedBufferServer和Client端应用程序SharedBufferClient通过Binder进程间通信机制来交互,因此,我们需要定义自己的Binder对象接口ISharedBuffer。Server端应用程序SharedBufferServer在内部实现了一个服务SharedBufferService,这个服务托管给Service Manager来管理,因此,Client端应用程序SharedBufferClient可以向Service Manager请求这个SharedBufferService服务的一个远接接口,然后就可以通过这个服务来操作Server端提供的这段共享内存了。

            这个工程由三个模块组成,第一个模块定义服务接口,它的相关源代码位于external/ashmem/common目录下,第二个模块实现Server端应用程序SharedBufferServer,它的相关源代码位于external/ashmem/server目录下,第三个模块实现Client端应用程序SharedBufferClient,它的相关源代码码位于external/ashmem/client目录下。

            首先来看common模块中的服务接口的定义。在external/ashmem/common目录下,有两个源文件ISharedBuffer.h和ISharedBuffer.cpp。源文件ISharedBuffer.h定义了服务的接口:

    #ifndef ISHAREDBUFFER_H_
    #define ISHAREDBUFFER_H_
    
    #include <utils/RefBase.h>
    #include <binder/IInterface.h>
    #include <binder/Parcel.h>
    
    #define SHARED_BUFFER_SERVICE "shy.luo.SharedBuffer"
    #define SHARED_BUFFER_SIZE 4
    
    using namespace android;
    
    class ISharedBuffer: public IInterface
    {
    public:
            DECLARE_META_INTERFACE(SharedBuffer);
            virtual sp<IMemory> getBuffer() = 0;
    };
    
    class BnSharedBuffer: public BnInterface<ISharedBuffer>
    {
    public:
            virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0);
    };
    
    #endif
            这个文件定义了一个ISharedBuffer接口,里面只有一个成员函数getBuffer,通过这个成员函数,Client端可以从Server端获得一个匿名共享内存,这块匿名共享内存通过我们上面分析的MemoryBase类来维护。这个文件同时也定义了一个必须要在Server端实现的BnSharedBuffer接口,它里面只有一个成员函数onTransact,这个成员函数是用来处理Client端发送过来的请求的。除了定义这两个接口之外,这个文件还定义了两个公共信息,一个是定义常量SHARED_BUFFER_SERVICE,它是Server端提供的内存共享服务的名称,即这个内存共享服务在Service Manager中是以SHARED_BUFFER_SERVICE来作关键字索引的,另外一个是定义常量SHARED_BUFFER_SIZE,它定义了Server端共享的内存块的大小,它的大小设置为4个字节,在这个例子,将把这个共享内存当作一个整型变量来访问。

            源代文件ISharedBuffer.cpp文件定义了一个在Client端使用的BpSharedBuffer接口,它是指向运行在Server端的实现了ISharedBuffer接口的内存共享服务的远程接口,同时,在这个文件里面,也实现了BnSharedBuffer类的onTransact成员函数:

    #define LOG_TAG "ISharedBuffer"
    
    #include <utils/Log.h>
    #include <binder/MemoryBase.h>
    
    #include "ISharedBuffer.h"
    
    using namespace android;
    
    enum
    {
    	GET_BUFFER = IBinder::FIRST_CALL_TRANSACTION
    };
    
    class BpSharedBuffer: public BpInterface<ISharedBuffer>
    {
    public:
    	BpSharedBuffer(const sp<IBinder>& impl)
    		: BpInterface<ISharedBuffer>(impl)
    	{
    
    	}
    
    public:
    	sp<IMemory> getBuffer()
    	{
    		Parcel data;
    		data.writeInterfaceToken(ISharedBuffer::getInterfaceDescriptor());
    
    		Parcel reply;
    		remote()->transact(GET_BUFFER, data, &reply);
    
    		sp<IMemory> buffer = interface_cast<IMemory>(reply.readStrongBinder());
    
    		return buffer;
    	}
    };
    
    IMPLEMENT_META_INTERFACE(SharedBuffer, "shy.luo.ISharedBuffer");
    
    status_t BnSharedBuffer::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
    {
    	switch(code)
    	{
    	case GET_BUFFER:
    		{
    			CHECK_INTERFACE(ISharedBuffer, data, reply);
    
    			sp<IMemory> buffer = getBuffer();
    			if(buffer != NULL)
    			{
    				reply->writeStrongBinder(buffer->asBinder());
    			}
    
    			return NO_ERROR;
    		}
    	default:
    		{
    			return BBinder::onTransact(code, data, reply, flags);
    		}
    	}
    }
            在BpSharedBuffer类的成员函数transact中,向Server端发出了一个请求代码为GET_BUFFER的Binder进程间调用请求,请求Server端返回一个匿名共享内存对象的远程接口IMemory,它实际指向的是一个BpMemory对象,获得了这个对象之后,就将它返回给调用者;在BnSharedBuffer类的成员函数onTransact中,当它接收到从Client端发送过来的代码为GET_BUFFER的Binder进程间调用请求后,便调用其子类的getBuffer成员函数来获一个匿名共享内存对象接口IMemory,它实际指向的是一个MemoryBase对象,获得了这个对象之后,就把它返回给Client端。

            接下来,我们再来看看server模块的实现。在external/ashmem/common目录下,只有一个源文件SharedBufferServer.cpp,它实现了内存共享服务SharedBufferService:

    #define LOG_TAG "SharedBufferServer"
    
    #include <utils/Log.h>
    #include <binder/MemoryBase.h>
    #include <binder/MemoryHeapBase.h>
    #include <binder/IServiceManager.h>
    #include <binder/IPCThreadState.h>
    
    #include "../common/ISharedBuffer.h"
    
    class SharedBufferService : public BnSharedBuffer
    {
    public:
    	SharedBufferService()
    	{
    		sp<MemoryHeapBase> heap = new MemoryHeapBase(SHARED_BUFFER_SIZE, 0, "SharedBuffer");
    		if(heap != NULL)
    		{
    			mMemory = new MemoryBase(heap, 0, SHARED_BUFFER_SIZE);
    
    			int32_t* data = (int32_t*)mMemory->pointer();
    			if(data != NULL)
    			{
    				*data = 0;
    			}
    		}
    	}
    
    	virtual ~SharedBufferService()
    	{
    		mMemory = NULL;
    	}
    
    public:
    	static void instantiate()
    	{
    		defaultServiceManager()->addService(String16(SHARED_BUFFER_SERVICE), new SharedBufferService());
    	}
    
    	virtual sp<IMemory> getBuffer()
    	{
    		return mMemory;
    	}
    
    private:
    	sp<MemoryBase> mMemory;
    };
    
    int main(int argc, char** argv)
    {
    	SharedBufferService::instantiate();
    
    	ProcessState::self()->startThreadPool();
    	IPCThreadState::self()->joinThreadPool();
    
    	return 0;
    }
            SharedBufferService服务实现了BnSharedBuffer接口。在它的构造函数里面,首先是使用MemoryHeapBase类创建了一个匿名共享内存,大小为SHARED_BUFFER_SIZE。接着,又以这个MemoryHeapBase对象为参数,创建一个MemoryBase对象,这个MemoryBase对象指定要维护的匿名共享内存的的偏移位置为0,大小为SHARED_BUFFER_SIZE,并且,将这个匿名共享内存当作一个整型变量地址,将它初始化为0。最终,这个匿名共享内存对象保存在SharedBufferService类的成员变量mMemory中,这个匿名共享内存对象可以通过成员函数getBuffer来获得。

            在Server端应用程序的入口函数main中,首先是调用SharedBufferService静态成员函数instantiate函数来创建一个SharedBufferService实例,然后通过defaultServiceManager函数来获得系统中的Service Manager接口,最后通过这个Service Manager接口的addService函数来把这个SharedBufferService服务添加到Service Manager中去,这样,Client端就可以通过Service Manager来获得这个共享内存服务了。有关Service Manager的实现,请参考前面一篇文章浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路,而用来获取Service Manager接口的defaultServiceManager函数的实现可以参考另外一篇文章浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路。初始化好这个共享内存服务之后,程序就通过ProcessState::self()->startThreadPool()函数来创建一个线程等待Client端来请求服务了,最后,程序的主线程也通过IPCThreadState::self()->joinThreadPool()函数来进入到等待Client端来请求服务的状态中。

           我们还需要为这个Server端应用程序编译一个编译脚本,在external/ashmem/server目录下,新建一个Android.mk文件,它的内容如下所示:

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE_TAGS := optional
    
    LOCAL_SRC_FILES := ../common/ISharedBuffer.cpp \
            SharedBufferServer.cpp
    
    LOCAL_SHARED_LIBRARIES:= libcutils libutils libbinder
    
    LOCAL_MODULE := SharedBufferServer
    
    include $(BUILD_EXECUTABLE)
            最后,我们再来看看client模块的实现。在external/ashmem/client目录下,只有一个源文件SharedBufferClient.cpp,它的内容如下所示:

    #define LOG_TAG "SharedBufferClient"
    
    #include <utils/Log.h>
    #include <binder/MemoryBase.h>
    #include <binder/IServiceManager.h>
    
    #include "../common/ISharedBuffer.h"
    
    int main()
    {
            sp<IBinder> binder = defaultServiceManager()->getService(String16(SHARED_BUFFER_SERVICE));
            if(binder == NULL)
            {
                    printf("Failed to get service: %s.\n", SHARED_BUFFER_SERVICE);
                    return -1;
            }
    
            sp<ISharedBuffer> service = ISharedBuffer::asInterface(binder);
            if(service == NULL)
            {
                    return -2;
            }
    
            sp<IMemory> buffer = service->getBuffer();
            if(buffer == NULL)
            {
                    return -3;
            }
    
            int32_t* data = (int32_t*)buffer->pointer();
            if(data == NULL)
            {
                    return -4;
            }
    
            printf("The value of the shared buffer is %d.\n", *data);
    
            *data = *data + 1;
    
            printf("Add value 1 to the shared buffer.\n");
    
            return 0;
    }
            在这个文件中,主要就是定义了Client端应用程序的入口函数main,在这个main函数里面,首先通过Service Manager接口获得前面所实现的匿名共享内存服务SharedBufferService的远程接口service,然后通过这个远程接口的getBuffer成员函数获得由Server端提供的一块匿名共享内存接口buffer,最后通过这个匿名共享内存接口获得这个匿名共享内存的基地址data。有了这个匿名共享内存的地址data之后,我们就可以对它进行读写了,先是把这个匿名共享内存当作是一个整型变量地址进行访问,并输出它的值的大小,然后对这个整量变量进行加1的操作,并写回到原来的共享内存空间中去。这样,当Server端应用程序运行之后,第一次运行这个Client端应用程序时,输出的值为0,第二次运行这个个Client端应用程序时,输出的值为1,第三次运行这个个Client端应用程序时,输出的值为3......依次类推,后面我们将在模拟器中对这个分析进行验证,如果验证成功的话,就说明这个匿名共享内存成功地在Server端和Client端实现共享了。

            同样,我们需要为这个Client端应用程序编译一个编译脚本,在external/ashmem/client目录下,新建一个Android.mk文件,它的内容如下所示:

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE_TAGS := optional
    
    LOCAL_SRC_FILES := ../common/ISharedBuffer.cpp \
            SharedBufferClient.cpp
    
    LOCAL_SHARED_LIBRARIES:= libcutils libutils libbinder
    
    LOCAL_MODULE := SharedBufferClient
    
    include $(BUILD_EXECUTABLE)
           源代码都准备好了之后,就可以对Server端和Client端应用程序进行编译了。关于如何单独编译Android源代码工程中的模块,以及如何打包system.img,请参考如何单独编译Android源代码中的模块一文。

           执行以下命令进行编译和打包:

    USER-NAME@MACHINE-NAME:~/Android$ mmm external/ashmem/server   
    USER-NAME@MACHINE-NAME:~/Android$ mmm external/ashmem/client           
    USER-NAME@MACHINE-NAME:~/Android$ make snod 
           这样,打包好的Android系统镜像文件system.img就包含我们前面创建的Server端应用程序SharedBufferServer和Client端应用程序SharedBufferClient了。
           至此,我们就可以运行模拟器来验证我们的程序了。关于如何在Android源代码工程中运行模拟器,请参考在Ubuntu上下载、编译和安装Android最新源代码一文。
           执行以下命令启动模拟器:

    USER-NAME@MACHINE-NAME:~/Android$ emulator  
           模拟器运行起来后,就可以通过adb shell命令连上它:

    USER-NAME@MACHINE-NAME:~/Android$ adb shell  
           最后,进入到/system/bin/目录下:

    luo@ubuntu-11-04:~/Android$ adb shell
    root@android:/ # cd system/bin 
           进入到/system/bin/目录后,首先在后台中运行Server端应用程序SharedBufferServer:

    root@android:/system/bin # ./SharedBufferServer &
           然后再在前台中重复运行Client端应用程序SharedBufferClient,以便验证程序的正确性:

    root@android:/system/bin # ./SharedBufferClient                                
    The value of the shared buffer is 0.
    Add value 1 to the shared buffer.
    root@android:/system/bin # ./SharedBufferClient                                
    The value of the shared buffer is 1.
    Add value 1 to the shared buffer.
    root@android:/system/bin # ./SharedBufferClient                                
    The value of the shared buffer is 2.
    Add value 1 to the shared buffer.
    root@android:/system/bin # ./SharedBufferClient                                
    The value of the shared buffer is 3.
    Add value 1 to the shared buffer.
           如果我们看到这样的输出,就说明我们成功地在Server端应用程序SharedBufferServer和Client端应用程序SharedBufferClietn共享内存中的数据了。

           至此,Android系统匿名共享内存的C++调用接口MemoryHeapBase和MemoryBase就分析完成了。

    老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

    展开全文
  • 上篇解释了闭包为什么会占用过多的内存。这一篇来讲如何使用闭包可以减少闭包对内存的占用。 闭包的作用域链中包含了外部的包含函数的活动对象。 通过解除对匿名函数的引用,可以将匿名函数占用的内存安全释放。 ...
  • 关于“Linux 中每个 TCP 连接占用多少内存”这一问题,中文网络上流传一种说法:TCP 连接建立的时候会分配接收缓冲区和发送缓冲区,各 4KB,一共是 8KB。如果加上 TCP 协议控制块(protocol control block)的 2KB,...
  • halcon/c++接口基础 之内存管理

    千次阅读 2016-08-31 23:19:18
    所有的HALCON类,不仅仅HImage,HRegion,HTuple,HFramegrabber等等,还有面向过程的方法中使用的Hobject,都可以使用默认的析构器自动释放内存。 ( see also section 2.4 “Destructors and Halcon Operators”))...
  • 全面理解Java内存模型

    万次阅读 多人点赞 2016-09-21 18:39:21
    Java内存模型即Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。如果我们要想深入了解Java并发编程,就要先理解好Java内存...
  • 域套字通信域共享内存通信性能小小小小比较
  • 公司查询报表出现问题,只能通过月份进行筛选,得到的数据过于庞大,导致查询运行缓慢,并且插入数据库异常出错,内存过大。 想到的办法: 1,查询接口是否有其他参数控制条件,分批次获取 2,解决数据库插入问题...
  • JVM内存结构和Java内存模型别傻傻分不清了

    万次阅读 多人点赞 2020-03-04 20:53:30
    JVM内存结构和Java内存模型都是面试的热点问题,名字看感觉都差不多,网上有些博客也都把这两个概念混着用,实际上他们之间差别还是挺大的。 通俗点说,JVM内存结构是与JVM的内部存储结构相关,而Java内存模型是与多...
  • jvm 内存模型

    万次阅读 多人点赞 2019-08-15 14:28:51
    2. jvm 内存模型划分 根据JVM规范,JVM 内存共分为虚拟机栈,堆,方法区,程序计数器,本地方法栈五个部分。 程序计数器(线程私有): 是当前线程锁执行字节码的行号治时期,每条线程都有一个独立的程序计数器...
  • 尝试读取或写入受保护的内存。这通常指示其他内存已损坏
  • 8G内存可以建立多少个tcp长连接?

    千次阅读 2017-12-08 15:39:37
    说法一:tcp连接数上限其实受限于机器的内存,以8G内存为例,假设一个tcp连接需要占用的最小内存是8k(发送接收缓存各4k,当然还要考虑socket描述符),那么最大连接数为:8*1024*1024/8=1048576个,即约100万个tcp...
  • 1. 背景本文将介绍进程通信中的共享内存,信号量和套字方法。2. 共享内存共享内存是最快的IPC(进程间通信)方式。共享内存是一个程序向内存写数据,另一个程序读数据,共享内存牵扯到同步的问题,一般有三种方案...
  • C++ 内存池介绍与经典内存池的实现

    万次阅读 多人点赞 2015-11-01 00:04:38
    1.默认内存管理函数的不足利用默认的内存管理函数new/delete或malloc/free在堆上分配和释放内存会有一些额外的开销。系统在接收到分配一定大小内存的请求时,首先查找内部维护的内存空闲块表,并且需要根据一定的...
  • 地址: http://blog.csdn.net/hujkay ... 关键词:Windows,curl,ssl, visual c++ 2005, libcurl, https, openssl, 内存泄露 时间: 2014/3/3 精灵族都是高富帅和白富美~~~~~  
  • 内存测试入门

    千次阅读 2018-06-27 19:48:27
    1.1新手入门当软件实现了新功能后,准备发布版本前,往往需要进行一轮性能测试以确定没有性能问题,这类测试通常包括功能的流畅度,电量消耗和内存使用情况等。由于内存组成的复杂性,实际上并...下来我们以一个最...
  • 一文理解内存屏障

    千次阅读 多人点赞 2019-05-15 14:40:20
    内存屏障是硬件之上、操作系统或JVM之下,对并发作出的最后一层支持。向下是是硬件提供的...下来,阐述问题的产生原理,了解为什么需要内存屏障;然后,浅谈内存屏障的标准、厂商对内存屏障的支持,并以volat...
  • Java内存管理

    万次阅读 多人点赞 2016-03-08 21:36:06
    前一段时间粗略看了一下《深入Java虚拟机 第二版》,可能是...下来一起学习下Java内存管理吧。请注意上图的这个:我们来复习下进程与线程吧:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进
  • *内存泄漏和内存溢出

    千次阅读 2017-06-27 14:03:20
    内存泄漏:分配出去的内存无法回收(不再使用的对象或者变量仍占内存空间),在Java中内存泄漏就是存在一些被分配的对象(可达的,却是无用的)无法被gc回收。 内存溢出:程序要求的内存超出了系统所能分配的范围...
  • JVM:图文详解Java虚拟机的内存结构

    万次阅读 多人点赞 2019-09-29 07:33:11
    本文将全面讲解Java虚拟机中的内存模型 & 分区,希望你们会喜欢
  • 共享内存

    千次阅读 2018-10-09 17:34:43
    共享内存是常用的进程之间的通信,两个进程可以直接共享访问同一块内存区域 一:共享内存的实现步骤如下: (1)创建共享内存区 进程1通过操作系统提供的API从内存中申请一块共享区域,Linux系统中可以通过...
  • 近日遇到一个关于打印机共享的问题在xp下安装的打印机,已经设置共享,局域网内的其他XP系统可以连接到这个网络打印机打印文档但是同局域网下的vista系统不能连接,每次连接都显示“内存不足,连接失败”或者“操作...
  • 内存泄漏和内存溢出

    万次阅读 多人点赞 2018-08-22 15:28:58
    内存溢出:(out of memory)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。 内存泄漏:(Memory Leak)是指程序中己动态分配...
  • JVM 内存泄漏 虚拟机内存泄漏总结

    万次阅读 2018-09-01 10:14:56
    内存泄漏 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。 一 . 以发生的方式来分类,内存泄漏可以分为4类:  ...
  • java内存模型

    千次阅读 多人点赞 2018-11-09 13:09:55
    java内存模型 下图就是java内存模型,但是一般讨论...下来介绍下图中两个线程内存分配的概念。 java里的堆是运行时的数据区,堆是由垃圾回收来负责的,堆的优势是可以动态的分配内存大小,生存期也不必事先告...
  • android 内存溢出 和 内存泄漏

    千次阅读 2018-05-21 17:05:28
    内存溢出(out of memory) :是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory(OOM)那就是内存溢出。内存溢出也就是内存不够用。内存溢出原因: 1.Android系统为每个应用程序申请到的内存有限,...
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-08-31 Linux-4.7 X86 & arm ... Linux内存管理 http://blog.csdn.net/vanbreaker/article/details/75799411 前景回顾前面我们讲到服务器体系(SMP, NUMA, M

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,330,836
精华内容 932,334
关键字:

内存再接