LV05-02-U-Boot-06-02-menuconfig

uboot的图形配置界面?make menuconfig是怎么执行的?若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
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内核官网
点击查看相关文件下载
分类 网址 说明
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源码
kernel/git/stable/linux.git - Linux kernel stable tree linux kernel源码(官网,tag 4.19.71)
https://elixir.bootlin.com/u-boot/latest/source uboot源码

在前面的学习中我们知道 uboot 可以通过 mx6ull_alpha_emmc_defconfig 来配置,或者通过文件 mx6ull_alpha_emmc.h 来配置 uboot。还有另外一种配置 uboot 的方法,就是图形化配置,以前的 uboot 是不支持图形化配置,只有 Linux 内核才支持图形化配置。不过不知道从什么时候开始, uboot 也支持图形化配置了,这里我们就来学习一下如何通过图形化配置 uboot,并且学习一下图形化配置的原理,因为后面学习 Linux 驱动开发的时候可能要修改图形配置文件。(这里还是以默认的mx6ull_14x14_evk_defconfig配置文件来学习)

一、u-boot图形配置界面

1. 菜单说明

uboot 或 Linux 内核可以通过输入“make menuconfig”来打开图形化配置界面, menuconfig 是一套图形化的配置工具,需要 ncurses 库支持。 ncurses 库提供了一系列的 API 函数供调用者生成基于文本的图形界面,因此需要先在 ubuntu 中安装 ncurses 库,命令如下:

1
2
sudo apt-get install build-essential
sudo apt-get install libncurses5-dev

menuconfig 重点会用到两个文件: .config 和 Kconfig, .config 文件前面已经说了,这个文件保存着 uboot 的配置项,使用 menuconfig 配置完 uboot 以后肯定要更新.config 文件。 Kconfig文件是图形界面的描述文件,也就是描述界面应该有什么内容,很多目录下都会有 Kconfig 文件。

在打开图形化配置界面之前,要先使用“make xxx_defconfig”对 uboot 进行一次默认配置,只需要一次即可。如果使用“ make clean”清理了工程的话就那就需要重新使用“ make xxx_defconfig”再对 uboot 进行一次配置。进入 uboot 根目录,输入如下命令:

1
2
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

打开后的图形化界面如图所示:

image-20250209091829362

这就是主界面,主界面上方的英文就是简单的操作说明,操作方法如下 :

(1)通过键盘上的“ ↑ ”和“ ↓ ”键来选择要配置的菜单,后面跟着“—>”表示此配置项是有子配置项的,按下回车键就可以进入子配置项。 按下“ Enter ”键进入子菜单。

(2)菜单中高亮的字母就是此菜单的热键,在键盘上按下此高亮字母对应的键可以快速选中对应的菜单。

(3)选中子菜单以后按下“ Y ”键就会将相应的代码编译进 uboot 中,菜单前面变为“< * >”。按下“ N ”键不编译相应的代码,按下“ M ”键就会将相应的代码编译为模块,菜单前面变为“< M >”。也可以通过 “ space ” 键进行切换。

(4)按两下 “ Esc ” 键退出,也就是返回到上一级.

(5)按下 “ ? ” 键查看此菜单的帮助信息。

(6)按下“ / ” 键打开搜索框,可以在搜索框输入要搜索的内容。

(7)在配置界面下方会有五个按钮,这五个按钮的功能如下:

1
2
3
4
5
<Select>:选中按钮,和“Enter”键的功能相同,负责选中并进入某个菜单。
<Exit> :退出按钮,和按两下“Esc”键功能相同,退出当前菜单,返回到上一级。
<Help> :帮助按钮,查看选中菜单的帮助信息。
<Save> :保存按钮,保存修改后的配置文件。
<Load> :加载按钮,加载指定的配置文件。

2. 配置实例

2.1 开启dns命令

我们以 dns 命令为例,来看一下怎么实现图形界面对uboot的配置。我们按以下配置路径找到dns配置项:

1
2
3
Command line interface  ---> 
[*] Network commands --->
[ ] dns

这个dns配置项,此配置项用于配置 uboot 的 dns 命令,进入以后如图 :

image-20250209092457401

从图中可以看出, uboot 中有很多和网络有关的命令,比如 bootp、 tftpboot、 dhcp 等等。选中 dns,然后按下键盘上的“Y”键,此时 dns 前面的“[ ]”就会变成了“[ * ]”。

image-20250209092624310

每个选项有 3 种编译选项:编译进 uboot 中(也就是编译进 u-boot.bin 中)、取消编译(也就是不编译这个功能模块)、编译为模块。按下“Y”键表示编译进 uboot 中,此时“[ ]”变成了“[ * ]”;按下“N”表示不编译,“[ ]”默认表示不编译;有些功能模块是支持编译为模块的,这个一般在 Linux 内核里面很常用,后边学习linux就会学到, uboot 下面不使用,如果要将某个功能编译为模块,那就按下“M”,此时“[ ]”就会变为“< M >”。

Tips:一个小疑问?

