uboot分析2018.11(一)

 

 

一.配置选择:
----------------------

配置取决于板子和cpu的类型,所有的这些信息都封装在配置文件中
"include/configs/<board_name>.h".

例如:对于TQM823L 模块,所有的设置都在 
"include/configs/TQM823L.h".

 

1.选择一个处理器架构和板子类型:
---------------------------------------------------

所有支持的板子有一个可用的默认配置,仅仅通过 "make <board_name>_defconfig".

例如: 对于TQM823L 模块类型:

        cd u-boot
        make TQM823L_defconfig

这样就会在当前顶层或者指定的编译目录下生成一个.config 文件。 如果有需要, 可以使用 make
menuconfig 进行修改保持。

注意:如果你正在为一个板子找默认配置文件, 你以前确定用过但是现在找不到了,查看doc/README文档,这里面是不在支持的板子清单。

2.配置编译的两种方式:一般默认在本地进行编译,文件也存在源码目录中。但有两种方式去改变这种方式,可以在源码外目录进行编译

 

a. 添加 O=make命令行:

        make O=/tmp/build distclean
        make O=/tmp/build NAME_defconfig
        make O=/tmp/build all

b. 设置环境变量 KBUILD_OUTPUT去指向预定位置

        export KBUILD_OUTPUT=/tmp/build
        make distclean
        make NAME_defconfig
        make all

注意: 这个命令行输入 "O=" 会覆盖 KBUILD_OUTPUT 环境变量

二.添加不在配置列表的板子配置。如有系统板子不在配置列表内,然后你需要把uboot移植到一个硬件平台。那么你需要有这些步骤:

1) 创建新目录去存放你自己板子的代码,添加一些你需要的文件。在你自己板子目录,至少需要一个Makefile和一个“<board.c>”文件。 
2)为你的板子创建一个新的配置文件"include/configs/<board>.h" 
3)如果你正在移植 U-Boot 到一个新的 CPU, 然后也需要创建一个新的目录去存放CPU的特定代码,并添加一些特定代码
4)运行"make <board>_defconfig" 用你新的板子名字.
5) 类型"make", 你需要得到一个工作 "u-boot.srec"文件,并安装到你的目标系统中
6)调试和解决可能出现的任何问题。当然最后一步比听起来更困难。
三.u-boot编译

1.指定编译器,我用的是STM32MP157的板子,一般编译之前会配置SDK,会执行环境脚本source <$PATH>/environment-setup-cortexa7t2hf-neon-vfpv4-openstlinux_weston-linux-gnueabi

注意:其他板子不一样,两种方式:在编译时指定make CROSS_COMPILE=xxxxx,或者修改顶层Makefile。

2.uboot编译分为两步:配置  编译

1)第一步配置:执行make   xxx_defconfig进行配置,生成.config文件

配置完成后也可以生成索引文件,在配置完毕生成.config 后, 可以生成 tags 和 cscope 索引文件, 方便用 vim 查
看。

  • make tags
  • make cscope

配置过程分析:

执行 make stm32mp15_trusted_defconfig 后, 会匹配到顶层 Makefile 的:

495 %config: scripts_basic outputmakefile FORCE
496     $(Q)$(MAKE) $(build)=scripts/kconfig $@

 对于目标,stm32mp15_trusted_defconfig,展开则有:

495 stm32mp15_trusted_defconfig: scripts_basic outputmakefile FORCE
496     $(Q)$(MAKE) $(build)=scripts/kconfig stm32mp15_trusted_defconfig

 其中$(build)kbuild.include中定义:

build := -f $(srctree)/scripts/Makefile.build obj

所以, 会执行类似如下命令:
make -f ./scripts/Makefile.build obj=scripts/kconfig stm32mp15_trusted_defconfig 

在 Makefile.build 会 include 目录 scripts/kconfig/下的 Makefile, 其中含有处理
stm32mp15_trusted_defconfig  的命令:

127 %_defconfig: $(obj)/conf                                                    
128     $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

 展开后就是:
scripts/kconfig/conf –defconfig=arch/../configs/stm32mp15_trusted_defconfig 

