You will have noticed that we tend to follow a one-kernel-module-per-directory rule of sorts. Yes, that definitely helps keep things organized. So, let's take our second kernel module, the ch4/printk_loglvl one. To build it, we just cd to its folder, type make, and (fingers crossed!) voilà, it's done. We have the printk_loglevel.ko kernel module object freshly generated (which we can then insmod(8)/rmmod(8)). But how exactly did it get built when we typed make? Ah, explaining this is the purpose of this section.
您会注意到,我们倾向于遵循一个内核模块一个目录的排序规则。是的,这绝对有助于使事情井然有序。那么,让我们来看第二个内核模块,ch4/printk_loglvl模块。为了构建它,我们只需cd到它的文件夹中,输入make,然后(祈祷!)瞧,一切都结束了。我们有printk_loglevel。新生成的ko内核模块对象(然后我们可以使用insmod(8)/rmmod(9))。但当我们输入make时,它到底是如何构建的呢?啊,解释这就是本节的目的。
As you will know, the make command will by default look for a file named Makefile in the current directory; if it exists, it will parse it and execute command sequences as specified within it. Here's our Makefile for the kernel module printk_loglevel project:
如您所知,make命令默认情况下将在当前目录中查找名为Makefile的文件;如果它存在,它将解析它并执行其中指定的命令序列。以下是一个典型Makefile的例子:
PWD := $(shell pwd)
obj-m += printk_loglvl.o
# Enable the pr_debug() as well (rm the comment from the line below)
#EXTRA_CFLAGS += -DDEBUG
#CFLAGS_printk_loglvl.o := -DDEBUG
all:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
install:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules_install
clean:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean
It should go without saying that the Unix Makefile syntax basically demands this:
不用说,Unix Makefile语法基本要求如下:
target: [dependent-source-file(s)]
rule(s)
The rule(s) instances are always prefixed with a [Tab] character, not white space.
rules(s)实例必须以[Tab]缩进开始,而不是空格。
Let's gather the basics regarding how this Makefile works. First off, a key point is this: the kernel's Kbuild system (which we've been mentioning and using since Chapter 2, Building the 5.x Linux Kernel from Source – Part 1), primarily uses two variable strings of software to build, chained up within the two obj-y and obj-m variables.
让我们收集有关此Makefile如何工作的基本信息。首先,一个关键点是:内核的Kbuild系统(自第2章“从源代码构建5.x Linux内核-第1部分”以来,我们一直在提到和使用该系统)主要使用两个变量串的软件进行构建,链接在两个obj-y和obj-m变量中。
The obj-y string has the concatenated list of all objects to build and merge into the final kernel image files - the uncompressed vmlinux and the compressed (bootable) [b]zImage images. Think about it – it makes sense: the y in obj-y stands for Yes. All kernel built-ins and Kconfig options that were set to Y during the kernel configuration process (or are Y by default) are chained together via this item, built, and ultimately woven into the final kernel image files by the Kbuild build system.
obj-y字符串包含要构建并合并到最终内核映像文件中的所有对象的串联列表-未压缩的vmlinux和压缩的(可引导的)[b]zImage映像。想想看,这很有道理:obj-y中的y代表Yes。在内核配置过程中设置为Y(或默认为Y)的所有内核内置项和Kconfig选项都通过该项链接在一起,并由Kbuild构建系统构建,最终编织到最终的内核映像文件中。
On the other hand, it's now easy to see that the obj-m string is a concatenated list of all kernel objects to build separately, as kernel modules! This is precisely why our Makefile has this all-important line:
另一方面,现在很容易看到obj-m字符串是要作为内核模块单独构建的所有内核对象的串联列表!这正是为什么我们的Makefile有这条至关重要的线:
obj-m += printk_loglvl.o
In effect, it tells the Kbuild system to include our code; more correctly, it tells it to implicitly compile the printk_loglvl.c source code into the printk_loglvl.o binary object, and then add this object to the obj-m list. Next, the default rule for make being the all rule, it is processed:
实际上,它告诉Kbuild系统包含我们的代码;更准确地说,它告诉kbuild编译printk_loglvl.c源码为printk_loglvl.o二进制文件,然后将此对象添加到obj-m列表。接下来,make的默认规则是all规则,它将被处理:
all:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
The processing of this single statement is quite involved; here's what transpires:
这条语句的处理相当复杂;下面是发生的事情:
- The -C option switch to make has the make process change directory (via the chdir(2) system call) to the directory name that follows -C. Thus, it changes directory to the kernel build folder (which, as we covered earlier, is the location of the 'limited' kernel source tree that got installed via the kernel-headers package).
- Once there, it parses in the content of the kernel's top-level Makefile – that is, the Makefile that resides there, in the root of this limited kernel source tree. This is a key point. This way, it's guaranteed that all kernel modules are tightly coupled to the kernel that they are being built against (more on this a bit later). This also guarantees that kernel modules are built with the exact same set of rules, that is, the compiler/linker configurations (the CFLAGS options, the compiler option switches, and so on), as the kernel image itself is. All this is required for binary compatibility.
- Next, you can see the initialization of the variable named M, and that the target specified is modules; hence, the make process now changes directory to that specified by the M variable, which you can see is set to $(PWD) – the very folder we started from (the present working directory; the PWD := $(shell pwd) in the Makefile initializes it to the correct value)!
- 要make的-C选项开关将make进程更改目录(通过chdir(2)系统调用)为-C后面的目录名。因此,它将目录更改为内核构建文件夹(如前所述,它是通过内核头包安装的“受限”内核源代码树的位置)。
- 在那里,它解析内核顶级Makefile的内容,也就是驻留在那里的Makefile,位于这个有限的内核源代码树的根目录中。这是一个关键点。通过这种方式,可以保证所有内核模块都与构建它们的内核紧密耦合(稍后将对此进行详细介绍)。这也保证了内核模块使用与内核映像本身完全相同的规则集构建,即编译器/链接器配置(CFLAGS选项、编译器选项开关等)。所有这些都是二进制兼容性所必需的。
- 接下来,您可以看到名为M的变量的初始化,并且指定的目标是模块;因此,make进程现在将目录更改为M变量指定的目录,您可以看到它被设置为$(PWD),即我们从中启动的文件夹(当前工作目录;Makefile中的PWD:=$(shell PWD)将其初始化为正确的值)!
So, interestingly, it's a recursive build: the build process, having (very importantly) parsed the kernel top-level Makefile, now switches back to the kernel module's directory and builds the module(s) therein.
因此,有趣的是,这是一个递归构建:构建过程(非常重要)解析了内核顶级Makefile,现在切换回内核模块的目录并在其中构建模块。
Did you notice that when a kernel module is built, a fair number of intermediate working files are generated as well? Among them are modules.order, .mod.c, .o, Module.symvers, .mod.o, ..o.cmd, ..ko.cmd, a folder called .tmp_versions/, and, of course, the kernel module binary object itself, .ko – the whole point of the build exercise. Getting rid of all these objects, including the kernel module object itself, is easy: just perform make clean. The clean rule cleans it all up. (We shall delve into the install target in the following chapter.)
您是否注意到,当构建内核模块时,也会生成大量中间工作文件?其中包括modules.order, .mod.c, .o, Module.symvers, .mod.o, ..o.cmd, ..ko.cmd,一个名为.tmpversions/的文件夹,当然还有内核模块二进制对象本身.ko,这是构建练习的全部要点。除去所有这些对象,包括内核模块对象本身,很容易:只需执行make clean。清洁规则可以清除一切。(我们将在下一章深入研究安装目标。)
You can look up what the modules.order and modules.builtin files (and other files) are meant for within the kernel documentation here: Documentation/kbuild/kbuild.rst.
您可以在documentation/kbuild/kbuilde.rst中查找modules.order和modules.builtin是什么。
Also as mentioned previously, we shall, in the following chapter, introduce and use a more sophisticated Makefile variant - a 'better' Makefile; it is designed to help you, the kernel module/driver developer, improve code quality by running targets related to kernel coding style checks, static analysis, simple packaging, and (a dummy target) for dynamic analysis.
同样如前所述,我们将在下一章中介绍和使用一个更复杂的Makefile变体——一个“更好”的Makefile;它旨在帮助内核模块/驱动程序开发人员,通过运行与内核编码风格检查、静态分析、简单打包和(虚拟目标)动态分析相关的目标,提高代码质量。
With that, we conclude this chapter. Well done – you are now well on your way to learning Linux kernel development!
至此,我们结束本章。干得好–您现在正在学习Linux内核开发!
本文翻译自
《Linux-Kernel-Programming-Kaiwan-N-Billimoria》