后面我们移植自己的开发板的时候,会创建 mx6ull_alpha_emmc.h ,这个文件里面我们配置使能了 dhcp 和 ping 命令,但是在上图中 dhcp 和 ping 前面的“[ ]”并不是“[ * ]”,也就是说不编译 dhcp 和 ping命令,这不是冲突了吗?实际情况是 dhcp 和 ping 命令是会编译的。之所以在图中没有体现出来时因为我们是直接在mx6ull_alipha_emmc.h 中定义的宏 CONFIG_CMD_PING 和CONFIG_CMD_DHCP,而 menuconfig 是通过读取.config 文件来判断使能了哪些功能, .config里面并没有宏CONFIG_CMD_PING和CONFIG_CMD_DHCP,所以menuconfig就会识别出错。

选中 dns,然后按下“H”或者“?”键可以打开 dns 命令的提示信息,如图所示:

image-20250209092839200

2.2 退出配置界面

按两下 ESC 键即可退出提示界面,相当于返回上一层。选择 dns 命令以后,按两下 ESC 键(按两下 ESC 键相当于返回上一层),退出当前配置项,进入到上一层配置项。如果没有要修改的就按两下 ESC 键,退出到主配界面,如果也没有其他要修改的,那就再次按两下 ESC 键退出menuconfig 配置界面。如果修改过配置的话,在退出主界面的时候会有如图所示提示:

image-20250209092933255

询问是否保存新的配置文件,通过键盘的←或→键来选择“Yes”项,然后按下键盘上的回车键确认保存。至此,我们就完成了通过图形界面使能了 uboot 的 dns 命令,打开.config文件,会发现多了“CONFIG_CMD_DNS=y”这一行,如图中的 552 行所示:

image-20250209093059717

2.3 编译源码

配置完成后,直接使用如下命令编译 uboot:

1
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

需要注意的是千万不要使用下边的两个命令

1
2
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- disclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig

上边的disclean命令会清理工程,会删除掉.config 文件!通过图形化界面配置所有配置项都会被删除,结果就是竹篮打水一场空。有什么办法避免吗?当然有啦,我们的 mx6ull_14x14_evk_defconfig 文件内容其实就是一小部分的.config,我们可以直接把.config重命名,替换掉原来的mx6ull_14x14_evk_defconfig文件,这样就可以啦。

2.4 dns命令测试

编译完成以后烧写到 SD 卡中,重启开发板进入 uboot 命令模式,输入“?”查看是否有“dns”命令,一般肯定有的。使用 dns 命令来查看一下百度官网“www.baidu.com”的 IP 地址。注意!如果要与外部互联网通信,比如百度官网,这个时候要保证开发板能访问到外部互联网。 如果我们的开发板和电脑直接用网线连接的,那么肯定无法连接到外部网络, 这个时候 dns命令查看百度官网也会失败!所以开发板一定要连接到路由器上,而且要保证你的路由器能访问外网,比如手机连接到这个路由器上以后可以正常访问互联网。

要先设置一下 dns 服务器的 IP 地址,也就是设置环境变量 dnsip 的值,命令如下:

1
2
=> setenv dnsip 114.114.114.114
=> saveenv

设置好以后就可以使用 dns 命令查看百度官网的 IP 地址了,输入命令:

1
=> dns www.baidu.com

可以看出,“www.baidu.com” 的 IP 地址为 14.215.177.38,说明 dns 命令工作正常。这个就是通过图形化命令来配置 uboot,一般用来使能一些命令还是很方便的,这样就不需要到处找命令的配置宏是什么,然后在到配置文件里面去定义。

二、图形配置界面原理

上面的图形界面怎么实现的?又是怎么实现配置的?接下来就来看一下这个make menuconfig的配置原理。

1. %config目标

我们可以在顶层 Makefile 找到 %config 这个配置项:

1
2
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@

其中:

1
2
3
Q=@       # 或为空
MAKE=make
@=%config # make menuconfig的话,这里 @ 表示的就是menuconfig

build定义在Kbuild.include - scripts/Kbuild.include 中,定义如下:

1
2
3
4
5
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj

这里的 srctree 的值就是.,所以这里的build其实就是:

1
build := -f ./scripts/Makefile.build obj

这个%config在前面分析顶层 Makefile 时有分析过,可以看这个笔记《LV05-02-U-Boot-06-01-顶层Makefile基础解析.md》——make xxx_defconfig这个过程的分析。

所以,这里简单展开的话就是:

1
2
%config: scripts_basic outputmakefile FORCE
@make -f ./scripts/Makefile.build obj=scripts/kconfig $@

这里其实就是会调用 Makefile.build - scripts/Makefile.build 这个makefile文件去完成对应的目标创建,在调用 Makefile.build - scripts/Makefile.build 的时候还会传入参数,至少会有 obj 这个变量。

2. %config的依赖分析

2.1 FORCE

在顶层 MakefileFORCE定义如下:

1
2
3
4
5
6
PHONY += FORCE
FORCE:

# Declare the contents of the .PHONY variable as phony. We keep that
# information in a variable so we can use it in if_changed and friends.
.PHONY: $(PHONY)

可以看出 FORCE 是没有规则和依赖的,所以每次都会重新生成 FORCE。当 FORCE 作为 其他目标的依赖时,由于 FORCE 总是被更新过的,因此依赖所在的规则总是会执行的。

