鸿蒙轻内核M核源码分析系列九 动态内存Dynamic Memory 第二部分

举报
zhushy 发表于 2021/06/23 08:43:40 2021/06/23
【摘要】 2.2 初始化动态内存池我们分析下初始化动态内存池函数UINT32 LOS_MemInit(VOID *pool, UINT32 size)的代码。我们先看看函数参数,VOID *pool是动态内存池的起始地址,UINT32 size是初始化的动态内存池的总大小,size需要小于等于*pool开始的内存区域的大小,否则会影响后面的内存区域,还需要大于动态内存池的最小值OS_MEM_MIN_...

2.2 初始化动态内存池

我们分析下初始化动态内存池函数UINT32 LOS_MemInit(VOID *pool, UINT32 size)的代码。我们先看看函数参数,VOID *pool是动态内存池的起始地址,UINT32 size是初始化的动态内存池的总大小,size需要小于等于*pool开始的内存区域的大小,否则会影响后面的内存区域,还需要大于动态内存池的最小值OS_MEM_MIN_POOL_SIZE[pool, pool + size]不能和其他内存池冲突。

我们看下代码,⑴处对传入参数进行校验,⑵处对传入参数进行是否内存对齐校验,如果没有内存对齐会返回错误码。⑶处调用函数OsMemPoolInit()进行内存池初始化,这是初始化内存的核心函数。⑷处开启宏LOSCFG_MEM_MUL_POOL多内存池支持时,才会执行。

UINT32 LOS_MemInit(VOID *pool, UINT32 size)
{if ((pool == NULL) || (size <= OS_MEM_MIN_POOL_SIZE)) {
        return OS_ERROR;
    }if (((UINTPTR)pool & (OS_MEM_ALIGN_SIZE - 1)) || \
        (size & (OS_MEM_ALIGN_SIZE - 1))) {
        PRINT_ERR("LiteOS heap memory address or size configured not aligned:address:0x%x,size:0x%x, alignsize:%d\n", \
                  (UINTPTR)pool, size, OS_MEM_ALIGN_SIZE);
        return OS_ERROR;
    }if (OsMemPoolInit(pool, size)) {
        return OS_ERROR;
    }

#if (LOSCFG_MEM_MUL_POOL == 1)if (OsMemPoolAdd(pool, size)) {
        (VOID)OsMemPoolDeinit(pool);
        return OS_ERROR;
    }
#endif

#if OS_MEM_TRACE
    LOS_TraceReg(LOS_TRACE_MEM_TIME, OsMemTimeTrace, LOS_TRACE_MEM_TIME_NAME, LOS_TRACE_ENABLE);
    LOS_TraceReg(LOS_TRACE_MEM_INFO, OsMemInfoTrace, LOS_TRACE_MEM_INFO_NAME, LOS_TRACE_ENABLE);
#endif

    OsHookCall(LOS_HOOK_TYPE_MEM_INIT, pool, size);

    return LOS_OK;
}

我们继续看下函数OsMemPoolInit()。⑴处设置动态内存池信息结构体struct OsMemPoolHead *poolHead的起始地址和大小,⑵处设置内存池属性设置为锁定、不可扩展。⑶处获取内存池的第一个内存控制节点,然后设置它的大小,该节点大小等于内存池总大小减去内存池池头大小和一个内存节点头大小。然后再设置该内存节点的上一个节点为内存池的最后一个节点OS_MEM_END_NODE(pool, size)

⑷处调用宏给节点设置魔术字,然后把内存节点插入到空闲内存链表中。⑸处获取内存池的尾节点,设置魔术字,然后执行⑹设置尾节点大小为0和设置上一个节点,并设置已使用标记。如果开启调测宏LOSCFG_MEM_WATERLINE,还会有些其他操作,自行阅读即可。

