内存管理(四):DTB的映射

10 篇文章 10 订阅
订阅专栏

linux版本:4.14.74
硬件:ARMV8 A53

1 建立系统映射的过程

经过前面几章的内容,我们开启了MMU,为kernel image创建了映射,通过调用start_kernel setup_arch建立页表映射,读取kernel建立页表映射的代码和流程如下图所示,下面分章节对这三个部分进行说明

1.	void __init setup_arch(char **cmdline_p)  
2.	{  
3.	    pr_info("Boot CPU: AArch64 Processor [%08x]\n", read_cpuid_id());  
4.	  
5.	        .....  
6.	  
7.	    early_fixmap_init();  
8.	    early_ioremap_init();  
9.	  
10.	    setup_machine_fdt(__fdt_pointer);  
11.	        ....  
12.	    arm64_memblock_init();  
13.	  
14.	    paging_init();  
15.	  
16.	        ....  
17.	}  

在这里插入图片描述
本文主要描述映射DTB的过程

2 FIXMAP概念

虽然可以通过kernel image mapping和identity mapping来窥探物理地址空间,但终究是管中窥豹,不了解全局,那么内核是如何了解对端的物理世界呢?答案就是DTB,但是问题来了,这时候,内核还没有为DTB这段内存创建映射,因此,打开MMU之后的kernel还不能直接访问,需要先创建dtb mapping,而要创建address mapping,就需要分配页表内存,而这时候,还没有了解内存布局,内存管理模块还没有初始化,如何来分配内存呢?这要用到在内核虚拟内存布局中用到的fix map区域。
fixed map是被linux kernel用来解决一类问题的机制,这类问题的共同特点是:(1)在很早期的阶段需要进行地址映射,而此时,由于内存管理模块还没有完成初始化,不能动态分配内存,也就是无法动态分配创建映射需要的页表内存空间。(2)物理地址是固定的,或者是在运行时就可以确定的。对于这类问题,内核定义了一段固定映射的虚拟地址,让使用fix map机制的各个模块可以在系统启动的早期就可以创建地址映射,当然,这种机制不是那么灵活,因为虚拟地址都是编译时固定分配的。
下图是DTB的映射图 在这里插入图片描述
Fixmap区域分为permanent区域和temporary区域,所谓permanent表示映射关系永远都是存在的,例如FDT区域,一旦完成地址映射,内核可以访问DTB之后,这个映射关系一直都是存在的。而temporary fixmap则不然,一般而言,某个模块使用了这部分的虚拟地址之后,需要尽快释放这段虚拟地址,以便给其他模块使用,例如fixmap中为页表预留的区域PGD/PUD/PMD/PTE,在系统进行映射时,这块区域将会被反复使用。
在这里插入图片描述
上图为fixmap区域的虚拟地址布局

  • FIXADDR_START是fix map虚拟地址的起始地址
  • 蓝色区域为fixmap永久映射区域,橙色区域为temporary区域
  • FDT为映射DTB的区域,大小为4M,起始地址是2M对齐。
  • PGD/PUD/PMD/PTE用于在建立系统映射时,临时的页表虚拟地址

3 FIXMAP的初步映射

好,现在虚拟地址有了,要创建映射还需要有实际的物理地址来存放页表。对于fixed-mapped address这段虚拟地址空间,由于也是位于内核空间,因此PGD当然就是复用swapper进程的PGD了(其实整个系统就一个PGD),而其他LEVEL,如PUD/PMD/PTE的物理地址空间如何获取,此时页表未建立好,后面我们要讲的memblock也还没有建立好,此时是无法分配物理地址空间,所以PUD/PMD/PTE需要提前静态定义好

1.	static pte_t bm_pte[PTRS_PER_PTE] __page_aligned_bss;  
2.	static pmd_t bm_pmd[PTRS_PER_PMD] __page_aligned_bss __maybe_unused;  
3.	static pud_t bm_pud[PTRS_PER_PUD] __page_aligned_bss __maybe_unused;  

上述定义位于内核bss段,由于所有的Translation table都在kernel image mapping的范围内,因此内核可以毫无压力的访问,并创建fixed-mapped address这段虚拟地址空间对应的PUD、PMD和PTE的entry。所有中间level的Translation table都是在early_fixmap_init函数中完成初始化的。early_fixmap_init的所做工作如下图所示,建立起对FIXADDR_START的初步映射,所谓“初步”映射指的是只建立中间页表的关系,如下面的示意图,只建立了PGD–>PMD–>PTE的关系,而最后一步的映射在最终用的时候才会进行映射(实际上FIXADDR_MAP并不会用到,也就不会进行最终的映射)。
在这里插入图片描述

