【嵌入式移植】4、U-Boot源码分析1—Makefile

U-Boot源码分析1—Makefile

  • 1 分析思路
  • 2 u-boot源码目录结构
  • 3 Makefile源码
    • 3.1 版本号
    • 3.2 环境变量
    • 3.3 Beautify output
    • 3.4 输出文件的目录设置、PHONY目标
    • 3.6 目录信息
    • 3.5 Source Code Checker
    • 3.7 设置单独编译模块、PHONY目标
    • 3.8 获取宿主机的架构和系统
    • 3.9 设置交叉编译工具链前缀
    • 3.10 设置配置文件
    • 3.11 定义变量
    • 3.12 include文件
    • 3.13 设置交叉编译工具链
    • 3.14 PHONY += scripts_basic
    • 3.15 PHONY += outputmakefile
    • 3.16 config相关变量
    • 3.17 ifeq ($(mixed-targets),1)
    • 3.18 ifeq ($(config-targets),1)
    • 3.19 PHONY += scripts
    • 3.20 ifeq ($(dot-config),1)
    • 3.21 一些include和变量
    • 3.22 libs-y u-boot-init u-boot-main
    • 3.23 Add GCC lib
    • 3.24 LDPPFLAGS
    • 3.25 BOARD_SIZE_CHECK
    • 3.26 DO_STATIC_RELA
    • 3.27 ALL-y
    • 3.28 LDFLAGS_u-boot
    • 3.29 quiet_cmd和cmd
    • 3.30 all
    • 3.31 PHONY += dtbs
    • 3.32 u-boot.bin

1 分析思路

上一章中,完成了u-boot-2017.11源码的编译、烧写和运行,本章对U-Boot源码进行分析,根据README文件中的描述,一方面从Makefile源码入手,另一方面将U-Boot编译过程的输出保存下来,辅助分析

