基于s5pv210(uboot-makfile、mkcomfig)分析

基于Samsung的s5vp210--Makefile_mkconfig_code_analyse

*****************************************************

Makefile_code_analyse:
(31-43)
VERSION = 1
PATCHLEVEL = 3
SUBLEVEL = 4
EXTRAVERSION =
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
VERSION_FILE = $(obj)include/version_autogenerated.h

VERSION:主版本号(大改)
PATCHLEVEL:次版本号(在源码中中改)
SUBLEVEL:次次版本号(在源码中小改)
EXTRAVERSION:自定义名称(可作为自己编译的版本)
U_BOOT_VERSION:实质就是上面四个变量的集合
VERSION_FILE:导出$(obj)include/version_autogenerated.h这个
文件中的内容,实质就是上面那几个版本号信息(#define U_BOOT_VERSION "U-Boot 1.3.4")
include/version_autogenerated.h:这个文件源码中是没有的,是配置编译过
后才会自动生成的
注意:这个是可以修改的,也可以自行验证
修改就是在这里修改,
验证就是可以将修改后的uboot重新编译,烧录到板中运行uboot查看版本号

******************************************
HOSTARCH:
HOST:主机
ARCH:架构
shell uname -m 输出unmae -m 的输出结果,就是输出host的架构
uname -m:x86_64(本机架构)
HOSTOS:
OS:操作系统
shell uname -s:输出使用什么操作系统
uname -s:Linux
其中:|是管道的意思,就是将管道前面的输出结果当作后方输入,最后的结构
为最终的输出结果


# Allow for silent builds
静默编译(50-54)
在完成uboot的配置后,在编译的阶段即make时,如果没有其余操作,那么就会
自动默认为动态编译,即显示有编译的操作过程
如果是make -s,那么就是一个静态编译指令,不会出现其中的操作过程,
除了一些必要的显示消息
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
这里的逻辑就是查看有没有s,如果没有的话,就XECHO = echo打印输出,
如果有的话,就输出空,就是完成静默编译
1、findstring s,$(MAKEFLAGS)
首先,明白它的作用:
在MAKEFLAGS变量中查找子字符串s
MAKEFLAGS是Make的内置变量,记录了传递给make的命令行参数(例如s)
如果MAKEFLAGS中包含s,那么findstring会返回s,否则会返回空字符串
2、这里就是说明,ifeq()中,判断里面是不是两个空,就是有没有s的参数
传进来,进而去判断完成什么样的输出


********************************************

uboot中支持两种文件输出形式:
1、原地文件输出
2、分离文件单独目录输出
(58-123)

实质:就是在编译时,makefile中会根据各种目标,生成各种目标文件,
根据自己的需求,可以将编译后的文件和源文件放在同一个文件夹,
也可以另存在一个单独的文件夹

1、原地文件输出:优劣
1)优势就是简单方便,不用过多的操作,让所有文件都在同一个目录中
2)坏处就是污染了源文件,让生成的编译的文件混杂在源文件中,
其次,就是这样操作后每一次的uboot配置后只能完成一个文件移植,比较局限性

2、分离文件单独目录输出:优劣
1)劣处:额外完成一些配置,如一些判断,输出,另起文件,完成对文件的额外输出
2)优势就是可以将文件单独放在某一个自定的文件夹内,一是可以做到不污染
源文件,二是可以做到一个源代码可以完成多个配置的输出,实质就是完成多次配置,
将每一个需要的文件放在不同的文件夹

相关操作:
注释中详解
1、
Add O= to the make command line
# 'make O=/tmp/build all'
就是在make时,加上命令行  O=/输出文件夹目录

2、
Set environement variable BUILD_DIR to point to the desired location
# 'export BUILD_DIR=/tmp/build'
# 'make'
第二个就是先导出环境变量  BUILD_DIR=/输出文件夹目录
然后在make