1.	/* 
2.	 * The p*d_populate functions call virt_to_phys implicitly so they can't be used 
3.	 * directly on kernel symbols (bm_p*d). This function is called too early to use 
4.	 * lm_alias so __p*d_populate functions must be used to populate with the 
5.	 * physical address from __pa_symbol. 
6.	 */  
7.	void __init early_fixmap_init(void)  
8.	{  
9.	    pgd_t *pgd;  
10.	    pud_t *pud;  
11.	    pmd_t *pmd;  
12.	    unsigned long addr = FIXADDR_START;  
13.	  
14.	    pgd = pgd_offset_k(addr);   /*****1****/  
15.	    if (CONFIG_PGTABLE_LEVELS > 3 &&  
16.	        !(pgd_none(*pgd) || pgd_page_paddr(*pgd) == __pa_symbol(bm_pud))) {  
17.	        /* 
18.	         * We only end up here if the kernel mapping and the fixmap 
19.	         * share the top level pgd entry, which should only happen on 
20.	         * 16k/4 levels configurations. 
21.	         */  
22.	        BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));  
23.	        pud = pud_offset_kimg(pgd, addr);  
24.	    } else {  
25.	        if (pgd_none(*pgd)) /******2*****/ 
26.	            __pgd_populate(pgd, __pa_symbol(bm_pud), PUD_TYPE_TABLE);  
27.	        pud = fixmap_pud(addr);  
28.	    }  
29.	    if (pud_none(*pud))   /******3*****/  
30.	        __pud_populate(pud, __pa_symbol(bm_pmd), PMD_TYPE_TABLE);  
31.	    pmd = fixmap_pmd(addr); /******4******/
32.	    __pmd_populate(pmd, __pa_symbol(bm_pte), PMD_TYPE_TABLE);  
33.34.	} 
  • (1)获取FIXADDR_MAP对应的pgd页表项
  • (2)以bm_pud的物理地址填充pgd
  • (3)以bm_pmd的物理地址填充pud
  • (4)以bm_pte的物理地址填充pmd
    系统对dtb的size有要求,不能大于2M,这个要求主要是要确保在创建地址映射(create_mapping)的时候不能分配其他的translation table page,也就是说,所有的translation table都必须静态定义。为什么呢?因为这时候内存管理模块还没有初始化,即便是memblock模块(初始化阶段分配内存的模块)都尚未初始化(没有内存布局的信息),不能动态分配内存。最后一个level则是在各个具体的模块进行的,对于DTB而言,这发生在fixmap_remap_fdt函数中

4 DTB的映射

DTB的实际映射位于fixmap_remap_fdt中,在前面的过程中,已经对FIXMAP区域的PGD–>PUD–>PMD–>PTE有了初步的映射,并且DTB区域的虚拟地址是2M的对齐的,这也是为了以2M为单位进行section map的映射。DTB映射的过程如下图
在这里插入图片描述
在这里插入图片描述

  • 第一次映射,PGD–>PMD的映射关系在early_fixmap_init已经建立,第一次映射是建立2M区域的映射,也就是对PMD的一个页表项进行映射,并且,相对于在early_fixmap_init中建立的映射关系,由于FIXADDR_START不是2M对齐的,而FDT是2M对齐的,所以此时PMD的页表项index会大1.
  • 第二次映射,虽然FDT的虚拟地址是2M对齐的,但是DTB的物理地址并不一定是2M对齐的,如果DTB正好位于跨2M的区域内,则需要进行第二次映射,在第一次映射中已经对DTB进行了部分映射,这一部分映射就可以读取出DTB的实际大小,在第二次映射中进行完整映射。
