当前位置: 首页 > wzjs >正文

晋江网站建设费用东莞网络推广招聘

晋江网站建设费用,东莞网络推广招聘,电影点播网站开发费用,传媒建站推荐目录 1、内存堆控制 1.1 内存堆控制器 1.2 内存块节点 1.3 内存堆管理 2、内存堆初始化 2.1 初始化接口 2.2 初始化示例 2.3 源码分析 3、内存堆操作 3.1 内存块申请 3.1.1 相关接口 3.1.2 原理分析 3.1.3 示例分析 3.1.4 代码分析 3.2 内存块伸缩 3.2.1 相关…

目录

1、内存堆控制

         1.1 内存堆控制器   

1.2 内存块节点

1.3 内存堆管理

2、内存堆初始化

2.1 初始化接口

2.2 初始化示例

2.3 源码分析

3、内存堆操作

3.1 内存块申请

3.1.1 相关接口

3.1.2 原理分析

3.1.3 示例分析

3.1.4 代码分析

3.2 内存块伸缩

3.2.1 相关接口

3.2.2 原理分析

3.2.3 示例分析

3.2.4 源码分析

3.3 内存快释放

3.3.1 相关接口

3.3.2 原理分析

3.3.3 示例分析

3.3.4 源码分析


   本章讲解RT_Thread Nano实时操作系统的动态内存(内存堆)部分,RT-Thread提供三种动态内存管理算法:小堆内存管理算法、堆内存管理方法、SLAB内存管理算法。小堆内存管理模块主要针对系统资源比较少,一般用于小于2M内存空间的系统;堆内存可以应用于多块不连续的内存堆,SLAB内存管理模块则主要是在系统资源比较丰富时,提供了一种近似多内存池管理算法的快速算法。三种内存管理模块在系统运行时只能选择其中之一或者完全不使用动态堆内存管理器。

        三种管理模块提供的API接口完全相同,小堆内存管理算法接口在mem.c,堆内存管理方法在memheap.c文件中定义,SLAB内存管理模块在slab.c文件中定义。

内存管理方法适用场景宏定义设置接口定义位置
小堆内存小于2M内存空间

RT_USING_HEAP

RT_USING_SMALL_MEM

mem.c
堆内存多块不连续的内存堆

RT_USING_MEMHEAP_AS_HEAP

RT_USING_MEMTRACE

memheap.c
SLAB系统资源比较丰富

RT_USING_HEAP

RT_USING_SLAB

slab.c

        本章主要讲解静态小堆内存管理算法,其特点是内存利用率高,适用于小存储空间。

本章基于RT_Thread Nano V3.1.5版本分析

1、内存堆控制

  1.1 内存堆控制器   

        内存堆控制器由一些全局变量定义,如下所示:

static rt_uint8_t *heap_ptr;         // 内存堆首地址
static struct heap_mem *heap_end;    // 内存堆尾部节点
static struct heap_mem *lfree;       // 指向最低地址空闲块节点,用于管理空闲内存块
static struct rt_semaphore heap_sem; // 内存信号,用于临界资源权限保护
static rt_size_t mem_size_aligned;   // 对齐后小堆内存总大小
static rt_size_t used_mem;           // 记录已使用字节数
static rt_size_t max_mem;            // 记录已使用字节数的最大值

1.2 内存块节点

         内存堆链表节点如下所示,宏定义ARCH_CPU_64BIT表示系统内核为64位。

struct heap_mem
{rt_uint16_t magic;     /*内存块标志字,默认0x1ea0*/rt_uint16_t used;      /*=0表示内存块空闲,=1表示内存块被申请*/#ifdef ARCH_CPU_64BITrt_uint32_t resv;#endifrt_size_t next, prev;  /*next表示上个内存块节点的相对位置,prev表示下个内存块节点的相对位置*//*内存监视使能*/#ifdef RT_USING_MEMTRACE#ifdef ARCH_CPU_64BITrt_uint8_t thread[8];   /* 记录线程名称*/#elsert_uint8_t thread[4];   /* 记录线程名称*/#endif#endif
};

