OK6410A 开发板 (八) 10 linux-5.11 OK6410A start_kernel 打印角度 第一阶段 setup_arch

setup_arch 的 简化过程
1. 处理器初始化(cache itcm dtcm)
2. 匹配 板级 mdesc , 并解析 atags(包括core cmdline mem)
3. early_fixmap_init, 为 early console 的 设备地址 做映射做准备
4. parse_early_param , 解析 cmdline  中的 earlycon earlyprintk , 并初始化 uart 设备
5. 初始化 各种类型的 描述符表
6. 添加 各种(kernel/initrd/设备树/atags/设备树中预留的/)预留的内存到 memblock
7. 对 即将存储页表的地址 清0
8. 针对 多项内容(lowmem/设备树/vectors/各项设备)填充页表
9. 创建zero_page
10. 申请内存,用于存储 很多个struct  page 
11. 初始化所有的 struct page
13. request_resource 很多内容(整个内存/kernel_code/kernel_data)
  • setup_arch(&command_line) 打印的信息
CPU: ARMv6-compatible processor [410fb766] revision 6 (ARMv7), cr=00c5387d
CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
Machine: SMDK6410
Memory policy: Data cache writeback
Samsung CPU ID: 0x36410101
CPU S3C6410 (id 0x36410101)
CPU: found DTCM0 8k @ 0c002000, enabled
CPU: moved DTCM0 8k to fffe8000, enabled
CPU: found DTCM1 8k @ 0c004000, enabled
CPU: moved DTCM1 8k to fffea000, enabled
CPU: found ITCM0 8k @ 00000000, not enabled
CPU: moved ITCM0 8k to fffe0000, enabled
CPU: found ITCM1 8k @ 00000000, not enabled
CPU: moved ITCM1 8k to fffe2000, enabled
Zone ranges:
  Normal   [mem 0x0000000050000000-0x000000005fffffff]
Movable zone start for each node
Early memory node ranges
  node   0: [mem 0x0000000050000000-0x000000005fffffff]
