Kbuild: the Linux Kernel Build System

来源:互联网 发布:苏州餐饮软件 编辑:程序博客网 时间:2024/05/25 18:11

Kbuild:Linux内核构建系统


Linux使用一样的基础代码却可以被上至超级计算机下至小型嵌入式设备的不同设备使用着实让我们惊讶。静下来想想其实Linux应该是唯一一款操作系统使用统一的代码基础的,相比之下微软的Windows NT和Windows CE和Apple的OSX和IOS在桌面和移动设备上使用了不同的内核。其中的原因我想大概是Linux内核有许多的抽象层和间接层次结构,并且Linux提供了允许高度定制的二进制内核文件。

由于Linux的内核有庞大但统一的体系结构,全部的内核程序都会在内核空间中运行并享有相同的地址空间。因为这种特殊的系统结构,人们必须在内核编译前就确定好内核的功能。但是严格意义上来讲,Linux内核并非完全统一的内核,因为它仍然可以在运行时一定程度上扩展内核模块。为什么说是一定程度上呢?因为实际上在运行时扩展的新模块仍然需要调用已经编译好的内核模块的接口来实现。如果这些调用的接口没有一开始就写进内核中并被编译,那么实际上在加载新模块的时候就会出现找不到依赖的错误,所以后来加载的新模块只是内核功能的一种特殊实现方式。一旦内核模块被装载,那么这个模块就和原来编译好的内核共享一片相同的地址空间。由此看来,就算Linux支持添加新模块,我们仍需要在内核编译前就确定好内核所需要的大部分功能,这些功能将被写入内核镜像中用来在系统运行时加载新的内核模块。

因为上述原因,选择哪些代码在内核编译的时候被编译与否是非常重要的。为了实践这种思想,我们就需要可选择配置的编译,成吨的选项支持我们可以选择我们需要的功能,这些配置选项可以决定编译器在编译的时候是否将特定的C文件、代码段或者数据结构编译进内核镜像和内核模块中。

所以,需要一个简单高效的管理方法来配置这些选项。这个管理这些配置的基础程序就是Linux内核构建系统(kbuild)。

Kbuild的组成

  1. Config symbols:一些可以决定哪些代码或数据结构应该被包含到内核映像或模块中的选项。
  2. Kconfig文件:定义每个配置选项和它的属性,比如说它的类型、描述和依赖。构建选项菜单树的程序(比如说make menuconfig)会根据这些文件加载整个选项菜单。
  3. .config文件:存储每个配置选项的选择。你可以手工编辑这些文件或者使用配置程序(比如说menuconfigxonfig)更新这些文件。
  4. Makefiles:标准的GNUMakefile文件描述了各个源文件的关系和一些指向各个make目标的命令,比如说内核映像和模块。

各个组成部分的详细说明


Compilation Options: Configuration Symbols

配置选项用来决定那些功能需要被包含到Linux内核映像中。两个主要的选项标志是:boolean选项tristate选项。这两种选项的区别仅仅是选择个数的区别,其中boolean选项选项毫无疑问是只有truefalse两个选项,而tristate选项则有yesnomodule三个选项。

并不是所有的在内核里面的东西都可以被编译成一个模块。许多功能具有干扰性了,你需要在配置时决定是否让你的内核支持这些功能。比如说,你不能添加Symmetric Multi- Processing (SMP)或者Kernel preemption support到一个正在运行的内核中。所以这个时候我们就需要用到boolean选项来明确这些功能。但仍然有大部分的模块是可以在编译完后的内核中添加的,这就是tristate选项存在的原因———来决定你是将这些可以后期添加的东西现在就编译到内核中(y),作为模块编译在运行内核的时候加载(m)还是根本就不编译(n)

除了上述两种形式,配置选择标志还有一些其他的形式比如说strings选项hex选项。但因为这并不是用来条件编译的,所以我并不打算在这里多讲。如果需要了解,可以阅读Linux内核的官方文档。