2.2 scripts_basic

2.2.1 依赖规则分析

在顶层 Makefilescripts_basic 定义如下:

1
2
3
4
5
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount

这是scripts_basic 的规则,其对应的命令用到了变量 Q、MAKE 和 build。这里展开就是:

1
2
3
4
5
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
@make -f $(srctree)/scripts/Makefile.build obj=scripts/basic # 也可以没有@,视配置而定
@rm -f .tmp_quiet_recordmcount

可以看到这里也是调用了 Makefile.build - scripts/Makefile.build ,并且 obj=scripts/basic

2.2.2 Makefile.build

接下来我们就来看一下Makefile.build 脚本里面都有什么。前面分析到scripts_basic展开后如下:

1
@make -f ./scripts/Makefile.build obj=scripts/basic

接下来就来看一下这个命令会怎么执行,最终会得到什么。

2.2.2.1 src与prefix变量

先看一下Makefile.build - scripts/Makefile.build最开始的两个变量:

1
2
3
4
5
6
7
8
9
10
# Modified for U-Boot
prefix := tpl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := spl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := .
endif
endif

第 2 行,变量 prefix 值为 tpl。

第 3 行定义了变量 src,这里用到了函数 patsubst,此行代码展开后为:

1
$(patsubst tpl/%,%, scripts/basic)

patsubst 是替换函数,格式如下:

1
$(patsubst <pattern>,<replacement>,<text>)

此函数用于在 text 中查找符合 pattern 的部分,如果匹配的话就用 replacement 替换掉。 pattern 是可以包含通配符“%”,如果 replacement 中也包含通配符“%”,那么 replacement 中的 这个“%”将是 pattern 中的那个“%”所代表的字符串。函数的返回值为替换后的字符串。因 此,第 3 行就是在“scripts/basic”中查找符合“tpl/%”的部分,然后将“tpl/”取消掉,但是 “scripts/basic”没有“tpl/”,所以 src= scripts/basic。

第 4 行判断变量 obj 和 src 是否相等,相等的话条件成立,很明显,此处条件成立。

第 6 行和第 3 行一样,只是这里处理的是“spl”,“scripts/basic”里面也没有“spl/”,所以 src 继续为 scripts/basic。

第 8 行因为变量 obj 和 src 相等,所以 prefix=. 。

2.2.2.2 kbuild-dir与kbuild-file

我们继续往下看Makefile.build - scripts/Makefile.build,这里有两个变量:

1
2
3
4
# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)

我们前面分析过srctree=.,src=scripts/basic, 将 kbuild-dir 展开后为:

1
kbuild-dir := $(if $(filter /%, scripts/basic), scripts/basic, ./scripts/basic)

因为没有以“/”为开头的单词,所以$(filter /%, scripts/basic)的结果为空,kbuild-dir=./scripts/basic。得到这个变量后,我们将 kbuild-file 展开后为:

1
kbuild-file := $(if $(wildcard ./scripts/basic/Kbuild), ./scripts/basic/Kbuild, ./scripts/basic/Makefile)

因为 scrpts/basic 目录中没有 Kbuild 这个文件,所以 kbuild-file = ./scripts/basic/Makefile。最后将第 4 行展开,即:

1
include ./scripts/basic/Makefile

也就是读取 Makefile - scripts/basic/Makefile 文件。

2.2.2.3 hostprogs-y

接下来看这一行 Makefile.build - scripts/Makefile.build - hostprogs-y ,这里也会包含一个Makefile文件:

1
2
3
4
# Do not include host rules unless needed
ifneq ($(hostprogs-y)$(hostprogs-m),)
include scripts/Makefile.host
endif

我们可以看一下这个 Makefile.host - scripts/Makefile.host 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# SPDX-License-Identifier: GPL-2.0
# ==========================================================================
# Building binaries on the host system
# Binaries are used during the compilation of the kernel, for example
# to preprocess a data file.
#
# Both C and C++ are supported, but preferred language is C for such utilities.
#
# Sample syntax (see Documentation/kbuild/makefiles.txt for reference)
# hostprogs-y := bin2hex
# Will compile bin2hex.c and create an executable named bin2hex
#
# hostprogs-y := lxdialog
# lxdialog-objs := checklist.o lxdialog.o
# Will compile lxdialog.c and checklist.c, and then link the executable
# lxdialog, based on checklist.o and lxdialog.o
#
# hostprogs-y := qconf
# qconf-cxxobjs := qconf.o
# qconf-objs := menu.o
# Will compile qconf as a C++ program, and menu as a C program.
# They are linked as C++ code to the executable qconf

其实这里主要就是把用到的工具的源码在主机上编译出对应的工具,例如mconf、qconf这些。

2.2.2.4 __build

我们继续向下看Makefile.build - scripts/Makefile.build,会看到这个__build目标:

1
2
3
4
5
6
# We keep a list of all modules in $(MODVERDIR)

__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:

__build 是默认目标,因为下面这条命令

1
@make -f ./scripts/Makefile.build obj=scripts/basic