Initmem setup node 0 [mem 0x0000000050000000-0x000000005fffffff]
CPU: All CPU(s) started in SVC mode.
setup_arch
	atags_vaddr = 0xff800100;
	setup_processor
		// 设置 cache 相关
	mdesc = setup_machine_tags(atags_vaddr, __machine_arch_type);
		// 做两件事
		// 1. 根据 board id (即__machine_arch_type) mdesc
		// 2. 根据 atags_vaddr , 找 函数 parse 每一个 atags // 函数 为  __tagtable_begin 到 __tagtable_end 地址 中的 变量的 parse 成员
		// u-boot 设置了 setup_start_tag setup_commandline_tag setup_memory_tags setup_end_tag
		// 需要  __tagtable_parse_tag_core __tagtable_parse_tag_cmdline __tagtable_parse_tag_mem32 解析
		// 依次做了
		// core : root_mountflags &= ~MS_RDONLY; ROOT_DEV = old_decode_dev(tag->u.core.rootdev);
		// cmdline : strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
		// mem32 : arm_add_memory(tag->u.mem.start, tag->u.mem.size);
	machine_desc = mdesc; // arch/arm/mach-s3c/mach-smdk6410.c 中的 MACHINE_START(SMDK6410, "SMDK6410")
	machine_name = mdesc->name; //  "SMDK6410"
	dump_stack_set_arch_desc("%s", mdesc->name);
		// 填充 dump_stack_arch_desc_str  为 "SMDK6410"
	
	init_mm.start_code = (unsigned long) _text; // c0008000
	init_mm.end_code   = (unsigned long) _etext; // c0600000
	init_mm.end_data   = (unsigned long) _edata; // c088e548
	init_mm.brk	   = (unsigned long) _end; // 等于 __bss_stop , c08c413c
	strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
	*cmdline_p = cmd_line;
	early_fixmap_init
		// 写了两个一级页表
		// 可以通过虚拟地址 ffef f000 索引 物理地址(1MB,还没填充) ? 
		// 消费者 earlycon_map->set_fixmap_io->__set_fixmap
	early_ioremap_init
		early_ioremap_setup
			// 填充了个数组?
			// slot_virt[0](地址看看 System.map) :  ffe1f000
			// slot_virt[1] : ffe3f000
			// slot_virt[2] : ffe5f000
			// slot_virt[3] : ffe7f000
			// slot_virt[4] : ffe9f000
			// slot_virt[5] : ffebf000
			// slot_virt[6] : ffedf000
		// 消费者 early_memremap->__early_ioremap
		// 消费者 early_ioremap->__early_ioremap
		// 在 OK6410A-linux-5.11 中 , 没有对 early_ioremap 的使用
		// early_ioremap  的地址 属于 temporary fixed address
	parse_early_param
		// 此次 为 parse_args "early options"
		// 处理用 early_param 定义的参数 
		// 例如 early_param("debug",debug_kernel); 
		/*
			1.  parse_args "early options"
					early_param
			2.  parse_args "Booting kernel"
					module_param
					__setup
			3.  parse_args initcall_level_names[level]
					module_param
		*/
		// 该实例中 无 early_param  对应的 commandline 字符串

	early_mm_init(mdesc);
		build_mem_type_table
			// 在 arm-linux中,所有的页表类型 为 以下 13种
			// arch/arm/include/asm/mach/map.h 中 的 MT_MEMORY_RW
			
			// 填充 mem_types , 该 变量可用做接下来的一级描述符表内容,二级描述符表内容
			// TODO : struct mem_type mem_types 成员的作用及值
			// 消费者 为  create_mapping
		early_paging_init(mdesc);
			// null
	setup_dma_zone(mdesc);
		// null
	xen_early_init
		// null
	efi_init
		// null
	adjust_lowmem_bounds
		// 调整低端内存的边界
		// memblock.current_limit:60000000
	arm_memblock_init
		1. 预留 kernel // 静态内存
			memblock_reserve(__pa(KERNEL_START), KERNEL_END - KERNEL_START); 
			// 添加到 memblock.reserved 中
			// pa:50100000,size:7c413c
		2. 预留 initrd // 静态内存
			arm_initrd_init
				// null
		3. 预留 页表
			arm_mm_memblock_reserve
				// pa:50004000,size:4000
		4. 预留 板级desc中 要求预留的内存
			mdesc->reserve
		5. 预留 fdt中 要求预留的内存 (reserved-mem节点) // 一般又DMA管理
			early_init_fdt_scan_reserved_mem
		6. CMA(连续内存分配器) 的初始化 // 申请了一个公共的CMA区域
			dma_contiguous_reserve(arm_dma_limit) // arm_dma_limit : ffffffff
			// null , 因为没有配置 CONFIG_DMA_CMA
			// 位于CMA管理区的内存,有映射页表,对于内核是  可见的
			// 位于DMA管理区的内存,无映射页表,对于内核是不可见的
		7. arm_memblock_steal_permitted = false;
		8. memblock_dump_all
			/*
			MEMBLOCK configuration:
			 memory size = 0x10000000 reserved size = 0x007c813c
			 memory.cnt  = 0x1
			 memory[0x0]    [0x50000000-0x5fffffff], 0x10000000 bytes flags: 0x0
			 reserved.cnt  = 0x2
			 reserved[0x0]  [0x50004000-0x50007fff], 0x00004000 bytes flags: 0x0
			 reserved[0x1]  [0x50100000-0x508c413b], 0x007c413c bytes flags: 0x0
			*/
	adjust_lowmem_bounds
		// memblock.current_limit:60000000
	early_ioremap_reset
		early_ioremap_shutdown
			// null
		after_paging_init = 1;
	paging_init // 该函数 中共有 19次 create_mapping
		prepare_page_table
			// 将 即将写入的页表地址(c0004000-c000741f)清0
			// 1. for (addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr));
			// Clear out all the mappings below the kernel image.
			// addr:0,pmd_off_k(addr):c0004000
			// addr:200000,pmd_off_k(addr):c0004008
			// ...
			// bee00000,pmd_off_k(addr):c0006fb8
			// 2. for ( ; addr < PAGE_OFFSET; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr));
			// Clear out all the mappings below the kernel image.
			// addr:bf000000,pmd_off_k(addr):c0006fc0
			// addr:bf200000,pmd_off_k(addr):c0006fc8
			// ...
			// addr:bfe00000,pmd_off_k(addr):c0006ff8
			// 3. for (addr = __phys_to_virt(end); addr < VMALLOC_START; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr));
			// Clear out all the kernel space mappings, except for the first memory bank, up to the vmalloc region.
			// addr:d0000000,pmd_off_k(addr):c0007400
			// addr:d0200000,pmd_off_k(addr):c0007408
			// addr:d0400000,pmd_off_k(addr):c0007410
			// addr:d0600000,pmd_off_k(addr):c0007418
		map_lowmem
			// 根据 memblock 中的内存信息创建映射
			create_mapping(&map); // 在这里,映射完了板子的所有物理内存 0x50000000 - 0x60000000 
				// 1. map.pfn:50000,map.virtual:c0000000,map.length:100000,map.type:a
				// 2. map.pfn:50100,map.virtual:c0100000,map.length:700000,map.type:9
				// 3. map.pfn:50800,map.virtual:c0800000,map.length:f800000,map.type:a
		memblock_set_current_limit(arm_lowmem_limit); // 60000000
		dma_contiguous_remap
			// null
		early_fixmap_shutdown
			// no map 
			// TODO
		devicemaps_init
			create_mapping(&map);
			create_mapping(&map);
			create_mapping(&map);
			mdesc->map_io/smdk6410_map_io
				s3c64xx_init_io(smdk6410_iodesc, ARRAY_SIZE(smdk6410_iodesc));
					iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc)); // 10个成员
					iotable_init(smdk6410_iodesc, size); // 1个成员
				
			
			// 14 个 map
			// 1. create a read-only mapping of the device tree 
			// map.pfn:50000,map.virtual:ff800000,map.length:200000,map.type:b
			// 2. Create a mapping for the machine vectors at the high-vectors location (0xffff0000).
			// map.pfn:5fffe,map.virtual:ffff0000,map.length:1000,map.type:8
			// 3. Now create a kernel read-only mapping
			// map.pfn:5ffff,map.virtual:ffff1000,map.length:1000,map.type:7
			
			以上 1 做了 设备树/atags的映射,设备树/atags在内存中,但是这里额外做一次映射.那么现在设备树/atags的物理地址对应了两个虚拟地址
				// 为了兼容start_kenrel之前做的 atags 映射?
			以上 2 做了 向量表的映射,向量表在内存中,但是这里额外做一次映射,那么现在向量表的物理地址对应了两个虚拟地址
				// 是因为 high vectors configured , pc = 0xFFFF0000
			
			............................................................

			// 4-14. Create the architecture specific mappings
			// map.pfn:7e00f,map.virtual:f6100000,map.length:1000,map.type:0 // S3C64XX_PA_SYSCON
			// map.pfn:70000,map.virtual:f6200000,map.length:1000,map.type:0 // S3C64XX_PA_SROM
			// map.pfn:7f005,map.virtual:f7005000,map.length:1000,map.type:0 // S3C_PA_UART
			// map.pfn:71200,map.virtual:f6000000,map.length:4000,map.type:0 // S3C64XX_PA_VIC0
			// map.pfn:71300,map.virtual:f6010000,map.length:4000,map.type:0 // S3C64XX_PA_VIC1
			// map.pfn:7f006,map.virtual:f6300000,map.length:4000,map.type:0 // S3C_PA_TIMER
			// map.pfn:7f008,map.virtual:f6500000,map.length:1000,map.type:0 // S3C64XX_PA_GPIO
			// map.pfn:74108,map.virtual:f6600000,map.length:1000,map.type:0 // S3C64XX_PA_MODEM
			// map.pfn:7e004,map.virtual:f6400000,map.length:1000,map.type:0 // S3C64XX_PA_WATCHDOG
			// map.pfn:7c100,map.virtual:f6700000,map.length:0400,map.type:0 // S3C64XX_PA_USB_HSPHY
			// map.pfn:77100,map.virtual:f7100000,map.length:4000,map.type:0 // S3C_PA_FB
		kmap_init
		tcm_init
			for (i = 0; i < dtcm_banks; i++) setup_tcm_bank
			for (i = 0; i < itcm_banks; i++) setup_tcm_bank
				
			// map.pfn:fffe8,map.virtual:fffe8000,map.length:4000,map.type:d
			// map.pfn:fffe0,map.virtual:fffe0000,map.length:4000,map.type:e
		top_pmd = pmd_off_k(0xffff0000);
		zero_page = early_alloc(PAGE_SIZE);
			// 第一次使用 memblock 内存管理器的 内存申请API
		bootmem_init
			// 在 linux-3.0.1 中 这里 存在着 arm_bootmem_init 
			// 在arm_bootmem_init 中 bootmem 接替 memblock 的内存管理
			// 并且 向 其他的消费者 提供 申请释放内存的接口
			// 在 linux-5.11 就没有了,看来是 memblock 已经完成了 申请释放内存的接口
			// memblock API 实例 :  request_standard_resources->memblock_alloc
			// linux-3.0.1 中也提供了 memblock ,只不过 memblock  返回的是物理地址,也就是说 其管理的是 物理内存?
			// linux-5.11 中提供的 memblock ,返回的是 虚拟地址,也就是说其管理的是 虚拟内存?
			memblock_allow_resize // memblock 已经初始化完成?
				memblock_can_resize = 1;
			find_limits(&min_low_pfn, &max_low_pfn, &max_pfn);
			early_memtest
			sparse_init
			zone_sizes_init	// 开始初始化 buddy
				free_area_init
		empty_zero_page = virt_to_page(zero_page);
		__flush_dcache_page(NULL, empty_zero_page);

			/*
			zero_page // 物理页面
				https://www.it1352.com/1526752.html
				1. 您的存储区域中的数据为零,则可以将此数据映射到零页,并使用"0"数据释放这些页.换句话说,这与内核如何节省内存有关
				2. 还允许分配一个大数组,但不消耗内存.所有页面最初都是零页面,并且映射到相同的物理零页面.如果数组稀疏,则只有少数条目(4k大小)会占用内存.内核不需要清理(零)分配的内存.在填充条目中不会浪费'tlb'和'cache'.
			
				总之
					0. 这是一个物理页面, 且被填充了 0 
					1. 只要消费者的虚拟页面是 0 , 且是只读的, 则可以映射
					2. 消费者的虚拟页面是可写的,那么 也可以映射,触发COW
			
			*/


	kasan_init
		// null
	request_standard_resources
		res->name  = "System RAM";
		res->start = start; // 50000000
		res->end = res_end; // 5fffffff
		res->flags = IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY;
	
		request_resource(&iomem_resource, res); // 注冊内存到iomem_resource
	
		kernel_code.start:50008000
		kernel_code.end:506fffff
		request_resource(res, &kernel_code); // 注册 kernel_code 到 内存
	
		kernel_data.start:50800000
		kernel_data.end:5108013b
		request_resource(res, &kernel_data); // 注册 kernel_data 到 内存

	unflatten_device_tree
		// null
	arm_dt_init_cpu_maps
		// null
	psci_dt_init
		// null
	hyp_mode_check
		// TODO
		// 虚拟化CONFIG_ARM_VIRT_EXT相关,先不管
	reserve_crashkernel
		// null
	handle_arch_irq = mdesc->handle_irq; //值 为 null
	
	if (mdesc->init_early) // null
		mdesc->init_early();
		