void *__init __fixmap_remap_fdt(phys_addr_t dt_phys, int *size, pgprot_t prot)  
{  
  /********1*******
    const u64 dt_virt_base = __fix_to_virt(FIX_FDT);  
    int offset;  
    void *dt_virt;  
  
    /* 
     * Check whether the physical FDT address is set and meets the minimum 
     * alignment requirement. Since we are relying on MIN_FDT_ALIGN to be 
     * at least 8 bytes so that we can always access the magic and size 
     * fields of the FDT header after mapping the first chunk, double check 
     * here if that is indeed the case. 
     */  
    BUILD_BUG_ON(MIN_FDT_ALIGN < 8);  
    if (!dt_phys || dt_phys % MIN_FDT_ALIGN)  
        return NULL;  
  
    /* 
     * Make sure that the FDT region can be mapped without the need to 
     * allocate additional translation table pages, so that it is safe 
     * to call create_mapping_noalloc() this early. 
     * 
     * On 64k pages, the FDT will be mapped using PTEs, so we need to 
     * be in the same PMD as the rest of the fixmap. 
     * On 4k pages, we'll use section mappings for the FDT so we only 
     * have to be in the same PUD. 
     */  
    BUILD_BUG_ON(dt_virt_base % SZ_2M);  
  
    BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> SWAPPER_TABLE_SHIFT !=  
             __fix_to_virt(FIX_BTMAP_BEGIN) >> SWAPPER_TABLE_SHIFT);  
  /*******2******/
    offset = dt_phys % SWAPPER_BLOCK_SIZE;  
    dt_virt = (void *)dt_virt_base + offset;  
  /*******3******
    /* map the first chunk so we can read the size from the header */  
    create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE),  
            dt_virt_base, SWAPPER_BLOCK_SIZE, prot);  
  
    if (fdt_magic(dt_virt) != FDT_MAGIC)  
        return NULL;  
  
    *size = fdt_totalsize(dt_virt);  
    if (*size > MAX_FDT_SIZE)  
        return NULL;  
  /*******4******/
    if (offset + *size > SWAPPER_BLOCK_SIZE)  
        create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE), dt_virt_base,  
                   round_up(offset + *size, SWAPPER_BLOCK_SIZE), prot);  
  
    return dt_virt;  
}   
  • (1)获取FDT的虚拟基地址
  • (2)获取实际的offset和虚拟地址
  • (3)映射第一块2M地址
  • (4)若跨2M区域,进行第二块2M映射
    在这里插入图片描述
