现在的位置: 首页 > 综合 > 正文

PHP源码分析之内存管理

2013年10月10日 ⁄ 综合 ⁄ 共 4817字 ⁄ 字号 评论关闭

欢迎转载,转载请注明出处http://blog.csdn.net/hackooo/article/details/8702156 谢谢!新浪微博:小灰马

主要内容

1.源码阅读
2.动态内存分配之边界标识法
3.PHP内存管理浅析

1.源码阅读

cscope

cscope 是一个 C 语言的浏览工具,通过这个工具可以很方便地找到某个函数或变量的定义位置、被调用的位置等信息。目前支持C/C++,java


cscope的简单使用步骤:
1.生成索引文件:cscope -Rbkq 生成三个文件:cscope.out,cscope.in.out, cscope.po.out(可在源码目录生成,也可以自己制定文件列表)
2.导入:打开源码文件,敲命令:cscope addcscope.out的路径,这样就导入完成了
3.命令使用:
  :cs help
  cs find[sgdctefi] [宏,函数名等]
  s: 查找C语言符号,即查找函数名、宏、枚举值等出现的地方
  g: 查找函数、宏、枚举等定义的位置,类似ctags所提供的功能
  d: 查找本函数调用的函数
  c: 查找调用本函数的函数
  t: 查找指定的字符串
  e: 查找egrep模式,相当于egrep功能,但查找速度快多了
  f: 查找并打开文件,类似vim的find功能
  i: 查找包含本文件的文


2.动态内存分配之边界标识法


2.1概述:边界标识法是动态存储分配的一种算法。它的特点就是,在内存块的头部和底部分别设上标识。这样,在回收内存块的时候,更容易判断物理位置上,在它前面和后面的内存块的空闲占用情况,以便合并成一个尽可能大的内存块。
    在许多动态内存分配程序中,把内存划成大小不同的块,并用指针组成链表,这样,链表相邻的内存块在物理空间上很大可能是不相邻的,这样,合并相邻内存块组成更大内存块,单靠链表的操作显然是性能十分低下的。 网上对边界标识法有多种版本,不过总体思路是一致的。

2.2一种简单的例子:



3.php的内存管理浅析

3.1内存大小的划分
        php的内存管理按处理的内存大小把内存分为三类,段segment,大块block,小块block。
段segment的大小>大块block>小块block,三者的关系如图所示:
        段的大小相对比较大,所以一般段的申请不是特别频繁,减少开销,而申请小块内存的概率相对来说较大,而且其大小相对来说比较常用到,所以要求它的申请和释放要高效。
三种结构体:
typedef struct _zend_mm_segment {
    size_t  size;
    struct _zend_mm_segment *next_segment;
} zend_mm_segment;

//大块内存小块内存都有的头部  zend_mm_block_info
typedef struct _zend_mm_block_info {
#if ZEND_MM_COOKIES
    size_t _cookie;
#endif
    size_t _size;
    size_t _prev;
} zend_mm_block_info;

//小块内存:zend_mm_small_free_block

typedef struct _zend_mm_small_free_block {
    zend_mm_block_info info;
#if ZEND_DEBUG
    unsigned int magic;
# ifdef ZTS
    THREAD_T thread_id;
# endif
#endif
    struct _zend_mm_free_block *prev_free_block;
    struct _zend_mm_free_block *next_free_block;
} zend_mm_small_free_block;

       小块内存:zend_mm_small_free_block逻辑结构图
小块内存每一块都是8字节的倍数(32位系统下),16,24...,256,264,共32个bucket,每个bucket下的双向循环队列里的block大小相等。所以任意给一个不大于264的整数size,很容易就能算出它跟8对齐后应该放到哪个bucket里。 
//大块内存:zend_mm_free_block
typedef struct _zend_mm_free_block {
	zend_mm_block_info info;
#if ZEND_DEBUG
	unsigned int magic;
# ifdef ZTS
	THREAD_T thread_id;
# endif
#endif
	struct _zend_mm_free_block *prev_free_block;
	struct _zend_mm_free_block *next_free_block;

	struct _zend_mm_free_block **parent;
	struct _zend_mm_free_block *child[2];
} zend_mm_free_block

        大块内存:zend_mm_free_block的逻辑结构图



        大块内存里面每个桶里面放的内存大小是一个范围,如下表所示:

字节范围

范围大小(多少个节点)

箱号

[256, 384) 左子树(实际上应该加个头部,比256大,这里只取256是为了说明其基本原理)

二进制    [0000 0000 0000 0000 0000 0001 0000 0000

至            0000 0000 0000 0000 0000 0001 0111 1111)   

128

8

[384, 512) 右子树

二进制    [0000 0000 0000 0000 0000 0001 1000 0000

至            0000 0000 0000 0000 0000 0001 1111 1111)

128

8

[512, 768)    依次类推

256

9

[768, 1024)

256

9

[1024, 1536)

512

……

……

……

……

……

……

[6291456, 8388608)

2097152

[8388608, 12582912)

4194304

31

[12582912, 与size_t相关)

二进制    [1100 0000 0000 0000 0000 0000 0000 0000

至            1111 1111 1111 1111 1111 1111 1111 1111)

与size_t相关

31

        以上几个结构都统一放在一个地方管理,那就是堆,heap
struct _zend_mm_heap {
    int                 use_zend_alloc;
    void               *(*_malloc)(size_t);
    void                (*_free)(void*);
    void               *(*_realloc)(void*, size_t);
    size_t              free_bitmap;  //小块内存的32个头结点里面是否有空闲几点的标记位图,每一位标记一个头结点
    size_t              large_free_bitmap;//大块内存的位图,类似小块内存位图
    size_t              block_size;    //申请一个大块内存的大小
    size_t              compact_size;
    zend_mm_segment    *segments_list;  //段队列
    zend_mm_storage    *storage;
    size_t              real_size; //一些内存的统计数据,包括峰值等
    size_t              real_peak;
    size_t              limit;     //堆最大能使用的内存限制
    size_t              size;      //已经用了多少堆的内存空间
    size_t              peak;
    size_t              reserve_size;//保留空间的大小,保留的空间用于记录一些错误日志报告等
    void               *reserve;
    int                 overflow;
    int                 internal;
#if ZEND_MM_CACHE
    unsigned int        cached;  //已经缓存的内存空间的大小
    zend_mm_free_block *cache[ZEND_MM_NUM_BUCKETS];//结构类似小块内存,做缓存用
#endif
    zend_mm_free_block *free_buckets[ZEND_MM_NUM_BUCKETS*2];  //小块内存的队列
    zend_mm_free_block *large_free_buckets[ZEND_MM_NUM_BUCKETS];  //大块内存的队列
    zend_mm_free_block *rest_buckets[2];//这其实应该也是缓存!
    int                 rest_count;
#if ZEND_MM_CACHE_STAT
    struct {
        int count;
        int max_count;
        int hit;
        int miss;
    } cache_stat[ZEND_MM_NUM_BUCKETS+1];//cache的统计信息,前面32个用来统计命中缓存对应index的数目,最后一个用来统计命中小块内存的数目,看看_zend_mm_alloc_int就知道啦
#endif
};

3.2申请及释放内存的源码分析
        内存申请基本思路
        在给定的heap中申请内存,申请内存基本是从小到大找最合适的!申请顺序:cache,small_free_block,large_free_block,找不着就找备胎rest,还没找着,就只能找申请segment一大段来解决了,这时候会清空cache去试图合并大块内存,但heap所有的内存大小不得超过limit.这里还有个保留着的storage,其实是个容器的概念,php里面把它保留着方便扩展。

        基本步骤

1.计算申请的size实际得占用的内存块大小true_size

2. 如果是小块内存

    2.1 到cache队列找找有木有匹配的,有的话把第一个合适的节点摘下来,返回,否则下一步

    2.2 到heap->free_buckets里找最接近的块,成功直接返回,否则下一步

3.如果申请的不是小块内存,到heap->large_free_buckets找最合适的

    3.1  如果找着合适的,就把它从树上摘下来,进行【找着处理】

    3.2  如果在large_free_buckets没找着,而且发现heap->real_size接近heap->limit了,那说明可分配的内存快用完了,那就去备胎中心rest_buckets找一个大小最接近的块,进行【找着处理】,否则下一步。

    3.3  尝试去堆里分配一块segment出来,segment_size<=heap->block_size。分配成功的话,把新的segment加入到原来heapd的segment队列里,进行【找着处理】,否则清空cache,申请失败,拜拜!
    3.4【找着处理】大块内存分配的时候,如果剩余的内存够下一次分配,就把剩余内存作为一个新的节点接回去对应大小的空闲队列里面,如果不够进行下一次分配,就直接把整块内存给申请者。注意返回的是可供程序使用的地址,而不是实际块的头部地址。

        内存释放基本步骤
1.根据传递的数据指针找到对应的block指针,计算block大小
2.如果是小块内存,优先放入heap->cache队列(头插法)
3.如果释放的内存块物理上临近的next block是空闲的,则将它们合并
4.如果释放的内存块物理上临近的prev block是空闲的,则将它们合并
5.如果这个block(可以是合并完的)的大小达到一个segment了,释放这个segment,否则,直接挂到相应大小的空闲队列上去。
我写的源码注释版:http://note.youdao.com/share/?id=0e99223294f2aa7b6523125a3a8bb29a&type=note  由于源码比较多,打开有点慢,好几秒才出来,耐心等待。
欢迎转载,转载请注明出处http://blog.csdn.net/hackooo/article/details/8702156 谢谢!新浪微博:小灰马

抱歉!评论已关闭.