在上述第二种的先导出环境变量这种方法中,还可以有第二种方法完成编译,
那就是’./MAKEALL‘

The second approach can also be used with a MAKEALL script
# 'export BUILD_DIR=/tmp/build'
# './MAKEALL'

MAKEALL是一个脚本文件,是协助makefile编译的

还有就是上述两种分离输出文件的操作,如果make O 和 export BUILD_DIR
这两个同时出现了,那么就会进行第一个操作,第一个的优先级更高
Command line 'O=' setting overrides BUILD_DIR environent variable.

When none of the above methods is used the local build is performed and
the object files are placed in the source directory.
最后注释表明,如果没有上述两种情况,那么就会保存到本地文件,就是源文件目录。
实质就是原地输出

代码中详解:
ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif

这个就是判断命令行中是否有-o这个命令,如果有就把o=的变量
解引用到BUILD_DIR,那么这个就是分离到的文件目录

ifneq ($(BUILD_DIR),)
saved-output := $(BUILD_DIR)
这个就是判断BUILD_DIR 是否为空,如果不为空就把这个变量赋值给saved-output ,
就是把输出的分离的文件目录赋值给saved-output 


# Attempt to create a output directory.
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
这个就是创建BUILD_DIR变量的目录

# Verify if it was successful.
BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)
$(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))
endif 

这个就是如果创建成功了就进入到BUILD_DIR的bin目录,
然后pwd打印输出这个目录然后赋值给BUILD_DIR,
然后判断它是否为空
这里涉及的逻辑有两部分:
1、BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)
这里就是简单的cd进入到BUILD_DIR的bin目录pwd打印并输出给BUILD_DIR
2、下面的if语句,首先了解其原型:
if函数的语法是$(if condition, then-part, else-part)。
如果condition非空,则执行then-part,否则执行else-part。
否则执行else-part。这里的condition是$(BUILD_DIR),也就是检查BUILD_DIR是否为空。
如果为空,则触发error,否则什么也不做。
这里的condition是$(BUILD_DIR),也就是检查BUILD_DIR是否为空。
如果为空,则触发error,否则什么也不做。但这里有一个疑问,因为前面的赋值操作是将BUILD_DIR设置为shell命令的结果
,理论上,如果BUILD_DIR原本不存在,cd命令可能失败,但pwd仍然返回当前目录的路径,因此BUILD_DIR可能不会是空的。
所以这个条件检查可能不会触发error,除非shell命令的结果为空。


# ifneq ($(BUILD_DIR),)
OBJTREE        := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE        := $(CURDIR)
TOPDIR        := $(SRCTREE)
LNDIR        := $(OBJTREE)
export    TOPDIR SRCTREE OBJTREE

MKCONFIG    := $(SRCTREE)/mkconfig
export MKCONFIG

ifneq ($(OBJTREE),$(SRCTREE))
REMOTE_BUILD    := 1
export REMOTE_BUILD
endif

1、先判断BUILD_DIR是否为空,不为空的话就进行下面的操作
2、OBJTREE:这个是.o文件的输出目录根目录,
OBJTREE        := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
这个就是判断BUILD_DIR是否为空,如果不为空,将BUILD_DIR赋值给OBJTREE,
不然就赋值为CURDIR
CURDIR实质就是源码根目录
SRCTREE:这个就是源码根目录,将CURDIR的实质目录就是源码根目录赋值给它
TOPDIR        := $(SRCTREE)
这个就是顶层目录,也是根目录
export    TOPDIR SRCTREE OBJTREE
主要导出这三个环境变量

MKCONFIG    := $(SRCTREE)/mkconfig
export MKCONFIG
将源码目录中的mkconfig直接赋予MKCONFIG,实质就是把MKCONFIG加载进来

ifneq ($(OBJTREE),$(SRCTREE))
REMOTE_BUILD    := 1
export REMOTE_BUILD
endif
这里就是判断OBJTREE、SRCTREE目录是否相等,如果相等,那么就说明没有
使用文件分离,不相等,那么就说明使用了编译文件的分离目录,
那么就将REMOTE_BUILD赋值为1,表明使用了文件分离
这个变量在config.mk中有用到(239)