Linux内存管理(十):memblock初始化
私房菜
07-05 978
源码基于:Linux5.4 在Linux kernel 初始化完成之后,系统中的内存分配和回收是由 buddy 系统、slab分配器来管理,但是在kernel 初始化阶段时内存的分配和释放是由memblock 分配器管理,记录物理内存的使用情况,本文主要介绍在系统启动阶段memblock 的初始化过程。 early boot memory 即系统上点到内核内存管理模型创建之前这段时间的内存管理,严格来说它是系统启动过程中的一个中间阶段的内存管理,当sparse 内存模型数据初始化完成之后,将会从boot m
Linux内存管理(八):fixmap详解
私房菜
04-12 1239
内核启动首先会进入汇编阶段,mmu已经启动 (也就是说,当前SOC只能使用虚拟地址访问RAM),paging_init还没有完成调用,在内核启动过程需要访问某些特定的内核模块 (例如 dtb)时,就需要将虚拟地址和物理地址进行映射。这就是fixmap 机制产生的原因。fixmap 理解为固定映射,其虚拟地址空间是为了早期 fdt、console、外设动态映射、paging_init()使用。下面会通过代码的方式分别来看下这几个固定映射区域的分配和使用。
SSD202D-kernel-uimage后面加入dtb
longmin96的博客
08-08 304
SSD202D-kernel-uimage后面加入dtb
将设备树.dtb合并到OS的镜像.bin中
csdnwxhw的博客
06-15 654
加载设备树的两条途径: 1、在uboot启动后,使用uboot所提供的命令,加载设备树; tftpboot [地址] xxx.dtb fdt addr [地址] 2、将.dtb直接嵌入到操作系统的镜像中,无需显式加载; .global fdt_blob FUNC_LABEL (fdt_blob) .incbin "xxx.dtb" FUNC_END (fdt_blob) //在sysInit入口处将fdt_blob放置到X20寄存器供后续使用 LDR X0,=fdt_blob MOV X20,X0
linux镜像中的dtb,一种在单Linux系统镜像中支持多DTB的方法及系统与流程
weixin_29380121的博客
05-14 376
技术特征:1.一种在单Linux系统镜像中支持多DTB的方法,其特征在于,包括:将多个不同的DTB文件整合成一个DTB整合文件,将DTB整合文件和包含同一个Linux系统镜像版本下的所有产品驱动的Linux系统镜像镜像,打包成Linux系统镜像文件;引导程序启动时,通过修改引导程序代码载入各产品对应的DTB文件。2.如权利要求1所述的一种在单Linux系统镜像中支持多DTB的方法,其特征在于:所述...
dtb
jason的笔记
11-26 1087
在kernel build目录下的scripts/dtc/dtc,会生成用于build dts的tool dtc,可以通过下面的数据 将dtb 反汇编成dts ./scripts/dtc/dtc  -I dtb -O dts arch/arm64/boot/dts/soc/soc.dtb > ~a.dts 在kernel的proc 目录下会存在config.gz, zcat confi
linux内存管理(上篇-物理内存组织)
01-16
Linux 内存管理(上篇-物理内存组织) Linux 内存管理是一个非常复杂的主题,本篇文章将着重介绍物理内存组织的概念和实现细节。 物理寻址和虚拟寻址 在 Linux 中, CPU 直接操作物理地址,读取 4 字节数据字。在...
Linux内存管理(十二):paging_init 详解
私房菜
07-14 808
从中得知在paging_init调用之前,存放KernelImage和DTB两端物理内存区域可以访问(相应的页表已经建立好)。在memblock初始化之后,物理内存已经添加到系统,但是这部分的物理内存到虚拟内存的映射还没有建立,可以通过memblock_alloc分配一段物理内存,但是无法访问,一切还需要等待paging_init之后,建立最终的页表,从而实现虚拟内存到物理内存的映射。VA_BITS=39,PAGE_SHIFT=12(4K页表).........
Linux内存管理(十一):reserved-memory 详解
私房菜
08-17 2332
随着内核的运行,内核中的物理内存越来越趋向于碎片化,但是某些特定的设备在使用时用到的 DMA 需要大量的连续物理内存,这可能导致设备在真正使用的时候因为申请不到满足要求的物理内存而无法使用,这显然是不能接受的。最简单的方式就是为特定设备预留一部分物理内存专用,这部分内存不受系统的管理,绑定到特定的设备,在设备需要使用的时候再对这部分内存进行管理,这就是内核中提供的。
DTB-温度控制器使用手册 简体中文.pdf
10-09
DTB-温度控制器使用手册 简体中文pdf,DTB-温度控制器使用手册 简体中文
Linux设备树DTB存储格式
qq_24601427的博客
05-17 2141
文章目录DTB存储格式DTB数据结构struct ftd_header区域数据结构memory reservation block区域数据结构struct block区域strings block DTB存储格式 头部(struct ftd_header):用来表明各个部分的偏移地址,整个文件的大小,版本号等等; 内存的保留信息块(memory reservation block):存放dts文件中申明的需要预留的内存的信息; 节点块(structure block):各个节点的信息将放在structu
Dtb文件组成
一墨的博客
08-25 2453
Dtb文件由4部分组成:fdt_header、memory reserve map、device-tree structure、device-tree strings。 前三部分都是4字节对齐,最后一部分——“device-tree strings”不要求4字节对齐。 struct fdt_header定义 struct fdt_header { fdt32_t magi...
Device Tree (二) - dtb格式
最新发布
滴水藏海
03-12 1585
dtb的文件格式
Linux设备树学习2 - DTB文件格式
to_be_better_wen的博客
11-26 4552
Linux 设备树的DTB文件详细介绍
设备树 dtb结构
qq_37932504的博客
01-26 1562
dtb结构由一个小的报头和三个大小可变的部分组成:内存预留块,结构块和字符串块。这些应该按这个顺序出现在扁平的设备树中。因此,设备树结构作为一个整体,当以地址载入内存时,将类似于下图的图(较低的地址位于图的顶部)。 注:内存预留块可能不存在,尽管在某些情况下可能需要它们来满足单个块的对齐约束。 自格式的原始定义以来,已经定义了几种扁平设备树结构的版本。 报头中的字段给出了版本,以便客户端程序可以确定设备树是否以兼容的格式编码。 本文档仅描述17版的格式。 兼容DTSpec的引导程...
dtb 文件的作用及生成
热门推荐
u010442934的专栏
06-25 2万+
dtb文件作用的描述是, 使用DTB文件 可以减少内核的版本数,比如同一块板子,在外设不同的情况下不使用dtb文件需要编译多个版本的内核。当使用dtb文件时同一份linux 内核代码可以在多个板卡上运行,每个板卡可以使用自己的dtb文件。2,PC机在启动时会自动扫描外设,而在嵌入式中,linux内核启动过程中只是解析dtb文件,从而加载对应的模块。3,编译linux内核时必须选择某外设模块,并且d...
2.2设备树的规范(dts和dtb)——DTB格式
一个嵌入式软件工程师的博客
12-16 8402
本节讲述设备的dtb格式。 上节讲述了dts格式。回顾上节,在dts文件和dtsi文件中,可以使用C语言的define和include,使用方法和作用也同C语言相同。 编写dts文件后,需要使用dtc工具将dts文件编译成dtb文件。dtc工具可以检查dts文件是否存在语法或格式错误,如果发现语法或格式上有错误,那么就会提示修改这些错误。 在dts文件中,可以包含一个或多个dtsi文件,通过dtc工具将这些文件编译得到一个dtb文件。同时,可以在dts文件中重新定义覆盖dtsi文件中设置的节点属性。
linux系统之驱动与FDT
eleven_xiy的博客
06-01 1万+
本文介绍linux驱动中扁平设备树FDT的实现方式。
内存管理(一)虚拟地址布局
进步源于记录,优秀来自分享
04-30 2268
Linux版本:4.14.74 #1 Linux虚拟内存布局# 在ARM64中,地址线由32bit变为64bit,但是64bit并不是全用到了,最大支持48位物理寻址,最大可寻找256T的物理地址空间,对于目前的应用来讲完全足够了。 虚拟地址的最大宽度可配置,最大为48bit,还可以有36bit,39bit,42bit,47bit 1. [arch/arm64/Kconfig] 2. ...
理解ARM板子设备树:DTS与DTB解析
DTS是描述硬件信息的文本文件,包括CPU、内存、中断控制器、外设等信息,而DTB是DTS编译后的二进制文件,被Linux内核用来匹配和加载相应的驱动程序。资料详细介绍了Device Tree规范,包括节点命名、属性定义、标准...
写文章

热门文章

  • linux驱动(一):linux驱动框架 36319
  • linux SPI读写过程 16007
  • printf 制表符格式对齐 11697
  • NFS服务器的安装和测试 8266
  • ARM架构过程调用标准AAPCS 7606

分类专栏

  • C++ 9篇
  • Linux内存管理 10篇
  • 开发工具使用 2篇
  • Linux 学习 64篇
  • flash 2篇
  • 数据结构及算法 4篇
  • C语言积累 4篇
  • 协议/接口 2篇
  • 调试错误相关
  • 俺也不知道
  • RTOS 4篇
  • LWIP 3篇
  • 出错记录
  • qnx

最新评论

  • LWIP内存管理之动态内存池

    coding-tan: 可以借鉴LWIP库的内存池实现

  • 详解应用层open函数如何调用到底层驱动中xxx_open函数

    qintianzhuzuidiao: 好图,请问还有这系列其它类似图吗?

  • 内存管理(四):DTB的映射

    gITACHI: dtb 2M块映射这一块是不是有问题:dtb部分是要访问物理内存的,是以pte表项 4k进行映射的吧?这一块看代码并非block映射。

  • linux SPI读写过程

    hehui0921: 如果从设备发给master的数据不读,会一直放在内存中吗?

  • linux SPI读写过程

    hehui0921: 说了这么多,能不能写出一个例子来看看是怎么读的?

大家在看

  • 语雀技巧:文档内部跳转 1
  • 物理学基础精解【115】 444
  • 科普文:软件架构数据库系列之【MySQL中事务、隔离级别、并发之间的关系以及如何解决幻读和不可重复读】 322
  • 一分钟学会MATLAB-时间序列预测模型 788
  • 如何在 CentOS 7 上使用 Nginx 将 www 重定向到非 www 862

最新文章

  • 内存管理(十):伙伴分配器
  • 内存管理(九):内存分配概述
  • 内存管理(八):zone的初始化
2020年13篇
2019年14篇
2018年36篇
2017年49篇
2015年2篇

目录

目录

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值

玻璃钢生产厂家浙江玻璃钢雕塑安装都匀玻璃钢雕塑厂金刚玻璃钢雕塑图片绍兴大型玻璃钢花盆淄博玻璃钢动物雕塑河北超市商场美陈销售公司苏州玻璃钢花盆设计金华玻璃钢海豚雕塑厂家玻璃钢熊本熊造型雕塑堆头美陈图片商场 效果图商场美陈卡通人物雕塑曲阳玻璃钢雕塑墙上挂蜘蛛侠玻璃钢雕塑品质玻璃钢雕塑加玻璃纤维短切丝玻璃钢仿铜人物雕塑品牌玻璃钢花箱放花盆池州户外玻璃钢雕塑市场卡通雕塑玻璃钢平台哪个好特色玻璃钢雕塑摆件售价河北保定玻璃钢雕塑雕塑厂家吉安人物玻璃钢雕塑批发奥体春节商场美陈深圳 玻璃钢雕塑厂家玻璃钢花盆惠新复材111河南装饰商场美陈厂家直销武汉护栏玻璃钢花盆玻璃钢鹦鹉雕塑安徽玻璃钢雕塑企业周口玻璃钢人物喷泉雕塑攀枝花玻璃钢雕塑厂家香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化