其他
arch/arm/include/asm/pgalloc.h
static inline void __pmd_populate(pmd_t *pmdp, phys_addr_t pte, pmdval_t prot)
https://github.com/novelinux/linux-4.x.y/blob/master/arch/arm/include/asm/pgalloc.h/__pmd_populate.md

early_fixmap_init // 设置的是一级页表
	pmd_populate_kernel(&init_mm, pmd, bm_pte); // pmd : c0007ff8
		__pmd_populate(pmdp, __pa(ptep), _PAGE_KERNEL_TABLE); // __pa(ptep):50724000 _PAGE_KERNEL_TABLE:11(十进制)
			// __pmd_populate 建立了两个一级页表
			// 第一个一级页表 的描述符 bit[10] 比 第二个一级页表的描述符 bit[10] 少 1
			// 即 pmdval_1 + 256 * sizeof(pte_t) = pmdval_2
			
			pmdp[0] = __pmd(pmdval); // addr : c0007ff8 , value :50724811 // 50724000 + 512*4 | 11
			pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t)); // addr : c0007ffc ,value : 50724811+256*4 = 50724c11
			
		
	
create_mapping  的实现
create_mapping
	__create_mapping
		alloc_init_p4d
			alloc_init_pud
				alloc_init_pmd
					__map_init_section 	// if (type->prot_sect && ((addr | next | phys) & ~SECTION_MASK) == 0) 满足,段表
						*pmd = __pmd(phys | type->prot_sect | (ng ? PMD_SECT_nG : 0));
					alloc_init_pte 		// if (type->prot_sect && ((addr | next | phys) & ~SECTION_MASK) == 0) 不满足,页表
						arm_pte_alloc
							alloc
							__pmd_populate
								pmdp[0] = __pmd(pmdval);
								pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t));