make CROSS_COMPILE=aarch64-linux-gnu- V=1
set -e; : '  CHK     include/config/uboot.release'; mkdir -p include/config/; 	echo "2017.11$(/bin/bash ./scripts/setlocalversion .)" < include/config/auto.conf > include/config/uboot.release.tmp; if [ -r include/config/uboot.release ] && cmp -s include/config/uboot.release include/config/uboot.release.tmp; then rm -f include/config/uboot.release.tmp; else : '  UPD     include/config/uboot.release'; mv -f include/config/uboot.release.tmp include/config/uboot.release; fi
set -e; : '  CHK     include/generated/version_autogenerated.h'; mkdir -p include/generated/; 	(echo \#define PLAIN_VERSION \"2017.11""\"; echo \#define U_BOOT_VERSION \"U-Boot \" PLAIN_VERSION; echo \#define CC_VERSION_STRING \"$(LC_ALL=C aarch64-linux-gnu-gcc --version | head -n 1)\"; echo \#define LD_VERSION_STRING \"$(LC_ALL=C aarch64-linux-gnu-ld.bfd --version | head -n 1)\"; ) < include/config/uboot.release > include/generated/version_autogenerated.h.tmp; if [ -r include/generated/version_autogenerated.h ] && cmp -s include/generated/version_autogenerated.h include/generated/version_autogenerated.h.tmp; then rm -f include/generated/version_autogenerated.h.tmp; else : '  UPD     include/generated/version_autogenerated.h'; mv -f include/generated/version_autogenerated.h.tmp include/generated/version_autogenerated.h; fi
set -e; : '  CHK     include/generated/timestamp_autogenerated.h'; mkdir -p include/generated/; 	(if test -n "${SOURCE_DATE_EPOCH}"; then SOURCE_DATE="@${SOURCE_DATE_EPOCH}"; DATE=""; for date in gdate date.gnu date; do ${date} -u -d "${SOURCE_DATE}" >/dev/null 2>&1 && DATE="${date}"; done; if test -n "${DATE}"; then LC_ALL=C ${DATE} -u -d "${SOURCE_DATE}" +'#define U_BOOT_DATE "%b %d %C%y"'; LC_ALL=C ${DATE} -u -d "${SOURCE_DATE}" +'#define U_BOOT_TIME "%T"'; LC_ALL=C ${DATE} -u -d "${SOURCE_DATE}" +'#define U_BOOT_TZ "%z"'; LC_ALL=C ${DATE} -u -d "${SOURCE_DATE}" +'#define U_BOOT_DMI_DATE "%m/%d/%Y"'; LC_ALL=C ${DATE} -u -d "${SOURCE_DATE}" +'#define U_BOOT_BUILD_DATE 0x%Y%m%d'; else return 42; fi; else LC_ALL=C date +'#define U_BOOT_DATE "%b %d %C%y"'; LC_ALL=C date +'#define U_BOOT_TIME "%T"'; LC_ALL=C date +'#define U_BOOT_TZ "%z"'; LC_ALL=C date +'#define U_BOOT_DMI_DATE "%m/%d/%Y"'; LC_ALL=C date +'#define U_BOOT_BUILD_DATE 0x%Y%m%d'; fi) < Makefile > include/generated/timestamp_autogenerated.h.tmp; if [ -r include/generated/timestamp_autogenerated.h ] && cmp -s include/generated/timestamp_autogenerated.h include/generated/timestamp_autogenerated.h.tmp; then rm -f include/generated/timestamp_autogenerated.h.tmp; else : '  UPD     include/generated/timestamp_autogenerated.h'; mv -f include/generated/timestamp_autogenerated.h.tmp include/generated/timestamp_autogenerated.h; fi
make -f ./scripts/Makefile.build obj=scripts/basic
rm -f .tmp_quiet_recordmcount
make -f ./scripts/Makefile.build obj=.
mkdir -p lib/aarch64-linux-gnu-gcc -Wp,-MD,lib/.asm-offsets.s.d -nostdinc -isystem /usr/lib/gcc-cross/aarch64-linux-gnu/11/include -Iinclude   -I./arch/arm/include -include ./include/linux/kconfig.h -D__KERNEL__ -D__UBOOT__ -Wall -Wstrict-prototypes -Wno-format-security -fno-builtin -ffreestanding -fshort-wchar -Os -fno-stack-protector -fno-delete-null-pointer-checks -g -fstack-usage -Wno-format-nonliteral -Werror=date-time -D__ARM__ -fno-pic -mstrict-align -ffunction-sections -fdata-sections -fno-common -ffixed-r9 -fno-common -ffixed-x18 -pipe -march=armv8-a -D__LINUX_ARM_ARCH__=8 -I./arch/arm/mach-sunxi/include -DDO_DEPS_ONLY    -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(asm_offsets)"  -D"KBUILD_MODNAME=KBUILD_STR(asm_offsets)"  -fverbose-asm -S -o lib/asm-offsets.s lib/asm-offsets.c
....

2 u-boot源码目录结构

U-Boot源码目录结构如下
请添加图片描述
根据u-boot-2017.11根目录下README文件的说明,各目录内容如下

目录/文件 名称描述
/archArchitecture specific files
/arch/armFiles generic to ARM architecture
/apiMachine/arch independent API for external apps
/boardBoard dependent files
/cmdU-Boot commands functions
/commonMisc architecture independent functions
/configsBoard default configuration files
/diskCode for disk drive partition handling
/docDocumentation (don’t expect too much)
/driversCommonly used device drivers
/dtsContains Makefile for building internal U-Boot fdt
/examplesExample code for standalone applications, etc
/fsFilesystem code (cramfs, ext2, jffs2, etc.)
/includeHeader Files
/libLibrary routines generic to all architectures
LicensesVarious license files
/netNetworking code
/postPower On Self Test
/scriptsVarious build scripts and Makefiles
/testVarious unit test files
/toolsTools to build S-Record or U-Boot images, etc.
config.mkThis file is included from ./Makefile and spl/Makefile
KbuildGenerate generic-asm-offsets.h, asm-offsets.h
MAINTAINERSMaintainers List
Makefile顶层Makefile

3 Makefile源码

以u-boot-2017.11源码根目录下Makefile为例,逐行分析。考虑行号对应,使用截图显示代码

3.1 版本号

第5行~9行是u-boot版本号
请添加图片描述
VSERION为主版本号,PATCHLEVEL为补丁版本,SUBLEVEL为子版本号,EXTRAVERSION为附加版本信息,NAME为名字。

从2008年10月之后,U-Boot中VSERION表示发布年份,PATCHLEVEL为月份,EXTRAVERSION表示候选版本,其它基本不用。

3.2 环境变量

第20行~29行是一些环境变量的定义
请添加图片描述
在Makefile中,通过export关键字将变量传递到下层Makefile中,或者通过unexport关键字不让某些变量传递到下层Makefile。但SHELLMAKEFLAGS两个变量不论是否export,总会传递到下层Makefile

第20行,在MAKEFLAGS变量后追加

-rR --include-dir=$(CURDIR)
  • -r表示禁止make使用任何隐含规则;
  • -R表示禁止make使用任何作用于变量上的隐含规则
  • –include-dir=< dir >或者-I < dir >用于指定一个被包含makefile的搜索目标。可以使用多个-I参数来指定多个目录
  • $(CURDIR)表示当前目录

第23行~29行,不传递LC_ALL、GREP_OPTIONS变量,并将LC_COLLATELC_NUMERIC变量均赋值为C,并进行传递。LC_COLLATE、LC_NUMERIC是有关区域支持的规则变量。

变量说明
LC_COLLATE字符串排序顺序
LC_CTYPE字符分类(什么是一个字符?它的大写形式是否等效?)
LC_MESSAGES消息使用的语言Language of messages
LC_MONETARY货币数量使用的格式
LC_NUMERIC数字的格式
LC_TIME日期和时间的格式

区域支持指的是应用遵守文化偏好的问题,包括字母表、排序、数字格式等。如果你想让系统表现得象没有区域支持,那么使用特殊的区域名C或者等效的POSIX。使用

locale -a

可以查看所有支持区域的列表
请添加图片描述

3.3 Beautify output

第73行~101行对编译过程中的信息输出进行控制。U-Boot的顶层Makefile中可以根据命令行参数控制编译过程中的信息输出,如V=1等。
请添加图片描述
这里使用origin函数,此函数不操作变量的值,返回变量的来源,比如来源于命令行、环境变量等

$(origin <varible>)
origin函数返回值说明
undefined从来没有定义过
default默认变量
environment环境变量,且当Makefile被执行时,-e参数没有被打开
file被定义在Makefile中
command line命令行
override变量是被override指示符重新定义的
automatic自动化变量

第73行~86行,使用origin函数判断变量V的来源,如果来源于命令行,则将KBUILD_VERBOSE赋值为V的值,否则为0;后续对KBUILD_VERBOSE的值进行判断,如果KBUILD_VERBOSE=1,则变量quiet和Q均为空,否则quiet=quiet_,Q = @,并将KBUILD_VERBOSE、quiet和Q传递到下层Makefile(第101行)。

第91行~99行,使用filter函数判断make版本,filter函数以< pattern >模式过滤< text >字符串中的单词,保留符合模式< pattern >的单词,可以有多个模式,返回符合< pattern >的字符串

$(filter <pattern...>,<text>)

则91行语句

ifneq ($(filter 4.%,$(MAKE_VERSION)),)

即使用filter函数获取MAKE_VERSION中类似4.%模式的字符串,在终端中查看make版本可知为4.3,即filter函数返回值为"4.3"。因此这条语句即为判断filter返回值是否不等于空值,很明显满足判断条件,执行下一行语句
请添加图片描述
第92行

ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)

这里与上一行语句类似,不过使用了firstword函数获取MAKEFLAGS变量的第一个单词
如果不为空则继续向下执行,quiet变量赋值为silent_

这里在Makefile中增加几行代码(图中103行~108行)看看变量MAKEFLAGS以及firstword输出的值(注意@echo必须以tab开头),后续编译U-Boot时记得删除这几行代码
请添加图片描述
请添加图片描述
因此,当使用默认参数编译时,quiet=quiet_,Q=@,编译信息输出较少

tzw@tzw-virtual-machine:~/arm/u-boot/u-boot-2017.11$ make CROSS_COMPILE=aarch64-linux-gnu-
MAKEFLAGS=rR -I/home/tzw/arm/u-boot/u-boot-2017.11 --no-print-directory -- CROSS_COMPILE=aarch64-linux-gnu-
firstword=xrR
quiet=quiet_
Q=@
KBUILD_VERBOSE=0