STATIC UINT32 OsMemPoolInit(VOID *pool, UINT32 size)
{
    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    struct OsMemNodeHead *newNode = NULL;
    struct OsMemNodeHead *endNode = NULL;

    (VOID)memset_s(poolHead, sizeof(struct OsMemPoolHead), 0, sizeof(struct OsMemPoolHead));

⑴  poolHead->info.pool = pool;
    poolHead->info.totalSize = size;
    poolHead->info.attr &= ~(OS_MEM_POOL_UNLOCK_ENABLE | OS_MEM_POOL_EXPAND_ENABLE); /* default attr: lock, not expand. */

⑶  newNode = OS_MEM_FIRST_NODE(pool);
    newNode->sizeAndFlag = (size - sizeof(struct OsMemPoolHead) - OS_MEM_NODE_HEAD_SIZE);
    newNode->ptr.prev = OS_MEM_END_NODE(pool, size);OS_MEM_SET_MAGIC(newNode);
    OsMemFreeNodeAdd(pool, (struct OsMemFreeNodeHead *)newNode);

    /* The last mem node */
⑸  endNode = OS_MEM_END_NODE(pool, size);
    OS_MEM_SET_MAGIC(endNode);
#if OS_MEM_EXPAND_ENABLE
    endNode->ptr.next = NULL;
    OsMemSentinelNodeSet(endNode, NULL, 0);
#else
⑹  endNode->sizeAndFlag = 0;
    endNode->ptr.prev = newNode;
    OS_MEM_NODE_SET_USED_FLAG(endNode->sizeAndFlag);
#endif
#if (LOSCFG_MEM_WATERLINE == 1)
    poolHead->info.curUsedSize = sizeof(struct OsMemPoolHead) + OS_MEM_NODE_HEAD_SIZE;
    poolHead->info.waterLine = poolHead->info.curUsedSize;
#endif

    return LOS_OK;
}

2.3 申请动态内存

初始化动态内存池后,我们可以使用函数VOID *LOS_MemAlloc(VOID *pool, UINT32 size)来申请动态内存,下面分析下源码。

⑴处对参数进行校验,内存池地址不能为空,申请的内存大小不能为0。⑵处判断申请的内存大小是否已标记为使用或内存对齐。⑶处调用函数OsMemAlloc(poolHead, size, intSave)申请内存块。

VOID *LOS_MemAlloc(VOID *pool, UINT32 size)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endifif ((pool == NULL) || (size == 0)) {
        return NULL;
    }

    if (size < OS_MEM_MIN_ALLOC_SIZE) {
        size = OS_MEM_MIN_ALLOC_SIZE;
    }

    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    VOID *ptr = NULL;
    UINT32 intSave;

    MEM_LOCK(poolHead, intSave);
    do {if (OS_MEM_NODE_GET_USED_FLAG(size) || OS_MEM_NODE_GET_ALIGNED_FLAG(size)) {
            break;
        }
⑶      ptr = OsMemAlloc(poolHead, size, intSave);
    } while (0);
    MEM_UNLOCK(poolHead, intSave);

#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_MALLOC, timeUsed);

    LOS_MEM_POOL_STATUS poolStatus = {0};
    (VOID)LOS_MemInfoGet(pool, &poolStatus);
    UINT8 fragment = 100 - poolStatus.maxFreeNodeSize * 100 / poolStatus.totalFreeSize; /* 100: percent denominator. */
    UINT8 usage = LOS_MemTotalUsedGet(pool) * 100 / LOS_MemPoolSizeGet(pool); /* 100: percent denominator. */
    LOS_Trace(LOS_TRACE_MEM_INFO, (UINTPTR)pool & MEM_POOL_ADDR_MASK, fragment, usage, poolStatus.totalFreeSize,
              poolStatus.maxFreeNodeSize, poolStatus.usedNodeNum, poolStatus.freeNodeNum);
#endif

    OsHookCall(LOS_HOOK_TYPE_MEM_ALLOC, pool, size);

    return ptr;
}

我们继续分析函数OsMemAlloc()。⑴处对申请内存大小加上头结点大小的和进行内存对齐,⑵处从空闲内存链表中获取一个满足申请大小的空闲内存块,如果申请失败,则打印错误信息。⑶处如果找到的内存块大于需要的内存大小,则执行分割操作。⑷处把已分配的内存节点标记为已使用,更新水线记录。⑸返回内存块的数据区的地址,这个是通过内存节点地址加1定位到数据区内存地址实现的。申请内存完成,调用申请内存的函数中可以使用申请的内存了。

STATIC INLINE VOID *OsMemAlloc(struct OsMemPoolHead *pool, UINT32 size, UINT32 intSave)
{
    struct OsMemNodeHead *allocNode = NULL;

#if (LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK == 1)
    if (OsMemAllocCheck(pool, intSave) == LOS_NOK) {
        return NULL;
    }
#endif

⑴  UINT32 allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE);