create_mapping  写地址和写入的值
	paging_init 函数 中共有 19次 create_mapping
	19 次直接来自于 paging_init
		3次 来自于 map_lowmem
			1次 Map all the lowmem memory banks.
				addr:c0007000,value:5000041e 	// 最低两位为10表明是段表
			1次 Map all the lowmem memory banks.
				addr:c0007004,value:5010040e
				addr:c0007008,value:5020040e
				...
				addr:c000701c,value:5070040e
			1次 Map all the lowmem memory banks.
				addr:c0007020,value:5080041e
				addr:c0007024,value:5090041e
				...
				addr:c00073fc,value:5ff0041e
		14次来自于 devicemaps_init
			1次 create a read-only mapping of the device tree
				addr:c0007fe0,value:5000840e
				addr:c0007fe4,value:5010840e

			1次 Create a mapping for the machine vectors at the high-vectors location (0xffff0000).
				addr:c0007ff8,value:5fffd861 PAGING // 最低两位为01表明是页表
				addr:c0007ffc,value:5fffdc61 PAGING
			1次 Now create a kernel read-only mapping
				null // BUG_ON(pmd_bad(*pmd));
			11次 mdesc->map_io -> iotable_init
				addr:c0007d80,value:5fffb841 	// 第1次 PAGING
				addr:c0007d84,value:5fffbc41 	// 第1次 PAGING
				addr:c0007d88,value:5fffa841 	// 第2次 PAGING
				addr:c0007d8c,value:5fffac41 	// 第2次 PAGING
				addr:c0007dc0,value:5fff9841 	// 第3次 PAGING
				addr:c0007dc4,value:5fff9c41 	// 第3次 PAGING
				addr:c0007d90,value:5fff8841 	// 第7次 PAGING
				addr:c0007d94,value:5fff8c41 	// 第7次 PAGING
				addr:c0007d98,value:5fff7841 	// 第8次 PAGING
				addr:c0007d9c,value:5fff7c41 	// 第8次 PAGING
				addr:c0007ff0,value:5fff6811 	// 第11次 PAGING
				addr:c0007ff4,value:5fff6c11 	// 第11次 PAGING

		2次 来自于 tcm_init
			1iotable_init(dtcm_iomap, 1);
				null
			1iotable_init(itcm_iomap, 1);
				null