# $(obj) and (src) are defined in config.mk but here in main Makefile
# we also need them before config.mk is included which is the case for
# some targets like unconfig, clean, clobber, distclean, etc.
ifneq ($(OBJTREE),$(SRCTREE))
obj := $(OBJTREE)/
src := $(SRCTREE)/
else
obj :=
src :=
endif
export obj src
这里注释和代码表明:
注释:
obj、src这两个是定义在config.mk脚本文件中的,它的值就是我们源码目录下面的mkconfig
,但这里是主Makefile我们需要在config.mk之前定义这些目标--like unconfig, clean, 
clobber, distclean, etc,在后面使用
代码:
主要是判断有没有使用文件分离,如果使用了,就把源码目录和分离的根目录
分别赋值在obj、src
如果没有使用,就把obj、src赋值为空

# Make sure CDPATH settings don't interfere
unexport CDPATH
确保CDPATH设置不会被干扰
unexport CDPATH  非输出/非导出

**************************************************

关于架构名称及CROSS_COMPILE配置相关的交叉编译工具链的路径:
(126--182)
前面主要是确定ARCH的架构名称,在CROSS_COMPILE定义后接到其后面
CROSS_COMPILE:定义交叉编译工具链的具体路径,是交叉编译工具链的一个前缀,
注意这个交叉编译工具链,在使用前要把它声明到外部环境变量中
整体CROSS_COMPILE由136-182来完整定义
在实际运用中,我们可以在Makefile中去更改设置这个CROSS_COMPILE,来完成
我们实际的交叉编译工具链的路径

# load ARCH, BOARD, and CPU configuration
include $(obj)include/config.mk
export    ARCH CPU BOARD VENDOR SOC
(132-134)
这里主要是加载include/config.mk下的变量,并导出
涉及5个导出的变量
在config.mk中,各变量参数:
ARCH   = arm
CPU    = s5pc11x
BOARD  = x210
VENDOR = samsung
SOC    = s5pc110

注意,config.mk这个脚本文件在源uboot中是没有的,它是在认为编辑,
在生成uboot配置编译后才自动生成的,具体的值是在(2581)

smdkv210single_ubi_config :    unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x smdkc110 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/smdkc110/config.mk

*******************************************
(185)
# load other configuration
include $(TOPDIR)/config.mk
加载其他的配置,就是这个config.mk
分析config.mk:
(26-40)
主要是判断obj目录、src根目录的情况,其实和上述的基本一样,就是判断
这两个是不是一样,从而赋值到不同的目录

(42-45)
清除一些状态

(48-70)
主要是确定主机架构及CROSS_COMPILE的一些情况

(74-84)
主要是确定主机中的编译,是cc还是gcc
以及HOSTCFLAGS、HOSTSTRIP

**************************************************

(95-107)
主要是编译工具的定义
AS    = $(CROSS_COMPILE)as
LD    = $(CROSS_COMPILE)ld
CC    = $(CROSS_COMPILE)gcc
CPP    = $(CC) -E
AR    = $(CROSS_COMPILE)ar
NM    = $(CROSS_COMPILE)nm
LDR    = $(CROSS_COMPILE)ldr
STRIP    = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
RANLIB    = $(CROSS_COMPILE)RANLIB


***********************************************8
(111-130)
加载通用的板载配置

sinclude $(OBJTREE)/include/autoconf.mk
(112)
加载autoconf.mk,这一个文件也是认为修改的,配置编译之后自动产生的
autoconf.mk文件中,主要是CONFIG_XXX的配置
例如里面可见很多常见的一些配置:
CONFIG_SYS_CLK_FREQ=24000000
CONFIG_GATEWAYIP="192.168.0.1"
CONFIG_BOOTARGS="console=ttySAC2,
    115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3"