1.3 内存堆管理

        小堆内存中通过一个双向链表管理内存块,每个内存块首定义一个节点,节点按照地址从小到大顺序双向连接。

        内存节点参数next, prev非指针变量,而是表示节点地址相对于堆首地址heap_ptr的偏移,通过偏移定位和连接上下内存块节点位置,例如一内存节点指针struct heap_mem *mem,其下个相邻节点位置为heap_ptr[mem->next],上个相邻节点位置为heap_ptr[mem->prev],同时,可计算出其内存块大小为

size=(&heap_ptr(mem->next)-(char*)mem-sizeof(struct heap_mem));

        空闲内存块指针*lfree指向地址最低的空闲内存块,内存申请时,从lfree指向位置开始向高地址遍历所有内存块,直至找到合适的空闲内存块。当内存块释放后,需要重新判断和定位lfree位置。

        系统内核为64位时,最小内存堆默认24字节,否则默认12字节。最小长度可调。

#ifdef ARCH_CPU_64BIT
#define MIN_SIZE 24
#else
#define MIN_SIZE 12
#endif

2、内存堆初始化

        小内存堆通过全局变量管理,将内存区对齐后,首尾各定义一个内存块节点,堆首内存节点为空闲内存块节点,堆尾节点属性是已使用,将2个节点双向连接,并将空闲内存块指针指向堆首节点。

2.1 初始化接口

/*** 系统堆初始化.* @param begin_addr 系统堆起始地址* @param end_addr 系统堆结束地址*/
void rt_system_heap_init(void *begin_addr, void *end_addr)

2.2 初始化示例

        代码示例:

unsigned char heap[1024*1024];rt_system_heap_init(heap,heap+sizeof(heap));

        内存结构图示:初始化完成后lfree指向首节点,heap_end指向尾部节点,收尾节单向链接。已使用字节数used_mem为0,已使用字节数的最大值max_mem为0。

2.3 源码分析

/*** 系统堆初始化.* @param begin_addr 系统堆起始地址* @param end_addr 系统堆结束地址*/
void rt_system_heap_init(void *begin_addr, void *end_addr)
{struct heap_mem *mem;/*内存堆首尾地址对齐,默认4字节对齐*/ rt_ubase_t begin_align = RT_ALIGN((rt_ubase_t)begin_addr, RT_ALIGN_SIZE);rt_ubase_t end_align   = RT_ALIGN_DOWN((rt_ubase_t)end_addr, RT_ALIGN_SIZE);RT_DEBUG_NOT_IN_INTERRUPT;/*内存堆大小检验*/if ((end_align > (2 * SIZEOF_STRUCT_MEM)) &&((end_align - 2 * SIZEOF_STRUCT_MEM) >= begin_align)){/* 有效内存大小 */mem_size_aligned = end_align - begin_align - 2 * SIZEOF_STRUCT_MEM;}else{rt_kprintf("mem init, error begin address 0x%x, and end address 0x%x\n",(rt_ubase_t)begin_addr, (rt_ubase_t)end_addr);return;}/* 内存头 */heap_ptr = (rt_uint8_t *)begin_align;/* 调试代码*/RT_DEBUG_LOG(RT_DEBUG_MEM, ("mem init, heap begin address 0x%x, size %d\n",(rt_ubase_t)heap_ptr, mem_size_aligned));/* 初始化内存头节点,next链接尾部节点位置 */mem        = (struct heap_mem *)heap_ptr;mem->magic = HEAP_MAGIC;mem->next  = mem_size_aligned + SIZEOF_STRUCT_MEM;// 指向内存相对位置(数组下标),非指针mem->prev  = 0;mem->used  = 0;/*记录线程名称"INIT"*/
#ifdef RT_USING_MEMTRACErt_mem_setname(mem, "INIT");
#endif/*初始化内存堆尾部节点*/heap_end    = (struct heap_mem *)&heap_ptr[mem->next];heap_end->magic = HEAP_MAGIC;heap_end->used  = 1;heap_end->next  = mem_size_aligned + SIZEOF_STRUCT_MEM;/*与尾部链表连接*/heap_end->prev  = mem_size_aligned + SIZEOF_STRUCT_MEM;
#ifdef RT_USING_MEMTRACErt_mem_setname(heap_end, "INIT");
#endif/*初始化内存权限信号*/rt_sem_init(&heap_sem, "heap", 1, RT_IPC_FLAG_FIFO);/* 初始化空闲块指针,指向堆首节点 */lfree = (struct heap_mem *)heap_ptr;
}