create_mapping  产生的效果
	其实就是在页表/段表的位置写了页表/段表描述符
	然后应用程序在 访问虚拟地址的时候,就可以访问到其对应的物理地址
	
	以 下面为例
	map.pfn:50000,map.virtual:c0000000,map.length:100000,map.type:a
	addr:c0007000,value:5000041e
	1.
		在 虚拟地址 c0007000(对应物理地址50007000) 处 写入了 值(段表描述符) 5000041e
	2.
		访问 虚拟地址 c0000000的时候, 就可以访问到其对应的物理地址 50000 000
		访问 虚拟地址 c0000004的时候, 就可以访问到其对应的物理地址 50000 004
		...
		访问 虚拟地址 c0100000的时候, 就可以访问到其对应的物理地址 50100 000
		


	
early_fixmap_init 的消费者


消费者 : earlycon_map->set_fixmap_io->__set_fixmap

early_param("earlycon", param_setup_earlycon); // earlycon 的实验 TODO early_printk
	param_setup_earlycon
		early_init_dt_scan_chosen_stdout // 设备树
			of_setup_earlycon
				earlycon_map
		setup_earlycon 					 // atags bootargs
			register_earlycon
				earlycon_map

early_ioremap_init 的消费者

copy_from_early_mem
	early_memremap
		__early_ioremap
			return (void __iomem *)(offset + slot_virt[slot]);	