Defining Configuration Symbols: Kconfig Files

定义配置选项的文件被称为Kconfig文件。每个Kconfig文件可以描述一系列配置,也可以包括一些其他的Kconfig文件的源码。构建选项菜单树的程序(比如说make menuconfig)会根据这些文件加载整个选项菜单树。

内核文件中的每一个目录都有一个Kconfig,里面包含了所有Kconfig文件和它的子目录。在最外层内核源代码目录,有一个Kconfig文件是整个树状结构的根。menuconfiggconfig和其他编译目标组合程序从这个根Kconfig文件开始搜索子目录来构建配置选项菜单树。选择哪个子目录去访问不仅决定于每个上层目录的Kconfig文件,还会根据配置过程中用户的选项。

Storing Symbol Values: .config File

存储每个配置选项的选择。每次你想要改变内核编译的配置选项都要使用配置程序(比如说menuconfigxonfig)更新这些文件。当然,这些工具不仅可以更新你的配置选项,还可以建新的.config文件如果文件不存在。

因为.confi文件是一个纯文本文件,所以你也可以不用特殊的程序,手动改变这些文件。这也不失为一种方便的保存配置选项的方法。

Compiling the Kernel: Makefiles

最后一个kbuild系统的组件就是Makefiles,它们用来编译内核映像和模块。就像Kconfig文件一样,内核源码包的每一层子目录都有一个Makefile用来编译这个文件夹下的文件。上一层Makefile会向下找各个子目录的Makefile的位置并且会编译它们。最后,这些被编译好的文件会成为Linux内核映像。

怎么将上述组件组合起来


想要在Linux内核中添加一个功能,需要做三件事:

  1. 将源代码文件放在相应目录下,比如说把WI-FI设备放在drivers/net/wireless目录下。
  2. 在你放置源代码的目录下为每一个子目录更新每一个Kconfig文件来让用户可以选择是否添加该功能。
  3. 在你放置源代码的目录下为每一个子目录更新Makefile文件,确保你的构建系统可以条件编译你的代码。

用Scull设备驱动程序作为范例


因为这个设备是一个字符型设备,所以把源代码放置到drivers/char目录下。

接着就是让用户可以选择是否编译这个设备驱动,为了完成这一步我们需要为设备添加一个Kconfig文件——drivers/char/Kconfig file

就像大部分的设备一样,scull设备可以编译到内核映像里、模块里或者根本就不编译。所以这个配置选项应该叫SCULL,是一个tristate选项,具有(y/n/m)三个选项。

Kconfig Entries for the SCULL Driver

# # Character device configuration#menu "Character devices"config SCULL    tristate "Coin char device support"    help    Say Y here if you want to add support for the coin char device.    If unsure, say N.    To compile this driver as a module, choose M here: the module will be called coin.

那么怎么使用最新添加的这个配置呢?

就像前面说的一样,构建配置选项菜单树的程序会用到这个配置选项,所以你可以选择让什么编译到你的内核里面。比如说,当我们运行命令make menuconfig时,命令行程序会开始读取所有Kconfig文件来构建基础菜单接口。接着你就可以更新我们对SCULL这个设备的配置选项了。将导航按照Drivers→Character devices就可以看到我们对于SCULL设备的选项了,再接着我们就可以根据我们的需要修改这个选项了。

一旦你完成了整个编译配置过程,退出菜单程序,如果配置过程中跟以前的配置发生了改变,那么程序会问你是否应用新的配置。这将会这些配置的选择到.config文件。对于每一个配置选项都会添加一个CONFIG_ prefix.config文件中。比如说,如果上述设备配置选项是boolean形式而且我们选择了yes的话,那么在相应的.config文件中就会添加对应条目CONFIG_SCULL=y。如果你并没有为这个配置选项选择,那么在.config文件里将会对应一条# CONFIG_SCULL is not set