没有指定目标,所以会使用到默认目标:__build。在顶层 Makefile 中,KBUILD_BUILTIN 为 1,KBUILD_MODULES 为 0,因此展开后目标__build 为:

1
2
__build:$(builtin-target) $(lib-target) $(extra-y) $(subdir-ym) $(always)
@:

可以看出目标__build 有 5 个依赖:builtin-target、lib-target、extra-y、subdir-ym 和 always。 这 5 个依赖的具体内容我们就不通过源码来分析了,直接在 Makefile.build - scripts/Makefile.build 中输入以下代码:

1
2
3
4
5
6
7
8
9
10
11
# We keep a list of all modules in $(MODVERDIR)

__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:
@echo "builtin-target="$(builtin-target)
@echo "lib-target="$(lib-target)
@echo "extra-y="$(extra-y)
@echo "subdir-ym="$(subdir-ym)
@echo "always="$(always)

然后我重新执行以下命令:

1
2
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig

会看到有如下打印信息出现:

1
2
3
4
5
builtin-target=
lib-target=
extra-y=
subdir-ym=
always=scripts/basic/fixdep

可以看出,只有 always 有效,因此__build 最终为:

1
2
__build:scripts/basic/fixdep
@:

__build 依赖于 scripts/basic/fixdep,所以要先编译 fixdep.c - scripts/basic/fixdep.c,生成 fixdep,前面已经读取了 Makefile - scripts/basic/Makefile 文件。

2.2.2.5 总结

需要注意的是,在 Makefile.build - scripts/Makefile.build 文件中将会包含两个Makefile,其中 Makefile.host - scripts/Makefile.host 是固定的,会编译出所需要的工具,还有一个受到参数的影响,不同的功能对应不同的Makefile,主要是根据 Makefile.build - scripts/Makefile.build - kbuild-file 来确定包含哪个Makefile。

2.2.3 总结

总的来说scripts_basic 目标的作用就是编译出 scripts/basic/fixdep 这个软件。我们可以执行以下命令看一下打印信息:

1
2
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- disclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

然后就会看到以下内容:

1
2
3
make -f ./scripts/Makefile.build obj=scripts/basic
cc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -o scripts/basic/fixdep scripts/basic/fixdep.c
rm -f .tmp_quiet_recordmcount

可以看到与上面的分析结果一致。这个应用是用来生成头文件依赖的,在编译过程中,使用-MD选项可以生成依赖关系文件。这些文件包含了源代码文件和它们之间的依赖关系,通常以 Makefile 的规则格式保存,这样可以在后续的编译过程中更好地管理文件的依赖关系。这在大型项目中是很有用的,因为它可以确保在修改一个源文件后,只重新编译与之相关的文件,而不是整个项目。

下面是一个简单的例子,假设有一个C语言项目,包含三个源文件:main.c,utils.c,和header.h。main.c 包含了main函数,utils.c包含了一些工具函数,而header.h包含了函数的声明。

假设我们使用GCC编译器,可以通过以下命令使用-MD选项生成依赖关系文件:

1
gcc -MD -o main main.c utils.c

这个命令会生成 main.d文件,内容可能如下:

1
2
main.o: main.c header.h
utils.o: utils.c header.h

这表示在编译main.c时,依赖于main.c和header.h;在编译utils.c时,依赖于utils.c和header.h。这样,如果修改了header.h,只有依赖于它的文件会重新编译,而不是整个项目。

这对于Makefile来说非常有用,因为它们可以根据这些依赖关系文件来判断哪些文件需要重新编译,从而提高编译效率。
fixdep是一个用于生成Makefile依赖关系的工具。它主要用于处理头文件之间的依赖关系,以确保在构建U-Boot时,当某个头文件发生变化时,只重新编译与之相关的文件,而不是整个项目。具体来说,fixdep主要完成以下任务:

(1)解析源代码文件中的#include指令:fixdep分析源代码文件,找到其中包含的头文件。

(2)生成Makefile规则: 通过分析源代码文件中的 #include 指令,fixdep 生成一个包含依赖关系的 Makefile 规则。这些规则通常包含了源文件和其所依赖的头文件,以及它们之间的关系。

(3)输出依赖关系: fixdep将生成的 Makefile 规则输出到标准输出或指定的文件中,以便后续的构建工具(如Make)使用。

例如,在U-Boot的Makefile中可能包含类似如下的使用fixdep的命令:

1
2
depend:
$(SRCTREE)/scripts/fixdep $(CFLAGS) $(CPPFLAGS) $(SRCARCH) $(SRC) > .depend

这个命令使用fixdep生成头文件依赖关系,并将结果保存到.depend文件中。在Makefile的其他地方可以包含这个文件,以便Make工具能够使用这些依赖关系。

2.3 outputmakefile

再来看一下这个 outputmakefile 依赖:

1
2
3
4
5
6
7
8
9
10
PHONY += outputmakefile
# outputmakefile generates a Makefile in the output directory, if using a
# separate output directory. This allows convenient use of make in the
# output directory.
outputmakefile:
ifneq ($(KBUILD_SRC),)
$(Q)ln -fsn $(srctree) source
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif

这个没有详细分析,可以加个打印看一眼:

1
2
3
4
5
6
7
outputmakefile:
echo "outputmakefile KBUILD_SRC=$(KBUILD_SRC)"
ifneq ($(KBUILD_SRC),)
$(Q)ln -fsn $(srctree) source
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif

然后执行:

1
2
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- disclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

会发现 KBUILD_SRC 值为空:

image-20250209102416540

所以下面的几个命令其实是没有执行的,这里就先不管了。

3. %config分析

上面已经分析完依赖了,现在再来分析这个 %config 中的命令。

1
2
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@

这里我们可以展开一下:

1
2
%config: scripts_basic outputmakefile FORCE
@make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig menuconfig

可以看到,还是要继续分析 Makefile.build - scripts/Makefile.build 。按照前面的分析,相关的变量值如下所示:

1
2
3
4
src= scripts/kconfig
kbuild-dir = ./scripts/kconfig
kbuild-file = ./scripts/kconfig/Makefile
include ./scripts/kconfig/Makefile

可以看出,在这个命令下,Makefilke.build 会读取 Makefile - scripts/kconfig/Makefile 中的内容,此文件中有如下所示内容:

1
2
menuconfig: $(obj)/mconf
$< $(silent) $(Kconfig)

其中$<表示依赖文件中的第一个文件。也就是 $(obj)/mconf,所以其实就是 scripts/kconfig/mconf文件,silent就是一个静默输出的控制标志,这里先不管。Kconfig=Kconfig(Makefile - scripts/kconfig/Makefile · Kconfig),所以这里可以展开:

1
2
menuconfig: scripts/kconfig/mconf
scripts/kconfig/mconf Kconfig

3.1 $(obj)/mconf

先来看这个 $(obj)/mconf ,我们知道 obj=scripts/kconfig,所以这里就是 scripts/kconfig/mconf,说实话这里我还真没理解,但是看打印信息的话我们可以自己猜测出来这个工具的编译过程,下面有一个自己写的编译实例。uboot的编译系统还是有些复杂,里面其实有很多可以借鉴的地方,感觉目前没必要深挖,越挖东西越多,先这样,后面有空再深入了解。

3.2 总结

编译出mconf后,就可以使用这个可执行程序来读取Kconfig文件了:

1
scripts/kconfig/mconf Kconfig 

然后就会调用对应的图形库,渲染出一个简单的配置界面。

4. 自定义带Kconfig的工程

LV41_kconfig/01_kconfig_demo · 苏木/imx6ull-app-demo - 码云 - 开源中国

4.1. mconf/conf的编译

可以看这个:LV41_kconfig/01_kconfig_demo/scripts/kconfig/Kconfig.mk · 苏木/imx6ull-app-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
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
TARGET_mconf    ?= mconf
TARGET_conf ?= conf

CROSS_COMPILE ?=
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump

HOSTCFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11
COBJ_FLAGS := $(HOSTCFLAGS)

OBJ_DIR ?= ./
SRC_DIR ?= ./
# 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)

# lxdialog stuff
check-lxdialog := ./lxdialog/check-lxdialog.sh

# Use recursively expanded variables so we do not call gcc unless
# we really need to do so. (Do not call gcc as part of make mrproper)
HOST_EXTRACFLAGS += $(shell $(CONFIG_SHELL) $(check-lxdialog) -ccflags) \
-DLOCALE
# Add environment specific flags
HOST_EXTRACFLAGS += $(shell $(CONFIG_SHELL) ./check.sh $(CC) $(HOSTCFLAGS))

# generated files seem to need this to find local include files
HOSTCFLAGS_zconf.lex.o := -I$(SRC_DIR)
HOSTCFLAGS_zconf.tab.o := -I$(SRC_DIR)
HOSTLOADLIBES_mconf = $(shell $(CONFIG_SHELL) $(check-lxdialog) -ldflags $(CC))
HOSTLOADLIBES_conf =

COBJ_FLAGS += $(HOST_EXTRACFLAGS)

lxdialog := lxdialog/checklist.o lxdialog/util.o lxdialog/inputbox.o
lxdialog += lxdialog/textbox.o lxdialog/yesno.o lxdialog/menubox.o

conf-objs := conf.o zconf.tab.o
mconf-objs := mconf.o zconf.tab.o $(lxdialog)

$(TARGET_mconf): $(mconf-objs)
$(CC) -o $@ $(mconf-objs) $(HOSTLOADLIBES_mconf)

$(TARGET_conf): $(conf-objs)
$(CC) -o $@ $(conf-objs) $(HOSTLOADLIBES_conf)


zconf.tab.o: zconf.tab.c zconf.lex.c
$(CC) -Wp,-MD,.$@.d $(COBJ_FLAGS) $(HOSTCFLAGS_zconf.tab.o) -c -o $@ zconf.tab.c

zconf.tab.c:
bison -o $@ -t -l zconf.y

zconf.lex.c:
flex -o $@ -L zconf.l

$(lxdialog): lxdialog/%.o : lxdialog/%.c
$(CC) -Wp,-MD,$@.d $(COBJ_FLAGS) -c $< -o $@

$(TARGET_mconf).o:
$(CC) -Wp,-MD,.$@.d $(COBJ_FLAGS) -Iscripts/kconfig -c -o $@ mconf.c

$(TARGET_conf).o:
$(CC) -Wp,-MD,.$@.d $(COBJ_FLAGS) -Iscripts/kconfig -c -o $@ conf.c

menuconfig: $(TARGET_mconf)
./$(TARGET_mconf) Kconfig
@python menu_anno.py .config config.h 0
@rm -f .config.old .config.cmd

%_defconfig: $(TARGET_conf)
@if [ -e ./configs/$@ ];then \
./$(TARGET_conf) --defconfig=lxdialog/../configs/$@ Kconfig; \
else \
echo "$@ not exists"; \
fi
@python menu_anno.py .config config.h 0
@rm -f .config.old .config.cmd

.PHONY: clean clean_c clean_obj clean_d clean_config

clean: clean_c clean_obj clean_d clean_config

clean_c:
@rm -rvf zconf.lex.c
@rm -rvf zconf.tab.c

clean_obj:
@find . -name $(TARGET_mconf) -exec rm -rvf {} \;
@find . -name \*.o -exec rm -rvf {} \;

clean_d:
@find . -name \*.d -exec rm -rvf {} \;

clean_config:
@rm -rvf .config .config.old config.h

4.2 自动创建头文件

当我们可以正常生成.config文件的时候,可以通过这个文件生成一个config.h头文件,方便C语言工程调用,可以通过python来实现:

LV41_kconfig/01_kconfig_demo/scripts/kconfig/config_head_create.py · 苏木/imx6ull-app-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
50
51
52
53
54
55
56
57
58
#!/usr/bin/env python3

import os
import sys

def write_config(config_filename, filename, chipname):
config_h = open(filename, 'w')
config_h.write('#ifndef __CONFIG_H__\n')
config_h.write('#define __CONFIG_H__\n\n')

config = open(config_filename, 'r')

empty_line = 1

for line in config:
line = line.lstrip(' ').replace('\n', '').replace('\r', '')

if len(line) == 0:
continue

if line[0] == '#':
if len(line) == 1:
if empty_line:
continue

config_h.write('\n')
empty_line = 1
continue

line = line[2:]

config_h.write('/*%s */\n' % line)
empty_line = 0

else:
empty_line = 0
setting = line.split('=')
if len(setting) >= 2:
if setting[1] == 'y':
config_h.write('#define %s\n' % setting[0])
else:
config_h.write('#define %s %s\n' % (setting[0], setting[1]))

config_h.write('\n')
config_h.write('#endif\n')
config_h.close()

if __name__ == '__main__':

if len(sys.argv) < 2:
sys.exit(1)

config_filename = sys.argv[1]
filename = sys.argv[2]
chipname = sys.argv[3]

write_config(config_filename, filename, chipname)

修改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
TARGET          ?= mconf.out

CROSS_COMPILE ?=
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump

HOSTCFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11
COBJ_FLAGS := $(HOSTCFLAGS)

OBJ_DIR ?= ./

# 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)

# lxdialog stuff
check-lxdialog := ./lxdialog/check-lxdialog.sh

# Use recursively expanded variables so we do not call gcc unless
# we really need to do so. (Do not call gcc as part of make mrproper)
HOST_EXTRACFLAGS += $(shell $(CONFIG_SHELL) $(check-lxdialog) -ccflags) \
-DLOCALE
# Add environment specific flags
HOST_EXTRACFLAGS += $(shell $(CONFIG_SHELL) ./check.sh $(CC) $(HOSTCFLAGS))

HOSTLOADLIBES_mconf = $(shell $(CONFIG_SHELL) $(check-lxdialog) -ldflags $(CC))

COBJ_FLAGS += $(HOST_EXTRACFLAGS)

lxdialog := lxdialog/checklist.o lxdialog/util.o lxdialog/inputbox.o
lxdialog += lxdialog/textbox.o lxdialog/yesno.o lxdialog/menubox.o
mconf-objs := mconf.o zconf.tab.o $(lxdialog)

$(TARGET): $(mconf-objs)
$(CC) -o $@ $(mconf-objs) $(HOSTLOADLIBES_mconf)

zconf.tab.o: zconf.tab.c zconf.lex.c
$(CC) -Wp,-MD,.$@.d $(COBJ_FLAGS) -Iscripts/kconfig -c -o zconf.tab.o zconf.tab.c

zconf.tab.c:
bison -o zconf.tab.c -t -l zconf.y

zconf.lex.c:
flex -o zconf.lex.c -L zconf.l

$(lxdialog): lxdialog/%.o : lxdialog/%.c
$(CC) -Wp,-MD,$@.d $(COBJ_FLAGS) -c $< -o $@

mconf.o:
$(CC) -Wp,-MD,.$@.d $(COBJ_FLAGS) -Iscripts/kconfig -c -o mconf.o mconf.c

menuconfig: $(TARGET)
./$(TARGET) Kconfig
@python menu_anno.py .config config.h 0
@rm -f .config.old .config.cmd

.PHONY: clean clean_c clean_obj clean_d clean_config

clean: clean_c clean_obj clean_d clean_config

clean_c:
@rm -rvf zconf.lex.c
@rm -rvf zconf.tab.c

clean_obj:
@find . -name $(TARGET) -exec rm -rvf {} \;
@find . -name \*.o -exec rm -rvf {} \;

clean_d:
@find . -name \*.d -exec rm -rvf {} \;

clean_config:
@rm -rvf .config .config.old config.h

4.3 功能完善

其实直接把uboot中的scripts目录拷贝出来,写好对应的顶层Makefile,是可以实现menuconfig的,我这里拷贝了一份,并且重新编写了Makefile,只能实现make menuconfig,也许以后会用于app的编译:

LV41_kconfig/02_kconfig_uboot · 苏木/imx6ull-app-demo - 码云 - 开源中国

这个的话就可以和其他的工程结合到一起,方便扩展。

三、Kconfig基本语法

这里是借用了之前的笔记,用的是2016版本的uboot,这里只是学习语法,版本什么的都无所谓。

1. Kconfig 语法简介

我们已经知道了 scripts/kconfig/mconf 会调用 uboot 根目录下的 Kconfig 文件开始构建图形化配置界面,接下来简单学习一下 Kconfig 的语法。因为后面学习 Linux 驱动开发的时候可能会涉及到修改 Kconfig,对于 Kconfig 语法我们不需要太深入的去研究,关于 Kconfig的详细语法介绍,可以参考 linux 内核源码(不知为何 uboot 源码中没有这个文件)中的文件Documentation/kbuild/kconfig-language.txt,本节我们大概了解其原理即可。打开 uboot 根目录下的 Kconfig,这个 Kconfig 文件就是顶层 Kconfig,我们就以这个文件为例来简单学习一下 Kconfig语法。

1.1 mainmenu

mainmenu 就是主菜单,也就是输入“make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig”以后打开的默认界面,在顶层 Kconfig 中有如下代码:

1
mainmenu "U-Boot $UBOOTVERSION Configuration"

上述代码就是定义了一个名为“U-Boot $UBOOTVERSION Configuration”的主菜单,其中UBOOTVERSION=2016.03,因此主菜单名为“U-Boot 2016.03 Configuration”,如图所示:

image-20230805065326598

1.2 调用其他目录下的 Kconfig 文件

和 makefile 一样, Kconfig 也可以调用其他子目录中的 Kconfig 文件,调用方法如下:

1
source "xxx/Kconfig" 

其中 xxx 为具体的目录名,相对路径,以u-boot源码顶层目录为根目录。在顶层 Kconfig 中有如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
source "common/Kconfig"

source "cmd/Kconfig"

source "dts/Kconfig"

source "net/Kconfig"

source "drivers/Kconfig"

source "fs/Kconfig"

source "lib/Kconfig"

source "test/Kconfig"

可以看出,顶层 Kconfig 文件调用了很多其他子目录下的 Kcofig 文件,这些子目录下的 Kconfig 文件在主菜单中生成各自的菜单项。

1.3 menu/endmenu 条目

menu 用于生成菜单, endmenu 就是菜单结束标志,这两个一般是成对出现的。在顶层Kconfig 中有如下代码:

image-20230805070033763

有两个 menu/endmenu 代码块,这两个代码块就是两个子菜单,第 14 行的“menu “General setup””表示子菜单“General setup”。后边的的“menu “Boot images””表示子菜单“Boot images”。体现在主菜单界面中就如上图所示了。

在“ General setup”菜单上面还有 “ Architecture select (ARM architecture)”和“ ARM architecture”这两个子菜单,但是在顶层 Kconfig 中并没有看到这两个子菜单对应的 menu/endmenu 代码块,那这两个子菜单是怎么来的呢?这两个子菜单就是 arch/Kconfig 文件生成的。包括主界面中的“Boot timing”、“Console recording”等等这些子菜单,都是分别由顶层Kconfig 所调用的 common/Kconfig、 cmd/Kconfig 等这些子 Kconfig 文件来创建的。

image-20230805070731647

1.4 config 条目

顶层 Kconfig 中的“General setup”子菜单内容如下:

image-20230805070853149

可以看出,在 menu/endmenu 代码块中有大量的“config xxxx”的代码块,也就是 config 条目。 config 条目就是“General setup”菜单的具体配置项,如上图所示。“config LOCALVERSION”对应着第一个配置项,“config LOCALVERSION_AUTO”对应着第二个配置项,以此类推。我们以 “ config LOCALVERSION ” 和 “ config LOCALVERSION_AUTO”这两个为例来分析一下 config 配置项的语法:

image-20230805071051567

第 16 和 26 行,这两行都以 config 关键字开头,后面跟着 LOCALVERSION 和LOCALVERSION_AUTO,这两个就是配置项名字。假如我们使能了 LOCALVERSION_AUTO这个功能,那么就会下.config 文件中生成 CONFIG_LOCALVERSION_AUTO,这个在上一小节学习如何使能 dns 命令的时候了解过了。由此可知, .config 文件中的“CONFIG_xxx” (xxx 就是具体的配置项名字)就是 Kconfig 文件中 config 关键字后面的配置项名字加上“CONFIG_”前缀。