arch/arm/include/asm/pgtable-2level.h
223 #define pmd_clear(pmdp)         \                                                
224     do {                \                                                        
225         pmdp[0] = __pmd(0); \                                                    
226         pmdp[1] = __pmd(0); \                                                    
227         clean_pmd_entry(pmdp);  \                                                
228     } while (0)

zone_sizes_init
	free_area_init(max_zone_pfn);
		for_each_online_node free_area_init_node(nid)
			alloc_node_mem_map // 申请空间用来存 所有的 struct page
				map = memblock_alloc_node(size, SMP_CACHE_BYTES, pgdat->node_id); // size: 0x20 0000 / 2M // map :cfdf5000 
				pgdat->node_mem_map = map + offset; // cfdf5000 + 0 =  cfdf5000 ; 
				
			free_area_init_core
				for (j = 0; j < MAX_NR_ZONES; j++)
				{
					zone_init_internals
					init_currently_empty_zone
						zone_init_free_lists
					memmap_init
						memmap_init_zone(size, nid, zone, start_pfn, range_end_pfn, MEMINIT_EARLY, NULL, MIGRATE_MOVABLE); // start_pfn : 50000 ,end_pfn:60000
							for (pfn = start_pfn; pfn < end_pfn; pfn++ ) //运行 0x10000 次 , 65536次 // 
							{
								page = pfn_to_page(pfn); 
								// page 地址范围 为 cfdf5000 - cfff4fe0 , 间隔 为 0x20 (一个page结构体大小为0x20)
								// 所有的page 共 2MB大小,
								// 物理内存 共 256MB大小 ,
								// 2M大小的 pages 管理 256MB物理内存
								// cfff5000 - cfffffff  共 0xB000/45056/44KB字节
								__init_single_page(page, pfn, zone, nid);
									// 初始化 page 结构体变量
									// 1. 建立 与 zone 的联系
									// 2. 标记其 被使用 还是 free?
								if (context == MEMINIT_HOTPLUG) __SetPageReserved(page);
								if (IS_ALIGNED(pfn, pageblock_nr_pages)) { set_pageblock_migratetype(page, migratetype);  cond_resched();}
							}
								
							
				}	
					
				
			
			
			


数据 
start_pfn:50000,end_pfn:60000,num:0x10000 // 65536个
0x10000struct page , 地址 从 cfdf5000 - cfff4fe0
	cfdf5000
	cfdf5020
	...
	cfff4fe0

memblock 在 setup_arch函数中的消费者

paging_init // 3
	devicemaps_init // 2
		
		vectors = early_alloc(PAGE_SIZE * 2); // 1
			memblock_alloc
				reserved[0x2]  [0x5fffe000-0x5fffffff], 0x00002000 bytes flags: 0x0

		mdesc->map_io // 1
			s3c64xx_init_io
				iotable_init
					memblock_alloc
						reserved[0x3]  [0x5fffce70-0x5fffffff], 0x00003190 bytes flags: 0x0
					memblock_alloc
						reserved[0x3]  [0x5fffce48-0x5fffffff], 0x000031b8 bytes flags: 0x0

	bootmem_init // 1
		zone_sizes_init
			free_area_init_node
				alloc_node_mem_map
					memblock_alloc_node
						reserved[0x4]  [0x5fdf5000-0x5fffbfff], 0x00207000 bytes flags: 0x0


request_standard_resources // 1
	memblock_alloc
		reserved[0x5]  [0x5fffcd80-0x5fffcd9f], 0x00000020 bytes flags: 0x0

request_resource
	https://blog.csdn.net/dahailinan/article/details/111691678
	只是看到 将 io资源 用 树结构 来表示出来
	但是 用途 是什么,还不知道

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值