3、内存堆操作

        内存堆操作可以分为内存申请、内存伸缩、内存释放。

3.1 内存块申请

3.1.1 相关接口

        接口rt_malloc仅申请一定大小的内存,并不进行初始化,所以申请的内存数据值不确定。接口rt_calloc审请内存后,会初始化为0。可以类比于C库的malloc、calloc接口。

/*动态内存申请接口,size--申请内存大小,申请成功后返回内存块首地址,否则返回Null*/
void *rt_malloc(rt_size_t size)/*动态内存申请接口,count--申请内存单元个数,size--单个单元大小
内存申请完成后初始化为0,申请成功后返回内存块首地址,否则返回NULL*/
void *rt_calloc(rt_size_t count, rt_size_t size)

3.1.2 原理分析

        内存控制块申请,是从最低地址空闲内存块节点lfree指向的位置开始向高地址遍历所有内存块,直至找到满足申请大小的空闲内存块,将该内存块标记为已使用,如果该内存块比较大,从该内存块中切割出所需的内存,剩余部分形成新的内存块,并定义新的内存块节点,插入到内存块双向链表中。

3.1.3 示例分析

unsigned char heap[1024*1024];
// 初始化调用
rt_system_heap_init(heap,heap+sizeof(heap));
// 线程1调用,假设线程名称"Led1"
void Thread1()
{char *p1=rt_malloc(1200);
}
// 线程2调用,假设线程名称"Led2"
void Thread1()
{char *p2=rt_malloc(1000);
}

3.1.4 代码分析