CONFIG_IPADDR="192.168.1.88"
CONFIG_BOOTCOMMAND="movi read kernel 30008000; 
    movi read rootfs 30B00000 300000; bootm 30008000 30B00000"
CONFIG_BAUDRATE=115200
CONFIG_NETMASK="255.255.0.0"
CONFIG_SERVERIP="192.168.1.102"
其它的一些基本的就是y

下面几行就是加载5个参数下的config.mk:
ifdef    ARCH
sinclude $(TOPDIR)/$(ARCH)_config.mk    # include architecture dependend rules
endif
ifdef    CPU
sinclude $(TOPDIR)/cpu/$(CPU)/config.mk    # include  CPU    specific rules
endif
ifdef    SOC
sinclude $(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk    # include  SoC    specific rules
endif
ifdef    VENDOR
BOARDDIR = $(VENDOR)/$(BOARD)
else
BOARDDIR = $(BOARD)
endif
ifdef    BOARD
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk    # include board specific rules
endif

这些都是要经过配置之后才产生的,config.mk    里面的内容为
TEXT_BASE = 0xc3e00000
config.mk是一个链接脚本


**************************************************************

(134-138)
ifneq (,$(findstring s,$(MAKEFLAGS)))
ARFLAGS = cr
else
ARFLAGS = crv
endif
这个也是判断是否由单独分离文件操作,
由的话输出ARFLAGS = cr,
没有的话ARFLAGS = crv

(142-149)
ifndef LDSCRIPT
#LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds.debug
ifeq ($(CONFIG_NAND_U_BOOT),y)
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds
else
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
endif
endif
这里处理的是链接的问题,u-boot.lds ....
这几个是一个.lds的链接脚本,里面像之前逻辑添加的那个脚本一样,
有链接地址、数据段、text段、bss段
$(BOARDDIR)这个是解引用为这个目录

(156-158)
TEXT_BASE = 0xc3e00000(这个是在makefile的2583有定义)
(实质就是在board/samsung/smdkc110/config.mk中的脚本文件内容)
ifneq ($(TEXT_BASE),)
CPPFLAGS += -DTEXT_BASE=$(TEXT_BASE)
endif
Makefile中配置x210开发板时,在在board/samsung/smdkc110中生成
config.mk
TEXT_BASE是将来我们整个uboot链接时指定的链接地址
为什么是0xc3e00000,因为uboot中启动了虚拟地址映射,那这个地址是
0x23e00000,还是0x33e00000,最后还是要取决于虚拟地址映射关系


********************************************
自动推导规则:(239-256)
ifndef REMOTE_BUILD

%.s:    %.S
    $(CPP) $(AFLAGS) -o $@ $<
%.o:    %.S
    $(CC) $(AFLAGS) -c -o $@ $<
%.o:    %.c
    $(CC) $(CFLAGS) -c -o $@ $<

else

$(obj)%.s:    %.S
    $(CPP) $(AFLAGS) -o $@ $<
$(obj)%.o:    %.S
    $(CC) $(AFLAGS) -c -o $@ $<
$(obj)%.o:    %.c
    $(CC) $(CFLAGS) -c -o $@ $<
endif


ifndef REMOTE_BUILD:
REMOTE_BUILD这个参数在makefile中(104-107)有定义,主要就是
用来判断是否采用单独文件分离

***********************************************

(188-289)
U-Boot objects....order is important 
(i.e. start must be first)
注释声明:uboot中对象的顺序是很重要的
这些代码主要是添加各种目标文件

**********************************************

(291)
这里出现Makefile中第一个目标all,就是默认目标
all:        $(ALL)

下面就是各种目标依赖,各种添加文件、添加各种库库文件、最终生成elf
可执行文件

(473)
unconfig:
就是未配置
这个unconfig符号作为了我们配置开发板时目标的依赖
所以我们在uboot中已经对开发板进行了配置后,仍然可以再配置,并不会报错
值得注意的是,我们在uboot中配置开发板:
make x210_sd_config
所以x210_sd_config其实也是makefile中的一个目标


**************************************************
smdkv210single_ubi_config :    unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x smdkc110 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/smdkc110/config.mk

分析根文件目录下的mkconfig
注意,在makefile文件中,(2589)传过来的6个参数,在mkconfig中运用
make x210_sd_config 结合$(@:_config=)
这个就是传进来的参数经过将_config替换为空
$1=x210_sd
$2=arm
$3=s5pc11x
$4=smdkc110
$5=samsung
$6=s5pc110

****************
代码分析:
(14-28)
while [ $# -gt 0 ] ; do
    case "$1" in
    --) shift ; break ;;
    -a) shift ; APPEND=yes ;;
    -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
    *)  break ;;
    esac