#if OS_MEM_EXPAND_ENABLE
retry:
#endif
⑵  allocNode = OsMemFreeNodeGet(pool, allocSize);
    if (allocNode == NULL) {
#if OS_MEM_EXPAND_ENABLE
        if (pool->info.attr & OS_MEM_POOL_EXPAND_ENABLE) {
            INT32 ret = OsMemPoolExpand(pool, allocSize, intSave);
            if (ret == 0) {
                goto retry;
            }
        }
#endif
        PRINT_ERR("---------------------------------------------------"
                  "--------------------------------------------------------\n");
        MEM_UNLOCK(pool, intSave);
        OsMemInfoPrint(pool);
        MEM_LOCK(pool, intSave);
        PRINT_ERR("[%s] No suitable free block, require free node size: 0x%x\n", __FUNCTION__, allocSize);
        PRINT_ERR("----------------------------------------------------"
                  "-------------------------------------------------------\n");
        return NULL;
    }if ((allocSize + OS_MEM_MIN_LEFT_SIZE) <= allocNode->sizeAndFlag) {
        OsMemSplitNode(pool, allocNode, allocSize);
    }OS_MEM_NODE_SET_USED_FLAG(allocNode->sizeAndFlag);
    OsMemWaterUsedRecord(pool, OS_MEM_NODE_GET_SIZE(allocNode->sizeAndFlag));

#if (LOSCFG_MEM_LEAKCHECK == 1)
    OsMemLinkRegisterRecord(allocNode);
#endifreturn OsMemCreateUsedNode((VOID *)allocNode);
}

2.4 按指定字节对齐申请动态内存

我们还可以使用函数VOID *LOS_MemAllocAlign(VOID *pool, UINT32 size, UINT32 boundary),从指定动态内存池中申请长度为size且地址按boundary字节对齐的内存。该函数需要3个参数,VOID *pool为内存池起始地址,UINT32 size为需要申请的内存大小,UINT32 boundary内存对齐数值。当申请内存后得到的内存地址VOID *ptr,对齐后的内存地址为VOID *alignedPtr,二者的偏移值使用UINT32 gapSize保存。因为已经按OS_MEM_ALIGN_SIZE内存对齐了,最大偏移值为boundary - OS_MEM_ALIGN_SIZE。下面分析下源码。

⑴处对参数进行校验,内存池地址不能为空,申请的内存大小不能为0,对齐字节boundary不能为0,还需要是2的幂。申请的内存大小必须大于最小的申请值OS_MEM_MIN_ALLOC_SIZE。⑵处校验下对齐内存后是否会数据溢出。⑶处计算对齐后需要申请的内存大小,然后判断内存大小数值没有已使用或已对齐标记。⑷处调用函数申请到内存VOID *ptr,然后计算出对齐的内存地址VOID *alignedPtr,如果二者相等则返回。⑸处计算出对齐内存的偏移值,⑹处获取申请到的内存的头节点,设置已对齐标记。⑺对偏移值设置对齐标记,然后把偏移值保存在内存VOID *alignedPtr的前4个字节里。⑻处重新定向要返回的指针,完成申请对齐的内存。

VOID *LOS_MemAllocAlign(VOID *pool, UINT32 size, UINT32 boundary)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endif

    UINT32 gapSize;if ((pool == NULL) || (size == 0) || (boundary == 0) || !OS_MEM_IS_POW_TWO(boundary) ||
        !OS_MEM_IS_ALIGNED(boundary, sizeof(VOID *))) {
        return NULL;
    }

    if (size < OS_MEM_MIN_ALLOC_SIZE) {
        size = OS_MEM_MIN_ALLOC_SIZE;
    }if ((boundary - sizeof(gapSize)) > ((UINT32)(-1) - size)) {
        return NULL;
    }

⑶  UINT32 useSize = (size + boundary) - sizeof(gapSize);
    if (OS_MEM_NODE_GET_USED_FLAG(useSize) || OS_MEM_NODE_GET_ALIGNED_FLAG(useSize)) {
        return NULL;
    }

    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    UINT32 intSave;
    VOID *ptr = NULL;
    VOID *alignedPtr = NULL;

    MEM_LOCK(poolHead, intSave);
    do {
⑷      ptr = OsMemAlloc(pool, useSize, intSave);
        alignedPtr = (VOID *)OS_MEM_ALIGN(ptr, boundary);
        if (ptr == alignedPtr) {
            break;
        }

        /* store gapSize in address (ptr - 4), it will be checked while free */
⑸      gapSize = (UINT32)((UINTPTR)alignedPtr - (UINTPTR)ptr);struct OsMemUsedNodeHead *allocNode = (struct OsMemUsedNodeHead *)ptr - 1;
        OS_MEM_NODE_SET_ALIGNED_FLAG(allocNode->header.sizeAndFlag);OS_MEM_SET_GAPSIZE_ALIGNED_FLAG(gapSize);
        *(UINT32 *)((UINTPTR)alignedPtr - sizeof(gapSize)) = gapSize;
⑻      ptr = alignedPtr;
    } while (0);
    MEM_UNLOCK(poolHead, intSave);