执行 make menuconfig 的逻辑类似, 首先会匹配到顶层 Makefile 的:

 492 config: scripts_basic outputmakefile FORCE
 493     $(Q)$(MAKE) $(build)=scripts/kconfig $@

然后匹配到 scripts/kconfig/Makefile 中的:

 34 menuconfig: $(obj)/mconf                                                    
 35     $< $(silent) $(Kconfig)

展开后就是:
scripts/kconfig/mconf Kconfig

上面的 Kconfig 表示顶层目录下的 Kconfig 文件, 这个文件会 source 其他的
Kconfig, 结构大致如下:
顶层 Kconfig
---- > arch/Kconfig
     ---- > arch/arm/Kconfig
          ---- > arch/arm/mach-exynos/Kconfig
          ---- > arch/arm/mach-stm32mp/Kconfig
                     ---- > board/st/stm32mp1/Kconfig
     ----> arch/x86/Kconfig
---- > ...
---- > api/Kconfig
---- > common/Kconfig
---- > cmd/Kconfig
---- > disk/Kconfig
---- > dts/Kconfig
---- > env/Kconfig
---- > net/Kconfig
---- > drivers/Kconfig
---- > fs/Kconfig
---- > lib/Kconfig
---- > test/Kconfig

make menuconfig可以从之前的.config文件中继续增加配置并保存。

 

2)第二步编译:执行make  all,生成uboot*.

编译 u-boot.bin

在执行 make 时, 会从顶层 Makefile 开始解析, 默认的目标是 all。
 

913 all:        $(ALL-y) cfg

921     @# Check that this build does not use CONFIG options that we do not
922     @# know about unless they are in Kconfig. All the existing CONFIG
923     @# options are whitelisted, so new ones should not be added.
924     $(call cmd,cfgcheck,u-boot.cfg)
  • $@:表示所有脚本参数的内容
  • $#:表示返回所有脚本参数的个数

 其中 ALL-y 会根据配置扩展成需要编译的目标, 根据stm32mp157 的配置, ALL-y 的值如
下:

804 # Always append ALL so that arch config.mk's can add custom ones
805 ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map binary_size_check

828 ALL-$(CONFIG_OF_SEPARATE) += u-boot.dtb



ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map binary_size_check
 u-boot.dtb
顶层 Makefile 会包含:
 

551 ifeq ($(autoconf_is_old),)
552 include config.mk
553 include arch/$(ARCH)/Makefile
554 endif

顶层文件 config.mk 比较重要, 其中含有以下比较重要的变量。 这些变量会依赖一些在
配置时设置的宏, 比如: (这个几个宏的作用可以参考 arch/Kconfig)
CONFIG_SYS_ARCH、 CONFIG_SYS_BOARD、 CONFIG_SYS_VENDOR、 CONFIG_SYS_SOC
从.config里面可以获得这几个宏的值

 19 CONFIG_SYS_ARCH="arm"
 20 CONFIG_SYS_CPU="armv7"
 21 CONFIG_SYS_SOC="stm32mp"
 22 CONFIG_SYS_VENDOR="st"                                                                      
 23 CONFIG_SYS_BOARD="stm32mp1"
 24 CONFIG_SYS_CONFIG_NAME="stm32mp1"

在 stm32mp15_trusted_defconfig 中定义了宏: CONFIG_ARM=y, 那么在 arch/arm/Kconfig
中会根据下面的配置生成 CONFIG_SYS_ARCH="arm“

   4 config SYS_ARCH
   5     default "arm"

在 stm32mp15_trusted_defconfig 中定义了宏:CONFIG_ARCH_STM32MP=y, 那么
arch/arm/mach-stm32mp/Kconfig 会 select CPU_V7A, 然后 arch/arm/Kconfig 中会将CONFIG_SYS_CPU 设置为”armv7"

 252 config SYS_CPU                                                                              
 253     default "arm720t" if CPU_ARM720T
 254     default "arm920t" if CPU_ARM920T
 255     default "arm926ejs" if CPU_ARM926EJS
 256     default "arm946es" if CPU_ARM946ES
 257     default "arm1136" if CPU_ARM1136
 258     default "arm1176" if CPU_ARM1176
 259     default "armv7" if CPU_V7A
 260     default "armv7" if CPU_V7R
 261     default "armv7m" if CPU_V7M
 262     default "pxa" if CPU_PXA
 263     default "sa1100" if CPU_SA1100
 264     default "armv8" if ARM64