config 关键字下面的这几行是配置项属性,17~24 行是 LOCALVERSION 的属性。27~44 行是 LOCALVERSION_AUTO 的属性。属性里面描述了配置项的类型、输入提示、依赖关系、帮助信息和默认值等。

第 17 行的 string 是变量类型,也就是“CONFIG_ LOCALVERSION”的变量类型。可以为:bool、 tristate、 string、 hex 和 int,一共 5 种。最常用的是 bool、 tristate 和 string 这三种。string 后面的“Local version - append to U-Boot release”就是这个配置项在图形界面上的显示出来的标题。

点击查看三种类型

(1)bool 类型有两种值: y 和 n,当为 y 的时候表示使能这个配置项,当为 n 的时候就禁止这个配置项。如下图所示:

image-20230805071706114

(2)tristate 类型有三种值: y、 m 和 n,其中 y 和 n 的涵义与 bool 类型一样, m 表示将这个配置项编译为模块,这种的在linux内核中使用较多。

(3)string 为字符串类型,所以 LOCALVERSION 是个字符串变量,用来存储本地字符串,选中以后即可输入用户定义的本地版本号,如下图:

image-20230805071444814

第 18 行, help 表示帮助信息,告诉我们配置项的含义,当我们按下“h”或“?”弹出来的帮助界面就是 help 的内容。

第 27 行,说明“CONFIG_LOCALVERSION_AUTO”是个 bool 类型,可以通过按下 Y 或 N 键来使能或者禁止 CONFIG_LOCALVERSION_AUTO。

第 28 行,“default y”表示 CONFIG_LOCALVERSION_AUTO 的默认值就是 y,所以这一行默认会被选中。

1.5 depends on 和 select

打开 arch/Kconfig 文件,在里面有这如下代码:

image-20230805072312327

第 9 行,“depends on”说明“SYS_GENERIC_BOARD”项依赖于“HAVE_GENERIC_BOARD”,也就是说“HAVE_GENERIC_BOARD”被选中以后“ SYS_GENERIC_BOARD”才能被选中。

第 17~20 行,“select”表示方向依赖,当选中“ARC”以后, “HAVE_PRIVATE_LIBGCC”、 “HAVE_GENERIC_BOARD”、 “SYS_GENERIC_BOARD” 和 “SUPPORT_OF_CONTROL” 这四个也会被选中。

1.6 choice/endchoice

在 arch/Kconfig 文件中有如下代码:

image-20230805072532230

choice/endchoice 代码段定义了一组可选择项,将多个类似的配置项组合在一起,供用户单选或者多选。上图中的代码就是选择处理器架构,可以从 ARC、 ARM、 AVR32 等这些架构中选择,这里是单选。在 uboot 图形配置界面上选择“Architecture select”,进入以后如图

image-20230805072748488

可以在图中通过移动光标来选择所使用的 CPU 架构。第 12 行的 prompt 给出这个choice/endchoice 段的提示信息为“Architecture select”。

1.7 menuconfig

menuconfig 和 menu 很类似,但是 menuconfig 是个带选项的菜单,其一般用法为:

1
2
3
4
5
menuconfig MODULES
bool "菜单"
if MODULES
# ...
endif # MODULES

第 1 行,定义了一个可选的菜单 MODULES,只有选中了 MODULES 第 3~5 行 if 到 endif之间的内容才会显示。在顶层 Kconfig 中有如下代码:

image-20230805073012950

第 74~99 行使用 menuconfig 实现了一个菜单,路径如下:

1
2
General setup
[*] Configure standard U-Boot features (expert users) --->
image-20230805073137961

从图可以看到,前面有“[ ]”说明这个菜单是可选的,当选中这个菜单以后就可以进入到子选项中,也就是图示代码中的第 83~99 行所描述的菜单,如下所示:

image-20230805073302638

如果不选择“Configure standard U-Boot features (expert users)”,那么图示代码中的第 83~99 行所描述的菜单就不会显示出来,进去以后是空白的。

image-20230805073346439

1.8 comment

comment 用于注释,也就是在图形化界面中显示一行注释,打开文件 drivers/mtd/nand/Kconfig,有如下所示代码:

image-20230805073631961

右侧的路径为【→ Device Drivers → NAND Device Support】,可以看出,在配置项“Configure Arasan Nand”下面有一行注释,注释内容为“*** Generic NAND options ***”。

1.9 source

source 用于读取另一个 Kconfig,比如:

1
source "arch/Kconfig"

基本上常用的语法就是这些,因为 uboot 相比 Linux 内核要小很多,所以配置项也要少很多,所以使用 uboot 来学习 Kconfig。一般不会修改 uboot中的 Kconfig 文件,甚至都不会使用 uboot 的图形化界面配置工具,本小节学习 Kconfig 的目的主要还是为了 Linux 内核作准备。

2. 自定义一个Kconfig文件

这里就不写详细过程了,可以看代码仓库:LV41_kconfig/02_kconfig_uboot/Kconfig · 苏木/imx6ull-app-demo - 码云 - 开源中国。最终的效果过如下:

image-20230805190016466