#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_MEMALIGN, timeUsed);
#endif

    OsHookCall(LOS_HOOK_TYPE_MEM_ALLOCALIGN, pool, size, boundary);

    return ptr;
}

2.5 释放动态内存

对申请的内存块使用完毕,我们可以使用函数UINT32 LOS_MemFree(VOID *pool, VOID *ptr)来释放动态态内存,需要2个参数,VOID *pool是初始化过的动态内存池地址。VOID *ptr是需要释放的动态内存块的数据区的起始地址,注意这个不是内存控制节点的地址。下面分析下源码,⑴处对传入的参数先进行校验。⑵处获取校准内存对齐后的真实的内存地址,然后获取内存节点头地址。⑶处调用函数OsMemFree(pool, ptr)完成内存的释放。

UINT32 LOS_MemFree(VOID *pool, VOID *ptr)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endifif ((pool == NULL) || (ptr == NULL) || !OS_MEM_IS_ALIGNED(pool, sizeof(VOID *)) ||
        !OS_MEM_IS_ALIGNED(ptr, sizeof(VOID *))) {
        return LOS_NOK;
    }

    UINT32 ret = LOS_NOK;
    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    struct OsMemNodeHead *node = NULL;
    UINT32 intSave;

    MEM_LOCK(poolHead, intSave);
    do {
⑵      ptr = OsGetRealPtr(pool, ptr);
        if (ptr == NULL) {
            break;
        }
        node = (struct OsMemNodeHead *)((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE);
⑶      ret = OsMemFree(poolHead, node);
    } while (0);
    MEM_UNLOCK(poolHead, intSave);

#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_FREE, timeUsed);
#endif

    OsHookCall(LOS_HOOK_TYPE_MEM_FREE, pool, ptr);

    return ret;
}

我们回过头来,继续看下函数OsGetRealPtr()。⑴获取内存对齐的偏移值,⑵如果偏移值同时标记为已使用和已对齐,则返回错误。⑶如果偏移值标记为已对齐,则执行⑷去除对齐标记,获取不带标记的偏移值。然后执行⑸,获取内存对齐之前的数据区内存地址。

STATIC INLINE VOID *OsGetRealPtr(const VOID *pool, VOID *ptr)
{
    VOID *realPtr = ptr;
⑴  UINT32 gapSize = *((UINT32 *)((UINTPTR)ptr - sizeof(UINT32)));if (OS_MEM_GAPSIZE_CHECK(gapSize)) {
        PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize);
        return NULL;
    }if (OS_MEM_GET_GAPSIZE_ALIGNED_FLAG(gapSize)) {
⑷      gapSize = OS_MEM_GET_ALIGNED_GAPSIZE(gapSize);
        if ((gapSize & (OS_MEM_ALIGN_SIZE - 1)) ||
            (gapSize > ((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE - (UINTPTR)pool))) {
            PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize);
            return NULL;
        }
⑸      realPtr = (VOID *)((UINTPTR)ptr - (UINTPTR)gapSize);
    }
    return realPtr;
}

2.6 重新申请动态内存

我们还可以使用函数VOID *LOS_MemRealloc(VOID *pool, VOID *ptr, UINT32 size),按指定size大小重新分配内存块,并将原内存块内容拷贝到新内存块。如果新内存块申请成功,则释放原内存块。该函数需要3个参数,VOID *pool为内存池起始地址,VOID *ptr为之前申请的内存地址,UINT32 size为重新申请的内存大小。返回值为新内存块地址,或者返回NULL。下面分析下源码。