在arch/arm/mach-stm32mp/Kconfig中会设置 CONFIG_SYS_SOC
为”stm32mp”:
 

 27 config SYS_SOC
 28     default "stm32mp" 


board/st/stm32mp1/Kconfig 中会设置, CONFIG_SYS_BOARD 为” default "stm32mp1"”,
CONFIG_SYS_VENDOR 为” default "st"”, CONFIG_SYS_CONFIG_NAME 为”default "stm32mp1"”:

  1 if TARGET_STM32MP1
  2 
  3 config SYS_BOARD
  4     default "stm32mp1"
  5 
  6 config SYS_VENDOR
  7     default "st"
  8 
  9 config SYS_CONFIG_NAME
 10     default "stm32mp1"
 11 
 12 config CMD_STBOARD
 13     bool "stboard - command for OTP board information"
 14     default y
 15     help
 16       This compile the stboard command to
 17       read and write the board in the OTP.
 18 
 19 endif 

在顶层config.mk中会根据这几个宏执行如下操作:

ARCH=arm
CPU=armv7
BOARD=stm32mp1
VENDOR=st
SOC=stm32mp
CPUDIR=arch/arm/cpu/armv7
BOARDDIR=st/stm32mp1
sinclude ./arch/arm/config.mk
sinclude ./arch/arm/cpu/armv7/config.mk
sinclude ./arch/arm/cpu/armv7/st/stm32mp1/config.mk
sinclude ./board/st/stm32mp1/config.mk

继续分析顶层 Makefile
在 include 完 config.mk 后, 第 553 行,就会 include arch/$(ARCH)/Makefile
, 这个文件干如下几件事:

arch-y =-march=armv7

48 tune-y := $(tune-y)
PLATFORM_CPPFLAGS += $(arch-y)  $(tune-y)
machine-y += stm32mp

86 machdirs := $(patsubst %,arch/arm/mach-%/,$(machine-y))
machdirs := arch/arm/mach-stm32mp/

88 PLATFORM_CPPFLAGS += $(patsubst %,-I$(srctree)/%include,$(machdirs))
PLATFORM_CPPFLAGS += -Iarch/arm/mach-stm32mp//include
libs-y += $(machdirs)

92 head-y := arch/arm/cpu/$(CPU)/start.o
head-y := arch/arm/cpu/armv7/start.o
libs-y += arch/arm/cpu/armv7/
libs-y += arch/arm/cpu/
libs-y += arch/arm/lib/

回到顶层 Makefile, 设置 LDSCRIPTS 为
arch/arm/cpu/armv7/u-boot.lds

接下来是给 libs-y 赋值, 这个变量包含了 fs、 net、 disk、 driver、 cmd、 env、 common
等目录下的 build-in.o
 

 681 libs-y += lib/
 682 libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
 683 libs-$(CONFIG_OF_EMBED) += dts/
 684 libs-y += fs/
 685 libs-y += net/
 686 libs-y += disk/
 687 libs-y += drivers/
.........................
.........................
 722 libs-y += drivers/usb/ulpi/
 723 libs-y += cmd/
 724 libs-y += common/
 725 libs-y += env/

 然后将 libs-y 赋值给 u-boot-main, 将 head-y 赋值给
u-boot-init
 

 743 u-boot-init := $(head-y)
 744 u-boot-main := $(libs-y)

前面说到 ALL-y 包含了目标文件, 其中会依赖 u-boot 目标, u-boot 目标的对应的
命令如下:

1380 u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE                                              
1381     +$(call if_changed,u-boot__)
1382 ifeq ($(CONFIG_KALLSYMS),y)
1383     $(call cmd,smap)
1384     $(call cmd,u-boot__) common/system_map.o
1385 endif
1386 
1387 ifeq ($(CONFIG_RISCV),y)
1388     @tools/prelink-riscv $@ 0
1389 endif

