Jemalloc malloc的分配过程,以redis的第一次内存分配malloc(1)展开

(gdb) bt
#0  je_malloc (size=1) at src/jemalloc.c:1422
#1  0x00000000004316b3 in zmalloc (size=1) at zmalloc.c:125
#2  0x000000000043197c in zstrdup (s=0x6cf8c5 "") at zmalloc.c:215
#3  0x000000000042824c in initServerConfig () at server.c:1476
#4  0x000000000042e55a in main (argc=1, argv=0x7fffffffe408) at server.c:3984

je_malloc
ret = imalloc_body(size, &tsd, &usize);
    imalloc_body
    *tsd = tsd_fetch();
    if (config_stats
        *usize = s2u(size); 调整大小为8
    imalloc(*tsd, size)
        iallocztm(tsd, size, false, tcache_get(tsd, true), false, NULL)
            先执行tcache_get(tsd, true)
                tcache = tsd_tcache_get(tsd); 返回tsd的tcache字段
                if (unlikely(tcache == NULL) && tsd_nominal(tsd)) { 第一次tcache为NULL
                    tcache = tcache_get_hard(tsd);
                        if (!tcache_enabled_get()) {
                            ...
                        }
                            tsd = tsd_fetch();
                            tcache_enabled = tsd_tcache_enabled_get(tsd);返回tsd的tcache_enabled字段
                            if (tcache_enabled == tcache_enabled_default)
                                tcache_enabled = (tcache_enabled_t)opt_tcache; true->1->tcache_enabled_true
                                tsd_tcache_enabled_set(tsd, tcache_enabled);
                        
                        arena = arena_choose(tsd, NULL);
                            unlikely((ret = tsd_arena_get(tsd)) == NULL)第一次为NULL,得到tsd的arena字段
                                ret = arena_choose_hard(tsd);
                                    malloc_mutex_lock(&arenas_lock);
                                    narenas_auto = 8
                                    找到第一个空的null,a0 != NULL,所以第一次是a1,但是不一定选a1,因为a0当前线程数是0
                                    arenas[choose]->nthreads == 0 || first_null == narenas_auto
                                        ret = arenas[choose];
                                    else
                                        choose = first_null;
                                        ret = arena_init_locked(choose);
                                    
                                    arena_bind_locked(tsd, choose);
                                        arena = arenas[ind];
                                        arena->nthreads++;
                                        
                                        tsd_arena_set(tsd, arena);设置tsd的arena字段
                                        
                                    malloc_mutex_unlock(&arenas_lock);
                                    
                        tcache_create(tsd, arena)
                            ((size_t)&(((tcache_t *)0)->tbins)) = 32 sizeof(tcache_bin_t) 32 je_nhbins 44
                            size = offsetof(tcache_t, tbins) + (sizeof(tcache_bin_t) * nhbins); 1440
                            stack_offset = size
                            size += stack_nelms * sizeof(void *); 3696 * 8 = 29568 31008
                            
                            (gdb) p je_large_maxclass
                            $32 = 1835008
                            (gdb) p je_chunksize
                            $33 = 2097152
                            
                            size = sa2u(size, CACHELINE);调整大小,32768
                            
                            tcache = ipallocztm(tsd, size, CACHELINE, true, false, true, a0get()); tcache = false = 0 = NULL
                                ret = arena_palloc(tsd, arena, usize, alignment, zero, tcache);arena = a0get
                                    ret = arena_malloc(tsd, arena, usize, zero, tcache);
                                        arena = arena_choose(tsd, arena);
                                        
                                        if (likely(tcache != NULL) && size <= tcache_maxclass) {
                                            return (tcache_alloc_large(tsd, arena, tcache, size, zero));
                                        } else
                                            return (arena_malloc_large(arena, size, zero));
                                                arena_run_t *run = arena_run_alloc_large(arena, usize + large_pad, zero);
                                                    32768 + 4096 = 36864
                                                    run = arena_run_alloc_large_helper(arena, size, zero);
                                                        arena_run_t *run = arena_run_first_best_fit(arena, s2u(size));
                                                            size_t search_size = run_quantize_first(size);
                                                            arena_chunk_map_misc_t *key = arena_miscelm_key_create(search_size);
                                                                return ((arena_chunk_map_misc_t *)
                                                                    (arena_mapbits_size_encode(size) | CHUNK_MAP_KEY));
                                                                    mapbits = size << CHUNK_MAP_SIZE_SHIFT; 53248 << 1
                                                                把size转换成1个地址
                                                                
                                                            arena_chunk_map_misc_t *miscelm =
                                                                arena_avail_tree_nsearch(&arena->runs_avail, key);    
                                                                在arena->runs_avail上面查找key大小
                                                                找不到,返回NULL
                                                                
                                                        if (run != NULL) {
                                                            if (arena_run_split_large(arena, run, size, zero))
                                                                run = NULL;
                                                        }
                                                        return (run);
                                                    chunk = arena_chunk_alloc(arena);
                                                        chunk = arena_chunk_init_hard(arena);
                                                            chunk = arena_chunk_alloc_internal(arena, &zero, &commit);
                                                                chunk = chunk_alloc_cache(arena, &chunk_hooks, NULL, chunksize, chunksize, zero, true);
                                                                    尝试从cache中分配
                                                                    ret = chunk_recycle(arena, chunk_hooks, &arena->chunks_szad_cached,
                                                                        &arena->chunks_ad_cached, true, new_addr, size, alignment, zero,
                                                                        &commit, dalloc_node);
                                                                        
                                                                        malloc_mutex_lock(&arena->chunks_mtx);
                                                                        
                                                                        chunk_hooks_assure_initialized_locked(arena, chunk_hooks);
                                                                            给chunk_hooks赋值,arena->chunk_hooks,arena_new时会赋值
                                                                            chunk_hooks_default: chunk_alloc_default...
                                                                        node = chunk_first_best_fit(arena, chunks_szad, chunks_ad, alloc_size);    
                                                                            extent_node_init(&key, arena, NULL, size, false, false);
                                                                            创建一个node代表大小,去树上查找
                                                                            return (extent_tree_szad_nsearch(chunks_szad, &key));
                                                                        malloc_mutex_unlock(&arena->chunks_mtx);
                                                                        return (NULL);
                                                                cache中没有,真正分配
                                                                chunk = arena_chunk_alloc_internal_hard(arena, &chunk_hooks, zero, commit);
                                                                    malloc_mutex_unlock(&arena->lock);
                                                                    
                                                                    chunk = (arena_chunk_t *)chunk_alloc_wrapper(arena, chunk_hooks, NULL,
                                                                        chunksize, chunksize, zero, commit);
                                                                        
                                                                        ret = chunk_hooks->alloc(new_addr, size, alignment, zero, commit,
                                                                            arena->ind);
                                                                            ret = chunk_alloc_core(arena, new_addr, size, alignment, zero,
                                                                                                    commit, arena->dss_prec);
                                                                                dss_prec=dss_prec_secondary
                                                                                /* Retained. */
                                                                                chunk_recycle
                                                                                /* "primary" dss. */
                                                                                chunk_alloc_dss(arena, new_addr, size, alignment, zero, commit)
                                                                                /*
                                                                                 * mmap.  Requesting an address is not implemented for
                                                                                 * chunk_alloc_mmap(), so only call it if (new_addr == NULL).
                                                                                 */
                                                                                chunk_alloc_mmap(size, alignment, zero, commit)
                                                                        
                                                                    arena_chunk_register(arena, chunk, *zero)
                                                                        extent_node_init(&chunk->node, arena, chunk, chunksize, zero, true);
                                                                            给node的各个字段赋值
                                                                        extent_node_achunk_set(&chunk->node, true);
                                                                        return (chunk_register(chunk, &chunk->node));
                                                                            rtree_set(&chunks_rtree, (uintptr_t)chunk, node)
                                                                                key是chunk的地址,value是chunk的node字段
                                                                                chunks_rtree在chunk_boot中创建
                                                                                start_level = rtree_start_level(rtree, key);
                                                                                    key = chunk,140737326874624
                                                                                    start_level = rtree->start_level[lg_floor(key) >> LG_RTREE_BITS_PER_LEVEL];
                                                                                        RTREE_HEIGHT_MAX = 4
                                                                                        unsigned        start_level[RTREE_HEIGHT_MAX];
                                                                                            2 2 1 0
                                                                                        rtree_level_t        levels[RTREE_HEIGHT_MAX];
                                                                                        je_lg_floor(key) = 46
                                                                                        46 >> 4 = 2
                                                                                        start_level = 1
                                                                                node = rtree_subtree_read(rtree, start_level);
                                                                                    subtree = rtree_subtree_tryread(rtree, level);
                                                                                        if (!rtree_node_valid(subtree))看subtree的地址是否比0x1大
                                                                                            subtree = atomic_read_p(&rtree->levels[level].subtree_pun);
                                                                                                return ((void *)atomic_add_uint64((uint64_t *)p, (uint64_t)x));
                                                                                                    atomic_add_p(p, NULL)交换并相加
                                                                                                        uint64_t t = x;
                                                                                                        asm volatile (
                                                                                                            "lock; xaddq %0, %1;" 交换t(寄存器的值)和*p(内存),二者和送入*p
                                                                                                            : "+r" (t), "=m" (*p) /* Outputs. */
                                                                                                            : "m" (*p) /* Inputs. */
                                                                                                            );

                                                                                                        return (t + x);
                                                                                                        
                                                                                        return (subtree);
                                                                                    if (unlikely(!rtree_node_valid(subtree)))
                                                                                        subtree = rtree_subtree_read_hard(rtree, level);
                                                                                            第一次,创建
                                                                                            rtree_node_init(rtree, level, &rtree->levels[level].subtree)
                                                                                                atomic_cas_p((void **)elmp, NULL, RTREE_NODE_INITIALIZING)
                                                                                                    atomic_cas_uint64((uint64_t *)p, (uint64_t)c, (uint64_t)s)
                                                                                                        uint8_t success;
                                                                                                        
                                                                                                        比较ax寄存器和*p(内存),
                                                                                                            如果相等,s(寄存器)的值覆盖*p,zf置1
                                                                                                            如果不等,*p的值覆盖ax,zf置0
                                                                                                        设置ax寄存器的值(success的值)为zf
                                                                                                            如果相等,即开始*p == 0, 之后*p = s, success = 1,返回0,
                                                                                                            不等success=0,返回1
                                                                                                        asm volatile (
                                                                                                            "lock; cmpxchgq %4, %0;"
                                                                                                            "sete %1;"
                                                                                                            : "=m" (*p), "=a" (success) /* Outputs. */
                                                                                                            : "m" (*p), "a" (c), "r" (s) /* Inputs. */
                                                                                                            : "memory" /* Clobbers. */
                                                                                                            );

                                                                                                        return (!(bool)success);
                                                                                                node = rtree->alloc(ZU(1) << rtree->levels[level].bits);
                                                                                                    rtree->levels[level].bits = 16
                                                                                                    static rtree_node_elm_t * chunks_rtree_node_alloc(size_t nelms)
                                                                                                        ((rtree_node_elm_t *)base_alloc(nelms * sizeof(rtree_node_elm_t)));
                                                                                                            (1<<16) * 8 = 512k
                                                                                                            extent_node_init(&key, NULL, NULL, usize, false, false);
                                                                                                            malloc_mutex_lock(&base_mtx);
                                                                                                            node = extent_tree_szad_nsearch(&base_avail_szad, &key);
                                                                                                                树上查找指定大小的node
                                                                                                                if (node != NULL) {
                                                                                                                    /* Use existing space. */找到了先移除
                                                                                                                    extent_tree_szad_remove(&base_avail_szad, node);
                                                                                                                } else {
                                                                                                                    /* Try to allocate more space. */
                                                                                                                    node = base_chunk_alloc(csize);没找到则分配
                                                                                                                }
                                                                                                            ret = extent_node_addr_get(node);
                                                                                                            if (extent_node_size_get(node) > csize) {
                                                                                                                extent_node_addr_set(node, (void *)((uintptr_t)ret + csize));
                                                                                                                extent_node_size_set(node, extent_node_size_get(node) - csize);
                                                                                                                extent_tree_szad_insert(&base_avail_szad, node);
                                                                                                                得到部分内存,剩余创建一个node,放回树中
                                                                                                            }
                                                                                                            malloc_mutex_unlock(&base_mtx);
                                                                                                            return ret;    
                                                                                                if (node == NULL)
                                                                                                    return (NULL);
                                                                                                atomic_write_p((void **)elmp, node);
                                                                                                    atomic_write_uint64((uint64_t *)p, (uint64_t)x);
                                                                                                        交换寄存器变量x和内存*p的值,即把node的地址赋给*elmp
                                                                                                        asm volatile (
                                                                                                            "xchgq %1, %0;" /* Lock is implied by xchgq. */
                                                                                                            : "=m" (*p), "+r" (x) /* Outputs. */
                                                                                                            : "m" (*p) /* Inputs. */
                                                                                                            : "memory" /* Clobbers. */
                                                                                                            );
                                                                                                返回node地址        
                                                                                    return (subtree);
                                                                                for (i = start_level; /**/; i++, node = child) {
                                                                                    subkey = rtree_subkey(rtree, key, i);
                                                                                        ((key >> ((ZU(1) << (LG_SIZEOF_PTR+3)) - rtree->levels[level].cumbits))
                                                                                            & ((ZU(1) << rtree->levels[level].bits) - 1));
                                                                                        1. 1 << 6 = 64
                                                                                        2. 64 - rtree->levels[1].cumbits = 64 - 32 = 32
                                                                                        3. key >> 32 = 32767
                                                                                        4. 1 << rtree->levels[1].bits = 1 << 16 = 65536
                                                                                        5. 32767 & (2^16 - 1) = 32767
                                                                                        第二次循环,level==2,rtree->levels[level].cumbits = 43
                                                                                        rtree->levels[1].bits = 11
                                                                                        (key >> (64 - 43)) & 2047 = 1971
                                                                                    if (i == rtree->height - 1) { rtree->height = 3
                                                                                        /* 第二次循环,进入这里
                                                                                         * node is a leaf, so it contains values rather than
                                                                                         * child pointers.
                                                                                         */这时的node是第一次的child,subkey = 1971
                                                                                        rtree_val_write(rtree, &node[subkey], val);
                                                                                            atomic_write_p(&elm->pun, val);
                                                                                                xchgq指令,交换内存和寄存器
                                                                                        return (false);
                                                                                    }
                                                                                    assert(i + 1 < rtree->height);
                                                                                    child = rtree_child_read(rtree, &node[subkey], i);
                                                                                        rtree_node_elm_t *child;

                                                                                        child = rtree_child_tryread(elm);
                                                                                            child = elm->child;
                                                                                            if (!rtree_node_valid(child)) child的地址是否比1大
                                                                                                child = atomic_read_p(&elm->pun); 和NULL交换,并相加
                                                                                            return (child); 0x0
                                                                                        if (unlikely(!rtree_node_valid(child)))
                                                                                            child = rtree_child_read_hard(rtree, elm, level);
                                                                                                return rtree_node_init(rtree, level, &elm->child)
                                                                                                    level = 1,
                                                                                                上面已经分析过,创建大小为ZU(1) << rtree->levels[level].bits的rtree_node_elm_t数组
                                                                                                起始地址写在elm->child中,并返回
                                                                                        return (child);
                                                                                    if (child == NULL)
                                                                                        return (true);
                                                                                }
                                                                    malloc_mutex_lock(&arena->lock);
                                                                    return (chunk);
                                                                arena->stats.mapped += chunksize; 2M
                                                                arena->stats.metadata_mapped += (map_bias << LG_PAGE); 13 * 2 ^12 = 52k
                                                            flag_unzeroed = (zero || !commit) ? 0 : CHUNK_MAP_UNZEROED;
                                                                0
                                                            flag_decommitted = commit ? 0 : CHUNK_MAP_DECOMMITTED;
                                                                0
                                                            arena_mapbits_unallocated_set(chunk, map_bias, arena_maxrun,
                                                                flag_unzeroed | flag_decommitted); pageind = 13, 0x1f3000 1996k 留了4k一页
                                                                size_t *mapbitsp = arena_mapbitsp_get(chunk, pageind);
                                                                    return (&arena_bitselm_get(chunk, pageind)->bits);
                                                                        return (&chunk->map_bits[pageind-map_bias]); 0
                                                                arena_mapbitsp_write(mapbitsp,
                                                                    arena_mapbits_size_encode(size) | CHUNK_MAP_BININD_INVALID | flags);
                                                                    mapbits = size << CHUNK_MAP_SIZE_SHIFT; CHUNK_MAP_SIZE_SHIFT = 1
                                                                        0x3e6000
                                                                    CHUNK_MAP_BININD_INVALID = 0x1fe0
                                                                    *mapbitsp = mapbits; 0x3e7fe0
                                                            
                                                            arena_mapbits_unallocated_set(chunk, chunk_npages-1, arena_maxrun, flag_unzeroed);
                                                                pageind = 511
                                                                &chunk->map_bits[pageind-map_bias] 511 - 13 = 498
                                                                0x3e7fe0
                                                            return (chunk);
                                                        arena_avail_insert(arena, chunk, map_bias, chunk_npages-map_bias);
                                                            13 512 - 13 = 499
                                                            (0x3e7fe0 >> 12) >> 1 = 499
                                                            arena_avail_tree_insert(&arena->runs_avail, arena_miscelm_get(chunk, pageind));
                                                                ((arena_chunk_map_misc_t *)
                                                                    ((uintptr_t)chunk + (uintptr_t)map_misc_offset) + pageind - map_bias);
                                                                    chunk + 4096 + 13 - 13
                                                                分配了一个arena_chunk_map_misc_t放到arena->runs_avail树中
                                                                
                                                        return (chunk);
                                                    if (chunk != NULL) {
                                                        run = &arena_miscelm_get(chunk, map_bias)->run;
                                                            得到chunk + 4096 + 13 - 13位置的arena_chunk_map_misc_t的run字段
                                                        if (arena_run_split_large(arena, run, size, zero))
                                                            arena_run_split_large_helper(arena, run, size, true, zero)
                                                                36864
                                                                chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
                                                                    ((void *)((uintptr_t)(a) & ~chunksize_mask))
                                                                    run = (arena_run_t *) 0x7ffff6601010
                                                                    chunk = (arena_chunk_t *) 0x7ffff6600000
                                                                miscelm = arena_run_to_miscelm(run);
                                                                    arena_chunk_map_misc_t *miscelm =
                                                                        (arena_chunk_map_misc_t*)
                                                                        ((uintptr_t)run - offsetof(arena_chunk_map_misc_t, run));
                                                                    通过结构体字段地址得到结构体地址
                                                                run_ind = arena_miscelm_to_pageind(miscelm);
                                                                    arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(miscelm);
                                                                    size_t pageind = ((uintptr_t)miscelm - ((uintptr_t)chunk + map_misc_offset))
                                                                                    / sizeof(arena_chunk_map_misc_t) + map_bias;
                                                                        map_misc_offset = 4096
                                                                        0 + 13
                                                                        chunk_npages > pageind >= map_bias
                                                                flag_dirty = arena_mapbits_dirty_get(chunk, run_ind);
                                                                    mapbits = arena_mapbits_get(chunk, pageind);
                                                                        return (arena_mapbitsp_read(arena_mapbitsp_get(chunk, pageind)));
                                                                            &arena_bitselm_get(chunk, pageind)->bits
                                                                            &chunk->map_bits[pageind-map_bias] 13 - 13
                                                                        0x3e7fe0
                                                                    return (mapbits & CHUNK_MAP_DIRTY); 0x10
                                                                    0x0
                                                                flag_decommitted = arena_mapbits_decommitted_get(chunk, run_ind);
                                                                    mapbits & CHUNK_MAP_DECOMMITTED 0x04
                                                                    0x0
                                                                need_pages = (size >> LG_PAGE);0x9000>>12 = 9
                                                                arena_run_split_remove(arena, chunk, run_ind, flag_dirty, flag_decommitted, need_pages);
                                                                    total_pages = arena_mapbits_unallocated_size_get(chunk, run_ind) >> LG_PAGE;
                                                                        &chunk->map_bits[pageind-map_bias] 13 - 13
                                                                        (mapbits & CHUNK_MAP_SIZE_MASK) >> CHUNK_MAP_SIZE_SHIFT;
                                                                        CHUNK_MAP_SIZE_MASK = ~(((size_t)0xffU) << 5 | ((size_t)0x1cU) | ((size_t)0x3U))
                                                                            0xffffffffffffe000
                                                                        0x3e6000 >> 1 = 0x1f3000 1996k >> 12 = 499
                                                                rem_pages = total_pages - need_pages; 490
                                                                arena_avail_remove(arena, chunk, run_ind, total_pages);
                                                                    arena_avail_tree_remove(&arena->runs_avail, arena_miscelm_get(chunk, pageind));
                                                                    从树runs_avail上移除arena_chunk_map_misc_t
                                                                arena_cactive_update(arena, need_pages, 0);
                                                                    统计atomic_add_z(&stats_cactive, size);
                                                                        "lock; xaddq %0, %1;"交换相加
                                                                arena->nactive += need_pages;
                                                                
                                                                flags = 0
                                                                flag_unzeroed_mask = CHUNK_MAP_UNZEROED = 8
                                                                
                                                                arena_mapbits_unallocated_set(chunk, run_ind+need_pages,
                                                                    (rem_pages << LG_PAGE), flags |
                                                                    (arena_mapbits_unzeroed_get(chunk, run_ind+need_pages) &
                                                                    flag_unzeroed_mask));
                                                                    13 + 9 = 22, 490 << 12
                                                                    &chunk->map_bits[pageind-map_bias] 22 - 13 = 9
                                                                    
                                                                arena_mapbits_unallocated_set(chunk, run_ind+total_pages-1,
                                                                    (rem_pages << LG_PAGE), flags |
                                                                    (arena_mapbits_unzeroed_get(chunk, run_ind+total_pages-1) &
                                                                    flag_unzeroed_mask));
                                                                    13 + 499 - 1 = 511, 490 << 12
                                                                    &chunk->map_bits[pageind-map_bias] 511- 13 = 498
                                                                
                                                                arena_avail_insert(arena, chunk, run_ind+need_pages, rem_pages);
                                                                    pageind = 13 + 9 = 22, rem_pages = 490
                                                                    arena_avail_tree_insert(&arena->runs_avail, arena_miscelm_get(chunk, pageind));
                                                                        ((arena_chunk_map_misc_t *)((uintptr_t)chunk + (uintptr_t)map_misc_offset) + pageind-map_bias);
                                                                        arena_chunk_map_misc_t数组的[pageind-map_bias]元素
                                                                flag_unzeroed_mask = CHUNK_MAP_UNZEROED = 8
                                                                arena_mapbits_large_set(chunk, run_ind+need_pages-1, 0,
                                                                    flag_dirty |
                                                                    (flag_unzeroed_mask & arena_mapbits_unzeroed_get(chunk, run_ind+need_pages-1)));
                                                                    13 + 9 -1 = 21,21 -13 = 8
                                                                    size = 0,
                                                                    arena_mapbits_size_encode(size) | CHUNK_MAP_BININD_INVALID | flags | CHUNK_MAP_LARGE | CHUNK_MAP_ALLOCATED
                                                                    0x1fe0,或完0x1fe3
                                                                arena_mapbits_large_set(chunk, run_ind, size,
                                                                    flag_dirty |
                                                                    (flag_unzeroed_mask & arena_mapbits_unzeroed_get(chunk, run_ind)));
                                                                    13, 13 -13 = 0
                                                                    *mapbitsp = 0x3e7fe0旧值,mapbits & CHUNK_MAP_UNZEROED = 0
                                                                    size = 36864 0x9000
                                                                    size encode 0x12000
                                                                    0x12000,或完0x13fe3
                                                                return (false);
                                                            run = NULL;返回false,不会执行NULL
                                                        return (run);
                                                    }
                                                miscelm = arena_run_to_miscelm(run);
                                                    run字段得到结构体miscelm地址
                                                ret = (void *)((uintptr_t)arena_miscelm_to_rpages(miscelm) + random_offset);
                                                    arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(miscelm);
                                                    size_t pageind = arena_miscelm_to_pageind(miscelm);
                                                        得到chunk地址,miscelm - (chunk + offset),arena_chunk_map_misc_t数组第几个元素 + map_bias
                                                        size_t pageind = ((uintptr_t)miscelm - ((uintptr_t)chunk + map_misc_offset))
                                                                                    / sizeof(arena_chunk_map_misc_t) + map_bias;
                                                        13
                                                    return ((void *)((uintptr_t)chunk + (pageind << LG_PAGE)));
                                                        得到第13页的首地址
                                                    random_offset = 2880
                                                
                                                szind_t index = size2index(usize) - NBINS; 43 - 39 = 4
                                                    意思是NBINS之下是小内存分配,不在这里统计()
                                                arena->stats.nmalloc_large++;
                                                arena->stats.nrequests_large++;
                                                arena->stats.allocated_large += usize;
                                                arena->stats.lstats[index].nmalloc++;
                                                arena->stats.lstats[index].nrequests++;
                                                arena->stats.lstats[index].curruns++;
                                                
                                                malloc_mutex_unlock(&arena->lock);
                                                return (ret);
                                    if (config_cache_oblivious)
                                        ret = (void *)((uintptr_t)ret & ~PAGE_MASK);去掉了page零头
                                    return (ret);
                                arena_metadata_allocated_add(iaalloc(ret), isalloc(ret,config_prof));
                                    arena_aalloc(ptr) 后执行
                                        return (extent_node_arena_get(&chunk->node));
                                            je_extent_node_arena_get函数
                                            return (node->en_arena); arena_t *
                                    arena_salloc(ptr, demote) 先执行
                                        chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
                                        pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
                                            0x7ffff660d000 - 0x7ffff6600000
                                            13
                                        binind = arena_mapbits_binind_get(chunk, pageind);
                                            &chunk->map_bits[pageind-map_bias]
                                            0x13fe3
                                            binind = (mapbits & CHUNK_MAP_BININD_MASK) >> CHUNK_MAP_BININD_SHIFT;
                                            CHUNK_MAP_BININD_MASK = 0xff, CHUNK_MAP_BININD_SHIFT = 5
                                            取mapbits的5-12位,0xff
                                        ret = arena_mapbits_large_size_get(chunk, pageind) - large_pad
                                            &chunk->map_bits[pageind-map_bias] 13 -13
                                            0x13fe3
                                            size = (mapbits & CHUNK_MAP_SIZE_MASK) >> CHUNK_MAP_SIZE_SHIFT;
                                                (gdb) p /x (0x13fe3 & 0xffffffffffffe000)
                                                $338 = 0x12000
                                                (gdb) p /x (0x13fe3 & 0xffffffffffffe000) >> 1
                                                $339 = 0x9000
                                            0x9000 - 4096 = 0x8000
                                        arena_mapbits_dirty_get(chunk, pageind) == arena_mapbits_dirty_get(chunk, pageind+((ret+large_pad)>>LG_PAGE)-1)
                                            13 == 13 + 9 - 1 21
                                        return (ret);
                                    atomic_add_z(&arena->stats.metadata_allocated, size);
                                        (&chunk->node)->en_arena->stats.metadata_allocated
                                        xaddq交换并相加
                                return (ret);0x7ffff660d000
                            tcache_arena_associate(tcache, arena);
                                config_stats == 1
                                malloc_mutex_lock(&arena->lock);
                                ql_elm_new(tcache, link);
                                ql_tail_insert(&arena->tcache_ql, tcache, link); tcache保存在arena->tcache_ql队列
                                malloc_mutex_unlock(&arena->lock);
                            for (i = 0; i < nhbins; i++) 44 stack_offset = 1440,初始值
                                tcache->tbins[i].lg_fill_div = 1;
                                tcache->tbins[i].avail = (void **)((uintptr_t)tcache + (uintptr_t)stack_offset);
                                stack_offset += tcache_bin_info[i].ncached_max * sizeof(void *);
                            return tcache;
                    tcache_get_hard return
                    tsd_tcache_set(tsd, tcache);
                        tsd的tcache字段赋值
                    return (tcache);
                }
            ret = arena_malloc(tsd, arena, size, zero, tcache); size = 1
                arena = arena_choose(tsd, arena);
                    ret = tsd_arena_get(tsd)得到tsd的arena字段
                size <= SMALL_MAXCLASS tcache != NULL
                    SMALL_MAXCLASS = 10k以内都用tcache,large_maxclass = 0x1c0000 7 * 2^18 = 1792k(1M多),之上就是huge了
                    tcache_maxclass = 32k,只要小于32k,都可以用tcache
                    tcache_alloc_small(tsd, arena, tcache, size, zero) size = 1
                        binind = size2index(size); 0
                        usize = index2size(binind); 8
                        tbin = &tcache->tbins[binind];
                        ret = tcache_alloc_easy(tbin);
                            第一次tbin->ncached == 0
                            tbin->low_water = -1;
                            return (NULL);
                        ret = tcache_alloc_small_hard(tsd, arena, tcache, tbin, binind);
                            arena_tcache_fill_small(arena, tbin, binind, config_prof ? tcache->prof_accumbytes : 0);
                                arena_bin_t *bin = &arena->bins[binind];
                                malloc_mutex_lock(&bin->lock);
                                
                                for (i = 0, nfill = (tcache_bin_info[binind].ncached_max >> tbin->lg_fill_div); i < nfill; i++)
                                je_tcache_bin_info[binind].ncached_max = 200,tbin->lg_fill_div = 1
                                初始化i = 0, nfill = 200 >> 1 = 100,连续填充100个
                                    arena_run_t *run;
                                    void *ptr;
                                    if ((run = bin->runcur) != NULL && run->nfree > 0)
                                        ptr = arena_run_reg_alloc(run, &arena_bin_info[binind]);第二次填充就会调用,第一次下面也会调
                                    else
                                        ptr = arena_bin_malloc_hard(arena, bin); 第一次分配走这里
                                            binind = arena_bin_index(arena, bin);
                                                szind_t binind = bin - arena->bins;带类型指针相减,结果是偏移,0
                                            bin_info = &arena_bin_info[binind]; 全局的arena_bin_info
                                            bin->runcur = NULL;
                                            run = arena_bin_nonfull_run_get(arena, bin);
                                                run = arena_bin_nonfull_run_tryget(bin);
                                                    arena_run_t *run = arena_bin_runs_first(bin);
                                                        arena_chunk_map_misc_t *miscelm = arena_run_tree_first(&bin->runs);
                                                            bin->runs是个rb树,树中节点是arena_chunk_map_misc_t,
                                                            最小的元素就是地址最小的arena_chunk_map_misc_t
                                                            第一次为NULL
                                                    if (run != NULL) {
                                                        arena_bin_runs_remove(bin, run);
                                                        if (config_stats)
                                                            bin->stats.reruns++;
                                                    }
                                                    return (run)第一次返回NULL
                                                binind = arena_bin_index(arena, bin);
                                                bin_info = &arena_bin_info[binind];
                                                
                                                malloc_mutex_unlock(&bin->lock);是不是写反了
                                                malloc_mutex_lock(&arena->lock);
                                                
                                                run = arena_run_alloc_small(arena, bin_info->run_size, binind);
                                                    binind = 0,bin_info->run_size = 4096
                                                    run = arena_run_alloc_small_helper(arena, size, binind);
                                                        arena_run_t *run = arena_run_first_best_fit(arena, size);
                                                            size_t search_size = run_quantize_first(size);
                                                                size_t qsize = run_quantize(size);
                                                                    if (size <= small_maxrun && small_run_tab[size >> LG_PAGE])
                                                                        7k,small_run_tab[1] = true
                                                                        return (size);
                                                                if (qsize < size) {
                                                                    /*
                                                                     * Skip a quantization that may have an adequately large run,
                                                                     * because under-sized runs may be mixed in.  This only happens
                                                                     * when an unusual size is requested, i.e. for aligned
                                                                     * allocation, and is just one of several places where linear
                                                                     * search would potentially find sufficiently aligned available
                                                                     * memory somewhere lower.
                                                                     */
                                                                    qsize = run_quantize_next(size);
                                                                }
                                                                return (qsize); 4096
                                                            arena_chunk_map_misc_t *key = arena_miscelm_key_create(search_size);
                                                                ((arena_chunk_map_misc_t *)(arena_mapbits_size_encode(size) | CHUNK_MAP_KEY));
                                                                4096 << 1 = 8k 0x2001 CHUNK_MAP_KEY=0x1
                                                            arena_chunk_map_misc_t *miscelm = arena_avail_tree_nsearch(&arena->runs_avail, key);
                                                                在arena->runs_avail中查找key
                                                                arena->runs_avail是个红黑树,树中节点类型是arena_chunk_map_misc_t
                                                                比较函数是arena_avail_comp,a,b比较,a有可能是key(根据CHUNK_MAP_KEY来区分),
                                                                如果是key,调用arena_miscelm_key_size_get(a)得到其大小,
                                                                    (mapbits & CHUNK_MAP_SIZE_MASK) >> CHUNK_MAP_SIZE_SHIFT;
                                                                    & 0xffffffffffffe000 >> 1
                                                                否则通过arena_miscelm_size_get()得到其大小,miscelm代表一个地址
                                                                    chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(miscelm); 去掉位数
                                                                    pageind = arena_miscelm_to_pageind(miscelm);
                                                                        (数组元素(miscelm)地址 - (chunk + 数组起始偏移))/sizeof(arena_chunk_map_misc_t)
                                                                        = 数组元素个数,几个元素,就是第几页,再加上map_bias,9 + 13 = 22
                                                                    mapbits = arena_mapbits_get(chunk, pageind);
                                                                        &chunk->map_bits[pageind-map_bias]
                                                                        *mapbitsp = 0x3d5fe0
                                                                        (mapbits & CHUNK_MAP_SIZE_MASK) >> CHUNK_MAP_SIZE_SHIFT;
                                                                    return (arena_mapbits_size_decode(mapbits));
                                                                        size = 0x1ea000
                                                                调用run_quantize,获取调整后的大小
                                                                    4096 --> 4096
                                                                    0x1ea000 --> 0x1c1000, large_pad = 4096
                                                                        qsize = index2size(size2index(size - large_pad + 1) - 1) + large_pad;
                                                            if (miscelm == NULL)
                                                                return (NULL);
                                                            return (&miscelm->run);能找到
                                                        if (run != NULL) {
                                                            if (arena_run_split_small(arena, run, size, binind))
                                                                chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
                                                                    去掉零头
                                                                miscelm = arena_run_to_miscelm(run);
                                                                    arena_chunk_map_misc_t *miscelm =
                                                                        (arena_chunk_map_misc_t*)
                                                                        ((uintptr_t)run - offsetof(arena_chunk_map_misc_t, run));
                                                                    通过字段地址得到结构体的地址
                                                                run_ind = arena_miscelm_to_pageind(miscelm);
                                                                    22
                                                                flag_dirty = arena_mapbits_dirty_get(chunk, run_ind);
                                                                    &chunk->map_bits[pageind-map_bias]
                                                                    0x3d5fe0 & 0x10U    
                                                                flag_decommitted = arena_mapbits_decommitted_get(chunk, run_ind);
                                                                    0x04U
                                                                
                                                                need_pages = (size >> LG_PAGE); 1
                                                                arena_run_split_remove(arena, chunk, run_ind, flag_dirty, flag_decommitted, need_pages);
                                                                    total_pages = arena_mapbits_unallocated_size_get(chunk, run_ind) >> LG_PAGE;
                                                                        0x3d5fe0 & CHUNK_MAP_SIZE_MASK >> CHUNK_MAP_SIZE_SHIFT >> 12
                                                                        后13位清0,0x3d4000,右移1位,0x1ea000,右移12位,0x1ea = 490
                                                                    rem_pages = total_pages - need_pages;
                                                                    arena_avail_remove(arena, chunk, run_ind, total_pages);
                                                                        arena_avail_tree_remove(&arena->runs_avail, arena_miscelm_get(chunk,pageind));
                                                                        从arena的runs_avail树中移除arena_chunk_map_misc_t节点
                                                                    arena_cactive_update(arena, need_pages, 0);
                                                                        ssize_t cactive_diff = CHUNK_CEILING((arena->nactive + add_pages
                                                                            - sub_pages) << LG_PAGE) - CHUNK_CEILING(arena->nactive <<
                                                                            LG_PAGE);
                                                                            CHUNK_CEILING(9 + 1  - 0 << 12)没有1个chunk大小,变成1个chunk大小
                                                                            两者都是0
                                                                        if (cactive_diff != 0)
                                                                            stats_cactive_add(cactive_diff);
                                                                    arena->nactive += need_pages;
                                                                    
                                                                    size_t flags = flag_dirty | flag_decommitted;
                                                                    size_t flag_unzeroed_mask = (flags == 0) ?  CHUNK_MAP_UNZEROED :0;
                                                                    arena_mapbits_unallocated_set(chunk, run_ind+need_pages, 1
                                                                        (rem_pages << LG_PAGE), flags |
                                                                        (arena_mapbits_unzeroed_get(chunk, run_ind+need_pages) &
                                                                        flag_unzeroed_mask));
                                                                        run_ind = 22, need_pages = 1, total_pages = 490
                                                                        &chunk->map_bits[ 23 - 13] 原来是0
                                                                        arena_mapbitsp_write(mapbitsp, arena_mapbits_size_encode(size) | CHUNK_MAP_BININD_INVALID | flags);
                                                                            size 0x1e9000,encode之后0x3d2000,或上之后0x3d3fe0,CHUNK_MAP_BININD_INVALID = 0x1fe0
                                                                    arena_mapbits_unallocated_set(chunk, run_ind+total_pages-1,
                                                                        (rem_pages << LG_PAGE), flags |
                                                                        (arena_mapbits_unzeroed_get(chunk, run_ind+total_pages-1) &
                                                                        flag_unzeroed_mask));
                                                                        arena_mapbitsp_write(mapbitsp, arena_mapbits_size_encode(size) | CHUNK_MAP_BININD_INVALID | flags);
                                                                            &chunk->map_bits[511 - 13]
                                                                            之前是0x3d5fe0
                                                                            CHUNK_MAP_UNZEROED = 0x08
                                                                            0x1e9000 0x3d3fe0
                                                                    if (flag_dirty != 0) {
                                                                        arena_run_dirty_insert(arena, chunk, run_ind+need_pages,
                                                                            rem_pages);
                                                                    }
                                                                    arena_avail_insert(arena, chunk, run_ind+need_pages, rem_pages);
                                                                        重新插入,重点是pageind是run_ind+need_pages,前移了,代表下一个空闲页
                                                                        arena_avail_tree_insert(&arena->runs_avail, arena_miscelm_get(chunk, pageind));
                                                                for (i = 0; i < need_pages; i++) {
                                                                    size_t flag_unzeroed = arena_mapbits_unzeroed_get(chunk, run_ind+i);
                                                                        &chunk->map_bits[pageind-map_bias] 22 - 13
                                                                        0x3d5fe0
                                                                        0
                                                                    arena_mapbits_small_set(chunk, run_ind+i, i, binind, flag_unzeroed);
                                                                        pageind = run_ind+i = 22, runind = binind = 0, i= 0,0
                                                                        &chunk->map_bits[22-13]
                                                                        
                                                                        arena_mapbitsp_write(mapbitsp, (runind << CHUNK_MAP_RUNIND_SHIFT) |
                                                                                (binind << CHUNK_MAP_BININD_SHIFT) | flags | CHUNK_MAP_ALLOCATED);
                                                                            0x1
                                                                    if (config_debug && flag_dirty == 0 && flag_unzeroed == 0) 条件不满足
                                                                        arena_run_page_validate_zeroed(chunk, run_ind+i);
                                                                }    
                                                                        
                                                            如果split失败    
                                                                run = NULL;
                                                        }
                                                        return (run);
                                                if (run != NULL) {
                                                    /* Initialize run internals. */
                                                    run->binind = binind; 0
                                                    run->nfree = bin_info->nregs; 512
                                                    bitmap_init(run->bitmap, &bin_info->bitmap_info);
                                                        run的bitmap数组,大小BITMAP_GROUPS_MAX = 9
                                                            #elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 2
                                                                9 <= 12
                                                            #  define BITMAP_GROUPS_MAX    BITMAP_GROUPS_2_LEVEL(BITMAP_MAXBITS)
                                                                BITMAP_MAXBITS 1 << 9 = 512
                                                                BITMAP_GROUPS_1_LEVEL(nbits) + BITMAP_GROUPS_L1(nbits)
                                                                BITMAP_GROUPS_L0(nbits) + BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(nbits))
                                                                BITMAP_BITS2GROUPS(nbits) + BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(nbits))
                                                                ((nbits + BITMAP_GROUP_NBITS_MASK) >> LG_BITMAP_GROUP_NBITS)
                                                                    (512 + 63) >> 6 = 8 (9 + 63) >> 6 = 1
                                                            8 + 1 = 9
                                                        memset(bitmap, 0xffU, binfo->levels[binfo->nlevels].group_offset <<    LG_SIZEOF_BITMAP);
                                                            {nbits = 512, nlevels = 2, levels = {{group_offset = 0}, {group_offset = 8}, {group_offset = 9}, {group_offset = 0}}}
                                                            9 << 3 = 9 * 8 = 72,9个long
                                                            初始化这个数组
                                                        extra = (BITMAP_GROUP_NBITS - (binfo->nbits & BITMAP_GROUP_NBITS_MASK))
                                                            & BITMAP_GROUP_NBITS_MASK;
                                                            (64 - (512 & 63)) & 63 = 0
                                                        if (extra != 0)
                                                            bitmap[binfo->levels[1].group_offset - 1] >>= extra;
                                                        for (i = 1; i < binfo->nlevels; i++) { 2
                                                            size_t group_count = binfo->levels[i].group_offset -
                                                                binfo->levels[i-1].group_offset; 8 - 0
                                                            extra = (BITMAP_GROUP_NBITS - (group_count &
                                                                BITMAP_GROUP_NBITS_MASK)) & BITMAP_GROUP_NBITS_MASK;
                                                                (64 - (8 & 63)) & 63 = 56
                                                            if (extra != 0)
                                                                bitmap[binfo->levels[i+1].group_offset - 1] >>= extra;
                                                                bitmap[9 - 1] >>= extra; 右移前,是-1,右移后是0xff
                                                        }
                                                }
                                                
                                                malloc_mutex_unlock(&arena->lock);
                                                malloc_mutex_lock(&bin->lock);
                                                
                                                if (run != NULL) {
                                                    if (config_stats) {
                                                        bin->stats.nruns++; 0->1
                                                        bin->stats.curruns++;
                                                    }
                                                    return (run);
                                                }
                                            bin->runcur = run;
                                            bin->runcur->nfree = 512
                                            return (arena_run_reg_alloc(bin->runcur, bin_info));
                                                regind = bitmap_sfu(run->bitmap, &bin_info->bitmap_info);
                                                    i = binfo->nlevels - 1; 2 - 1
                                                    g = bitmap[binfo->levels[i].group_offset]; 8
                                                        0xff,第一次第二次都一样
                                                    bit = jemalloc_ffsl(g) - 1;
                                                        __builtin_ffsl,返回右起第一个1的位置,从1开始,1 - 1 = 0
                                                    while (i > 0) {
                                                        i--;
                                                        g = bitmap[binfo->levels[i].group_offset + bit]; 0 + 0
                                                            -1,第二次-2
                                                        bit = (bit << LG_BITMAP_GROUP_NBITS) + (jemalloc_ffsl(g) - 1);
                                                            0,第二次bit = 0 + 1 = 1
                                                    }
                                                    bitmap_set(bitmap, binfo, bit);
                                                        goff = bit >> LG_BITMAP_GROUP_NBITS; 0,第二次1>>6 = 0
                                                            当bit=64时,goff就不等于0了,因为1个long就是64位
                                                        bitmap_t *gp = &bitmap[goff];
                                                        bitmap_t g = *gp;
                                                        g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);
                                                            (bit = 0)-2,第二次(bit = 1)g^(1 << (1 & 63)) = g ^ 2 = -4, (bit=2)-8, (bit=3)-16, (bit=4)-32
                                                            逐步蚕食
                                                            bit = 63,g只剩最高位1个1,和(size_t)1<<63相等,异或后为0
                                                        *gp = g;
                                                        /* Propagate group state transitions up the tree. */
                                                        if (g == 0) { 这里没走到,当bit=63时可以走到
                                                            unsigned i;
                                                            for (i = 1; i < binfo->nlevels; i++) { 2
                                                                bit = goff; 0, goff也可能为1
                                                                goff = bit >> LG_BITMAP_GROUP_NBITS; 0 >> 6,1>>6 = 0,除非原来的goff=64,新的goff=1
                                                                gp = &bitmap[binfo->levels[i].group_offset + goff]; bitmap[8]
                                                                g = *gp;
                                                                assert(g & (1LU << (bit & BITMAP_GROUP_NBITS_MASK)));
                                                                g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);
                                                                    0xff->0xfe
                                                                *gp = g; 改变bitmap[8]
                                                                if (g != 0)
                                                                    break;
                                                            }
                                                        }
                                                        这个函数改变了bitmap某个元素的值
                                                    return (bit); 返回0
                                                miscelm = arena_run_to_miscelm(run);
                                                    字段地址得到结构体地址
                                                rpages = arena_miscelm_to_rpages(miscelm);
                                                    return ((void *)((uintptr_t)chunk + (pageind << LG_PAGE))); 22
                                                    
                                                ret = (void *)(
                                                        (uintptr_t)rpages +
                                                        (uintptr_t)bin_info->reg0_offset + 0
                                                        (uintptr_t)(bin_info->reg_interval * regind) 8 * 0,第二次8 * 1
                                                    );
                                                run->nfree--;
                                                return (ret);
                                        end arena_bin_malloc_hard
                                    end else
                                    tbin->avail[nfill - 1 - i] = ptr; 100 - 1 - 0
                                end for
                                malloc_mutex_unlock(&bin->lock);
                                地址递减,因为是从大到小填充的
                                (gdb) p tbin->avail[0]
                                $245 = (void *) 0x7ffff6616318
                                (gdb) p tbin->avail[1]
                                $246 = (void *) 0x7ffff6616310
                                (gdb) p tbin->avail[2]
                                $247 = (void *) 0x7ffff6616308
                                (gdb) p tbin->avail[100]
                                $248 = (void *) 0x0
                                (gdb) p tbin->avail[99]
                                $249 = (void *) 0x7ffff6616000 rpages的地址
                                
                                tbin->ncached = i; 100
                            end arena_tcache_fill_small    
                            ret = tcache_alloc_easy(tbin);
                                tbin->ncached--;
                                ret = tbin->avail[tbin->ncached];
                            
                            return (ret);
                        end je_tcache_alloc_small_hard
                        
                        assert(tcache_salloc(ret) == usize);
                            由指针地址得到chunk地址,pageind,进而拿到pageind的arena_mapbits_large_get(chunk, pageind)
                            mapbits中包含binind,进而可以得到usize
                            binind = (mapbits & CHUNK_MAP_BININD_MASK) >> CHUNK_MAP_BININD_SHIFT;
                        tbin->tstats.nrequests++;
                        tcache_event(tsd, tcache);
                            tcache->ev_cnt++;
                            #define    TCACHE_GC_SWEEP            8192

                            /* Number of tcache allocation/deallocation events between incremental GCs. */
                            #define    TCACHE_GC_INCR                            \ 210 + 1
                                ((TCACHE_GC_SWEEP / NBINS) + ((TCACHE_GC_SWEEP / NBINS == 0) ? 0 : 1))
                            if (unlikely(tcache->ev_cnt == TCACHE_GC_INCR))
                                tcache_event_hard(tsd, tcache); do gc 这里没走到
                        return (ret);
                    end tcache_alloc_small
            end arena_malloc
            return ret
        end je_iallocztm
    end imalloc
    end imalloc_body
    
    *tsd_thread_allocatedp_get(tsd) += usize;
    tsd->thread_allocated += 8
    return ret
end je_malloc
    
update_zmalloc_stat_alloc(zmalloc_size(ptr));
    #define zmalloc_size(p) je_malloc_usable_size(p)
        isalloc(ptr, config_prof);
            arena_salloc(ptr, demote) demote = false
                上面已经分析过,为ret = index2size(binind); 8
    __sync_add_and_fetch(&used_memory, (__n))
return ptr;

end zmalloc




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值