本文主要是uboot——uboot编译时间与版本信息的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 Windows Windows11 Ubuntu Ubuntu20.04.2的64位版本 VMware® Workstation 17 Pro 17.6.0 build-24238078 终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license)) Win32DiskImager Win32DiskImager v1.0 Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板 uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04 linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
点击查看相关文件下载
一、为什么? 为什么要研究这个?因为linux内核的还没去学呢啊,主要是因为后面在搞驱动开发学习的时候需要这个。为什么?在驱动开发的时候,一般我是把驱动的源文件放在linux内核源码之外,通过调用内核的makefile来编译生成独立的内核模块,仓库在这里:imx6ull-driver-demo: i.mx6ull驱动开发demo
因为我是进行了git版本管理的,在实际的工作中,也经常遇到不知道用的驱动是什么版本的问题,用的是这一个版本,随着功能的完善,版本肯定要迭代更新的,可是回头谁知道自己用的哪个驱动版本啊,也许有别的办法能追溯,但起码我写这个笔记的时候还没有接触到。
目前想到的办法就是把git版本号以及编译时间这些信息写进去,git版本号的获取可以在Makefile中这样获取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 GIT_SHA = $(shell git rev-parse --short HEAD | awk 'NR==1') GIT_SEQ = $(shell git rev-list HEAD | wc -l) GIT_VER_INFO = $(GIT_SHA) -$(GIT_SEQ) GIT_SVR_PATH = $(shell git remote -v | awk 'NR==1' | sed 's/[() ]//g' | sed 's/\t/ /g' |cut -d " " -f2) ifneq ($(GIT_VER_INFO) ,) CFLAGS += -DGIT_VERSION=\"$(GIT_VER_INFO)\" else CFLAGS += -DGIT_VERSION=\"unknown\" endif ifneq ($(GIT_SVR_PATH) ,) CFLAGS += -DGIT_PATH=\"$(GIT_SVR_PATH)\" else CFLAGS += -DGIT_PATH=\"unknown\" endif
然后再GCC编译的时候加进去,就可以直接再c语言工程中调用了,但是吧有个问题,就是这样的-D参数是要加在编译工具后面的,例如:
1 gcc -DDEBUG source.c -o program
但是我们在内核源码之外写的驱动源文件,通过调用内核的makefile来编译的,经过实践发现,这里没办法直接把-D参数传给内核Makefile,至少写这个笔记的时候我还没发现怎么传进去让ko的源文件可以调用。所以我就去扒了uboot的源码,因为我们知道uboot是会生成两个文件的:
1 2 3 4 5 #define U_BOOT_DATE "Nov 19 2024" #define U_BOOT_TIME "21:45:10" #define U_BOOT_TZ "+0800" #define U_BOOT_DMI_DATE "11/19/2024" #define U_BOOT_BUILD_DATE 0x20241119
1 2 3 4 #define PLAIN_VERSION "2019.04-gf32bcf5b" #define U_BOOT_VERSION "U-Boot " PLAIN_VERSION #define CC_VERSION_STRING "arm-linux-gnueabihf-gcc (GNU Toolchain for the A-profile Architecture 8.3-2019.03 (arm-rel-8.36)) 8.3.0" #define LD_VERSION_STRING "GNU ld (GNU Toolchain for the A-profile Architecture 8.3-2019.03 (arm-rel-8.36)) 2.32.0.20190321"
这里直接定义成了宏,生成的是头文件,这样我们直接在C工程中调用也可以,这样不就可以实现了吗,哈哈。所以就来看一下这两个文件怎么生成和创建的吧。
Tips:在uboot的git管理中,这两个文件没有必要纳入版本库的管理,这里只是为了方便学习。另外将这个纳入版本库后,这个远程仓库中的version_autogenerated.h文件中git版本号永远都比最新一次提交落后,因为每次编译完,这个文件中都是最新一次提交的commit id,但是当我们传到远端的时候,在本地需要将所有的文件提交,这就会导致这个文件晚于最终的最新版本,当被重新编译的时候才会变成最新的。
二、timestamp 我们先来看这个关于时间的头文件都怎么生成的。
1. 编译信息 我们通过以下命令编译一次uboot,获取编译过程信息,看看里面关于这个文件的信息:
1 2 3 make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16 > make_info.txt
获取到这个make_info.txt文件后,我们打开搜索一下timestamp_autogenerated.h,就会发现所有相关的信息都在这里:
1 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
整理一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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
接下来我们就来看一看这个编译信息怎么出来的。
2. timestamp_h 先找一下timestamp_autogenerated.h定义的位置,这个文件位置的定义是在Makefile · timestamp_h :
1 timestamp_h := include /generated/timestamp_autogenerated.h
看到这里定义了这个timestamp_h变量,我们接着搜一下这个timestamp_h关键词,会发现它也是一个目标,在Makefile 中的规则如下:
1 2 $(timestamp_h) : $(srctree) /Makefile FORCE $(call filechk,timestamp.h)
我们前面分析顶层Makefile的时候分析过,这个Makefile · srctree 最后是.
,所以这里简单先展开一下就是:
1 2 include/generated/timestamp_autogenerated.h: ./Makefile FORCE $(call filechk,timestamp.h)
接下来就是看这个filechk和timestamp.h是什么了。
3. filechk 3.1 Makefile中的自定义函数 我们先回顾一下Makefile中的自定义函数,在Makefile中是可以自己定义一个函数然后调用的。比如:
1 2 3 4 5 6 define filechk_timestamp.h @echo "this is a makefile function!" @echo "\$(0)=$(0):表示函数名" @echo "\$(1)=$(1):表示传给函数的第一个参数" @echo "\$(2)=$(2):表示传给函数的第二个参数" endef
然后可以通过以下语句调用:
1 $(call func_name,arg1,arg2,...)
一般而言,call 本身并不具备编译功能,call 只是将自定义函数在当前位置展开,遇到Makefile内置函数会自动执行。其中arg1、arg2、…是传入函数的参数。我们可以尝试一下,新建一个Makefile文件,输入以下内容:
1 2 3 4 5 6 7 8 9 define filechk_timestamp @echo "this is a makefile function!" @echo "$(0):表示函数名" @echo "$(1):表示传给函数的第一个参数" @echo "$(2):表示传给函数的第二个参数" endef test: $(call filechk_timestamp,arg1,arg2)
我们直接执行make,就会得到如下打印信息:
1 2 3 4 this is a makefile function! filechk_timestamp:表示函数名 arg1:表示传给函数的第一个参数 arg2:表示传给函数的第二个参数
前面说到call 只是将自定义函数在当前位置展开,所以上面的函数其实等价于:
1 2 3 4 5 6 7 8 9 10 11 12 test: @echo "this is a makefile function!" @echo "filechk_timestamp:表示函数名" @echo "arg1:表示传给函数的第一个参数" @echo "arg2:表示传给函数的第二个参数"
3.2 filechk 接下来我们来看一下这个filechk函数,它定义在scripts/Kbuild.include · filechk :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 define filechk $(Q) set -e; \ $(kecho) ' CHK $@ '; \ mkdir -p $(dir $@ ) ; \ $(filechk_$(1)) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ $(kecho) ' UPD $@ '; \ mv -f $@ .tmp $@ ; \ fi endef
可以看到这里的$(1)
应该就是前面的timestamp.h,所以这里就是:
1 2 3 4 5 6 7 8 9 10 11 12 define filechk $(Q) set -e; \ $(kecho) ' CHK $@ '; \ mkdir -p $(dir $@ ) ; \ $(filechk_timestamp.h) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ $(kecho) ' UPD $@ '; \ mv -f $@ .tmp $@ ; \ fi endef
3.3 变量的值 哪些变量都是啥啊?其实这这些$@
、$<
都是在规则中确定的,我们省点事,加点打印信息好了,我们修改scripts/Kbuild.include · filechk 中 filechk 函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 define filechk echo "" \ echo "Q=$(Q)" \ echo "kecho=$(kecho)" \ echo "$$@=$@" \ echo "$$<=$<" \ echo "" \ $ (Q)set -e; \ $(kecho) ' CHK $@' ; \ mkdir -p $(dir $@ ); \ $(filechk_$(1)) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ $(kecho) ' UPD $@' ; \ mv -f $@ .tmp $@ ; \ fi endef
可以找到有这么一条打印信息:
1 echo "" echo "Q=" echo "kecho=:" echo "$@=include/generated/timestamp_autogenerated.h" echo "$<=Makefile" echo "" 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
可以看到这些就是我们添加的打印信息:
1 echo "" echo "Q=" echo "kecho=:" echo "$@=include/generated/timestamp_autogenerated.h" echo "$<=Makefile" echo ""
3.4 filehck展开 所以这里我们对这个函数展开:
1 2 3 4 5 6 7 8 9 10 11 12 define filechk set -e; \ : ' CHK include /generated/timestamp_autogenerated.h'; \ mkdir -p $(dir include/generated/timestamp_autogenerated.h) ; \ $(filechk_$(1)) < 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 endef
**set -e
**:使脚本在遇到任何命令失败时立即终止。
这个:
是啥意思???:
在 shell 中是一个不执行任何操作的命令,在这里应该只是用于记录日志或者调试信息,因为从前面的打印信息可以看出这里会打印出来这条提示信息。
-r include/generated/timestamp_autogenerated.h
:检查这个文件是否可读。
cmp -s
:cmp
这是一个用于比较两个文件内容的命令,但它不会输出任何内容,而是通过退出状态码来表示文件是否相同。-s
选项:静默模式,不输出任何内容,只通过退出状态码来表示结果。这个命令退出状态码为0
表示文件内容完全相同,1
表示文件内容不同,>1
表示发生错误。这里的逻辑就是判断新生成的文件timestamp_autogenerated.h.tmp与timestamp_autogenerated.h文件是否相同,若相同,这次就没有必要更新了,就把中间文件删掉,若不相同,就把timestamp_autogenerated.h.tmp重命名为timestamp_autogenerated.h。
3.5 filechk_$(1) 我们再来看一下这个filechk_$(1)
,在调用filechk函数的时候是这样的:
1 $(call filechk,timestamp.h)
我们将$(1)
替换后,就是:
1 $(filechk_timestamp.h) < Makefile > include /generated/timestamp_autogenerated.h.tmp;
这里我们看一下这个filechk_timestamp.h,它定义在顶层Makefile · filechk_timestamp.h :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 define filechk_timestamp.h (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}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' else \ return 42; \ fi; \ else \ LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' fi) endef
if test -n
是判断字符串是否为空,我找了下顶层 Makefile ,好像也没找到这个SOURCE_DATE_EPOCH的定义,SOURCE_DATE_EPOCH它是一个环境变量,通常用于构建过程中,以确保生成的二进制文件和相关文件具有确定的时间戳。加个打印信息就会发现,这里为空,所以走的是else的部分,非空的部分就没去仔细研究了。
LC_ALL=C
:这个是一个环境变量设置,用于覆盖所有本地化设置,使其使用 C 本地化。这意味着程序的行为将遵循 POSIX 标准,忽略用户的本地化设置。这对于确保跨平台一致性尤其有用,特别是在处理文本数据时。
date +'#define ...'
:这个就是以后面指定的格式获取时间字符串,放到终端执行以下就知道了:
1 2 sumu@sumu-virtual-machine:~/7Linux/imx6ull-uboot$ LC_ALL=C date +'#define U_BOOT_DATE "%b %d %C%y"' # define U_BOOT_DATE "Nov 22 2024"
因为走的是else分支,所以这里我们可以简化一下:
1 2 3 4 5 6 7 define filechk_timestamp.h (LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' endef
3.6 timestamp_autogenerated.h timestamp_autogenerated.h这个文件怎么生成的?上面已经分析了里面的内容怎么来的,那文件怎么创建的?其实在这里scripts/Kbuild.include · filechk :
1 $(filechk_$(1)) < $< > $@ .tmp;
前面我们已经可以将这个展开了:
1 $(filechk_timestamp.h) < Makefile > include /generated/timestamp_autogenerated.h.tmp;
把这个filechk_timestamp.h也替换一下:
1 2 3 4 5 (LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +'
这个用了两个重定向操作符<
和>
,<
是输入重定向操作符,用于将文件内容作为命令的标准输入。这在处理文件内容时非常有用,可以避免手动输入数据或从命令行传递大量数据。>
是重定向操作符,用于将命令的输出重定向到文件或其他目标。
所以这里的含义就是,将当前目录下的Makefile文件作为左边命令的输入(我并没有发现它有什么具体的作用,后面发现了再补充),将命令的结果重定向到include/generated/timestamp_autogenerated.h.tmp文件中去。当文件不存在的时候就会直接创建。
4. 规则展开 到这里为止,就可以把这个timestamp_h规则完全展开了,但是这里展开写的太长了,我们直接写一个单独的demo来演示效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 define filechk_timestamp_autogenerated.h (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}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' else \ return 42; \ fi; \ else \ LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' fi) endef define filechk set -e; \ : ' CHK $@ '; \ mkdir -p $(dir $@ ) ; \ $(filechk_$(1)) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ : ' UPD $@ '; \ mv -f $@ .tmp $@ ; \ fi endef srctree := . timestamp_h := timestamp_autogenerated.h $(timestamp_h) : $(srctree) /Makefile FORCE $(call filechk,timestamp_autogenerated.h) PHONY += FORCE FORCE: .PHONY : $(PHONY)
其中FORCE是一个伪目标,他可以保证每次都执行timestamp_h规则,可以保证每次都更新time_stamp.h文件。
三、version 接下来我们看一下这个uboot的版本是怎么显示和获取的。我们前面知道version_autogenerated.h 这个文件的内容如下:
1 2 3 4 #define PLAIN_VERSION "2019.04-gf32bcf5b" #define U_BOOT_VERSION "U-Boot " PLAIN_VERSION #define CC_VERSION_STRING "arm-linux-gnueabihf-gcc (GNU Toolchain for the A-profile Architecture 8.3-2019.03 (arm-rel-8.36)) 8.3.0" #define LD_VERSION_STRING "GNU ld (GNU Toolchain for the A-profile Architecture 8.3-2019.03 (arm-rel-8.36)) 2.32.0.20190321"
可以看到它包含了ubuntu的版本号、git版本信息、编译器和链接器的版本信息。下面我们来分析一下这些都是怎么生成的。
1. 编译信息 和上面一样,我们通过以下命令编译一次uboot,获取编译过程信息,看看里面关于这个文件的信息:
1 2 3 make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16 > make_info.txt
获取到这个make_info.txt文件后,我们打开搜索一下version_autogenerated.h,就会发现所有相关的信息都在这里:
1 set -e; : ' CHK include/generated/version_autogenerated.h'; mkdir -p include/generated/; (echo \#define PLAIN_VERSION \"2019.04""\"; echo \#define U_BOOT_VERSION \"U-Boot \" PLAIN_VERSION; echo \#define CC_VERSION_STRING \"$(LC_ALL=C arm-linux-gnueabihf-gcc --version | head -n 1)\"; echo \#define LD_VERSION_STRING \"$(LC_ALL=C arm-linux-gnueabihf-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
整理一下:
1 2 3 4 5 6 7 8 9 10 11 12 set -e; : ' CHK include/generated/version_autogenerated.h'; mkdir -p include/generated/; (echo \#define PLAIN_VERSION \"2019.04""\"; echo \#define U_BOOT_VERSION \"U-Boot \" PLAIN_VERSION; echo \#define CC_VERSION_STRING \"$(LC_ALL=C arm-linux-gnueabihf-gcc --version | head -n 1)\"; echo \#define LD_VERSION_STRING \"$(LC_ALL=C arm-linux-gnueabihf-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
接下来我们就来看一看这个编译信息怎么出来的。
2. version_h 和时间信息一样,我们先在顶层Makefile中找到version_h的定义: Makefile · version_h :
1 version_h := include /generated/version_autogenerated.h
这里定义了这个version_h变量,我们接着搜一下这个version_h关键词,会发现它也是一个目标,在Makefile 中的规则如下:
1 2 $(version_h) : include /config/uboot.release FORCE $(call filechk,version.h)
发现这里和前面的 Makefile · timestamp.h 一样,都是调用了filechk函数,这里简单展开一下:
1 2 include/generated/version_autogenerated.h: include/config/uboot.release FORCE $(call filechk,version_h)
3. filechk filechk函数前面已经找过了,它定义在scripts/Kbuild.include · filechk :
1 2 3 4 5 6 7 8 9 10 11 12 define filechk $(Q) set -e; \ $(kecho) ' CHK $@ '; \ mkdir -p $(dir $@ ) ; \ $(filechk_$(1)) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ $(kecho) ' UPD $@ '; \ mv -f $@ .tmp $@ ; \ fi endef
可以看到这里的$(1)
应该就是前面的version_h,所以这里就是:
1 2 3 4 5 6 7 8 9 10 11 12 define filechk $(Q) set -e; \ $(kecho) ' CHK $@ '; \ mkdir -p $(dir $@ ) ; \ $(filechk_version_h) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ $(kecho) ' UPD $@ '; \ mv -f $@ .tmp $@ ; \ fi endef
3.1 变量的值 还是和前面一样,这些$@
、$<
都是在规则中确定的,我们省点事,加点打印信息好了,我们修改scripts/Kbuild.include · filechk 中 filechk 函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 define filechk echo "" \ echo "Q=$(Q)" \ echo "kecho=$(kecho)" \ echo "$$@=$@" \ echo "$$<=$<" \ echo "" \ $ (Q)set -e; \ $(kecho) ' CHK $@' ; \ mkdir -p $(dir $@ ); \ $(filechk_$(1)) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ $(kecho) ' UPD $@' ; \ mv -f $@ .tmp $@ ; \ fi endef
可以找到有这么一条打印信息:
1 echo "" echo "Q=" echo "kecho=:" echo "$@=include/generated/version_autogenerated.h" echo "$<=include/config/uboot.release" echo "" set -e; : ' CHK include/generated/version_autogenerated.h'; mkdir -p include/generated/; (echo \#define PLAIN_VERSION \"2019.04""\"; echo \#define U_BOOT_VERSION \"U-Boot \" PLAIN_VERSION; echo \#define CC_VERSION_STRING \"$(LC_ALL=C arm-linux-gnueabihf-gcc --version | head -n 1)\"; echo \#define LD_VERSION_STRING \"$(LC_ALL=C arm-linux-gnueabihf-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
可以看到这些就是我们添加的打印信息:
1 echo "" echo "Q=" echo "kecho=:" echo "$@=include/generated/version_autogenerated.h" echo "$<=include/config/uboot.release" echo ""
3.2 filehck展开 所以这里我们对这个函数展开:
1 2 3 4 5 6 7 8 9 10 11 12 define filechk set -e; \ : ' CHK include /generated/version_autogenerated.h'; \ mkdir -p $(dir include/generated/version_autogenerated.h) ; \ $(filechk_$(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 endef
这里和前面都是一样的。
3.3 filechk_$(1) 3.3.1 filechk_version.h 我们再来看一下这个filechk_$(1)
,在调用filechk函数的时候是这样的:
1 $(call filechk,version.h)
我们将$(1)
替换后,就是:
1 $(filechk_version.h) < include /config/uboot.release > include /generated/version_autogenerated.h.tmp;
这里我们看一下这个filechk_version.h,它定义在顶层Makefile · filechk_version.h :
1 2 3 4 5 6 define filechk_version.h (echo \ echo \ echo \ echo \ endef
PLAIN_VERSION:这个的值由UBOOTRELEASE决定,我们后面再说。
U_BOOT_VERSION:这个是最终的uboot版本信息,它的内容与PLAIN_VERSION有关。
CC_VERSION_STRING是编译器的版本,我们可以在终端执行一下看看打印信息:
1 2 sumu@sumu-virtual-machine:~/7Linux/imx6ull-uboot$ LC_ALL=C arm-linux-gnueabihf-gcc --version | head -n 1 arm-linux-gnueabihf-gcc (GNU Toolchain for the A-profile Architecture 8.3-2019.03 (arm-rel-8.36)) 8.3.0
LD_VERSION_STRING是链接器的版本:
1 2 sumu@sumu-virtual-machine:~/7Linux/imx6ull-uboot$ LC_ALL=C arm-linux-gnueabihf-ld --version | head -n 1 GNU ld (GNU Toolchain for the A-profile Architecture 8.3-2019.03 (arm-rel-8.36)) 2.32.0.20190321
接下来我们再来看看前面的uboot版本怎么得到的。
3.3.2 UBOOTRELEASE 接下来我们看一下这个UBOOTRELEASE变量,它在Makefile · UBOOTRELEASE 中有定义:
1 2 UBOOTRELEASE = $(shell cat include/config/uboot.release 2> /dev/null)
可以看到它其实是获取了include/config/uboot.release文件的内容。接下来就是分析uboot.release的来源了。我们来看一下这个文件的内容:
所以这里就对应了:
1 #define PLAIN_VERSION "2019.04-gf32bcf5b"
这个版本没对应上,可以发现是本地的上一个版本,这是因为,我们本地提交后,会产生一个新的git版本号,但是我们编译的时候是还没有提交的,所以这里就会晚一个版本啦,这个影响不大,这只是提交到版本库的记录会落后,我们拉到本地重新编译,这个文件就会更新成最新版本啦。
3.4 uboot.release 接下来就是看一看uboot.release中的内容来自于哪里?
3.4.1 uboot.release规则 我们这部分来分析一下。这个文件其实是编译后生成的,我们在Makefile · uboot.release 可以看到它的定义:
1 2 3 include/config/uboot.release: include/config/auto.conf FORCE $(call filechk,uboot.release)
发现这里也是调用了filechk函数,并且传入的参数为uboot.release。
3.4.3 filechk展开 我们先看一下这个filechk函数,和前面是一样的,定义在scripts/Kbuild.include · filechk :
1 2 3 4 5 6 7 8 9 10 11 12 define filechk $(Q) set -e; \ $(kecho) ' CHK $@ '; \ mkdir -p $(dir $@ ) ; \ $(filechk_$(1)) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ $(kecho) ' UPD $@ '; \ mv -f $@ .tmp $@ ; \ fi endef
我们根据调用命令:
1 $(call filechk,uboot.release)
对其进行展开,我们前面知道:
1 2 3 4 5 Q= kecho=: $@ =include /config/uboot.release@<=include /config/auto.conf $(1)=uboot.release
所以这里是:
1 2 3 4 5 6 7 8 9 10 11 12 define filechk set -e; \ : ' CHK include /config/uboot.release'; \ mkdir -p $(dir include/config/uboot.release) ; \ $(filechk_uboot.release) < 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 endef
3.4.2 filechk_$(1) 我们来看一下Makefile · filechk_uboot.release :
1 2 3 define filechk_uboot.release echo "$(UBOOTVERSION) $$($(CONFIG_SHELL) $(srctree) /scripts/setlocalversion $(srctree) )" endef
1 UBOOTVERSION = $(VERSION) $(if $(PATCHLEVEL) ,.$(PATCHLEVEL) $(if $(SUBLEVEL) ,.$(SUBLEVEL) ) )$(EXTRAVERSION)
这里其实就是把Makefile · VERSION 这些版本号组合起来。
1 2 3 4 CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \ else if [ -x /bin/bash ]; then echo /bin/bash; \ else echo sh; fi ; fi)
就不分析那几个变量了,我们直接从编译信息里找一下,我们去前面生成的make_info.txt中搜一下这个scripts/setlocalversion关键词:
1 set -e; : ' CHK include/config/uboot.release'; mkdir -p include/config/; echo "2019.04$(/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
整理一下就是:
1 2 3 4 5 6 7 8 9 10 set -e; : ' CHK include/config/uboot.release'; mkdir -p include/config/; echo "2019.04$(/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
可以看到:
1 2 3 $(filechk_uboot.release) < include /config/auto.conf > include /config/uboot.release.tmp; echo "2019.04$(/bin/bash ./scripts/setlocalversion .)" < include /config/auto.conf > include /config/uboot.release.tmp;
所以其实有:
1 2 3 4 5 6 7 8 9 10 11 12 define filechk_uboot.release echo "$(UBOOTVERSION) $$($(CONFIG_SHELL) $(srctree) /scripts/setlocalversion $(srctree) )" endef define filechk_uboot.release echo "2019.04$(/bin/bash ./scripts/setlocalversion .)" endf
其实这里就是执行了scripts/setlocalversion中的这个脚本并把脚本的结果和UBOOTVERSION拼接起来。然后通过>
将拼接后的字符串重定向到include/config/uboot.release.tmp文件中。
3.5 setlocalversion 我们来看一下这个脚本 scripts/setlocalversion 在做什么:
1 2 3 4 5 6 7 8 # This scripts adds local version information from the version # control systems git, mercurial (hg) and subversion (svn). # # (see MAINTAINERS) and CC Nico Schottelius # <nico-linuxsetlocalversion -at- schottelius.org>. #
根据前面的信息,可以看到这个其实是获取了git或者hg或者sv版本号,我们直接终端执行一下:
所以这里得到的""-gde6d2f71-dirty
和前面的2019.04
结合起来就是2019.04""-gde6d2f71-dirty
4. 生成git版本号 我们了解了uboot的git版本号生成过程,我们自己来写一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 UBOOTRELEASE=$(shell git rev-parse --verify --short HEAD) define filechk_version_autogenerated.h (echo \ echo \ echo \ echo \ endef define filechk set -e; \ : ' CHK $@ '; \ mkdir -p $(dir $@ ) ; \ $(filechk_$(1)) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ : ' UPD $@ '; \ mv -f $@ .tmp $@ ; \ fi endef srctree := . version_h := version_autogenerated.h CC := arm-linux-gnueabihf-gcc LD := arm-linux-gnueabihf-ld export LD CC$(version_h) : $(srctree) /Makefile FORCE echo "UBOOTRELEASE=" $(UBOOTRELEASE) $(call filechk,version_autogenerated.h) PHONY += FORCE FORCE: .PHONY : $(PHONY)
四、应用实例 这里先写一个在内核模块中打印编译时间和版本信息的实例。整体可以看这里:03_module_basic/03_printk_prj_info · 苏木/imx6ull-driver-demo - 码云 - 开源中国
1. Kbuild.include 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 define filechk_timestamp_autogenerated.h (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}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' else \ return 42; \ fi; \ else \ LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' fi) endef define filechk_version_autogenerated.h (echo \ echo \ echo \ echo \ endef define filechk $(Q) set -e; \ : ' CHK $@ '; \ mkdir -p $(dir $@ ) ; \ $(filechk_$(1)) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ : ' UPD $@ '; \ mv -f $@ .tmp $@ ; \ fi endef
2. Makefile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 MODULE_NAME := printk_prj_info ARCH ?= arm MAKE_PARAM := CURRENT_PATH := $(shell pwd) KERNEL_KO_RELEASE = $(shell git rev-parse --verify --short HEAD) ifeq ("$(origin V)" , "command line" ) KBUILD_VERBOSE = $(V) endif ifndef KBUILD_VERBOSE KBUILD_VERBOSE = 0 endif ifeq ($(KBUILD_VERBOSE) ,1) quiet = Q = else quiet=quiet_ Q = @ endif INCDIRS := ./ SRCDIRS := ./ INCLUDE := $(patsubst %, -I %, $(INCDIRS) ) TFTP_SERVER ?= ~/3tftp NFS_SERVER ?= ~/4nfs TFTP_DIR ?= $(TFTP_SERVER) ROOTFS_ROOT_DIR ?= $(NFS_SERVER) /imx6ull_rootfs ROOTFS_MODULE_DIR ?= $(ROOTFS_ROOT_DIR) /drivers_demo ifeq ($(KERNELRELEASE) ,)ifeq ($(ARCH) , arm)KERNELDIR ?= ~/7Linux/imx6ull-kernel MAKE_PARAM += ARCH=arm MAKE_PARAM += CROSS_COMPILE=arm-linux-gnueabihf- CROSS_COMPILE_PREFIX ?= arm-linux-gnueabihf- else KERNELDIR ?= /lib/modules/$(shell uname -r) /build CROSS_COMPILE_PREFIX ?= endif CC := $(CROSS_COMPILE_PREFIX) gcc LD := $(CROSS_COMPILE_PREFIX) ld include Kbuild.include srctree := . timestamp_h := timestamp_autogenerated.h version_h := version_autogenerated.h export CC LD srctreeall: $(timestamp_h) $(version_h) modules modules: $(MAKE) $(MAKE_PARAM) -C $(KERNELDIR) M=$(CURRENT_PATH) modules $(INCLUDE) modules_install: $(MAKE) $(MAKE_PARAM) -C $(KERNELDIR) M=$(CURRENT_PATH) modules INSTALL_MOD_PATH=$(ROOTFS_MODULE_DIR) modules_install $(timestamp_h) : $(srctree) /Makefile FORCE $(call filechk,timestamp_autogenerated.h) $(version_h) : $(srctree) /Makefile FORCE $(call filechk,version_autogenerated.h) install: @sudo cp -v $(MODULE_NAME) .ko $(ROOTFS_MODULE_DIR) uninstall: @sudo rm -vf $(ROOTFS_MODULE_DIR) /$(MODULE_NAME) .ko PHONY += FORCE FORCE: PHONY += clean clean: rm -rf *.o *.ko *.o.d rm -rf .*.cmd *.mod.* *.mod modules.order Module.symvers .tmp_versions .cache.mk rm -rf $(timestamp_h) rm -rf $(version_h) .PHONY : $(PHONY)help: @echo "\033[1;32m================================ Help ================================\033[0m" @echo "Ubuntu may need to add sudo:" @echo "insmod <module_name>.ko # Load module" @echo "rmmod <module_name> # Uninstall the module" @echo "dmesg -C # Clear the kernel print information" @echo "lsmod # Check the kernel modules that have been inserted" @echo "dmesg # View information printed by the kernel" @echo "file <module_name>.ko # View \".Ko\" file information" @echo "" @echo "make ARCH=x86_64 # x86_64 platform" @echo "make # arm platform" @echo "\033[1;32m======================================================================\033[0m" print: @echo "KERNELDIR = $(KERNELDIR) " @echo "INCLUDE = $(INCLUDE) " else CONFIG_MODULE_SIG = n obj-m += $(MODULE_NAME) .o endif
3. printk_prj_info.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include "./timestamp_autogenerated.h" #include "./version_autogenerated.h" static int __init printk_demo_init (void ) { printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n" , __FUNCTION__, __LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION); printk("printk_demo module init!\n" ); printk(KERN_EMERG"KERN_EMERG:%s\r\n" , KERN_EMERG); printk(KERN_ALERT"KERN_ALERT:%s\r\n" , KERN_ALERT); printk(KERN_CRIT"KERN_CRIT:%s\r\n" , KERN_CRIT); printk(KERN_ERR"KERN_ERR:%s\r\n" , KERN_ERR); printk(KERN_WARNING"KERN_WARNING:%s\r\n" , KERN_WARNING); printk(KERN_NOTICE"KERN_NOTICE:%s\r\n" , KERN_NOTICE); printk(KERN_INFO"KERN_INFO:%s\r\n" , KERN_INFO); printk(KERN_DEBUG"KERN_DEBUG:%s\r\n" , KERN_DEBUG); return 0 ; } static void __exit printk_demo_exit (void ) { printk("printk_demo exit!\n" ); } module_init(printk_demo_init); module_exit(printk_demo_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("sumu" ); MODULE_DESCRIPTION("Description" ); MODULE_ALIAS("module's other name" );