u-boot__对应的命令是:
 

1364 # Rule to link u-boot                                                                               
1365 # May be overridden by arch/$(ARCH)/config.mk
1366 quiet_cmd_u-boot__ ?= LD      $@
1367       cmd_u-boot__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_u-boot) -o $@ \
1368       -T u-boot.lds $(u-boot-init)                             \
1369       --start-group $(u-boot-main) --end-group                 \
1370       $(PLATFORM_LIBS) -Map u-boot.map;                        \
1371       $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)

 展开之后就会生成一系列的built.o文件

在生成 u-boot 目标后, u-boot-nodtb.bin 就会生成
 

1039 u-boot-nodtb.bin: u-boot FORCE                                                                      
1040     $(call if_changed,objcopy)
1041     $(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
1042     $(BOARD_SIZE_CHECK)

在 ALL-y 中包含 u-boot.bin 目标, 根据依赖关系, 需要生成 dts/dt.dtb:
 

 950 u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE                                                   
 951     $(call if_changed,cat)
 952 
 953 u-boot.bin: u-boot-dtb.bin FORCE
 954     $(call if_changed,copy)

此外, ALL-y 中也包含设备树镜像 u-boot.dtb:

 969 u-boot.dtb: dts/dt.dtb
 970     $(call cmd,copy)

 可以看到 u-boot.dtb 就是 dts/dt.dtb 重命名了一份。
dt.dtb 对应的命令如下:

 927 dtbs: dts/dt.dtb
 928     @:
 929 dts/dt.dtb: u-boot
 930     $(Q)$(MAKE) $(build)=dts dtbs

此时会用到 dts/Makefile,在配置文件 stm32mp15_trusted_defconfig 中指定了设备树镜像的名字:

 36 CONFIG_DEFAULT_DEVICE_TREE="stm32mp157c-ev1"

主要:因为这里是“ev1"的板子,而我需要的是“dk2”的板子,所以在编译时应该指定设备树,如

make DEVICE_TREE=stm32mp157c-dk2 all

在 dts/Makefile 中, DTB 为 arch/arm/dts/stm32mp157c-dk2.dtb, 目标 dtbs 的命令如下:
 

 58 dtbs: $(obj)/dt.dtb $(obj)/dt-spl.dtb
 59     @:

 这里的 obj 所指的就是 dts 目录, 下面是 dts/Makefile 中生成这两个 dtb 的命令
 

 23 $(obj)/dt-spl.dtb: $(DTB) $(objtree)/tools/fdtgrep FORCE
 24     $(call if_changed,fdtgrep)
 25 
 26 $(obj)/dt.dtb: $(DTB) FORCE
 27     $(call if_changed,shipped)

上面 shipped 所作的仅仅是 cat arch/arm/dts/stm32mp157c-dk2.dtb > dts/dt.dtb,而
fdtgrep 是对 arch/arm/dts/stm32mp157c-dk2.dtb 做了一些裁剪, 然后输出到
dts/dt-spl.dtb 中, 具体参考 scripts/Makefile.lib

./tools/fdtgrep -b u-boot,dm-pre-reloc -b u-boot,dm-spl -RT arch/arm/dts/stm32mp157c-dk2.dtb -n /chosen -n /config -O dtb
| ./tools/fdtgrep -r -O dtb - -o dts/dt-spl.dtb -P pinctrl-0 -P pinctrl-names -P clock-names -P interrupt-parent -P assigned-clocks -P
assigned-clock-rates -P assigned-clock-parents

上面两个 dtb 文件都会依赖 arch/arm/dts/stm32mp157c-dk2.dtb, 在 dts/Makefile 中生
成这个 dtb 文件的命令如下
 

 31 $(DTB): $(dtb_depends)                                                      
 32 ifeq ($(EXT_DTB),)
 33     $(Q)$(MAKE) $(build)=$(ARCH_PATH) $@

dtb_depends 是 arch/arm/dts/stm32mp157c-dk2.dts, ARCH_PATH 是 arch/arm/dts
 所以 arch/arm/dts/Makefile 会被 include 进来, 在这个 Makefile 中并没有看到生成
dtb 的命令, 这个命令其实是在 scripts/Makefile.lib 中实现的:
$(obj)/%.dtb: $(src)/%.dts FORCE
$(call if_changed_dep,dtc)

在顶层 Makefile 中 ALL-y 中还有一个 u-boot.bin 会依赖 u-boot-dtb.bin 目标:
 

 950 u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
 951     $(call if_changed,cat)
 952 
 953 u-boot.bin: u-boot-dtb.bin FORCE
 954     $(call if_changed,copy)

根据命令, 会将 u-boot-nodtb.bin 和 dts/dt.dtb 拼成 u-boot-dtb.bin:
cat u-boot-nodtb.bin dts/dt.dtb > u-boot-dtb.bin
 然后将 u-boot-dtb.bin 拷贝一份, 并命名为 u-boot.bin

接着再分析一下:arch/arm/mach-stm32mp/config.mk

  6 ifndef CONFIG_SPL
  7 ALL-y += u-boot.stm32
  8 else
  9 ifdef CONFIG_SPL_BUILD
 10 ALL-y += spl/u-boot-spl.stm32
 11 endif
 12 endif
 14 MKIMAGEFLAGS_u-boot.stm32 = -T stm32image -a $(CONFIG_SYS_TEXT_BASE) -e $(CO
 15 
 16 u-boot.stm32: MKIMAGEOUTPUT = u-boot.stm32.log
 17 
 18 u-boot.stm32: u-boot.bin FORCE
 19     $(call if_changed,mkimage)
 22 MKIMAGEFLAGS_u-boot-spl.stm32 = -T stm32image -a $(CONFIG_SPL_TEXT_BASE) -e 
 23 
 24 spl/u-boot-spl.stm32: MKIMAGEOUTPUT = spl/u-boot-spl.stm32.log
 25 
 26 spl/u-boot-spl.stm32: spl/u-boot-spl.bin FORCE
 27     $(call if_changed,mkimage)   

all定制u-boot.stm32或者spl/u-boot-spl.stm32文件。

u-boot.stm32依赖于u-boot.bin;如果定义了SPL_BUILD,spl/u-boot-spl.stm32,文件又依赖与spl/u-boot-spl.bin。所以最终最终需要生成u-boot.bin和spl/u-boot-spl.bin两个文件,u-boot.bin文件上面已经详细说明,接着讲怎么编译u-boot-spl.bin然后生成spl/u-boot-spl.bin

编译 u-boot-spl.bin
除了 u-boot.bin, 我们还需要关注 u-boot-spl.bin, 在 ALL-y 中含有 spl/u-boot-spl.bin(顶层Makefile)。
 

1534 spl/u-boot-spl.bin: spl/u-boot-spl
1535     @:
1536 spl/u-boot-spl: tools prepare \
1537         $(if $(CONFIG_OF_SEPARATE)$(CONFIG_OF_EMBED)$(CONFIG_SPL_OF_PLATDAT
1538         $(if $(CONFIG_OF_SEPARATE)$(CONFIG_OF_EMBED)$(CONFIG_TPL_OF_PLATDAT
1539     $(Q)$(MAKE) obj=spl -f $(srctree)/scripts/Makefile.spl all

也就是编译 spl 会用到 scripts/Makefile.spl:
 

222 all:    $(ALL-y)  

这里 ALL-y 包含需要生成的目标: spl/u-boot-spl.bin:

242 $(obj)/$(SPL_BIN)-dtb.bin: $(obj)/$(SPL_BIN)-nodtb.bin \
243         $(if $(CONFIG_SPL_SEPARATE_BSS),,$(obj)/$(SPL_BIN)-pad.bin) \
244         $(FINAL_DTB_CONTAINER)  FORCE
245     $(call if_changed,cat)
246 
247 $(obj)/$(SPL_BIN).bin: $(obj)/$(SPL_BIN)-dtb.bin FORCE
248     $(call if_changed,copy)

上面的 SPL_BIN 就是 u-boot-spl, FINAL_DTB_CONTAINER 是 spl/u-boot-spl.dtb.
最终的 spl/u-boot-spl-dtb.bin 就是将 u-boot-spl-nodtb.bin 跟 spl/u-boot-spl.dtb 拼接
成的。
下面分析 u-boot-spl-nodtb.bin 和 spl/u-boot-spl.dtb 是如何生成的。
 

305 $(obj)/$(SPL_BIN)-nodtb.bin: $(obj)/$(SPL_BIN) FORCE
306     $(call if_changed,objcopy)

spl/u-boot-spl-nodtb.bin 会依赖 spl/u-boot-spl。

352 # Rule to link u-boot-spl
353 # May be overridden by arch/$(ARCH)/config.mk
354 quiet_cmd_u-boot-spl ?= LD      $@
355       cmd_u-boot-spl ?= (cd $(obj) && $(LD) $(LDFLAGS) $(LDFLAGS_$(@F)) \
356                $(patsubst $(obj)/%,%,$(u-boot-spl-init)) --start-group \
357                $(patsubst $(obj)/%,%,$(u-boot-spl-main))  \
358                $(patsubst $(obj)/%,%,$(u-boot-spl-platdata)) \
359                --end-group \
360                $(PLATFORM_LIBS) -Map $(SPL_BIN).map -o $(SPL_BIN))
361 
362 $(obj)/$(SPL_BIN): $(u-boot-spl-platdata) $(u-boot-spl-init) \
363         $(u-boot-spl-main) $(obj)/u-boot-spl.lds FORCE
364     $(call if_changed,u-boot-spl) 

 上面用的到 u-boot-spl.lds 依赖关系如下:
 

372 quiet_cmd_cpp_lds = LDS     $@
373 cmd_cpp_lds = $(CPP) -Wp,-MD,$(depfile) $(cpp_flags) $(LDPPFLAGS) -ansi \
374         -D__ASSEMBLY__ -x assembler-with-cpp -std=c99 -P -o $@ $<
375                                                                             
376 $(obj)/u-boot-spl.lds: $(LDSCRIPT) FORCE
377     $(call if_changed_dep,cpp_lds)

上面的 LDSCRIPTS 表示的是arch/arm/mach-omap2/u-boot-spl.lds,参考
arch/arm/Kconfig, 如果时armv7A默认 CONFIG_SPL_LDSCRIPT 就是
arch/arm/mach-omap2/u-boot-spl.lds, 生成 spl/u-boot-spl.lds 的 log 如下:
log1
 生成 u-boot-spl 的 log 如下:

log2
spl 的链接地址是由宏 CONFIG_SPL_TEXT_BASE 指定的
u-boot-spl 一旦生成, 那么 u-boot-spl-nodtb.bin 就会通过 objcopy 的方式生成:

log3

下面是 spl/u-boot-spl.dtb 生成的命令, 在 scripts/Makefile.spl 中:

259 $(obj)/$(SPL_BIN).dtb: dts/dt-spl.dtb FORCE
260     $(call if_changed,copy)

u-boot-spl.dtb 是由 cp dts/dt-spl.dtb  spl/u-boot-spl.dtb 而来。

根据前面的分析 dts/dt-spl.dtb 是对 arch/arm/dts/stm32mp157c-dk2.dtb 做了一些裁剪
得到的。

最后就是拼接生成 u-boot-spl-dtb.bin:
cat spl/u-boot-spl-nodtb.bin  spl/u-boot-spl.dtb > spl/u-boot-spl-dtb.bin
cp spl/u-boot-spl-dtb.bin  spl/u-boot-spl.bin

3) SPL( Secondary Program Loader)
在编译 SPL 时, 执行的是 scripts/Makefile.spl, 这个文件会添加宏:
KBUILD_CPPFLAGS += -DCONFIG_SPL_BUILD
这样同一份代码, 根据宏的不同, 就可以做到 uboot 和 spl 共用。


 

 

 

 


 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值