tristate形式的选项和boolean形式的选项一样,都有yesno两种情况,也有没有选择的情况,当然别忘记了这种形式下还有一个选项module。如果上述设备使用的是这个形式的选项并且选择了module,那么在.config文件中就会添加一行CONFIG_SCULL=m

在这里我们可以让我们的设备驱动在编译内核的时候就编译到内核映像中或者选择在编译完内核之后在运行的时候再装载这个模块,对应的两种在.config文件中的指令就是CONFIG_SCULL=mCONFIG_SCULL=y

一旦你已经生成好了.config文件,就说明你已经准备好编译内核和内核模块。当你执行编译命令来编译内核以及模块的时候,计算机会先执行一个二进制程序来读取所有Kconfig文件.config文件$ scripts/kconfig/conf Kconfig

这个二进制程序更新(或者新建)了一个内有你对所有配置的选择的C语言头文件——include/generated/autoconf.h,并且所有GCC编译器都会包含这个头文件,所以这些配置选项对于所有内核文件的编译都生效。这个文件定义了数以千计用以描述配置选项的选择的#define宏定义。

下面我们来看看这些宏定义是什么样的。

boolean型选项的truetristate型选项的yes是等价的,拿SCULL设备做例子,对于这个选项,会生成三个宏定义如下:

#define __enabled_CONFIG_SCULL 1#define __enabled_CONFIG_SCULL_MODULE 0#define CONFIG_SCULL 1

同样的,对于boolean型选项的falsetristate型选项的no也是等价的,还是SCULL设备做例子,对于这个选项,会生成宏定义如下:

#define __enabled_CONFIG_SCULL 0#define __enabled_CONFIG_SCULL_MODULE 0

当然别忘了tristate型选项还有一个选择module,继续拿SCULL设备做例子,对于这个选项,会生成宏定义如下:

#define __enabled_CONFIG_SCULL 0#define __enabled_CONFIG_SCULL_MODULE 1#define     CONFIG_SCULL_MODULE 1#define IS_ENABLED(option) \    (__enabled_ ## option || __enabled_ ## option ## _MODULE)#define IS_BUILTIN(option) __enabled_ ## option#define IS_MODULE(option) __enabled_ ## option ## _MODULE

更新Makefiles。

最后一步就是为我们这个设备的子目录更新Makefiles
好让kbuild可以为我们编译这个设备的驱动。但是我们怎么让kbuild来条件编译我们的内核源文件呢?

内核编译系统Kbuild有两个主要的任务:创造二进制的内核映像和内核模块。为了做到这两个任务,kbuild将会分别生成一系列的两种文件:obj-yobj-m。前者是一系列和构建内核映像有关的对象,而后者则是一系列将会被编译成内核模块的对象。

.config文件aotuconf.h中的配置选项及其选择被用来根据GUN``make的语法完成以上一系列对象的创造。Kbuild将会递归的进入到各个文件夹Makefile文件中并构建列出的一系列对象。更多的信息关于make语法和对象列表的描述可以读取源码包中的Documentation/kbuild/makefiles.txt

对于我们这个SCULL设备,只需要添加一行obj-$(CONFIG_SCULL) += scull.odrivers/char/Makefile中去就可以了。这句话的意思就是告诉kbuild去根据scull.c源文件创造一个对象并把它加入到构建对象的列表里面去。因为CONFIG_SCULL的选择可能是y或者mscull.o对象文件将会根据选项被添加到obj-yobj-m列表当中去,接着这个源文件就会被编译成内核或者模块。如果我们没有选择CONFIG_SCULL的值,那么整个scull.o对象文件将根本不会被编译。

现在你应该了解怎么让源文件有条件地包含到源码包中。最后一个难题就是怎么有条件地编译内核源文件,通过使用autoconfig.h中的宏定义,这个难题将会被很轻松的解决。

作业


把我们自己编写的驱动程序加入Linux的源码树。

翻译信息


原文档:<北京交通大学 杨武杰老师课件>

原作者:杨武杰

译者:刘瀚文

0 0
原创粉丝点击