增加-s(静默编译)时,quiet=silent_,Q=@此时不输出信息

tzw@tzw-virtual-machine:~/arm/u-boot/u-boot-2017.11$ make CROSS_COMPILE=aarch64-linux-gnu- -s
MAKEFLAGS=rRs -I/home/tzw/arm/u-boot/u-boot-2017.11 --no-print-directory -- CROSS_COMPILE=aarch64-linux-gnu-
firstword=xrRs
quiet=silent_
Q=@
KBUILD_VERBOSE=0

增加V=1(显示所有编译过程输出信息)时,quiet和Q均为空,此时输出完整信息

tzw@tzw-virtual-machine:~/arm/u-boot/u-boot-2017.11$ make CROSS_COMPILE=aarch64-linux-gnu- V=1
MAKEFLAGS=rR -I/home/tzw/arm/u-boot/u-boot-2017.11 --no-print-directory -- V=1 CROSS_COMPILE=aarch64-linux-gnu-
firstword=xrR
quiet=
Q=
KBUILD_VERBOSE=1

通过变量quiet和Q的值对输出信息进行控制,通常make会把其要执行的命令行在命令执行前进行输出,当在make命令前使用@字符则不会输出显示

$(Q)$(MAKE) XXX

对于quiet变量的使用则不同,通常相同的命令有两种版本,quiet_cmd_xxx输出信息少,cmd_xxx则输出完整的信息,如果变量quiet为空的话,整个命令都会输出,如果变量quiet为“quiet_”的话,仅输出信息少的版本,如果变量quiet为“silent_”的话,整个命令都不会输出。

quiet_cmd_clean    = CLEAN   $(obj)cmd_clean    = rm -f $(__clean-files)

3.4 输出文件的目录设置、PHONY目标

第120行~156行用于设置编译输出文件保存的目录,这个功能一般很少使用
请添加图片描述
第120行判断KBUILD_SRC是否为空,满足条件(每次执行总是空),执行后续代码

第124行使用origin函数,判断O的来源是否为命令行,如果是那么将KBUILD_OUTPUT赋值为O的值,即可以通过

make O=/xxx/xxx/xxx

指定KBUILD_OUTPUT输出目录output_dir=/xxx/xxx/xxx

第129行指定PHONY目标,依赖于_all,而_all下为空

这里补充一下Makefile的规则格式,其中targets为目标,这里是_all,冒号后接目标的必要条件(依赖文件),这里没有,下一行为命令,必须以tab开头,这里也为空

targets: prerequisitescommand

一个简单的Makefile如下,第一行的All即为make的默认目标,这里同时是伪目标,即不会生成All文件,但是All的依赖文件将会生成;而All的依赖文件如何生成呢,在第6行可以找到,hello依赖于hello.o,并且第7行以tab开头,给出生成hello这个目标所需执行的命令,即gcc hello.o -o hello;同理第3行给出了hello.o目标的规则,即依赖于hello.c,通过执行命令gcc -c hello.c -o hello.o来生成hello.o

All: hellohello.o: hello.cgcc -c hello.c -o hello.ohello: hello.ogcc hello.o -o helloclean:rm -rf hello hello.o

而第9行的clean目标没有依赖文件,仅有执行的命令,即rm -rf hello hello.o,删除生成的所有文件;这里clean不是默认目标,因此执行时须通过指定目标,即make clean指定执行的目标为clean才会执行

因此第129行为Makefile中首次出现目标,即PHONY为其默认目标,此时对应的依赖文件为_all,而_all为空

第133行是一个特殊的语法,它被用于取消Makefile默认的隐含规则。这个语句的作用是阻止Make命令在当前目录下自动查找和使用默认的隐含规则来构建目标

一般来说,Makefile: 这个目标是用来制定如何创建或更新名为 “Makefile” 的目标的规则。当在命令行运行 “make” 命令或者 “make Makefile” 命令时,这个目标就会被构建。
然而在$(CURDIR)/Makefile Makefile: ;这条规则中,“Makefile:” 后面并没有跟随任何命令,因此这实际上是一个空规则,用途在于取消 Make 的隐含规则,而没有指向具体的构建方法。
特别地,如果对一个项目使用 “make” 命令,而该项目的 Makefile 中包含了类似你给出的规则,那么 “make” 命令就不会尝试寻找并运行任何默认的或者隐含的规则去构建名为 “Makefile” 的目标。
这样就避免了可能存在的,由默认或隐含规则产生的意外构建行为

说白了,直接在Makefile文件所在的目录运行命令make时,如果命中了 make Makefile,就会调用一条空命令什么都不做

第135行~140行,首先判断KBUILD_OUTPUT是否不为空,即如果通过O=/xxx/xxx/xxx指定了输出目录路径,则将路径保存到saved-output变量中(后续用于信息输出),然后使用shell函数执行mkdir和cd,并将创建成功后的绝对路径赋值给KBUILD_OUTPUT,然后进入此目录

shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号`是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回
注意,这个函数会新生成一个Shell程序来执行命令,所以需要注意其运行性能,如果Makefile中有一些比较复杂的规则,并大量使用了这个函数,那么对于系统性能是有害的。特别是Makefile的隐晦的规则可能会让shell函数执行的次数比想像的多得多

/bin/pwd --help
Usage: /bin/pwd [OPTION]...
Print the full filename of the current working directory.-L, --logical   use PWD from environment, even if it contains symlinks-P, --physical  avoid all symlinks--help     display this help and exit--version  output version information and exitIf no option is specified, -P is assumed.NOTE: your shell may have its own version of pwd, which usually supersedes
the version described here.  Please refer to your shell's documentation
for details about the options it supports.GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Full documentation <https://www.gnu.org/software/coreutils/pwd>
or available locally via: info '(coreutils) pwd invocation'

第141行,如果创建不成功,则输出相关错误信息

第144~147行,在伪目标后添加$ (MAKECMDGOALS)和sub-make伪目标(这里MAKECMDGOALS为空,可以使用@echo输出查看),然后使用反过滤函数filter-out,返回$ (MAKECMDGOALS)中去除_all、sub-make、$(CURDIR)/Makefile的内容

make的环境变量叫MAKECMDGOALS 这个变量中会存放指定的最终目标的列表,如果在命令行上没有指定目标,那么这个变量是空值。

第149~151行,确定sub-make的规则,即

$(Q)$(MAKE) -C output_dir KBUILD_SRC='pwd' -f 'pwd'/Makefile [Targets]

其中'pwd'为当前目录绝对路径即~/arm/u-boot/u-boot-2017.11。即指定输出目录output_dir=/xxx/xxx/xxx进行make

第154行,将skip-makefile的值覆盖为1,此时根据第159行的判断,skip-makefile的值不为空,不满足条件,第159行~1699行的内容不会执行,Makefile将提前结束

这一段的功能一般很少使用,因此后续继续执行第159行~1699行的内容。则PHONY的依赖文件依旧只有_all all

3.6 目录信息

第164行,在MAKEFLAGS变量后增加--no-print-directory选项,在编译过程中不会输出"Entering directory ..."之类的信息
请添加图片描述

3.5 Source Code Checker

第156行~181行,是否启用代码检查功能
请添加图片描述
首先还是通过origin函数判断是否从命令行输入C参数,即make C=1或者make C=2

如果有的话将C的值赋值给KBUILD_CHECKSRC,然后判断是否定义了KBUILD_CHECKSRC,否则将KBUILD_CHECKSRC的值赋为0

这里根据166行~174行的注释,可知C=1时仅检查重新编译的文件,C=2时对所有源文件进行检查

3.7 设置单独编译模块、PHONY目标

第186行~223行,设置单独的模块编译,并设置一些变量
请添加图片描述

第186行~188行,判断是否定义了SUBDIRS,如果定义了SUBDIRS,变量KBUILD_EXTMOD=SUBDIRS,这里是为了支持老语法make SUBIDRS=dir

第190行~192行,同样使用origin函数判断命令行是否输入参数M,所有则将KBUILD_EXTMOD赋值为M的值,即使用make M=/dir方式定义所需编译的模块

第196行添加allPHONY的依赖文件,则此时PHONY依赖文件为_all all

第197行~201行,判断KBUILD_EXTMOD是否为空,即是否定义了需要编译的模块,若无则_all规则依赖于all,因此将先编译all;否则将_all规则依赖modules,进行模块编译。模块编译一般很少用,因此会编译all

第203行~213行,判断KBUILD_SRC是否为空,若为空则将当前位置的srctree值赋值为.,即当前目录;否则根据KBUILD_SRC值对srctree进行赋值,这里KBUILD_SRC为空,srctree值赋值为.

第214行~216行,设置src和obj的值,均为当前目录.

第218行,由于KBUILD_EXTMOD为空,因此VPATH的值为srctree的值,即当前目录.
第220行,传递srctree objtree VPATH三个变量的值
第223行,不传递CDPATH的值

3.8 获取宿主机的架构和系统

第227行~240行,获取宿主机架构和系统
请添加图片描述
首先介绍几个命令
获取主机的架构uname -m uname --machine,获取主机系统uname -s或者uname --kernel-name
请添加图片描述
sed是linux的一个流编辑器,每次从输入中读取一行数据,根据所提供的编辑器命令匹配数据,并返回新的数据或输出到STDOUT。其中s命令如下

sed s/pattern/replacement/flags

即使用replacement替换数据中的pattern
而-e选项用于多个sed指令的情况

tr命令,将输入的数据中SET1全部转换为SET2,而[:lower:]表示所有小写字母,[:upper:]表示所有大写字母

tr [option] 'SET1' 'SET2'

因此对于第227行~235行,首先通过uname -m 命令获取宿主机系统架构,这里为x86_64,并通过sed命令,将其中的i.86替换为x86,其它行同理,这里最终结果仍为HOSTARCH=x86_64

第237行~238行,首先通过uname -s命令获取宿主机系统名称,这里为Linux,然后通过tr命令将其转变为小写,后续的sed命令因没有匹配的数据则不执行,最终结果为HOSTOS=linux

第240行,传递HOSTARCHHOSTOS变量

3.9 设置交叉编译工具链前缀

第245行~247行,设置交叉编译工具链
请添加图片描述
这里首先对Makefile的几种赋值方式进行说明

赋值方式说明
=直接赋值,与位置无关,系统将自动推导,将最终的赋值作为该变量的值
:=覆盖式赋值,只能推导该符号位置之前的值
+=追加赋值,旧值保持不变,将新值增加到旧值后面
?=当某变量前面已经定义赋值过,则不执行本次定义赋值,否则执行本次赋值

这里第245行判断HOSTARCHARCH是否相等,肯定不相等,因此对CROSS_COMPILE进行赋值,即执行make命令时,总是需要指定交叉编译工具链,否则CROSS_COMPILE为空,后续执行将报错

make CROSS_COMPILE=aarch64-linux-gnu-

3.10 设置配置文件

第249行~255行,设置配置文件、设置所使用的shell
请添加图片描述
第249行~250行,将KCONFIG_CONFIG的值设置为.config,即通过make xxx_defconfig在u-boot-2017.11根目录下生成的配置文件

第253行~255行,使用shell函数,通过if -x FILE判断FILE 存在且是可执行的则为真,这里CONFIG_SHELL最终为/bin/bash(可以使用@echo输出查看)

3.11 定义变量

第257行~261行,定义HOSTCCHOSTCXXHOSTCFLAGSHOSTCXXFLAGS4个变量
请添加图片描述
后续第263行~295行,判断HOSTOS是否为cygwin或者darwin,将对上述变量值进行修改,这里HOSTOS=linux,因此上述变量值不变,最终为

HOSTCC       = cc
HOSTCXX      = c++
HOSTCFLAGS   = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer 
HOSTCXXFLAGS = -O2

第300行~326行,对KBUILD_MODULESKBUILD_BUILTINKBUILD_CHECKSRCKBUILD_SRCKBUILD_EXTMOD变量进行赋值
请添加图片描述
结合前述分析,最终值为

KBUILD_MODULES   =
KBUILD_BUILTIN   = 1
KBUILD_CHECKSRC  = 0
KBUILD_SRC       =
KBUILD_EXTMOD    =

3.12 include文件

第329行~330行,调用scripts/Kbuild.include,引入其中定义的变量
请添加图片描述

3.13 设置交叉编译工具链

第334行~391行,设置交叉编译工具链以及一些变量值,并传递相关变量
请添加图片描述
第334行~348行,定义所需的交叉编译工具链

其中第336行用到一个特殊的设备/dev/null,是一个空设备文件,向其写入的东西将被丢弃
请添加图片描述
这里首先使用shell函数新生成一个shell执行后续命令,即执行

aarch64-linux-gnu-ld.bfd -v 2> /dev/null

其中aarch64-linux-gnu-ld.bfd -v表示取aarch64-linux-gnu-ld.bfd的版本号,后续的2>意为将标准错误输出(stderr)重定向至/dev/null,即丢弃所有错误输出(2为stderr的文件描述符,stdout文件描述符为1,stdin文件描述符为0

因此336行语句意为,检查aarch64-linux-gnu-ld.bfd的版本,如果报错则将所有错误信息丢弃,因此会返回空值。也就是通过检查版本判断系统是否有aarch64-linux-gnu-ld.bfd,如果有则将LD的赋值为aarch64-linux-gnu-ld.bfd
请添加图片描述
第349行~368行,定义一些工具和变量

这里编辑Makefile文件,将关心的变量进行输出,然后通过make CROSS_COMPILE=aarch64-linux-gnu- PrintInfo命令,将Target指定为PrintInfo,仅输出需要的打印信息
请添加图片描述
请添加图片描述
这些变量在顶层Makefile中没有定义,因此必定在其它文件中定义,然后在Makefile中使用,后续结合具体的make编译过程进行分析

3.14 PHONY += scripts_basic

第397行~403行,在PHONY后添加scripts_basic依赖项,此时PHONY依赖项为_all all scripts_basic
请添加图片描述
对于scripts_basic的执行语句,根据前述分析,这里Q的值为空或者@,build的值在Makefile中没有出现,应该在其它文件中有定义。根据名称来看,大概率在scripts目录下,执行搜索匹配,其中-r表示在所有子目录下递归搜索,-w表示全字匹配

grep -rw "build"

请添加图片描述
从结果中可以看到,在/scripts/Kbuild.include中对build进行了赋值,根据3.12节,可知顶层Makefile确实引用了这个文件,查看其内容,第181行将build赋值为build := -f $(srctree)/scripts/Makefile.build obj
请添加图片描述
由3.7节中的分析,可知srctree的值为.

因此这里scripts_basic的执行语句最终为

scripts_basic:make -f ./scripts/Makefile.build obj=scripts/basicrm -f .tmp_quiet_recordmcount

3.15 PHONY += outputmakefile

第405行~414行,在PHONY后添加outputmakefile依赖项,此时PHONY依赖项为_all all scripts_basic outputmakefile
请添加图片描述
由于未定义KBUILD_SRC的值,因此outputmakefile执行命令为空

3.16 config相关变量

第424行~448行,定义一些变量
请添加图片描述

include/generated/version_autogenerated.hinclude/generated/timestamp_autogenerated.h两个文件是自动生成的文件,包含版本信息和时间信息

第435行~448行,根据make的输入目标对config-targetsmixed-targetsdot-config三个变量的值进行修改
当执行make xxx_defconfig

config-targets = 1
mixed-targets  = 0
dot-config     = 1

当执行make CROSS_COMPILE=aarch64-linux-gnu-

config-targets = 0
mixed-targets  = 0
dot-config     = 1

3.17 ifeq ($(mixed-targets),1)

第450行,根据上述分析,不满足条件,因此执行else后面的语句
请添加图片描述

3.18 ifeq ($(config-targets),1)

第467行,根据上述分析,当执行make xxx_defconfig时,满足条件,因此继续执行472行~479行
请添加图片描述
当执行make CROSS_COMPILE=aarch64-linux-gnu-时,不满足条件,因此执行else分支后续的语句

3.19 PHONY += scripts

第489行~491行,在PHONY后添加scripts依赖项,此时PHONY依赖项为_all all scripts_basic outputmakefile scripts
请添加图片描述
scripts依赖项为scripts_basic include/config/auto.confscripts_basic在前已经分析过,include/config/auto.conf文件是执行make xxx_defconfig时自动生成的,保存了各个配置内容

scripts执行语句如下,$(@)表示规则中的目标文件集

make -f ./scripts/Makefile.build obj=$(@)

3.20 ifeq ($(dot-config),1)

第493行,根据上述分析,满足条件,因此执行后续语句
请添加图片描述

3.21 一些include和变量

第495行~524行
请添加图片描述
include/config/auto.conf文件是执行make xxx_defconfig时自动生成的,保存了各个配置内容
include/config/auto.conf.cmd文件中保存了所有Kconfig文件的路径,Kconfig文件主要是各种配置界面的源文件
KCONFIG_CONFIG变量的值在前面分析过,为.config
include/autoconf.mk文件中保存了各项配置内容
include/autoconf.mk.dep文件中保存了所有需要的头文件的路径
config.mk文件保存了与ARCH、CPU、BOARD等相关的参数

第530行~578行
请添加图片描述
其中EFI_LDSEFI_CRT0EFI_RELOC/arch/arm/cpu/armv8/config.mk中定义,CFLAGS_EFICFLAGS_NON_EFIarch/arm/config.mk中定义

EFI_LDS=elf_aarch64_efi.lds
EFI_CRT0=crt0_aarch64_efi.o
EFI_RELOC=reloc_aarch64_efi.o
CFLAGS_EFI=-fpic -fshort-wchar
CFLAGS_NON_EFI=-fno-pic -ffixed-r9 -ffunction-sections -fdata-sections
EFI_TARGET=
LDSCRIPT=./arch/arm/cpu/armv8/u-boot.lds
BOARDDIR=sunxi
CPUDIR=arch/arm/cpu/armv8
ARCH=arm

第584行判断,未定义CONFIG_XTENSA,因此满足条件,LDPPFLAGS += -ansi
请添加图片描述
第588行~624行,
请添加图片描述
这里KBUILD_CFLAGS在前面已经进行了赋值,588行前其值为

KBUILD_CFLAGS = -Wall -Wstrict-prototypes -Wno-format-security -fno-builtin -ffreestanding -fshort-wchar

查看是否有CONFIG_CC_OPTIMIZE_FOR_SIZE的定义
请添加图片描述
因此执行KBUILD_CFLAGS += -Os
随后继续在后添加-fno-stack-protector-fno-delete-null-pointer-checks-g选项
第603行,满足条件
请添加图片描述
因此添加-fstack-usage选项
随后添加-Wno-format-nonliteral-Werror=date-time选项
因此,最终KBUILD_CFLAGS的值为-Wall -Wstrict-prototypes -Wno-format-security -fno-builtin -ffreestanding -fshort-wchar -Os -fno-stack-protector -fno-delete-null-pointer-checks -g -fstack-usage -Wno-format-nonliteral -Werror=date-time

第628行~643行
请添加图片描述

UBOOTINCLUDE = -Iinclude -I./arch/arm/include -include ./include/linux/kconfig.h
NOSTDINC_FLAGS = -nostdinc -isystem /usr/lib/gcc-cross/aarch64-linux-gnu/11/include
CHECKFLAGS = -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ -Wbitwise -Wno-return-void -D__CHECK_ENDIAN__ -nostdinc -isystem /usr/lib/gcc-cross/aarch64-linux-gnu/11/include
cpp_flags = -D__KERNEL__ -D__UBOOT__ -D__ARM__ -fno-pic -mstrict-align -ffunction-sections -fdata-sections -fno-common -ffixed-r9 -fno-common -ffixed-x18 -pipe -march=armv8-a -D__LINUX_ARM_ARCH__=8 -I./arch/arm/mach-sunxi/include -Iinclude -I./arch/arm/include -include ./include/linux/kconfig.h -nostdinc -isystem /usr/lib/gcc-cross/aarch64-linux-gnu/11/include
c_flags = -Wall -Wstrict-prototypes -Wno-format-security -fno-builtin -ffreestanding -fshort-wchar -Os -fno-stack-protector -fno-delete-null-pointer-checks -g -fstack-usage -Wno-format-nonliteral -Werror=date-time -D__KERNEL__ -D__UBOOT__ -D__ARM__ -fno-pic -mstrict-align -ffunction-sections -fdata-sections -fno-common -ffixed-r9 -fno-common -ffixed-x18 -pipe -march=armv8-a -D__LINUX_ARM_ARCH__=8 -I./arch/arm/mach-sunxi/include -Iinclude -I./arch/arm/include -include ./include/linux/kconfig.h -nostdinc -isystem /usr/lib/gcc-cross/aarch64-linux-gnu/11/include

3.22 libs-y u-boot-init u-boot-main

第648行~713行,主要为获取配置的libs-y的值
请添加图片描述
这里有一个巧妙的用法,libs-$(CONFIG_CMD_NAND)其中CONFIG_CMD_NAND一般在XXXconfig文件中定义CONFIG_CMD_NAND=y,因此若配置的时候定义了,那么libs-$(CONFIG_CMD_NAND)的值即为libs-y

根据配置将所有用到的库路径保存在libs-y中,然后第704行通过sort函数按照字母顺序升序排序,第710行通过patsubst函数将libs-y末尾的/替换成/built-in.o

各值如下

libs-y = arch/arm/cpu/built-in.o arch/arm/cpu/armv8/built-in.o arch/arm/lib/built-in.o arch/arm/mach-sunxi/built-in.o board/sunxi/built-in.o cmd/built-in.o common/built-in.o disk/built-in.o drivers/built-in.o drivers/dma/built-in.o drivers/gpio/built-in.o drivers/i2c/built-in.o drivers/mtd/built-in.o drivers/mtd/onenand/built-in.o drivers/mtd/spi/built-in.o drivers/net/built-in.o drivers/net/phy/built-in.o drivers/pci/built-in.o drivers/power/built-in.o drivers/power/battery/built-in.o drivers/power/domain/built-in.o drivers/power/fuel_gauge/built-in.o drivers/power/mfd/built-in.o drivers/power/pmic/built-in.o drivers/power/regulator/built-in.o drivers/serial/built-in.o drivers/spi/built-in.o drivers/usb/common/built-in.o drivers/usb/dwc3/built-in.o drivers/usb/emul/built-in.o drivers/usb/eth/built-in.o drivers/usb/gadget/built-in.o drivers/usb/gadget/udc/built-in.o drivers/usb/host/built-in.o drivers/usb/musb-new/built-in.o drivers/usb/musb/built-in.o drivers/usb/phy/built-in.o drivers/usb/ulpi/built-in.o env/built-in.o fs/built-in.o lib/built-in.o net/built-in.o test/built-in.o test/dm/built-in.ou-boot-dirs =arch/arm/cpu arch/arm/cpu/armv8 arch/arm/lib arch/arm/mach-sunxi board/sunxi cmd common disk drivers drivers/dma drivers/gpio drivers/i2c drivers/mtd drivers/mtd/onenand drivers/mtd/spi drivers/net drivers/net/phy drivers/pci drivers/power drivers/power/battery drivers/power/domain drivers/power/fuel_gauge drivers/power/mfd drivers/power/pmic drivers/power/regulator drivers/serial drivers/spi drivers/usb/common drivers/usb/dwc3 drivers/usb/emul drivers/usb/eth drivers/usb/gadget drivers/usb/gadget/udc drivers/usb/host drivers/usb/musb-new drivers/usb/musb drivers/usb/phy drivers/usb/ulpi env fs lib net test test/dm tools examplesu-boot-alldirs=api arch/arm/cpu arch/arm/cpu/armv8 arch/arm/lib arch/arm/mach-sunxi board/sunxi cmd common disk drivers drivers/ddr/altera drivers/ddr/fsl drivers/dma drivers/gpio drivers/i2c drivers/mtd drivers/mtd/nand drivers/mtd/onenand drivers/mtd/spi drivers/mtd/ubi drivers/net drivers/net/fm drivers/net/phy drivers/pci drivers/power drivers/power/battery drivers/power/domain drivers/power/fuel_gauge drivers/power/mfd drivers/power/pmic drivers/power/regulator drivers/serial drivers/spi drivers/usb/common drivers/usb/dwc3 drivers/usb/emul drivers/usb/eth drivers/usb/gadget drivers/usb/gadget/udc drivers/usb/host drivers/usb/musb drivers/usb/musb-new drivers/usb/phy drivers/usb/ulpi dts env examples fs lib net post test test/dm test/env test/overlay toolshead-y=arch/arm/cpu/armv8/start.ou-boot-init=arch/arm/cpu/armv8/start.ou-boot-main= arch/arm/cpu/built-in.o arch/arm/cpu/armv8/built-in.o arch/arm/lib/built-in.o arch/arm/mach-sunxi/built-in.o board/sunxi/built-in.o cmd/built-in.o common/built-in.o disk/built-in.o drivers/built-in.o drivers/dma/built-in.o drivers/gpio/built-in.o drivers/i2c/built-in.o drivers/mtd/built-in.o drivers/mtd/onenand/built-in.o drivers/mtd/spi/built-in.o drivers/net/built-in.o drivers/net/phy/built-in.o drivers/pci/built-in.o drivers/power/built-in.o drivers/power/battery/built-in.o drivers/power/domain/built-in.o drivers/power/fuel_gauge/built-in.o drivers/power/mfd/built-in.o drivers/power/pmic/built-in.o drivers/power/regulator/built-in.o drivers/serial/built-in.o drivers/spi/built-in.o drivers/usb/common/built-in.o drivers/usb/dwc3/built-in.o drivers/usb/emul/built-in.o drivers/usb/eth/built-in.o drivers/usb/gadget/built-in.o drivers/usb/gadget/udc/built-in.o drivers/usb/host/built-in.o drivers/usb/musb-new/built-in.o drivers/usb/musb/built-in.o drivers/usb/phy/built-in.o drivers/usb/ulpi/built-in.o env/built-in.o fs/built-in.o lib/built-in.o net/built-in.o test/built-in.o test/dm/built-in.o

3.23 Add GCC lib

第717行~724行
请添加图片描述
没有定义CONFIG_USE_PRIVATE_LIBGCC=y,第717行不满足条件,因此PLATFORM_LIBGCC = -L /usr/lib/gcc-cross/aarch64-linux-gnu/11 -lgccPLATFORM_LIBS = -L /usr/lib/gcc-cross/aarch64-linux-gnu/11 -lgcc,表示使用GCC的支持库libgcc.a

3.24 LDPPFLAGS

第729行~733行,给定LDPPFLAGS的值,LDPPFLAGS=-ansi -include ./include/u-boot/u-boot.lds.h -DCPUDIR=arch/arm/cpu/armv8
请添加图片描述

3.25 BOARD_SIZE_CHECK

第738行~751行,设定BOARD_SIZE_CHECK的值,这里为空
请添加图片描述

3.26 DO_STATIC_RELA

**第757行765行**,这里`CONFIG_STATIC_RELA`在`./include/autoconfg.mk`中定义为`y`,因此DO_STATIC_RELA为760行762行所定义的内容
请添加图片描述

3.27 ALL-y

第768行~812行,定义ALL-y的内容
请添加图片描述

ALL-y = checkarmreloc u-boot.srec u-boot.bin u-boot.sym System.map binary_size_check spl/u-boot-spl.bin u-boot.img u-boot.dtb u-boot-dtb.img u-boot.itb

3.28 LDFLAGS_u-boot

第814行~821行,设置LDFLAGS_u-boot的值
请添加图片描述

LDFLAGS_u-boot = -pie --gc-sections -Bstatic --no-dynamic-linker -Ttext 0x4a000000
LDFLAGS_FINAL = --gc-sections -Bstatic
CONFIG_SYS_TEXT_BASE = 0x4a000000

3.29 quiet_cmd和cmd

第824行~857行,定义一些命令
请添加图片描述

3.30 all

第859行~870行,定义all的依赖项
请添加图片描述

3.31 PHONY += dtbs

第872行~876行,添加dtbs依赖项,此时PHONY依赖项为_all all scripts_basic outputmakefile scripts dtbs
请添加图片描述

3.32 u-boot.bin

第881行~904行,定义一些目标
请添加图片描述
未定义CONFIG_MULTI_DTB_FIT,因此第881行不满足条件,进入第895行的判断,查找可知在配置的时候定义了CONFIG_OF_SEPARATE,因此执行此分支
请添加图片描述

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

本章先分析到此处,后续根据具体的编译过程进行分析,或留待以后补充
完结撒花✿✿ヽ(°▽°)ノ✿

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/638484.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一个非常流行的R语言调色板:RColorBrewer

R 语言有许多非常优秀的调色板&#xff0c;本文就介绍一个非常流行的&#xff0c;我也经常在用的调色板 R 包&#xff1a;RColorBrewer。 安装 install.packages("RColorBrewer") 加载 library(RColorBrewer) library(knitr) 初探 ?RColorBrewer 在帮助页面可以看到…

Python实现单因素方差分析

Python实现单因素方差分析 1.背景 正念越来越受到人们关注&#xff0c;正念是一种有意的、不加评判的对当下的注意觉察。可以通过可以通过观呼吸、身体扫描、正念饮食等多种方式培养。 为了验证正念对记忆力的影响&#xff0c;选取三组被试分别进行正念训练&#xff0c;运动训…

使用STM32的UART实现蓝牙通信

✅作者简介&#xff1a;热爱科研的嵌入式开发者&#xff0c;修心和技术同步精进 代码获取、问题探讨及文章转载可私信。 ☁ 愿你的生命中有够多的云翳,来造就一个美丽的黄昏。 &#x1f34e;获取更多嵌入式资料可点击链接进群领取&#xff0c;谢谢支持&#xff01;&#x1f447…

解密.dataru被困的数据:如何应对.dataru勒索病毒威胁

导言&#xff1a; 在数字时代&#xff0c;勒索病毒如.dataru正在不断演变&#xff0c;威胁着用户的数据安全。本文91数据恢复将深入介绍.dataru勒索病毒的特点、被加密数据的恢复方法&#xff0c;以及预防措施&#xff0c;帮助您更好地了解并对抗这一数字威胁。当面对被勒索病…

基于SpringBoot的在线问卷调查管理系统

基于SpringBoot的在线问卷调查管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 前台主页 问卷列表 问卷详情 管理员界面 摘要 基于Spring Boot的在线问卷调…

JVM篇--垃圾回收器高频面试题

1 你知道哪几种垃圾收集器&#xff0c;各自的优缺点是啥&#xff0c;重点讲下cms和G1&#xff0c;包括原理&#xff0c;流程&#xff0c;优缺点&#xff1f; 1&#xff09;首先简单介绍下 有以下这些垃圾回收器 Serial收集器&#xff1a; 单线程的收集器&#xff0c;收集垃圾时…

云贝教育 |【OceanBase】OBCA认证考试预约流程

一、OBCA账号登录/注册&#xff0c;链接 https://www.oceanbase.com/ob/login/mobile?gotohttps%3A%2F%2Fwww.oceanbase.com%2Ftraining%2Fdetail%3Flevel%3DOBCA 注册完之后&#xff0c;请点击右上“登录”进行实名认证 OBCA考试报名链接&#xff1a;https://www.oceanbase.…

stm32cubemx下载以及安装【最新版本傻瓜式教程】

一、官网 https://www.st.com/zh/development-tools/stm32cubemx.html 二、下载【废话不多说&#xff0c;按照图示所圈进行】 没有登录账号的建议先注册登录再进行下载&#xff0c;这样省去后面认证的麻烦。 选择自己电脑对应环境的版本&#xff0c;我的是windows11&#xf…

MSE Nacos:解决敏感配置的安全隐患

作者&#xff1a;察溯 前言 Nacos 简介 Nacos [ 1] 是一个更易于构建云原生应用的配置管理和服务管理平台。Nacos 的配置中心具有众多优势&#xff1a;动态实时更新配置、支持水平扩展的高可用系统架构、API 简单易上手、开源免费、多语言支持、集成度高等。基于以上特点&am…

Html+Css+JavaScript实现完整的轮播图功能

概要 这个案例具备常见轮播图完整的功能&#xff0c;大家可以根据自己的需求去修改&#xff1b; 代码可以直接复制运行&#xff0c;需要安装sass 主要功能&#xff1a; &#xff08;1&#xff09;鼠标移入轮播图&#xff0c;左右两边的按钮出现&#xff0c;离开则隐藏按钮&a…

proteus8.15安装教程

proteus8.15安装教程 1.管理员运行 2.一直NEXT到这一步&#xff0c;需要注意&#xff0c;一定要选这一个 3.选中后出现 4.一直下一步到更新 这边结束后准备激活&#xff1a; 1.安装激活插件&#xff0c;先关闭防火墙 2.下一步 3.最后&#xff0c;将数据库放在根目录下 …

从零开始配置pwn环境:sublime配置并解决pwn脚本报错问题

1.sublime安装 Download - Sublime Text ──(holyeyes㉿kali2023)-[~] └─$ sudo dpkg -i sublime-text_build-4169_amd64.deb [sudo] password for holyeyes: Selecting previously unselected package sublime-text. (Reading database ... 409163 files and directori…

huggingface学习 | 云服务器使用hf_hub_download下载huggingface上的模型文件

系列文章目录 huggingface学习 | 云服务器使用git-lfs下载huggingface上的模型文件 文章目录 系列文章目录一、hf_hub_download介绍二、找到需要下载的huggingface文件三、准备工作及下载过程四、全部代码 一、hf_hub_download介绍 hf_hub_download是huggingface官方支持&…

力扣【四数之和】

一、题目描述 18. 四数之和 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两个四元组元素一一对应&#xff0c;则认为两个四元组重复&#x…

大数据开发之电商数仓(hadoop、flume、hive、hdfs、zookeeper、kafka)

第 1 章&#xff1a;数据仓库 1.1 数据仓库概述 1.1.1 数据仓库概念 1、数据仓库概念&#xff1a; 为企业制定决策&#xff0c;提供数据支持的集合。通过对数据仓库中数据的分析&#xff0c;可以帮助企业&#xff0c;改进业务流程、控制成本&#xff0c;提高产品质量。 数据…

C#中chart控件

C#中chart控件 图表的5大集合 例子 第一步&#xff1a;创建工程 放入chart控件 series集合 选择图标类型 选择绘制曲线的宽度和颜色。 显示数据标签 Title集合 添加标题 调整标题字体&#xff1a;大小和颜色 CharsArea集合 对坐标轴进行说明 设置间隔 设置刻度…

第11章_常用类和基础API拓展练习(字符串相关练习,日期时间API练习,比较器练习,其它API练习)

文章目录 第11章_常用类和基础API拓展练习字符串相关练习1&#xff1a;阅读题1、length说明2、阅读代码&#xff0c;分析结果3、阅读代码&#xff0c;分析结果4、阅读代码&#xff0c;分析结果5、阅读代码&#xff0c;分析结果6、阅读代码&#xff0c;分析结果7、阅读代码&…

【Linux】Linux基本操作(二):rm rmdir man cp mv cat echo

承接上文&#xff1a; 【【Linux】Linux基本操作&#xff08;一&#xff09;&#xff1a;初识操作系统、ls、cd、touch、mkdir、pwd 】 目录 1.rmdir指令 && rm 指令&#xff1a; rmdir -p #当子目录被删除后如果父目录也变成空目录的话&#xff0c;就连带父目录一…

崩溃了!我说用attach进行问题定位,面试官问我原理

Arthas&#xff08;阿尔萨斯&#xff09;是一款开源的Java诊断和监控工具&#xff0c;可以在生产环境中进行实时的应用程序分析和故障排查。Arthas的实现原理主要基于Java Instrumentation API和Java Agent技术。 Java Agent 是 Java 编程语言提供的一种特殊机制&#xff0c;允…

3.php开发-个人博客项目输入输出类留言板访问IPUA头来源

目录 知识点 : 输入输出 配置环境时&#xff1a; 搜索框&#xff1a; 留言板&#xff1a; 留言板的显示&#xff08;html&#xff09;&#xff1a; php代码显示提交的留言&#xff1a; 写入数据库 对留言内容进行显示&#xff1a; php全局变量-$_SERVER 检测来源 墨…