LV05-02-U-Boot-07-uboot编译时间与版本

本文主要是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官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
其他网站 kernel - Linux source code (v4.15) - Bootlin linux内核源码在线查看
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、为什么?

为什么要研究这个?因为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-list HEAD | awk 'NR==1')
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
#define filechk_timestamp
# @echo "this is a makefile function!"
# @echo "$(0):表示函数名"
# @echo "$(1):表示传给函数的第一个参数"
# @echo "$(2):表示传给函数的第二个参数"
#endef

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
###
# filechk is used to check if the content of a generated file is updated.
# Sample usage:
# define filechk_sample
# echo $KERNELRELEASE
# endef
# version.h : Makefile
# $(call filechk,sample)
# The rule defined shall write to stdout the content of the new file.
# The existing file will be compared with the new one.
# - If no file exist it is created
# - If the content differ the new file is used
# - If they are equal no change, and no timestamp update
# - stdin is piped in from the first prerequisite ($<) so one has
# to specify a valid file as first prerequisite (often the kbuild file)
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 -scmp这是一个用于比较两个文件内容的命令,但它不会输出任何内容,而是通过退出状态码来表示文件是否相同。-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
# The SOURCE_DATE_EPOCH mechanism requires a date that behaves like GNU date.
# The BSD date on the other hand behaves different and would produce errors
# with the misused '-d' switch. Respect that and search a working date with
# well known pre- and suffixes for the GNU variant of date.
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}" +'#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)
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 +'#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';)
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 +'#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';) < Makefile > include/generated/timestamp_autogenerated.h.tmp;

这个用了两个重定向操作符<>< 是输入重定向操作符,用于将文件内容作为命令的标准输入。这在处理文件内容时非常有用,可以避免手动输入数据或从命令行传递大量数据。> 是重定向操作符,用于将命令的输出重定向到文件或其他目标。

所以这里的含义就是,将当前目录下的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}" +'#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)
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 \#define PLAIN_VERSION \"$(UBOOTRELEASE)\"; \
echo \#define U_BOOT_VERSION \"U-Boot \" PLAIN_VERSION; \
echo \#define CC_VERSION_STRING \"$$(LC_ALL=C $(CC) --version | head -n 1)\"; \
echo \#define LD_VERSION_STRING \"$$(LC_ALL=C $(LD) --version | head -n 1)\"; )
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
# Read UBOOTRELEASE from include/config/uboot.release (if it exists)
UBOOTRELEASE = $(shell cat include/config/uboot.release 2> /dev/null)

可以看到它其实是获取了include/config/uboot.release文件的内容。接下来就是分析uboot.release的来源了。我们来看一下这个文件的内容:

image-20241122215559817

所以这里就对应了:

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
# Store (new) UBOOTRELEASE string in include/config/uboot.release
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
# SHELL used by kbuild
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

# UBOOTVERSION=2019.04
# CONFIG_SHELL=/bin/bash
# srctree=.

其实这里就是执行了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).
#
# If something goes wrong, send a mail the kernel build mailinglist
# (see MAINTAINERS) and CC Nico Schottelius
# <nico-linuxsetlocalversion -at- schottelius.org>.
#
#

根据前面的信息,可以看到这个其实是获取了git或者hg或者sv版本号,我们直接终端执行一下:

image-20241122230044913

所以这里得到的""-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 \#define PLAIN_VERSION \"$(UBOOTRELEASE)\"; \
echo \#define U_BOOT_VERSION \"U-Boot \" PLAIN_VERSION; \
echo \#define CC_VERSION_STRING \"$$(LC_ALL=C $(CC) --version | head -n 1)\"; \
echo \#define LD_VERSION_STRING \"$$(LC_ALL=C $(LD) --version | head -n 1)\"; )
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}" +'#define KERNEL_KO_DATE "%b %d %C%y"'; \
LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +'#define KERNEL_KO_TIME "%T"'; \
LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +'#define KERNEL_KO_TZ "%z"'; \
LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +'#define KERNEL_KO_DMI_DATE "%m/%d/%Y"'; \
LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +'#define KERNEL_KO_BUILD_DATE 0x%Y%m%d'; \
else \
return 42; \
fi; \
else \
LC_ALL=C date +'#define KERNEL_KO_DATE "%b %d %C%y"'; \
LC_ALL=C date +'#define KERNEL_KO_TIME "%T"'; \
LC_ALL=C date +'#define KERNEL_KO_TZ "%z"'; \
LC_ALL=C date +'#define KERNEL_KO_DMI_DATE "%m/%d/%Y"'; \
LC_ALL=C date +'#define KERNEL_KO_BUILD_DATE 0x%Y%m%d'; \
fi)
endef

define filechk_version_autogenerated.h
(echo \#define PLAIN_VERSION \"$(KERNEL_KO_RELEASE)\"; \
echo \#define KERNEL_KO_VERSION \"\" PLAIN_VERSION; \
echo \#define CC_VERSION_STRING \"$$(LC_ALL=C $(CC) --version | head -n 1)\"; \
echo \#define LD_VERSION_STRING \"$$(LC_ALL=C $(LD) --version | head -n 1)\"; )
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
# 模块名和模块测试APP名称
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))
#=====================================================
# NFS 共享目录
TFTP_SERVER ?= ~/3tftp
NFS_SERVER ?= ~/4nfs

TFTP_DIR ?= $(TFTP_SERVER)
ROOTFS_ROOT_DIR ?= $(NFS_SERVER)/imx6ull_rootfs
#ROOTFS_MODULE_DIR ?= $(ROOTFS_ROOT_DIR)/lib/modules/4.19.71-00007-g51f3cd8ec-dirty
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 srctree

# 编译模块和测试程序
all: $(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)

# 拷贝相关文件到nfs共享目录
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> /* module_init module_exit */
#include <linux/module.h> /* MODULE_LICENSE */
#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");
}

// 将__init定义的函数指定为驱动的入口函数
module_init(printk_demo_init);


// 将__exit定义的函数指定为驱动的出口函数
module_exit(printk_demo_exit);

/* 模块信息(通过 modinfo printk_demo 查看) */
MODULE_LICENSE("GPL"); /* 源码的许可证协议 */
MODULE_AUTHOR("sumu"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */