LV120-编译时间与版本解析
一、为什么?
为什么要研究这个?因为 linux 内核的还没去学呢啊,主要是因为后面在搞驱动开发学习的时候需要这个。为什么?在驱动开发的时候,一般我是把驱动的源文件放在 linux 内核源码之外,通过调用内核的 makefile 来编译生成独立的内核模块,仓库在这里:imx6ull-driver-demo: i.mx6ull 驱动开发 demo
因为我是进行了 git 版本管理的,在实际的工作中,也经常遇到不知道用的驱动是什么版本的问题,用的是这一个版本,随着功能的完善,版本肯定要迭代更新的,可是回头谁知道自己用的哪个驱动版本啊,也许有别的办法能追溯,但起码我写这个笔记的时候还没有接触到。
目前想到的办法就是把 git 版本号以及编译时间这些信息写进去,git 版本号的获取可以在 Makefile 中这样获取:
#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 参数是要加在编译工具后面的,例如:
gcc -DDEBUG source.c -o program但是我们在内核源码之外写的驱动源文件,通过调用内核的 makefile 来编译的,经过实践发现,这里没办法直接把-D 参数传给内核 Makefile,至少写这个笔记的时候我还没发现怎么传进去让 ko 的源文件可以调用。所以我就去扒了 uboot 的源码,因为我们知道 uboot 是会生成两个文件的:
#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#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,获取编译过程信息,看看里面关于这个文件的信息:
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,就会发现所有相关的信息都在这里:
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整理一下:
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:
timestamp_h := include/generated/timestamp_autogenerated.h看到这里定义了这个 timestamp_h 变量,我们接着搜一下这个 timestamp_h 关键词,会发现它也是一个目标,在 Makefile 中的规则如下:
$(timestamp_h): $(srctree)/Makefile FORCE
$(call filechk,timestamp.h)我们前面分析顶层 Makefile 的时候分析过,这个 Makefile · srctree 最后是 .,所以这里简单先展开一下就是:
include/generated/timestamp_autogenerated.h: ./Makefile FORCE
$(call filechk,timestamp.h)接下来就是看这个 filechk 和 timestamp.h 是什么了。
3. filechk
3.1 Makefile 中的自定义函数
我们先回顾一下 Makefile 中的自定义函数,在 Makefile 中是可以自己定义一个函数然后调用的。比如:
define filechk_timestamp.h
@echo "this is a makefile function!"
@echo "\$(0)=$(0):表示函数名"
@echo "\$(1)=$(1):表示传给函数的第一个参数"
@echo "\$(2)=$(2):表示传给函数的第二个参数"
endef然后可以通过以下语句调用:
$(call func_name,arg1,arg2,...)一般而言,call 本身并不具备编译功能,call 只是将自定义函数在当前位置展开,遇到 Makefile 内置函数会自动执行。其中 arg1、arg2、...是传入函数的参数。我们可以尝试一下,新建一个 Makefile 文件,输入以下内容:
define filechk_timestamp
@echo "this is a makefile function!"
@echo "$(0):表示函数名"
@echo "$(1):表示传给函数的第一个参数"
@echo "$(2):表示传给函数的第二个参数"
endef
test:
$(call filechk_timestamp,arg1,arg2)我们直接执行 make,就会得到如下打印信息:
this is a makefile function!
filechk_timestamp:表示函数名
arg1:表示传给函数的第一个参数
arg2:表示传给函数的第二个参数前面说到 call 只是将自定义函数在当前位置展开,所以上面的函数其实等价于:
#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:
###
# 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,所以这里就是:
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
endef3.3 变量的值
哪些变量都是啥啊?其实这这些 $@、$< 都是在规则中确定的,我们省点事,加点打印信息好了,我们修改 scripts/Kbuild.include · filechk 中 filechk 函数如下:
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可以找到有这么一条打印信息:
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可以看到这些就是我们添加的打印信息:
echo "" echo "Q=" echo "kecho=:" echo "$@=include/generated/timestamp_autogenerated.h" echo "$<=Makefile" echo ""3.4 filehck 展开
所以这里我们对这个函数展开:
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
endefset -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 函数的时候是这样的:
$(call filechk,timestamp.h)我们将 $(1) 替换后,就是:
$(filechk_timestamp.h) < Makefile > include/generated/timestamp_autogenerated.h.tmp;这里我们看一下这个 filechk_timestamp.h,它定义在顶层 Makefile · filechk_timestamp.h:
# 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)
endefif test -n是判断字符串是否为空,我找了下顶层 Makefile,好像也没找到这个 SOURCE_DATE_EPOCH 的定义,SOURCE_DATE_EPOCH 它是一个环境变量,通常用于构建过程中,以确保生成的二进制文件和相关文件具有确定的时间戳。加个打印信息就会发现,这里为空,所以走的是 else 的部分,非空的部分就没去仔细研究了。LC_ALL=C:这个是一个环境变量设置,用于覆盖所有本地化设置,使其使用 C 本地化。这意味着程序的行为将遵循 POSIX 标准,忽略用户的本地化设置。这对于确保跨平台一致性尤其有用,特别是在处理文本数据时。date +'#define ...':这个就是以后面指定的格式获取时间字符串,放到终端执行以下就知道了:
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 分支,所以这里我们可以简化一下:
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';)
endef3.6 timestamp_autogenerated.h
timestamp_autogenerated.h 这个文件怎么生成的?上面已经分析了里面的内容怎么来的,那文件怎么创建的?其实在这里 scripts/Kbuild.include · filechk:
$(filechk_$(1)) < $< > $@.tmp;前面我们已经可以将这个展开了:
$(filechk_timestamp.h) < Makefile > include/generated/timestamp_autogenerated.h.tmp;把这个 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';) < Makefile > include/generated/timestamp_autogenerated.h.tmp;这个用了两个重定向操作符 < 和 >,< 是输入重定向操作符,用于将文件内容作为命令的标准输入。这在处理文件内容时非常有用,可以避免手动输入数据或从命令行传递大量数据。> 是重定向操作符,用于将命令的输出重定向到文件或其他目标。
所以这里的含义就是,将当前目录下的 Makefile 文件作为左边命令的输入(我并没有发现它有什么具体的作用,后面发现了再补充),将命令的结果重定向到 include/generated/timestamp_autogenerated.h.tmp 文件中去。当文件不存在的时候就会直接创建。
4. 规则展开
到这里为止,就可以把这个 timestamp_h 规则完全展开了,但是这里展开写的太长了,我们直接写一个单独的 demo 来演示效果:
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 这个文件的内容如下:
#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,获取编译过程信息,看看里面关于这个文件的信息:
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,就会发现所有相关的信息都在这里:
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整理一下:
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:
version_h := include/generated/version_autogenerated.h这里定义了这个 version_h 变量,我们接着搜一下这个 version_h 关键词,会发现它也是一个目标,在 Makefile 中的规则如下:
$(version_h): include/config/uboot.release FORCE
$(call filechk,version.h)发现这里和前面的 Makefile · timestamp.h 一样,都是调用了 filechk 函数,这里简单展开一下:
include/generated/version_autogenerated.h: include/config/uboot.release FORCE
$(call filechk,version_h)3. filechk
filechk 函数前面已经找过了,它定义在 scripts/Kbuild.include · filechk:
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,所以这里就是:
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
endef3.1 变量的值
还是和前面一样,这些 $@、$< 都是在规则中确定的,我们省点事,加点打印信息好了,我们修改 scripts/Kbuild.include · filechk 中 filechk 函数如下:
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可以找到有这么一条打印信息:
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可以看到这些就是我们添加的打印信息:
echo "" echo "Q=" echo "kecho=:" echo "$@=include/generated/version_autogenerated.h" echo "$<=include/config/uboot.release" echo ""3.2 filehck 展开
所以这里我们对这个函数展开:
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 函数的时候是这样的:
$(call filechk,version.h)我们将 $(1) 替换后,就是:
$(filechk_version.h) < include/config/uboot.release > include/generated/version_autogenerated.h.tmp;这里我们看一下这个 filechk_version.h,它定义在顶层 Makefile · filechk_version.h:
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 是编译器的版本,我们可以在终端执行一下看看打印信息:
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 是链接器的版本:
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 中有定义:
# 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 的来源了。我们来看一下这个文件的内容:

所以这里就对应了:
#define PLAIN_VERSION "2019.04-gf32bcf5b"这个版本没对应上,可以发现是本地的上一个版本,这是因为,我们本地提交后,会产生一个新的 git 版本号,但是我们编译的时候是还没有提交的,所以这里就会晚一个版本啦,这个影响不大,这只是提交到版本库的记录会落后,我们拉到本地重新编译,这个文件就会更新成最新版本啦。
3.4 uboot.release
接下来就是看一看 uboot.release 中的内容来自于哪里?
3.4.1 uboot.release 规则
我们这部分来分析一下。这个文件其实是编译后生成的,我们在 Makefile · uboot.release 可以看到它的定义:
# 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:
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我们根据调用命令:
$(call filechk,uboot.release)对其进行展开,我们前面知道:
Q=
kecho=:
$@=include/config/uboot.release
@<=include/config/auto.conf
$(1)=uboot.release所以这里是:
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
endef3.4.2 filechk_$(1)
我们来看一下 Makefile · filechk_uboot.release:
define filechk_uboot.release
echo "$(UBOOTVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"
endef- UBOOTVERSION,它定义在 Makefile · UBOOTVERSION:
UBOOTVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)这里其实就是把 Makefile · VERSION 这些版本号组合起来。
srctree,之前分析过,这里是
.。CONFIG_SHELL,它定义在 Makefile · CONFIG_SHELL
# 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 关键词:
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整理一下就是:
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可以看到:
$(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;所以其实有:
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 在做什么:
# 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 版本号,我们直接终端执行一下:

所以这里得到的 ""-gde6d2f71-dirty 和前面的 2019.04 结合起来就是 2019.04""-gde6d2f71-dirty
4. 生成 git 版本号
我们了解了 uboot 的 git 版本号生成过程,我们自己来写一个:
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)四、应用实例
这里先写一个在内核模块中打印编译时间和版本信息的实例。整体可以看这里:linux-dev-org/imx6ull-driver-demo/03_module_basic/03_printk_prj_info
1. Kbuild.include
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
endef2. Makefile
# 模块名和模块测试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
endif3. printk_prj_info.c
#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"); /* 字符串常量内容为模块别名 */