/*动态内存申请基础接口*/
void *rt_malloc(rt_size_t size)
{rt_size_t ptr, ptr2;struct heap_mem *mem, *mem2;if (size == 0) return RT_NULL;RT_DEBUG_NOT_IN_INTERRUPT;/*调试代码,长度是否对齐判断, */if (size != RT_ALIGN(size, RT_ALIGN_SIZE))RT_DEBUG_LOG(RT_DEBUG_MEM, ("malloc size %d, but align to %d\n",size, RT_ALIGN(size, RT_ALIGN_SIZE)));elseRT_DEBUG_LOG(RT_DEBUG_MEM, ("malloc size %d\n", size));/*长度对齐,默认4字节 */size = RT_ALIGN(size, RT_ALIGN_SIZE);/*分配长度size超过可用内存长度,返回失败*/if (size > mem_size_aligned){RT_DEBUG_LOG(RT_DEBUG_MEM, ("no memory\n"));return RT_NULL;}/* 数据块长度容错 */if (size < MIN_SIZE_ALIGNED)size = MIN_SIZE_ALIGNED;/* 获取信号,阻塞方式为永久阻塞 */rt_sem_take(&heap_sem, RT_WAITING_FOREVER);/*根据相对位置,遍历空闲链表,即从空闲表头开始遍历,一直找到符合大小的控制块*/for (ptr = (rt_uint8_t *)lfree - heap_ptr;ptr < mem_size_aligned - size;ptr = ((struct heap_mem *)&heap_ptr[ptr])->next){/*取节点*/mem = (struct heap_mem *)&heap_ptr[ptr];/*判断内存块属性,未使用,且大小满足size*/if ((!mem->used) && (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size){/* 判断是否需要分割内存块 */if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >=(size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)){/*插入新节点*/ptr2 = ptr + SIZEOF_STRUCT_MEM + size;/* 设置新节点 create mem2 struct */mem2       = (struct heap_mem *)&heap_ptr[ptr2];mem2->magic = HEAP_MAGIC;mem2->used = 0;mem2->next = mem->next;mem2->prev = ptr;#ifdef RT_USING_MEMTRACE/*内存监视使能*/rt_mem_setname(mem2, "    ");#endif/*按照地址顺序 插入节点and insert it between mem and mem->next */mem->next = ptr2;mem->used = 1;/**/if (mem2->next != mem_size_aligned + SIZEOF_STRUCT_MEM){((struct heap_mem *)&heap_ptr[mem2->next])->prev = ptr2;}
#ifdef RT_MEM_STATS/*记录当前使用内存和最大使用内存*/used_mem += (size + SIZEOF_STRUCT_MEM);if (max_mem < used_mem)max_mem = used_mem;
#endif}/* 不需要分割内存块 */else{/*设置为已使用*/mem->used = 1;
#ifdef RT_MEM_STATS/*记录当前使用内存和最大使用内存*/used_mem += mem->next - ((rt_uint8_t *)mem - heap_ptr);if (max_mem < used_mem)     max_mem = used_mem;
#endif}/* 设置新节点标志字 set memory block magic */mem->magic = HEAP_MAGIC;
#ifdef RT_USING_MEMTRACE/*内存监视,记录线程名称*/if (rt_thread_self())rt_mem_setname(mem, rt_thread_self()->name);elsert_mem_setname(mem, "NONE");
#endif/*lfree指向的节点已被分配,调整空闲链表指针*/if (mem == lfree){/* 指向下一个未被分配的内存块 */while (lfree->used && lfree != heap_end)lfree = (struct heap_mem *)&heap_ptr[lfree->next];/*断言*/RT_ASSERT(((lfree == heap_end) || (!lfree->used)));}/*释放信号*/rt_sem_release(&heap_sem);/*断言*/RT_ASSERT((rt_ubase_t)mem + SIZEOF_STRUCT_MEM + size <= (rt_ubase_t)heap_end);RT_ASSERT((rt_ubase_t)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM) % RT_ALIGN_SIZE == 0);RT_ASSERT((((rt_ubase_t)mem) & (RT_ALIGN_SIZE - 1)) == 0);/*调试代码*/RT_DEBUG_LOG(RT_DEBUG_MEM,("allocate memory at 0x%x, size: %d\n",(rt_ubase_t)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM),(rt_ubase_t)(mem->next - ((rt_uint8_t *)mem - heap_ptr))));RT_OBJECT_HOOK_CALL(rt_malloc_hook,(((void *)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM)), size));/* 返回块地址 */return (rt_uint8_t *)mem + SIZEOF_STRUCT_MEM;}}rt_sem_release(&heap_sem);return RT_NULL;
}
void *rt_calloc(rt_size_t count, rt_size_t size)
{void *p;/* 申请内存,大小size */p = rt_malloc(count * size);/* 将申请的内存初始化为0 */if (p)rt_memset(p, 0, count * size);return p;
}

3.2 内存块伸缩

3.2.1 相关接口

        接口rt_realloc可以将已申请的内存大小进行调整或新申请一块内存。可以类比于C库的realloc接口。        

/*动态内存长度伸缩,rmem--内存块首地址,newsize--内存块新长度,申请成功后返回内存块首地址,否则返回NULL*/
void *rt_realloc(void *rmem, rt_size_t newsize)

3.2.2 原理分析

        使用rt_realloc分配或调整内存块分以下几种情况,newsize先进行对齐:

(1)newsize对齐后为0,直接释放内存块。

(2)内存块rmem未被分配,直接按照newsize对齐后大小分配内存,等同于rt_malloc(newsize)。

(3)rmem已被分配,当newsize对齐后小于等于原内存块大小时,可以对原内存块进行部分释放:若newsize与原内存块差不足以划分一个新内存块时,维持原内存结构不变。若足以划分一个新内存块时,则划分出新的空闲内存块进行释放。

(4)rmem已被分配,当newsize对齐后大于原内存块大小时,则根据newsize重新申请内存,并将原内存的数据复制到新内存块,并释放原内存块。

3.2.3 示例分析

3.2.4 源码分析

void *void *rt_realloc(void *rmem, rt_size_t newsize)
{rt_size_t size;rt_size_t ptr, ptr2;struct heap_mem *mem, *mem2;void *nmem;RT_DEBUG_NOT_IN_INTERRUPT;/*新尺寸对齐,默认4字节 */newsize = RT_ALIGN(newsize, RT_ALIGN_SIZE);/*新尺寸过大,返回失败*/if (newsize > mem_size_aligned){RT_DEBUG_LOG(RT_DEBUG_MEM, ("realloc: out of memory\n"));return RT_NULL;}/*新尺寸为0,释放内存块*/else if (newsize == 0){rt_free(rmem);return RT_NULL;}/* rmem 未被分配,直接按照newsize大小分配内存 */if (rmem == RT_NULL)return rt_malloc(newsize);/*获取信号权限*/rt_sem_take(&heap_sem, RT_WAITING_FOREVER);/*内存块容错*/if ((rt_uint8_t *)rmem < (rt_uint8_t *)heap_ptr ||(rt_uint8_t *)rmem >= (rt_uint8_t *)heap_end){/* 释放信号 */rt_sem_release(&heap_sem);return rmem;}/*获取当前内存块节点*/mem = (struct heap_mem *)((rt_uint8_t *)rmem - SIZEOF_STRUCT_MEM);/*新旧大小相同,直接释放信号后返回原内存块首地址*/ptr = (rt_uint8_t *)mem - heap_ptr;size = mem->next - ptr - SIZEOF_STRUCT_MEM;if (size == newsize){/*释放信号*/rt_sem_release(&heap_sem);return rmem;}/*新长度小于原长度,并且满足划分新块的条件*/if (newsize + SIZEOF_STRUCT_MEM + MIN_SIZE < size){/* 划分新内存块 */
#ifdef RT_MEM_STATSused_mem -= (size - newsize);
#endif/*设置新内存块节点*/ptr2 = ptr + SIZEOF_STRUCT_MEM + newsize;mem2 = (struct heap_mem *)&heap_ptr[ptr2];mem2->magic = HEAP_MAGIC;mem2->used = 0;mem2->next = mem->next;mem2->prev = ptr;/*设置线程名称*/
#ifdef RT_USING_MEMTRACErt_mem_setname(mem2, "    ");
#endifmem->next = ptr2;if (mem2->next != mem_size_aligned + SIZEOF_STRUCT_MEM){((struct heap_mem *)&heap_ptr[mem2->next])->prev = ptr2;}/*调整空闲内存块指针*/if (mem2 < lfree){/* the splited struct is now the lowest */lfree = mem2;}/**/plug_holes(mem2);/*释放信号*/rt_sem_release(&heap_sem);return rmem;}/*新长度大于原长度*//*释放信号*/ rt_sem_release(&heap_sem);/* 重新分配内存 */nmem = rt_malloc(newsize);if (nmem != RT_NULL) /* check memory */{/*原内存区数据复制到新内存区,然后释放原内存*/rt_memcpy(nmem, rmem, size < newsize ? size : newsize);rt_free(rmem);}return nmem;
}

3.3 内存快释放

3.3.1 相关接口

/** 释放内存块,rmem指向内存块首地址*/
void rt_free(void *rmem)

3.3.2 原理分析

        使用rt_free释放内存块:

(1)设置内存块节点为空闲状态。

(2)如果下个相邻内存块为空闲状态,则合并内存块,将下个内存块的节点从链表删除。

(3)如果上个相邻内存块为空闲状态,则合并内存块,将当前内存块的节点从链表删除。

3.3.3 示例分析

3.3.4 源码分析

/** 释放内存块*/
void rt_free(void *rmem)
{struct heap_mem *mem;if (rmem == RT_NULL)return;RT_DEBUG_NOT_IN_INTERRUPT;/*断言*/RT_ASSERT((((rt_ubase_t)rmem) & (RT_ALIGN_SIZE - 1)) == 0);RT_ASSERT((rt_uint8_t *)rmem >= (rt_uint8_t *)heap_ptr &&(rt_uint8_t *)rmem < (rt_uint8_t *)heap_end);/*执行钩子*/RT_OBJECT_HOOK_CALL(rt_free_hook, (rmem));/*地址检查*/if ((rt_uint8_t *)rmem < (rt_uint8_t *)heap_ptr ||(rt_uint8_t *)rmem >= (rt_uint8_t *)heap_end){RT_DEBUG_LOG(RT_DEBUG_MEM, ("illegal memory\n"));return;}/* 获取当前内存块节点*/mem = (struct heap_mem *)((rt_uint8_t *)rmem-SIZEOF_STRUCT_MEM);/*调试代码*/RT_DEBUG_LOG(RT_DEBUG_MEM,("release memory 0x%x, size: %d\n",(rt_ubase_t)rmem,(rt_ubase_t)(mem->next - ((rt_uint8_t *)mem - heap_ptr))));/* 获取内存信号权限 */rt_sem_take(&heap_sem, RT_WAITING_FOREVER);/* 检查内存块节点*/if (!mem->used || mem->magic != HEAP_MAGIC){rt_kprintf("to free a bad data block:\n");rt_kprintf("mem: 0x%08x, used flag: %d, magic code: 0x%04x\n", mem, mem->used, mem->magic);}/*断言,检查内存块节点*/RT_ASSERT(mem->used);RT_ASSERT(mem->magic == HEAP_MAGIC);/*修改节点(修改为未使用)*/mem->used  = 0;mem->magic = HEAP_MAGIC;
#ifdef RT_USING_MEMTRACE/*复归线程名称为空格*/rt_mem_setname(mem, "    ");
#endif/*调整空闲头部lfree,指向地址最低的空闲控制块*/if (mem < lfree){lfree = mem;}#ifdef RT_MEM_STATS/*计算已使用内存*/used_mem -= (mem->next - ((rt_uint8_t *)mem - heap_ptr));
#endif/* 合并内存块,前后内存块空闲判断*/plug_holes(mem);/*释放信号*/rt_sem_release(&heap_sem);
}/* 合并内存块,前后内存块空闲判断*/
static void plug_holes(struct heap_mem *mem)
{struct heap_mem *nmem;struct heap_mem *pmem;/*断言*/RT_ASSERT((rt_uint8_t *)mem >= heap_ptr);RT_ASSERT((rt_uint8_t *)mem < (rt_uint8_t *)heap_end);RT_ASSERT(mem->used == 0);/* 判断下一个内存块,如果是空闲块,则合并 */nmem = (struct heap_mem *)&heap_ptr[mem->next];if (mem != nmem &&nmem->used == 0 &&(rt_uint8_t *)nmem != (rt_uint8_t *)heap_end){/*纠正空闲节点lfree,lfree指向地址最低的空闲内存块*/ if (lfree == nmem){lfree = mem;}/*删除下一个内存块节点*/mem->next = nmem->next;((struct heap_mem *)&heap_ptr[nmem->next])->prev = (rt_uint8_t *)mem - heap_ptr;}/* 判断上一个内存块,如果是空闲块,则合并 */pmem = (struct heap_mem *)&heap_ptr[mem->prev];if (pmem != mem && pmem->used == 0){/* 纠正空闲节点lfree,lfree指向地址最低的空闲内存块 */if (lfree == mem){lfree = pmem;}/*删除当前节点*/pmem->next = mem->next;((struct heap_mem *)&heap_ptr[mem->next])->prev = (rt_uint8_t *)pmem - heap_ptr;}
}
http://www.dtcms.com/wzjs/51198.html

相关文章:

  • 哪个网站可以做h5页面怎么做网址
  • 网站建设 中国联盟网百度热搜榜排名
  • 邮件服务商seo怎么收费的
  • 最新版wordpress背景音乐济南网站万词优化
  • 揭阳网站制作建设成都企业seo
  • 绍兴建设公司网站广州网络推广策划公司
  • 网络网站建设推广网站怎么做优化排名
  • 阿里网站怎样做seo必应搜索引擎网址
  • 阜宁做网站价格软考培训机构排名
  • 没有服务器 怎么做网站seo网站推广免费
  • 做网站能带来什么seo常用的工具
  • 广州做网站建设哪家专业社区推广方法有哪些
  • 网站策划的具体内容是什么附近电脑培训速成班一个月
  • 上海关键词优化推荐seo关键词分析
  • 北京北站最火的推广软件
  • 做贸易 公司网站放哪里推广赚钱一个50元
  • 南宁如何做百度的网站推广北京有限公司
  • 有没有便宜做网站的 我要做个关键词优化快速排名
  • 合肥php网站开发网店推广的作用
  • 成都网站制作怎么样谷歌广告推广
  • 做装修效果图的网站有哪些软件网络营销有哪些特点
  • 小语种网站怎么做外链免费发布平台
  • 做网站的技术要求产品营销网站建设
  • 大型车网站建设天津网站优化
  • 设计师喜欢的购物网站seo如何快速排名
  • dw做网站的流程徐州seo建站
  • 手机做兼职的网站设计网络服务器图片
  • 深圳设计网站有哪些杭州百度seo代理
  • 如何给国外网站做seo上海关键词优化排名哪家好
  • 宝安网站建设方案外包久久seo正规吗