⑴处对参数进行校验,内存池地址不能为空,内存大小不能含有已使用、已对齐标记。⑵处如果传入的内存地址为空,则等价于LOS_MemAlloc()函数。⑶如果传入size为0,等价于函数LOS_MemFree()。⑷处保证申请的内存块大小至少为系统允许的最小值OS_MEM_MIN_ALLOC_SIZE。⑸处获取内存对齐之前的内存地址,上文已分析该函数OsGetRealPtr()。⑹处由数据域内存地址计算出内存控制节点node的内存地址,然后执行⑺处函数重新申请内存。

VOID *LOS_MemRealloc(VOID *pool, VOID *ptr, UINT32 size)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endifif ((pool == NULL) || OS_MEM_NODE_GET_USED_FLAG(size) || OS_MEM_NODE_GET_ALIGNED_FLAG(size)) {
        return NULL;
    }

    OsHookCall(LOS_HOOK_TYPE_MEM_REALLOC, pool, ptr, size);if (ptr == NULL) {
        return LOS_MemAlloc(pool, size);
    }if (size == 0) {
        (VOID)LOS_MemFree(pool, ptr);
        return NULL;
    }if (size < OS_MEM_MIN_ALLOC_SIZE) {
        size = OS_MEM_MIN_ALLOC_SIZE;
    }

    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    struct OsMemNodeHead *node = NULL;
    VOID *newPtr = NULL;
    UINT32 intSave;

    MEM_LOCK(poolHead, intSave);
    do {
⑸      ptr = OsGetRealPtr(pool, ptr);
        if (ptr == NULL) {
            break;
        }

⑹      node = (struct OsMemNodeHead *)((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE);
        if (OsMemCheckUsedNode(pool, node) != LOS_OK) {
            break;
        }

⑺      newPtr = OsMemRealloc(pool, ptr, node, size, intSave);
    } while (0);
    MEM_UNLOCK(poolHead, intSave);

#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_REALLOC, timeUsed);
#endif

    return newPtr;
}

继续分析下函数OsMemRealloc。⑴处处理重新申请的内存小于等于现有的内存的情况,需要调用函数OsMemReAllocSmaller()进行分割,分割完毕返回(VOID *)ptr即可。如果重新申请更大的内存,则执行⑵处代码获取下一个节点,然后执行⑶处理下一个节点可用且两个节点大小之和大于等于重新申请内存的大小allocSize。执行⑷处的函数,合并节点重新分配内存。

如果连续的节点的大小不满足重新申请内存的大小,则执行⑸处函数重新申请内存。申请成功后,执行⑹把之前内存的数据复制到新申请的内存区域,复制失败的话,则把新申请的内存释放掉,并返回NULL,退出函数。如果复制成功,继续执行⑺释放掉之前的节点。

STATIC INLINE VOID *OsMemRealloc(struct OsMemPoolHead *pool, const VOID *ptr,
                struct OsMemNodeHead *node, UINT32 size, UINT32 intSave)
{
    struct OsMemNodeHead *nextNode = NULL;
    UINT32 allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE);
    UINT32 nodeSize = OS_MEM_NODE_GET_SIZE(node->sizeAndFlag);
    VOID *tmpPtr = NULL;if (nodeSize >= allocSize) {
        OsMemReAllocSmaller(pool, allocSize, node, nodeSize);
        return (VOID *)ptr;
    }

⑵  nextNode = OS_MEM_NEXT_NODE(node);if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->sizeAndFlag) &&
        ((nextNode->sizeAndFlag + nodeSize) >= allocSize)) {OsMemMergeNodeForReAllocBigger(pool, allocSize, node, nodeSize, nextNode);
        return (VOID *)ptr;
    }

⑸  tmpPtr = OsMemAlloc(pool, size, intSave);
    if (tmpPtr != NULL) {if (memcpy_s(tmpPtr, size, ptr, (nodeSize - OS_MEM_NODE_HEAD_SIZE)) != EOK) {
            MEM_UNLOCK(pool, intSave);
            (VOID)LOS_MemFree((VOID *)pool, (VOID *)tmpPtr);
            MEM_LOCK(pool, intSave);
            return NULL;
        }(VOID)OsMemFree(pool, node);
    }
    return tmpPtr;
}

小结

本文带领大家一起剖析了鸿蒙轻内核的静态内存模块的源代码,包含动态内存的结构体、动态内存池初始化、动态内存申请、释放等。感谢阅读,如有任何问题、建议,都可以留言给我们: https://gitee.com/openharmony/kernel_liteos_m/issues 。为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony/kernel_liteos_m ,关注Watch、点赞Star、并Fork到自己账户下,谢谢。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。