done

[ "${BOARD_NAME}" ] || BOARD_NAME="$1"

[ $# -lt 4 ] && exit 1
[ $# -gt 6 ] && exit 1

echo "Configuring for ${BOARD_NAME} board..."

1、$# = 6
2、判断传进来参数
$1 为 x210_sd ,必定不等于里面的case 中的一个,那么就会break
退出整个循环
3、[ "${BOARD_NAME}" ] || BOARD_NAME="$1"
判断式子是否成立,BOARD_NAME是否为空,前面case没有成立,
而且一开始
BOARD_NAME=""    # Name to print in make output,
BOARD_NAME等于空,所以执行下一式子,
将BOARD_NAME="$1",
所以现在BOARD_NAME = x210_sd

[ $# -lt 4 ] && exit 1
[ $# -gt 6 ] && exit 1
同时,这里要满足$#小于4的退出循环,大于6的也会退出循环

最后,在make x210_sd_config 时配置成功后会打印输出
echo "Configuring for ${BOARD_NAME} board..."

***************
(31-49)
Create link to architecture specific headers
创建特殊的头文件架构

第一个文件:
代码分析:
if [ "$SRCTREE" != "$OBJTREE" ] ; then
    mkdir -p ${OBJTREE}/include
    mkdir -p ${OBJTREE}/include2
    cd ${OBJTREE}/include2
    rm -f asm
    ln -s ${SRCTREE}/include/asm-$2 asm
    LNPREFIX="../../include2/asm/"
    cd ../include
    rm -rf asm-$2
    rm -f asm
    mkdir asm-$2
    ln -s asm-$2 asm
else
    cd ./include
    rm -f asm
    ln -s asm-$2 asm
fi
这里主要是先判断有无单独分离编译后输出文件
主要分析下面:
分析没有单独分离的,就是原地文件输出的

就是进入else
首先进入到./include 文件夹,rm -f asm 
然后创建指向asm-$2(asm-arm)的符号链接asm
lrwxrwxrwx 1 root root 7 Feb 13 23:47 asm -> asm-arm

*****************************

第二个文件:(实质也不是第二个文件,会在下面的操作中删除重新链接)
代码分析:
rm -f asm-$2/arch

if [ -z "$6" -o "$6" = "NULL" ] ; then
    ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
    ln -s ${LNPREFIX}arch-$6 asm-$2/arch
fi

先rm -f asm-$2/arch(asm-arm/arch)
再判断,-z "$6"是否为空 -o(或者) "$6" = "NULL"是否为NULL
很明显$6为s5pc110,既不是空,也不是NULL
执行
else
    ln -s ${LNPREFIX}arch-$6 asm-$2/arch
    创建一个符号链接asm-$2/arch指向${LNPREFIX}arch-$6
    asm-$2/arch(asm-arm/arch)
    ${LNPREFIX}arch-$6(${LNPREFIX}arch-s5pc110)
    ${LNPREFIX}当前include目录
lrwxrwxrwx 1 root root 12 Feb 13 23:47 arch -> arch-s5pc11x
这里说明指向的不是s5pc110
就是下面这个文件,实质是第二个
***********************

第三、四个文件:
(59-105)都是创建第三个文件的
但是它是选择行创建,就是你需要的soc是那个就创建哪个
# create link for s5pc11x SoC
if [ "$3" = "s5pc11x" ] ; then
        rm -f regs.h
        ln -s $6.h regs.h
        rm -f asm-$2/arch
        ln -s arch-$3 asm-$2/arch
fi

我们需要的是s5pc11x
这里"$3" = "s5pc11x"
执行rm -f regs.h
然后ln -s $6.h regs.h
执行rm -f asm-$2/arch (删除第二个文件)
然后 ln -s arch-$3 asm-$2/arch

三:
$6.h(s5pc110.h)
1、创建符号链接(regs.h)指向 $6.h(s5pc110.h)
lrwxrwxrwx 1 root root 9 Feb 13 23:47 regs.h -> s5pc110.h

四:
2、创建符号链接asm-$2/arch(asm-arm/arch)指向 arch-$3(arch-s5pc11x)
lrwxrwxrwx 1 root root 12 Feb 13 23:47 arch -> arch-s5pc11x


******************************
第五个文件:
代码分析:
if [ "$2" = "arm" ] ; then
    rm -f asm-$2/proc
    ln -s ${LNPREFIX}proc-armv asm-$2/proc
fi
当前"$2" = "arm",
执行:rm -f asm-$2/proc
创建符号链接asm-$2/proc指向${LNPREFIX}proc-armv


****

以上总共创建了四个符号链接,这几个符号链接将来在写代码过程中,
应用的头文件包含,可能为:
#include<asm/xx.h>

******************************
(120-129)
#
# Create include file for Make
#
echo "ARCH   = $2" >  config.mk
echo "CPU    = $3" >> config.mk
echo "BOARD  = $4" >> config.mk

[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk

[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC    = $6" >> config.mk

这里是创建包含文件给config.mk
>创建文件并添加内容
>>是追加内容

把ARCH CPU   BOARD  VENDOR SOC 这五个添加到 include/config.mk
include/config.mk里面内容:
ARCH   = arm
CPU    = s5pc11x
BOARD  = x210
VENDOR = samsung
SOC    = s5pc110

这个include/config.mk文件就是在这里产生的,makefile里做的是传参

****************************
#
# Create board specific header file
#
if [ "$APPEND" = "yes" ]    # Append to existing config file
then
    echo >> config.h
else
    > config.h        # Create new config file
fi
echo "/* Automatically generated - do not edit */" >>config.h
echo "#include <configs/$1.h>" >>config.h

exit 0

"$APPEND" = "yes" 这一个条件不成立,
因为在开头定义APPEND=no    # Default: Create new config file
所以执行的是else,
创建 config.h    
里面的内容就是echo要打印输出的内容
/* Automatically generated - do not edit */
#include <configs/x210_sd.h>


******************************8
最后就是链接脚本的描述:
uboot\board\samsung\x210\u-boot.lds
这个是uboot中的一个链接脚本
分析整个程序:
1、代码入口从ENTRY(_start)开始,就像c语言中的main
2、里面有一个链接地址,. = 0x00000000;
实质就像-Ttext 0x00000000这个链接一样
但是注意,这两个链接的功能可以同时使用,
一旦检测到有这两个同时使用,链接的地址要依赖-Ttext为准
3、. = ALIGN(4);
这个是字节对齐的功能
4、lds中可以有固定的编译工具自带的段外,还可以有自定义的段
自带段定义:
.text  :文本段
.rodata : 只读数据段
.data :数据段
.bss :bss段

自定义段:
.got :
.u_boot_cmd : 
.mmudata :

5、在文本段中,注意文件排列的顺序,
一些比较重要的要先编译排布在内存中的,在启动时要启动或者关闭某一些
功能的功能性代码要提前放到代码段中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值