The Linux kernel configuration system consists of 3 parts: 1. Makefile, which is distributed in the Linux kernel source code root directory and each layer directory and defines the compilation rules of the Linux kernel; 2. Configuration file (config.in) , providing users with the ability to make configuration choices; 3. Configuration tools, including configuration command interpreters (which interpret configuration commands used in configuration scripts) and configuration user interfaces.
#The operating environment of this tutorial: linux7.3 system, Dell G3 computer.
The Linux kernel configuration system consists of three parts: Makefile, configuration file (config.in), and configuration tools.
Makefile: distributed in the Linux kernel source code root directory and each level directory, defining the compilation rules of the Linux kernel;
Configuration file (config.in): Provides users with the ability to make configuration choices;
Configuration tools: including configuration command interpreter (interprets configuration commands used in configuration scripts) and configuration User interface (providing user configuration interfaces based on character interface, Ncurses graphical interface and Xwindows graphical interface, respectively corresponding to Make config, Make menuconfig and make xconfig).
These configuration tools are written using scripting languages such as Tcl/TK and Perl (also including some code written in C). This article is not an analysis of the configuration system itself, but an introduction to how to use the configuration system. Therefore, unless they are maintainers of configuration systems, ordinary kernel developers do not need to understand their principles, they only need to know how to write Makefiles and configuration files. Therefore, in this article, we only discuss Makefiles and configuration files. In addition, when it comes to content related to specific CPU architectures, we will use ARM as an example. This will not only clarify the issues discussed, but also have no impact on the content itself.
Makefile overview
The function of Makefile is to construct a A list of source files that need to be compiled is then compiled separately and the target code is linked together to finally form a Linux kernel binary file.
Since the Linux kernel source code is organized according to a tree structure, Makefiles are also distributed in the directory tree. Makefiles in the Linux kernel and files directly related to Makefiles are:
Makefile: The top-level Makefile is the overall control file for the entire kernel configuration and compilation.
.config: Kernel configuration file, containing configuration options selected by the user, used to store the results of kernel configuration (such as make config).
arch/*/Makefile: Makefiles located in various CPU system directories, such as arch/arm/Makefile, are Makefiles for specific platforms.
Makefile in each subdirectory: such as drivers/Makefile, is responsible for the management of source code in the subdirectory.
Rules.make: Rules file, used by all Makefiles.
After the user configures through make config, .config is generated. The top-level Makefile reads the configuration choices in .config. The top-level Makefile has two main tasks: generating vmlinux files and kernel modules. In order to achieve this goal, the top-level Makefile recursively enters each subdirectory of the kernel and calls the Makefiles located in these subdirectories respectively. As for which subdirectories to enter, it depends on the kernel configuration. In the top-level Makefile, there is a sentence: include arch/$(ARCH)/Makefile, which contains the Makefile under a specific CPU architecture. This Makefile contains platform-related information.
The Makefile located in each subdirectory also constructs a list of source files required under the current configuration based on the configuration information given by .config, and includes include $(TOPDIR)/Rules.make at the end of the file. .
The Rules.make file plays a very important role, it defines compilation rules common to all Makefiles. For example, if you need to compile all C programs in this directory into assembly code, you need to have the following compilation rules in the Makefile:
%.s: %.c
There are many subdirectories. For the same requirement, you need to include this compilation rule in your respective Makefile, which will be more troublesome. In the Linux kernel, such compilation rules are unified into Rules.make, and included in Rules.make (include Rules.make) in their respective Makefiles, thus avoiding duplication of the same in multiple Makefiles. rule. For the above example, the corresponding rules in Rules.make are:
(CC) (CFLAGS) -S ##<−o @
%.s: %.c
(CC) (CFLAGS) (EXTRACFLAGS) (CFLAGS_ (∗F)) (CFLAGS_ @)−S < -o $@
Makefile中的变量
顶层 Makefile 定义并向环境中输出了许多变量,为各个子目录下的 Makefile 传递一些信息。有些变量,比如 SUBDIRS,不仅在顶层 Makefile 中定义并且赋初值,而且在 arch/*/Makefile 还作了扩充。
常用的变量有以下几类:
1) 版本信息
版本信息有:VERSION,PATCHLEVEL, SUBLEVEL, EXTRAVERSION,KERNELRELEASE。 版本信息定义了当前内核的版本,比如 VERSION=2,PATCHLEVEL=4,SUBLEVEL=18,EXATAVERSION=-rmk7,它们共同构成内核的发行版本KERNELRELEASE:2.4.18-rmk7
2) CPU 体系结构:ARCH
在顶层 Makefile 的开头,用 ARCH 定义目标 CPU 的体系结构,比如 ARCH:=arm 等。许多子目录的 Makefile 中,要根据 ARCH 的定义选择编译源文件的列表。
3) 路径信息:TOPDIR, SUBDIRS
TOPDIR 定义了 Linux 内核源代码所在的根目录。例如,各个子目录下的 Makefile 通过 $(TOPDIR)/Rules.make 就可以找到 Rules.make 的位置。
SUBDIRS 定义了一个目录列表,在编译内核或模块时,顶层 Makefile 就是根据 SUBDIRS 来决定进入哪些子目录。SUBDIRS 的值取决于内核的配置,在顶层 Makefile 中 SUBDIRS 赋值为 kernel drivers mm fs net ipc lib;根据内核的配置情况,在 arch/*/Makefile 中扩充了 SUBDIRS 的值,参见4)中的例子。
4) 内核组成信息:HEAD, CORE_FILES, NETWORKS, DRIVERS, LIBS
Linux 内核文件 vmlinux 是由以下规则产生的:
vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o \ --start-group \ $(CORE_FILES) \ $(DRIVERS) \ $(NETWORKS) \ $(LIBS) \ --end-group \ -o vmlinux
可以看出,vmlinux 是由 HEAD、main.o、version.o、CORE_FILES、DRIVERS、NETWORKS 和 LIBS 组成的。这些变量(如 HEAD)都是用来定义连接生成 vmlinux 的目标文件和库文件列表。其中,HEAD在arch/*/Makefile 中定义,用来确定被最先链接进 vmlinux 的文件列表。比如,对于 ARM 系列的 CPU,HEAD 定义为:
HEAD := arch/arm/kernel/head-$(PROCESSOR).o \ arch/arm/kernel/init_task.o
表明 head-$(PROCESSOR).o 和 init_task.o 需要最先被链接到 vmlinux 中。PROCESSOR 为 armv 或 armo,取决于目标 CPU。 CORE_FILES,NETWORK,DRIVERS 和 LIBS 在顶层 Makefile 中定义,并且由 arch/*/Makefile 根据需要进行扩充。 CORE_FILES 对应着内核的核心文件,有 kernel/kernel.o,mm/mm.o,fs/fs.o,ipc/ipc.o,可以看出,这些是组成内核最为重要的文件。同时,arch/arm/Makefile 对 CORE_FILES 进行了扩充:
# arch/arm/Makefile # If we have a machine-specific directory, then include it in the build. MACHDIR := arch/arm/mach-$(MACHINE) ifeq ($(MACHDIR),$(wildcard $(MACHDIR))) SUBDIRS += $(MACHDIR) CORE_FILES := $(MACHDIR)/$(MACHINE).o $(CORE_FILES) endif HEAD := arch/arm/kernel/head-$(PROCESSOR).o \ arch/arm/kernel/init_task.o SUBDIRS += arch/arm/kernel arch/arm/mm arch/arm/lib arch/arm/nwfpe CORE_FILES := arch/arm/kernel/kernel.o arch/arm/mm/mm.o $(CORE_FILES) LIBS := arch/arm/lib/lib.a $(LIBS)
5) 编译信息:CPP, CC, AS, LD, AR,CFLAGS,LINKFLAGS
在 Rules.make 中定义的是编译的通用规则,具体到特定的场合,需要明确给出编译环境,编译环境就是在以上的变量中定义的。针对交叉编译的要求,定义了 CROSS_COMPILE。比如:
CROSS_COMPILE = arm-linux- CC = $(CROSS_COMPILE)gcc LD = $(CROSS_COMPILE)ld
CROSS_COMPILE 定义了交叉编译器前缀 arm-linux-,表明所有的交叉编译工具都是以 arm-linux- 开头的,所以在各个交叉编译器工具之前,都加入了 $(CROSS_COMPILE),以组成一个完整的交叉编译工具文件名,比如 arm-linux-gcc。
CFLAGS 定义了传递给 C 编译器的参数。
LINKFLAGS 是链接生成 vmlinux 时,由链接器使用的参数。LINKFLAGS 在 arm/*/Makefile 中定义,比如:
# arch/arm/Makefile LINKFLAGS :=-p -X -T arch/arm/vmlinux.lds
Rules.make变量
前面讲过,Rules.make 是编译规则文件,所有的 Makefile 中都会包括 Rules.make。Rules.make 文件定义了许多变量,最为重要是那些编译、链接列表变量。
O_OBJS,L_OBJS,OX_OBJS,LX_OBJS:本目录下需要编译进 Linux 内核 vmlinux 的目标文件列表,其中 OX_OBJS 和 LX_OBJS 中的 “X” 表明目标文件使用了 EXPORT_SYMBOL 输出符号。
M_OBJS,MX_OBJS:本目录下需要被编译成可装载模块的目标文件列表。同样,MX_OBJS 中的 “X” 表明目标文件使用了 EXPORT_SYMBOL 输出符号。
O_TARGET,L_TARGET:每个子目录下都有一个 O_TARGET 或 L_TARGET,Rules.make 首先从源代码编译生成 O_OBJS 和 OX_OBJS 中所有的目标文件,然后使用 $(LD) -r 把它们链接成一个 O_TARGET 或 L_TARGET。O_TARGET 以 .o 结尾,而 L_TARGET 以 .a 结尾。
子目录Makefile
目录 Makefile 用来控制本级目录以下源代码的编译规则。我们通过一个例子来讲解子目录 Makefile 的组成:
# Makefile for the linux kernel. # # All of the (potential) objects that export symbols. # This list comes from 'grep -l EXPORT_SYMBOL *.[hc]'. export-objs := tc.o # Object file lists. obj-y := obj-m := obj-n := obj- := obj-$(CONFIG_TC) += tc.o obj-$(CONFIG_ZS) += zs.o obj-$(CONFIG_VT) += lk201.o lk201-map.o lk201-remap.o # Files that are both resident and modular: remove from modular. obj-m := $(filter-out $(obj-y), $(obj-m)) # Translate to Rules.make lists. L_TARGET := tc.a L_OBJS := $(sort $(filter-out $(export-objs), $(obj-y))) LX_OBJS := $(sort $(filter $(export-objs), $(obj-y))) M_OBJS := $(sort $(filter-out $(export-objs), $(obj-m))) MX_OBJS := $(sort $(filter $(export-objs), $(obj-m))) include $(TOPDIR)/Rules.make
a) 注释
对 Makefile 的说明和解释,由#开始。
b) 编译目标定义
类似于 obj-(CONFIGTC)+=tc.o的语句是用来定义编译的目标,是子目录Makefile中最重要的部分。编译目标定义那些在本子目录下,需要编译到Linux内核中的目标文件列表。为了只在用户选择了此功能后才编译,所有的目标定义都融合了对配置变量的判断。前面说过,每个配置变量取值范围是:y,n,m和空,obj−(CONFIG_TC) 分别对应着 obj-y,obj-n,obj-m,obj-。如果 CONFIG_TC 配置为 y,那么 tc.o 就进入了 obj-y 列表。obj-y 为包含到 Linux 内核 vmlinux 中的目标文件列表;obj-m 为编译成模块的目标文件列表;obj-n 和 obj- 中的文件列表被忽略。配置系统就根据这些列表的属性进行编译和链接。
export-objs 中的目标文件都使用了 EXPORT_SYMBOL() 定义了公共的符号,以便可装载模块使用。在 tc.c 文件的最后部分,有 “EXPORT_SYMBOL(search_tc_card);”,表明 tc.o 有符号输出。
这里需要指出的是,对于编译目标的定义,存在着两种格式,分别是老式定义和新式定义。老式定义就是前面 Rules.make 使用的那些变量,新式定义就是 obj-y,obj-m,obj-n 和 obj-。Linux 内核推荐使用新式定义,不过由于 Rules.make 不理解新式定义,需要在 Makefile 中的适配段将其转换成老式定义。
c) 适配段
适配段的作用是将新式定义转换成老式定义。在上面的例子中,适配段就是将 obj-y 和 obj-m 转换成 Rules.make 能够理解的 L_TARGET,L_OBJS,LX_OBJS,M_OBJS,MX_OBJS。
L_OBJS := (sort(filter-out (export−objs),(obj-y))) 定义了 L_OBJS 的生成方式:在 obj-y 的列表中过滤掉 export-objs(tc.o),然后排序并去除重复的文件名。这里使用到了 GNU Make 的一些特殊功能,具体的含义可参考 Make 的文档(info make)。
d) include $(TOPDIR)/Rules.make
配置文件功能概述
除了 Makefile 的编写,另外一个重要的工作就是把新功能加入到 Linux 的配置选项中,提供此项功能的说明,让用户有机会选择此项功能。所有的这些都需要在 config.in 文件中用配置语言来编写配置脚本,
在 Linux 内核中,配置命令有多种方式:
配置命令 | 解释脚本 |
---|---|
Make Config,make oldconfig | scripts/Configure |
Make menuconfig | scripts/Menuconfig |
Make xconfig | scripts/tkparse |
以字符界面配置(make config)为例,顶层 Makefile 调用 scripts/Configure, 按照 arch/arm/config.in 来进行配置。命令执行完后产生文件 .config,其中保存着配置信息。下一次再做 make config 将产生新的 .config 文件,原 .config 被改名为 .config.old
对于一个开发者来说,将自己开发的内核代码加入到 Linux 内核中,需要有三个步骤。首先确定把自己开发代码放入到内核的位置;其次,把自己开发的功能增加到 Linux 内核的配置选项中,使用户能够选择此功能;最后,构建子目录 Makefile,根据用户的选择,将相应的代码编译到最终生成的 Linux 内核中去。下面,我们就通过一个简单的例子–test driver,结合前面学到的知识,来说明如何向 Linux 内核中增加新的功能。
目录结构
test driver 放置在 drivers/test/ 目录下:
cddrivers/testtree
.
|– Config.in
|– Makefile
|– cpu
| |– Makefile
| -- cpu.c <br/> |-- test.c <br/> |-- test_client.c <br/> |-- test_ioctl.c <br/> |-- test_proc.c <br/> |-- test_queue.c <br/>
– test
|– Makefile
配置文件
# TEST driver configuration # mainmenu_option next_comment comment 'TEST Driver' bool 'TEST support' CONFIG_TEST if [ "$CONFIG_TEST" = "y" ]; then tristate 'TEST user-space interface' CONFIG_TEST_USER bool 'TEST CPU ' CONFIG_TEST_CPU fi endmenu
由于 test driver 对于内核来说是新的功能,所以首先创建一个菜单 TEST Driver。然后,显示 “TEST support”,等待用户选择;接下来判断用户是否选择了 TEST Driver,如果是(CONFIG_TEST=y),则进一步显示子功能:用户接口与 CPU 功能支持;由于用户接口功能可以被编译成内核模块,所以这里的询问语句使用了 tristate(因为 tristate 的取值范围包括 y、n 和 m,m 就是对应着模块)。
2) arch/arm/config.in
在文件的最后加入:source drivers/test/Config.in,将 TEST Driver 子功能的配置纳入到 Linux 内核的配置中。
Makefile
1)drivers/test/Makefile
# drivers/test/Makefile # # Makefile for the TEST. # SUB_DIRS := MOD_SUB_DIRS := $(SUB_DIRS) ALL_SUB_DIRS := $(SUB_DIRS) cpu L_TARGET := test.a export-objs := test.o test_client.o obj-$(CONFIG_TEST) += test.o test_queue.o test_client.o obj-$(CONFIG_TEST_USER) += test_ioctl.o obj-$(CONFIG_PROC_FS) += test_proc.o subdir-$(CONFIG_TEST_CPU) += cpu include $(TOPDIR)/Rules.make clean: for dir in $(ALL_SUB_DIRS); do make -C $$dir clean; done rm -f *.[oa] .*.flags
drivers/test 目录下最终生成的目标文件是 test.a。在 test.c 和 test-client.c 中使用了 EXPORT_SYMBOL 输出符号,所以 test.o 和 test-client.o 位于 export-objs 列表中。然后,根据用户的选择(具体来说,就是配置变量的取值),构建各自对应的 obj-* 列表。由于 TEST Driver 中包一个子目录 cpu,当 CONFIG_TEST_CPU=y(即用户选择了此功能)时,需要将 cpu 目录加入到 subdir-y 列表中。
2)drivers/test/cpu/Makefile
# drivers/test/test/Makefile # # Makefile for the TEST CPU # SUB_DIRS := MOD_SUB_DIRS := $(SUB_DIRS) ALL_SUB_DIRS := $(SUB_DIRS) L_TARGET := test_cpu.a obj-$(CONFIG_test_CPU) += cpu.o include $(TOPDIR)/Rules.make clean: rm -f *.[oa] .*.flags
3)drivers/Makefile
…… subdir-$(CONFIG_TEST) += test …… include $(TOPDIR)/Rules.make
在 drivers/Makefile 中加入 subdir-$(CONFIG_TEST)+= test,使得在用户选择 TEST Driver 功能后,内核编译时能够进入 test 目录。
4)Makefile
…… DRIVERS-$(CONFIG_PLD) += drivers/pld/pld.o DRIVERS-$(CONFIG_TEST) += drivers/test/test.a DRIVERS-$(CONFIG_TEST_CPU) += drivers/test/cpu/test_cpu.a DRIVERS := $(DRIVERS-y) ……
在顶层 Makefile 中加入 DRIVERS-(CONFIGTEST)+=drivers/test/test.a和DRIVERS−(CONFIGTEST)+=drivers/test/test.a。如何用户选择了 TEST Driver,那么 CONFIG_TEST 和 CONFIG_TEST_CPU 都是 y,test.a 和 test_cpu.a 就都位于 DRIVERS-y 列表中,然后又被放置在 DRIVERS 列表中。在前面曾经提到过,Linux 内核文件 vmlinux 的组成中包括 DRIVERS,所以 test.a 和 test_cpu.a 最终可被链接到 vmlinux 中。
相关推荐:《Linux视频教程》
The above is the detailed content of The Linux kernel configuration system consists of several parts. For more information, please follow other related articles on the PHP Chinese website!